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 { discoverAgents } from "../../src/agents/agents.ts"; import { computeMcpServerHash } from "../../src/runs/shared/mcp-direct-tool-allowlist.ts"; import { MCP_RUNTIME_SNAPSHOT_EVENT, MCP_RUNTIME_SNAPSHOT_VERSION, type McpRuntimeSnapshotHost, } from "../../src/runs/shared/mcp-direct-tool-allowlist.ts"; import { TOOL_BUDGET_ENV, TOOL_BUDGET_ZERO_AUTH_ENV, } from "../../src/runs/shared/tool-budget.ts"; import { WAIT_TOOL_DEFAULT_TIMEOUT_MS_ENV, WAIT_TOOL_ENABLED_ENV } from "../../src/runs/background/wait-config.ts"; import { SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV } from "../../src/shared/utils.ts"; import { CHILD_TOOL_DIAGNOSTIC_PATH_ENV, MCP_DIRECT_CHILD_TOOLS_ENV, REQUIRED_CHILD_TOOLS_ENV, } from "../../src/runs/shared/tool-availability.ts"; import { CHILD_WATCHDOG_CONFIG_ENV } from "../../src/watchdog/child-status.ts"; import { PERMISSION_AUDIT_PATH_ENV, PERMISSION_POLICY_ENV, } from "../../src/runs/shared/permissions.ts"; import { SUBAGENT_FANOUT_CHILD_ENV, SUBAGENT_PARENT_CHILD_INDEX_ENV, SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV, SUBAGENT_PARENT_CONTROL_INBOX_ENV, SUBAGENT_PARENT_DEPTH_ENV, SUBAGENT_PARENT_EVENT_SINK_ENV, SUBAGENT_PARENT_PATH_ENV, SUBAGENT_PARENT_ROOT_RUN_ID_ENV, SUBAGENT_PARENT_RUN_ID_ENV, SUBAGENT_PARENT_SESSION_ENV, SUBAGENT_ORCHESTRATOR_SESSION_ID_ENV, SUBAGENT_SUPERVISOR_CHANNEL_DIR_ENV, SUBAGENT_RUN_ID_ENV, PI_INTERCOM_STABLE_ID_ENV, PI_INTERCOM_SESSION_ID_ENV, applyThinkingSuffix, buildPiArgs, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan, } from "../../src/runs/shared/pi-args.ts"; const originalEnv = { HOME: process.env.HOME, USERPROFILE: process.env.USERPROFILE, SELESAI_CODING_AGENT_DIR: process.env.SELESAI_CODING_AGENT_DIR, SELESAI_SUBAGENT_FANOUT_CHILD: process.env.SELESAI_SUBAGENT_FANOUT_CHILD, SELESAI_SUBAGENT_PARENT_EVENT_SINK: process.env.SELESAI_SUBAGENT_PARENT_EVENT_SINK, SELESAI_SUBAGENT_PARENT_CONTROL_INBOX: process.env.SELESAI_SUBAGENT_PARENT_CONTROL_INBOX, SELESAI_SUBAGENT_PARENT_ROOT_RUN_ID: process.env.SELESAI_SUBAGENT_PARENT_ROOT_RUN_ID, SELESAI_SUBAGENT_PARENT_RUN_ID: process.env.SELESAI_SUBAGENT_PARENT_RUN_ID, SELESAI_SUBAGENT_PARENT_CHILD_INDEX: process.env.SELESAI_SUBAGENT_PARENT_CHILD_INDEX, SELESAI_SUBAGENT_PARENT_DEPTH: process.env.SELESAI_SUBAGENT_PARENT_DEPTH, SELESAI_SUBAGENT_PARENT_PATH: process.env.SELESAI_SUBAGENT_PARENT_PATH, SELESAI_SUBAGENT_PARENT_CAPABILITY_TOKEN: process.env.SELESAI_SUBAGENT_PARENT_CAPABILITY_TOKEN, SELESAI_SUBAGENT_PARENT_SESSION: process.env.SELESAI_SUBAGENT_PARENT_SESSION, SELESAI_SUBAGENT_RUN_ID: process.env.SELESAI_SUBAGENT_RUN_ID, [MCP_DIRECT_CHILD_TOOLS_ENV]: process.env[MCP_DIRECT_CHILD_TOOLS_ENV], [TOOL_BUDGET_ZERO_AUTH_ENV]: process.env[TOOL_BUDGET_ZERO_AUTH_ENV], [SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV]: process.env[SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV], [PI_INTERCOM_STABLE_ID_ENV]: process.env[PI_INTERCOM_STABLE_ID_ENV], [PI_INTERCOM_SESSION_ID_ENV]: process.env[PI_INTERCOM_SESSION_ID_ENV], MCP_HASH_ROOT: process.env.MCP_HASH_ROOT, MCP_HASH_TOKEN: process.env.MCP_HASH_TOKEN, SELESAI_SUBAGENT_TASK_DELIVERY: process.env.SELESAI_SUBAGENT_TASK_DELIVERY, }; const originalCwd = process.cwd(); const tempRoots: string[] = []; interface McpFixture { root: string; agentDir: string; projectDir: string; } function createMcpFixture(): McpFixture { const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-args-mcp-")); tempRoots.push(root); const home = path.join(root, "home"); const agentDir = path.join(home, ".selesai", "agent"); const projectDir = path.join(root, "project"); fs.mkdirSync(agentDir, { recursive: true }); fs.mkdirSync(projectDir, { recursive: true }); process.env.HOME = home; process.env.USERPROFILE = home; process.env.SELESAI_CODING_AGENT_DIR = agentDir; process.chdir(projectDir); return { root, agentDir, projectDir }; } function writeJson(filePath: string, value: unknown): void { fs.mkdirSync(path.dirname(filePath), { recursive: true }); fs.writeFileSync(filePath, JSON.stringify(value, null, 2), "utf-8"); } function writeMcpFixture( fixture: McpFixture, options: { serverName?: string; definition?: Record; settings?: Record; tools?: Array<{ name: string; description?: string }>; resources?: Array<{ name: string; uri: string; description?: string }>; configPath?: string; cachedAt?: number; configHash?: string; } = {}, ): void { const serverName = options.serverName ?? "chrome-devtools"; const definition = { command: "npx", args: ["chrome-devtools-mcp"], ...(options.definition ?? {}), }; writeJson(options.configPath ?? path.join(fixture.agentDir, "mcp.json"), { ...(options.settings ? { settings: options.settings } : {}), mcpServers: { [serverName]: definition, }, }); writeJson(path.join(fixture.agentDir, "mcp-cache.json"), { version: 1, servers: { [serverName]: { configHash: options.configHash ?? computeMcpServerHash(definition), cachedAt: options.cachedAt ?? Date.now(), tools: options.tools ?? [ { name: "take_screenshot" }, { name: "click" }, ], resources: options.resources ?? [], }, }, }); } afterEach(() => { process.chdir(originalCwd); for (const [key, value] of Object.entries(originalEnv)) { if (value === undefined) { delete process.env[key]; } else { process.env[key] = value; } } for (const root of tempRoots.splice(0)) { fs.rmSync(root, { recursive: true, force: true }); } }); describe("buildPiArgs session wiring", () => { it("keeps empty-extension warnings in the resolved plan without logging", () => { const originalWarn = console.warn; const warnCalls: string[] = []; console.warn = (...args: unknown[]) => { warnCalls.push(args.map(String).join(" ")); }; try { const plan = resolvePiLaunchToolPlan({ extensions: [], agentName: "quota-reviewer", }); assert.equal(plan.disableAmbientExtensions, true); assert.equal(plan.warnings.length, 1); assert.match(plan.warnings[0]!, /extensions: \[\] override for agent 'quota-reviewer'/); assert.match(plan.warnings[0]!, /disables ALL ambient extensions/); assert.deepEqual(warnCalls, []); } finally { console.warn = originalWarn; } }); it("keeps empty-extension warnings quiet across repeated argument builds", () => { const originalWarn = console.warn; const warnCalls: string[] = []; console.warn = (...args: unknown[]) => { warnCalls.push(args.map(String).join(" ")); }; try { buildPiArgs({ baseArgs: [], task: "Test warning output.", inheritProjectContext: true, inheritSkills: true, extensions: [], childAgentName: "quota-reviewer", }); buildPiArgs({ baseArgs: [], task: "Retry warning output.", inheritProjectContext: true, inheritSkills: true, extensions: [], childAgentName: "quota-reviewer", }); assert.deepEqual(warnCalls, []); } finally { console.warn = originalWarn; } }); it("does not warn when extensions are omitted (ambient extensions inherited normally)", () => { const plan = resolvePiLaunchToolPlan({}); assert.equal(plan.disableAmbientExtensions, false); assert.deepEqual(plan.warnings, []); }); it("does not warn when extensions is a non-empty explicit list", () => { const plan = resolvePiLaunchToolPlan({ extensions: ["./tools/provider-ext.ts"] }); assert.equal(plan.disableAmbientExtensions, true); assert.deepEqual(plan.warnings, []); }); it("projects launch-resolved extension identifiers without raw paths", () => { const privateExt = path.join( os.tmpdir(), "private-extension-root", "secret-extension.ts", ); const toolExt = path.join( os.tmpdir(), "tool-extension-root", "tool-extension.ts", ); const plan = resolvePiLaunchToolPlan({ tools: ["read", toolExt], extensions: [privateExt], subagentOnlyExtensions: ["package-extension"], }); const projection = projectLaunchResolvedChildExtensions(plan); assert.equal(projection.version, 1); assert.equal(projection.source, "launch-resolved"); assert.equal(projection.disableAmbientExtensions, true); assert.ok( projection.runtime.length >= 1, `expected at least 1 runtime extension, got ${projection.runtime.length}`, ); assert.equal(projection.configured.length, 3); assert.ok( projection.effective.length >= 4, `expected at least 4 effective extensions, got ${projection.effective.length}`, ); for (const id of [ ...projection.runtime, ...projection.configured, ...projection.effective, ]) { assert.match(id, /^sha256:[a-f0-9]{16}$/); } assert.ok( !JSON.stringify(projection).includes(os.tmpdir()), "projection should not expose raw extension paths", ); }); it("uses --session when sessionFile is provided", () => { const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-args-session-")); try { const sessionFile = path.join(tempDir, "nested", "session.jsonl"); const { args } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: true, sessionFile, sessionDir: "/tmp/should-not-be-used", inheritProjectContext: false, inheritSkills: false, }); assert.ok(args.includes("--session")); assert.ok(args.includes(sessionFile)); assert.ok(fs.existsSync(path.dirname(sessionFile))); assert.ok( !args.includes("--session-dir"), "--session-dir should not be emitted with --session", ); assert.ok( !args.includes("--no-session"), "--no-session should not be emitted with --session", ); } finally { fs.rmSync(tempDir, { recursive: true, force: true }); } }); it("keeps fresh mode behavior (sessionDir + no session file)", () => { const { args } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: true, sessionDir: "/tmp/subagent-sessions", inheritProjectContext: false, inheritSkills: false, }); assert.ok(args.includes("--session-dir")); assert.ok(args.includes("/tmp/subagent-sessions")); assert.ok(!args.includes("--session")); }); it("emits explicit parent session env for permission forwarding", () => { process.env.SELESAI_SUBAGENT_PARENT_SESSION = "inherited-parent"; const { env } = buildPiArgs({ parentSessionId: "direct-parent", baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, }); assert.equal(env[SUBAGENT_PARENT_SESSION_ENV], "direct-parent"); }); it("passes the child display session name through as PI_SUBAGENT_SESSION_NAME", () => { const { env } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, sessionName: "worker: Fix the flaky test", }); assert.equal(env.PI_SUBAGENT_SESSION_NAME, "worker: Fix the flaky test"); }); it("omits PI_SUBAGENT_SESSION_NAME when no session name is provided", () => { const { env } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, }); assert.equal(env.PI_SUBAGENT_SESSION_NAME, undefined); }); it("falls back to inherited parent session env for permission forwarding", () => { process.env.SELESAI_SUBAGENT_PARENT_SESSION = "inherited-parent"; const { env } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, }); assert.equal(env[SUBAGENT_PARENT_SESSION_ENV], "inherited-parent"); }); it("passes the effective wait-tool setting explicitly to children", () => { assert.equal( buildPiArgs({ baseArgs: [], task: "test", sessionEnabled: false, inheritProjectContext: true, inheritSkills: true, waitToolEnabled: false, }).env[WAIT_TOOL_ENABLED_ENV], "false", ); assert.equal( buildPiArgs({ baseArgs: [], task: "test", sessionEnabled: false, inheritProjectContext: true, inheritSkills: true, waitToolEnabled: true, }).env[WAIT_TOOL_ENABLED_ENV], "true", ); assert.equal( buildPiArgs({ baseArgs: [], task: "test", sessionEnabled: false, inheritProjectContext: true, inheritSkills: true, waitToolDefaultTimeoutMs: 12_000, }).env[WAIT_TOOL_DEFAULT_TIMEOUT_MS_ENV], "12000", ); }); it("passes child watchdog config only when explicitly provided", () => { const withoutWatchdog = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, }); assert.equal(withoutWatchdog.env[CHILD_WATCHDOG_CONFIG_ENV], undefined); const withWatchdog = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, childWatchdog: { enabled: true, runId: "run-1", agent: "worker", childIndex: 2, watchdogTailTimeoutMs: 1234, agentEndTimeoutMs: 500, maxWarnings: 1, lsp: { enabled: false, timeoutMs: 50, maxFiles: 2, maxDiagnostics: 3 }, autoFollowBlockers: true, autoFollowMaxAttempts: 3, stalemateRepeats: 2, }, }); const encoded = withWatchdog.env[CHILD_WATCHDOG_CONFIG_ENV]; assert.equal(typeof encoded, "string"); assert.deepEqual(JSON.parse(encoded ?? "{}"), { enabled: true, runId: "run-1", agent: "worker", childIndex: 2, watchdogTailTimeoutMs: 1234, agentEndTimeoutMs: 500, maxWarnings: 1, lsp: { enabled: false, timeoutMs: 50, maxFiles: 2, maxDiagnostics: 3 }, autoFollowBlockers: true, autoFollowMaxAttempts: 3, stalemateRepeats: 2, }); }); }); describe("buildPiArgs model wiring", () => { it("uses --model for provider-qualified model ids", () => { const { args } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, model: "openai-codex/gpt-5.4-mini", inheritProjectContext: false, inheritSkills: false, }); assert.ok(args.includes("--model")); assert.ok(args.includes("openai-codex/gpt-5.4-mini")); assert.ok(!args.includes("--models")); }); it("uses --model for bare model ids too", () => { const { args } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, model: "kimi-k2.5", inheritProjectContext: false, inheritSkills: false, }); assert.ok(args.includes("--model")); assert.ok(args.includes("kimi-k2.5")); assert.ok(!args.includes("--models")); }); it("preserves thinking suffixes on model args", () => { const { args } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, model: "openai-codex/gpt-5.4-mini", thinking: "high", inheritProjectContext: false, inheritSkills: false, }); assert.equal( applyThinkingSuffix("openai-codex/gpt-5.4-mini", "high"), "openai-codex/gpt-5.4-mini:high", ); assert.ok(args.includes("--model")); assert.ok(args.includes("openai-codex/gpt-5.4-mini:high")); }); it("passes max thinking through to the model argument", () => { const { args } = buildPiArgs({ baseArgs: ["-p"], task: "hello", model: "openai/gpt-5", thinking: "max", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, }); assert.equal( applyThinkingSuffix("openai/gpt-5", "max"), "openai/gpt-5:max", ); assert.equal( applyThinkingSuffix("openai/gpt-5:max", "high"), "openai/gpt-5:max", ); assert.equal( applyThinkingSuffix("openai/gpt-5:max", "high", true), "openai/gpt-5:high", ); assert.ok(args.includes("--model")); assert.ok(args.includes("openai/gpt-5:max")); }); it("passes explicit thinking off through to the model arg", () => { const { args } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, model: "anthropic/claude-haiku-4-5", thinking: "off", inheritProjectContext: false, inheritSkills: false, }); assert.equal( applyThinkingSuffix("anthropic/claude-haiku-4-5", "off"), "anthropic/claude-haiku-4-5:off", ); assert.equal( applyThinkingSuffix("anthropic/claude-haiku-4-5:high", "off", true), "anthropic/claude-haiku-4-5:off", ); assert.ok(args.includes("--model")); assert.ok(args.includes("anthropic/claude-haiku-4-5:off")); }); it("does not append a thinking suffix for boolean false", () => { const model = "glm-5.2-short-fast"; const once = applyThinkingSuffix(model, false); assert.equal(once, model); assert.equal(applyThinkingSuffix(once, false), model); const { args } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, model, thinking: false, inheritProjectContext: false, inheritSkills: false, }); assert.ok(args.includes("--model")); assert.ok(args.includes(model)); assert.ok(!args.some((arg) => arg.includes(":false"))); }); it("leaves provider-specific model suffixes untouched when thinking is disabled", () => { const model = "openai-compatible/qwen2.5-coder:7b"; const { args } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, model, inheritProjectContext: false, inheritSkills: false, }); assert.ok(args.includes("--model")); assert.ok(args.includes(model)); assert.ok(!args.includes(`${model}:high`)); }); }); describe("buildPiArgs task delivery", () => { const longTask = "x".repeat(8001); function taskFileFromArgs(args: string[]): string | undefined { const ref = args.find((arg) => arg.startsWith("@") && arg.endsWith("task.md")); return ref ? ref.slice(1) : undefined; } it("delivers short tasks inline by default", () => { const { args } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, }); assert.ok(args.includes("Task: hello")); assert.equal(taskFileFromArgs(args), undefined); }); it("delivers tasks over the argv limit via a temp file by default", () => { const { args } = buildPiArgs({ baseArgs: ["-p"], task: longTask, sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, }); const taskFile = taskFileFromArgs(args); assert.ok(taskFile, "expected an @task.md argv reference"); assert.equal(fs.readFileSync(taskFile, "utf-8"), `Task: ${longTask}`); assert.ok(!args.includes(`Task: ${longTask}`)); }); it("delivers short tasks via file when SELESAI_SUBAGENT_TASK_DELIVERY=file", () => { process.env.SELESAI_SUBAGENT_TASK_DELIVERY = "file"; const { args } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, }); const taskFile = taskFileFromArgs(args); assert.ok(taskFile, "expected an @task.md argv reference"); assert.equal(fs.readFileSync(taskFile, "utf-8"), "Task: hello"); assert.ok(!args.includes("Task: hello")); }); it("falls back to auto when SELESAI_SUBAGENT_TASK_DELIVERY is invalid", () => { process.env.SELESAI_SUBAGENT_TASK_DELIVERY = "carrier-pigeon"; const { args } = buildPiArgs({ baseArgs: ["-p"], task: longTask, sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, }); assert.ok(taskFileFromArgs(args), "expected file delivery for over-limit task"); }); it("lets the per-launch taskDelivery override beat the env setting", () => { delete process.env.SELESAI_SUBAGENT_TASK_DELIVERY; const { args } = buildPiArgs({ baseArgs: ["-p"], task: "hello", taskDelivery: "file", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, }); assert.ok(taskFileFromArgs(args), "expected an @task.md argv reference"); assert.ok(!args.includes("Task: hello")); }); }); describe("buildPiArgs system prompt mode wiring", () => { it("uses --append-system-prompt by default", () => { const { args } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, systemPrompt: "You are a worker", inheritProjectContext: false, inheritSkills: false, }); assert.ok(args.includes("--append-system-prompt")); assert.ok(!args.includes("--system-prompt")); }); it("uses --system-prompt when systemPromptMode=replace", () => { const { args } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, systemPrompt: "You are a worker", systemPromptMode: "replace", inheritProjectContext: false, inheritSkills: false, }); assert.ok(args.includes("--system-prompt")); assert.ok(!args.includes("--append-system-prompt")); }); it("injects the subagent prompt runtime extension and env flags", () => { const { args, env } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritGlobalContext: false, inheritSkills: true, }); const extensionArgs = args.filter( (arg, index) => args[index - 1] === "--extension", ); assert.ok( extensionArgs.some((arg) => arg.endsWith( path.join("src", "runs", "shared", "subagent-prompt-runtime.ts"), ), ), ); assert.ok(args.includes("--no-context-files")); assert.equal(env.SELESAI_SUBAGENT_CHILD, "1"); assert.equal(env.SELESAI_SUBAGENT_INHERIT_PROJECT_CONTEXT, "0"); assert.equal(env.SELESAI_SUBAGENT_INHERIT_GLOBAL_CONTEXT, "0"); assert.equal(env.SELESAI_SUBAGENT_INHERIT_SKILLS, "1"); }); it("propagates the global context inheritance flag through env", () => { const { env } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: true, inheritGlobalContext: true, inheritSkills: true, }); assert.equal(env.SELESAI_SUBAGENT_INHERIT_PROJECT_CONTEXT, "1"); assert.equal(env.SELESAI_SUBAGENT_INHERIT_GLOBAL_CONTEXT, "1"); }); it("keeps context file loading enabled when project context is inherited", () => { const { args } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: true, inheritSkills: true, }); assert.equal(args.includes("--no-context-files"), false); }); it("passes tool budget through env", () => { const { env } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, toolBudget: { soft: 2, hard: 3, block: ["read"] }, }); assert.deepEqual(JSON.parse(env[TOOL_BUDGET_ENV] ?? "{}"), { soft: 2, hard: 3, block: ["read"], }); assert.equal(env[TOOL_BUDGET_ZERO_AUTH_ENV], undefined); }); it("clears inherited zero tool-budget authorization unless this launch owns it", () => { process.env[TOOL_BUDGET_ZERO_AUTH_ENV] = "1"; const inherited = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, toolBudget: { hard: 1, block: ["read"] }, }); assert.equal(inherited.env[TOOL_BUDGET_ZERO_AUTH_ENV], undefined); const owned = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, toolBudget: { hard: 0, block: "*" }, allowZeroToolBudget: true, }); assert.equal(owned.env[TOOL_BUDGET_ZERO_AUTH_ENV], "1"); }); it("clears inherited MCP direct-tool metadata for non-MCP launches", () => { for (const staleValue of [JSON.stringify(["fixture_search"]), "not-json"]) { process.env[MCP_DIRECT_CHILD_TOOLS_ENV] = staleValue; const { env } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["read", "fixture_search"], }); assert.equal(env[MCP_DIRECT_CHILD_TOOLS_ENV], undefined); } }); it("passes child intercom and orchestrator metadata through env", () => { process.env[PI_INTERCOM_STABLE_ID_ENV] = "subagent-chat-parent"; process.env[PI_INTERCOM_SESSION_ID_ENV] = "session-parent-runtime"; const { env } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: true, inheritSkills: true, intercomSessionName: "subagent-worker-78f659a3", orchestratorIntercomTarget: "subagent-chat-parent", parentSessionId: "session-parent-123", runId: "78f659a3", childAgentName: "worker", childIndex: 2, }); assert.equal( env.SELESAI_SUBAGENT_INTERCOM_SESSION_NAME, "subagent-worker-78f659a3", ); assert.equal(env[PI_INTERCOM_STABLE_ID_ENV], "subagent-worker-78f659a3"); assert.equal(env[PI_INTERCOM_SESSION_ID_ENV], undefined); assert.equal(env.SELESAI_SUBAGENT_ORCHESTRATOR_TARGET, "subagent-chat-parent"); assert.equal( env[SUBAGENT_ORCHESTRATOR_SESSION_ID_ENV], "session-parent-123", ); assert.equal(env.SELESAI_SUBAGENT_RUN_ID, "78f659a3"); assert.equal(env.SELESAI_SUBAGENT_CHILD_AGENT, "worker"); assert.equal(env.SELESAI_SUBAGENT_CHILD_INDEX, "2"); assert.equal(typeof env[SUBAGENT_SUPERVISOR_CHANNEL_DIR_ENV], "string"); assert.match( env[SUBAGENT_SUPERVISOR_CHANNEL_DIR_ENV] ?? "", /supervisor-channels/, ); }); it("clears inherited pi-intercom identity when no child intercom session name is set", () => { process.env[PI_INTERCOM_STABLE_ID_ENV] = "subagent-chat-parent"; process.env[PI_INTERCOM_SESSION_ID_ENV] = "session-parent-runtime"; const { env } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: true, inheritSkills: true, }); assert.equal(env[PI_INTERCOM_STABLE_ID_ENV], undefined); assert.equal(env[PI_INTERCOM_SESSION_ID_ENV], undefined); }); it("creates a private permission audit path without enabling the supervisor channel", () => { const { env, tempDir } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: true, inheritSkills: true, parentSessionId: "session-parent-123", runId: "permission-run", childAgentName: "worker", childIndex: 3, permissionRules: { write: "ask" }, }); assert.equal(env.SELESAI_SUBAGENT_ORCHESTRATOR_TARGET, undefined); assert.equal(env[PERMISSION_POLICY_ENV], JSON.stringify({ write: "ask" })); assert.equal(env[SUBAGENT_SUPERVISOR_CHANNEL_DIR_ENV], undefined); assert.equal( env[PERMISSION_AUDIT_PATH_ENV], path.join(tempDir!, "permission-audit.jsonl"), ); }); it("does not create a supervisor channel without an exact parent session id", () => { const { env } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: true, inheritSkills: true, orchestratorIntercomTarget: "subagent-chat-parent", runId: "78f659a3", childAgentName: "worker", childIndex: 2, }); assert.equal(env[SUBAGENT_ORCHESTRATOR_SESSION_ID_ENV], undefined); assert.equal(env[SUBAGENT_SUPERVISOR_CHANNEL_DIR_ENV], undefined); }); it("emits explicit builtin tool allowlists", () => { const { args, env, toolDiagnosticPath } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: [ "read", "grep", "find", "ls", "bash", "edit", "write", "contact_supervisor", ], }); const toolsArg = args[args.indexOf("--tools") + 1]; assert.equal( toolsArg, "read,grep,find,ls,bash,edit,write,contact_supervisor", ); // Supervisor-coordination names are runtime-registered in children, so // they are never strict requirements even when named explicitly (#1207). assert.deepEqual( JSON.parse(env[REQUIRED_CHILD_TOOLS_ENV] ?? "[]"), ["read", "grep", "find", "ls", "bash", "edit", "write"], ); assert.equal(env[CHILD_TOOL_DIAGNOSTIC_PATH_ENV], toolDiagnosticPath); }); it("strips the legacy supervisor pairing from requirements", () => { const { args, env } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["read", "intercom", "contact_supervisor"], }); assert.equal( args[args.indexOf("--tools") + 1], "read,intercom,contact_supervisor", ); assert.deepEqual( JSON.parse(env[REQUIRED_CHILD_TOOLS_ENV] ?? "[]"), ["read"], ); }); it("keeps a lone explicit intercom tool strict", () => { const { args, env } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["read", "intercom"], }); assert.equal( args[args.indexOf("--tools") + 1], "read,intercom", ); assert.deepEqual( JSON.parse(env[REQUIRED_CHILD_TOOLS_ENV] ?? "[]"), ["read", "intercom"], ); }); it("launches the bundled reviewer without mutation-capable tools", () => { const reviewer = discoverAgents(process.cwd(), "project").agents.find((agent) => agent.name === "reviewer"); assert.ok(reviewer, "expected bundled reviewer"); const { args } = buildPiArgs({ baseArgs: ["-p"], task: "Review this change.", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: reviewer.tools, }); assert.equal(args[args.indexOf("--tools") + 1], "read,grep,find,ls"); assert.doesNotMatch(args[args.indexOf("--tools") + 1] ?? "", /\b(?:bash|edit|write)\b/); }); it("keeps structured_output available under explicit tool allowlists", () => { const { args, env } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["read", "fixture_search"], structuredOutput: { schema: { type: "object", properties: {}, additionalProperties: false }, schemaPath: "/tmp/schema.json", outputPath: "/tmp/output.json", }, }); assert.equal( args[args.indexOf("--tools") + 1], "read,fixture_search,structured_output", ); assert.deepEqual(JSON.parse(env[REQUIRED_CHILD_TOOLS_ENV] ?? "[]"), [ "read", "fixture_search", "structured_output", ]); }); it("forwards the Pi package root to child processes for host peer resolution", () => { process.env[SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV] = "/opt/pi-coding-agent"; const { env } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, }); assert.equal(env[SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV], "/opt/pi-coding-agent"); }); it("adds read to explicit tool allowlists when skills must be loaded lazily", () => { const { args } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, requireReadTool: true, tools: ["bash"], }); assert.equal(args[args.indexOf("--tools") + 1], "read,bash"); }); it("does not duplicate read in explicit tool allowlists for lazy skills", () => { const { args } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, requireReadTool: true, tools: ["read", "bash"], }); assert.equal(args[args.indexOf("--tools") + 1], "read,bash"); }); it("includes adapter tool filters and protocol version in MCP cache identity", () => { const base = { command: "npx", args: ["browser-mcp"] }; assert.notEqual( computeMcpServerHash(base), computeMcpServerHash({ ...base, includeTools: ["browser_navigate"] }), ); assert.notEqual( computeMcpServerHash(base), computeMcpServerHash({ ...base, protocolVersion: "2025-03-26" }), ); }); it("matches pi-mcp-adapter 2.26.0 metadata cache hashes", () => { process.env.MCP_HASH_ROOT = "/tmp/mcp-root"; process.env.MCP_HASH_TOKEN = "token-value"; assert.deepEqual( [ computeMcpServerHash({ command: "npx", args: ["-y", "browser-mcp"], env: { ROOT: "{env:MCP_HASH_ROOT}", SECRET_COMMAND: "!op read test" }, cwd: "${MCP_HASH_ROOT}/server", exposeResources: false, includeTools: ["browser_navigate"], excludeTools: ["browser_close"], }), computeMcpServerHash({ url: "https://example.test/$env:MCP_HASH_TOKEN", headers: { Authorization: "Bearer ${MCP_HASH_TOKEN}", Secret: "!op read test", }, requestHeadersCommand: { command: "headers --token $env:MCP_HASH_TOKEN", args: ["--root", "{env:MCP_HASH_ROOT}"], env: { TOKEN: "${MCP_HASH_TOKEN}", SECRET_COMMAND: "!op read test" }, timeoutMs: 2500, }, auth: "bearer", bearerTokenEnv: "MCP_HASH_TOKEN", }), computeMcpServerHash({ socket: "{env:MCP_HASH_ROOT}/rmcp.sock" }), ], [ "2c6d629872df1d4243906b17c57ebf688d8be0426e471bc2b0c956d952823c63", "a7d142f0300b3fc6cce3039823eab3d9da9635a20f8d0c5d1c414d6c2da83968", "592c6a094c7ba78133bffa5498e268e70dac7b9c450f9c23d9a46585a54edb50", ], ); }); it("augments explicit builtin allowlists with selected direct MCP tool names", () => { const fixture = createMcpFixture(); writeMcpFixture(fixture); const { args, env } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["read", "bash"], mcpDirectTools: ["chrome-devtools"], }); assert.equal( args[args.indexOf("--tools") + 1], "read,bash,chrome_devtools_take_screenshot,chrome_devtools_click", ); assert.equal(env.MCP_DIRECT_TOOLS, "chrome-devtools"); assert.equal( env[REQUIRED_CHILD_TOOLS_ENV], JSON.stringify([ "read", "bash", "chrome_devtools_take_screenshot", "chrome_devtools_click", ]), ); assert.equal( env[MCP_DIRECT_CHILD_TOOLS_ENV], JSON.stringify([ "chrome_devtools_take_screenshot", "chrome_devtools_click", ]), ); }); it("resolves direct MCP tool selections from adapter-style protocol version cache entries", () => { const fixture = createMcpFixture(); writeMcpFixture(fixture, { serverName: "github", definition: { command: "github-mcp", protocolVersion: "2025-03-26" }, configHash: "e2be19d9c42c791c8c125397cc9a5c1b592effe15c422a7f7d5fbf2eb6397251", tools: [{ name: "search_repositories" }], }); const { args } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["read"], mcpDirectTools: ["github/search_repositories"], }); assert.equal(args[args.indexOf("--tools") + 1], "read,github_search_repositories"); }); it("hands runtime MCP snapshots to child launches for server and server/tool selectors", () => { const fixture = createMcpFixture(); const serverName = "runtime-github"; const definition = { command: "runtime-github", args: ["--stdio"] }; writeJson(path.join(fixture.agentDir, "mcp.json"), { mcpServers: {} }); writeJson(path.join(fixture.agentDir, "mcp-cache.json"), { version: 1, servers: { [serverName]: { configHash: computeMcpServerHash(definition), cachedAt: Date.now(), tools: [{ name: "search_repositories" }, { name: "create_issue" }], }, }, }); const requestedNames: string[] = []; const runtimeSnapshotHost: McpRuntimeSnapshotHost = { events: { emit(event, request) { assert.equal(event, MCP_RUNTIME_SNAPSHOT_EVENT); assert.equal(request.version, MCP_RUNTIME_SNAPSHOT_VERSION); requestedNames.push(request.name); request.result = { ok: true, snapshot: { name: request.name, definition: structuredClone(definition), runtime: true, persisted: false, }, }; }, }, }; const serverLaunch = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["read"], mcpDirectTools: [serverName], systemPrompt: "system", runtimeSnapshotHost, }); const toolLaunch = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["read"], mcpDirectTools: [`${serverName}/search_repositories`], runtimeSnapshotHost, }); assert.deepEqual(requestedNames, [serverName, serverName]); assert.equal(serverLaunch.args[serverLaunch.args.indexOf("--tools") + 1], "read,runtime_github_search_repositories,runtime_github_create_issue"); assert.equal(toolLaunch.args[toolLaunch.args.indexOf("--tools") + 1], "read,runtime_github_search_repositories"); assert.ok(serverLaunch.args.indexOf("--mcp-config") < serverLaunch.args.indexOf("Task: hello")); for (const launch of [serverLaunch, toolLaunch]) { const configPath = launch.args[launch.args.indexOf("--mcp-config") + 1]; assert.ok(configPath); assert.equal(path.dirname(configPath), launch.tempDir); assert.deepEqual(JSON.parse(fs.readFileSync(configPath, "utf-8")), { mcpServers: { [serverName]: definition }, }); } assert.deepEqual( [serverLaunch.env.MCP_DIRECT_TOOLS, toolLaunch.env.MCP_DIRECT_TOOLS], [serverName, `${serverName}/search_repositories`], ); assert.deepEqual(JSON.parse(serverLaunch.env[MCP_DIRECT_CHILD_TOOLS_ENV]!), [ "runtime_github_search_repositories", "runtime_github_create_issue", ]); assert.deepEqual(JSON.parse(toolLaunch.env[MCP_DIRECT_CHILD_TOOLS_ENV]!), [ "runtime_github_search_repositories", ]); }); it("fails closed when a selected runtime MCP server has no snapshot", () => { const fixture = createMcpFixture(); const serverName = "runtime-missing"; const definition = { command: "runtime-missing", args: ["--stdio"] }; writeJson(path.join(fixture.agentDir, "mcp.json"), { mcpServers: {} }); writeJson(path.join(fixture.agentDir, "mcp-cache.json"), { version: 1, servers: { [serverName]: { configHash: computeMcpServerHash(definition), cachedAt: Date.now(), tools: [{ name: "search" }], }, }, }); const runtimeSnapshotHost: McpRuntimeSnapshotHost = { events: { emit(_event, request) { request.result = { ok: false, error: new Error("runtime server is unavailable") }; }, }, }; assert.throws(() => buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["read"], mcpDirectTools: [serverName], runtimeSnapshotHost, }), /Unresolved MCP direct-tool selectors: runtime-missing\./); }); it("fails closed on malformed selected runtime MCP server fields", () => { const fixture = createMcpFixture(); const serverName = "runtime-filtered"; const definition = { command: "runtime-filtered", includeTools: "safe_only" } as unknown as Parameters[0]; writeJson(path.join(fixture.agentDir, "mcp.json"), { mcpServers: {} }); writeJson(path.join(fixture.agentDir, "mcp-cache.json"), { version: 1, servers: { [serverName]: { configHash: computeMcpServerHash(definition), cachedAt: Date.now(), tools: [{ name: "safe_only" }, { name: "dangerous" }], }, }, }); const runtimeSnapshotHost: McpRuntimeSnapshotHost = { events: { emit(_event, request) { request.result = { ok: true, snapshot: { name: request.name, definition, runtime: true, persisted: false } }; }, }, }; assert.throws(() => buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["read"], mcpDirectTools: [serverName], runtimeSnapshotHost, }), /Unresolved MCP direct-tool selectors: runtime-filtered\./); }); it("does not serialize runtime MCP servers denied by a capability ceiling", () => { const fixture = createMcpFixture(); const definitions = { "runtime-a": { command: "runtime-a", args: ["--stdio"] }, "runtime-b": { command: "runtime-b", args: ["--stdio"] }, }; writeJson(path.join(fixture.agentDir, "mcp.json"), { mcpServers: {} }); writeJson(path.join(fixture.agentDir, "mcp-cache.json"), { version: 1, servers: { "runtime-a": { configHash: computeMcpServerHash(definitions["runtime-a"]), cachedAt: Date.now(), tools: [{ name: "search" }] }, "runtime-b": { configHash: computeMcpServerHash(definitions["runtime-b"]), cachedAt: Date.now(), tools: [{ name: "secret" }] }, }, }); const runtimeSnapshotHost: McpRuntimeSnapshotHost = { events: { emit(_event, request) { const definition = definitions[request.name as keyof typeof definitions]; if (!definition) { request.result = { ok: false, error: new Error(`unknown runtime server ${request.name}`) }; return; } request.result = { ok: true, snapshot: { name: request.name, definition, runtime: true, persisted: false } }; }, }, }; const launch = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["read"], mcpDirectTools: ["runtime-a", "runtime-b"], capabilityCeiling: { version: 1, allowedTools: ["read", "runtime_a_search"], denyExtensions: false, sources: ["test"] }, runtimeSnapshotHost, }); assert.equal(launch.args[launch.args.indexOf("--tools") + 1], "read,runtime_a_search"); const configPath = launch.args[launch.args.indexOf("--mcp-config") + 1]; assert.ok(configPath); assert.deepEqual(JSON.parse(fs.readFileSync(configPath, "utf-8")), { mcpServers: { "runtime-a": definitions["runtime-a"] }, }); assert.deepEqual(JSON.parse(launch.env[MCP_DIRECT_CHILD_TOOLS_ENV]!), ["runtime_a_search"]); }); it("emits --no-tools for explicit empty tool allowlists", () => { for (const requireReadTool of [false, true]) { const { args, env } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, requireReadTool, tools: [], }); assert.ok(args.includes("--no-tools")); assert.equal(args.includes("--tools"), false); assert.equal(env.MCP_DIRECT_TOOLS, "__none__"); } }); it("restricts MCP-only agents to selected direct MCP tool names", () => { for (const requireReadTool of [false, true]) { const fixture = createMcpFixture(); writeMcpFixture(fixture); const { args, env } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, requireReadTool, mcpDirectTools: ["chrome-devtools"], }); assert.equal( args[args.indexOf("--tools") + 1], "chrome_devtools_take_screenshot,chrome_devtools_click", ); assert.equal(env.MCP_DIRECT_TOOLS, "chrome-devtools"); } }); it("fails closed with a deterministic diagnostic when MCP selectors cannot be resolved", () => { for (const requireReadTool of [false, true]) { const fixture = createMcpFixture(); writeJson(path.join(fixture.agentDir, "mcp.json"), { mcpServers: { "chrome-devtools": { command: "npx", args: ["chrome-devtools-mcp"] }, }, }); assert.throws(() => buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, requireReadTool, mcpDirectTools: ["chrome-devtools"], }), /Unresolved MCP direct-tool selectors: chrome-devtools\./); } }); it("supports direct MCP server/tool filters", () => { const fixture = createMcpFixture(); writeMcpFixture(fixture, { serverName: "github", definition: { command: "github-mcp" }, tools: [{ name: "search_repositories" }, { name: "create_issue" }], }); const { args } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["read"], mcpDirectTools: ["github/search_repositories"], }); assert.equal( args[args.indexOf("--tools") + 1], "read,github_search_repositories", ); }); it("matches adapter prefix modes for direct MCP names", () => { for (const [prefix, expected] of [ ["server", "read,linear_mcp_list_issues"], ["short", "read,linear_list_issues"], ["none", "read,list_issues"], ] as const) { const fixture = createMcpFixture(); writeMcpFixture(fixture, { serverName: "linear-mcp", settings: { toolPrefix: prefix }, tools: [{ name: "list_issues" }], }); const { args } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["read"], mcpDirectTools: ["linear-mcp"], }); assert.equal(args[args.indexOf("--tools") + 1], expected); } }); it("includes resource tools and respects excludeTools", () => { const fixture = createMcpFixture(); writeMcpFixture(fixture, { serverName: "browser-mcp", definition: { excludeTools: ["browser_click"] }, tools: [{ name: "click" }, { name: "navigate" }], resources: [{ name: "Console Logs", uri: "resource://console" }], }); const { args } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["read"], mcpDirectTools: ["browser-mcp"], }); assert.equal( args[args.indexOf("--tools") + 1], "read,browser_mcp_navigate,browser_mcp_get_console_logs", ); }); it("fails closed when direct MCP cache or config is missing or invalid", () => { const missingFixture = createMcpFixture(); writeJson(path.join(missingFixture.agentDir, "mcp.json"), { mcpServers: { "chrome-devtools": { command: "npx", args: ["chrome-devtools-mcp"] }, }, }); assert.throws(() => buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["read", "bash"], mcpDirectTools: ["chrome-devtools"], }), /Unresolved MCP direct-tool selectors: chrome-devtools\./); const invalidFixture = createMcpFixture(); writeMcpFixture(invalidFixture, { cachedAt: Date.now() - 8 * 24 * 60 * 60 * 1000, }); assert.throws(() => buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["read", "bash"], mcpDirectTools: ["chrome-devtools"], }), /Unresolved MCP direct-tool selectors: chrome-devtools\./); }); it("rejects malformed MCP server and metadata entry fields at their load boundaries", () => { const malformedConfig = createMcpFixture(); writeJson(path.join(malformedConfig.agentDir, "mcp.json"), { mcpServers: { "unsafe-server": { command: "unsafe-server", env: { TOKEN: 42 } }, }, }); assert.throws(() => buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["read"], mcpDirectTools: ["unsafe-server"], }), /Unresolved MCP direct-tool selectors: unsafe-server\./); const malformedCache = createMcpFixture(); const definition = { command: "cached-server" }; writeJson(path.join(malformedCache.agentDir, "mcp.json"), { mcpServers: { "cached-server": definition } }); writeJson(path.join(malformedCache.agentDir, "mcp-cache.json"), { version: 1, servers: { "cached-server": { configHash: computeMcpServerHash(definition), cachedAt: Date.now(), tools: [{ name: 42 }], }, }, }); assert.throws(() => buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["read"], mcpDirectTools: ["cached-server"], }), /Unresolved MCP direct-tool selectors: cached-server\./); }); it("preserves MCP configuration errors during direct-tool resolution", () => { const fixture = createMcpFixture(); writeJson(path.join(fixture.agentDir, "mcp.json"), { mcpServers: { "remote-mcp": { url: "https://example.test/${MISSING_MCP_TOKEN}" }, }, }); assert.throws(() => buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["read"], mcpDirectTools: ["remote-mcp"], }), /Missing environment variable in MCP server URL: MISSING_MCP_TOKEN/); }); it("resolves project MCP config from the child cwd and expands SELESAI_CODING_AGENT_DIR", () => { const fixture = createMcpFixture(); process.env.SELESAI_CODING_AGENT_DIR = "~/.selesai/agent"; process.chdir(fixture.root); fs.mkdirSync(path.join(fixture.projectDir, ".selesai")); writeMcpFixture(fixture, { serverName: "project-mcp", configPath: path.join(fixture.projectDir, ".mcp.json"), tools: [{ name: "inspect" }], }); const { args } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["read"], mcpDirectTools: ["project-mcp"], cwd: fixture.projectDir, }); assert.equal(args[args.indexOf("--tools") + 1], "read,project_mcp_inspect"); }); it("resolves direct MCP tools from Pi package manifests", () => { const fixture = createMcpFixture(); const packageRoot = path.join(fixture.agentDir, "npm", "node_modules", "@acme", "tools"); const definition = { command: "node", args: ["package-mcp"] }; writeJson(path.join(fixture.agentDir, "settings.json"), { packages: ["npm:@acme/tools@1.0.0"] }); writeJson(path.join(packageRoot, "package.json"), { name: "@acme/tools", pi: { mcp: "./mcp.json" } }); writeJson(path.join(packageRoot, "mcp.json"), { mcpServers: { wiki: definition } }); writeJson(path.join(fixture.agentDir, "mcp-cache.json"), { version: 1, servers: { "acme_tools__wiki": { configHash: computeMcpServerHash(definition), cachedAt: Date.now(), tools: [{ name: "read_wiki_structure" }], }, }, }); const { args } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["read"], mcpDirectTools: ["acme_tools__wiki/read_wiki_structure"], cwd: fixture.projectDir, }); assert.equal(args[args.indexOf("--tools") + 1], "read,acme_tools__wiki_read_wiki_structure"); }); it("fails closed on malformed MCP server fields from Pi package manifests", () => { const fixture = createMcpFixture(); const packageRoot = path.join(fixture.agentDir, "npm", "node_modules", "@acme", "tools"); const malformedFields: Record = { includeTools: "read_wiki_structure" }; const definition = { command: "node", args: ["package-mcp"], ...malformedFields }; writeJson(path.join(fixture.agentDir, "settings.json"), { packages: ["npm:@acme/tools@1.0.0"] }); writeJson(path.join(packageRoot, "package.json"), { name: "@acme/tools", pi: { mcp: "./mcp.json" } }); writeJson(path.join(packageRoot, "mcp.json"), { mcpServers: { wiki: definition } }); writeJson(path.join(fixture.agentDir, "mcp-cache.json"), { version: 1, servers: { "acme_tools__wiki": { configHash: computeMcpServerHash(definition), cachedAt: Date.now(), tools: [{ name: "read_wiki_structure" }, { name: "delete_wiki" }], }, }, }); assert.throws(() => buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["read"], mcpDirectTools: ["acme_tools__wiki"], cwd: fixture.projectDir, }), /Unresolved MCP direct-tool selectors: acme_tools__wiki\./); }); it("resolves direct MCP tools from the git-root package when child cwd has an incidental .pi directory", () => { const fixture = createMcpFixture(); const nestedCwd = path.join(fixture.projectDir, "packages", "app"); const packageRoot = path.join(fixture.projectDir, ".selesai", "npm", "node_modules", "@acme", "tools"); const definition = { command: "node", args: ["package-mcp"] }; fs.mkdirSync(nestedCwd, { recursive: true }); fs.mkdirSync(path.join(nestedCwd, ".selesai")); fs.mkdirSync(path.join(fixture.projectDir, ".git")); writeJson(path.join(fixture.projectDir, ".selesai", "settings.json"), { packages: ["npm:@acme/tools@1.0.0"], subagents: { projectRootResolution: "git-root" }, }); writeJson(path.join(packageRoot, "package.json"), { name: "@acme/tools", pi: { mcp: "./mcp.json" } }); writeJson(path.join(packageRoot, "mcp.json"), { mcpServers: { wiki: definition } }); writeJson(path.join(fixture.agentDir, "mcp-cache.json"), { version: 1, servers: { "acme_tools__wiki": { configHash: computeMcpServerHash(definition), cachedAt: Date.now(), tools: [{ name: "read_wiki_structure" }], }, }, }); const { args } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["read"], mcpDirectTools: ["acme_tools__wiki/read_wiki_structure"], cwd: nestedCwd, }); assert.equal(args[args.indexOf("--tools") + 1], "read,acme_tools__wiki_read_wiki_structure"); }); it("resolves direct MCP tools from pinned Git package manifests", () => { const fixture = createMcpFixture(); const packageRoot = path.join(fixture.agentDir, "git", "github.com", "acme", "tools"); const definition = { command: "node", args: ["git-package-mcp"] }; writeJson(path.join(fixture.agentDir, "settings.json"), { packages: ["git:https://github.com/acme/tools.git#main"] }); writeJson(path.join(packageRoot, "package.json"), { name: "@acme/tools", pi: { mcp: "./mcp.json" } }); writeJson(path.join(packageRoot, "mcp.json"), { mcpServers: { wiki: definition } }); writeJson(path.join(fixture.agentDir, "mcp-cache.json"), { version: 1, servers: { "acme_tools__wiki": { configHash: computeMcpServerHash(definition), cachedAt: Date.now(), tools: [{ name: "read_wiki_structure" }], }, }, }); const { args } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["read"], mcpDirectTools: ["acme_tools__wiki/read_wiki_structure"], cwd: fixture.projectDir, }); assert.equal(args[args.indexOf("--tools") + 1], "read,acme_tools__wiki_read_wiki_structure"); }); it("resolves direct MCP tools from Agent Plugin MCP config", () => { const fixture = createMcpFixture(); const pluginRoot = path.join(fixture.projectDir, "plugins", "acme-tools"); const definition = { url: "https://example.test/mcp", headers: { "X-Tenant": "public" } }; fs.mkdirSync(path.join(fixture.projectDir, ".selesai")); writeJson(path.join(pluginRoot, "plugin.json"), { $schema: "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", name: "acme.tools", }); writeJson(path.join(pluginRoot, "mcp.json"), { $schema: "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json", mcpServers: { wiki: { type: "streamable-http", ...definition } }, }); writeJson(path.join(fixture.projectDir, ".mcp.json"), { settings: { agentPluginPaths: ["./plugins/acme-tools"] }, mcpServers: {}, }); writeJson(path.join(fixture.agentDir, "mcp-cache.json"), { version: 1, servers: { "acme_tools__wiki": { configHash: computeMcpServerHash(definition), cachedAt: Date.now(), tools: [{ name: "read_wiki_structure" }], }, }, }); const { args } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["read"], mcpDirectTools: ["acme_tools__wiki/read_wiki_structure"], cwd: fixture.projectDir, }); assert.equal(args[args.indexOf("--tools") + 1], "read,acme_tools__wiki_read_wiki_structure"); }); it("resolves root Agent Plugin MCP tools when child cwd is nested", () => { const fixture = createMcpFixture(); const nestedCwd = path.join(fixture.projectDir, "packages", "app"); const pluginRoot = path.join(fixture.projectDir, "plugins", "acme-tools"); const definition = { url: "https://example.test/mcp", headers: { "X-Tenant": "public" } }; fs.mkdirSync(nestedCwd, { recursive: true }); fs.mkdirSync(path.join(nestedCwd, ".selesai")); fs.mkdirSync(path.join(fixture.projectDir, ".git")); writeJson(path.join(fixture.projectDir, ".selesai", "settings.json"), { subagents: { projectRootResolution: "git-root" }, }); writeJson(path.join(pluginRoot, "plugin.json"), { $schema: "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", name: "acme.tools", }); writeJson(path.join(pluginRoot, "mcp.json"), { $schema: "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json", mcpServers: { wiki: { type: "streamable-http", ...definition } }, }); writeJson(path.join(fixture.projectDir, ".mcp.json"), { settings: { agentPluginPaths: ["./plugins/acme-tools"] }, mcpServers: {}, }); writeJson(path.join(fixture.agentDir, "mcp-cache.json"), { version: 1, servers: { "acme_tools__wiki": { configHash: computeMcpServerHash(definition), cachedAt: Date.now(), tools: [{ name: "read_wiki_structure" }], }, }, }); const { args } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["read"], mcpDirectTools: ["acme_tools__wiki/read_wiki_structure"], cwd: nestedCwd, }); assert.equal(args[args.indexOf("--tools") + 1], "read,acme_tools__wiki_read_wiki_structure"); }); it("keeps tool extension paths when explicit extensions are allowlisted", () => { const fixture = createMcpFixture(); writeMcpFixture(fixture, { tools: [{ name: "take_screenshot" }] }); const { args } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["read", "./custom-tool.ts"], extensions: ["./allowed-ext.ts"], mcpDirectTools: ["chrome-devtools"], }); const extensionArgs = args.filter( (arg, index) => args[index - 1] === "--extension", ); assert.equal( args[args.indexOf("--tools") + 1], "read,chrome_devtools_take_screenshot", ); assert.ok( extensionArgs.some((arg) => arg.endsWith( path.join("src", "runs", "shared", "subagent-prompt-runtime.ts"), ), ), ); assert.ok(extensionArgs.includes("./custom-tool.ts")); assert.ok(extensionArgs.includes("./allowed-ext.ts")); }); it("loads subagent-only extension paths only through child process extension args", () => { const { args } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["read"], extensions: ["./main-allowed-ext.ts"], subagentOnlyExtensions: ["./child-tool.ts"], }); const extensionArgs = args.filter( (arg, index) => args[index - 1] === "--extension", ); assert.ok(args.includes("--no-extensions")); assert.equal(args[args.indexOf("--tools") + 1], "read"); assert.ok(extensionArgs.includes("./main-allowed-ext.ts")); assert.ok(extensionArgs.includes("./child-tool.ts")); }); it("authorizes child fanout from an exact declared builtin subagent", () => { const { args, env } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["read", "subagent"], runId: "parent-run", childIndex: 1, parentEventSink: "/tmp/root/events", parentControlInbox: "/tmp/root/control", parentRootRunId: "root-run", parentCapabilityToken: "token-1", }); const extensionArgs = args.filter( (arg, index) => args[index - 1] === "--extension", ); assert.equal(args[args.indexOf("--tools") + 1], "read,subagent"); assert.equal(env[SUBAGENT_FANOUT_CHILD_ENV], "1"); assert.equal(env[SUBAGENT_PARENT_EVENT_SINK_ENV], "/tmp/root/events"); assert.equal(env[SUBAGENT_PARENT_CONTROL_INBOX_ENV], "/tmp/root/control"); assert.equal(env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV], "root-run"); assert.equal(env[SUBAGENT_PARENT_RUN_ID_ENV], "parent-run"); assert.equal(env[SUBAGENT_PARENT_CHILD_INDEX_ENV], "1"); assert.equal(env[SUBAGENT_PARENT_DEPTH_ENV], "1"); assert.deepEqual(JSON.parse(env[SUBAGENT_PARENT_PATH_ENV] ?? "[]"), [ { runId: "parent-run", stepIndex: 1 }, ]); assert.equal(env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV], "token-1"); assert.ok( extensionArgs.some((arg) => arg.endsWith(path.join("src", "extension", "fanout-child.ts")), ), ); }); it("authorizes explicit nested fanout without creating a tool allowlist", () => { const inherited = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, allowNestedSubagents: true, }); const extensionArgs = inherited.args.filter((arg, index) => inherited.args[index - 1] === "--extension"); assert.ok(!inherited.args.includes("--tools")); assert.ok(!inherited.args.includes("--no-tools")); assert.ok(!inherited.args.includes("--no-extensions")); assert.equal(inherited.env[SUBAGENT_FANOUT_CHILD_ENV], "1"); assert.ok(extensionArgs.some((arg) => arg.endsWith(path.join("src", "extension", "fanout-child.ts")))); const ceilingDenied = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, allowNestedSubagents: true, capabilityCeiling: { version: 1, allowedTools: ["read"], denyExtensions: false, sources: ["test"] }, }); const deniedExtensions = ceilingDenied.args.filter((arg, index) => ceilingDenied.args[index - 1] === "--extension"); assert.equal(ceilingDenied.env[SUBAGENT_FANOUT_CHILD_ENV], "0"); assert.ok(!deniedExtensions.some((arg) => arg.endsWith(path.join("src", "extension", "fanout-child.ts")))); }); it("clears all fanout routing env values for non-fanout children", () => { const { args, env } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["read", "mcp:server/subagent"], parentEventSink: "/tmp/should-not-leak/events", parentControlInbox: "/tmp/should-not-leak/control", parentRootRunId: "root-should-not-leak", parentRunId: "should-not-leak", parentChildIndex: 9, parentCapabilityToken: "token-should-not-leak", }); const extensionArgs = args.filter( (arg, index) => args[index - 1] === "--extension", ); assert.equal(env[SUBAGENT_FANOUT_CHILD_ENV], "0"); assert.equal(env[SUBAGENT_PARENT_EVENT_SINK_ENV], ""); assert.equal(env[SUBAGENT_PARENT_CONTROL_INBOX_ENV], ""); assert.equal(env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV], ""); assert.equal(env[SUBAGENT_PARENT_RUN_ID_ENV], ""); assert.equal(env[SUBAGENT_PARENT_CHILD_INDEX_ENV], ""); assert.equal(env[SUBAGENT_PARENT_DEPTH_ENV], ""); assert.equal(env[SUBAGENT_PARENT_PATH_ENV], ""); assert.equal(env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV], ""); assert.ok( !extensionArgs.some((arg) => arg.endsWith(path.join("src", "extension", "fanout-child.ts")), ), ); }); it("inherits routing env only for authorized fanout children", () => { process.env[SUBAGENT_PARENT_EVENT_SINK_ENV] = "/tmp/inherited/events"; process.env[SUBAGENT_PARENT_CONTROL_INBOX_ENV] = "/tmp/inherited/control"; process.env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV] = "inherited-root"; process.env[SUBAGENT_PARENT_RUN_ID_ENV] = "inherited-run"; process.env[SUBAGENT_RUN_ID_ENV] = "owner-run"; process.env[SUBAGENT_PARENT_CHILD_INDEX_ENV] = "4"; process.env[SUBAGENT_PARENT_DEPTH_ENV] = "2"; process.env[SUBAGENT_PARENT_PATH_ENV] = JSON.stringify([ { runId: "root-run", stepIndex: 0 }, { runId: "../unsafe", stepIndex: 1 }, { runId: "owner-run", stepIndex: 1 }, ]); process.env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV] = "inherited-token"; const fanout = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["subagent"], }); assert.equal( fanout.env[SUBAGENT_PARENT_EVENT_SINK_ENV], "/tmp/inherited/events", ); assert.equal( fanout.env[SUBAGENT_PARENT_CONTROL_INBOX_ENV], "/tmp/inherited/control", ); assert.equal(fanout.env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV], "inherited-root"); assert.equal(fanout.env[SUBAGENT_PARENT_RUN_ID_ENV], "owner-run"); assert.equal(fanout.env[SUBAGENT_PARENT_CHILD_INDEX_ENV], "4"); assert.equal(fanout.env[SUBAGENT_PARENT_DEPTH_ENV], "3"); assert.deepEqual(JSON.parse(fanout.env[SUBAGENT_PARENT_PATH_ENV] ?? "[]"), [ { runId: "root-run", stepIndex: 0 }, { runId: "owner-run", stepIndex: 1 }, { runId: "owner-run", stepIndex: 4 }, ]); assert.equal( fanout.env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV], "inherited-token", ); const nonFanout = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["read"], }); assert.equal(nonFanout.env[SUBAGENT_FANOUT_CHILD_ENV], "0"); assert.equal(nonFanout.env[SUBAGENT_PARENT_EVENT_SINK_ENV], ""); assert.equal(nonFanout.env[SUBAGENT_PARENT_CONTROL_INBOX_ENV], ""); assert.equal(nonFanout.env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV], ""); assert.equal(nonFanout.env[SUBAGENT_PARENT_RUN_ID_ENV], ""); assert.equal(nonFanout.env[SUBAGENT_PARENT_CHILD_INDEX_ENV], ""); assert.equal(nonFanout.env[SUBAGENT_PARENT_DEPTH_ENV], ""); assert.equal(nonFanout.env[SUBAGENT_PARENT_PATH_ENV], ""); assert.equal(nonFanout.env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV], ""); }); it("prefers the current subagent run id over inherited ancestor ids for nested fanout routing", () => { process.env[SUBAGENT_PARENT_EVENT_SINK_ENV] = "/tmp/inherited/events"; process.env[SUBAGENT_PARENT_CONTROL_INBOX_ENV] = "/tmp/inherited/control"; process.env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV] = "root-run"; process.env[SUBAGENT_PARENT_RUN_ID_ENV] = "older-parent"; process.env[SUBAGENT_RUN_ID_ENV] = "ancestor-run"; process.env[SUBAGENT_PARENT_CHILD_INDEX_ENV] = "4"; process.env[SUBAGENT_PARENT_DEPTH_ENV] = "1"; process.env[SUBAGENT_PARENT_PATH_ENV] = JSON.stringify([ { runId: "root-run", stepIndex: 0 }, ]); process.env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV] = "inherited-token"; const { env } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["subagent"], runId: "current-nested-run", childIndex: 2, }); assert.equal(env[SUBAGENT_PARENT_RUN_ID_ENV], "current-nested-run"); assert.equal(env[SUBAGENT_PARENT_CHILD_INDEX_ENV], "2"); assert.equal(env[SUBAGENT_PARENT_DEPTH_ENV], "2"); assert.deepEqual(JSON.parse(env[SUBAGENT_PARENT_PATH_ENV] ?? "[]"), [ { runId: "root-run", stepIndex: 0 }, { runId: "current-nested-run", stepIndex: 2 }, ]); }); it("does not let direct MCP tools authorize child fanout", () => { const fixture = createMcpFixture(); writeMcpFixture(fixture, { serverName: "delegator", definition: { command: "delegator-mcp" }, tools: [{ name: "subagent" }], }); const { args, env } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["read"], mcpDirectTools: ["delegator"], }); const extensionArgs = args.filter( (arg, index) => args[index - 1] === "--extension", ); assert.equal(args[args.indexOf("--tools") + 1], "read,delegator_subagent"); assert.equal(env[SUBAGENT_FANOUT_CHILD_ENV], "0"); assert.ok( !extensionArgs.some((arg) => arg.endsWith(path.join("src", "extension", "fanout-child.ts")), ), ); }); it("keeps child-safe fanout registration in explicit extensions mode", () => { const { args, env } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, inheritProjectContext: false, inheritSkills: false, tools: ["subagent"], extensions: ["./agent-allowed-ext.ts"], }); const extensionArgs = args.filter( (arg, index) => args[index - 1] === "--extension", ); assert.ok(args.includes("--no-extensions")); assert.equal(env[SUBAGENT_FANOUT_CHILD_ENV], "1"); assert.ok( extensionArgs.some((arg) => arg.endsWith(path.join("src", "extension", "fanout-child.ts")), ), ); assert.ok(extensionArgs.includes("./agent-allowed-ext.ts")); }); it("emits an empty prompt file when replace mode is used with an empty prompt", () => { const { args } = buildPiArgs({ baseArgs: ["-p"], task: "hello", sessionEnabled: false, systemPrompt: "", systemPromptMode: "replace", inheritProjectContext: false, inheritSkills: false, }); assert.ok(args.includes("--system-prompt")); }); });