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);
|
const terminal = item.kind === "error" ? "failed" : (item.kind === "agent-status" && item.status === "idle" ? "completed" : undefined);
|
||||||
if (!terminal) return;
|
if (!terminal) return;
|
||||||
const executionID = item.kind === "agent-status" ? item.execution_id : undefined;
|
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);
|
const summary = executionProgress.finish(executionID, terminal, now);
|
||||||
if (!summary) return;
|
if (!summary) return;
|
||||||
runtime.replaceExecutionSummaries(executionProgress.snapshot());
|
runtime.replaceExecutionSummaries(executionProgress.snapshot());
|
||||||
|
|||||||
@@ -43,4 +43,15 @@ describe("ExecutionProgressState", () => {
|
|||||||
expect.objectContaining({ id: "exec_002", status: "completed" }),
|
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);
|
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. */
|
/** Ends only the matching running execution; never steals another turn's card. */
|
||||||
public finish(id: string | undefined, status: "completed" | "failed", now: string): ExecutionProgressSummary | undefined {
|
public finish(id: string | undefined, status: "completed" | "failed", now: string): ExecutionProgressSummary | undefined {
|
||||||
if (!id) return this.finishActive(status, now);
|
if (!id) return this.finishActive(status, now);
|
||||||
|
|||||||
Reference in New Issue
Block a user