diff --git a/tauri/src/main.ts b/tauri/src/main.ts index 9b64b89..733c8c0 100644 --- a/tauri/src/main.ts +++ b/tauri/src/main.ts @@ -480,6 +480,13 @@ function projectExecutionProgress(item: ConversationItem, now: string): void { const terminal = item.kind === "error" ? "failed" : (item.kind === "agent-status" && item.status === "idle" ? "completed" : undefined); if (!terminal) return; const executionID = item.kind === "agent-status" ? item.execution_id : undefined; + if (!executionID) { + const summaries = executionProgress.finishAllActive(terminal, now); + if (summaries.length === 0) return; + runtime.replaceExecutionSummaries(executionProgress.snapshot()); + for (const summary of summaries) rendererContext.renderExecutionSummary(summary); + return; + } const summary = executionProgress.finish(executionID, terminal, now); if (!summary) return; runtime.replaceExecutionSummaries(executionProgress.snapshot()); diff --git a/tauri/src/runtime/coordination/execution-progress-state.test.ts b/tauri/src/runtime/coordination/execution-progress-state.test.ts index 27db78c..05baeb7 100644 --- a/tauri/src/runtime/coordination/execution-progress-state.test.ts +++ b/tauri/src/runtime/coordination/execution-progress-state.test.ts @@ -43,4 +43,15 @@ describe("ExecutionProgressState", () => { expect.objectContaining({ id: "exec_002", status: "completed" }), ]); }); + + it("finishes every live summary when the turn terminal has no execution id", () => { + const state = new ExecutionProgressState(); + state.begin("exec_001", T0); + state.begin("exec_002", T1); + expect(state.finishAllActive("completed", T1)).toEqual([ + expect.objectContaining({ id: "exec_001", status: "completed", finished_at: T1 }), + expect.objectContaining({ id: "exec_002", status: "completed", finished_at: T1 }), + ]); + expect(state.snapshot().every(summary => summary.status === "completed")).toBe(true); + }); }); diff --git a/tauri/src/runtime/coordination/execution-progress-state.ts b/tauri/src/runtime/coordination/execution-progress-state.ts index ca00afa..e1f6b19 100644 --- a/tauri/src/runtime/coordination/execution-progress-state.ts +++ b/tauri/src/runtime/coordination/execution-progress-state.ts @@ -72,6 +72,24 @@ export class ExecutionProgressState { return copy(active); } + /** + * A status terminal without an execution_id closes the current Agent turn. + * If several execution summaries were opened during that turn, all of them + * must leave the live state together; closing only the first leaves a card + * ticking forever in the renderer. + */ + public finishAllActive(status: "completed" | "failed", now: string): readonly ExecutionProgressSummary[] { + const finished: ExecutionProgressSummary[] = []; + for (const summary of this.summaries) { + if (summary.status !== "running") continue; + summary.status = status; + summary.updated_at = now; + summary.finished_at = now; + finished.push(copy(summary)); + } + return finished; + } + /** Ends only the matching running execution; never steals another turn's card. */ public finish(id: string | undefined, status: "completed" | "failed", now: string): ExecutionProgressSummary | undefined { if (!id) return this.finishActive(status, now);