import assert from "node:assert/strict"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; import { afterEach, describe, it } from "node:test"; import registerFanoutChildSubagentExtension from "../../src/extension/fanout-child.ts"; import { createSubagentExecutor } from "../../src/runs/foreground/subagent-executor.ts"; import { createNestedRoute, projectNestedEvents, readNestedControlRequests, readNestedControlResults, writeNestedControlRequest, writeNestedControlResult, writeNestedEvent } from "../../src/runs/shared/nested-events.ts"; import { SUBAGENT_CHILD_ENV, SUBAGENT_FANOUT_CHILD_ENV, SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV, SUBAGENT_PARENT_CHILD_INDEX_ENV, SUBAGENT_PARENT_CONTROL_INBOX_ENV, SUBAGENT_PARENT_EVENT_SINK_ENV, SUBAGENT_PARENT_ROOT_RUN_ID_ENV, SUBAGENT_PARENT_RUN_ID_ENV, } from "../../src/runs/shared/pi-args.ts"; import { ASYNC_DIR, type SubagentState } from "../../src/shared/types.ts"; const routeRoots: string[] = []; const savedEnv = { [SUBAGENT_CHILD_ENV]: process.env[SUBAGENT_CHILD_ENV], [SUBAGENT_FANOUT_CHILD_ENV]: process.env[SUBAGENT_FANOUT_CHILD_ENV], [SUBAGENT_PARENT_EVENT_SINK_ENV]: process.env[SUBAGENT_PARENT_EVENT_SINK_ENV], [SUBAGENT_PARENT_CONTROL_INBOX_ENV]: process.env[SUBAGENT_PARENT_CONTROL_INBOX_ENV], [SUBAGENT_PARENT_ROOT_RUN_ID_ENV]: process.env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV], [SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV]: process.env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV], [SUBAGENT_PARENT_RUN_ID_ENV]: process.env[SUBAGENT_PARENT_RUN_ID_ENV], [SUBAGENT_PARENT_CHILD_INDEX_ENV]: process.env[SUBAGENT_PARENT_CHILD_INDEX_ENV], }; afterEach(() => { for (const root of routeRoots.splice(0)) fs.rmSync(root, { recursive: true, force: true }); for (const [key, value] of Object.entries(savedEnv)) { if (value === undefined) delete process.env[key]; else process.env[key] = value; } }); function createState(): SubagentState { return { baseCwd: "", currentSessionId: null, asyncJobs: new Map(), foregroundRuns: new Map(), foregroundControls: new Map(), lastForegroundControlId: null, pendingForegroundControlNotices: new Map(), cleanupTimers: new Map(), lastUiContext: null, poller: null, completionSeen: new Map(), watcher: null, watcherRestartTimer: null, resultFileCoalescer: { schedule: () => false, clear: () => {} }, }; } function createExecutor(state = createState(), agents: Array> = [], allowMutatingManagementActions = true, events: any = { emit() {}, on() { return () => {}; } }) { return createSubagentExecutor({ pi: { events, getSessionName() { return "parent"; } } as any, state, config: { maxSubagentDepth: 2, control: {}, intercomBridge: {} } as any, asyncByDefault: false, tempArtifactsDir: os.tmpdir(), getSubagentSessionRoot: (parentSessionFile) => parentSessionFile ? path.join(path.dirname(parentSessionFile), path.basename(parentSessionFile, ".jsonl")) : os.tmpdir(), expandTilde: (value) => value, discoverAgents: () => ({ agents: agents as any }), allowMutatingManagementActions, }); } function ctx(root: string, sessionFile: string | null = null) { return { cwd: root, hasUI: false, sessionManager: { getSessionId() { return "session"; }, getSessionFile() { return sessionFile; } }, modelRegistry: { getAvailable() { return []; } }, } as any; } function createNestedRun(id = "nested-live", state: "running" | "complete" | "failed" | "paused" = "running", extras: Record = {}) { const route = createNestedRoute("root-control"); routeRoots.push(path.dirname(route.eventSink)); writeNestedEvent(route, { type: state === "running" ? "subagent.nested.updated" : "subagent.nested.completed", ts: 100, parentRunId: "root-control", parentStepIndex: 0, child: { id, parentRunId: "root-control", parentStepIndex: 0, depth: 1, path: [{ runId: "root-control", stepIndex: 0 }], state, agent: "worker", ownerState: state === "running" ? "live" : "gone", ...extras }, }); return route; } function stateWithNestedRoute(route: ReturnType): SubagentState { const state = createState(); state.foregroundControls.set(route.rootRunId, { runId: route.rootRunId, mode: "single", startedAt: 1, updatedAt: 1, nestedRoute: route, }); state.lastForegroundControlId = route.rootRunId; return state; } function setNestedRouteEnv(route: ReturnType, parentRunId = route.rootRunId) { process.env[SUBAGENT_PARENT_EVENT_SINK_ENV] = route.eventSink; process.env[SUBAGENT_PARENT_CONTROL_INBOX_ENV] = route.controlInbox; process.env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV] = route.rootRunId; process.env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV] = route.capabilityToken; process.env[SUBAGENT_PARENT_RUN_ID_ENV] = parentRunId; process.env[SUBAGENT_PARENT_CHILD_INDEX_ENV] = "0"; } function text(result: Awaited["execute"]>>): string { return result.content[0]?.type === "text" ? result.content[0].text : ""; } async function waitFor(predicate: () => boolean, timeoutMs = 1_000): Promise { const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { if (predicate()) return; await new Promise((resolve) => setTimeout(resolve, 25)); } assert.equal(predicate(), true); } describe("nested control routing", () => { it("routes interrupt to an explicit nested id through the control inbox", async () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-nested-control-")); try { const route = createNestedRun(); const executor = createExecutor(stateWithNestedRoute(route)); setTimeout(() => { const request = readNestedControlRequests(route)[0]; assert.ok(request, "expected a nested control request"); writeNestedControlResult(route, { ts: Date.now(), requestId: request.requestId, targetRunId: request.targetRunId, ok: true, message: "nested interrupt accepted" }); }, 50); const result = await executor.execute("interrupt", { action: "interrupt", id: "nested-live" }, new AbortController().signal, undefined, ctx(root)); assert.equal(result.isError, undefined); assert.match(text(result), /nested interrupt accepted/); } finally { fs.rmSync(root, { recursive: true, force: true }); } }); it("renders nested children in foreground status output", async () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-nested-foreground-status-")); try { const route = createNestedRun("nested-foreground"); const state = createState(); state.foregroundControls.set("root-control", { runId: "root-control", mode: "single", startedAt: 1, updatedAt: 1, currentAgent: "orchestrator", currentIndex: 0, nestedRoute: route, }); state.lastForegroundControlId = "root-control"; const result = await createExecutor(state).execute("status", { action: "status", id: "root-control" }, new AbortController().signal, undefined, ctx(root)); assert.equal(result.isError, undefined); assert.match(text(result), /Run: root-control/); assert.match(text(result), /↳ worker \[nested-foreground\] running/); assert.match(text(result), /Status: subagent\(\{ action: "status", id: "nested-foreground" \}\)/); } finally { fs.rmSync(root, { recursive: true, force: true }); } }); it("scopes child-safe nested status lookup to the inherited route and child address", async () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-nested-child-scope-")); try { const allowedRoute = createNestedRun("shared-nested"); setNestedRouteEnv(allowedRoute, "root-control"); const outsideRoute = createNestedRoute("root-outside"); routeRoots.push(path.dirname(outsideRoute.eventSink)); writeNestedEvent(outsideRoute, { type: "subagent.nested.updated", ts: 100, parentRunId: "root-outside", parentStepIndex: 0, child: { id: "shared-nested", parentRunId: "root-outside", parentStepIndex: 0, depth: 1, path: [{ runId: "root-outside", stepIndex: 0 }], state: "running", agent: "outside" }, }); const result = await createExecutor(createState(), [], false).execute("status", { action: "status", id: "shared-nested" }, new AbortController().signal, undefined, ctx(root)); assert.equal(result.isError, undefined); assert.match(text(result), /Nested run: shared-nested/); assert.match(text(result), /Root: root-control/); assert.doesNotMatch(text(result), /root-outside/); } finally { fs.rmSync(root, { recursive: true, force: true }); } }); it("requires an id for child-safe status instead of listing unrelated top-level async runs", async () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-nested-child-safe-status-")); const runId = `child-safe-unrelated-${Date.now().toString(36)}`; const asyncDir = path.join(ASYNC_DIR, runId); try { fs.mkdirSync(asyncDir, { recursive: true }); fs.writeFileSync(path.join(asyncDir, "status.json"), JSON.stringify({ runId, mode: "single", state: "running", pid: 12345, startedAt: 100, lastUpdate: 100, steps: [{ agent: "outside", status: "running", startedAt: 100 }], }, null, 2), "utf-8"); const result = await createExecutor(createState(), [], false).execute("status", { action: "status" }, new AbortController().signal, undefined, ctx(root)); assert.equal(result.isError, true); assert.match(text(result), /requires an id/); assert.doesNotMatch(text(result), new RegExp(runId)); } finally { fs.rmSync(root, { recursive: true, force: true }); fs.rmSync(asyncDir, { recursive: true, force: true }); } }); it("does not let bare interrupt target hidden nested descendants", async () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-nested-bare-interrupt-")); try { createNestedRun("nested-only"); const result = await createExecutor().execute("interrupt", { action: "interrupt" }, new AbortController().signal, undefined, ctx(root)); assert.equal(result.isError, true); assert.match(text(result), /No interrupt-capable run found/); } finally { fs.rmSync(root, { recursive: true, force: true }); } }); it("times out owner-gone nested control and ignores late results", async () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-nested-timeout-")); try { const route = createNestedRun("nested-timeout"); const executor = createExecutor(stateWithNestedRoute(route)); setTimeout(() => { const request = readNestedControlRequests(route)[0]; if (request) writeNestedControlResult(route, { ts: Date.now(), requestId: request.requestId, targetRunId: request.targetRunId, ok: true, message: "late success" }); }, 1_200); const result = await executor.execute("interrupt", { action: "interrupt", id: "nested-timeout" }, new AbortController().signal, undefined, ctx(root)); assert.equal(result.isError, true); assert.match(text(result), /owner is not reachable/); assert.doesNotMatch(text(result), /late success/); } finally { fs.rmSync(root, { recursive: true, force: true }); } }); it("routes resume for live nested runs through the control inbox", async () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-nested-live-resume-")); try { const emitted: Array<{ name: string; payload: unknown }> = []; const events = { emit(name: string, payload: unknown) { emitted.push({ name, payload }); }, on() { return () => {}; } }; const route = createNestedRun("nested-live-resume", "running", { intercomTarget: "attacker-target", leafIntercomTarget: "attacker-leaf" }); const executor = createExecutor(stateWithNestedRoute(route), [], true, events); setTimeout(() => { const request = readNestedControlRequests(route)[0]; assert.ok(request, "expected a nested resume request"); assert.equal(request.action, "resume"); assert.equal(request.message, "continue please"); writeNestedControlResult(route, { ts: Date.now(), requestId: request.requestId, targetRunId: request.targetRunId, ok: true, message: "nested resume accepted" }); }, 50); const result = await executor.execute("resume", { action: "resume", id: "nested-live-resume", message: "continue please" }, new AbortController().signal, undefined, ctx(root)); assert.equal(result.isError, undefined); assert.match(text(result), /nested resume accepted/); assert.equal(emitted.some((event) => { const payload = event.payload as { to?: unknown }; return payload.to === "attacker-target" || payload.to === "attacker-leaf"; }), false); } finally { fs.rmSync(root, { recursive: true, force: true }); } }); it("validates terminal nested resume session files before revive", async () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-nested-terminal-resume-")); try { const route = createNestedRun("nested-terminal-resume", "complete", { sessionFile: path.join(root, "missing-session.jsonl") }); const result = await createExecutor(stateWithNestedRoute(route), [{ name: "worker", description: "Worker", prompt: "Do work" }]) .execute("resume", { action: "resume", id: "nested-terminal-resume", message: "continue" }, new AbortController().signal, undefined, ctx(root)); assert.equal(result.isError, true); assert.match(text(result), /session file does not exist/); } finally { fs.rmSync(root, { recursive: true, force: true }); } }); it("rejects terminal nested resume session files outside trusted roots", async () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-nested-terminal-untrusted-")); try { const parentSessionFile = path.join(root, "parent.jsonl"); const attackerSessionFile = path.join(root, "outside", "session.jsonl"); fs.mkdirSync(path.dirname(attackerSessionFile), { recursive: true }); fs.writeFileSync(parentSessionFile, ""); fs.writeFileSync(attackerSessionFile, ""); const route = createNestedRun("nested-untrusted-resume", "complete", { sessionFile: attackerSessionFile }); const result = await createExecutor(stateWithNestedRoute(route), [{ name: "worker", description: "Worker", prompt: "Do work" }]) .execute("resume", { action: "resume", id: "nested-untrusted-resume", message: "continue" }, new AbortController().signal, undefined, ctx(root, parentSessionFile)); assert.equal(result.isError, true); assert.match(text(result), /outside trusted nested session roots/); } finally { fs.rmSync(root, { recursive: true, force: true }); } }); it("rejects terminal nested resume session files from sibling run directories", async () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-nested-terminal-sibling-")); try { const parentSessionFile = path.join(root, "parent.jsonl"); const siblingSessionFile = path.join(root, "parent", "other-run", "run-0", "session.jsonl"); fs.mkdirSync(path.dirname(siblingSessionFile), { recursive: true }); fs.writeFileSync(parentSessionFile, ""); fs.writeFileSync(siblingSessionFile, ""); const route = createNestedRun("nested-sibling-resume", "complete", { sessionFile: siblingSessionFile }); const result = await createExecutor(stateWithNestedRoute(route), [{ name: "worker", description: "Worker", prompt: "Do work" }]) .execute("resume", { action: "resume", id: "nested-sibling-resume", message: "continue" }, new AbortController().signal, undefined, ctx(root, parentSessionFile)); assert.equal(result.isError, true); assert.match(text(result), /not under that nested run's session directory/); } finally { fs.rmSync(root, { recursive: true, force: true }); } }); it("emits a failed completed nested event when foreground execution throws after start", async () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-nested-foreground-throw-")); try { const route = createNestedRoute("root-parent"); routeRoots.push(path.dirname(route.eventSink)); setNestedRouteEnv(route, "root-parent"); const throwingCtx = { ...ctx(root), modelRegistry: { getAvailable() { throw new Error("model registry exploded"); } }, }; const result = await createExecutor(createState(), [{ name: "worker", description: "Worker", prompt: "Do work" }]) .execute("run", { agent: "worker", task: "go" }, new AbortController().signal, undefined, throwingCtx); assert.equal(result.isError, true); assert.match(text(result), /model registry exploded/); const registry = projectNestedEvents(route); assert.equal(registry.children.length, 1); assert.equal(registry.children[0]?.state, "failed"); assert.match(registry.children[0]?.error ?? "", /model registry exploded/); } finally { fs.rmSync(root, { recursive: true, force: true }); } }); it("keeps the fanout child control listener alive after control inbox polling errors", async () => { const route = createNestedRoute("root-poll-error"); routeRoots.push(path.dirname(route.eventSink)); setNestedRouteEnv(route, "root-poll-error"); process.env[SUBAGENT_CHILD_ENV] = "1"; process.env[SUBAGENT_FANOUT_CHILD_ENV] = "1"; const pi = { events: { emit() {}, on() { return () => {}; } }, registerTool() {}, getSessionName() { return "child"; }, } as any; fs.rmSync(route.controlInbox, { recursive: true, force: true }); fs.writeFileSync(route.controlInbox, "not a directory", "utf-8"); const originalError = console.error; const logged: unknown[][] = []; console.error = (...args: unknown[]) => { logged.push(args); }; try { registerFanoutChildSubagentExtension(pi); await waitFor(() => logged.some((entry) => String(entry[0] ?? "").includes(route.controlInbox) && String(entry[0] ?? "").includes("root-poll-error"))); fs.rmSync(route.controlInbox, { force: true }); fs.mkdirSync(route.controlInbox, { recursive: true }); const requestPath = writeNestedControlRequest(route, { ts: Date.now(), requestId: "poll-error-recovers", targetRunId: "missing-run", action: "interrupt", }); await waitFor(() => readNestedControlResults(route).some((result) => result.requestId === "poll-error-recovers" && result.ok === false)); assert.equal(fs.existsSync(requestPath), false); } finally { console.error = originalError; } }); it("keeps fanout child control requests when result writing fails and retries after recovery", async () => { const route = createNestedRoute("root-result-write-fails"); routeRoots.push(path.dirname(route.eventSink)); setNestedRouteEnv(route, "root-result-write-fails"); process.env[SUBAGENT_CHILD_ENV] = "1"; process.env[SUBAGENT_FANOUT_CHILD_ENV] = "1"; const pi = { events: { emit() {}, on() { return () => {}; } }, registerTool() {}, getSessionName() { return "child"; }, } as any; fs.rmSync(route.eventSink, { recursive: true, force: true }); fs.writeFileSync(route.eventSink, "not a directory", "utf-8"); const requestPath = writeNestedControlRequest(route, { ts: Date.now(), requestId: "result-write-fails", targetRunId: "missing-run", action: "interrupt", }); const originalError = console.error; const logged: unknown[][] = []; console.error = (...args: unknown[]) => { logged.push(args); }; try { registerFanoutChildSubagentExtension(pi); await waitFor(() => logged.some((entry) => String(entry[0] ?? "").includes("result-write-fails") && /keeping request for retry/.test(String(entry[0] ?? "")))); assert.equal(fs.existsSync(requestPath), true); fs.rmSync(route.eventSink, { force: true }); fs.mkdirSync(route.eventSink, { recursive: true }); await waitFor(() => readNestedControlResults(route).some((result) => result.requestId === "result-write-fails" && result.ok === false)); assert.equal(fs.existsSync(requestPath), false); } finally { console.error = originalError; } }); it("negatively acknowledges ownerless fanout child control requests and removes them", async () => { const route = createNestedRoute("root-ownerless"); routeRoots.push(path.dirname(route.eventSink)); setNestedRouteEnv(route, "root-ownerless"); process.env[SUBAGENT_CHILD_ENV] = "1"; process.env[SUBAGENT_FANOUT_CHILD_ENV] = "1"; const pi = { events: { emit() {}, on() { return () => {}; } }, registerTool() {}, getSessionName() { return "child"; }, } as any; const requestPath = writeNestedControlRequest(route, { ts: Date.now(), requestId: "ownerless-request", targetRunId: "missing-run", action: "interrupt", }); registerFanoutChildSubagentExtension(pi); await waitFor(() => readNestedControlResults(route).some((result) => result.requestId === "ownerless-request" && result.ok === false)); assert.equal(fs.existsSync(requestPath), false); const result = readNestedControlResults(route).find((item) => item.requestId === "ownerless-request"); assert.match(result?.message ?? "", /not active/); }); });