diff --git a/packages/server/src/server/agent/providers/codex-app-server-agent.ts b/packages/server/src/server/agent/providers/codex-app-server-agent.ts index 81c2e06b37..179e0db8c6 100644 --- a/packages/server/src/server/agent/providers/codex-app-server-agent.ts +++ b/packages/server/src/server/agent/providers/codex-app-server-agent.ts @@ -1864,7 +1864,7 @@ function readCodexSubAgentActivity(item: unknown): CodexSubAgentActivity | null interface CodexCollabAgentUpdate { threadId: string; status: ToolCallTimelineItem["status"]; - shutdown: boolean; + preservesTerminal: boolean; reopen: boolean; } @@ -1891,11 +1891,8 @@ function readCodexCollabAgentControl(item: unknown): CodexCollabAgentControl | n return [ { threadId, - status: - record.tool === "closeAgent" && record.status === "completed" && status === "running" - ? "canceled" - : status, - shutdown: state.status === "shutdown", + status: record.tool === "closeAgent" && status === "running" ? "canceled" : status, + preservesTerminal: state.status === "shutdown" || record.tool === "closeAgent", reopen: record.tool === "sendInput" && record.status === "completed", }, ]; @@ -1916,7 +1913,7 @@ function resolveCodexCollabAgentUpdate( ): ToolCallTimelineItem["status"] { if ( isTerminalSubAgentStatus(previous) && - (update.shutdown || (update.status === "running" && !update.reopen)) + (update.preservesTerminal || (update.status === "running" && !update.reopen)) ) { return previous; } diff --git a/packages/server/src/server/agent/providers/codex/collab-replay.test.ts b/packages/server/src/server/agent/providers/codex/collab-replay.test.ts index da86b1eb79..1773600bdb 100644 --- a/packages/server/src/server/agent/providers/codex/collab-replay.test.ts +++ b/packages/server/src/server/agent/providers/codex/collab-replay.test.ts @@ -86,6 +86,35 @@ describe("Codex collaboration history", () => { ).toEqual([{ id: "first", status: "canceled" }]); }); + test("stops counting an errored child whose close record reports failed", async () => { + const history = await replay([ + collab("spawnAgent", { first: "pendingInit" }, "spawn-first"), + { ...collab("closeAgent", { first: "errored" }), status: "failed" }, + ]); + expect( + history.flatMap((event) => + event.type === "provider_subagent" && event.event.type === "upsert" + ? [{ id: event.event.id, status: event.event.status }] + : [], + ), + ).toEqual([{ id: "first", status: "canceled" }]); + }); + + test("preserves completion when a close carries an older running snapshot", async () => { + const history = await replay([ + collab("spawnAgent", { first: "pendingInit" }, "spawn-first"), + collab("wait", { first: "completed" }), + collab("closeAgent", { first: "running" }), + ]); + expect( + history.flatMap((event) => + event.type === "provider_subagent" && event.event.type === "upsert" + ? [{ id: event.event.id, status: event.event.status }] + : [], + ), + ).toEqual([{ id: "first", status: "completed" }]); + }); + test.each([ ["shutdown", "canceled"], ["notFound", "failed"],