From b1fc1dd9233f2e0ba8564e6227977659da38da52 Mon Sep 17 00:00:00 2001 From: ciregenz Date: Thu, 18 Jun 2026 05:02:19 -0700 Subject: [PATCH] [eric] chat: on stop, promote the in-flight assistant text to a message immediately so the partial doesn't blink out during the SDK cancel lag --- frontend/src/shared/ws/WebSocketManager.ts | 24 ++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/frontend/src/shared/ws/WebSocketManager.ts b/frontend/src/shared/ws/WebSocketManager.ts index dd90844f..970664ac 100644 --- a/frontend/src/shared/ws/WebSocketManager.ts +++ b/frontend/src/shared/ws/WebSocketManager.ts @@ -494,6 +494,30 @@ class WebSocketManager { // before its own aux call lands. if (session_id && (data.status === 'completed' || data.status === 'error' || data.status === 'stopped')) { store.dispatch(clearTurnLabel(session_id)); + // Mid-stream stop: the backend keeps the partial reply, but + // cancelling the SDK can lag several seconds, so its authoritative + // agent:message lands late. Promote the in-flight assistant text to + // a real message NOW so it doesn't blink out the instant we clear + // the overlay; the late agent:message (same id) just refreshes it. + if (data.status === 'stopped') { + const entry = store.getState().streaming.bySession[session_id]; + if (entry && entry.role === 'assistant' && entry.content) { + const sess = store.getState().agents.sessions[session_id]; + if (!sess?.messages?.some((m) => m.id === entry.id)) { + store.dispatch(addMessage({ + sessionId: session_id, + message: { + id: entry.id, + role: 'assistant', + content: entry.content, + timestamp: new Date().toISOString(), + branch_id: sess?.active_branch_id ?? 'main', + parent_id: null, + }, + })); + } + } + } store.dispatch(clearStreamingForSession(session_id)); } }