diff --git a/packages/server/src/server/agent/providers/codex-app-server-agent.test.ts b/packages/server/src/server/agent/providers/codex-app-server-agent.test.ts index 7a585f0ce..4c05495fa 100644 --- a/packages/server/src/server/agent/providers/codex-app-server-agent.test.ts +++ b/packages/server/src/server/agent/providers/codex-app-server-agent.test.ts @@ -3144,6 +3144,115 @@ describe("Codex app-server provider", () => { ]); }); + test("does not import a parent interaction from child history as another sub-agent", async () => { + const appServer = createFakeCodexAppServer({ + "thread/read": (params) => { + const threadId = (params as { threadId?: string }).threadId; + if (threadId === "test-thread") { + return { + thread: { + turns: [ + { + items: [ + { + type: "subAgentActivity", + id: "child-started", + kind: "started", + agentThreadId: "child-thread", + agentPath: "/root/child", + }, + ], + }, + ], + }, + }; + } + return { + thread: { + turns: [ + { + items: [ + { + type: "subAgentActivity", + id: "parent-interacted", + kind: "interacted", + agentThreadId: "test-thread", + agentPath: "/root", + }, + ], + }, + ], + }, + }; + }, + }); + const session = new CodexAppServerAgentSession( + createConfig({ cwd: "/workspace/project" }), + { sessionId: "test-thread" }, + createTestLogger(), + async () => appServer.child, + ); + + try { + await session.connect(); + + const providerSubagentIds = new Set(); + for await (const event of session.streamHistory()) { + if (event.type === "provider_subagent" && event.event.type === "upsert") { + providerSubagentIds.add(event.event.id); + } + } + + expect(providerSubagentIds).toEqual(new Set(["child-thread"])); + appServer.assertNoErrors(); + } finally { + await session.close(); + } + }); + + test("does not register a parent interaction on a child thread as another sub-agent", async () => { + const appServer = createFakeCodexAppServer(); + const session = new CodexAppServerAgentSession( + createConfig({ cwd: "/workspace/project" }), + null, + createTestLogger(), + async () => appServer.child, + ); + const providerSubagentIds = new Set(); + session.subscribe((event) => { + if (event.type === "provider_subagent" && event.event.type === "upsert") { + providerSubagentIds.add(event.event.id); + } + }); + + try { + const resultPromise = session.run("Delegate the investigation."); + await appServer.waitForTurnStart(); + + const child = waitForProviderSubagent(session, "child-thread"); + appServer.startsSubAgent({ + callId: "child-started", + threadId: "child-thread", + agentPath: "/root/child", + }); + await child; + appServer.beginsSubAgentActivity({ + callId: "parent-interacted", + threadId: "thread-1", + parentThreadId: "child-thread", + kind: "interacted", + agentPath: "/root", + }); + appServer.completeTurn(); + await resultPromise; + + expect(providerSubagentIds).toEqual(new Set(["child-thread"])); + appServer.assertNoErrors(); + } finally { + await session.close(); + } + }); + test("uses Codex turn timestamps for timestamp-less persisted history items", async () => { const session = createSession(); session.client = { 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 b1d12194c..165e1a30d 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 @@ -3503,7 +3503,7 @@ export class CodexAppServerAgentSession implements AgentSession { rootRoutes: readonly PersistedSubAgentRoute[], ): Promise { const queue = rootRoutes.map((route) => ({ route, parentCallId: null as string | null })); - const visitedThreadIds = new Set(); + const visitedThreadIds = new Set(this.currentThreadId ? [this.currentThreadId] : []); while (queue.length > 0 && visitedThreadIds.size < 100) { const next = queue.shift(); if (!next || visitedThreadIds.has(next.route.childThreadId)) { @@ -4831,7 +4831,7 @@ export class CodexAppServerAgentSession implements AgentSession { : null; const childThreadIds = Array.from( new Set(agentThreadId ? [...receiverThreadIds, agentThreadId] : receiverThreadIds), - ); + ).filter((threadId) => threadId !== this.currentThreadId); for (const receiverThreadId of childThreadIds) { this.subAgentCallIdByChildThreadId.set(receiverThreadId, timelineItem.callId); state.childThreadIds.add(receiverThreadId);