diff --git a/src/agents/subagent-spawn.depth-limits.test.ts b/src/agents/subagent-spawn.depth-limits.test.ts index 88fb51bfb5a..72dc4469bad 100644 --- a/src/agents/subagent-spawn.depth-limits.test.ts +++ b/src/agents/subagent-spawn.depth-limits.test.ts @@ -18,6 +18,13 @@ const hoisted = vi.hoisted(() => ({ let spawnSubagentDirect: typeof import("./subagent-spawn.js").spawnSubagentDirect; let persistedStore: Record> | undefined; +type SpawnResult = Awaited>; +type AcceptedSpawnResult = SpawnResult & { + childSessionKey: string; + runId: string; + status: "accepted"; +}; + function createDepthLimitConfig(subagents?: Record) { return createSubagentSpawnTestConfig("/tmp/workspace-main", { agents: { @@ -45,6 +52,24 @@ async function spawnFrom(sessionKey: string, params?: Record) { ); } +function expectForbidden(result: SpawnResult, error: string) { + expect(result.status).toBe("forbidden"); + if (result.status !== "forbidden") { + throw new Error(`Expected forbidden spawn result, received ${result.status}`); + } + expect(result.error).toBe(error); +} + +function expectAccepted(result: SpawnResult, runId: string): AcceptedSpawnResult { + expect(result.status).toBe("accepted"); + if (result.status !== "accepted") { + throw new Error(`Expected accepted spawn result, received ${result.status}`); + } + expect(result.runId).toBe(runId); + expect(typeof result.childSessionKey).toBe("string"); + return result as AcceptedSpawnResult; +} + describe("subagent spawn depth + child limits", () => { beforeAll(async () => { ({ spawnSubagentDirect } = await loadSubagentSpawnModuleForTest({ @@ -80,10 +105,10 @@ describe("subagent spawn depth + child limits", () => { const result = await spawnFrom("agent:main:subagent:parent"); - expect(result).toMatchObject({ - status: "forbidden", - error: "sessions_spawn is not allowed at this depth (current depth: 1, max: 1)", - }); + expectForbidden( + result, + "sessions_spawn is not allowed at this depth (current depth: 1, max: 1)", + ); }); it("allows depth-1 callers when maxSpawnDepth is 2 and patches child capabilities", async () => { @@ -92,19 +117,18 @@ describe("subagent spawn depth + child limits", () => { const result = await spawnFrom("agent:main:subagent:parent"); - expect(result).toMatchObject({ - status: "accepted", - childSessionKey: expect.stringMatching(/^agent:main:subagent:/), - runId: "run-1", - }); + const accepted = expectAccepted(result, "run-1"); + expect(accepted.childSessionKey).toMatch(/^agent:main:subagent:/); - const childSession = persistedStore?.[result.childSessionKey as string]; - expect(childSession).toMatchObject({ - spawnedBy: "agent:main:subagent:parent", - spawnDepth: 2, - subagentRole: "leaf", - subagentControlScope: "none", - }); + const childSession = persistedStore?.[accepted.childSessionKey]; + expect(childSession).toBeDefined(); + if (!childSession) { + throw new Error("Expected persisted child session"); + } + expect(childSession.spawnedBy).toBe("agent:main:subagent:parent"); + expect(childSession.spawnDepth).toBe(2); + expect(childSession.subagentRole).toBe("leaf"); + expect(childSession.subagentControlScope).toBe("none"); expect(typeof childSession?.spawnedWorkspaceDir).toBe("string"); }); @@ -114,10 +138,10 @@ describe("subagent spawn depth + child limits", () => { const result = await spawnFrom("agent:main:subagent:flat-depth-2"); - expect(result).toMatchObject({ - status: "forbidden", - error: "sessions_spawn is not allowed at this depth (current depth: 2, max: 2)", - }); + expectForbidden( + result, + "sessions_spawn is not allowed at this depth (current depth: 2, max: 2)", + ); }); it("rejects when active children for requester session reached maxChildrenPerAgent", async () => { @@ -130,10 +154,10 @@ describe("subagent spawn depth + child limits", () => { const result = await spawnFrom("agent:main:subagent:parent"); - expect(result).toMatchObject({ - status: "forbidden", - error: "sessions_spawn has reached max active children for this session (1/1)", - }); + expectForbidden( + result, + "sessions_spawn has reached max active children for this session (1/1)", + ); }); it("does not use subagent maxConcurrent as a per-parent spawn gate", async () => { @@ -147,10 +171,7 @@ describe("subagent spawn depth + child limits", () => { const result = await spawnFrom("agent:main:subagent:parent"); - expect(result).toMatchObject({ - status: "accepted", - runId: "run-1", - }); + expectAccepted(result, "run-1"); }); it("fails spawn when the initial child session patch rejects the model", async () => { @@ -167,9 +188,7 @@ describe("subagent spawn depth + child limits", () => { const result = await spawnFrom("main", { model: "bad-model" }); - expect(result).toMatchObject({ - status: "error", - }); + expect(result.status).toBe("error"); expect(result.error ?? "").toContain("invalid model"); expect( hoisted.callGatewayMock.mock.calls.some(