feat: add chat modes and interrupt feature (#46)
Security Scan and Upload / Security & DefectDojo Upload (push) Successful in 52s
CI / Lint & Test (push) Successful in 14m15s
CI / Build Linux (push) Successful in 16m37s
CI / Build Windows (cross-compile) (push) Successful in 26m35s

### Explanation

_No response_

### Issue

Closes #40

### Attestations

- [ ] I have read and agree to the [Code of Conduct](https://docs.nhcarrigan.com/community/coc/)
- [ ] I have read and agree to the [Community Guidelines](https://docs.nhcarrigan.com/community/guide/).
- [ ] My contribution complies with the [Contributor Covenant](https://docs.nhcarrigan.com/dev/covenant/).

### Dependencies

- [ ] I have pinned the dependencies to a specific patch version.

### Style

- [ ] I have run the linter and resolved any errors.
- [ ] My pull request uses an appropriate title, matching the conventional commit standards.
- [ ] My scope of feat/fix/chore/etc. correctly matches the nature of changes in my pull request.

### Tests

- [ ] My contribution adds new code, and I have added tests to cover it.
- [ ] My contribution modifies existing code, and I have updated the tests to reflect these changes.
- [ ] All new and existing tests pass locally with my changes.
- [ ] Code coverage remains at or above the configured threshold.

### Documentation

_No response_

### Versioning

_No response_

Reviewed-on: #46
Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com>
Co-committed-by: Naomi Carrigan <commits@nhcarrigan.com>
This commit was merged in pull request #46.
This commit is contained in:
2026-01-20 08:33:39 -08:00
committed by Naomi Carrigan
parent 70fcaa8650
commit 2d3adcab1c
11 changed files with 521 additions and 28 deletions
+34
View File
@@ -1,5 +1,6 @@
import { writable, derived } from "svelte/store";
import type { ConnectionStatus, PermissionRequest } from "$lib/types/messages";
import { characterState } from "$lib/stores/character";
export interface TerminalLine {
id: string;
@@ -18,6 +19,8 @@ function createClaudeStore() {
const isProcessing = writable<boolean>(false);
const grantedTools = writable<Set<string>>(new Set());
const pendingRetryMessage = writable<string | null>(null);
const shouldRestoreHistory = writable<boolean>(false);
const savedConversationHistory = writable<string | null>(null);
let lineIdCounter = 0;
@@ -34,6 +37,8 @@ function createClaudeStore() {
isProcessing: { subscribe: isProcessing.subscribe },
grantedTools: { subscribe: grantedTools.subscribe },
pendingRetryMessage: { subscribe: pendingRetryMessage.subscribe },
shouldRestoreHistory: { subscribe: shouldRestoreHistory.subscribe },
savedConversationHistory: { subscribe: savedConversationHistory.subscribe },
setConnectionStatus: (status: ConnectionStatus) => connectionStatus.set(status),
setSessionId: (id: string | null) => sessionId.set(id),
@@ -106,6 +111,21 @@ function createClaudeStore() {
setPendingRetryMessage: (message: string | null) => pendingRetryMessage.set(message),
setShouldRestoreHistory: (should: boolean) => shouldRestoreHistory.set(should),
setSavedConversationHistory: (history: string | null) => savedConversationHistory.set(history),
getShouldRestoreHistory: (): boolean => {
let should = false;
shouldRestoreHistory.subscribe((s) => (should = s))();
return should;
},
getSavedConversationHistory: (): string | null => {
let history: string | null = null;
savedConversationHistory.subscribe((h) => (history = h))();
return history;
},
reset: () => {
connectionStatus.set("disconnected");
sessionId.set(null);
@@ -115,6 +135,8 @@ function createClaudeStore() {
isProcessing.set(false);
grantedTools.set(new Set());
pendingRetryMessage.set(null);
shouldRestoreHistory.set(false);
savedConversationHistory.set(null);
},
};
}
@@ -125,3 +147,15 @@ export const hasPermissionPending = derived(
claudeStore.pendingPermission,
($permission) => $permission !== null
);
// Derived store to check if Claude is currently processing (can be interrupted)
export const isClaudeProcessing = derived(
[claudeStore.connectionStatus, characterState],
([$connectionStatus, $characterState]) => {
// Must be connected and in one of the processing states
return (
$connectionStatus === "connected" &&
["thinking", "typing", "searching", "coding", "mcp"].includes($characterState)
);
}
);
+29
View File
@@ -0,0 +1,29 @@
// Separate module for history restoration to ensure persistence across reconnects
let shouldRestore = false;
let savedHistory: string | null = null;
export function setShouldRestoreHistory(should: boolean) {
shouldRestore = should;
console.log("Setting shouldRestoreHistory to:", should);
}
export function setSavedHistory(history: string | null) {
savedHistory = history;
console.log("Setting savedHistory, length:", history?.length || 0);
}
export function getShouldRestoreHistory(): boolean {
console.log("Getting shouldRestoreHistory:", shouldRestore);
return shouldRestore;
}
export function getSavedHistory(): string | null {
console.log("Getting savedHistory, length:", savedHistory?.length || 0);
return savedHistory;
}
export function clearHistoryRestore() {
console.log("Clearing history restore flags");
shouldRestore = false;
savedHistory = null;
}
+20
View File
@@ -0,0 +1,20 @@
import { writable } from "svelte/store";
// Default to chat mode
const messageModeStore = writable<string>("chat");
export const messageMode = {
subscribe: messageModeStore.subscribe,
set: (mode: string) => {
console.log("Setting message mode to:", mode);
messageModeStore.set(mode);
},
reset: () => messageModeStore.set("chat"),
};
// Helper to get current mode
export function getCurrentMode(): string {
let currentMode = "chat";
messageMode.subscribe((mode) => (currentMode = mode))();
return currentMode;
}