From f6d7fda60a2961664fa7ed70f2f3bb1973eddb21 Mon Sep 17 00:00:00 2001 From: Haze <709547807@qq.com> Date: Mon, 20 Apr 2026 18:37:19 +0800 Subject: [PATCH] refactor(gateway): remove phase completion timer logic and update run completion handling Eliminate the phase completion timer and its associated logic from the Gateway. The handling of run completion is now solely based on Gateway phase events and streaming final events. This change simplifies the code and ensures that the state transitions are more reliable, as run completion is no longer inferred from the timer. Additionally, update the runtime send actions to finalize the sending state immediately after the chat.send RPC completes, ensuring accurate state management during agent conversations. --- src/stores/chat/runtime-send-actions.ts | 15 +++++++-- src/stores/gateway.ts | 44 +++---------------------- 2 files changed, 18 insertions(+), 41 deletions(-) diff --git a/src/stores/chat/runtime-send-actions.ts b/src/stores/chat/runtime-send-actions.ts index b268786..51a13a4 100644 --- a/src/stores/chat/runtime-send-actions.ts +++ b/src/stores/chat/runtime-send-actions.ts @@ -223,8 +223,19 @@ export function createRuntimeSendActions(set: ChatSet, get: ChatGet): Pick | null = null; -const PHASE_COMPLETION_GRACE_MS = 5_000; -function clearPhaseCompletionTimer(): void { - if (_phaseCompletionTimer) { - clearTimeout(_phaseCompletionTimer); - _phaseCompletionTimer = null; - } -} - interface GatewayHealth { ok: boolean; error?: string; @@ -144,7 +130,6 @@ function handleGatewayNotification(notification: { method?: string; params?: Rec if (hasChatData) { // Any streaming data cancels the phase-completion grace timer — the // run is still producing output (or a new sub-run has started). - clearPhaseCompletionTimer(); const normalizedEvent: Record = { ...data, runId: p.runId ?? data.runId, @@ -166,7 +151,6 @@ function handleGatewayNotification(notification: { method?: string; params?: Rec const runId = p.runId ?? data.runId; const sessionKey = p.sessionKey ?? data.sessionKey; if (phase === 'started' && runId != null && sessionKey != null) { - clearPhaseCompletionTimer(); import('./chat') .then(({ useChatStore }) => { const state = useChatStore.getState(); @@ -206,29 +190,11 @@ function handleGatewayNotification(notification: { method?: string; params?: Rec if (matchesCurrentSession || matchesActiveRun) { maybeLoadHistory(state); } - if ((matchesCurrentSession || matchesActiveRun) && state.sending) { - // The Gateway sends phase "end" after each tool-execution round, - // not only when the entire conversation finishes. Delay the - // sending=false so a subsequent sub-run's "started" event or - // streaming delta can cancel it and keep the UI in the active state. - clearPhaseCompletionTimer(); - _phaseCompletionTimer = setTimeout(() => { - _phaseCompletionTimer = null; - const current = useChatStore.getState(); - // Only finalize if still in the same run and no new streaming data arrived. - if (current.sending && !current.streamingMessage) { - useChatStore.setState({ - sending: false, - activeRunId: null, - pendingFinal: false, - lastUserMessageAt: null, - error: null, - }); - // Reload history to get the final state. - maybeLoadHistory(current); - } - }, PHASE_COMPLETION_GRACE_MS); - } + // Note: we do NOT set sending=false here. The Gateway sends + // phase "end" after each tool-execution round (sub-run), not only + // when the entire conversation finishes. Run completion is + // determined by the chat.send RPC returning (runtime-send-actions) + // or a streaming "final" event with output (runtime-event-handlers). }) .catch(() => {}); }