fix(chat): close all active execution summaries on turn end
This commit is contained in:
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user