From fc195be7b7e7dbd5833028f42834f25406a1c264 Mon Sep 17 00:00:00 2001 From: Charles Packer Date: Mon, 22 Dec 2025 18:39:12 -0800 Subject: [PATCH] fix: fix/skip interrupt banner for server tools (#353) Co-authored-by: Letta --- src/cli/App.tsx | 16 +++++++++++++--- src/cli/helpers/accumulator.ts | 6 +++++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/cli/App.tsx b/src/cli/App.tsx index b77ad6e..b2a6cd6 100644 --- a/src/cli/App.tsx +++ b/src/cli/App.tsx @@ -836,6 +836,7 @@ export default function App({ ); // Core streaming function - iterative loop that processes conversation turns + // biome-ignore lint/correctness/useExhaustiveDependencies: refs read .current dynamically const processConversation = useCallback( async ( initialInput: Array, @@ -849,6 +850,12 @@ export default function App({ return; } + // Guard against concurrent processConversation calls + // This can happen if user submits two messages in quick succession + if (streamingRef.current) { + return; + } + setStreaming(true); abortControllerRef.current = new AbortController(); @@ -1532,10 +1539,13 @@ export default function App({ // Set cancellation flag to prevent processConversation from starting userCancelledRef.current = true; - // Stop streaming and show error message + // Stop streaming and show error message (unless tool calls were cancelled, + // since the tool result will show "Interrupted by user") setStreaming(false); - markIncompleteToolsAsCancelled(buffersRef.current); - appendError("Stream interrupted by user"); + const toolsCancelled = markIncompleteToolsAsCancelled(buffersRef.current); + if (!toolsCancelled) { + appendError("Stream interrupted by user"); + } refreshDerived(); // Clear any pending approvals since we're cancelling diff --git a/src/cli/helpers/accumulator.ts b/src/cli/helpers/accumulator.ts index 8b57e2c..72ea7f3 100644 --- a/src/cli/helpers/accumulator.ts +++ b/src/cli/helpers/accumulator.ts @@ -170,11 +170,13 @@ export function markCurrentLineAsFinished(b: Buffers) { /** * Mark any incomplete tool calls as cancelled when stream is interrupted. * This prevents blinking tool calls from staying in progress state. + * @returns true if any tool calls were marked as cancelled */ -export function markIncompleteToolsAsCancelled(b: Buffers) { +export function markIncompleteToolsAsCancelled(b: Buffers): boolean { // Mark buffer as interrupted to skip stale throttled refreshes b.interrupted = true; + let anyToolsCancelled = false; for (const [id, line] of b.byId.entries()) { if (line.kind === "tool_call" && line.phase !== "finished") { const updatedLine = { @@ -184,10 +186,12 @@ export function markIncompleteToolsAsCancelled(b: Buffers) { resultText: INTERRUPTED_BY_USER, }; b.byId.set(id, updatedLine); + anyToolsCancelled = true; } } // Also mark any streaming assistant/reasoning lines as finished markCurrentLineAsFinished(b); + return anyToolsCancelled; } type ToolCallLine = Extract;