From c47346cc6237044ecb60ff22c4011d89744af581 Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Fri, 22 May 2026 20:54:19 +0900 Subject: feat: message queue/interrupt system, CORS fix, mobile fixes, chat splitting - Add message queue allowing users to send messages while agent is running - Queue messages are injected into tool results as [USER INTERRUPT] - Retrieve tool interrupted via Promise.race when user message arrives - Queued messages show with 'queued' badge and cancel button - Consumed messages repositioned and chat splits at interrupt point - New assistant message block created after interrupt for clean flow - Add POST /chat/cancel endpoint for cancelling queued messages - Fix CORS to allow any origin (Tailscale/LAN access) - Fix crypto.randomUUID fallback for non-secure contexts (HTTP) - Fix frontend API URL derivation from page hostname - Auto-create DB tab if missing on processMessage (foreign key fix) - Add error logging to processMessage catch block - Fix working directory input sync on agent switch - Fix agent mode button to re-apply agent settings --- .../frontend/src/lib/components/ChatInput.svelte | 5 +- .../frontend/src/lib/components/ChatMessage.svelte | 29 ++- .../frontend/src/lib/components/ChatPanel.svelte | 3 +- .../src/lib/components/ModelSelector.svelte | 22 +- packages/frontend/src/lib/config.ts | 12 +- packages/frontend/src/lib/tabs.svelte.ts | 260 +++++++++++++++++++-- packages/frontend/src/lib/types.ts | 11 +- 7 files changed, 308 insertions(+), 34 deletions(-) (limited to 'packages/frontend') diff --git a/packages/frontend/src/lib/components/ChatInput.svelte b/packages/frontend/src/lib/components/ChatInput.svelte index 21c85fe..dac7a3a 100644 --- a/packages/frontend/src/lib/components/ChatInput.svelte +++ b/packages/frontend/src/lib/components/ChatInput.svelte @@ -3,7 +3,6 @@ import { tabStore } from "../tabs.svelte.js"; let inputEl: HTMLInputElement | undefined; let inputValue = $state(""); -const isDisabled = $derived((tabStore.activeTab?.agentStatus ?? "idle") === "running"); $effect(() => { inputEl?.focus(); @@ -18,7 +17,7 @@ function handleKeydown(e: KeyboardEvent) { function submit() { const text = inputValue.trim(); - if (!text || isDisabled) return; + if (!text) return; inputValue = ""; tabStore.sendMessage(text); } @@ -36,7 +35,7 @@ function submit() { + + {/if} {/if} diff --git a/packages/frontend/src/lib/components/ChatPanel.svelte b/packages/frontend/src/lib/components/ChatPanel.svelte index 8c92e4f..82bd4ee 100644 --- a/packages/frontend/src/lib/components/ChatPanel.svelte +++ b/packages/frontend/src/lib/components/ChatPanel.svelte @@ -8,6 +8,7 @@ let userScrolledUp = $state(false); let isAutoScrolling = false; const messages = $derived(tabStore.activeTab?.messages ?? []); +const activeTabId = $derived(tabStore.activeTab?.id); function isNearBottom(el: HTMLElement): boolean { return el.scrollHeight - el.scrollTop - el.clientHeight < 64; @@ -60,7 +61,7 @@ $effect(() => { {/if} {#each messages as message (message.id)} - + {/each} diff --git a/packages/frontend/src/lib/components/ModelSelector.svelte b/packages/frontend/src/lib/components/ModelSelector.svelte index 21364f9..5949e71 100644 --- a/packages/frontend/src/lib/components/ModelSelector.svelte +++ b/packages/frontend/src/lib/components/ModelSelector.svelte @@ -175,7 +175,7 @@ const modelCache = new Map(); class="input input-bordered input-sm font-mono text-xs flex-1" placeholder="default (project root)" value={workingDirectory ?? ""} - oninput={(e) => { + onchange={(e) => { const val = e.currentTarget.value.trim(); onWorkingDirectoryChange(val || null); }} @@ -202,7 +202,19 @@ const modelCache = new Map(); @@ -253,7 +265,11 @@ const modelCache = new Map(); {#each agents as agent (agent.slug + ":" + agent.scope)}