diff --git a/src/agents/subagent-registry.test.ts b/src/agents/subagent-registry.test.ts index d260b524324c..a8efe947937f 100644 --- a/src/agents/subagent-registry.test.ts +++ b/src/agents/subagent-registry.test.ts @@ -3373,6 +3373,61 @@ describe("subagent registry seam flow", () => { }); }); + it("wakes a sessions_yield-paused parent when pending descendants settle", async () => { + mocks.loadSessionStore.mockReturnValue({ + "agent:main:subagent:parent": { + sessionId: "sess-parent", + updatedAt: 1, + }, + "agent:main:subagent:child": { + sessionId: "sess-child", + updatedAt: 1, + }, + }); + + mod.addSubagentRunForTests({ + runId: "run-yielded-parent", + childSessionKey: "agent:main:subagent:parent", + requesterSessionKey: "agent:main:main", + requesterDisplayKey: "main", + task: "yielded parent waiting on descendants", + cleanup: "keep", + createdAt: Date.parse("2026-06-26T02:17:00Z"), + startedAt: Date.parse("2026-06-26T02:18:00Z"), + endedAt: Date.parse("2026-06-26T02:19:00Z"), + pauseReason: "sessions_yield", + wakeOnDescendantSettle: true, + cleanupHandled: false, + cleanupCompletedAt: undefined, + }); + + mod.registerSubagentRun({ + runId: "run-yielded-child-finished", + childSessionKey: "agent:main:subagent:child", + requesterSessionKey: "agent:main:subagent:parent", + requesterDisplayKey: "parent", + task: "descendant settles after yield", + cleanup: "keep", + }); + + await waitForFast(() => { + expect(mocks.runSubagentAnnounceFlow).toHaveBeenCalledTimes(2); + }); + expectRecordFields( + getMockCallArg(mocks.runSubagentAnnounceFlow, 0, 0, "child finished announce"), + { childRunId: "run-yielded-child-finished" }, + "child finished announce params", + ); + expectRecordFields( + getMockCallArg(mocks.runSubagentAnnounceFlow, 1, 0, "yielded parent wake announce"), + { + childRunId: "run-yielded-parent", + wakeOnDescendantSettle: true, + }, + "yielded parent wake announce params", + ); + }); + it("loads runtime plugins before emitting killed subagent ended hooks", async () => { const endedHookRunner = { hasHooks: (hookName: string) => hookName === "subagent_ended", diff --git a/src/agents/subagent-registry.ts b/src/agents/subagent-registry.ts index 1d8536bd9188..feeff7f2d6cb 100644 --- a/src/agents/subagent-registry.ts +++ b/src/agents/subagent-registry.ts @@ -626,7 +626,9 @@ function resumeSubagentRun(runId: string) { if (typeof entry.endedAt === "number" && isDeliverySuspended(entry)) { return; } - if (entry.pauseReason === "sessions_yield") { + // Yielded runs stay paused until explicitly steered, except orchestrators + // waiting on descendants: their settle retry must reach the wake path. + if (entry.pauseReason === "sessions_yield" && entry.wakeOnDescendantSettle !== true) { return; } // Skip entries that have exhausted their retry budget or expired (#18264).