diff --git a/CHANGELOG.md b/CHANGELOG.md index 04a687300..24f38588d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ Docs: https://docs.clawd.bot - MS Teams (plugin): remove `.default` suffix from Graph scopes to avoid double-appending. (#1507) Thanks @Evizero. - Browser: keep extension relay tabs controllable when the extension reuses a session id after switching tabs. (#1160) - TUI: track active run ids from chat events so tool/lifecycle updates show for non-TUI runs. (#1567) Thanks @vignesh07. +- TUI: ignore lifecycle updates from non-active runs to keep status accurate. (#1567) Thanks @vignesh07. ## 2026.1.22 diff --git a/src/tui/tui-event-handlers.test.ts b/src/tui/tui-event-handlers.test.ts index 226ca3229..ee661da39 100644 --- a/src/tui/tui-event-handlers.test.ts +++ b/src/tui/tui-event-handlers.test.ts @@ -184,4 +184,33 @@ describe("tui-event-handlers: handleAgentEvent", () => { expect(chatLog.startTool).not.toHaveBeenCalled(); expect(tui.requestRender).not.toHaveBeenCalled(); }); + + it("ignores lifecycle updates for non-active runs in the same session", () => { + const state = makeState({ activeChatRunId: "run-active" }); + const { chatLog, tui, setActivityStatus } = makeContext(state); + const { handleChatEvent, handleAgentEvent } = createEventHandlers({ + chatLog: chatLog as any, + tui: tui as any, + state, + setActivityStatus, + }); + + handleChatEvent({ + runId: "run-other", + sessionKey: state.currentSessionKey, + state: "delta", + message: { content: "hello" }, + }); + setActivityStatus.mockClear(); + tui.requestRender.mockClear(); + + handleAgentEvent({ + runId: "run-other", + stream: "lifecycle", + data: { phase: "end" }, + }); + + expect(setActivityStatus).not.toHaveBeenCalled(); + expect(tui.requestRender).not.toHaveBeenCalled(); + }); }); diff --git a/src/tui/tui-event-handlers.ts b/src/tui/tui-event-handlers.ts index 1d99c4414..bc857c704 100644 --- a/src/tui/tui-event-handlers.ts +++ b/src/tui/tui-event-handlers.ts @@ -125,7 +125,8 @@ export function createEventHandlers(context: EventHandlerContext) { syncSessionKey(); // Agent events (tool streaming, lifecycle) are emitted per-run. Filter against the // active chat run id, not the session id. - if (evt.runId !== state.activeChatRunId && !sessionRuns.has(evt.runId)) return; + const isActiveRun = evt.runId === state.activeChatRunId; + if (!isActiveRun && !sessionRuns.has(evt.runId)) return; if (evt.stream === "tool") { const data = evt.data ?? {}; const phase = asString(data.phase, ""); @@ -147,6 +148,7 @@ export function createEventHandlers(context: EventHandlerContext) { return; } if (evt.stream === "lifecycle") { + if (!isActiveRun) return; const phase = typeof evt.data?.phase === "string" ? evt.data.phase : ""; if (phase === "start") setActivityStatus("running"); if (phase === "end") setActivityStatus("idle");