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 { describe, it } from "node:test"; import type { AssistantMessage, Message, Model, ToolResultMessage } from "@earendil-works/pi-ai"; import { convertResponsesMessages } from "@earendil-works/pi-ai/api/openai-responses-shared"; import { normalizeContext } from "@earendil-works/pi-ai/utils/transcript"; import { createEventBus } from "../support/helpers.ts"; import { RUNTIME_EXTENSION_ACK_EVENT } from "../../src/runs/shared/runtime-acknowledged-extensions.ts"; import { clearStructuredOutputCaptures } from "../../src/runs/shared/structured-output.ts"; import { getAgentDir } from "../../src/shared/utils.ts"; import { formatChildToolDiagnostic, type ChildToolDiagnostic } from "../../src/runs/shared/tool-availability.ts"; import type { ChildRuntimeConfig } from "../../src/runs/shared/child-runtime-config.ts"; import type { ChildWatchdogConfig } from "../../src/watchdog/child-status.ts"; import { SUBAGENT_WATCHDOG_WARNING_TYPE } from "../../src/watchdog/types.ts"; import { randomUUID } from "node:crypto"; import { execFileSync } from "node:child_process"; import { createNestedRoute, nestedResultsPath } from "../../src/runs/shared/nested-events.ts"; import { updateActiveRunIndex } from "../../src/runs/background/active-run-index.ts"; import { INTERCOM_SESSION_IDENTITY_EVENT, SUBAGENT_ASYNC_COMPLETE_EVENT, TEMP_ROOT_DIR, SUBAGENT_FOREGROUND_COMPLETE_EVENT, type SubagentState } from "../../src/shared/types.ts"; import registerSubagentPromptRuntime, { CHILD_FANOUT_BOUNDARY_INSTRUCTIONS, CHILD_SUBAGENT_BOUNDARY_INSTRUCTIONS, registerPermissionGate, rewriteSubagentPrompt, stripGlobalContext, stripInheritedSkills, stripParentOnlySubagentMessages, stripProjectContext, stripSubagentOrchestrationSkill, } from "../../src/runs/shared/subagent-prompt-runtime.ts"; function childConfig(overrides: Partial = {}): ChildRuntimeConfig { return { fanoutChild: false, depth: 1, waitTool: { enabled: true }, fast: false, ...overrides }; } it("does not skip drain for in-process child sessions when hasUI is true", async () => { const handlers = new Map(); const listeners = new Map void>>(); const held: boolean[] = []; const sessionId = "reviewer-ui-session.jsonl"; const runtimeState = { foregroundRuns: new Map([["fg", { runId: "fg", mode: "single", cwd: "/tmp", sessionId, updatedAt: 1, children: [{ agent: "reviewer", index: 0, status: "detached", updatedAt: 1 }], }]]), } as SubagentState; const ctx = { hasUI: true, sessionManager: { getSessionFile: () => sessionId } }; const emit = async (name: string, event: unknown) => { for (const fn of handlers.get(name) ?? []) await fn(event, ctx); }; registerSubagentPromptRuntime({ on: (name: string, fn: Function) => handlers.set(name, [...(handlers.get(name) ?? []), fn]), registerTool: () => {}, events: { on: (channel: string, handler: () => void) => { listeners.set(channel, [...(listeners.get(channel) ?? []), handler]); return () => {}; } }, } as never, childConfig({ runtimeState, holdFinalDrain: (value) => { held.push(value); } })); await emit("session_start", {}); let settled = false; const ended = emit("agent_end", { messages: [] }).then(() => { settled = true; }); await new Promise((resolve) => setTimeout(resolve, 30)); assert.equal(settled, false); assert.deepEqual(held, [true]); runtimeState.foregroundRuns.get("fg")!.children[0]!.status = "completed"; for (const handler of listeners.get(SUBAGENT_FOREGROUND_COMPLETE_EVENT) ?? []) handler(); await ended; assert.equal(settled, true); assert.deepEqual(held, [true, false]); }); it("reads a late-installed owner barrier for each final drain and balances the hold", async () => { const handlers = new Map(); const listeners = new Map void>>(); const held: boolean[] = []; const sessionId = "fanout-owner-session.jsonl"; const runtimeState = { foregroundRuns: new Map([["fg", { runId: "fg", mode: "single", cwd: "/tmp", sessionId, updatedAt: 1, children: [{ agent: "worker", index: 0, status: "detached", updatedAt: 1 }], }]]), } as SubagentState; const config = childConfig({ runtimeState, holdFinalDrain: (value) => { held.push(value); } }); const ctx = { hasUI: true, sessionManager: { getSessionFile: () => sessionId } }; const emit = async (name: string) => { for (const fn of handlers.get(name) ?? []) await fn({}, ctx); }; registerSubagentPromptRuntime({ on: (name: string, fn: Function) => handlers.set(name, [...(handlers.get(name) ?? []), fn]), registerTool: () => {}, events: { on: (channel: string, handler: () => void) => { listeners.set(channel, [...(listeners.get(channel) ?? []), handler]); return () => {}; } }, } as never, config); await emit("session_start"); let pending = true; config.hasPendingSupervisorRequest = () => pending; await emit("agent_end"); assert.deepEqual(held, [true, false]); assert.equal(runtimeState.foregroundRuns.get("fg")!.children[0]!.status, "detached"); pending = false; let settled = false; const ended = emit("agent_end").then(() => { settled = true; }); await new Promise((resolve) => setTimeout(resolve, 30)); assert.equal(settled, false); runtimeState.foregroundRuns.get("fg")!.children[0]!.status = "completed"; for (const handler of listeners.get(SUBAGENT_FOREGROUND_COMPLETE_EVENT) ?? []) handler(); await ended; assert.deepEqual(held, [true, false, true, false]); }); it("does not grant nested wait access for an invalid inherited route", async (t) => { const route = createNestedRoute(randomUUID()); const runId = randomUUID(); const root = path.join(TEMP_ROOT_DIR, "nested-subagent-runs", route.rootRunId); const dir = path.join(root, runId); t.after(() => { for (const entry of [root, path.dirname(route.eventSink)]) fs.rmSync(entry, { recursive: true, force: true }); }); t.mock.method(console, "error", () => {}); fs.mkdirSync(dir, { recursive: true }); fs.writeFileSync(path.join(dir, "status.json"), JSON.stringify({ runId, sessionId: "owner", mode: "single", state: "running", pid: process.pid, startedAt: Date.now(), steps: [] })); updateActiveRunIndex(dir, "running"); const tools = new Map(); // SAFETY: this fixture supplies the registration API and session identity used by the empty wait path. registerSubagentPromptRuntime({ on: () => {}, registerTool: (tool: { name: string; execute: Function }) => tools.set(tool.name, tool), } as never, childConfig({ runtimeState: { currentSessionId: "owner" } as SubagentState, nestedRoute: { ...route, capabilityToken: "invalid" } })); const result = await tools.get("bg_wait")!.execute("wait", { id: runId, timeoutMs: 1 }); assert.match(result.content[0].text, /No active run matched/); }); it("registered child bg_wait discovers nested personas and agent_end drains the same scope", async () => { const route = createNestedRoute(randomUUID()); const runId = randomUUID(); const asyncRoot = path.join(TEMP_ROOT_DIR, "nested-subagent-runs", route.rootRunId); const asyncDir = path.join(asyncRoot, runId); const resultPath = nestedResultsPath(route.rootRunId, runId); const sessionId = "nested-reviewer-session.jsonl"; const handlers = new Map(); const events = createEventBus(); const tools = new Map(); const held: boolean[] = []; const ctx = { hasUI: false, sessionManager: { getSessionFile: () => sessionId } }; const emit = async (event: string) => { for (const fn of handlers.get(event) ?? []) await fn({}, ctx); }; const writeStatus = (state: "running" | "complete") => { fs.mkdirSync(asyncDir, { recursive: true }); fs.writeFileSync(path.join(asyncDir, "status.json"), JSON.stringify({ runId, sessionId, mode: "single", state, pid: process.pid, startedAt: Date.now(), lastUpdate: Date.now(), steps: [{ agent: "persona", status: state }], })); updateActiveRunIndex(asyncDir, state); }; try { // SAFETY: this fixture supplies the registration/event APIs exercised by the wait and drain hooks. registerSubagentPromptRuntime({ on: (event: string, fn: Function) => handlers.set(event, [...(handlers.get(event) ?? []), fn]), registerTool: (tool: { name: string; execute: Function }) => tools.set(tool.name, tool), events, } as never, childConfig({ fanoutChild: true, nestedRoute: route, holdFinalDrain: (value) => { held.push(value); } })); await emit("session_start"); writeStatus("running"); const wait = tools.get("bg_wait")!; const timed = await wait.execute("wait", { id: runId, timeoutMs: 1 }, undefined, undefined, ctx); assert.deepEqual(timed.details.wait?.activeRunIds, [runId], "registered wait must discover its nested persona"); let settled = false; const draining = emit("agent_end").then(() => { settled = true; }); await new Promise((resolve) => setTimeout(resolve, 20)); assert.equal(settled, false, "agent_end cannot skip the nested namespace"); assert.deepEqual(held, [true]); fs.mkdirSync(path.dirname(resultPath), { recursive: true }); fs.writeFileSync(resultPath, JSON.stringify({ runId, sessionId, success: true, results: [{ agent: "persona", output: "PERSONA_EVIDENCE" }] })); writeStatus("complete"); events.emit(SUBAGENT_ASYNC_COMPLETE_EVENT, undefined); await draining; assert.deepEqual(held, [true, false]); const terminal = await wait.execute("collect", { id: runId }, undefined, undefined, ctx); assert.equal(terminal.details.completions?.[0]?.runId, runId); assert.ok(terminal.content.some((part: { text?: string }) => part.text?.includes(resultPath)), "model receives a readable result reference, not details alone"); assert.equal(JSON.parse(fs.readFileSync(resultPath, "utf8")).results[0].output, "PERSONA_EVIDENCE"); } finally { fs.rmSync(asyncRoot, { recursive: true, force: true }); fs.rmSync(path.dirname(resultPath), { recursive: true, force: true }); fs.rmSync(path.dirname(route.eventSink), { recursive: true, force: true }); } }); function supervisorConfig(overrides: Partial = {}): ChildRuntimeConfig { return childConfig({ orchestratorTarget: "subagent-chat-parent", orchestratorSessionId: "session-parent", supervisorChannelDir: path.join(os.tmpdir(), "subagent-supervisor-runtime-test"), runId: "run-123", agent: "worker", childIndex: 0, ...overrides, }); } const watchdogConfig: ChildWatchdogConfig = { enabled: true, runId: "run-1", agent: "worker", childIndex: 0, watchdogTailTimeoutMs: 1000, agentEndTimeoutMs: 500, maxWarnings: null, lsp: { enabled: false, timeoutMs: 3000, maxFiles: 20, maxDiagnostics: 50 }, stalemateRepeats: 2, cadence: { everyNTools: null }, } as ChildWatchdogConfig; const SKILLS_SECTION = "\n\nThe following skills provide specialized instructions for specific tasks.\nUse the read tool to load a skill's file when the task matches its description.\nWhen a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.\n\n\n \n safe-bash\n desc\n /tmp/SKILL.md\n \n \n pi-subagents\n delegate to subagents\n /tmp/pi-subagents/SKILL.md\n \n"; const BASE_PROMPT = [ "You are a subagent.", "\n\n# Project Context\n\nProject-specific instructions and guidelines:\n\n## /repo/AGENTS.md\n\nProject rules\n\n", SKILLS_SECTION, "\nCurrent date: 2026-04-16", "\nCurrent working directory: /repo", ].join(""); const PROMPT_WITH_EXPLICIT_SKILL = [ "You are a subagent.\n\n\nKeep this section\n", "\n\n# Project Context\n\nProject-specific instructions and guidelines:\n\n## /repo/AGENTS.md\n\nProject rules\n\n", SKILLS_SECTION, "\nCurrent date: 2026-04-16", ].join(""); const CONFIGURED_SKILLS_SECTION = "\n\nThe following configured skills are available to this subagent.\nUse the read tool to load a skill's file when the task matches its description.\nWhen a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.\n\n\n \n configured-skill\n explicit agent skill\n /tmp/configured-skill/SKILL.md\n \n"; describe("subagent prompt runtime", () => { it("ignores an unconfigured path-based load", () => { assert.doesNotThrow(() => registerSubagentPromptRuntime({} as never)); }); it("registers a requested watchdog_diff at launch HEAD and reports unavailable baseline outside Git", async (t) => { const repo = fs.mkdtempSync(path.join(os.tmpdir(), "reviewer-runtime-diff-")); const outside = fs.mkdtempSync(path.join(os.tmpdir(), "reviewer-runtime-no-git-")); t.after(() => { fs.rmSync(repo, { recursive: true, force: true }); fs.rmSync(outside, { recursive: true, force: true }); }); execFileSync("git", ["init", "-q"], { cwd: repo }); execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: repo }); execFileSync("git", ["config", "user.name", "Test User"], { cwd: repo }); fs.writeFileSync(path.join(repo, "tracked.txt"), "base\n"); execFileSync("git", ["add", "."], { cwd: repo }); execFileSync("git", ["commit", "-q", "-m", "base"], { cwd: repo }); const registered = new Map(); const handlers = new Map(); const diagnostics: Array = []; registerSubagentPromptRuntime({ on: (event: string, handler: Function) => handlers.set(event, handler), registerTool: (tool: { name: string }) => registered.set(tool.name, tool), getAllTools: () => [...registered.keys()].map((name) => ({ name })), } as never, childConfig({ cwd: repo, requiredTools: ["watchdog_diff"], toolDiagnostic: (value) => diagnostics.push(value) })); assert.ok(registered.has("watchdog_diff"), "the required child runtime registers the bounded diff tool"); handlers.get("agent_start")?.({}); assert.equal(diagnostics.at(-1), undefined); fs.writeFileSync(path.join(repo, "tracked.txt"), "changed\n"); const diffTool = registered.get("watchdog_diff") as { execute(id: string, params: object): Promise<{ content: Array<{ text: string }> }> }; const result = await diffTool.execute("review", {}); assert.match(result.content[0]?.text ?? "", /\+changed/); assert.equal(registered.has("contact_supervisor"), false, "reviewing the fixture needs no supervisor contact"); const outsideTools = new Map(); const outsideHandlers = new Map(); const outsideDiagnostics: Array = []; registerSubagentPromptRuntime({ on: (event: string, handler: Function) => outsideHandlers.set(event, handler), registerTool: (tool: { name: string }) => outsideTools.set(tool.name, tool), getAllTools: () => [...outsideTools.keys()].map((name) => ({ name })), } as never, childConfig({ cwd: outside, requiredTools: ["watchdog_diff"], toolDiagnostic: (value) => outsideDiagnostics.push(value) })); assert.equal(outsideTools.has("watchdog_diff"), true); assert.doesNotThrow(() => outsideHandlers.get("agent_start")?.({})); assert.deepEqual(outsideDiagnostics, [undefined]); const unavailable = outsideTools.get("watchdog_diff") as { execute(id: string, params: object): Promise<{ content: Array<{ type: string; text: string }>; details: { chars: number } }> }; const unavailableResult = await unavailable.execute("review", {}); assert.equal(unavailableResult.content[0]?.type, "text"); assert.match(unavailableResult.content[0]?.text ?? "", /no valid Git HEAD baseline/); assert.match(unavailableResult.content[0]?.text ?? "", /No diff can be shown/); assert.equal(unavailableResult.details.chars, unavailableResult.content[0]?.text.length); assert.deepEqual(await unavailable.execute("review", { path: "tracked.txt", stat: true }), unavailableResult); assert.equal(outsideTools.has("contact_supervisor"), false); const missingHandlers = new Map(); registerSubagentPromptRuntime({ on: (event: string, handler: Function) => missingHandlers.set(event, handler), registerTool: (tool: { name: string }) => outsideTools.set(tool.name, tool), getAllTools: () => [...outsideTools.keys()].map((name) => ({ name })), } as never, childConfig({ cwd: outside, requiredTools: ["watchdog_diff", "fixture_search"] })); assert.throws(() => missingHandlers.get("agent_start")?.({}), /requested unavailable child tools: fixture_search/); }); it("registers no permission hook by default and routes ask only to the watchdog arbiter", async () => { const handlers: Array<(event: { toolName?: string; input?: unknown }, ctx?: unknown) => unknown> = []; const pi = { on(event: string, handler: (event: { toolName?: string; input?: unknown }, ctx?: unknown) => unknown) { if (event === "tool_call") handlers.push(handler); } }; registerPermissionGate(pi as never, undefined, undefined); assert.equal(handlers.length, 0); registerPermissionGate(pi as never, { rules: { write: "deny" } }, undefined); assert.equal(handlers.length, 1); assert.equal(await handlers[0]!({ toolName: "bash", input: { command: "rm -rf /" } }), undefined); assert.equal(await handlers[0]!({ toolName: "contact_supervisor", input: {} }), undefined); assert.deepEqual(await handlers[0]!({ toolName: "write", input: {} }), { block: true, reason: "Blocked by pi-subagents permission rule: 'write' is denied.", }); const askHandlers: Array<(event: { toolName?: string; input?: unknown }, ctx: unknown) => unknown> = []; const requests: Array<{ toolName: string; args: unknown }> = []; registerPermissionGate({ on(event: string, handler: (event: { toolName?: string; input?: unknown }, ctx: unknown) => unknown) { if (event === "tool_call") askHandlers.push(handler); } } as never, { rules: { write: "ask" } }, undefined, async (request) => { requests.push({ toolName: request.toolName, args: request.args }); return { approved: true, reason: "approved by watchdog", source: "watchdog" }; }); assert.equal(await askHandlers[0]!({ toolName: "write", input: { path: "out.txt" } }, { signal: undefined }), undefined); assert.deepEqual(requests, [{ toolName: "write", args: { path: "out.txt" } }]); }); it("fails closed when an ask permission decision stalls", async () => { { const stallingWatchdog = { ...watchdogConfig, agentEndTimeoutMs: 5, lsp: { enabled: false, timeoutMs: 100, maxFiles: 1, maxDiagnostics: 1 } } as ChildWatchdogConfig; const handlers: Array<(event: { toolName?: string; input?: unknown }, ctx: { signal?: AbortSignal }) => unknown> = []; registerPermissionGate({ on(event: string, handler: (event: { toolName?: string; input?: unknown }, ctx: { signal?: AbortSignal }) => unknown) { if (event === "tool_call") handlers.push(handler); } } as never, { rules: { write: "ask" } }, stallingWatchdog, async () => new Promise(() => undefined)); const result = await Promise.race([ handlers[0]!({ toolName: "write", input: { path: "out.txt" } }, { signal: undefined }), new Promise((resolve) => setTimeout(() => resolve("hung"), 100)), ]); assert.notEqual(result, "hung"); assert.deepEqual(result, { block: true, reason: "Blocked by pi-subagents permission rule: Watchdog permission arbiter failed closed: Watchdog permission decision timed out after 5ms.", }); } }); it("collects runtime extension acknowledgements until terminal serialization", () => { { const acknowledged: string[][] = []; const runtimeHandlers = new Map unknown>>(); const extensionHandlers = new Map unknown>>(); const pushHandler = (target: Map unknown>>, event: string, handler: (payload?: unknown) => unknown): void => { target.set(event, [...(target.get(event) ?? []), handler]); }; const emitAll = (target: Map unknown>>, event: string, payload?: unknown): void => { for (const handler of target.get(event) ?? []) handler(payload); }; registerSubagentPromptRuntime({ events: { on(event: string, handler: (payload?: unknown) => unknown) { pushHandler(extensionHandlers, event, handler); } }, on(event: string, handler: (payload?: unknown) => unknown) { pushHandler(runtimeHandlers, event, handler); }, } as never, childConfig({ runtimeAcknowledgements: (ids) => acknowledged.push(ids) })); emitAll(extensionHandlers, RUNTIME_EXTENSION_ACK_EVENT, { id: "ext.one" }); emitAll(extensionHandlers, RUNTIME_EXTENSION_ACK_EVENT, { id: "ext.one" }); emitAll(extensionHandlers, RUNTIME_EXTENSION_ACK_EVENT, { id: "bad/path" }); runtimeHandlers.get("agent_end")?.[0]?.({}); emitAll(extensionHandlers, RUNTIME_EXTENSION_ACK_EVENT, { id: "late" }); assert.deepEqual(acknowledged, [["ext.one", "ext.one"]]); } }); it("nudges after the tool budget soft limit and blocks configured tools after hard", () => { const handlers = new Map unknown>(); const sent: string[] = []; registerSubagentPromptRuntime({ on(event: string, handler: (payload: { toolName?: string }) => unknown) { handlers.set(event, handler); }, sendUserMessage(content: string) { sent.push(content); }, } as { on(event: string, handler: (payload: { toolName?: string }) => unknown): void; sendUserMessage(content: string): void }, childConfig({ toolBudget: { soft: 2, hard: 2, block: ["read"] } })); const toolCall = handlers.get("tool_call"); assert.ok(toolCall, "tool_call handler should be registered"); assert.equal(toolCall({ toolName: "grep" }), undefined); assert.equal(toolCall({ toolName: "grep" }), undefined); assert.equal(sent.length, 1); assert.match(sent[0] ?? "", /soft limit reached/); assert.deepEqual(toolCall({ toolName: "read" }), { block: true, reason: "Tool budget hard limit reached after 3 tool calls (hard 2). The 'read' tool is blocked so you can finalize from the context you already have.", }); assert.equal(toolCall({ toolName: "write" }), undefined); }); it("registers child watchdog lifecycle handlers only when the config enables them", () => { const handlersWithout = new Map(); registerSubagentPromptRuntime({ on(event: string, handler: unknown) { handlersWithout.set(event, [...(handlersWithout.get(event) ?? []), handler]); }, } as { on(event: string, handler: unknown): void }, childConfig()); assert.equal(handlersWithout.get("agent_end")?.length ?? 0, 1, "headless auto-drain is always registered"); const handlersWith = new Map(); registerSubagentPromptRuntime({ on(event: string, handler: unknown) { handlersWith.set(event, [...(handlersWith.get(event) ?? []), handler]); }, getThinkingLevel() { return "off"; }, sendMessage() {}, } as { on(event: string, handler: unknown): void; getThinkingLevel(): string; sendMessage(): void }, childConfig({ childWatchdog: watchdogConfig, watchdogStatus: () => {} })); assert.ok((handlersWith.get("before_agent_start")?.length ?? 0) >= 2); assert.ok((handlersWith.get("turn_end")?.length ?? 0) >= 1); assert.ok((handlersWith.get("agent_end")?.length ?? 0) >= 2, "watchdog and auto-drain both observe agent_end"); }); it("registered structured_output tool accepts valid schema output and captures it", async () => { { const captured: unknown[] = []; const terminalState = { captured: false }; let execute: ((_id: string, params: { value: unknown }) => Promise<{ terminate?: boolean }>) | undefined; let parameters: unknown; let exposure: unknown; registerSubagentPromptRuntime({ registerTool(tool: { name: string; parameters: unknown; exposure?: string; execute: (_id: string, params: { value: unknown }) => Promise<{ terminate?: boolean }> }) { if (tool.name === "structured_output") { execute = tool.execute; parameters = tool.parameters; exposure = tool.exposure; } }, on() {}, } as { registerTool(tool: { name: string; parameters: unknown; execute: (_id: string, params: { value: unknown }) => Promise<{ terminate?: boolean }> }): void; on(): void }, childConfig({ structuredOutput: { schema: { type: "object", required: ["ok"], properties: { ok: { type: "boolean" } } }, terminalState, capture: (value) => captured.push(value) }, })); assert.ok(execute, "structured_output tool should be registered"); assert.equal(exposure, "model-only", "a nested structured_output call cannot terminate the step"); assert.deepEqual(parameters, { type: "object", properties: { value: { type: "object", required: ["ok"], properties: { ok: { type: "boolean" } } } }, required: ["value"], additionalProperties: false, }); await assert.rejects(execute("invalid", { value: { ok: "not-boolean" } }), /validation failed/); assert.equal(terminalState.captured, false, "schema rejection is not a terminal capture"); const result = await execute("tool-1", { value: { ok: true } }); assert.equal(result.terminate, true); assert.deepEqual(captured, [{ ok: true }]); assert.equal(terminalState.captured, true); } }); it("requires and validates acceptanceReport when structured capture is required", async () => { { const captured: Array<{ value: unknown; acceptanceReport: unknown }> = []; let execute: ((_id: string, params: { value: unknown; acceptanceReport?: unknown }) => Promise) | undefined; let parameters: { required?: string[] } | undefined; registerSubagentPromptRuntime({ registerTool(tool: { name: string; parameters: unknown; execute: typeof execute }) { if (tool.name === "structured_output") { execute = tool.execute; parameters = tool.parameters as { required?: string[] }; } }, on() {}, } as { registerTool(tool: { name: string; parameters: unknown; execute: typeof execute }): void; on(): void }, childConfig({ structuredOutput: { schema: { type: "object" }, acceptanceReport: "required", capture: (value, acceptanceReport) => captured.push({ value, acceptanceReport }) }, })); assert.deepEqual(parameters?.required, ["value", "acceptanceReport"]); await assert.rejects(execute!("missing", { value: {} }), /Missing acceptanceReport/); await assert.rejects(execute!("empty", { value: {}, acceptanceReport: {} }), /expected at least one acceptance report field/); await execute!("valid", { value: {}, acceptanceReport: { manualNotes: "validated evidence" } }); assert.deepEqual(captured, [{ value: {}, acceptanceReport: { manualNotes: "validated evidence" } }]); } }); it("captures an omitted optional acceptance report as undefined", async () => { { const captured: Array<{ value: unknown; acceptanceReport: unknown }> = []; let execute: ((_id: string, params: { value: unknown }) => Promise) | undefined; registerSubagentPromptRuntime({ registerTool(tool: { name: string; execute: typeof execute }) { if (tool.name === "structured_output") execute = tool.execute; }, on() {}, } as { registerTool(tool: { name: string; execute: typeof execute }): void; on(): void }, childConfig({ structuredOutput: { schema: { type: "object" }, acceptanceReport: "optional", capture: (value, acceptanceReport) => captured.push({ value, acceptanceReport }) }, })); await execute!("without-report", { value: {} }); assert.deepEqual(captured, [{ value: {}, acceptanceReport: undefined }]); } }); it("reports capture cleanup failures after clearing every stale file it can", () => { const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-structured-cleanup-")); try { const outputPath = path.join(dir, "output.json"); const acceptancePath = path.join(dir, "acceptance.json"); fs.mkdirSync(outputPath); fs.writeFileSync(acceptancePath, JSON.stringify({ manualNotes: "stale" }), "utf-8"); const error = clearStructuredOutputCaptures({ schema: { type: "object" }, schemaPath: path.join(dir, "schema.json"), outputPath, acceptanceReportPath: acceptancePath }); assert.match(error ?? "", /Failed to clear stale structured output capture/); assert.equal(fs.existsSync(outputPath), true); assert.equal(fs.existsSync(acceptancePath), false); } finally { fs.rmSync(dir, { recursive: true, force: true }); } }); it("scopes local structured_output schema refs under the value parameter", () => { { const schema = { $defs: { item: { type: "string" } }, type: "object", properties: { name: { $ref: "#/$defs/item" }, nested: { type: "object", properties: { label: { $ref: "#/$defs/item" } }, }, }, }; let parameters = {} as { properties?: { value?: { properties?: { name?: { $ref?: string }; nested?: { properties?: { label?: { $ref?: string } } } } } } }; registerSubagentPromptRuntime({ registerTool(tool: { name: string; parameters: unknown }) { if (tool.name === "structured_output") parameters = tool.parameters as typeof parameters; }, on() {}, } as { registerTool(tool: { name: string; parameters: unknown }): void; on(): void }, childConfig({ structuredOutput: { schema, capture: () => {} } })); assert.equal(parameters.properties?.value?.properties?.name?.$ref, "#/properties/value/$defs/item"); assert.equal(parameters.properties?.value?.properties?.nested?.properties?.label?.$ref, "#/properties/value/$defs/item"); } }); it("strips only the project context block", () => { const rewritten = stripProjectContext(BASE_PROMPT); assert.ok(!rewritten.includes("# Project Context")); assert.ok(rewritten.includes("The following skills provide specialized instructions for specific tasks.")); assert.ok(rewritten.includes("Current date: 2026-04-16")); }); it("strips an XML block as full project context removal", () => { const globalDir = getAgentDir(); const prompt = [ "You are a subagent.", "\n\n\n\nProject-specific instructions and guidelines:\n\n", `\nGlobal rules\n\n\n`, "\nRepo rules\n\n\n", "\n\n", "Current working directory: /repo", ].join(""); const rewritten = stripProjectContext(prompt); assert.ok(!rewritten.includes("")); assert.ok(!rewritten.includes(" { const globalDir = getAgentDir(); const prompt = [ "You are a subagent.", "\n\n\n\nProject-specific instructions and guidelines:\n\n", `\nGlobal rules\n\n\n`, "\nRepo rules\n\n\n", "\n\n", "Current working directory: /repo", ].join(""); const rewritten = stripGlobalContext(prompt); assert.ok(!rewritten.includes("Global rules")); assert.ok(rewritten.includes("Repo rules")); assert.ok(rewritten.includes("")); }); it("strips only global context files from legacy Markdown while preserving repository context", () => { const globalDir = getAgentDir(); const prompt = [ "You are a subagent.", "\n\n# Project Context\n\nProject-specific instructions and guidelines:\n\n", `## ${globalDir}/AGENTS.md\n\nGlobal rules\n\n`, "## /repo/AGENTS.md\n\nRepo rules\n\n", SKILLS_SECTION, "\nCurrent date: 2026-04-16", ].join(""); const rewritten = rewriteSubagentPrompt(prompt, { inheritProjectContext: true, inheritGlobalContext: false, inheritSkills: true, }); assert.ok(!rewritten.includes("Global rules")); assert.ok(rewritten.includes("## /repo/AGENTS.md")); assert.ok(rewritten.includes("Repo rules")); }); it("recognizes Pi context filenames case-insensitively", () => { const globalDir = getAgentDir(); const prompt = [ "You are a subagent.", "\n\n\n\nProject-specific instructions and guidelines:\n\n", `\nGlobal rules\n\n\n`, "\nRepo rules\n\n\n", "\n\n", "Current working directory: /repo", ].join(""); const rewritten = stripGlobalContext(prompt); assert.ok(!rewritten.includes("Global rules")); assert.ok(rewritten.includes("Repo rules")); }); for (const [label, prompt] of [ ["CRLF context", `\r\n\r\nGlobal rules\r\n\r\n\r\nRepo rules\r\n\r\n`], ["content without a trailing newline", `Global rulesRepo rules`], ["same-line content", `Global rulesRepo rules`], ] as const) { it(`strips global instructions from ${label}`, () => { const rewritten = stripGlobalContext(prompt); assert.ok(!rewritten.includes("Global rules")); assert.ok(rewritten.includes("Repo rules")); }); } it("does not strip matching examples outside project context", () => { const example = `Example only`; assert.equal(stripGlobalContext(example), example); }); it("inherits global context when inheritGlobalContext is true", () => { const globalDir = getAgentDir(); const prompt = [ "You are a subagent.", "\n\n\n\nProject-specific instructions and guidelines:\n\n", `\nGlobal rules\n\n\n`, "\n\n", "Current working directory: /repo", ].join(""); const rewritten = rewriteSubagentPrompt(prompt, { inheritProjectContext: true, inheritGlobalContext: true, inheritSkills: true, }); assert.ok(rewritten.includes("Global rules")); }); it("removes global context files while keeping repository context via rewriteSubagentPrompt", () => { const globalDir = getAgentDir(); const prompt = [ "You are a subagent.", "\n\n\n\nProject-specific instructions and guidelines:\n\n", `\nGlobal rules\n\n\n`, "\nRepo rules\n\n\n", "\n\n", "Current working directory: /repo", ].join(""); const rewritten = rewriteSubagentPrompt(prompt, { inheritProjectContext: true, inheritGlobalContext: false, inheritSkills: true, }); assert.ok(!rewritten.includes("Global rules")); assert.ok(rewritten.includes("Repo rules")); }); it("strips only the inherited skills block", () => { const rewritten = stripInheritedSkills(BASE_PROMPT); assert.ok(rewritten.includes("# Project Context")); assert.ok(!rewritten.includes("")); assert.ok(rewritten.includes("Current date: 2026-04-16")); }); it("can strip both inherited sections together", () => { const rewritten = rewriteSubagentPrompt(BASE_PROMPT, { inheritProjectContext: false, inheritGlobalContext: false, inheritSkills: false, }); assert.ok(!rewritten.includes("# Project Context")); assert.ok(!rewritten.includes("")); assert.ok(rewritten.includes("Current working directory: /repo")); }); it("injects a child-only boundary that forbids proposing or running subagents", () => { const rewritten = rewriteSubagentPrompt(BASE_PROMPT, { inheritProjectContext: true, inheritGlobalContext: true, inheritSkills: true, }); assert.ok(rewritten.endsWith(`\n\n${CHILD_SUBAGENT_BOUNDARY_INSTRUCTIONS}`)); assert.ok(rewritten.includes("Do not propose or run subagents.")); assert.ok(rewritten.includes("If you need to edit files, use the available editing tools.")); assert.ok(!rewritten.includes("call the actual edit/write tools")); assert.ok(rewritten.includes("Do not print tool-call syntax, patches, or pseudo-tool calls as text.")); assert.equal(rewriteSubagentPrompt(rewritten, { inheritProjectContext: true, inheritGlobalContext: true, inheritSkills: true }), rewritten); }); it("keeps Pi's base prompt first and appends the boundary and structured-output instructions once", () => { const piPrompt = "You are an expert coding assistant operating inside pi, a coding agent harness.\n\nAvailable tools:\n- read\n\nCurrent working directory: /repo"; const options = { inheritProjectContext: true, inheritGlobalContext: true, inheritSkills: true, structuredOutput: true }; const rewritten = rewriteSubagentPrompt(piPrompt, options); assert.ok(rewritten.startsWith("You are an expert coding assistant operating inside pi")); assert.ok(rewritten.indexOf(CHILD_SUBAGENT_BOUNDARY_INSTRUCTIONS) > rewritten.indexOf("Current working directory: /repo")); assert.ok(rewritten.endsWith("if you do not call `structured_output`, the parent will fail this step.")); assert.equal(rewriteSubagentPrompt(rewritten, options), rewritten); }); it("replaces inherited child boundaries with the fanout boundary when authorized", () => { const strictPrompt = `${CHILD_SUBAGENT_BOUNDARY_INSTRUCTIONS}\n\n${BASE_PROMPT}`; const rewritten = rewriteSubagentPrompt(strictPrompt, { inheritProjectContext: true, inheritGlobalContext: true, inheritSkills: true, fanoutChild: true, }); assert.ok(rewritten.startsWith("You are a subagent.")); assert.ok(rewritten.endsWith(`\n\n${CHILD_FANOUT_BOUNDARY_INSTRUCTIONS}`)); assert.ok(rewritten.includes("You may use the `subagent` tool only for the fanout work explicitly requested in this task.")); assert.ok(rewritten.includes("If you need to edit files, use the available editing tools.")); assert.ok(!rewritten.includes("call the actual edit/write tools")); assert.ok(!rewritten.includes("Do not propose or run subagents.")); assert.equal(rewritten.indexOf(CHILD_FANOUT_BOUNDARY_INSTRUCTIONS), rewritten.lastIndexOf(CHILD_FANOUT_BOUNDARY_INSTRUCTIONS)); }); it("replaces inherited fanout boundaries with the strict boundary when fanout is not authorized", () => { const fanoutPrompt = `${CHILD_FANOUT_BOUNDARY_INSTRUCTIONS}\n\n${BASE_PROMPT}`; const rewritten = rewriteSubagentPrompt(fanoutPrompt, { inheritProjectContext: true, inheritGlobalContext: true, inheritSkills: true, }); assert.ok(rewritten.startsWith("You are a subagent.")); assert.ok(rewritten.endsWith(`\n\n${CHILD_SUBAGENT_BOUNDARY_INSTRUCTIONS}`)); assert.ok(!rewritten.includes("explicit fanout responsibility")); assert.equal(rewritten.indexOf(CHILD_SUBAGENT_BOUNDARY_INSTRUCTIONS), rewritten.lastIndexOf(CHILD_SUBAGENT_BOUNDARY_INSTRUCTIONS)); }); it("keeps explicitly injected skill content when inherited skills are stripped", () => { const rewritten = rewriteSubagentPrompt(PROMPT_WITH_EXPLICIT_SKILL, { inheritProjectContext: false, inheritGlobalContext: false, inheritSkills: false, }); assert.ok(rewritten.includes("")); assert.ok(!rewritten.includes("")); assert.ok(!rewritten.includes("# Project Context")); }); it("keeps configured lazy skill references when inherited skills are stripped", () => { const prompt = [ "You are a subagent.", CONFIGURED_SKILLS_SECTION, "\n\n# Project Context\n\nProject-specific instructions and guidelines:\n\n## /repo/AGENTS.md\n\nProject rules\n\n", SKILLS_SECTION, "\nCurrent date: 2026-04-16", ].join(""); const rewritten = rewriteSubagentPrompt(prompt, { inheritProjectContext: false, inheritGlobalContext: false, inheritSkills: false, }); assert.ok(rewritten.includes("configured-skill")); assert.ok(rewritten.includes("/tmp/configured-skill/SKILL.md")); assert.ok(!rewritten.includes("safe-bash")); assert.ok(!rewritten.includes("# Project Context")); }); it("strips the subagent orchestration skill even when inherited skills remain", () => { const rewritten = rewriteSubagentPrompt(BASE_PROMPT, { inheritProjectContext: true, inheritGlobalContext: true, inheritSkills: true, }); assert.ok(rewritten.includes("safe-bash")); assert.ok(!rewritten.includes("pi-subagents")); assert.ok(!rewritten.includes("delegate to subagents")); }); it("strips explicit pi-subagents skill injection from child prompts", () => { const prompt = "Before\n\n\nDo not keep this.\n\n\n\nKeep this.\n\nAfter"; const rewritten = stripSubagentOrchestrationSkill(prompt); assert.ok(!rewritten.includes("Do not keep this")); assert.ok(rewritten.includes("")); }); it("strips parent-only subagent custom messages from forked child context", () => { const user = { role: "user", content: "Task" }; const instruction = { role: "custom", customType: "subagent-orchestration-instructions", content: "Subagent orchestration is enabled." }; const slashResult = { role: "custom", customType: "subagent-slash-result", content: "## Orchestration" }; const slashTextResult = { role: "custom", customType: "subagent-slash-text-result", content: "Subagent profiles" }; const notify = { role: "custom", customType: "subagent-notify", content: "Background task completed" }; const control = { role: "custom", customType: "subagent_control_notice", content: "needs attention" }; const watchdogWarning = { role: "custom", customType: SUBAGENT_WATCHDOG_WARNING_TYPE, content: "parent-only" }; const childWatchdogWarning = { role: "custom", customType: SUBAGENT_WATCHDOG_WARNING_TYPE, content: "child-visible", details: { source: "child" } }; const otherCustom = { role: "custom", customType: "other", content: "keep" }; assert.deepEqual(stripParentOnlySubagentMessages([user, instruction, slashResult, slashTextResult, notify, control, watchdogWarning, childWatchdogWarning, otherCustom]), [user, otherCustom]); }); it("strips prior parent subagent tool calls and results from forked child context", () => { const user = { role: "user", content: "Task" }; const subagentResult = { role: "toolResult", toolName: "subagent", content: "subagent results" }; const readResult = { role: "toolResult", toolName: "read", content: "file contents" }; const mixedAssistant = { role: "assistant", content: [ { type: "text", text: "I will inspect the repo." }, { type: "toolCall", name: "subagent", input: { agent: "worker" } }, { type: "toolCall", name: "read", input: { path: "README.md" } }, ], }; const pureSubagentCall = { role: "assistant", content: [{ type: "toolCall", name: "subagent", input: { agent: "reviewer" } }], }; assert.deepEqual( stripParentOnlySubagentMessages([user, subagentResult, readResult, mixedAssistant, pureSubagentCall]), [ user, readResult, { role: "assistant", content: [ { type: "text", text: "I will inspect the repo." }, { type: "toolCall", name: "read", input: { path: "README.md" } }, ], }, ], ); }); it("sanitizes non-portable tool ids in forked child context", () => { const assistant = { role: "assistant", content: [ { type: "toolCall", id: "call_read|fc_123", name: "read", input: { path: "README.md" } }, { type: "toolCall", id: "call_bash-ok", name: "bash", input: { command: "pwd" } }, ], }; const readResult = { role: "toolResult", toolName: "read", toolCallId: "call_read|fc_123", content: "file contents" }; const bashResult = { role: "toolResult", toolName: "bash", toolCallId: "call_bash-ok", content: "cwd" }; assert.deepEqual(stripParentOnlySubagentMessages([assistant, readResult, bashResult]), [ { role: "assistant", content: [ { type: "toolCall", id: "tool_Y2FsbF9yZWFkfGZjXzEyMw", name: "read", input: { path: "README.md" } }, { type: "toolCall", id: "call_bash-ok", name: "bash", input: { command: "pwd" } }, ], }, { role: "toolResult", toolName: "read", toolCallId: "tool_Y2FsbF9yZWFkfGZjXzEyMw", content: "file contents" }, bashResult, ]); }); it("preserves live nested subagent calls and results in fanout child context", () => { const user = { role: "user", content: "Task" }; const subagentResult = { role: "toolResult", toolName: "subagent", content: "OK" }; const subagentCall = { role: "assistant", content: [{ type: "toolCall", name: "subagent", input: { agent: "delegate" } }] }; const instruction = { role: "custom", customType: "subagent-orchestration-instructions", content: "Subagent orchestration is enabled." }; assert.deepEqual(stripParentOnlySubagentMessages([user, subagentCall, subagentResult, instruction], { preserveFanoutToolHistory: true }), [user, subagentCall, subagentResult]); }); it("defers native supervisor registration until runtime events and respects installed pi-intercom tools", async () => { const handlers = new Map unknown>(); const registered: string[] = []; registerSubagentPromptRuntime({ on(event: string, handler: (payload?: unknown) => unknown) { handlers.set(event, handler); }, getAllTools: () => [{ name: "intercom" }, { name: "contact_supervisor" }], registerTool(tool: { name: string }) { registered.push(tool.name); }, } as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(tool: { name: string }): void }, supervisorConfig()); assert.deepEqual(registered, ["bg_wait", "subagent_wait"]); handlers.get("session_start")?.({}); await handlers.get("before_agent_start")?.({ systemPrompt: BASE_PROMPT }); assert.deepEqual(registered, ["bg_wait", "subagent_wait"]); }); it("does not satisfy strict allowlists with native generic intercom", () => { { const diagnostics: Array = []; const handlers = new Map unknown>(); const registered: string[] = []; registerSubagentPromptRuntime({ on(event: string, handler: (payload?: unknown) => unknown) { handlers.set(event, handler); }, getAllTools: () => registered.map((name) => ({ name })), registerTool(tool: { name: string }) { registered.push(tool.name); }, } as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(tool: { name: string }): void }, supervisorConfig({ agent: "scout", requiredTools: ["read", "grep", "find", "ls", "bash", "edit", "write", "intercom"], toolDiagnostic: (diagnostic) => diagnostics.push(diagnostic), })); handlers.get("session_start")?.({}); assert.deepEqual(registered, ["bg_wait", "subagent_wait", "contact_supervisor"]); assert.throws(() => handlers.get("agent_start")?.({}), /requested unavailable child tools: read, grep, find, ls, bash, edit, write, intercom/); assert.deepEqual(diagnostics, [{ agent: "scout", required: ["read", "grep", "find", "ls", "bash", "edit", "write", "intercom"], available: ["bg_wait", "subagent_wait", "contact_supervisor"], missing: ["read", "grep", "find", "ls", "bash", "edit", "write", "intercom"], }]); } }); it("records missing core write tools from the actual child registry", () => { { const diagnostics: Array = []; const handlers = new Map unknown>(); registerSubagentPromptRuntime({ on(event: string, handler: (payload?: unknown) => unknown) { handlers.set(event, handler); }, getAllTools: () => ["read", "grep", "find", "ls", "contact_supervisor"].map((name) => ({ name })), registerTool() {}, } as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(): void }, childConfig({ agent: "worker", requiredTools: ["read", "grep", "find", "ls", "bash", "edit", "write"], toolDiagnostic: (diagnostic) => diagnostics.push(diagnostic), })); assert.throws(() => handlers.get("agent_start")?.({}), /requested unavailable child tools: bash, edit, write/); assert.deepEqual(diagnostics, [{ agent: "worker", required: ["read", "grep", "find", "ls", "bash", "edit", "write"], available: ["read", "grep", "find", "ls", "contact_supervisor"], missing: ["bash", "edit", "write"], }]); } }); it("keeps installed pi-intercom while filling only a missing child contact_supervisor tool", async () => { const handlers = new Map unknown>(); const registered: string[] = []; registerSubagentPromptRuntime({ on(event: string, handler: (payload?: unknown) => unknown) { handlers.set(event, handler); }, getAllTools: () => [{ name: "intercom" }, ...registered.map((name) => ({ name }))], registerTool(tool: { name: string }) { registered.push(tool.name); }, } as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(tool: { name: string }): void }, supervisorConfig()); handlers.get("session_start")?.({}); await handlers.get("before_agent_start")?.({ systemPrompt: BASE_PROMPT }); assert.deepEqual(registered, ["bg_wait", "subagent_wait", "contact_supervisor"]); }); it("registers only native supervisor tools at runtime when pi-intercom is absent", async () => { const handlers = new Map unknown>(); const registered: string[] = []; { registerSubagentPromptRuntime({ on(event: string, handler: (payload?: unknown) => unknown) { handlers.set(event, handler); }, getAllTools: () => registered.map((name) => ({ name })), registerTool(tool: { name: string }) { registered.push(tool.name); }, } as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(tool: { name: string }): void }, supervisorConfig()); handlers.get("session_start")?.({}); assert.deepEqual(registered, ["bg_wait", "subagent_wait", "contact_supervisor"]); await handlers.get("before_agent_start")?.({ systemPrompt: BASE_PROMPT }); assert.deepEqual(registered, ["bg_wait", "subagent_wait", "contact_supervisor"]); } }); it("records requested tools missing from the child registry after startup hooks settle", async () => { { const diagnostics: Array = []; const handlers = new Map unknown>(); const available = ["read"]; registerSubagentPromptRuntime({ on(event: string, handler: (payload?: unknown) => unknown) { handlers.set(event, handler); }, getAllTools: () => available.map((name) => ({ name })), registerTool() {}, } as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(): void }, childConfig({ agent: "extension-worker", requiredTools: ["read", "fixture_search"], toolDiagnostic: (diagnostic) => diagnostics.push(diagnostic), })); const promptRewrite = await handlers.get("before_agent_start")?.({ systemPrompt: BASE_PROMPT }) as { systemPrompt?: string } | undefined; assert.deepEqual(diagnostics, []); assert.doesNotMatch(promptRewrite?.systemPrompt ?? "", /requested unavailable child tools/); assert.throws(() => handlers.get("agent_start")?.({}), /requested unavailable child tools: fixture_search/); assert.deepEqual(diagnostics, [{ agent: "extension-worker", required: ["read", "fixture_search"], available: ["read"], missing: ["fixture_search"], }]); available.push("fixture_search"); handlers.get("agent_start")?.({}); assert.equal(diagnostics.at(-1), undefined); } }); it("classifies missing resolved MCP direct tools without softening strict diagnostics", () => { { const diagnostics: Array = []; const handlers = new Map unknown>(); registerSubagentPromptRuntime({ on(event: string, handler: (payload?: unknown) => unknown) { handlers.set(event, handler); }, getAllTools: () => [{ name: "read" }], registerTool() {}, } as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(): void }, childConfig({ agent: "worker", requiredTools: ["read", "rust_symbols_workspace_symbols", "fixture_search"], mcpDirectTools: ["rust_symbols_workspace_symbols"], toolDiagnostic: (diagnostic) => diagnostics.push(diagnostic), })); assert.throws(() => handlers.get("agent_start")?.({}), /requested unavailable child tools: rust_symbols_workspace_symbols, fixture_search/); const diagnostic = diagnostics[0]; assert.deepEqual(diagnostic, { agent: "worker", required: ["read", "rust_symbols_workspace_symbols", "fixture_search"], available: ["read"], missing: ["rust_symbols_workspace_symbols", "fixture_search"], missingMcpDirectTools: ["rust_symbols_workspace_symbols"], }); assert.match(formatChildToolDiagnostic(diagnostic!), /must match what the host or pi-mcp-adapter registers/); assert.match(formatChildToolDiagnostic(diagnostic!), /fixture_search/); } }); for (const intercomAsks of [true, false]) { it(`names an intercom child ${intercomAsks ? "readably when pi-intercom takes its route as the intercom ID" : "by its route when pi-intercom does not ask for an ID"}`, async () => { const events = createEventBus(); let sessionName: string | undefined; let beforeAgentStart: ((event: { systemPrompt: string }) => Promise<{ systemPrompt: string } | undefined>) | undefined; registerSubagentPromptRuntime({ events, on(event: string, handler: (payload: { systemPrompt: string }) => Promise<{ systemPrompt: string } | undefined>) { if (event === "before_agent_start") beforeAgentStart = handler; }, getAllTools: () => [{ name: "intercom" }, { name: "contact_supervisor" }], setSessionName(name: string) { sessionName = name; }, } as never, childConfig({ intercomSessionName: "subagent-worker-78f659a3", sessionName: "worker: display name" })); const claimed: string[] = []; if (intercomAsks) events.emit(INTERCOM_SESSION_IDENTITY_EVENT, { version: 1, claim: (id: string) => claimed.push(id) }); await beforeAgentStart?.({ systemPrompt: BASE_PROMPT }); assert.deepEqual(claimed, intercomAsks ? ["subagent-worker-78f659a3"] : []); assert.equal(sessionName, intercomAsks ? "worker: display name" : "subagent-worker-78f659a3"); }); } it("rewrites the final child-visible prompt through before_agent_start", async () => { let beforeAgentStart: ((event: { systemPrompt: string }) => Promise<{ systemPrompt: string } | undefined>) | undefined; registerSubagentPromptRuntime({ on(event: string, handler: (payload: { systemPrompt: string }) => Promise<{ systemPrompt: string } | undefined>) { if (event === "before_agent_start") beforeAgentStart = handler; }, getAllTools: () => [{ name: "intercom" }, { name: "contact_supervisor" }], } as { on(event: string, handler: (payload: { systemPrompt: string }) => Promise<{ systemPrompt: string } | undefined>): void; getAllTools(): Array<{ name: string }> }, childConfig({ inheritProjectContext: false, inheritGlobalContext: true, inheritSkills: false })); assert.ok(beforeAgentStart, "expected before_agent_start handler"); const rewritten = await beforeAgentStart?.({ systemPrompt: BASE_PROMPT }); assert.ok(rewritten); assert.ok(!rewritten.systemPrompt.includes("# Project Context")); assert.ok(!rewritten.systemPrompt.includes("")); assert.ok(rewritten.systemPrompt.includes("Current date: 2026-04-16")); }); it("uses the fanout boundary through before_agent_start for a fanout child", async () => { let beforeAgentStart: ((event: { systemPrompt: string }) => Promise<{ systemPrompt: string } | undefined>) | undefined; registerSubagentPromptRuntime({ on(event: string, handler: (payload: { systemPrompt: string }) => Promise<{ systemPrompt: string } | undefined>) { if (event === "before_agent_start") beforeAgentStart = handler; }, getAllTools: () => [{ name: "intercom" }, { name: "contact_supervisor" }], } as { on(event: string, handler: (payload: { systemPrompt: string }) => Promise<{ systemPrompt: string } | undefined>): void; getAllTools(): Array<{ name: string }> }, childConfig({ fanoutChild: true, inheritProjectContext: true, inheritGlobalContext: true, inheritSkills: true })); const rewritten = await beforeAgentStart?.({ systemPrompt: BASE_PROMPT }); assert.ok(rewritten); assert.ok(rewritten.systemPrompt.startsWith("You are a subagent.")); assert.ok(rewritten.systemPrompt.endsWith(`\n\n${CHILD_FANOUT_BOUNDARY_INSTRUCTIONS}`)); }); it("filters parent-only artifacts from polluted fork context while preserving ordinary history", () => { let contextHandler: ((event: { messages: unknown[] }) => { messages: unknown[] } | undefined) | undefined; registerSubagentPromptRuntime({ on(event: string, handler: (payload: { messages: unknown[] }) => { messages: unknown[] } | undefined) { if (event === "context") contextHandler = handler; }, } as { on(event: string, handler: (payload: { messages: unknown[] }) => { messages: unknown[] } | undefined): void }, childConfig()); const priorParentTurn = { role: "user", content: "Earlier we said planner → worker → reviewers → worker." }; const currentTask = { role: "user", content: "Now implement only the assigned fix." }; const instruction = { role: "custom", customType: "subagent-orchestration-instructions", content: "Subagent orchestration is enabled." }; const slashResult = { role: "custom", customType: "subagent-slash-result", content: "## Orchestration" }; const subagentResult = { role: "toolResult", toolName: "subagent", content: "subagent results" }; const subagentCall = { role: "assistant", content: [{ type: "toolCall", name: "subagent", input: { agent: "worker" } }] }; const watchdogWarning = { role: "custom", customType: SUBAGENT_WATCHDOG_WARNING_TYPE, content: "parent-only" }; const childWatchdogWarning = { role: "custom", customType: SUBAGENT_WATCHDOG_WARNING_TYPE, content: "child-visible", details: { source: "child" } }; const otherCustom = { role: "custom", customType: "other", content: "keep" }; assert.deepEqual(contextHandler?.({ messages: [priorParentTurn, instruction, slashResult, subagentCall, subagentResult, watchdogWarning, childWatchdogWarning, otherCustom, currentTask] }), { messages: [priorParentTurn, otherCustom, currentTask], }); }); describe("Codex child context tool ids on the Responses wire", () => { const model: Model<"openai-codex-responses"> = { id: "codex-test", name: "Codex test", api: "openai-codex-responses", provider: "openai-codex", baseUrl: "https://example.invalid", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 128000, maxTokens: 4096, }; let contextHandler: ((event: { messages: unknown[] }, ctx: { model: typeof model }) => { messages: unknown[] } | undefined) | undefined; registerSubagentPromptRuntime({ on(event: string, handler: typeof contextHandler) { if (event === "context") contextHandler = handler; }, } as never, childConfig()); function history(toolCallId: string): Message[] { return [ { role: "user", content: "Task", timestamp: 1 }, { role: "assistant", api: model.api, provider: model.provider, model: model.id, content: [{ type: "toolCall", id: toolCallId, name: "read", arguments: { path: "README.md" } }], usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }, stopReason: "toolUse", timestamp: 2, }, { role: "toolResult", toolName: "read", toolCallId, content: [{ type: "text", text: "file" }], isError: false, timestamp: 3 }, ]; } function assertWire(messages: Message[], callId: string, itemId: string | undefined): void { const wire = convertResponsesMessages(model, normalizeContext({ messages }), new Set(["openai", "openai-codex", "opencode"])); assert.deepEqual(wire.filter((item) => item.type === "function_call" || item.type === "function_call_output"), [ { type: "function_call", id: itemId, call_id: callId, name: "read", arguments: '{"path":"README.md"}' }, { type: "function_call_output", call_id: callId, output: "file" }, ]); } for (const [label, toolCallId] of [ ["original #903 fixture", "call_N7iYNRPXLl9czpXh3bDyMpIL|fc_0e76718634eca88f016a76fdc89aec81919763fa7858f67a0d"], ["exactly 64 characters per half", `${"a".repeat(64)}|fc_${"b".repeat(61)}`], ["portable mixed charset", "call_AZaz09_-|fc_AZaz09_-"], ["non-fc_ item (serializer omits item id)", "call_read|ctc_item"], ["ordinary portable single id", "call_read-OK_09"], ] as const) { it(`preserves ${label}`, () => { const messages = history(toolCallId); assert.ok(contextHandler); assert.equal(contextHandler({ messages }, { model }), undefined, "preservation must not rewrite context"); const [callId, itemId] = toolCallId.split("|"); assertWire(messages, callId!, itemId?.startsWith("fc_") ? itemId : undefined); }); } for (const [label, toolCallId] of [ ["65-character call half", `${"a".repeat(65)}|fc_item`], ["65-character item half", `call_read|fc_${"b".repeat(62)}`], ["both halves oversized", `${"a".repeat(65)}|fc_${"b".repeat(62)}`], ["empty call half", "|fc_item"], ["empty item half", "call_read|"], ["both halves empty", "|"], ["extra separator", "call_read|fc_item|extra"], ["invalid call charset", "call.read|fc_item"], ["invalid item charset", "call_read|fc_item/bad"], ["whitespace", "call_read|fc_item\n"], ["non-ASCII", "call_read|fc_é"], ["empty single id", ""], ["oversized single id", "a".repeat(65)], ] as const) { it(`uses the deterministic bounded fallback for ${label}`, () => { const messages = history(toolCallId); assert.ok(contextHandler); const context = contextHandler({ messages }, { model }); assert.ok(context); assert.deepEqual(context.messages, stripParentOnlySubagentMessages(messages), "fallback must match the existing default sanitizer"); assert.deepEqual(contextHandler({ messages }, { model }), context, "mapping must be deterministic"); const assistant = context.messages[1] as AssistantMessage; const call = assistant.content[0]; assert.equal(call?.type, "toolCall"); if (call?.type !== "toolCall") assert.fail("expected tool call"); assert.notEqual(call.id, toolCallId); assert.match(call.id, /^[A-Za-z0-9_-]+$/); assert.ok(call.id.length <= 64); assert.equal((context.messages[2] as ToolResultMessage).toolCallId, call.id); assertWire(context.messages as Message[], call.id, undefined); }); } }); it("preserves composite tool ids for non-Codex APIs that normalize them", () => { let contextHandler: ((event: { messages: unknown[] }, ctx: { model?: { api: string } }) => { messages: unknown[] } | undefined) | undefined; registerSubagentPromptRuntime({ on(event: string, handler: (payload: { messages: unknown[] }, ctx: { model?: { api: string } }) => { messages: unknown[] } | undefined) { if (event === "context") contextHandler = handler; }, } as { on(event: string, handler: (payload: { messages: unknown[] }, ctx: { model?: { api: string } }) => { messages: unknown[] } | undefined): void }, childConfig()); const toolCallId = "call_7XJjvAJfk07117JO8LgBCZjY|fc_0e92b09b28010bac016a756e9e79cc8197b01825a5dc3d9eaa"; const messages = [ { role: "user", content: "Task" }, { role: "assistant", content: [{ type: "toolCall", id: toolCallId, name: "read", input: { path: "README.md" } }] }, { role: "toolResult", toolName: "read", toolCallId, content: "file" }, ]; assert.equal(contextHandler?.({ messages }, { model: { api: "openai-responses" } }), undefined); }); it("preserves composite tool ids for cursor-native children", () => { let contextHandler: ((event: { messages: unknown[] }, ctx: { model?: { api: string } }) => { messages: unknown[] } | undefined) | undefined; registerSubagentPromptRuntime({ on(event: string, handler: (payload: { messages: unknown[] }, ctx: { model?: { api: string } }) => { messages: unknown[] } | undefined) { if (event === "context") contextHandler = handler; }, } as { on(event: string, handler: (payload: { messages: unknown[] }, ctx: { model?: { api: string } }) => { messages: unknown[] } | undefined): void }, childConfig()); const toolCallId = "call_7XJjvAJfk07117JO8LgBCZjY\nfc_0e92b09b28010bac016a756e9e79cc8197b01825a5dc3d9eaa"; const messages = [ { role: "user", content: "Task" }, { role: "assistant", content: [{ type: "toolCall", id: toolCallId, name: "read", input: { path: "README.md" } }] }, { role: "toolResult", toolName: "read", toolCallId, content: "file" }, ]; assert.equal(contextHandler?.({ messages }, { model: { api: "cursor-native" } }), undefined); assert.equal((messages[1] as { content: Array<{ id?: unknown }> }).content[0]?.id, toolCallId); assert.equal((messages[2] as { toolCallId?: unknown }).toolCallId, toolCallId); }); it("does not rewrite child context when no parent-only artifacts are present", () => { let contextHandler: ((event: { messages: unknown[] }, ctx: { model?: { api: string } }) => { messages: unknown[] } | undefined) | undefined; registerSubagentPromptRuntime({ on(event: string, handler: (payload: { messages: unknown[] }, ctx: { model?: { api: string } }) => { messages: unknown[] } | undefined) { if (event === "context") contextHandler = handler; }, } as { on(event: string, handler: (payload: { messages: unknown[] }, ctx: { model?: { api: string } }) => { messages: unknown[] } | undefined): void }, childConfig()); const messages = [ { role: "user", content: "Task" }, { role: "toolResult", toolName: "read", content: "file" }, { role: "assistant", content: [{ type: "toolCall", name: "read", input: { path: "README.md" } }] }, ]; assert.equal(contextHandler?.({ messages }, {}), undefined); }); });