test: tighten subagent depth assertions

This commit is contained in:
Peter Steinberger
2026-05-09 18:44:37 +01:00
parent 4cfcc32ee3
commit 8f8426342e

View File

@@ -18,6 +18,13 @@ const hoisted = vi.hoisted(() => ({
let spawnSubagentDirect: typeof import("./subagent-spawn.js").spawnSubagentDirect;
let persistedStore: Record<string, Record<string, unknown>> | undefined;
type SpawnResult = Awaited<ReturnType<typeof spawnSubagentDirect>>;
type AcceptedSpawnResult = SpawnResult & {
childSessionKey: string;
runId: string;
status: "accepted";
};
function createDepthLimitConfig(subagents?: Record<string, unknown>) {
return createSubagentSpawnTestConfig("/tmp/workspace-main", {
agents: {
@@ -45,6 +52,24 @@ async function spawnFrom(sessionKey: string, params?: Record<string, unknown>) {
);
}
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(