mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 08:44:46 +00:00
test: tighten subagent depth assertions
This commit is contained in:
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user