@tekmidian/pai 0.41.0 → 0.42.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aibroker-client-BP74gGwD.mjs +215 -0
- package/dist/aibroker-client-BP74gGwD.mjs.map +1 -0
- package/dist/aibroker-client-C6m71p6O.mjs +3 -0
- package/dist/{auto-route-BwzmlW3W.mjs → auto-route-_5zzJz5q.mjs} +2 -2
- package/dist/{auto-route-BwzmlW3W.mjs.map → auto-route-_5zzJz5q.mjs.map} +1 -1
- package/dist/{chain-zMjCDW-R.mjs → chain-DsvSHnO9.mjs} +133 -361
- package/dist/chain-DsvSHnO9.mjs.map +1 -0
- package/dist/cli/index.mjs +12 -10
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/program.mjs +12 -10
- package/dist/{clusters-DRHhfli9.mjs → clusters-j0ZuNYp9.mjs} +1 -1
- package/dist/{clusters-DRHhfli9.mjs.map → clusters-j0ZuNYp9.mjs.map} +1 -1
- package/dist/{config-D1G9IFpn.mjs → config-B64vFg14.mjs} +3 -50
- package/dist/config-B64vFg14.mjs.map +1 -0
- package/dist/{config-Dl8lT4Lu.mjs → config-Bi_F6lXf.mjs} +32 -3
- package/dist/config-Bi_F6lXf.mjs.map +1 -0
- package/dist/{config-DD52BPB-.mjs → config-C_ErGddD.mjs} +1 -1
- package/dist/{context-handover-cache-C6_rs7JT.mjs → context-handover-cache--bm6Siim.mjs} +28 -9
- package/dist/context-handover-cache--bm6Siim.mjs.map +1 -0
- package/dist/daemon/index.mjs +13 -12
- package/dist/daemon/index.mjs.map +1 -1
- package/dist/daemon-BOAPyL_b.mjs +23 -0
- package/dist/{daemon-CpdVUnl_.mjs → daemon-DvXih78C.mjs} +359 -27
- package/dist/daemon-DvXih78C.mjs.map +1 -0
- package/dist/daemon-mcp/index.mjs +10 -6
- package/dist/daemon-mcp/index.mjs.map +1 -1
- package/dist/detector-C-7KRi42.mjs +5 -0
- package/dist/{detector-smpCm7ku.mjs → detector-C73Nxm94.mjs} +1 -1
- package/dist/{detector-smpCm7ku.mjs.map → detector-C73Nxm94.mjs.map} +1 -1
- package/dist/{factory-Bc5EyN54.mjs → factory-DEb9NmHJ.mjs} +7 -7
- package/dist/{factory-Bc5EyN54.mjs.map → factory-DEb9NmHJ.mjs.map} +1 -1
- package/dist/factory-Dbo8yNPi.mjs +3 -0
- package/dist/{fallback-CgQ_x4I7.mjs → fallback-8bzmg6No.mjs} +6 -5
- package/dist/fallback-8bzmg6No.mjs.map +1 -0
- package/dist/hooks/route-agents-to-worker.mjs.map +2 -2
- package/dist/hooks/worker-proxy.mjs.map +2 -2
- package/dist/hooks/worker-status-line.mjs +13 -5
- package/dist/hooks/worker-status-line.mjs.map +3 -3
- package/dist/hooks/worker-supervision.mjs +593 -0
- package/dist/hooks/worker-supervision.mjs.map +7 -0
- package/dist/{indexer-backend-rohVVKrA.mjs → indexer-backend-BPTzKFZU.mjs} +1 -1
- package/dist/{indexer-backend-rohVVKrA.mjs.map → indexer-backend-BPTzKFZU.mjs.map} +1 -1
- package/dist/{ipc-client-C7AVpsyv.mjs → ipc-client-BmypMNYk.mjs} +2 -2
- package/dist/{ipc-client-C7AVpsyv.mjs.map → ipc-client-BmypMNYk.mjs.map} +1 -1
- package/dist/{kg-entity-DbOMPdF9.mjs → kg-entity-r8duqhi9.mjs} +1 -1
- package/dist/{kg-entity-DbOMPdF9.mjs.map → kg-entity-r8duqhi9.mjs.map} +1 -1
- package/dist/{latent-ideas-iNp5s55R.mjs → latent-ideas-CtSca4qG.mjs} +2 -2
- package/dist/{latent-ideas-iNp5s55R.mjs.map → latent-ideas-CtSca4qG.mjs.map} +1 -1
- package/dist/{link-boost-NNzADHX_.mjs → link-boost-Bucnz3n8.mjs} +1 -1
- package/dist/{link-boost-NNzADHX_.mjs.map → link-boost-Bucnz3n8.mjs.map} +1 -1
- package/dist/main-resolver-CqppP_Bw.mjs +7 -0
- package/dist/{main-resolver-CM1IHbuu.mjs → main-resolver-Dip2VLB5.mjs} +4 -214
- package/dist/main-resolver-Dip2VLB5.mjs.map +1 -0
- package/dist/{neighborhood-AGL7oXQ2.mjs → neighborhood-CM7Jgb51.mjs} +1 -1
- package/dist/{neighborhood-AGL7oXQ2.mjs.map → neighborhood-CM7Jgb51.mjs.map} +1 -1
- package/dist/{note-context-BHwYpSFf.mjs → note-context-DCwE2A-M.mjs} +1 -1
- package/dist/{note-context-BHwYpSFf.mjs.map → note-context-DCwE2A-M.mjs.map} +1 -1
- package/dist/{planner-DsmMRIZR.mjs → planner-ogy-Zy4O.mjs} +6 -5
- package/dist/{planner-DsmMRIZR.mjs.map → planner-ogy-Zy4O.mjs.map} +1 -1
- package/dist/{postgres-Dwxe290H.mjs → postgres-CtOzfoQU.mjs} +1 -1
- package/dist/{postgres-Dwxe290H.mjs.map → postgres-CtOzfoQU.mjs.map} +1 -1
- package/dist/{program-Bed-Pd-8.mjs → program-B7N5OIJR.mjs} +36 -33
- package/dist/{program-Bed-Pd-8.mjs.map → program-B7N5OIJR.mjs.map} +1 -1
- package/dist/query-feedback-DGoVRaWD.mjs +3 -0
- package/dist/{query-feedback-DZfjI34w.mjs → query-feedback-Dqz-HC__.mjs} +1 -1
- package/dist/{query-feedback-DZfjI34w.mjs.map → query-feedback-Dqz-HC__.mjs.map} +1 -1
- package/dist/router-BTIGZR2_.mjs +3 -0
- package/dist/{router-DHracTUC.mjs → router-DD6D4j31.mjs} +1 -1
- package/dist/{router-DHracTUC.mjs.map → router-DD6D4j31.mjs.map} +1 -1
- package/dist/runtime-paths-rni52zHX.mjs +50 -0
- package/dist/runtime-paths-rni52zHX.mjs.map +1 -0
- package/dist/scope-CfYnjy5R.mjs +360 -0
- package/dist/scope-CfYnjy5R.mjs.map +1 -0
- package/dist/{server-BOuAOj9b.mjs → server-DvN-IFIV.mjs} +42 -3
- package/dist/server-DvN-IFIV.mjs.map +1 -0
- package/dist/skills/Worker/SKILL.md +3 -0
- package/dist/{sources-ju6AA7lL.mjs → sources-CS43CpuS.mjs} +1 -1
- package/dist/{sources-ju6AA7lL.mjs.map → sources-CS43CpuS.mjs.map} +1 -1
- package/dist/{sqlite-kFZeWCJ2.mjs → sqlite-Cq3gJcq9.mjs} +1 -1
- package/dist/{sqlite-kFZeWCJ2.mjs.map → sqlite-Cq3gJcq9.mjs.map} +1 -1
- package/dist/{state-ahFAbD8b.mjs → state-BTa10SOM.mjs} +1 -1
- package/dist/{state-ahFAbD8b.mjs.map → state-BTa10SOM.mjs.map} +1 -1
- package/dist/{state-DH_mp5Xe.mjs → state-RXepvbnO.mjs} +1 -1
- package/dist/{themes-DqhNUUQk.mjs → themes-B7h9YDiy.mjs} +1 -1
- package/dist/{themes-DqhNUUQk.mjs.map → themes-B7h9YDiy.mjs.map} +1 -1
- package/dist/{tools-COna9vZR.mjs → tools-BW3NLMPk.mjs} +1 -1
- package/dist/{tools-CMlHa9lZ.mjs → tools-LlzcV_eK.mjs} +14 -14
- package/dist/{tools-CMlHa9lZ.mjs.map → tools-LlzcV_eK.mjs.map} +1 -1
- package/dist/{trace-DTP6k4B_.mjs → trace-u4hT-pLD.mjs} +1 -1
- package/dist/{trace-DTP6k4B_.mjs.map → trace-u4hT-pLD.mjs.map} +1 -1
- package/dist/{vault-indexer-YMH22mLU.mjs → vault-indexer-mata9S7b.mjs} +1 -1
- package/dist/{vault-indexer-YMH22mLU.mjs.map → vault-indexer-mata9S7b.mjs.map} +1 -1
- package/dist/work-queue-worker-19r0cpH4.mjs +14 -0
- package/dist/{work-queue-worker-B3nl8yyn.mjs → work-queue-worker-DXh_FnT5.mjs} +4 -4
- package/dist/{work-queue-worker-B3nl8yyn.mjs.map → work-queue-worker-DXh_FnT5.mjs.map} +1 -1
- package/dist/{zettelkasten-DDvKRm2V.mjs → zettelkasten-F0D_XfQ4.mjs} +3 -3
- package/dist/{zettelkasten-DDvKRm2V.mjs.map → zettelkasten-F0D_XfQ4.mjs.map} +1 -1
- package/docs/commands/worker.md +1 -1
- package/docs/merge-demo.md +1 -0
- package/docs/worker.md +40 -0
- package/package.json +1 -1
- package/plugins/productivity/hooks/hooks.json +5 -0
- package/src/hooks/ts/user-prompt/worker-supervision.ts +123 -0
- package/dist/chain-zMjCDW-R.mjs.map +0 -1
- package/dist/config-D1G9IFpn.mjs.map +0 -1
- package/dist/config-Dl8lT4Lu.mjs.map +0 -1
- package/dist/context-handover-cache-C6_rs7JT.mjs.map +0 -1
- package/dist/daemon-BSL9sXxm.mjs +0 -22
- package/dist/daemon-CpdVUnl_.mjs.map +0 -1
- package/dist/detector-BLVOCiJ_.mjs +0 -5
- package/dist/factory-C7ua_3ik.mjs +0 -3
- package/dist/fallback-CgQ_x4I7.mjs.map +0 -1
- package/dist/main-resolver-CM1IHbuu.mjs.map +0 -1
- package/dist/main-resolver-E5WlilhM.mjs +0 -6
- package/dist/query-feedback-CGcEiGtY.mjs +0 -3
- package/dist/router-CAtyVQud.mjs +0 -3
- package/dist/server-BOuAOj9b.mjs.map +0 -1
- package/dist/work-queue-worker-BM1GcAAq.mjs +0 -14
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"chain-zMjCDW-R.mjs","names":["readKey"],"sources":["../src/workers/args.ts","../src/workers/ledger.ts","../src/workers/status.ts","../src/workers/scope.ts","../src/workers/pane.ts","../src/workers/tree.ts","../src/workers/operator.ts","../src/workers/handoff.ts","../src/workers/worktree.ts","../src/workers/report.ts","../src/workers/mcp.ts","../src/workers/codex.ts","../src/workers/run.ts","../src/workers/chain.ts"],"sourcesContent":["/**\n * args.ts — parse the claude-args tail that `pai worker run` receives.\n *\n * The runner needs a few things out of the caller's argument vector: the\n * prompt (for the default label), the requested --output-format (so the final\n * print matches what plain `claude -p` would have produced), whether the\n * caller already chose a --model or an --mcp-config (both suppress the\n * defaults the runner would otherwise force), any --mcp allowlist names, any\n * --allowedTools grants (their mcp__server__… entries decide which servers\n * load), and whether they appended their own system prompt (the worker\n * contract is then added alongside, not instead). Everything is passed\n * through untouched — the runner never rewrites the caller's task.\n */\n\nexport interface ParsedRunnerArgs {\n /** Prompt string after -p/--print, when the run is headless. */\n prompt: string | null;\n /** Caller's --output-format: text (default), json, or stream-json. */\n outputFormat: \"text\" | \"json\" | \"stream-json\";\n /** Args to hand to claude (minus --output-format/--verbose, which we add). */\n rest: string[];\n /** Headless: a -p/--print flag is present. */\n headless: boolean;\n /** Caller passed --model (or --model=…): do not force the provider model. */\n callerModel: boolean;\n /** Caller passed --mcp-config (or --mcp-config=…): keep their MCP setup. */\n callerMcpConfig: boolean;\n /** Caller passed --append-system-prompt: the contract is added alongside. */\n callerSystemPrompt: boolean;\n /** --mcp values (repeatable, comma-separated inside one flag). */\n mcp: string[];\n /** --allowedTools values (repeatable, comma-separated inside one flag). */\n allowedTools: string[];\n}\n\nexport function parseRunnerArgs(argv: string[]): ParsedRunnerArgs {\n let prompt: string | null = null;\n let outputFormat: ParsedRunnerArgs[\"outputFormat\"] = \"text\";\n const rest: string[] = [];\n let headless = false;\n let callerModel = false;\n let callerMcpConfig = false;\n let callerSystemPrompt = false;\n const mcp: string[] = [];\n const allowedTools: string[] = [];\n\n let i = 0;\n while (i < argv.length) {\n const a = argv[i];\n if (a === \"-p\" || a === \"--print\") {\n headless = true;\n rest.push(a);\n if (i + 1 < argv.length && !argv[i + 1].startsWith(\"-\")) {\n prompt = argv[i + 1];\n rest.push(prompt);\n i += 1;\n }\n } else if (a === \"--output-format\") {\n const v = argv[i + 1];\n if (v === \"json\" || v === \"stream-json\") outputFormat = v;\n i += 1; // dropped; the runner prints the result in this format itself\n } else if (a.startsWith(\"--output-format=\")) {\n const v = a.slice(\"--output-format=\".length);\n if (v === \"json\" || v === \"stream-json\") outputFormat = v;\n } else if (a === \"--verbose\") {\n // dropped; re-added by the runner\n } else if (a === \"--mcp\") {\n const v = argv[i + 1];\n if (v !== undefined && !v.startsWith(\"-\")) {\n mcp.push(v);\n i += 1;\n }\n } else if (a.startsWith(\"--mcp=\")) {\n mcp.push(a.slice(\"--mcp=\".length));\n } else if (a === \"--allowedTools\") {\n // captured for the MCP derivation, but still claude's flag: it passes through\n rest.push(a);\n const v = argv[i + 1];\n if (v !== undefined && !v.startsWith(\"-\")) {\n allowedTools.push(v);\n rest.push(v);\n i += 1;\n }\n } else if (a.startsWith(\"--allowedTools=\")) {\n allowedTools.push(a.slice(\"--allowedTools=\".length));\n rest.push(a);\n } else {\n if (a === \"--model\") callerModel = true;\n if (a.startsWith(\"--model=\")) callerModel = true;\n if (a === \"--mcp-config\") callerMcpConfig = true;\n if (a.startsWith(\"--mcp-config=\")) callerMcpConfig = true;\n if (a === \"--append-system-prompt\") callerSystemPrompt = true;\n if (a.startsWith(\"--append-system-prompt=\")) callerSystemPrompt = true;\n if (\n prompt === null && !a.startsWith(\"-\") && rest.length > 0 &&\n (rest[rest.length - 1] === \"-p\" || rest[rest.length - 1] === \"--print\")\n ) {\n prompt = a;\n }\n rest.push(a);\n }\n i += 1;\n }\n\n return {\n prompt,\n outputFormat,\n rest,\n headless,\n callerModel,\n callerMcpConfig,\n callerSystemPrompt,\n mcp,\n allowedTools,\n };\n}\n\n/**\n * Turn `-p \"<prompt>\"` into bare `-p` (same for --print): for stream-json\n * stdin runs the prompt moves to the first user message on stdin, so the\n * value must not stay on the command line. Only prompt values directly\n * following the flag are touched; everything else passes through.\n */\nexport function stripPromptValues(argv: string[]): string[] {\n const out: string[] = [];\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i];\n const isPrint = a === \"-p\" || a === \"--print\";\n out.push(a);\n if (isPrint && i + 1 < argv.length && !argv[i + 1].startsWith(\"-\")) {\n i += 1; // drop the prompt value; the flag itself stays\n }\n }\n return out;\n}\n\n/** Collapse whitespace and cut to n chars with an ellipsis (label rendering). */\nexport function shortText(s: unknown, n: number): string {\n const t = String(s ?? \"\").split(/\\s+/).filter(Boolean).join(\" \");\n return t.length <= n ? t : t.slice(0, n - 1) + \"…\";\n}\n","/**\n * ledger.ts — the routing ledger, one append-only log for every worker event.\n *\n * Line shapes (whitespace-aligned so `tail` reads as a table):\n *\n * 2026-09-17 12:00:00 WORKER-START id=<id> provider=<p> mode=headless model=<m> cwd=<cwd> label=<label>\n * 2026-09-17 12:01:00 WORKER-END id=<id> provider=<p> mode=headless model=<m> rc=0 secs=60 turns=3 tools=8 label=<label>\n * 2026-09-17 12:00:20 WORKER-REROUTE from=<a> to=<b> reason=quota\n * 2026-09-17 12:00:00 DENIED-ANTHROPIC-AGENT cwd=<cwd> desc=<desc>\n * 2026-09-17 12:00:00 ALLOWED-ANTHROPIC-AGENT cwd=<cwd> desc=<desc>\n *\n * `pai worker log` (and glm-log before it) counts these; keep the tags stable.\n */\n\nimport { appendFileSync, existsSync, mkdirSync, readFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\n\nexport function ledgerStamp(d: Date = new Date()): string {\n const p = (n: number) => String(n).padStart(2, \"0\");\n return (\n `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ` +\n `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`\n );\n}\n\nexport interface LedgerLine {\n stamp: string;\n event: string;\n fields: Record<string, string>;\n}\n\n/** Parse `2026-09-17 12:00:00 TAG key=value …` into its parts. */\nexport function parseLedgerLine(line: string): LedgerLine | null {\n const m = line.match(/^(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}) (\\S+)(?: (.*))?$/);\n if (!m) return null;\n const fields: Record<string, string> = {};\n // values are whitespace-collapsed by the writers, so spaces safely delimit\n for (const part of (m[3] ?? \"\").split(\" \")) {\n if (!part) continue;\n const eq = part.indexOf(\"=\");\n if (eq <= 0) continue;\n fields[part.slice(0, eq)] = part.slice(eq + 1);\n }\n return { stamp: m[1], event: m[2], fields };\n}\n\nexport function parseLedger(text: string): LedgerLine[] {\n const out: LedgerLine[] = [];\n for (const line of text.split(\"\\n\")) {\n if (!line.trim()) continue;\n const parsed = parseLedgerLine(line);\n if (parsed) out.push(parsed);\n }\n return out;\n}\n\n/** Append one ledger line. `kv` order is the caller's; values are flattened. */\nexport function appendLedger(\n path: string,\n event: string,\n kv: Record<string, string | number | null | undefined>,\n now: Date = new Date()\n): void {\n const parts: string[] = [];\n for (const [k, v] of Object.entries(kv)) {\n if (v === undefined || v === null) continue;\n parts.push(`${k}=${String(v).replace(/\\s+/g, \" \")}`);\n }\n const dir = dirname(path);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n const tail = parts.length ? \" \" + parts.join(\" \") : \"\";\n appendFileSync(path, `${ledgerStamp(now)} ${event}${tail}\\n`, \"utf8\");\n}\n\nexport interface LedgerSummary {\n scope: string;\n started: number;\n endedOk: number;\n endedFailed: number;\n denied: number;\n allowed: number;\n reroutes: number;\n lastLines: string[];\n}\n\n/** The counts `pai worker log` prints, over today's lines or the whole file. */\nexport function ledgerSummary(\n path: string,\n scope: \"today\" | \"all\",\n lastN = 15,\n now: Date = new Date()\n): LedgerSummary | null {\n if (!existsSync(path)) return null;\n const text = readFileSync(path, \"utf8\");\n const today = ledgerStamp(now).slice(0, 10);\n const lines = text.split(\"\\n\").filter((l) => l.trim());\n const scoped =\n scope === \"all\" ? lines : lines.filter((l) => l.startsWith(today));\n const parsed = scoped.map(parseLedgerLine).filter((x): x is LedgerLine => x !== null);\n const count = (ev: string) => parsed.filter((p) => p.event === ev).length;\n const ends = parsed.filter((p) => p.event === \"WORKER-END\");\n const ok = ends.filter((p) => p.fields.rc === \"0\").length;\n return {\n scope: scope === \"all\" ? \"all time\" : `today ${today}`,\n started: count(\"WORKER-START\"),\n endedOk: ok,\n endedFailed: ends.length - ok,\n denied: count(\"DENIED-ANTHROPIC-AGENT\"),\n allowed: count(\"ALLOWED-ANTHROPIC-AGENT\"),\n reroutes: count(\"WORKER-REROUTE\"),\n lastLines: lines.slice(-lastN),\n };\n}\n","/**\n * status.ts — the live per-worker status file.\n *\n * One JSON file per run, rewritten atomically on every turn, read by `ps`,\n * `follow`, the status line and the pane logic. Same field set the Python\n * runner wrote, plus `provider` (routing is multi-provider now) and `session`\n * (the AIBroker identity of the launching session, see scope.ts).\n */\n\nimport { existsSync, readFileSync, renameSync, writeFileSync, readdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { shortText } from \"./args.js\";\nimport { statusPath } from \"./paths.js\";\n\nexport interface WorkerSessionRef {\n /** AIBroker/iTerm session id (the iTerm UUID). */\n id: string;\n /** AIBroker name of that session, when it has one. */\n name: string;\n}\n\nexport interface WorkerStatus {\n id: string;\n pid: number;\n label: string;\n cwd: string;\n /** iTerm session id of the launching terminal (\"\" outside iTerm). */\n term: string;\n provider: string;\n model: string;\n state: \"running\" | \"done\" | \"failed\" | \"killed\" | \"lost\";\n started: string;\n updated: string;\n turns: number;\n tools: number;\n last: string;\n rc: number | null;\n secs: number | null;\n /** Launching session resolved through AIBroker, when it was. */\n session?: WorkerSessionRef | null;\n /** Claude Code session id (system/init) — what `resume` continues. */\n claudeSession?: string | null;\n /**\n * Claude Code session id of the orchestrator whose Bash launched this run,\n * when the status line's session map knew it (see scope.ts). Distinct from\n * claudeSession, which is the run's own session.\n */\n spawnerSession?: string | null;\n /** Context meter: tokens of the last assistant turn (input+cache+output). */\n contextTokens?: number | null;\n /** Context meter: window size (init model info or provider default). */\n contextWindow?: number | null;\n /** Chain this stage belongs to (the chain id), when it is a chain stage. */\n parent?: string;\n /** Class name of the chain stage (\"draft\", \"implement\", …). */\n stage?: string;\n /** Worktree this run executed in, when it got one (see worktree.ts). */\n worktreeDir?: string | null;\n /** Branch the worker committed on (worker/<id>), set on worktree runs. */\n branch?: string | null;\n /** Commit the branch started from (worktree base) for the commits count. */\n worktreeBase?: string | null;\n /** Commits the worker made on its branch; set with `branch` on success. */\n commits?: number | null;\n /** Set by `pai worker merge` once the branch landed in the original checkout. */\n merged?: boolean;\n /**\n * How the run came to be: \"spawn\" (a `pai worker run` subagent) or \"chat\"\n * (the terminal's interactive pane itself, tracked like a worker). Absent\n * on statuses written before the flag existed — read as a spawn.\n */\n origin?: \"spawn\" | \"chat\";\n}\n\n/** Label a worker gets when launched with neither --label nor a prompt. */\nexport const UNLABELED = \"unlabeled\";\n\n/** Context-meter percentage 0–100, null when the numbers are missing. */\nexport function contextPercent(s: Pick<WorkerStatus, \"contextTokens\" | \"contextWindow\">): number | null {\n if (!s.contextTokens || !s.contextWindow) return null;\n return Math.round((s.contextTokens / s.contextWindow) * 100);\n}\n\n/** Compact token count: 84k, 200k, 900. */\nexport function fmtContextK(n: number): string {\n return n >= 1000 ? `${Math.round(n / 1000)}k` : String(n);\n}\n\n/**\n * The one context-meter label, shared by every renderer so identical\n * numbers always render identically: `ctx 84k/200k (42%)` — used-style\n * percent. Empty when the numbers are missing (callers drop the part).\n */\nexport function contextLabel(\n s: Pick<WorkerStatus, \"contextTokens\" | \"contextWindow\">\n): string {\n const pct = contextPercent(s);\n if (pct === null) return \"\";\n return `ctx ${fmtContextK(s.contextTokens ?? 0)}/${fmtContextK(s.contextWindow ?? 0)} (${pct}%)`;\n}\n\n/** Timestamp format shared by status files and the ledger. */\nexport function nowStamp(d: Date = new Date()): string {\n const p = (n: number) => String(n).padStart(2, \"0\");\n return (\n `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ` +\n `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`\n );\n}\n\n// second-resolution ids repeat when two workers/chains start together in one\n// process; a repeat gets a monotonic suffix so files never collide\nlet lastId = \"\";\nlet idSeq = 0;\n\nexport function newWorkerId(d: Date = new Date(), pid = process.pid): string {\n const p = (n: number) => String(n).padStart(2, \"0\");\n const base = `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}-${pid}`;\n if (base === lastId) {\n idSeq += 1;\n return `${base}-${String(idSeq).padStart(2, \"0\")}`;\n }\n lastId = base;\n idSeq = 0;\n return base;\n}\n\n/** Write status atomically (temp + rename) and stamp `updated`. */\nexport function saveStatus(logDir: string, status: WorkerStatus, d: Date = new Date()): void {\n status.updated = nowStamp(d);\n const path = statusPath(logDir, status.id);\n const tmp = `${path}.tmp`;\n writeFileSync(tmp, JSON.stringify(status), \"utf8\");\n renameSync(tmp, path);\n}\n\n/** Load every status file in the logDir, oldest id first, skipping damage. */\nexport function loadStatuses(logDir: string): WorkerStatus[] {\n if (!existsSync(logDir)) return [];\n const out: WorkerStatus[] = [];\n for (const name of readdirSync(logDir).sort()) {\n if (!name.endsWith(\".status\")) continue;\n try {\n out.push(JSON.parse(readFileSync(join(logDir, name), \"utf8\")) as WorkerStatus);\n } catch {\n // a half-written or damaged status file is not worth a crash\n }\n }\n return out;\n}\n\nexport function loadStatus(logDir: string, id: string): WorkerStatus | null {\n const path = statusPath(logDir, id);\n if (!existsSync(path)) return null;\n try {\n return JSON.parse(readFileSync(path, \"utf8\")) as WorkerStatus;\n } catch {\n return null;\n }\n}\n\nexport function alive(pid: number | null | undefined): boolean {\n if (!pid || pid <= 0) return false;\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n/** \"42s\" under 90s, \"7m\" above — the coarse age the table and bar show. */\nexport function ageOf(ts: string, now: Date = new Date()): string {\n const t = Date.parse(ts.replace(\" \", \"T\"));\n if (Number.isNaN(t)) return \"?\";\n const s = Math.max(0, Math.floor((now.getTime() - t) / 1000));\n return s < 90 ? `${s}s` : `${Math.floor(s / 60)}m`;\n}\n\n/** One-line description of a tool call, e.g. \"Bash: npm test\". */\nexport function describeTool(name: string, inp: unknown): string {\n if (typeof inp !== \"object\" || inp === null) return name;\n const i = inp as Record<string, unknown>;\n const get = (k: string) => (typeof i[k] === \"string\" ? (i[k] as string) : \"\");\n if (name === \"Bash\") return `Bash: ${shortText(get(\"command\"), 70)}`;\n if (name === \"Read\" || name === \"Edit\" || name === \"Write\" || name === \"MultiEdit\") {\n const file = get(\"file_path\").split(\"/\").pop() ?? \"\";\n return `${name}: ${file}`;\n }\n if (name === \"Grep\" || name === \"Glob\") return `${name}: ${shortText(get(\"pattern\"), 50)}`;\n if (name === \"WebSearch\") return `${name}: ${shortText(get(\"query\"), 50)}`;\n if (name === \"WebFetch\") return `${name}: ${shortText(get(\"url\"), 60)}`;\n return name;\n}\n","/**\n * scope.ts — which workers \"belong\" to the terminal asking about them.\n *\n * Two identities, in priority order:\n *\n * 1. AIBroker's session registry (~/.aibroker/session-names.json): iTerm\n * session UUID → session name. The launching session's UUID is stored in\n * each worker's status file as `session.id`, so every pane of a named\n * session sees its workers — regardless of tab layout.\n * 2. The iTerm tab key (`w<window>t<tab>` prefix of ITERM_SESSION_ID): the\n * original heuristic, kept as the fallback when no registry entry\n * matches (AIBroker absent, unnamed session, non-iTerm terminal).\n *\n * If neither is available, viewers fall back to \"all workers\".\n *\n * A third case: workers spawned from a Claude Code Bash tool (the\n * orchestrator pattern) have neither — Claude Code exports no terminal or\n * session identity to its Bash children. Those record `spawnerSession`, the\n * orchestrator's claude session id bridged through the status line's\n * session map (see below), so their tab still claims them.\n */\n\nimport { existsSync, readFileSync, renameSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { loadStatus, type WorkerStatus } from \"./status.js\";\n\nexport const AIBROKER_REGISTRY = join(homedir(), \".aibroker\", \"session-names.json\");\n\n/**\n * `w<window>t<tab>` prefix of an iTerm session id, \"\" if empty or malformed.\n * Panes split from the same tab share this prefix and differ only in `p<n>`.\n */\nexport function tabKey(term: string): string {\n if (!term) return \"\";\n const head = term.split(\"p\", 1)[0];\n const parts = head.slice(1).split(\"t\");\n if (head.startsWith(\"w\") && parts.length === 2 && parts.every((p) => /^\\d+$/.test(p))) {\n return head;\n }\n return \"\";\n}\n\n/** Tab key of the iTerm tab this process runs in, \"\" if not inside iTerm2. */\nexport function currentTabKey(env: NodeJS.ProcessEnv = process.env): string {\n return tabKey(env.ITERM_SESSION_ID ?? \"\");\n}\n\n/** The iTerm UUID part of an ITERM_SESSION_ID (after the last colon). */\nexport function itermUuid(term: string): string {\n if (!term) return \"\";\n return term.split(\":\").pop() ?? \"\";\n}\n\nexport interface SessionIdentity {\n id: string;\n name: string;\n}\n\n/**\n * Resolve the AIBroker session for an ITERM_SESSION_ID. Reads the persistent\n * name registry (the same store `aibroker_rename` writes); returns null when\n * AIBroker is absent or the session is not in it — callers then use the tab\n * key, which is the pre-AIBroker behaviour.\n */\nexport function resolveSession(\n term: string,\n registryPath: string = AIBROKER_REGISTRY\n): SessionIdentity | null {\n const uuid = itermUuid(term);\n if (!uuid) return null;\n let names: Record<string, unknown>;\n try {\n if (!existsSync(registryPath)) return null;\n names = JSON.parse(readFileSync(registryPath, \"utf8\")) as Record<string, unknown>;\n } catch {\n return null;\n }\n const name = names[uuid];\n if (typeof name !== \"string\" || !name) return null;\n return { id: uuid, name };\n}\n\n/**\n * True when `worker` was launched from `term` (the terminal asking about it).\n * Session-id match first; the tab key only when no registry entry matched —\n * a session without a name keeps the tab-scoped behaviour it always had.\n */\nexport function workerInScope(worker: WorkerStatus, term: string): boolean {\n if (!term) return false;\n const uuid = itermUuid(term);\n if (worker.session?.id && uuid) return worker.session.id === uuid;\n return tabKey(worker.term) === tabKey(term) && tabKey(term) !== \"\";\n}\n\n/**\n * Registry key for pane files: the session id when AIBroker knows this\n * terminal, else the tab key, else the raw iTerm UUID.\n */\nexport function scopeKey(term: string): string {\n const session = resolveSession(term);\n if (session) return session.id;\n return currentTabKey() || itermUuid(term);\n}\n\n/** `[Name]` when the worker has an AIBroker session name, else \"\". */\nexport function sessionTag(worker: { session?: { name?: string } | null }): string {\n return worker.session?.name ? `[${worker.session.name}]` : \"\";\n}\n\n// ---------------------------------------------------------------------------\n// spawner sessions — attribution for workers launched from a Claude Code Bash\n// ---------------------------------------------------------------------------\n\n/**\n * Claude Code exports neither ITERM_SESSION_ID nor its own session id to the\n * Bash tool, so a `pai worker run` from an orchestrator has no terminal to\n * record and its status would be unattributable. The status line is the one\n * process that sees both identities at once — the payload's session_id and\n * the tab's ITERM_SESSION_ID — so it bridges them: every refresh writes this\n * cwd-keyed map, and the runner reads it back at spawn time.\n */\nexport interface SessionMapEntry {\n session: string;\n ts: number;\n}\n\n/** How old a map entry may be for a spawn to adopt it (status lines refresh constantly while a session lives). */\nexport const SPAWNER_SESSION_TTL_MS = 10 * 60_000;\n\n/** Map entries not refreshed within this window are pruned on write. */\nconst SESSION_MAP_PRUNE_MS = 60 * 60_000;\n\nexport function sessionMapPath(logDir: string): string {\n return join(logDir, \"claude-session-map.json\");\n}\n\n/**\n * Record that the claude session `session` renders its status line in `cwd`.\n * Never throws — a broken map must not break the bar. Prunes stale entries;\n * skips the write when the entry is unchanged and fresh.\n */\nexport function recordSessionMapEntry(\n logDir: string,\n cwd: string,\n session: string,\n now: number = Date.now()\n): void {\n if (!cwd || !session) return;\n const path = sessionMapPath(logDir);\n let map: Record<string, SessionMapEntry> = {};\n try {\n if (existsSync(path)) {\n map = JSON.parse(readFileSync(path, \"utf8\")) as Record<string, SessionMapEntry>;\n }\n } catch {\n map = {}; // a damaged map is rewritten, never fatal\n }\n const prev = map[cwd];\n if (prev && prev.session === session && now - prev.ts < 60_000) return;\n const pruned: Record<string, SessionMapEntry> = {};\n for (const [dir, e] of Object.entries(map)) {\n if (now - e.ts < SESSION_MAP_PRUNE_MS) pruned[dir] = e;\n }\n pruned[cwd] = { session, ts: now };\n try {\n mkdirSync(logDir, { recursive: true });\n const tmp = `${path}.tmp`;\n writeFileSync(tmp, JSON.stringify(pruned), \"utf8\");\n renameSync(tmp, path);\n } catch {\n // unwritable log dir: no attribution this round, nothing else breaks\n }\n}\n\n/**\n * The claude session a new run was spawned by, when it can be known: a run\n * inside another worker inherits its spawner (chain stages keep the\n * orchestrator's tab that way); otherwise a fresh map entry for `cwd`.\n */\nexport function resolveSpawnerSession(\n logDir: string,\n cwd: string,\n env: NodeJS.ProcessEnv = process.env,\n now: number = Date.now()\n): string | null {\n const parentId = env.PAI_WORKER_ID;\n if (parentId) {\n const inherited = loadStatus(logDir, parentId)?.spawnerSession;\n if (inherited) return inherited;\n }\n const path = sessionMapPath(logDir);\n if (!existsSync(path) || !cwd) return null;\n try {\n const entry = (JSON.parse(readFileSync(path, \"utf8\")) as Record<string, SessionMapEntry>)[cwd];\n if (entry && entry.session && now - entry.ts < SPAWNER_SESSION_TTL_MS) return entry.session;\n } catch {\n // a damaged map simply attributes nothing\n }\n return null;\n}\n","/**\n * pane.ts — the per-worker follow pane in iTerm2.\n *\n * One small-font pane per worker, stacked in a right-hand column: the first\n * worker of a scope splits the launching session vertically, every further\n * one splits the lowest live worker pane horizontally, so panes stack top to\n * bottom. The split never sizes the new session — that would grow the whole\n * window; instead the window's bounds are read before the split and restored\n * right after, so the panes share the space the window already had. A split\n * would also make the new session the tab's active one — the scripts re-select\n * the launching session right after, so a pane opening never steals the\n * keystrokes the operator is typing. Each\n * pane runs `pai worker follow <id> --auto-exit <n>` under the `pai-worker`\n * dynamic profile (Close Sessions On End), so panes disappear by themselves.\n *\n * Panes are tracked per scope (AIBroker session id, else tab key) in\n * <logDir>/panes/<key>.json, keyed by iTerm session unique id, newest first —\n * the split lands below the lowest live worker pane.\n *\n * The dynamic profile's font is the family of iTerm's DEFAULT profile (the\n * `Default Bookmark Guid` entry in New Bookmarks) at workers.pane.fontSize —\n * never the launching session's profile, which the Python version did and\n * which produced iTerm's \"unknown parent name\" dialog whenever the two\n * differed. `Dynamic Profile Parent Name` is only written when that name\n * actually exists in New Bookmarks; when iTerm's preferences cannot be read\n * the profile is still written, with font Menlo-Regular and no parent.\n */\n\nimport { spawn } from \"node:child_process\";\nimport { execFileSync } from \"node:child_process\";\nimport {\n existsSync,\n mkdirSync,\n mkdtempSync,\n readFileSync,\n renameSync,\n rmSync,\n writeFileSync,\n} from \"node:fs\";\nimport { homedir, tmpdir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport type { WorkersConfig } from \"./config.js\";\nimport { panesDir } from \"./paths.js\";\nimport { itermUuid, scopeKey } from \"./scope.js\";\n\nexport const PROFILE_NAME = \"pai-worker\";\n\n/** Where the dynamic profile lives (env override keeps tests off the real one). */\nexport function dynamicProfilePath(): string {\n return process.env.PAI_WORKER_PROFILE ?? join(\n homedir(),\n \"Library\",\n \"Application Support\",\n \"iTerm2\",\n \"DynamicProfiles\",\n \"pai-worker.json\"\n );\n}\n\n// ---------------------------------------------------------------------------\n// AppleScripts (arguments are passed as argv items, never interpolated)\n// ---------------------------------------------------------------------------\n\n// One pane per worker: split the launching session vertically, or the lowest\n// live candidate horizontally. Returns \"<live ids>,|<new session id>\".\n// Exported for the script-content tests (window size, argv-only arguments).\nexport const WORKER_SPLIT_SCRIPT = `on run(argv)\n set targetID to item 1 of argv\n set candList to item 2 of argv\n set followCmd to item 3 of argv\n set profileName to item 4 of argv\n tell application id \"com.googlecode.iterm2\"\n if not running then return \"notrunning\"\n repeat with w in windows\n repeat with t in tabs of w\n repeat with s in sessions of t\n if id of s is targetID then\n -- sizing the new session would grow the whole window:\n -- pin the window's bounds now and restore them after\n -- the split, so the panes share the existing space.\n -- copy, never set: set stores the property\n -- reference lazily, so restoring it would re-read the\n -- post-split bounds instead of these — observed as the\n -- window jumping to the main display\n copy bounds of w to winBounds\n set sessIDs to {}\n repeat with other in sessions of t\n set end of sessIDs to (id of other as text)\n end repeat\n set lived to {}\n set splitS to missing value\n repeat with cid in my splitIds(candList)\n set cidText to (cid as text)\n if sessIDs contains cidText then\n set end of lived to cidText\n if splitS is missing value then\n set splitS to first session of t whose id is cidText\n end if\n end if\n end repeat\n if profileName is \"\" then\n if splitS is missing value then\n tell s\n set newS to split vertically with default profile\n end tell\n else\n tell splitS\n set newS to split horizontally with default profile\n end tell\n end if\n else\n if splitS is missing value then\n tell s\n set newS to split vertically with profile profileName\n end tell\n else\n tell splitS\n set newS to split horizontally with profile profileName\n end tell\n end if\n end if\n tell newS\n write text followCmd\n end tell\n -- a split makes the new session the tab's active one:\n -- re-select the launching session so keystrokes keep\n -- going where the operator was typing, not into the\n -- pane's chat prompt\n try\n select s\n end try\n try\n set bounds of w to winBounds\n end try\n set out to \"\"\n repeat with lid in lived\n set out to out & lid & \",\"\n end repeat\n return out & \"|\" & (id of newS as text)\n end if\n end repeat\n end repeat\n end repeat\n end tell\n return \"notfound\"\nend run\n\non splitIds(s)\n set out to {}\n if s is \"\" then return out\n set prevDels to AppleScript's text item delimiters\n set AppleScript's text item delimiters to \",\"\n repeat with part in text items of s\n set end of out to (part as text)\n end repeat\n set AppleScript's text item delimiters to prevDels\n return out\nend splitIds`;\n\n// TTys of every session in the launching session's tab (no-worker pane variant).\nconst TAB_TTYS_SCRIPT = `on run(argv)\n set targetID to item 1 of argv\n tell application id \"com.googlecode.iterm2\"\n if not running then return \"notrunning\"\n repeat with w in windows\n repeat with t in tabs of w\n repeat with s in sessions of t\n if id of s is targetID then\n set ttys to {}\n repeat with other in sessions of t\n copy (tty of other) to end of ttys\n end repeat\n return ttys\n end if\n end repeat\n end repeat\n end repeat\n end tell\n return \"notfound\"\nend run`;\n\n// Bounds (x1, y1, x2, y2, comma-joined) of the window hosting one iTerm\n// session — read-only, for `pai worker pane <id> --check`. Exported for the\n// script-content tests (reads bounds, never sets them).\nexport const WINDOW_BOUNDS_SCRIPT = `on run(argv)\n set targetID to item 1 of argv\n tell application id \"com.googlecode.iterm2\"\n if not running then return \"notrunning\"\n repeat with w in windows\n repeat with t in tabs of w\n repeat with s in sessions of t\n if id of s is targetID then\n copy bounds of w to winBounds\n set prevDels to AppleScript's text item delimiters\n set AppleScript's text item delimiters to \", \"\n set out to winBounds as text\n set AppleScript's text item delimiters to prevDels\n return out\n end if\n end repeat\n end repeat\n end repeat\n end tell\n return \"notfound\"\nend run`;\n\n// Split the launching session vertically and run followCmd in the new pane.\n// Exported for the script-content tests (focus stays on the launching session).\nexport const SPLIT_SCRIPT = `on run(argv)\n set targetID to item 1 of argv\n set followCmd to item 2 of argv\n tell application id \"com.googlecode.iterm2\"\n if not running then return \"notrunning\"\n repeat with w in windows\n repeat with t in tabs of w\n repeat with s in sessions of t\n if id of s is targetID then\n tell s\n set newS to split vertically with default profile\n end tell\n tell newS\n write text followCmd\n end tell\n -- keep the tab's active session where it was (see\n -- WORKER_SPLIT_SCRIPT)\n try\n select s\n end try\n return \"opened\"\n end if\n end repeat\n end repeat\n end repeat\n end tell\n return \"notfound\"\nend run`;\n\n// ---------------------------------------------------------------------------\n// osascript / ps / defaults helpers\n// ---------------------------------------------------------------------------\n\n/** Run an AppleScript with argv. Rejects when osascript itself cannot run. */\nfunction osascript(script: string, args: string[]): Promise<{ stdout: string; stderr: string }> {\n return new Promise((resolve, reject) => {\n const proc = spawn(\"osascript\", [\"-\", ...args], { stdio: [\"pipe\", \"pipe\", \"pipe\"] });\n let out = \"\";\n let err = \"\";\n proc.stdout.on(\"data\", (c: Buffer) => (out += c.toString(\"utf8\")));\n proc.stderr.on(\"data\", (c: Buffer) => (err += c.toString(\"utf8\")));\n proc.on(\"error\", reject);\n proc.on(\"close\", () => resolve({ stdout: out, stderr: err }));\n proc.stdin.write(script);\n proc.stdin.end();\n });\n}\n\nfunction psOutput(format: string): string {\n try {\n return execFileSync(\"ps\", [\"-axo\", format], { encoding: \"utf8\" });\n } catch {\n return \"\";\n }\n}\n\n/** TTys of running `worker follow` processes, normalised to /dev/ttysNNN. */\nfunction followTtys(): Set<string> {\n const ttys = new Set<string>();\n for (const line of psOutput(\"tty=,command=\").split(\"\\n\")) {\n const parts = line.trim().split(/\\s+/, 2);\n if (parts.length < 2 || parts[0] === \"ps\") continue;\n if (!/(^|\\/)(worker-follow|pai worker follow|glm-ps follow)\\b/.test(parts[1])) continue;\n if (parts[0].startsWith(\"ttys\")) ttys.add(`/dev/${parts[0]}`);\n }\n return ttys;\n}\n\n/** True while a `follow <wid>` process runs (its pane shows that worker). */\nexport function workerPaneOpen(wid: string): boolean {\n const pat = new RegExp(`(?:worker follow|worker-follow|glm-ps follow) ${wid.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")}\\\\b`);\n return psOutput(\"command=\").split(\"\\n\").some((ln) => pat.test(ln));\n}\n\n/** One entry of iTerm's New Bookmarks (the fields the pane cares about). */\nexport interface Bookmark {\n Name?: string;\n Guid?: string;\n \"Normal Font\"?: string;\n}\n\n/** What reading iTerm's preferences yielded — `error` carries the reason. */\nexport interface PrefsRead {\n bookmarks: Bookmark[];\n defaultGuid: string | null;\n error: string | null;\n}\n\n/** plutil's message for an ExecFileSync failure, else the exception text. */\nfunction toolErr(e: unknown): string {\n const err = e as { stderr?: string | Buffer; message?: string };\n const stderr = typeof err.stderr === \"string\" ? err.stderr : err.stderr?.toString(\"utf8\");\n return (stderr || err.message || String(e)).trim();\n}\n\n/**\n * Read New Bookmarks and Default Bookmark Guid from an iTerm preferences\n * plist. Key-scoped `plutil -extract`, never a whole-file JSON conversion:\n * real iTerm preferences carry `<date>` objects (SULastCheckTime & friends)\n * which `plutil -convert json` rejects outright — \"Invalid object in plist\n * for JSON format\" — so the whole-file read always failed and the dynamic\n * profile was never written.\n */\nexport function readItermPlist(plistPath: string): PrefsRead {\n let bookmarks: Bookmark[] = [];\n try {\n const out = execFileSync(\n \"plutil\",\n [\"-extract\", \"New Bookmarks\", \"json\", \"-o\", \"-\", plistPath],\n { encoding: \"utf8\", timeout: 10_000 }\n );\n const parsed = JSON.parse(out) as unknown;\n if (Array.isArray(parsed)) bookmarks = parsed as Bookmark[];\n } catch (e) {\n return { bookmarks: [], defaultGuid: null, error: `extracting New Bookmarks: ${toolErr(e)}` };\n }\n let defaultGuid: string | null = null;\n try {\n const out = execFileSync(\n \"plutil\",\n [\"-extract\", \"Default Bookmark Guid\", \"raw\", \"-o\", \"-\", plistPath],\n { encoding: \"utf8\", timeout: 10_000 }\n );\n defaultGuid = out.trim() || null;\n } catch {\n // no default guid set — defaultBookmarkFrom() yields null, fine\n }\n return { bookmarks, defaultGuid, error: null };\n}\n\n/** iTerm's exported preferences, via a temp plist (no shell pipe involved). */\nexport function itermPrefs(): PrefsRead {\n let dir: string | null = null;\n try {\n dir = mkdtempSync(join(tmpdir(), \"pai-iterm-\"));\n const plist = join(dir, \"iterm2.plist\");\n execFileSync(\"defaults\", [\"export\", \"com.googlecode.iterm2\", plist], {\n encoding: \"utf8\",\n timeout: 10_000,\n });\n return readItermPlist(plist);\n } catch (e) {\n return { bookmarks: [], defaultGuid: null, error: `defaults export: ${toolErr(e)}` };\n } finally {\n if (dir) {\n try {\n rmSync(dir, { recursive: true, force: true });\n } catch {\n // best effort cleanup of a temp dir\n }\n }\n }\n}\n\n/** The New Bookmarks entry of that profile name, null if absent. */\nfunction bookmarkFrom(read: PrefsRead, named: string): Bookmark | null {\n return read.bookmarks.find((b) => b.Name === named) ?? null;\n}\n\n/** The bookmark iTerm marks default, null when it is missing or unreadable. */\nfunction defaultBookmarkFrom(read: PrefsRead): Bookmark | null {\n return read.defaultGuid\n ? read.bookmarks.find((b) => b.Guid === read.defaultGuid) ?? null\n : null;\n}\n\n/** The default profile, read fresh from iTerm's preferences. */\nexport function defaultBookmark(): Bookmark | null {\n return defaultBookmarkFrom(itermPrefs());\n}\n\n/**\n * The pane profile's font: the family of iTerm's default profile at\n * `fontSize` points (\"MesloLGLNFM-Regular 18\" + 13 → \"MesloLGLNFM-Regular 13\"),\n * or \"Menlo-Regular <fontSize>\" when the default font cannot be read.\n */\nexport function paneFont(font: string | undefined, fontSize: number): string {\n const idx = (font ?? \"\").lastIndexOf(\" \");\n if (idx > 0) {\n const family = font!.slice(0, idx);\n if (!Number.isNaN(parseFloat(font!.slice(idx + 1)))) return `${family} ${fontSize}`;\n }\n return `Menlo-Regular ${fontSize}`;\n}\n\n/** Write the pai-worker dynamic profile; iTerm2 loads that directory itself. */\nexport function writeDynamicProfile(\n parent: Bookmark | null,\n fontSize: number,\n read: () => PrefsRead = itermPrefs\n): void {\n const profile: Record<string, unknown> = {\n Name: PROFILE_NAME,\n Guid: \"pai-worker-dynamic-profile\",\n \"Normal Font\": paneFont(parent?.[\"Normal Font\"], fontSize),\n \"Close Sessions On End\": true,\n };\n // Parent name only when iTerm actually has that profile loaded — a name it\n // does not know makes every split open an error dialog instead of a pane.\n if (parent?.Name && bookmarkFrom(read(), parent.Name)) {\n profile[\"Dynamic Profile Parent Name\"] = parent.Name;\n }\n const path = dynamicProfilePath();\n const dir = dirname(path);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n const tmp = `${path}.tmp`;\n writeFileSync(tmp, JSON.stringify({ Profiles: [profile] }, null, 2) + \"\\n\", \"utf8\");\n renameSync(tmp, path);\n}\n\nlet warnedPrefs = false;\n\n/**\n * Profile for new worker panes, creating the small-font one on demand.\n * \"\" means \"split with the default profile\" (also the fallback when the\n * dynamic profile does not show up in time).\n *\n * The profile file is written whenever it is missing — even when iTerm's\n * preferences cannot be read; then it gets font \"Menlo-Regular <fontSize>\"\n * and no parent, and the reason is logged to stderr once.\n */\nexport async function followProfile(\n fontSize: number,\n read: () => PrefsRead = itermPrefs\n): Promise<string> {\n const first = read();\n const parent = defaultBookmarkFrom(first);\n const path = dynamicProfilePath();\n const existed = existsSync(path);\n if (!existed) writeDynamicProfile(parent, fontSize, () => first);\n if (!parent) {\n if (!warnedPrefs) {\n warnedPrefs = true;\n const reason = first.error ?? \"no profile is marked default\";\n const what = existed\n ? `keeping ${path} as-is`\n : `wrote ${path} with Menlo-Regular ${fontSize} and no parent`;\n process.stderr.write(\n `pai worker pane: cannot read iTerm's default profile (${reason}) — ${what}\\n`\n );\n }\n if (first.error) return \"\"; // polling cannot succeed against unreadable prefs\n }\n for (let i = 0; i < 10; i++) {\n // iTerm2 picks DynamicProfiles up quickly; allow it 2 s\n if (bookmarkFrom(read(), PROFILE_NAME)) return PROFILE_NAME;\n await new Promise((r) => setTimeout(r, 200));\n }\n process.stderr.write(`pai worker pane: profile ${PROFILE_NAME} not visible, splitting with default\\n`);\n return \"\";\n}\n\n// ---------------------------------------------------------------------------\n// Pane registry\n// ---------------------------------------------------------------------------\n\ninterface PaneEntry {\n session: string;\n worker: string;\n opened: string;\n}\n\nfunction loadRegistry(path: string): PaneEntry[] {\n try {\n const reg = JSON.parse(readFileSync(path, \"utf8\")) as unknown;\n return Array.isArray(reg) ? (reg as PaneEntry[]) : [];\n } catch {\n return [];\n }\n}\n\nfunction saveRegistry(path: string, reg: PaneEntry[]): void {\n const dir = dirname(path);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n const tmp = `${path}.tmp`;\n writeFileSync(tmp, JSON.stringify(reg, null, 1), \"utf8\");\n renameSync(tmp, path);\n}\n\n// ---------------------------------------------------------------------------\n// Public entry points\n// ---------------------------------------------------------------------------\n\n/** Open (or only report on) the stacked follow pane of one worker. */\nexport async function openPaneForWorker(\n logDir: string,\n config: WorkersConfig,\n wid: string,\n term: string\n): Promise<string> {\n if (workerPaneOpen(wid)) return `pane for ${wid} already open`;\n const uid = itermUuid(term);\n const regPath = join(panesDir(logDir), `${scopeKey(term)}.json`);\n const reg = loadRegistry(regPath);\n const profile = await followProfile(config.pane.fontSize);\n // leading space keeps the command out of shell history; `exec` replaces the\n // pane's shell with follow itself, so signals reach it directly and no shell\n // lingers once the pane closes\n const cmd = ` exec pai worker follow ${JSON.stringify(wid)} --auto-exit ${config.pane.autoExitSecs}`;\n // registry newest first: the split lands below the lowest live worker pane\n const cands = [...reg].reverse().map((e) => e.session).join(\",\");\n const p = await osascript(WORKER_SPLIT_SCRIPT, [uid, cands, cmd, profile]);\n const out = p.stdout.trim();\n if (out === \"notfound\" || out === \"notrunning\") {\n throw new Error(\n out === \"notfound\"\n ? \"pai worker pane: no open iTerm2 session matches ITERM_SESSION_ID\"\n : \"pai worker pane: iTerm2 is not running\"\n );\n }\n const bar = out.indexOf(\"|\");\n if (bar < 0) {\n throw new Error(\n `pai worker pane: osascript failed: ${(p.stderr || out).slice(0, 200)}`\n );\n }\n const live = new Set(out.slice(0, bar).split(\",\").filter(Boolean));\n const pruned = reg.filter((e) => live.has(e.session));\n pruned.push({\n session: out.slice(bar + 1),\n worker: wid,\n opened: new Date().toISOString().replace(\"T\", \" \").slice(0, 19),\n });\n saveRegistry(regPath, pruned);\n return `pane opened for ${wid}`;\n}\n\n/**\n * One `--check` line with the bounds of the window hosting `term`'s iTerm\n * session — the before/after pair that shows whether a split moved it.\n * Read-only; never touches the window.\n */\nasync function windowBoundsLine(term: string): Promise<string> {\n const uid = itermUuid(term);\n if (!uid) return \"window bounds: (not in iTerm2)\";\n try {\n const p = await osascript(WINDOW_BOUNDS_SCRIPT, [uid]);\n const out = p.stdout.trim();\n if (out && out !== \"notfound\" && out !== \"notrunning\") return `window bounds: ${out}`;\n const why =\n out === \"notfound\" ? \"iTerm2 session not found\"\n : out === \"notrunning\" ? \"iTerm2 not running\"\n : (p.stderr.trim() || \"no output\").slice(0, 120);\n return `window bounds: (${why})`;\n } catch (e) {\n return `window bounds: (osascript: ${String((e as Error).message ?? e).slice(0, 120)})`;\n }\n}\n\n/**\n * Report-only variant used by `pai worker pane <id> --check`: whether a pane\n * runs for the worker, the bounds of the window hosting the asking session,\n * plus the dynamic profile's path, its existence, and the font it contains\n * (or, when missing, would write).\n */\nexport async function checkPaneForWorker(wid: string, fontSize: number, term: string): Promise<string> {\n const lines = [workerPaneOpen(wid) ? `pane for ${wid} open` : `no pane for ${wid}`];\n lines.push(await windowBoundsLine(term));\n const path = dynamicProfilePath();\n if (existsSync(path)) {\n let font = \"(unreadable)\";\n try {\n const parsed = JSON.parse(readFileSync(path, \"utf8\")) as {\n Profiles?: Array<{ \"Normal Font\"?: string }>;\n };\n font = parsed.Profiles?.[0]?.[\"Normal Font\"] ?? \"(none)\";\n } catch {\n // font stays \"(unreadable)\"\n }\n lines.push(`profile file: ${path} (exists)`);\n lines.push(`profile font: ${font}`);\n } else {\n const parent = defaultBookmark();\n lines.push(`profile file: ${path} (missing)`);\n lines.push(`profile font (would write): ${paneFont(parent?.[\"Normal Font\"], fontSize)}`);\n }\n return lines.join(\"\\n\");\n}\n\n/**\n * Split this iTerm tab and run `pai worker follow` in the new pane, unless\n * one already runs here (detected by TTY — iTerm overwrites session names\n * with the running command, so names cannot serve as idempotence).\n */\nexport async function openFollowPane(\n logDir: string,\n _config: WorkersConfig,\n term: string,\n checkOnly: boolean\n): Promise<string> {\n void logDir;\n const uid = itermUuid(term);\n const p = await osascript(TAB_TTYS_SCRIPT, [uid]);\n const out = p.stdout.trim();\n if (out === \"notfound\") throw new Error(\"pai worker pane: no open iTerm2 session matches ITERM_SESSION_ID\");\n if (out === \"notrunning\") throw new Error(\"pai worker pane: iTerm2 is not running\");\n const tabTtys = new Set(out.split(\",\").map((t) => t.trim()).filter(Boolean));\n const overlap = [...tabTtys].filter((t) => followTtys().has(t));\n if (overlap.length > 0) return \"follow pane already open\";\n if (checkOnly) return \"no follow pane\";\n const cmd = \" exec pai worker follow\";\n const s = await osascript(SPLIT_SCRIPT, [uid, cmd]);\n const sout = s.stdout.trim();\n if (sout === \"opened\") return \"follow pane opened\";\n if (sout === \"notfound\") throw new Error(\"pai worker pane: no open iTerm2 session matches ITERM_SESSION_ID\");\n throw new Error(`pai worker pane: osascript failed: ${(s.stderr || sout).slice(0, 200)}`);\n}\n","/**\n * tree.ts — sub-workers: parent detection and the tree caps.\n *\n * A worker launched by another worker (the runner exports PAI_WORKER_ID in\n * every worker's environment) carries `parent` in its status, so the worker\n * forest is visible in `ps`, the status line and the ledger. Two caps keep\n * the tree from growing without bound, both under `workers.tree`:\n *\n * - maxDepth (default 2): how deep sub-workers may nest. A top-level worker\n * sits at depth 0; its children at 1; grandchildren at 2 — one level past\n * the cap refuses with a clear message.\n * - maxChildren (default 4): how many children of one parent may run at the\n * same time. Finished children do not count; a planner works through its\n * sub-tasks in waves of this size.\n *\n * Chain stages also carry `parent` (the chain id), but chains are not workers\n * and never get a status file — a parent without a status is not capped.\n */\n\nimport type { WorkersTreeConfig } from \"./config.js\";\nimport { alive, loadStatus, loadStatuses, type WorkerStatus } from \"./status.js\";\n\n/** The env var the runner sets in every worker's environment. */\nexport const WORKER_ID_ENV = \"PAI_WORKER_ID\";\n\n/** The worker this process runs inside, when it runs inside one. */\nexport function parentFromEnv(env: NodeJS.ProcessEnv = process.env): string | null {\n const id = env[WORKER_ID_ENV];\n return typeof id === \"string\" && id.trim() ? id.trim() : null;\n}\n\n/**\n * Depth of `id` in the worker forest: 0 for a top-level worker, 1 + the\n * parent's depth for a sub-worker. Parents without a status file (chain ids,\n * unknown ids) count as roots; a cycle reads as its own depth and is cut off\n * after the statuses it walked. `chatIsRoot` stops one level short of the\n * chat-pane tracker (origin \"chat\"): the status line treats the pane as the\n * bar itself, so the workers it spawned are its top level.\n */\nexport function workerDepth(\n statuses: WorkerStatus[],\n id: string,\n opts?: { chatIsRoot?: boolean }\n): number {\n const byId = new Map(statuses.map((s) => [s.id, s]));\n let depth = 0;\n let cur = byId.get(id);\n const seen = new Set<string>([id]);\n while (cur?.parent && !seen.has(cur.parent)) {\n seen.add(cur.parent);\n const next = byId.get(cur.parent);\n if (!next) break; // chain id or stale parent: a root, not a level\n if (!(opts?.chatIsRoot && next.origin === \"chat\")) depth += 1;\n cur = next;\n }\n return depth;\n}\n\n/** Children of `parent` that are still running, oldest first. */\nexport function runningChildren(statuses: WorkerStatus[], parent: string): WorkerStatus[] {\n return statuses.filter((s) => s.parent === parent && s.state === \"running\" && alive(s.pid));\n}\n\n/**\n * Would starting a child of `parent` stay within the caps? Throws with a\n * clear message when it would not; returns silently when `parent` has no\n * status file (a chain id or an unknown id — not a worker, not capped).\n */\nexport function assertChildAllowed(\n logDir: string,\n parent: string,\n caps: WorkersTreeConfig,\n statuses: WorkerStatus[] = loadStatuses(logDir)\n): void {\n if (!statuses.some((s) => s.id === parent)) return;\n const depth = workerDepth(statuses, parent);\n if (depth + 1 > caps.maxDepth) {\n throw new Error(\n `worker tree: ${parent} sits at depth ${depth} and ` +\n `workers.tree.maxDepth is ${caps.maxDepth} — it cannot start another level of sub-workers. ` +\n `Hand the task up instead: pai worker handoff '{\"kind\":\"proposal\",\"text\":\"…\"}'`\n );\n }\n const kids = runningChildren(statuses, parent);\n if (kids.length >= caps.maxChildren) {\n throw new Error(\n `worker tree: ${parent} already has ${kids.length} running sub-workers ` +\n `(${kids.map((k) => k.id).join(\", \")}) and workers.tree.maxChildren is ${caps.maxChildren} — ` +\n `wait for one to finish, or raise the cap in the workers config`\n );\n }\n}\n\n/**\n * The parent a launch should record: an explicit one (chain stage) wins, else\n * the worker this process runs inside. Returns null for top-level runs.\n */\nexport function launchParent(explicit: string | undefined, env: NodeJS.ProcessEnv = process.env): string | null {\n return explicit ?? parentFromEnv(env);\n}\n\n/** Does a status file exist for `id` (i.e. is it a worker rather than a chain)? */\nexport function isWorkerId(logDir: string, id: string): boolean {\n return loadStatus(logDir, id) !== null;\n}\n","/**\n * operator.ts — talking to a running worker.\n *\n * Headless workers run with `--input-format stream-json` and their stdin held\n * open, so a run is a conversation: every line sent to the per-worker Unix\n * socket `<logDir>/<id>.sock` is forwarded to the child as a user message and\n * mirrored into the transcript as an `operator` event (rendered with a »\n * marker). Once the worker has finished its turn, stdin closes 2 s later\n * unless a new message arrives — after that, `say` refuses and `resume`\n * continues the same Claude session with the worker's context intact.\n */\n\nimport { createServer, connect, type Socket } from \"node:net\";\nimport { existsSync, unlinkSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { loadStatus, alive } from \"./status.js\";\n\nexport function operatorSocketPath(logDir: string, id: string): string {\n return join(logDir, `${id}.sock`);\n}\n\n/**\n * The runner's side: listen on the worker socket, hand every received line to\n * `onLine`. Returns the server (close it when the run ends; the socket file is\n * unlinked on close, best effort).\n */\nexport function createOperatorServer(\n logDir: string,\n id: string,\n onLine: (text: string) => void\n): import(\"node:net\").Server {\n const path = operatorSocketPath(logDir, id);\n try {\n if (existsSync(path)) unlinkSync(path);\n } catch {\n // a stale socket from a crashed run must not block the new one\n }\n const server = createServer((socket: Socket) => {\n let buf = \"\";\n socket.on(\"data\", (chunk: Buffer) => {\n buf += chunk.toString(\"utf8\");\n let nl: number;\n while ((nl = buf.indexOf(\"\\n\")) >= 0) {\n const line = buf.slice(0, nl).replace(/\\r$/, \"\");\n buf = buf.slice(nl + 1);\n if (line.trim()) onLine(line);\n socket.write(\"ok\\n\");\n }\n });\n });\n server.listen(path);\n server.on(\"close\", () => {\n try {\n if (existsSync(path)) unlinkSync(path);\n } catch {\n // already gone\n }\n });\n return server;\n}\n\n/**\n * `pai worker say <id> \"<text>\"`: forward one line to a running worker.\n * Resolves \"ok\", rejects with a clear message when the worker is not running.\n */\nexport function sayToWorker(logDir: string, id: string, text: string, timeoutMs = 4000): Promise<string> {\n const status = loadStatus(logDir, id);\n if (!status) {\n return Promise.reject(new Error(`no worker named \"${id}\"`));\n }\n if (status.state !== \"running\" || !alive(status.pid)) {\n return Promise.reject(\n new Error(\n `worker ${id} is not running (state: ${status.state}) — ` +\n `continue it instead with: pai worker resume ${id} \"<text>\"`\n )\n );\n }\n const path = operatorSocketPath(logDir, id);\n if (!existsSync(path)) {\n return Promise.reject(\n new Error(`worker ${id} has no operator socket (${path}) — it may predate this PAI version`)\n );\n }\n return new Promise((resolve, reject) => {\n const sock = connect(path);\n const fail = (e: Error) => {\n sock.destroy();\n reject(new Error(`cannot talk to worker ${id}: ${e.message}`));\n };\n sock.setTimeout(timeoutMs, () => fail(new Error(\"timeout\")));\n sock.once(\"error\", (e: Error) => fail(e));\n sock.once(\"connect\", () => {\n sock.write(text.replace(/\\n/g, \" \") + \"\\n\");\n });\n sock.once(\"data\", () => {\n sock.end();\n resolve(\"ok\");\n });\n });\n}\n","/**\n * handoff.ts — upward messages between workers.\n *\n * Coordination in the worker tree is upward only: a child appends a handoff\n * to `<logDir>/<parent>.inbox.jsonl` and (when the parent is a running\n * worker) the same text is said to it, so it lands in the parent's\n * conversation as `[handoff from <child>] …`. The parent's pane renders the\n * inbox as `◆` lines, `ps`/`worker_status` show an inbox count, and a child\n * that finishes delivers its structured report automatically as a\n * `kind: \"result\"` handoff. There is no downward or sideways path — telling\n * a worker something is the operator's job (`pai worker say`).\n */\n\nimport { appendFileSync, existsSync, mkdirSync, readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { alive, loadStatus } from \"./status.js\";\nimport { sayToWorker } from \"./operator.js\";\n\nexport const HANDOFF_KINDS = [\"proposal\", \"result\", \"question\", \"blocker\"] as const;\n\nexport type HandoffKind = (typeof HANDOFF_KINDS)[number];\n\nexport interface Handoff {\n /** Sending worker id. */\n from: string;\n /** Receiving worker id (the parent). */\n to: string;\n kind: HandoffKind;\n text: string;\n /** Structured payload (a report, a proposal's fields, …). */\n data?: Record<string, unknown>;\n /** ISO stamp, attached on append. */\n _ts?: string;\n}\n\n/** Where a worker's handoffs land: <logDir>/<id>.inbox.jsonl. */\nexport function inboxPath(logDir: string, id: string): string {\n return join(logDir, `${id}.inbox.jsonl`);\n}\n\nexport function isHandoffKind(v: unknown): v is HandoffKind {\n return typeof v === \"string\" && (HANDOFF_KINDS as readonly string[]).includes(v);\n}\n\n/**\n * Normalize a raw parsed value into a Handoff, or explain what is missing.\n * `from`/`to` may be preset by the caller (the CLI fills them from the\n * environment); everything else must be present and well-formed.\n */\nexport function parseHandoff(\n v: unknown,\n preset: Partial<Pick<Handoff, \"from\" | \"to\">> = {}\n): Handoff {\n if (typeof v !== \"object\" || v === null || Array.isArray(v)) {\n throw new Error(\"handoff payload must be a JSON object\");\n }\n const o = v as Record<string, unknown>;\n const from = typeof o.from === \"string\" && o.from ? o.from : preset.from;\n const to = typeof o.to === \"string\" && o.to ? o.to : preset.to;\n if (!from) throw new Error(`handoff needs \"from\" (the sending worker id)`);\n if (!to) throw new Error(`handoff needs \"to\" (the parent worker id)`);\n if (!isHandoffKind(o.kind)) {\n throw new Error(`handoff \"kind\" must be one of: ${HANDOFF_KINDS.join(\", \")}`);\n }\n const text = typeof o.text === \"string\" ? o.text : \"\";\n if (!text.trim()) throw new Error(`handoff needs a non-empty \"text\"`);\n const data =\n typeof o.data === \"object\" && o.data !== null && !Array.isArray(o.data)\n ? (o.data as Record<string, unknown>)\n : undefined;\n return { from, to, kind: o.kind, text, ...(data ? { data } : {}) };\n}\n\n/** Append one handoff to its recipient's inbox, stamped now. */\nexport function appendHandoff(logDir: string, h: Handoff, now: Date = new Date()): void {\n const path = inboxPath(logDir, h.to);\n const dir = dirname(path);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n appendFileSync(path, JSON.stringify({ ...h, _ts: now.toISOString() }) + \"\\n\", \"utf8\");\n}\n\n/** Every handoff in a worker's inbox, oldest first; damaged lines skip. */\nexport function readInbox(logDir: string, id: string): Handoff[] {\n const path = inboxPath(logDir, id);\n if (!existsSync(path)) return [];\n const out: Handoff[] = [];\n for (const line of readFileSync(path, \"utf8\").split(\"\\n\")) {\n if (!line.trim()) continue;\n try {\n out.push(JSON.parse(line) as Handoff);\n } catch {\n // a half-written line is not worth a crash; the rest still reads\n }\n }\n return out;\n}\n\n/** How the say path prefixes a handoff so the parent model knows its source. */\nexport function handoffMessage(h: Pick<Handoff, \"from\" | \"kind\" | \"text\">): string {\n return `[handoff from ${h.from}] (${h.kind}) ${h.text.replace(/\\s+/g, \" \").trim()}`;\n}\n\n/** True for the exact shape of handoffMessage() — the runner marks the mirror. */\nexport function isHandoffMessage(text: string): boolean {\n return new RegExp(`^\\\\[handoff from \\\\S+\\\\] \\\\((${HANDOFF_KINDS.join(\"|\")})\\\\) `).test(text);\n}\n\n/** Test seam for deliverHandoff: the say path, overridable with a mock. */\nexport interface HandoffDeps {\n say?: (id: string, text: string) => Promise<string>;\n}\n\n/**\n * Deliver one handoff: append it to the parent's inbox, then — when the\n * parent is a running worker with a live operator socket — say it so it\n * enters the parent's conversation. The say is best effort: a parent that is\n * busy, finished or gone still has the inbox line, and nothing here may make\n * a finishing child's exit path fail.\n */\nexport async function deliverHandoff(\n logDir: string,\n h: Handoff,\n deps: HandoffDeps = {},\n now: Date = new Date()\n): Promise<Handoff> {\n appendHandoff(logDir, h, now);\n const say = deps.say ?? ((id: string, text: string) => sayToWorker(logDir, id, text));\n const parent = loadStatus(logDir, h.to);\n if (parent && parent.state === \"running\" && alive(parent.pid)) {\n try {\n await say(h.to, handoffMessage(h));\n } catch {\n // the inbox line is the durable record; a failed say is not an error\n }\n }\n return h;\n}\n\n/**\n * Send a handoff from inside a worker (the `pai worker handoff` CLI and the\n * MCP tool both land here): the sender comes from PAI_WORKER_ID, the\n * recipient from its status file's `parent`. Rejects with a clear message\n * outside a worker or under a parentless worker.\n */\nexport async function handoffFromInside(\n logDir: string,\n env: NodeJS.ProcessEnv,\n payload: unknown,\n deps: HandoffDeps = {}\n): Promise<Handoff> {\n const mine = env.PAI_WORKER_ID;\n if (!mine) {\n throw new Error(\n \"not inside a worker — handoffs are sent by workers (PAI_WORKER_ID unset). \" +\n \"Talk to a running worker with: pai worker say <id> \\\"<text>\\\"\"\n );\n }\n const st = loadStatus(logDir, mine);\n if (!st) throw new Error(`no status for this worker (${mine}) — cannot find its parent`);\n if (!st.parent || !loadStatus(logDir, st.parent)) {\n throw new Error(\n `worker ${mine} has no worker parent to hand off to ` +\n `(parent: ${st.parent ?? \"(none)\"}) — handoffs go up the worker tree only`\n );\n }\n const h = parseHandoff(payload, { from: mine, to: st.parent });\n return deliverHandoff(logDir, h, deps);\n}\n","/**\n * worktree.ts — one git worktree per writing worker.\n *\n * A run whose class edits files (implement, complex, plan) and whose cwd is a\n * git repository gets its own worktree by default: `git worktree add\n * <logDir>/worktrees/<id> -b worker/<id>` from the current HEAD. The worker\n * commits its own work on that branch — the no-commit rule applies to the\n * main branch only — and the parent (or the operator) merges the result back:\n *\n * pai worker merge <id> git merge --no-ff worker/<id> + remove worktree + delete branch\n * pai worker discard <id> remove worktree and branch, keep nothing\n *\n * `ps` marks a worker with an unmerged branch `⎇`. Draft and review run in\n * place; `--no-worktree` opts out, `--worktree` forces one on.\n */\n\nimport { execFileSync } from \"node:child_process\";\nimport { existsSync, rmSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { loadStatus, saveStatus, UNLABELED, type WorkerStatus } from \"./status.js\";\n\nexport function worktreesDir(logDir: string): string {\n return join(logDir, \"worktrees\");\n}\n\nexport function worktreeBranch(id: string): string {\n return `worker/${id}`;\n}\n\nexport function worktreePath(logDir: string, id: string): string {\n return join(worktreesDir(logDir), id);\n}\n\n/** Run git in `cwd`, returning trimmed stdout; throws with stderr on failure. */\nexport function git(cwd: string, args: string[]): string {\n try {\n return execFileSync(\"git\", [\"-C\", cwd, ...args], {\n encoding: \"utf8\",\n timeout: 30_000,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n }).trim();\n } catch (e) {\n const err = e as { stderr?: Buffer | string; message?: string };\n const why =\n (typeof err.stderr === \"string\" ? err.stderr : err.stderr?.toString(\"utf8\")) ||\n err.message ||\n String(e);\n throw new Error(`git ${args.join(\" \")} in ${cwd}: ${why.trim()}`);\n }\n}\n\n/** Is `cwd` inside a git repository (a .git dir — worktrees: a .git file)? */\nexport function isGitRepo(cwd: string): boolean {\n try {\n return git(cwd, [\"rev-parse\", \"--git-dir\"]) !== \"\";\n } catch {\n return false;\n }\n}\n\n/**\n * Does the prompt read as a read-only task? Writing classes default to a\n * worktree; a prompt that only asks to look at things should not pay for one.\n * First-word verbs plus the explicit markers people actually write.\n */\nexport function promptLooksReadonly(prompt: string): boolean {\n const p = prompt.trim();\n if (!p) return true;\n if (/\\b(read[- ]only|do not (modify|change|edit|write)|don'?t (modify|change|edit|write)|no changes)\\b/i.test(p)) {\n return true;\n }\n return /^(review|read|analy[sz]e|research|summar[iy]|inspect|investigate|spotcheck|report|find|list|check|verify|describe|explain|show)\\b/i.test(\n p\n );\n}\n\n/** The classes whose runs write files and therefore default to a worktree. */\nexport const WORKTREE_CLASSES = [\"implement\", \"complex\", \"plan\"] as const;\n\n/** How the run flags decide the worktree question; undefined = decide by default. */\nexport type WorktreeFlag = boolean | undefined;\n\n/** Should this run get a worktree? Explicit flag first, then the default rule. */\nexport function worktreeWanted(\n flag: WorktreeFlag,\n opts: { cwd: string; className?: string; prompt: string | null }\n): boolean {\n if (flag !== undefined) return flag;\n if (!opts.className || !(WORKTREE_CLASSES as readonly string[]).includes(opts.className)) {\n return false;\n }\n if (!isGitRepo(opts.cwd)) return false;\n return !promptLooksReadonly(opts.prompt ?? \"\");\n}\n\nexport interface WorktreeInfo {\n dir: string;\n branch: string;\n base: string;\n}\n\n/**\n * Create the worktree and branch for `id` from `cwd`'s HEAD. Throws when git\n * refuses (no commits yet, branch exists, …) — the caller decides whether to\n * degrade to an in-place run.\n */\nexport function addWorktree(logDir: string, id: string, cwd: string): WorktreeInfo {\n const dir = worktreePath(logDir, id);\n const branch = worktreeBranch(id);\n const base = git(cwd, [\"rev-parse\", \"HEAD\"]);\n git(cwd, [\"worktree\", \"add\", dir, \"-b\", branch]);\n return { dir, branch, base };\n}\n\n/** Commits the branch collected on top of its base. */\nexport function commitsSince(dir: string, base: string): number {\n try {\n return parseInt(git(dir, [\"rev-list\", \"--count\", `${base}..HEAD`]), 10) || 0;\n } catch {\n return 0;\n }\n}\n\n/**\n * Record the worktree result in the status file: branch and commit count on\n * success, the worktree cleaned up on failure. Returns the updated status.\n */\nexport function recordWorktree(\n logDir: string,\n status: WorkerStatus,\n info: WorktreeInfo,\n ok: boolean\n): WorkerStatus {\n const s = { ...status };\n if (ok) {\n s.branch = info.branch;\n s.commits = commitsSince(info.dir, info.base);\n s.worktreeDir = info.dir;\n s.worktreeBase = info.base;\n } else {\n // a failed run leaves nothing to merge; the branch dies with the worktree\n removeWorktree(s.cwd, info.dir, true);\n try {\n git(s.cwd, [\"branch\", \"-D\", info.branch]);\n } catch {\n // already gone or never created\n }\n s.branch = null;\n s.worktreeDir = null;\n s.worktreeBase = null;\n s.commits = null;\n }\n saveStatus(logDir, s);\n return s;\n}\n\n/** Remove a worktree directory from git's books and the filesystem. */\nfunction removeWorktree(cwd: string, dir: string, force: boolean): void {\n try {\n git(cwd, [\"worktree\", \"remove\", ...(force ? [\"--force\"] : []), dir]);\n return;\n } catch {\n // fall through to the manual cleanup\n }\n if (existsSync(dir)) {\n try {\n rmSync(dir, { recursive: true, force: true });\n git(cwd, [\"worktree\", \"prune\"]);\n } catch {\n // best effort: a leftover directory is visible in the logDir\n }\n }\n}\n\n/**\n * Tracked changes (staged or not) plus untracked files — everything a\n * `git add -A` in `wtDir` would commit. The same two calls the old patch\n * carry used; name-only, not porcelain: a worktree-only change renders as\n * \" M path\" and the shared git() helper trims that leading space away.\n */\nexport function uncommittedPaths(wtDir: string): string[] {\n const tracked = git(wtDir, [\"diff\", \"--name-only\", \"HEAD\"]).split(\"\\n\").filter(Boolean);\n const untracked = git(wtDir, [\"ls-files\", \"--others\", \"--exclude-standard\"])\n .split(\"\\n\")\n .filter(Boolean);\n return [...tracked, ...untracked];\n}\n\n/**\n * Commit a worktree's uncommitted changes to its branch so the merge carries\n * them. `git merge` only moves committed work, so a worker that stopped\n * without committing would lose its edits to `worktree remove` — exactly\n * what happened live on 2026-09-17. Returns the salvaged paths, [] when the\n * worktree is clean. A failed commit throws with the worktree untouched:\n * its edits are still on disk, so nothing is lost.\n */\nexport function salvageUncommitted(wtDir: string, label: string): string[] {\n const paths = uncommittedPaths(wtDir);\n if (!paths.length) return [];\n try {\n git(wtDir, [\"add\", \"-A\"]);\n git(wtDir, [\"commit\", \"-m\", `salvaged: ${label}`]);\n } catch (e) {\n throw new Error(\n `cannot salvage the uncommitted changes in ${wtDir} — ${(e as Error).message}; ` +\n `nothing was lost: commit them there by hand, then re-run merge`\n );\n }\n return paths;\n}\n\n/**\n * The dirty paths of a checkout, parsed from `git status --porcelain -z`:\n * NUL-separated (a path with a newline in it cannot corrupt the parse),\n * rename entries contribute both sides, and any quoting is stripped.\n */\nfunction dirtyPaths(cwd: string): string[] {\n // raw execFileSync, not the shared git(): it trims stdout, which eats the\n // leading space of a worktree-only \" M path\" record and breaks the parse\n const raw = execFileSync(\"git\", [\"-C\", cwd, \"status\", \"--porcelain\", \"-z\"], {\n encoding: \"utf8\",\n timeout: 30_000,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n const fields = raw.split(\"\\0\");\n const strip = (p: string) => (p.startsWith('\"') && p.endsWith('\"') ? p.slice(1, -1) : p);\n const out: string[] = [];\n for (let i = 0; i < fields.length; i++) {\n const f = fields[i];\n if (!f || f.length < 4 || f.charAt(2) !== \" \") continue; // not an \"XY path\" record\n out.push(strip(f.slice(3)));\n const xy = f.slice(0, 2);\n if (xy.includes(\"R\") || xy.includes(\"C\")) {\n const orig = fields[i + 1]; // rename/copy records carry the source path next\n if (orig && orig.charAt(2) !== \" \") {\n out.push(strip(orig));\n i += 1;\n }\n }\n }\n return out;\n}\n\n/**\n * Refuse the merge when the original checkout is dirty in paths the branch\n * touches: the merge would overwrite those edits or fail on them, either way\n * leaving a half-state. No merge is made, the worktree stays.\n */\nfunction assertNoDirtyOverlap(\n cwd: string,\n incoming: string[],\n id: string,\n branch: string\n): void {\n const dirty = new Set(dirtyPaths(cwd));\n const overlap = [...new Set(incoming)].filter((p) => dirty.has(p)).sort();\n if (!overlap.length) return;\n throw new Error(\n `worker ${id}: the checkout ${cwd} has uncommitted changes in paths ${branch} touches: ` +\n `${overlap.join(\", \")}. Commit or stash them in the checkout, then re-run: pai worker merge ${id}. ` +\n `No merge was made; the worktree was kept.`\n );\n}\n\n/** Never remove a worktree that still holds uncommitted changes. */\nexport function assertWorktreeClean(wtDir: string, id: string): void {\n const leftover = uncommittedPaths(wtDir);\n if (leftover.length) {\n throw new Error(\n `worker ${id}: the worktree ${wtDir} still holds uncommitted changes ` +\n `(${leftover.join(\", \")}) — it was NOT removed; commit or copy them by hand, then re-run merge`\n );\n }\n}\n\n/**\n * `pai worker merge <id>`: salvage whatever the worker left uncommitted onto\n * its branch, refuse when the original checkout is dirty in paths the branch\n * touches, merge with --no-ff (the merge commit names the worker), then\n * remove the worktree and delete the branch; the status gains `merged: true`.\n * A branch with nothing to merge is refused loudly — after salvage that\n * genuinely means it holds nothing, and reporting success there would\n * destroy the worktree for no gain.\n */\nexport function mergeWorker(logDir: string, id: string): string {\n const st = mustHaveBranch(logDir, id);\n if (st.merged) return `worker ${id}: branch ${st.branch} already merged`;\n // salvage first: only a commit can carry uncommitted work through the merge\n const salvaged = existsSync(st.worktreeDir!)\n ? salvageUncommitted(st.worktreeDir!, st.label || UNLABELED)\n : [];\n const incoming = parseInt(git(st.cwd, [\"rev-list\", \"--count\", `HEAD..${st.branch}`]), 10) || 0;\n if (incoming <= 0) {\n throw new Error(\n `worker ${id}: branch ${st.branch} has no commits to merge. ` +\n `The worktree ${st.worktreeDir} was NOT removed — uncommitted work there would be destroyed. ` +\n `Commit it yourself, or drop everything with: pai worker discard ${id}`\n );\n }\n const mergeBase = git(st.cwd, [\"merge-base\", \"HEAD\", st.branch!]);\n const incomingPaths = git(st.cwd, [\"diff\", \"--name-only\", mergeBase, st.branch!])\n .split(\"\\n\")\n .filter(Boolean);\n assertNoDirtyOverlap(st.cwd, incomingPaths, id, st.branch!);\n try {\n git(st.cwd, [\"merge\", \"--no-ff\", st.branch!, \"-m\", `merge worker ${id} (${st.label})`]);\n } catch (e) {\n throw new Error(\n `worker ${id}: git refused the merge of ${st.branch} — ${(e as Error).message}. ` +\n `The worktree ${st.worktreeDir} was kept: resolve the conflict, then re-run merge`\n );\n }\n if (existsSync(st.worktreeDir!)) assertWorktreeClean(st.worktreeDir!, id);\n removeWorktree(st.cwd, st.worktreeDir!, false);\n let branchGone = true;\n try {\n git(st.cwd, [\"branch\", \"-d\", st.branch!]);\n } catch {\n branchGone = false; // -d refuses anything not fully merged; keep the branch, say so\n }\n const s = { ...st, merged: true };\n saveStatus(logDir, s);\n const base = `merged ${st.branch} into ${st.cwd} (worktree removed${branchGone ? \", branch deleted\" : \"; branch kept: git refused -d\"})`;\n return salvaged.length\n ? `${base}; salvaged ${salvaged.length} uncommitted change(s): ${salvaged.join(\", \")}`\n : base;\n}\n\n/** `pai worker discard <id>`: drop worktree and branch, keep nothing. */\nexport function discardWorker(logDir: string, id: string): string {\n const st = mustHaveBranch(logDir, id);\n removeWorktree(st.cwd, st.worktreeDir!, true);\n let branchGone = false;\n try {\n git(st.cwd, [\"branch\", \"-D\", st.branch!]);\n branchGone = true;\n } catch {\n branchGone = false;\n }\n const s = { ...st, branch: null, worktreeDir: null, worktreeBase: null, commits: null, merged: false };\n saveStatus(logDir, s);\n return `discarded worker ${id}: worktree removed${branchGone ? `, branch ${st.branch} deleted` : \"\"}`;\n}\n\nfunction mustHaveBranch(logDir: string, id: string): WorkerStatus & Required<Pick<WorkerStatus, \"branch\" | \"worktreeDir\">> {\n const st = loadStatus(logDir, id);\n if (!st) throw new Error(`no worker named \"${id}\"`);\n if (!st.branch || !st.worktreeDir) {\n throw new Error(\n `worker ${id} has no worktree branch to ${st.branch ? \"clean up\" : \"merge\"} — ` +\n `it ran in place or its branch was already handled`\n );\n }\n return st as WorkerStatus & Required<Pick<WorkerStatus, \"branch\" | \"worktreeDir\">>;\n}\n\n/**\n * The paragraph a worktree run's system prompt gains: it may commit on its\n * own branch (the no-commit rule holds for the main branch only), it must not\n * merge or push itself, and the parent or operator merges.\n */\nexport function worktreeSystemPrompt(id: string, branch: string, dir: string): string {\n return [\n \"You are running in your own git worktree:\",\n ` ${dir} on branch ${branch} (worker id ${id}).`,\n \"Commit your work on that branch as you go (git add / git commit) — committing here is expected;\",\n \"the no-commit rule applies to the main branch only, and this is not it.\",\n \"Do not merge, rebase or push; the operator merges your branch back with `pai worker merge`.\",\n \"Use ONLY relative paths inside the worktree, never absolute worktree paths — absolute paths break after merge and leak machine layout.\",\n ].join(\"\\n\");\n}\n","/**\n * report.ts — the worker contract: terse, structured final reports.\n *\n * Headless workers run with an appended system prompt that fixes their output\n * shape: act, verify, then finish with ONE final message that is a JSON object\n * and nothing else. The runner parses it out of the result; the viewer renders\n * it as a compact block instead of a wall of text.\n */\n\nimport { relative } from \"node:path\";\nimport { shortText } from \"./args.js\";\nimport type { Paint } from \"./render.js\";\n\n/** The marker the runner puts on operator messages the worker must answer. */\nexport const OPERATOR_MARK = \"[operator]\";\n\n/** Appended to the caller's system prompt on every headless run (2h). */\nexport const WORKER_CONTRACT_PROMPT = [\n \"You are a headless implementation worker, run non-interactively by an orchestrating session.\",\n \"No narration, no timestamps, no greetings, no summaries of what you read; act, verify, then stop.\",\n \"Your ONE final message is a JSON object and nothing else — no prose before or after, no code fence:\",\n '{\"changed\":[{\"path\":\"…\",\"summary\":\"…\"}],\"commands\":[\"…\"],\"checks\":[{\"name\":\"…\",\"ok\":true,\"detail\":\"…\"}],\"open\":[\"…\"],\"notes\":\"one line\"}',\n \"changed: files you touched (path + one-line summary). commands: the commands that verify the work.\",\n \"checks: each with ok true/false and the evidence in detail. open: what you could not finish, if anything.\",\n \"notes: one line, the headline a reviewer reads first.\",\n \"Never write shell heredocs — write the script to a file with the Write tool, then run it\",\n \"(heredocs fail silently or get swallowed).\",\n \"\",\n `Operator messages: a user turn starting with ${OPERATOR_MARK} was typed by the operator while you`,\n \"run (the prompt of a resumed run is the operator's too). Answer it FIRST, in one or two plain lines —\",\n \"a question gets its answer, an instruction gets one line naming what will change — then continue the\",\n \"task you were on.\",\n \"\",\n \"Sub-workers: you may start your own workers with `pai worker run --class <class> -p '<prompt>'`\",\n \"(Bash tool). Your children run on their own provider and report back to you automatically when\",\n \"they finish. Send a handoff UP instead of doing work yourself when a cheaper provider would\",\n \"suffice, the task is out of your scope, or a decision is needed:\",\n \"`pai worker handoff '{\\\"kind\\\":\\\"proposal\\\",\\\"text\\\":\\\"…\\\",\\\"data\\\":{…}}'` (kinds: proposal,\",\n \"question, blocker; results are sent for you when you finish). Sibling workers are not a\",\n \"coordination path — there is no sideways channel; everything goes up to your parent.\",\n].join(\"\\n\");\n\nexport interface WorkerReport {\n changed?: Array<{ path?: string; summary?: string }>;\n commands?: string[];\n checks?: Array<{ name?: string; ok?: boolean; detail?: string }>;\n open?: string[];\n notes?: string;\n}\n\n/**\n * Extract the JSON report from a worker's final message. Accepts a bare\n * object, a ```json fenced block, or an object embedded in surrounding text.\n * Only objects that look like the contract count (at least one of changed /\n * checks / notes) — any other JSON falls through to null so the raw text is\n * kept as-is.\n */\nexport function parseWorkerReport(text: string): WorkerReport | null {\n const trimmed = (text ?? \"\").trim();\n if (!trimmed) return null;\n const candidates: string[] = [];\n const fence = trimmed.match(/```(?:json)?\\s*(\\{[\\s\\S]*?\\})\\s*```/);\n if (fence) candidates.push(fence[1]);\n if (trimmed.startsWith(\"{\")) {\n // a bare object may still carry trailing punctuation/newlines\n candidates.push(trimmed);\n }\n // object embedded in prose: first \"{\" to the matching last \"}\"\n const first = trimmed.indexOf(\"{\");\n const last = trimmed.lastIndexOf(\"}\");\n if (first >= 0 && last > first) candidates.push(trimmed.slice(first, last + 1));\n for (const cand of candidates) {\n try {\n const v = JSON.parse(cand) as unknown;\n if (typeof v === \"object\" && v !== null && !Array.isArray(v) && looksLikeReport(v)) {\n return v as WorkerReport;\n }\n } catch {\n // try the next candidate\n }\n }\n return null;\n}\n\n/** The contract's fingerprint — guards against unrelated JSON in a reply. */\nfunction looksLikeReport(v: object): boolean {\n const o = v as WorkerReport;\n return Array.isArray(o.changed) || Array.isArray(o.checks) || typeof o.notes === \"string\";\n}\n\n/**\n * Render a parsed report as the compact block the viewer shows: changed paths\n * with summaries, checks with ✓/✗, open items, notes. Empty report → [].\n */\nexport function renderReport(c: Paint, prefix: string, r: WorkerReport, cwd = \"\"): string[] {\n const out: string[] = [];\n const has = (xs: unknown[] | undefined) => (xs ?? []).length > 0;\n if (has(r.changed)) {\n out.push(`${prefix}${c(\"bold\", \"changed\")}`);\n for (const ch of r.changed!) {\n out.push(`${prefix} ${relShort(ch.path ?? \"?\", cwd)} — ${shortText(ch.summary ?? \"\", 90)}`);\n }\n }\n if (has(r.checks)) {\n out.push(`${prefix}${c(\"bold\", \"checks\")}`);\n for (const ck of r.checks!) {\n const mark = ck.ok === false ? c(\"red\", \"✗\") : c(\"green\", \"✓\");\n const detail = ck.detail ? c(\"dim\", ` · ${shortText(ck.detail, 80)}`) : \"\";\n out.push(`${prefix} ${mark} ${ck.name ?? \"?\"}${detail}`);\n }\n }\n if (has(r.commands)) {\n out.push(`${prefix}${c(\"bold\", \"commands\")}`);\n for (const cmd of r.commands!) out.push(`${prefix} ${c(\"dim\", shortText(cmd, 100))}`);\n }\n if (has(r.open)) {\n out.push(`${prefix}${c(\"bold\", \"open\")}`);\n for (const o of r.open!) out.push(`${prefix} ${c(\"yellow\", shortText(o, 100))}`);\n }\n if (r.notes) out.push(`${prefix}${c(\"bold\", \"notes\")} ${shortText(r.notes, 120)}`);\n return out;\n}\n\n/** repo-relative display of a changed path when it lies under cwd. */\nfunction relShort(p: string, cwd: string): string {\n if (!cwd) return p;\n const r = relative(cwd, p);\n return r && !r.startsWith(\"..\") ? r : p;\n}\n","/**\n * mcp.ts — the MCP allowlist for headless workers.\n *\n * Headless workers start with NO MCP servers: every server definition is a\n * prompt-time tool inventory the model pays to know, and a worker that only\n * reads and edits files needs none of it. A run may opt in with `--mcp\n * name[,name…]`, a role carrying `\"mcp\": [...]`, or implicitly by naming\n * `mcp__server__tool` in --allowedTools (a grant without its server loaded is\n * a dead letter). Names may be single servers from ~/.claude.json's\n * `mcpServers` or `workers.mcpSets` set names, which expand to their member\n * list. The filtered config lands in\n * `<logDir>/<id>.mcp.json` and is passed with `--strict-mcp-config\n * --mcp-config` so exactly those servers load. MCP servers are chosen at\n * launch only — a mid-run `say` cannot add any.\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { WorkersConfigError, type WorkersConfig } from \"./config.js\";\n\n/** ~/.claude.json — the user's MCP server definitions (top-level mcpServers). */\nexport const CLAUDE_JSON = join(homedir(), \".claude.json\");\n\nexport function readMcpServers(claudeJson = CLAUDE_JSON): Record<string, unknown> {\n try {\n if (!existsSync(claudeJson)) return {};\n const parsed = JSON.parse(readFileSync(claudeJson, \"utf8\")) as Record<string, unknown>;\n const servers = parsed.mcpServers;\n if (typeof servers !== \"object\" || servers === null || Array.isArray(servers)) return {};\n return servers as Record<string, unknown>;\n } catch {\n // a damaged ~/.claude.json must not take workers down with it\n return {};\n }\n}\n\n/**\n * Split a `--mcp a,b,c` flag value (also accepts repeated flags already split\n * by the caller) and expand set names from workers.mcpSets.\n */\nexport function expandMcpNames(\n names: string[],\n config: Pick<WorkersConfig, \"mcpSets\">,\n claudeJson = CLAUDE_JSON\n): string[] {\n const available = readMcpServers(claudeJson);\n const out: string[] = [];\n for (const raw of names) {\n for (const name of raw.split(\",\").map((s) => s.trim()).filter(Boolean)) {\n if (name in config.mcpSets) {\n for (const member of config.mcpSets[name]) {\n if (!out.includes(member)) out.push(member);\n }\n continue;\n }\n if (!(name in available)) {\n const sets = Object.keys(config.mcpSets);\n throw new WorkersConfigError(\n `unknown MCP server \"${name}\". Available servers: ` +\n `${Object.keys(available).join(\", \") || \"(none in ~/.claude.json)\"}` +\n `${sets.length ? `; sets: ${sets.join(\", \")}` : \"\"}`\n );\n }\n if (!out.includes(name)) out.push(name);\n }\n }\n return out;\n}\n\n/**\n * Derive server names from tool grants: every `mcp__<server>__<tool>` (or bare\n * `mcp__<server>`, or `mcp__<server>__*`) in an --allowedTools list names a\n * server the run expects to be loaded. A grant is a dead letter unless its\n * server is in the filtered config, so the runner treats these as implicit\n * --mcp names — and only these; non-mcp grants load nothing.\n */\nexport function mcpServersFromToolGrants(tools: string[]): string[] {\n const out: string[] = [];\n for (const entry of tools) {\n for (const name of entry.split(\",\").map((s) => s.trim()).filter(Boolean)) {\n if (!name.startsWith(\"mcp__\")) continue;\n const server = name.slice(\"mcp__\".length).split(\"__\")[0];\n if (server && !out.includes(server)) out.push(server);\n }\n }\n return out;\n}\n\n/** Path of a run's filtered MCP config. */\nexport function runMcpConfigPath(logDir: string, id: string): string {\n return join(logDir, `${id}.mcp.json`);\n}\n\n/**\n * Write a config containing only `names` (already expanded) and return its\n * path. Unknown names fail fast with the available list.\n */\nexport function writeMcpConfig(\n logDir: string,\n id: string,\n names: string[],\n claudeJson = CLAUDE_JSON\n): string {\n const available = readMcpServers(claudeJson);\n const unknown = names.filter((n) => !(n in available));\n if (unknown.length) {\n throw new WorkersConfigError(\n `unknown MCP server(s): ${unknown.join(\", \")}. Available: ` +\n `${Object.keys(available).join(\", \") || \"(none in ~/.claude.json)\"}`\n );\n }\n const servers: Record<string, unknown> = {};\n for (const n of names) servers[n] = available[n];\n const path = runMcpConfigPath(logDir, id);\n if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true });\n writeFileSync(path, JSON.stringify({ mcpServers: servers }, null, 2) + \"\\n\", \"utf8\");\n return path;\n}\n\n/** `pai worker mcp list`: the servers and sets a run could name. */\nexport function describeMcp(config: Pick<WorkersConfig, \"mcpSets\">, claudeJson = CLAUDE_JSON): string[] {\n const servers = Object.keys(readMcpServers(claudeJson));\n const lines: string[] = [];\n if (servers.length) {\n lines.push(`servers (~/.claude.json):`);\n for (const s of servers) lines.push(` ${s}`);\n } else {\n lines.push(`no MCP servers defined in ~/.claude.json`);\n }\n const sets = Object.entries(config.mcpSets);\n if (sets.length) {\n lines.push(`sets (workers.mcpSets):`);\n for (const [name, members] of sets) lines.push(` ${name} = ${members.join(\", \")}`);\n }\n lines.push(`usage: pai worker run --mcp <name>[,<name>…] -p '<task>'`);\n return lines;\n}\n","/**\n * codex.ts — the \"codex\" runner engine.\n *\n * A ChatGPT plan gives no API key, only Codex CLI access, so a provider may\n * set `engine: \"codex\"`: `run` then shells out to `codex exec --json <prompt>`\n * (non-interactive) instead of Claude Code. The JSONL event stream is parsed\n * into the same status-file fields (turns, tools, last) and ledger lines as a\n * Claude run, and is normalised into claude-code-shaped events in the\n * worker's .jsonl so follow/replay/the pane render it unchanged.\n *\n * Implemented against the documented `codex exec --json` interface\n * (thread.started / item.completed / turn.completed / turn.failed lines);\n * verify against the installed CLI when one is present.\n * `--allowedTools` has no Codex equivalent and is dropped (ledgered).\n */\n\nimport { spawn, execFileSync } from \"node:child_process\";\nimport { existsSync, readFileSync as readKey } from \"node:fs\";\nimport { providerKeyPath, type WorkerProvider } from \"./config.js\";\n\n/** Build the codex exec argument vector (without the binary itself). */\nexport function buildCodexArgs(prompt: string, model: string | undefined): string[] {\n return [\n \"exec\",\n \"--json\",\n \"--skip-git-repo-check\",\n ...(model ? [\"-m\", model] : []),\n \"--\",\n prompt,\n ];\n}\n\n/** Env for a codex run: caller's env, Anthropic vars stripped, key applied. */\nexport function buildCodexEnv(provider: WorkerProvider): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = { ...process.env };\n delete env.ANTHROPIC_API_KEY;\n delete env.ANTHROPIC_BASE_URL;\n delete env.ANTHROPIC_AUTH_TOKEN;\n delete env.ANTHROPIC_DEFAULT_HAIKU_MODEL;\n delete env.ANTHROPIC_DEFAULT_SONNET_MODEL;\n delete env.ANTHROPIC_DEFAULT_OPUS_MODEL;\n // API-key providers: OpenAI env from keyFile/baseUrl; ChatGPT-login codex\n // (no keyFile) keeps its own auth from ~/.codex.\n const keyPath = providerKeyPath(provider);\n if (keyPath && existsSync(keyPath)) {\n env.OPENAI_API_KEY = readKey(keyPath, \"utf8\").trim();\n }\n if (provider.upstreamUrl) env.OPENAI_BASE_URL = provider.upstreamUrl;\n for (const [k, v] of Object.entries(provider.env)) env[k] = v;\n env.PAI_WORKER = \"1\";\n return env;\n}\n\n/** Codex \"not applicable\" flags the runner drops with a ledger note. */\nexport function codexDroppedFlags(argv: string[]): string[] {\n const dropped: string[] = [];\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i];\n if (a === \"--allowedTools\" || a === \"--disallowedTools\" || a === \"--mcp-config\" || a === \"--mcp\") {\n dropped.push(a);\n if (i + 1 < argv.length && !argv[i + 1].startsWith(\"-\")) i++; // and its value\n } else if (a.startsWith(\"--allowedTools=\") || a.startsWith(\"--mcp-config=\")) {\n dropped.push(a.split(\"=\")[0]);\n }\n }\n return dropped;\n}\n\n// ---------------------------------------------------------------------------\n// Event stream parsing (pure — tested with recorded JSONL lines)\n// ---------------------------------------------------------------------------\n\n/** One normalised claude-code-shaped event + its effect on the status. */\nexport interface CodexEventResult {\n events: Array<Record<string, unknown>>;\n turns: number;\n tools: number;\n last: string | null;\n isError: boolean;\n contextTokens: number | null;\n finalText: string | null;\n threadId: string | null;\n}\n\nexport const emptyCodexResult = (): CodexEventResult => ({\n events: [],\n turns: 0,\n tools: 0,\n last: null,\n isError: false,\n contextTokens: null,\n finalText: null,\n threadId: null,\n});\n\n/**\n * Fold one parsed `codex exec --json` line into the running result: appends\n * normalised transcript events and updates the counters in place.\n */\nexport function foldCodexLine(line: unknown, r: CodexEventResult): void {\n if (typeof line !== \"object\" || line === null) return;\n const e = line as Record<string, unknown>;\n const type = typeof e.type === \"string\" ? e.type : \"\";\n\n if (type === \"thread.started\" && typeof e.thread_id === \"string\") {\n r.threadId = e.thread_id;\n return;\n }\n if (type === \"item.completed\" && typeof e.item === \"object\" && e.item !== null) {\n const item = e.item as Record<string, unknown>;\n const kind = typeof item.type === \"string\" ? item.type : \"\";\n if (kind === \"agent_message\" && typeof item.text === \"string\") {\n r.turns += 1;\n r.last = `says: ${item.text.slice(0, 70)}`;\n r.finalText = item.text;\n r.events.push({\n type: \"assistant\",\n message: { content: [{ type: \"text\", text: item.text }] },\n });\n } else if (kind === \"command_execution\") {\n r.tools += 1;\n const cmd = typeof item.command === \"string\" ? item.command : \"?\";\n const rc = typeof item.exit_code === \"number\" ? item.exit_code : 0;\n r.last = `Bash: ${cmd.slice(0, 70)}`;\n r.events.push({\n type: \"assistant\",\n message: { content: [{ type: \"tool_use\", name: \"Bash\", input: { command: cmd } }] },\n });\n r.events.push({\n type: \"user\",\n message: {\n content: [\n {\n type: \"tool_result\",\n tool_use_id: \"\",\n is_error: rc !== 0,\n content: item.aggregated_output ?? \"\",\n },\n ],\n },\n });\n } else if (kind === \"file_change\") {\n r.tools += 1;\n const changes = Array.isArray(item.changes) ? item.changes : [];\n const files = changes\n .map((c) => (typeof c === \"object\" && c !== null && typeof (c as { path?: unknown }).path === \"string\" ? (c as { path: string }).path : \"?\"))\n .join(\", \");\n r.last = `files: ${files.slice(0, 70)}`;\n r.events.push({\n type: \"assistant\",\n message: { content: [{ type: \"tool_use\", name: \"Write\", input: { file_path: files } }] },\n });\n } else if (kind === \"mcp_tool_call\") {\n r.tools += 1;\n r.last = `mcp: ${String(item.tool ?? \"?\")}`;\n }\n return;\n }\n if (type === \"turn.completed\") {\n const usage = (typeof e.usage === \"object\" && e.usage !== null ? e.usage : {}) as {\n input_tokens?: number;\n output_tokens?: number;\n cached_input_tokens?: number;\n };\n const tokens =\n (usage.input_tokens ?? 0) + (usage.cached_input_tokens ?? 0) + (usage.output_tokens ?? 0);\n if (tokens > 0) r.contextTokens = tokens;\n return;\n }\n if (type === \"turn.failed\" || type === \"error\") {\n r.isError = true;\n const err = typeof e.error === \"object\" && e.error !== null ? e.error : {};\n r.last = String((err as { message?: unknown }).message ?? e.message ?? \"codex turn failed\");\n }\n}\n\n/** Parse one JSONL line; null for blanks and non-JSON noise. */\nexport function parseCodexLine(line: string): unknown | null {\n const t = line.trim();\n if (!t.startsWith(\"{\")) return null;\n try {\n return JSON.parse(t) as unknown;\n } catch {\n return null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// The engine probe used by `providers test`\n// ---------------------------------------------------------------------------\n\n/** True when the Codex CLI is on PATH (test reports \"not installed\" else). */\nexport function codexInstalled(): boolean {\n try {\n execFileSync(\"codex\", [\"--version\"], { timeout: 5000, stdio: \"ignore\" });\n return true;\n } catch {\n return false;\n }\n}\n\n/** Spawn helper shared by run and test; caller wires stdout. */\nexport function spawnCodex(args: string[], env: NodeJS.ProcessEnv, cwd: string) {\n return spawn(\"codex\", args, { env, cwd, stdio: [\"ignore\", \"pipe\", \"inherit\"] });\n}\n","/**\n * run.ts — the worker runner (port of the glm / glm-run pair, provider-neutral).\n *\n * One claude-code process per call, pointed at the chosen provider:\n *\n * - env: ANTHROPIC_BASE_URL/AUTH_TOKEN from the provider (token from its\n * key file, never from the environment), the three DEFAULT_*_MODEL vars,\n * the provider's extra env, nonessential traffic off, and ANTHROPIC_API_KEY\n * stripped so nothing can fall back to Anthropic billing. OpenAI-protocol\n * providers point at the PAI proxy instead (started on demand, the\n * provider name in the URL path); codex-engine providers run the Codex CLI.\n * - headless (-p): strict empty MCP config unless the caller brings one or\n * names servers via --mcp / a role (then a filtered <id>.mcp.json),\n * PAI_WORKER=1 so PAI's per-session hooks leave it alone, the worker\n * contract appended to the system prompt, `--input-format stream-json`\n * with the prompt as the first stdin user message (the operator socket\n * can add more mid-run), stream-json mirroring (every line stamped `_ts`)\n * into <logDir>/<id>.jsonl, a live <id>.status for ps/follow/status line\n * (context meter included), ledger lines, result printed in the caller's\n * --output-format (json adds the parsed `report`), and the follow pane\n * (unless --no-pane).\n * - interactive: no MCP restriction, no pane, ENABLE_TOOL_SEARCH=true.\n *\n * Auto-routed runs that die of a quota error before the first tool call are\n * restarted on the next provider in routing order (WORKER-REROUTE ledger line).\n */\n\nimport { spawn } from \"node:child_process\";\nimport { createInterface } from \"node:readline\";\nimport {\n existsSync,\n mkdirSync,\n openSync,\n writeFileSync,\n closeSync,\n writeSync,\n} from \"node:fs\";\nimport { parseRunnerArgs, shortText, stripPromptValues } from \"./args.js\";\nimport {\n assertProviderRunnable,\n classModelCapability,\n isModelCapability,\n readWorkersSection,\n resolveModelCapability,\n type WorkerProvider,\n} from \"./config.js\";\nimport { buildRunEnv } from \"./run-env.js\";\n\nexport { buildRunEnv } from \"./run-env.js\";\nimport { appendLedger } from \"./ledger.js\";\nimport { eventsPath, ledgerPath, noMcpConfigPath, workersLogDir } from \"./paths.js\";\nimport {\n type WorkerStatus,\n newWorkerId,\n saveStatus,\n describeTool,\n nowStamp,\n UNLABELED,\n} from \"./status.js\";\nimport { resolveSession, resolveSpawnerSession } from \"./scope.js\";\nimport { isQuotaFailure, nextAutoProvider, resolveTarget, setCooldown } from \"./routing.js\";\nimport { openPaneForWorker } from \"./pane.js\";\nimport { assertChildAllowed, isWorkerId, launchParent } from \"./tree.js\";\nimport { deliverHandoff, isHandoffMessage } from \"./handoff.js\";\nimport {\n addWorktree,\n recordWorktree,\n worktreeSystemPrompt,\n worktreeWanted,\n type WorktreeInfo,\n} from \"./worktree.js\";\nimport { OPERATOR_MARK, WORKER_CONTRACT_PROMPT, parseWorkerReport, type WorkerReport } from \"./report.js\";\nimport { expandMcpNames, mcpServersFromToolGrants, writeMcpConfig } from \"./mcp.js\";\nimport { createOperatorServer } from \"./operator.js\";\nimport { DEFAULT_PROXY_PORT, ensureProxyRunning } from \"./proxy/server.js\";\nimport {\n buildCodexArgs,\n buildCodexEnv,\n codexDroppedFlags,\n codexInstalled,\n emptyCodexResult,\n foldCodexLine,\n parseCodexLine,\n} from \"./codex.js\";\n\nexport interface RunOptions {\n providerFlag?: string;\n /** --class value (the old --role): a task class from workers.classes. */\n className?: string;\n modelFlag?: string;\n label?: string;\n noPane?: boolean;\n /** --mcp value: server/set names, comma-separated. */\n mcpFlag?: string;\n /** Everything after `--` (the claude args). */\n claudeArgs: string[];\n /** Working directory for the run (default: this process's cwd). */\n cwd?: string;\n /** Chain stage bookkeeping: the chain id this stage belongs to. */\n parent?: string;\n /** Chain stage bookkeeping: the class name of this stage. */\n stage?: string;\n /** Suppress result printing (MCP worker_run: its stdout is the RPC channel). */\n quiet?: boolean;\n /** Internal: notified with the worker id once it exists (resume uses it). */\n onWorkerStart?: (wid: string) => void;\n /** Internal: preset worker id (the planner mints its id before phase 1). */\n id?: string;\n /** --worktree/--no-worktree; undefined lets the class default decide. */\n worktreeFlag?: boolean;\n /** Internal: suppress recursion depth on reroute. */\n _reroutes?: number;\n /** Internal: this run is the planner's phase-1 worker, not a new orchestration. */\n _planner?: boolean;\n}\n\n/** The strict empty MCP config for headless workers, written on demand. */\nexport function ensureNoMcpConfig(logDir: string): string {\n const path = noMcpConfigPath(logDir);\n if (!existsSync(path)) {\n mkdirSync(logDir, { recursive: true });\n writeFileSync(path, '{ \"mcpServers\": {} }\\n', \"utf8\");\n }\n return path;\n}\n\n/** A stream-json user message for the child's stdin. */\nexport function stdinUserMessage(text: string): string {\n return JSON.stringify({ type: \"user\", message: { role: \"user\", content: text } });\n}\n\n/** An operator line as the worker sees it: carrying the contract's marker. */\nexport function operatorUserText(text: string): string {\n return `${OPERATOR_MARK} ${text}`;\n}\n\n/**\n * ISO stamp with seconds, attached to every mirrored event (2g): local time\n * with its offset (`2026-09-17T14:19:23+02:00`), so the viewer can render the\n * wall clock the operator lives in. `offMin` is east-positive minutes —\n * injectable so tests do not depend on the machine's zone.\n */\nexport function isoStamp(d = new Date(), offMin = -d.getTimezoneOffset()): string {\n const t = new Date(d.getTime() + offMin * 60_000);\n const sign = offMin < 0 ? \"-\" : \"+\";\n const abs = Math.abs(offMin);\n const hh = String(Math.floor(abs / 60)).padStart(2, \"0\");\n const mm = String(abs % 60).padStart(2, \"0\");\n return `${t.toISOString().slice(0, 19)}${sign}${hh}:${mm}`;\n}\n\nexport interface UsageBlock {\n input_tokens?: number;\n output_tokens?: number;\n cache_read_input_tokens?: number;\n cache_creation_input_tokens?: number;\n}\n\n// a type alias (not an interface): it must stay assignable to\n// Record<string, unknown> when written into the event transcript\nexport type StreamEvent = {\n type?: string;\n subtype?: string;\n session_id?: string;\n model?: string;\n cwd?: string;\n context_window?: number;\n model_info?: { context_window?: number } | null;\n message?: {\n content?: Array<{ type?: string; text?: string; name?: string; id?: string; input?: unknown }>;\n usage?: UsageBlock;\n };\n usage?: UsageBlock;\n result?: string;\n is_error?: boolean;\n is_compact?: boolean;\n num_turns?: number;\n duration_ms?: number;\n};\n\n/** Context tokens of an assistant/result usage block (input+cache+output). */\nexport function usageContextTokens(u: UsageBlock | undefined): number | null {\n if (!u) return null;\n const t =\n (u.input_tokens ?? 0) +\n (u.cache_read_input_tokens ?? 0) +\n (u.cache_creation_input_tokens ?? 0) +\n (u.output_tokens ?? 0);\n return t > 0 ? t : null;\n}\n\n/**\n * Context never shrinks mid-segment, so the status keeps the high-water\n * mark of the usage it has seen: a smaller later reading (short reply,\n * sidechain answer) must not drag the meter down. Compaction is the one\n * legitimate drop — see `resetContextTokensOnCompact`.\n */\nexport function bumpContextTokens(status: Pick<WorkerStatus, \"contextTokens\">, tokens: number | null): void {\n if (tokens === null || tokens <= 0) return;\n status.contextTokens = Math.max(status.contextTokens ?? 0, tokens);\n}\n\n/**\n * A compact boundary (`system`/`compact_boundary`, the shape Claude Code\n * writes with `compactMetadata.preTokens`; `compact` kept as the older\n * spelling) legitimately restarts the context at a lower size: the floor\n * drops to the event's own usage — usually none — so the next usage\n * reading re-seeds the meter at the fresh, smaller context.\n */\nexport function resetContextTokensOnCompact(status: Pick<WorkerStatus, \"contextTokens\">, e: StreamEvent): void {\n status.contextTokens = usageContextTokens(e.usage) ?? null;\n}\n\n/** A compact boundary in a worker's stream, in either event spelling. */\nexport function isCompactBoundary(e: StreamEvent): boolean {\n return e.type === \"system\" && (e.subtype === \"compact_boundary\" || e.subtype === \"compact\");\n}\n\n/** Context window announced by the init event, when the endpoint sends one. */\nexport function initContextWindow(e: StreamEvent): number | null {\n if (typeof e.context_window === \"number\" && e.context_window > 0) return e.context_window;\n if (e.model_info && typeof e.model_info.context_window === \"number\" && e.model_info.context_window > 0) {\n return e.model_info.context_window;\n }\n // the `[1m]` model variant announces a 1M-token window by suffix\n if (/\\[1m\\]$/.test((e.model ?? \"\").trim())) return 1_000_000;\n return null;\n}\n\n/**\n * Run one worker. Returns the process exit code to pass through.\n * Throws WorkersConfigError-shaped Errors for configuration problems.\n */\nexport async function runWorker(opts: RunOptions): Promise<number> {\n const { raw: _raw, workers: config } = readWorkersSection();\n void _raw;\n if (!config.enabled) {\n throw new Error(\n `workers are off. Turn them on with: pai worker on` +\n `\\n(then the Agent-tool hook stops denying Anthropic subagents only when you do)`\n );\n }\n const logDir = workersLogDir(config);\n mkdirSync(logDir, { recursive: true });\n\n // class plan is not one worker but the planner orchestration (planner.ts)\n if (opts.className === \"plan\" && !opts._planner) {\n const { runPlanner } = await import(\"./planner.js\");\n return runPlanner(opts);\n }\n\n // Sub-worker bookkeeping: an explicit parent (chain stage, planner child)\n // wins, else the worker this process runs inside (PAI_WORKER_ID). Both\n // caps from workers.tree apply to parents that are workers themselves.\n const parent = launchParent(opts.parent);\n if (parent) assertChildAllowed(logDir, parent, config.tree);\n\n const target = resolveTarget(config, logDir, {\n flagProvider: opts.providerFlag,\n className: opts.className,\n });\n assertProviderRunnable(target.providerName, target.provider);\n\n const parsed = parseRunnerArgs(opts.claudeArgs);\n const label =\n opts.label ??\n shortText(parsed.prompt ?? UNLABELED, 70);\n\n // model from the class target's alias (\"glm/fast\"), else the capability the\n // class implies (image class → image model, everything else → default)\n const alias = target.modelAlias;\n const capability =\n alias && isModelCapability(alias) ? alias : classModelCapability(opts.className);\n const model = opts.modelFlag ?? resolveModelCapability(target.provider, capability);\n\n try {\n if (target.provider.engine === \"codex\") {\n return await executeCodexRun({\n config,\n logDir,\n target,\n model,\n label,\n parsed,\n claudeArgs: opts.claudeArgs,\n noPane: opts.noPane ?? false,\n cwd: opts.cwd,\n parent: parent ?? undefined,\n stage: opts.stage,\n quiet: opts.quiet,\n onWorkerStart: opts.onWorkerStart,\n id: opts.id,\n worktreeFlag: opts.worktreeFlag,\n className: opts.className,\n });\n }\n return await executeRun({\n config,\n logDir,\n target,\n model,\n label,\n parsed,\n claudeArgs: opts.claudeArgs,\n noPane: opts.noPane ?? false,\n mcpFlag: opts.mcpFlag,\n cwd: opts.cwd,\n parent: parent ?? undefined,\n stage: opts.stage,\n quiet: opts.quiet,\n onWorkerStart: opts.onWorkerStart,\n id: opts.id,\n worktreeFlag: opts.worktreeFlag,\n className: opts.className,\n reroutes: opts._reroutes ?? 0,\n });\n } catch (e) {\n if (e instanceof Error && e.message.startsWith(\"key file\")) {\n throw new Error(\n `provider \"${target.providerName}\": ${e.message}` +\n `\\nPut the token in that file (chmod 600) or point keyFile elsewhere.`\n );\n }\n throw e;\n }\n}\n\ninterface ExecuteArgs {\n config: ReturnType<typeof readWorkersSection>[\"workers\"];\n logDir: string;\n target: ReturnType<typeof resolveTarget>;\n model: string;\n label: string;\n parsed: ReturnType<typeof parseRunnerArgs>;\n claudeArgs: string[];\n noPane: boolean;\n mcpFlag?: string;\n cwd?: string;\n parent?: string;\n stage?: string;\n quiet?: boolean;\n onWorkerStart?: (wid: string) => void;\n id?: string;\n worktreeFlag?: boolean;\n className?: string;\n reroutes: number;\n}\n\nasync function executeRun(a: ExecuteArgs): Promise<number> {\n const { config, logDir, target, model, label, parsed, noPane } = a;\n const headless = parsed.headless;\n\n // openai-protocol providers run through the local proxy (started on demand)\n let proxyUrl: string | undefined;\n if (target.provider.protocol === \"openai\") {\n const base = await ensureProxyRunning(DEFAULT_PROXY_PORT, logDir);\n proxyUrl = `${base}/${target.providerName}`;\n }\n const env = buildRunEnv(target.provider, headless, proxyUrl);\n\n const wid = a.id ?? newWorkerId();\n const cwd = a.cwd ?? process.cwd();\n const term = process.env.ITERM_SESSION_ID ?? \"\";\n const session = resolveSession(term);\n // orchestrator Bash children have no terminal identity (see scope.ts) — the\n // session map supplies the claude session they were spawned by instead\n const spawnerSession = resolveSpawnerSession(logDir, cwd);\n\n // One worktree per writing run (implement/complex/plan, a git cwd, a prompt\n // that is not read-only; --worktree/--no-worktree override). A git refusal\n // degrades to an in-place run — the worker itself must still run.\n let worktree: WorktreeInfo | null = null;\n if (headless && worktreeWanted(a.worktreeFlag, { cwd, className: a.className, prompt: parsed.prompt })) {\n try {\n worktree = addWorktree(logDir, wid, cwd);\n } catch (e) {\n const why = (e as Error).message;\n process.stderr.write(`pai worker: no worktree (${why}) — running in place\\n`);\n appendLedger(ledgerPath(logDir), \"WORKER-NOTE\", { id: wid, note: `no worktree: ${why}` });\n }\n }\n // sub-workers detect themselves (and their parent) through this variable\n env.PAI_WORKER_ID = wid;\n\n const status: WorkerStatus = {\n id: wid,\n pid: process.pid,\n label,\n cwd,\n term,\n provider: target.providerName,\n model,\n state: \"running\",\n started: nowStamp(),\n updated: nowStamp(),\n turns: 0,\n tools: 0,\n last: headless ? \"starting\" : \"interactive\",\n rc: null,\n secs: null,\n // interactive runs ARE the chat pane, not a spawned subagent of it\n origin: headless ? \"spawn\" : \"chat\",\n ...(session ? { session } : {}),\n ...(spawnerSession ? { spawnerSession } : {}),\n // no window seed: contextWindow comes from the init event only, and the\n // meter stays hidden until one is announced (never a guessed default)\n ...(a.parent ? { parent: a.parent, stage: a.stage } : {}),\n ...(worktree ? { worktreeDir: worktree.dir, branch: worktree.branch, worktreeBase: worktree.base } : {}),\n };\n saveStatus(logDir, status);\n a.onWorkerStart?.(wid);\n const ledger = ledgerPath(logDir);\n appendLedger(ledger, \"WORKER-START\", {\n id: wid,\n provider: target.providerName,\n mode: headless ? \"headless\" : \"interactive\",\n model,\n cwd,\n label,\n });\n\n // Follow pane: headless only, best effort, never blocking the worker.\n if (headless && !noPane && config.pane.enabled && term && process.env.PAI_WORKER_AUTOPANE !== \"0\") {\n void openPaneForWorker(logDir, config, wid, term).catch(() => {});\n }\n\n // MCP: caller config > allowlist (--mcp flag / --mcp args / role / mcp__\n // grants in --allowedTools) > the strict empty set.\n let mcpArgs: string[] = [];\n if (headless && !parsed.callerMcpConfig) {\n const wanted = [\n ...(a.mcpFlag ? [a.mcpFlag] : []),\n ...parsed.mcp,\n ...(target.classMcp ?? []),\n ...mcpServersFromToolGrants(parsed.allowedTools),\n ];\n if (wanted.length) {\n const names = expandMcpNames(wanted, config); // unknown names fail fast\n mcpArgs = [\"--strict-mcp-config\", \"--mcp-config\", writeMcpConfig(logDir, wid, names)];\n } else {\n mcpArgs = [\"--strict-mcp-config\", \"--mcp-config\", ensureNoMcpConfig(logDir)];\n }\n }\n\n // In stdin mode the prompt moves to the first user message on stdin, so it\n // must come off the command line (bare -p stays: stream-json needs --print).\n const restArgs = headless ? stripPromptValues(parsed.rest) : parsed.rest;\n const cmd: string[] = [\"claude\"];\n if (!parsed.callerModel) cmd.push(\"--model\", model);\n cmd.push(...mcpArgs, ...restArgs);\n if (headless) {\n cmd.push(\"--output-format\", \"stream-json\", \"--verbose\", \"--input-format\", \"stream-json\");\n if (!parsed.callerSystemPrompt) cmd.push(\"--append-system-prompt\", WORKER_CONTRACT_PROMPT);\n if (worktree) {\n cmd.push(\n \"--append-system-prompt\",\n worktreeSystemPrompt(wid, worktree.branch, worktree.dir)\n );\n }\n }\n\n const t0 = Date.now();\n const proc = spawn(cmd[0], cmd.slice(1), {\n env,\n cwd: worktree?.dir ?? cwd,\n stdio: headless ? [\"pipe\", \"pipe\", \"inherit\"] : \"inherit\",\n });\n\n // --- stdin lifecycle (2i): prompt in, socket forwards, close 2 s after result\n let operatorInFlight = 0;\n let closeTimer: NodeJS.Timeout | null = null;\n const armStdinClose = () => {\n if (closeTimer) clearTimeout(closeTimer);\n closeTimer = setTimeout(() => {\n if (operatorInFlight === 0) {\n try {\n proc.stdin?.end();\n } catch {\n /* already closed */\n }\n }\n }, 2_000);\n };\n\n let eventsFd: number | null = null;\n const writeEvent = (obj: Record<string, unknown>): void => {\n if (eventsFd === null) return;\n try {\n writeSync(eventsFd, JSON.stringify({ ...obj, _ts: isoStamp() }) + \"\\n\");\n } catch {\n // a full disk must not take the worker transcript's process down\n }\n };\n\n const operatorServer = headless\n ? createOperatorServer(logDir, wid, (text) => {\n operatorInFlight += 1;\n if (closeTimer) {\n clearTimeout(closeTimer);\n closeTimer = null;\n }\n // a handoff delivery's mirror carries a flag: the viewer shows the\n // inbox ◆ line instead, never both\n writeEvent({ type: \"operator\", text, handoff: isHandoffMessage(text) });\n try {\n proc.stdin?.write(stdinUserMessage(operatorUserText(text)) + \"\\n\");\n } catch {\n /* child gone; the socket is closed by the run's cleanup */\n }\n })\n : null;\n\n let killed = false;\n const cleanup = () => {\n if (closeTimer) clearTimeout(closeTimer);\n operatorServer?.close();\n };\n const onSignal = (sig: string) => {\n killed = true;\n status.state = \"killed\";\n status.rc = 143;\n status.secs = Math.floor((Date.now() - t0) / 1000);\n status.last = `killed by signal ${sig}`;\n saveStatus(logDir, status);\n appendLedger(ledger, \"WORKER-END\", {\n id: wid,\n provider: target.providerName,\n mode: \"headless\",\n model,\n rc: 143,\n secs: status.secs,\n killed: 1,\n label,\n });\n cleanup();\n try {\n proc.kill();\n } catch {\n /* already gone */\n }\n process.exit(143);\n };\n process.once(\"SIGTERM\", () => onSignal(\"SIGTERM\"));\n process.once(\"SIGINT\", () => onSignal(\"SIGINT\"));\n\n // Holder for the last result event + its parsed report: assigned inside the\n // readline callback below, read after the await.\n const ctx: { resultEvent: StreamEvent | null; resultReport: WorkerReport | null } = {\n resultEvent: null,\n resultReport: null,\n };\n\n if (headless) {\n // the first user message carries the prompt (the -p value was stripped)\n if (parsed.prompt !== null) {\n try {\n proc.stdin!.write(stdinUserMessage(parsed.prompt) + \"\\n\");\n } catch {\n /* child died instantly; the close handler reports it */\n }\n }\n eventsFd = openSync(eventsPath(logDir, wid), \"a\");\n const rl = createInterface({ input: proc.stdout! });\n rl.on(\"line\", (line) => {\n if (parsed.outputFormat === \"stream-json\") {\n process.stdout.write(line + \"\\n\");\n }\n if (!line.startsWith(\"{\")) return;\n let e: StreamEvent;\n try {\n e = JSON.parse(line) as StreamEvent;\n } catch {\n return;\n }\n writeEvent(e as Record<string, unknown>);\n if (e.type === \"system\" && e.subtype === \"init\") {\n if (e.session_id) status.claudeSession = e.session_id;\n const cw = initContextWindow(e);\n if (cw) status.contextWindow = cw;\n saveStatus(logDir, status);\n } else if (isCompactBoundary(e)) {\n resetContextTokensOnCompact(status, e);\n saveStatus(logDir, status);\n } else if (e.type === \"assistant\") {\n status.turns += 1;\n bumpContextTokens(status, usageContextTokens(e.message?.usage));\n for (const block of e.message?.content ?? []) {\n if (block.type === \"tool_use\") {\n status.tools += 1;\n status.last = describeTool(block.name ?? \"?\", block.input);\n } else if (block.type === \"text\" && (block.text ?? \"\").trim()) {\n status.last = \"says: \" + shortText(block.text, 70);\n }\n }\n saveStatus(logDir, status);\n } else if (e.type === \"result\") {\n const tokens = usageContextTokens(e.usage);\n // a compact result legitimately restarts the context lower\n if (e.is_compact) status.contextTokens = tokens ?? status.contextTokens;\n else bumpContextTokens(status, tokens);\n const report = parseWorkerReport(e.result ?? \"\");\n if (report?.notes) status.last = shortText(report.notes, 90);\n else if (e.result) status.last = shortText(e.result, 90);\n saveStatus(logDir, status);\n operatorInFlight = 0;\n armStdinClose();\n ctx.resultEvent = e;\n ctx.resultReport = report;\n }\n });\n }\n\n const rc = await new Promise<number>((resolve, reject) => {\n proc.on(\"error\", reject);\n proc.on(\"close\", (code) => resolve(code ?? (killed ? 143 : 1)));\n });\n if (eventsFd !== null) closeSync(eventsFd);\n cleanup();\n\n const secs = Math.floor((Date.now() - t0) / 1000);\n const resultEvent = ctx.resultEvent;\n const ok = rc === 0 && resultEvent !== null && !resultEvent.is_error;\n status.state = ok ? \"done\" : \"failed\";\n status.rc = rc;\n status.secs = secs;\n if (resultEvent) status.last = shortText(resultEvent.result ?? \"\", 90);\n if (ctx.resultReport?.notes) status.last = shortText(ctx.resultReport.notes, 90);\n saveStatus(logDir, status);\n appendLedger(ledger, \"WORKER-END\", {\n id: wid,\n provider: target.providerName,\n mode: headless ? \"headless\" : \"interactive\",\n model,\n rc,\n secs,\n turns: status.turns,\n tools: status.tools,\n label,\n });\n\n // worktree outcome: keep branch + commit count on success, clean up on failure\n if (worktree) recordWorktree(logDir, status, worktree, ok);\n\n if (headless && !a.quiet) {\n printResult(parsed.outputFormat, resultEvent, rc, logDir, wid, ctx.resultReport, worktreeExtras(status));\n }\n\n // Quota reroute: only auto-routed runs, dead before the first tool call.\n const resultText = resultEvent?.result ?? \"\";\n if (\n !ok &&\n headless &&\n a.target.via === \"auto\" &&\n config.routing.retryOnQuota &&\n status.turns <= 1 &&\n status.tools === 0 &&\n isQuotaFailure(resultText)\n ) {\n setCooldown(logDir, target.providerName, config.routing.cooldownMinutes);\n const next = nextAutoProvider(config, logDir, target.providerName);\n if (next && a.reroutes < config.routing.order.length) {\n appendLedger(ledger, \"WORKER-REROUTE\", {\n from: target.providerName,\n to: next,\n reason: \"quota\",\n });\n return runWorker({\n providerFlag: next,\n label,\n noPane: a.noPane,\n mcpFlag: a.mcpFlag,\n cwd: a.cwd,\n parent: a.parent,\n stage: a.stage,\n claudeArgs: a.claudeArgs,\n onWorkerStart: a.onWorkerStart,\n worktreeFlag: a.worktreeFlag,\n className: a.className,\n _reroutes: a.reroutes + 1,\n });\n }\n }\n\n // A finishing child reports to its worker parent automatically: the report\n // lands in the parent's inbox and (when the parent still runs) is said to\n // it so it enters the parent's conversation.\n const finalRc = rc !== 0 ? rc : ok ? 0 : 1;\n if (status.parent && isWorkerId(logDir, status.parent)) {\n try {\n await deliverHandoff(logDir, {\n from: wid,\n to: status.parent,\n kind: \"result\",\n text: shortText(\n ctx.resultReport?.notes ?? resultEvent?.result ?? (ok ? \"done\" : \"failed\"),\n 400\n ),\n data: {\n rc: finalRc,\n ok,\n ...(status.branch ? { branch: status.branch, commits: status.commits ?? 0 } : {}),\n ...(ctx.resultReport ? { report: ctx.resultReport } : {}),\n },\n });\n } catch {\n // the inbox line is best effort; it must never fail the exit path\n }\n }\n\n return finalRc;\n}\n\n/** The worktree fields printResult adds to a json payload, when there is one. */\nfunction worktreeExtras(s: WorkerStatus): Record<string, unknown> | undefined {\n return s.branch ? { branch: s.branch, commits: s.commits ?? 0 } : undefined;\n}\n\n// ---------------------------------------------------------------------------\n// codex engine (2d)\n// ---------------------------------------------------------------------------\n\ninterface CodexArgs extends Omit<ExecuteArgs, \"reroutes\" | \"mcpFlag\"> {}\n\nasync function executeCodexRun(a: CodexArgs): Promise<number> {\n const { config, logDir, target, model, label, parsed, noPane } = a;\n if (!parsed.headless || parsed.prompt === null) {\n throw new Error(\n `provider \"${target.providerName}\" (engine codex) supports headless runs only: ` +\n `pass the task with -p '<prompt>'`\n );\n }\n const env = buildCodexEnv(target.provider);\n const wid = a.id ?? newWorkerId();\n const cwd = a.cwd ?? process.cwd();\n const term = process.env.ITERM_SESSION_ID ?? \"\";\n const session = resolveSession(term);\n const spawnerSession = resolveSpawnerSession(logDir, cwd);\n\n let worktree: WorktreeInfo | null = null;\n if (worktreeWanted(a.worktreeFlag, { cwd, className: a.className, prompt: parsed.prompt })) {\n try {\n worktree = addWorktree(logDir, wid, cwd);\n } catch (e) {\n const why = (e as Error).message;\n process.stderr.write(`pai worker: no worktree (${why}) — running in place\\n`);\n appendLedger(ledgerPath(logDir), \"WORKER-NOTE\", { id: wid, note: `no worktree: ${why}` });\n }\n }\n env.PAI_WORKER_ID = wid;\n // codex takes instructions through the prompt, not a system prompt flag\n const prompt = (worktree ? worktreeSystemPrompt(wid, worktree.branch, worktree.dir) + \"\\n\\n\" : \"\") + parsed.prompt;\n\n const status: WorkerStatus = {\n id: wid,\n pid: process.pid,\n label,\n cwd,\n term,\n provider: target.providerName,\n model,\n state: \"running\",\n started: nowStamp(),\n updated: nowStamp(),\n turns: 0,\n tools: 0,\n last: \"starting\",\n rc: null,\n secs: null,\n origin: \"spawn\",\n ...(session ? { session } : {}),\n ...(spawnerSession ? { spawnerSession } : {}),\n // codex has no init event of its own: the synthetic one below announces\n // an explicitly configured window (never a guessed default)\n ...(target.provider.contextWindow ? { contextWindow: target.provider.contextWindow } : {}),\n ...(a.parent ? { parent: a.parent, stage: a.stage } : {}),\n ...(worktree ? { worktreeDir: worktree.dir, branch: worktree.branch, worktreeBase: worktree.base } : {}),\n };\n saveStatus(logDir, status);\n a.onWorkerStart?.(wid);\n const ledger = ledgerPath(logDir);\n appendLedger(ledger, \"WORKER-START\", {\n id: wid,\n provider: target.providerName,\n mode: \"headless\",\n engine: \"codex\",\n model,\n cwd,\n label,\n });\n const dropped = codexDroppedFlags(a.claudeArgs);\n if (dropped.length) {\n appendLedger(ledger, \"WORKER-NOTE\", {\n id: wid,\n note: `dropped for codex: ${dropped.join(\", \")}`,\n });\n }\n\n if (!noPane && config.pane.enabled && term && process.env.PAI_WORKER_AUTOPANE !== \"0\") {\n void openPaneForWorker(logDir, config, wid, term).catch(() => {});\n }\n\n const t0 = Date.now();\n const proc = spawn(\"codex\", buildCodexArgs(prompt, parsed.callerModel ? undefined : model), {\n env,\n cwd: worktree?.dir ?? cwd,\n stdio: [\"ignore\", \"pipe\", \"inherit\"],\n });\n\n const fold = emptyCodexResult();\n const eventsFd = openSync(eventsPath(logDir, wid), \"a\");\n const writeEvent = (obj: Record<string, unknown>) => {\n try {\n writeSync(eventsFd, JSON.stringify({ ...obj, _ts: isoStamp() }) + \"\\n\");\n } catch {\n // best effort transcript\n }\n };\n writeEvent({\n type: \"system\",\n subtype: \"init\",\n model,\n cwd,\n ...(target.provider.contextWindow ? { context_window: target.provider.contextWindow } : {}),\n });\n\n let killed = false;\n process.once(\"SIGTERM\", onCodexSignal(\"SIGTERM\"));\n process.once(\"SIGINT\", onCodexSignal(\"SIGINT\"));\n function onCodexSignal(sig: string) {\n return () => {\n killed = true;\n status.state = \"killed\";\n status.rc = 143;\n status.secs = Math.floor((Date.now() - t0) / 1000);\n status.last = `killed by signal ${sig}`;\n saveStatus(logDir, status);\n try {\n proc.kill();\n } catch {\n /* already gone */\n }\n process.exit(143);\n };\n }\n\n const rl = createInterface({ input: proc.stdout! });\n rl.on(\"line\", (line) => {\n if (parsed.outputFormat === \"stream-json\") process.stdout.write(line + \"\\n\");\n const parsedLine = parseCodexLine(line);\n if (parsedLine === null) return;\n foldCodexLine(parsedLine, fold);\n if (fold.threadId && !status.claudeSession) status.claudeSession = fold.threadId;\n status.turns = fold.turns;\n status.tools = fold.tools;\n if (fold.last) status.last = shortText(fold.last, 90);\n bumpContextTokens(status, fold.contextTokens);\n saveStatus(logDir, status);\n for (const ev of fold.events.splice(0)) writeEvent(ev);\n });\n\n const rc = await new Promise<number>((resolve, reject) => {\n proc.on(\"error\", reject);\n proc.on(\"close\", (code) => resolve(code ?? (killed ? 143 : 1)));\n });\n closeSync(eventsFd);\n\n const secs = Math.floor((Date.now() - t0) / 1000);\n const finalText = fold.finalText ?? \"\";\n const report = parseWorkerReport(finalText);\n const resultEvent: StreamEvent = {\n type: \"result\",\n result: finalText,\n is_error: fold.isError || rc !== 0,\n num_turns: fold.turns,\n duration_ms: secs * 1000,\n };\n writeEvent(resultEvent);\n\n const ok = rc === 0 && !fold.isError;\n status.state = ok ? \"done\" : \"failed\";\n status.rc = rc;\n status.secs = secs;\n status.last = shortText(report?.notes ?? finalText, 90) || (ok ? \"done\" : \"failed\");\n saveStatus(logDir, status);\n appendLedger(ledger, \"WORKER-END\", {\n id: wid,\n provider: target.providerName,\n mode: \"headless\",\n engine: \"codex\",\n model,\n rc,\n secs,\n turns: status.turns,\n tools: status.tools,\n label,\n });\n\n if (worktree) recordWorktree(logDir, status, worktree, ok);\n\n if (!a.quiet) printResult(parsed.outputFormat, resultEvent, rc, logDir, wid, report, worktreeExtras(status));\n\n // the codex engine reports to its worker parent the same way (handoff.ts)\n const finalRc = rc !== 0 ? rc : ok ? 0 : 1;\n if (status.parent && isWorkerId(logDir, status.parent)) {\n try {\n await deliverHandoff(logDir, {\n from: wid,\n to: status.parent,\n kind: \"result\",\n text: shortText(report?.notes ?? finalText ?? (ok ? \"done\" : \"failed\"), 400),\n data: {\n rc: finalRc,\n ok,\n ...(status.branch ? { branch: status.branch, commits: status.commits ?? 0 } : {}),\n ...(report ? { report } : {}),\n },\n });\n } catch {\n // best effort; never fail the exit path\n }\n }\n return finalRc;\n}\n\n// ---------------------------------------------------------------------------\n// result printing\n// ---------------------------------------------------------------------------\n\nexport function printResult(\n fmt: \"text\" | \"json\" | \"stream-json\",\n resultEvent: StreamEvent | null,\n rc: number,\n logDir: string,\n wid: string,\n report?: WorkerReport | null,\n extras?: Record<string, unknown>\n): void {\n if (fmt === \"stream-json\") return; // already mirrored live\n if (fmt === \"json\") {\n const payload = report\n ? { ...(resultEvent ?? { is_error: true, result: \"no result event\", rc }), report, ...extras }\n : { ...(resultEvent ?? { is_error: true, result: \"no result event\", rc }), ...extras };\n console.log(JSON.stringify(payload));\n return;\n }\n if (resultEvent) {\n console.log(resultEvent.result ?? \"\");\n } else {\n process.stderr.write(\n `pai worker: run produced no result (rc=${rc}); see ${eventsPath(logDir, wid)}\\n`\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// providers test: the 90-second pong probe\n// ---------------------------------------------------------------------------\n\nexport interface ProviderTestResult {\n provider: string;\n model: string;\n latencyMs: number;\n result: string;\n ok: boolean;\n /** Set when the probe could not run (e.g. \"codex not installed\"). */\n skipped?: string;\n}\n\n/**\n * Run a one-word pong probe through the provider (headless, no pane) and\n * report provider, model, latency and the reply. ok is false unless the reply\n * was exactly \"pong\" (case-insensitive, whitespace-trimmed).\n */\nexport async function testProvider(\n providerName: string,\n provider: WorkerProvider,\n logDir: string,\n timeoutMs = 90_000\n): Promise<ProviderTestResult> {\n assertProviderRunnable(providerName, provider);\n const model = provider.models.default;\n\n if (provider.engine === \"codex\") {\n const skipped = codexInstalled() ? undefined : \"codex not installed\";\n return {\n provider: providerName,\n model,\n latencyMs: 0,\n result: skipped ?? \"codex engine: pong probe through codex exec not implemented\",\n ok: false,\n ...(skipped ? { skipped } : {}),\n };\n }\n\n let proxyUrl: string | undefined;\n if (provider.protocol === \"openai\") {\n const base = await ensureProxyRunning(DEFAULT_PROXY_PORT, logDir);\n proxyUrl = `${base}/${providerName}`;\n }\n const env = buildRunEnv(provider, true, proxyUrl);\n const t0 = Date.now();\n const proc = spawn(\n \"claude\",\n [\n \"--model\", model,\n \"--strict-mcp-config\", \"--mcp-config\", ensureNoMcpConfig(logDir),\n \"-p\", \"Reply with exactly one word: pong\",\n \"--output-format\", \"json\",\n ],\n { env, stdio: [\"ignore\", \"pipe\", \"inherit\"] }\n );\n const timer = setTimeout(() => {\n try { proc.kill(\"SIGKILL\"); } catch { /* already gone */ }\n }, timeoutMs);\n\n let out = \"\";\n proc.stdout.on(\"data\", (chunk: Buffer) => {\n out += chunk.toString(\"utf8\");\n });\n const rc = await new Promise<number>((resolve) => {\n proc.on(\"error\", () => resolve(1));\n proc.on(\"close\", (code) => resolve(code ?? 1));\n });\n clearTimeout(timer);\n\n return {\n provider: providerName,\n model,\n latencyMs: Date.now() - t0,\n result: resultFromOutput(out),\n ok: rc === 0 && resultFromOutput(out).trim().toLowerCase() === \"pong\",\n };\n}\n\n/**\n * Pull the reply out of claude's stdout. With --verbose, `--output-format\n * json` dumps a JSON array of stream events and the reply sits in the last\n * \"result\" event; without it, stdout is the single result object. Accept\n * either shape, plus a bare-text fallback for error output.\n */\nexport function resultFromOutput(out: string): string {\n const trimmed = out.trim();\n if (!trimmed) return \"\";\n const parse = (s: string): unknown => {\n try {\n return JSON.parse(s);\n } catch {\n return undefined;\n }\n };\n const fromValue = (v: unknown): string | null => {\n if (Array.isArray(v)) {\n for (let i = v.length - 1; i >= 0; i--) {\n const r = fromValue(v[i]);\n if (r !== null) return r;\n }\n return null;\n }\n if (typeof v === \"object\" && v !== null) {\n const o = v as StreamEvent;\n if (o.type === \"result\" && typeof o.result === \"string\") return o.result;\n }\n return null;\n };\n const direct = fromValue(parse(trimmed));\n if (direct !== null) return direct;\n const lines = trimmed.split(\"\\n\");\n for (let i = lines.length - 1; i >= 0; i--) {\n const r = fromValue(parse(lines[i]));\n if (r !== null) return r;\n }\n return trimmed.slice(0, 200);\n}\n","/**\n * chain.ts — draft-then-implement chains: `--chain draft,implement[,review]`.\n *\n * The chain runs each stage as its own worker (own id, own pane, `parent` set\n * to the chain id), so `ps` shows the chain as a tree and every stage can be\n * followed, replayed and said to like any other worker:\n *\n * - draft turns the operator's brief into a full spec file under\n * <logDir>/specs/<chain id>.md (goal, constraints, files likely\n * touched, acceptance checks, verification commands);\n * - any other stage (implement, plan, …) runs with that spec as its prompt\n * and the original brief attached;\n * - review reads the spec and the working-tree diff and produces the\n * structured report.\n *\n * A stage that fails (or a draft that produces no spec file) stops the chain;\n * the caller then writes the spec itself and re-runs without the draft stage.\n */\n\nimport { existsSync, mkdirSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { readWorkersSection } from \"./config.js\";\nimport { appendLedger } from \"./ledger.js\";\nimport { ledgerPath, workersLogDir } from \"./paths.js\";\nimport { newWorkerId } from \"./status.js\";\nimport { shortText } from \"./args.js\";\nimport { runWorker, type RunOptions } from \"./run.js\";\n\n/** Where a chain's spec file lives: <logDir>/specs/<chain id>.md. */\nexport function specPathFor(logDir: string, chainId: string): string {\n return join(logDir, \"specs\", `${chainId}.md`);\n}\n\n/**\n * Replace the caller's -p value with `prompt`, dropping every existing\n * -p/--print pair first (two -p flags on one claude command line are an error,\n * so the chain must never leave the brief in place when swapping prompts).\n */\nexport function swapPromptArg(claudeArgs: string[], prompt: string): string[] {\n const out: string[] = [];\n for (let i = 0; i < claudeArgs.length; i++) {\n const a = claudeArgs[i];\n if (a === \"-p\" || a === \"--print\") {\n if (i + 1 < claudeArgs.length && !claudeArgs[i + 1].startsWith(\"-\")) i += 1;\n continue;\n }\n out.push(a);\n }\n out.push(\"-p\", prompt);\n return out;\n}\n\nexport function draftPrompt(brief: string, specPath: string): string {\n return [\n \"You are the DRAFT stage of a worker chain. Turn the operator's brief below into a full implementation spec and write it to\",\n specPath,\n \"with the Write tool.\",\n \"\",\n \"Use exactly these five sections as Markdown headings:\",\n \"# Goal\",\n \"# Constraints\",\n \"# Files likely touched\",\n \"# Acceptance checks\",\n \"# Verification commands\",\n \"\",\n \"Read the repository first (Glob/Grep/Read) so the spec names real files and real commands. Do not implement anything.\",\n \"\",\n \"## Operator brief\",\n \"\",\n brief,\n ].join(\"\\n\");\n}\n\nexport function implementPrompt(brief: string, specPath: string | null, spec: string | null): string {\n const head = spec\n ? [\n \"You are the IMPLEMENT stage of a worker chain. The draft stage wrote the spec below (also at \" +\n specPath +\n \"). Implement it exactly, then run the spec's verification commands before finishing.\",\n \"\",\n \"## Spec\",\n \"\",\n spec,\n ]\n : [\"Implement the operator's brief below.\"];\n return [...head, \"\", \"## Operator brief\", \"\", brief].join(\"\\n\");\n}\n\nexport function reviewPrompt(brief: string, specPath: string | null, spec: string | null): string {\n const specPart = spec\n ? [\n \"\",\n \"## Spec (also at \" + specPath + \")\",\n \"\",\n spec,\n ]\n : [];\n return [\n \"You are the REVIEW stage of a worker chain. The implement stage just ran. Read the repository's diff (run `git diff` and `git status`; use `git diff --stat` for the overview) and check it against the spec's acceptance checks and verification commands — run the checks when they are cheap. Do not fix anything you find; report it.\",\n ...specPart,\n \"\",\n \"## Operator brief\",\n \"\",\n brief,\n ].join(\"\\n\");\n}\n\nexport interface ChainOptions {\n /** Class names, run in order: e.g. [\"draft\", \"implement\", \"review\"]. */\n stages: string[];\n /** Overrides the class of every stage when given (--class with --chain). */\n className?: string;\n providerFlag?: string;\n modelFlag?: string;\n label?: string;\n noPane?: boolean;\n mcpFlag?: string;\n /** The operator's brief — the -p value of the run. */\n brief: string;\n /** The caller's claude args (allowedTools etc.); the -p value is swapped. */\n claudeArgs: string[];\n cwd?: string;\n /** Internal: notified with the chain id once it exists (worker_run uses it). */\n onChainStart?: (chainId: string) => void;\n /** Internal: suppress result printing (the MCP shim's stdout is the RPC channel). */\n quiet?: boolean;\n}\n\nexport interface ChainDeps {\n /** Stage runner; tests inject a mock, production uses runWorker. */\n runStage?: (opts: RunOptions) => Promise<number>;\n /** logDir override for tests; default: the configured workers logDir. */\n logDir?: string;\n}\n\n/** Run a chain of stages; returns the exit code of the first failed stage, 0 when all pass. */\nexport async function runChain(opts: ChainOptions, deps: ChainDeps = {}): Promise<number> {\n const stages = opts.stages.map((s) => s.trim()).filter(Boolean);\n if (!stages.length) throw new Error(\"--chain needs at least one class, e.g. --chain draft,implement\");\n const runStage = deps.runStage ?? runWorker;\n const logDir = deps.logDir ?? workersLogDir(readWorkersSection().workers);\n const chainId = newWorkerId();\n const specPath = specPathFor(logDir, chainId);\n mkdirSync(join(logDir, \"specs\"), { recursive: true }); // the draft stage writes into it\n const baseLabel = opts.label ?? shortText(opts.brief, 40);\n\n appendLedger(ledgerPath(logDir), \"WORKER-CHAIN\", {\n chain: chainId,\n stages: stages.join(\",\"),\n label: baseLabel,\n });\n opts.onChainStart?.(chainId);\n\n let spec: string | null = null;\n for (let i = 0; i < stages.length; i++) {\n const stage = stages[i];\n if (stage === \"draft\") {\n spec = null; // a chain may legally start over; the draft rewrites it\n } else if (spec === null && existsSync(specPath)) {\n spec = readFileSync(specPath, \"utf8\");\n }\n const prompt =\n stage === \"draft\"\n ? draftPrompt(opts.brief, specPath)\n : stage === \"review\"\n ? reviewPrompt(opts.brief, spec ? specPath : null, spec)\n : implementPrompt(opts.brief, spec ? specPath : null, spec);\n process.stderr.write(\n `chain ${chainId}: stage ${i + 1}/${stages.length} ${stage} (spec: ${specPath})\\n`\n );\n const rc = await runStage({\n className: opts.className ?? stage,\n providerFlag: opts.providerFlag,\n modelFlag: opts.modelFlag,\n label: `${baseLabel} · ${stage}`,\n noPane: opts.noPane,\n mcpFlag: opts.mcpFlag,\n claudeArgs: swapPromptArg(opts.claudeArgs, prompt),\n cwd: opts.cwd,\n parent: chainId,\n stage,\n quiet: opts.quiet,\n });\n if (stage === \"draft\") {\n if (!existsSync(specPath)) {\n process.stderr.write(\n `chain ${chainId}: draft stage produced no spec at ${specPath} — stopping. ` +\n `Write the spec yourself and re-run without the draft stage.\\n`\n );\n appendLedger(ledgerPath(logDir), \"WORKER-CHAIN-END\", {\n chain: chainId,\n rc: rc !== 0 ? rc : 1,\n failed: \"draft\",\n });\n return rc !== 0 ? rc : 1;\n }\n spec = readFileSync(specPath, \"utf8\");\n }\n if (rc !== 0) {\n appendLedger(ledgerPath(logDir), \"WORKER-CHAIN-END\", {\n chain: chainId,\n rc,\n failed: stage,\n });\n return rc;\n }\n }\n appendLedger(ledgerPath(logDir), \"WORKER-CHAIN-END\", { chain: chainId, rc: 0 });\n return 0;\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"config-D1G9IFpn.mjs","names":[],"sources":["../src/runtime-paths.ts","../src/notifications/types.ts","../src/tasks/types.ts","../src/daemon/config.ts"],"sourcesContent":["/**\n * runtime-paths.ts — the shared files PAI talks to the running daemon through.\n *\n * Sockets, logs and pidfiles live at fixed absolute paths under /tmp, and every\n * one of them is shared with a *live* daemon. That makes them the same hazard\n * as any other piece of real user state a test can reach: writing one from a\n * test does not fail, it corrupts something already running, silently.\n *\n * The home guard in test/setup-home-guard.ts does not cover them. It works by\n * redirecting HOME, so it protects paths *derived* from the home directory and\n * nothing else — a hardcoded \"/tmp/pai.sock\" walks straight past it. Verified\n * on 2026-08-01 by snapshotting all five and running the suite: nothing writes\n * to them today. But \"no test does this yet\" is not a property, and the next\n * daemon test to be written would clobber the live socket with nothing to stop\n * it.\n *\n * So each path is read from an environment variable with the historical /tmp\n * value as its default. Behaviour is unchanged for every real invocation; the\n * test setup overrides the variables and the whole class becomes unreachable\n * rather than merely absent, the same way the home guard works.\n *\n * Read at call time, not at module load: the test setup file runs before any\n * module is imported, but resolving these eagerly would still bake in whatever\n * the environment held at import and make the override order matter.\n */\n\n/** IPC socket for the PAI daemon. */\nexport function paiSocketPath(): string {\n return process.env.PAI_SOCKET_PATH ?? \"/tmp/pai.sock\";\n}\n\n/** IPC socket for the AIBroker daemon — read by PAI, owned by AIBroker. */\nexport function aibrokerSocketPath(): string {\n return process.env.PAI_AIBROKER_SOCKET_PATH ?? \"/tmp/aibroker.sock\";\n}\n\n/** Where the daemon's stdout and stderr are collected. */\nexport function daemonLogPath(): string {\n return process.env.PAI_DAEMON_LOG_PATH ?? \"/tmp/pai-daemon.log\";\n}\n\n/** Pidfile for the running daemon. */\nexport function daemonPidPath(): string {\n return process.env.PAI_DAEMON_PID_PATH ?? \"/tmp/pai-daemon.pid\";\n}\n\n/** Where the launchd scheduler tick writes its output. */\nexport function schedulerLogPath(): string {\n return process.env.PAI_SCHEDULER_LOG_PATH ?? \"/tmp/pai-scheduler.log\";\n}\n\n/**\n * Every runtime path, for the test guard to redirect in one place.\n *\n * Listed here rather than in the guard so that adding a path and forgetting to\n * protect it is not possible: the guard iterates this.\n */\nexport const RUNTIME_PATH_ENV_VARS = [\n \"PAI_SOCKET_PATH\",\n \"PAI_AIBROKER_SOCKET_PATH\",\n \"PAI_DAEMON_LOG_PATH\",\n \"PAI_DAEMON_PID_PATH\",\n \"PAI_SCHEDULER_LOG_PATH\",\n] as const;\n","/**\n * types.ts — Unified Notification Framework type definitions\n *\n * Defines the channel registry, event routing, and configuration schema\n * for PAI's notification subsystem.\n */\n\n// ---------------------------------------------------------------------------\n// Channel identifiers\n// ---------------------------------------------------------------------------\n\nexport type ChannelId = \"ntfy\" | \"whatsapp\" | \"macos\" | \"voice\" | \"cli\";\n\n// ---------------------------------------------------------------------------\n// Notification event types\n// ---------------------------------------------------------------------------\n\n/**\n * The semantic type of a notification event.\n * Used to route events to the appropriate channels.\n */\nexport type NotificationEvent =\n | \"error\"\n | \"progress\"\n | \"completion\"\n | \"info\"\n | \"debug\";\n\n// ---------------------------------------------------------------------------\n// Notification mode\n// ---------------------------------------------------------------------------\n\n/**\n * The current notification mode.\n *\n * - \"auto\" — Use the per-event routing table (default)\n * - \"voice\" — All events go to voice (WhatsApp TTS)\n * - \"whatsapp\" — All events go to WhatsApp text\n * - \"ntfy\" — All events go to ntfy.sh\n * - \"macos\" — All events go to macOS notifications\n * - \"cli\" — All events go to CLI stdout only\n * - \"off\" — Suppress all notifications\n */\nexport type NotificationMode =\n | \"auto\"\n | \"voice\"\n | \"whatsapp\"\n | \"ntfy\"\n | \"macos\"\n | \"cli\"\n | \"off\";\n\n// ---------------------------------------------------------------------------\n// Per-channel configuration\n// ---------------------------------------------------------------------------\n\nexport interface NtfyChannelConfig {\n enabled: boolean;\n /** ntfy.sh topic URL, e.g. \"https://ntfy.sh/my-topic\" */\n url?: string;\n /** ntfy priority: min | low | default | high | urgent */\n priority?: \"min\" | \"low\" | \"default\" | \"high\" | \"urgent\";\n}\n\nexport interface WhatsAppChannelConfig {\n enabled: boolean;\n /** Optional recipient (phone, JID, or contact name). Omit for self-chat. */\n recipient?: string;\n}\n\nexport interface MacOsChannelConfig {\n enabled: boolean;\n}\n\nexport interface VoiceChannelConfig {\n enabled: boolean;\n /** Kokoro voice name, e.g. \"bm_george\", \"af_bella\". Default: \"bm_george\" */\n voiceName?: string;\n}\n\nexport interface CliChannelConfig {\n enabled: boolean;\n}\n\nexport interface ChannelConfigs {\n ntfy: NtfyChannelConfig;\n whatsapp: WhatsAppChannelConfig;\n macos: MacOsChannelConfig;\n voice: VoiceChannelConfig;\n cli: CliChannelConfig;\n}\n\n// ---------------------------------------------------------------------------\n// Routing table\n// ---------------------------------------------------------------------------\n\n/**\n * Maps each event type to the ordered list of channels that should receive it.\n * Only channels that are enabled in `channels` and present in this list are used.\n */\nexport type RoutingTable = {\n [K in NotificationEvent]: ChannelId[];\n};\n\nexport const DEFAULT_ROUTING: RoutingTable = {\n error: [\"whatsapp\", \"macos\", \"ntfy\", \"cli\"],\n completion: [\"whatsapp\", \"macos\", \"ntfy\", \"cli\"],\n info: [\"cli\"],\n progress: [\"cli\"],\n debug: [],\n};\n\n// ---------------------------------------------------------------------------\n// Top-level notification config (embedded in PaiDaemonConfig)\n// ---------------------------------------------------------------------------\n\nexport interface NotificationConfig {\n /** Current routing mode. Default: \"auto\" */\n mode: NotificationMode;\n /** Per-channel configuration */\n channels: ChannelConfigs;\n /** Event → channel routing (used in \"auto\" mode) */\n routing: RoutingTable;\n}\n\nexport const DEFAULT_CHANNELS: ChannelConfigs = {\n ntfy: {\n enabled: false,\n url: undefined,\n priority: \"default\",\n },\n whatsapp: {\n enabled: true,\n recipient: undefined,\n },\n macos: {\n enabled: true,\n },\n voice: {\n enabled: false,\n voiceName: \"bm_george\",\n },\n cli: {\n enabled: true,\n },\n};\n\nexport const DEFAULT_NOTIFICATION_CONFIG: NotificationConfig = {\n mode: \"auto\",\n channels: DEFAULT_CHANNELS,\n routing: DEFAULT_ROUTING,\n};\n\n// ---------------------------------------------------------------------------\n// Notification payload\n// ---------------------------------------------------------------------------\n\nexport interface NotificationPayload {\n /** Semantic event type — used for routing */\n event: NotificationEvent;\n /** The notification message body */\n message: string;\n /** Optional title (used by macOS, ntfy) */\n title?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Provider interface\n// ---------------------------------------------------------------------------\n\nexport interface NotificationProvider {\n readonly channelId: ChannelId;\n /**\n * Send a notification.\n * Returns true on success, false on failure (failure is non-fatal).\n */\n send(payload: NotificationPayload, config: NotificationConfig): Promise<boolean>;\n}\n\n// ---------------------------------------------------------------------------\n// Send result\n// ---------------------------------------------------------------------------\n\nexport interface SendResult {\n channelsAttempted: ChannelId[];\n channelsSucceeded: ChannelId[];\n channelsFailed: ChannelId[];\n mode: NotificationMode;\n}\n","/**\n * types.ts — Task Bus type definitions\n *\n * Defines the provider registry, ownership resolution, and configuration schema\n * for PAI's cross-session task subsystem.\n *\n * The task bus routes work between PAI sessions through an external tracker.\n * A session files a task; a routine reads it later and dispatches it to the\n * session that owns it — spawning one if none is running.\n *\n * See Notes/docs/task-bus.md for the architecture and its constraints.\n */\n\n// ---------------------------------------------------------------------------\n// Provider identifiers\n// ---------------------------------------------------------------------------\n\nexport type ProviderId = \"todoist\";\n\n// ---------------------------------------------------------------------------\n// Ownership\n// ---------------------------------------------------------------------------\n\n/**\n * Prefix marking a tracker label as a PAI ownership assertion.\n * A task labelled `pai:acme-api` is owned by the `acme-api` project.\n */\nexport const OWNER_LABEL_PREFIX = \"pai:\";\n\n/**\n * How a task's owner was determined. Recorded so the routine can explain\n * itself, and so a mis-resolution is diagnosable rather than silent.\n *\n * - \"label\" — an explicit `pai:<project>` label (authoritative)\n * - \"container\" — the enclosing sub-project name matched a PAI alias (fallback)\n * - \"none\" — unresolved; the task stays in the findings inbox\n */\nexport type OwnerSource = \"label\" | \"container\" | \"none\";\n\nexport interface TaskOwner {\n /** Resolved PAI project short name, e.g. \"acme-api\". Null when UNROUTED. */\n project: string | null;\n /** Absolute path to the project root. Null when UNROUTED. */\n rootPath: string | null;\n source: OwnerSource;\n /**\n * The raw string that resolution was attempted against, kept for diagnostics\n * when `source` is \"none\" — e.g. a \"Reading List 📚\" container matches no\n * PAI project, which is expected rather than a fault.\n */\n rawHint?: string;\n}\n\n/** An unresolved owner. UNROUTED is a normal state, not an error. */\nexport const UNROUTED: TaskOwner = {\n project: null,\n rootPath: null,\n source: \"none\",\n};\n\n// ---------------------------------------------------------------------------\n// Tasks\n// ---------------------------------------------------------------------------\n\nexport type TaskPriority = \"p1\" | \"p2\" | \"p3\" | \"p4\";\n\nexport interface Task {\n /** Provider-native task ID. Opaque; never parsed. */\n id: string;\n title: string;\n /**\n * Full procedure AND reasoning — enough that the task is actionable months\n * later, or by the user alone, without re-deriving anything. Enforced at\n * filing time rather than left to discipline.\n */\n body: string;\n owner: TaskOwner;\n /** ISO 8601 date or datetime. Null when the task has no due date. */\n due: string | null;\n /**\n * The tracker's own recurrence text, e.g. \"every day at 08:00\". Null for a\n * one-off.\n *\n * Kept verbatim rather than parsed into a rule because it is also the only\n * way to write a recurrence back: Todoist re-parses this string, and it is\n * what lets a due date be restored without destroying the recurrence.\n */\n recurrence?: string | null;\n priority: TaskPriority;\n labels: string[];\n /**\n * Stable reference to the artifact this task is about. Prefer a `hook://`\n * URL over a filesystem path — it survives renames and moves, and opens in\n * DEVONthink To Go on iOS.\n */\n sourceUrl?: string;\n /** True for organizational headers that cannot be completed. */\n isHeader?: boolean;\n}\n\n/** A task being filed. `owner` is a project short name, resolved on write. */\nexport interface NewTask {\n title: string;\n body: string;\n owner?: string | null;\n due?: string;\n priority?: TaskPriority;\n labels?: string[];\n sourceUrl?: string;\n /**\n * Sub-project to file into, created if absent.\n *\n * The convention is one sub-project per PAI project under the bus root. It\n * previously existed only in the shape of the data, so every session had to\n * re-derive it — and a session that inferred cautiously filed flat instead,\n * which is exactly the pile the convention prevents.\n */\n into?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Provider interface\n// ---------------------------------------------------------------------------\n\nexport interface ListOptions {\n /** Only tasks due on or before this ISO date. Omit for all open tasks. */\n dueBefore?: string;\n /** Restrict to one resolved owner. Omit for every owner. */\n owner?: string;\n /** Include tasks that resolved to UNROUTED. Default: true. */\n includeUnrouted?: boolean;\n limit?: number;\n}\n\nexport interface TaskProvider {\n readonly providerId: ProviderId;\n\n /**\n * False when no credential is configured. The bus degrades to a no-op\n * rather than failing — a user without a tracker still gets working PAI.\n */\n isConfigured(): boolean;\n\n listOpen(opts: ListOptions): Promise<Task[]>;\n add(task: NewTask): Promise<Task>;\n complete(id: string): Promise<void>;\n\n /**\n * Rewrite a task's due date through the tracker's natural-language field.\n *\n * Optional, and deliberately expressed as a string rather than a date: a\n * recurring task's schedule and its next occurrence are the same field, so\n * moving the date without the rule silently downgrades a routine to a one-off.\n * A provider that cannot express both at once should not offer this.\n */\n setDue?(id: string, dueString: string): Promise<void>;\n\n /**\n * Sub-projects under the bus root — the set of addresses a task can be filed\n * against, one per session.\n *\n * Optional because it is not universal: a tracker may address work by tag or\n * list rather than by nested project, and forcing a nesting concept onto one\n * that has none would mean faking it. A provider without these simply does\n * not offer session-scoped inboxes, and callers say so rather than failing.\n */\n listSubProjects?(): Promise<Array<{ id: string; name: string }>>;\n findOrCreateSubProject?(name: string): Promise<{ id: string; created: boolean }>;\n\n /**\n * The comment thread on a task, oldest first.\n *\n * Optional because not every tracker has threaded comments. Where it exists,\n * the thread is usually where the reasoning lives — the question, the answer,\n * the correction — and completing the task takes it out of view. That is what\n * the archive exists to keep.\n */\n listComments?(taskId: string): Promise<Array<{ id: string; content: string; postedAt?: string }>>;\n\n /**\n * One task by id, whether open or completed.\n *\n * Needed because archiving runs at or after completion, and a completed task\n * is gone from `listOpen` — which is exactly the moment its discussion stops\n * being visible and most needs keeping.\n */\n getTask?(id: string): Promise<Task | null>;\n}\n\n// ---------------------------------------------------------------------------\n// Configuration\n// ---------------------------------------------------------------------------\n\nexport interface TodoistProviderConfig {\n enabled: boolean;\n /**\n * API token. Resolution order is apiKey → TODOIST_API_KEY env → unconfigured.\n *\n * Never read this from another tool's config file. PAI ships as a product;\n * scraping ~/.claude.json for a key belonging to the Todoist MCP is not\n * acceptable even though the key is sitting there.\n */\n apiKey?: string;\n /**\n * Tracker project ID that roots the bus (the \"Claude 🤖\" project).\n *\n * Stored as an ID, never a name. Todoist's project search silently returns\n * zero results for names containing emoji — resolving by name would report\n * \"no tasks\" instead of failing, which is the exact class of silent failure\n * this subsystem exists to surface.\n */\n rootProjectId?: string;\n /** Section ID for the findings inbox. Tasks land here when UNROUTED. */\n findingsSectionId?: string;\n}\n\nexport interface TaskConfig {\n /** Master switch. When false the bus is inert. */\n enabled: boolean;\n providers: {\n todoist: TodoistProviderConfig;\n };\n /**\n * Dispatch work to the owning session automatically, spawning one if absent.\n * Requires AIBroker. When false — or when AIBroker is unavailable — PAI\n * reports which project owns each task and leaves acting to the user.\n */\n autoDispatch: boolean;\n\n /**\n * Seconds AIBroker may spend on a single dispatch, spawn included.\n *\n * Passed down to the transport so both sides share one deadline. Raise it on\n * a loaded machine where sessions are slow to start accepting input.\n */\n dispatchTimeoutSecs?: number;\n\n /**\n * Project a task goes to when it carries the bare `pai` marker and its\n * location says nothing — an Inbox capture, typically.\n *\n * This is the one thing a task's location cannot express: \"an AI should take\n * this, and I do not know which one yet\". Everything else is answered by the\n * project the task sits in.\n *\n * Unset means such a task stays UNROUTED, which is a legitimate choice: it\n * then surfaces in the findings inbox for triage rather than being guessed at.\n */\n defaultOwner?: string;\n}\n\nexport const DEFAULT_TASK_CONFIG: TaskConfig = {\n enabled: false,\n providers: {\n todoist: {\n enabled: false,\n },\n },\n autoDispatch: false,\n};\n\n// ---------------------------------------------------------------------------\n// Dispatch results\n// ---------------------------------------------------------------------------\n\n/**\n * What happened to one task during a dispatch run.\n *\n * - \"delivered\" — sent to an already-running session, and confirmed submitted\n * - \"queued\" — typed into a live session that was mid-turn, so submission\n * could not be confirmed inside the window. This is delivery:\n * Claude Code holds typed input until the current turn ends.\n * Never retried — the text is already in the input box, so a\n * second attempt is a second copy, not a retry. One trigger\n * arrived three times on 2026-08-01 for exactly that reason.\n * - \"spawned\" — none running; one was launched, came up, and received it\n * - \"unrouted\" — no owner resolved; left in the findings inbox\n * - \"unlaunchable\" — an owner resolved but no PAI alias exists to launch it\n * - \"unreachable\" — a session was launched but never became ready to accept input\n * - \"skipped\" — autoDispatch is off, or no transport; reported only\n *\n * `unlaunchable` and `unreachable` are distinct because the fixes differ:\n * the first is a setup gap (register an alias), the second is a runtime\n * failure (find out why the session did not come up). Collapsing them would\n * send users looking in the wrong place.\n *\n * None of these are errors. A task that could not be delivered is a routing\n * result to report, not an exception to throw.\n */\nexport type DispatchOutcome =\n | \"delivered\"\n | \"queued\"\n | \"spawned\"\n | \"unrouted\"\n | \"unlaunchable\"\n | \"unreachable\"\n | \"skipped\";\n\nexport interface DispatchResult {\n task: Task;\n outcome: DispatchOutcome;\n /** Session the task reached, when it reached one. */\n session?: string;\n /** Why the task did not reach a session. Present on failure outcomes. */\n reason?: string;\n}\n","/**\n * config.ts — Configuration loader for PAI Daemon\n *\n * Loads config from ~/.config/pai/config.json (XDG convention).\n * Deep-merges with defaults so partial configs work fine.\n * Expands ~ in path values at runtime.\n */\n\nimport { existsSync, readFileSync, mkdirSync, writeFileSync } from \"node:fs\";\nimport { homedir, userInfo } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { NotificationConfig } from \"../notifications/types.js\";\nimport { DEFAULT_NOTIFICATION_CONFIG } from \"../notifications/types.js\";\nimport type { TaskConfig } from \"../tasks/types.js\";\nimport { DEFAULT_TASK_CONFIG } from \"../tasks/types.js\";\nimport { paiSocketPath } from \"../runtime-paths.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface SearchConfig {\n /** Default search mode: 'keyword', 'semantic', or 'hybrid'. Default: 'keyword'. */\n mode: \"keyword\" | \"semantic\" | \"hybrid\";\n /** Enable cross-encoder reranking by default. Default: true. */\n rerank: boolean;\n /** Recency boost half-life in days. 0 = off. Default: 90. */\n recencyBoostDays: number;\n /** Default max results. Default: 10. */\n defaultLimit: number;\n /** Default snippet length for MCP results. Default: 200. */\n snippetLength: number;\n}\n\nexport interface PostgresConfig {\n /** Connection string — if set, overrides individual host/port/etc. fields */\n connectionString?: string;\n /** Postgres host (default: \"localhost\") */\n host?: string;\n /** Postgres port (default: 5432) */\n port?: number;\n /** Postgres database name (default: \"pai\") */\n database?: string;\n /** Postgres user (default: \"pai\") */\n user?: string;\n /** Postgres password (default: \"pai\") */\n password?: string;\n /** Maximum pool connections (default: 5) */\n maxConnections?: number;\n /** Connection timeout in ms (default: 5000) */\n connectionTimeoutMs?: number;\n}\n\nexport interface PaiDaemonConfig {\n /** Unix Domain Socket path for IPC */\n socketPath: string;\n\n /** Index schedule interval in seconds (default: 300 = 5 minutes) */\n indexIntervalSecs: number;\n\n /** Embedding schedule interval in seconds (default: 600 = 10 minutes) */\n embedIntervalSecs: number;\n /** Run an embed pass 60s after daemon start. Off by default: with a large\n * backlog it makes every restart a CPU storm, and it ignores the interval. */\n embedOnStartup: boolean;\n\n /** Local hour (0-23) to anchor the recurring index/embed cycle to. When unset,\n * the cycle is anchored to daemon start, so a daytime restart pins every\n * later pass to daytime too — a 24h interval does not by itself mean \"at\n * night\". Set this to run maintenance in a fixed window regardless of when\n * the machine last booted. */\n maintenanceHour?: number;\n\n /** Storage backend: \"sqlite\" (default) or \"postgres\" */\n storageBackend: \"sqlite\" | \"postgres\";\n\n /** PostgreSQL connection config (used when storageBackend = \"postgres\") */\n postgres?: PostgresConfig;\n\n /** Embedding model name (used for semantic/hybrid search) */\n embeddingModel: string;\n\n /** Log level */\n logLevel: \"debug\" | \"info\" | \"warn\" | \"error\";\n\n /** Obsidian vault root path for zettelkasten indexing. If set, vault indexing runs alongside project indexing. */\n vaultPath?: string;\n\n /** Registry project_id to use for vault chunks in memory_chunks. Default: auto-detected. */\n vaultProjectId?: number;\n\n /** Notification subsystem configuration */\n notifications: NotificationConfig;\n\n /** Search defaults — applied when MCP tool or CLI doesn't specify a value */\n search: SearchConfig;\n\n /** Task bus — optional external tracker for cross-session work */\n tasks: TaskConfig;\n\n /** Who \"me\" is — addresses that count as the user's own. */\n identity: IdentityConfig;\n}\n\n/**\n * The user's own identity, for anything that delivers back to them.\n *\n * This exists so \"my own address\" is a fact the system can check rather than\n * something a model infers from context. An assistant deciding on the spot\n * whether an address looks like the user's is exactly the judgement that should\n * not be re-made per message.\n *\n * Empty by default and never guessed at install time: an empty `selfEmails`\n * means nothing is self-addressed, so anything reading this fails closed.\n */\nexport interface IdentityConfig {\n /**\n * Where digests and \"mail me X\" requests are delivered.\n *\n * Must be a mailbox separate from the account doing the sending. Gmail files\n * a message sent from an account to itself — or to one of its own domain\n * aliases — under Sent only, and it never reaches the inbox. The send reports\n * success, so this fails silently and looks exactly like delivery. Observed\n * 2026-08-01: owner@example.ch → owner@example.de, sent fine, invisible.\n *\n * Where a separate mailbox is not available, deliver by writing the message\n * and adding the INBOX label to it rather than relying on the send path.\n */\n deliverTo?: string;\n\n /**\n * Every address that counts as the user's own.\n *\n * Used as an allowlist by anything that may act without review — outbound\n * mail being the case that motivated it. Membership is the whole test: an\n * address that is not listed is not the user's, however similar it looks.\n * Plus-aliases and domain aliases must be listed explicitly rather than\n * pattern-matched, because the patterns that would match them also match\n * addresses belonging to other people.\n */\n selfEmails: string[];\n\n /** The account used to send on the user's behalf, when one is configured. */\n sendingAccount?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Per-user Postgres isolation\n// ---------------------------------------------------------------------------\n\n/** Derive a per-user Postgres database name: pai_<username> */\nfunction perUserDbName(): string {\n const username = userInfo().username;\n // Sanitize: only allow alphanumeric and underscore for Postgres identifiers\n const safe = username.replace(/[^a-zA-Z0-9_]/g, \"_\").toLowerCase();\n return `pai_${safe}`;\n}\n\n/** Derive the per-user connection string */\nfunction perUserConnectionString(): string {\n const db = perUserDbName();\n return `postgresql://pai:pai@localhost:5432/${db}`;\n}\n\n// ---------------------------------------------------------------------------\n// Defaults\n// ---------------------------------------------------------------------------\n\nexport const DEFAULTS: PaiDaemonConfig = {\n socketPath: paiSocketPath(),\n indexIntervalSecs: 300,\n embedIntervalSecs: 600,\n embedOnStartup: false,\n storageBackend: \"sqlite\",\n postgres: {\n connectionString: perUserConnectionString(),\n maxConnections: 5,\n connectionTimeoutMs: 5000,\n },\n embeddingModel: \"Snowflake/snowflake-arctic-embed-m-v1.5\",\n logLevel: \"info\",\n notifications: DEFAULT_NOTIFICATION_CONFIG,\n tasks: DEFAULT_TASK_CONFIG,\n // Deliberately empty. An install must not guess who the user is: a wrong\n // guess here is an address that can be mailed without review.\n identity: { selfEmails: [] },\n search: {\n mode: \"keyword\",\n rerank: true,\n recencyBoostDays: 90,\n defaultLimit: 10,\n snippetLength: 200,\n },\n};\n\n/** Config template — generated at runtime so the DB name is per-user */\nfunction configTemplate(): string {\n return `{\n \"socketPath\": \"/tmp/pai.sock\",\n \"indexIntervalSecs\": 300,\n \"embedIntervalSecs\": 600,\n \"storageBackend\": \"sqlite\",\n \"postgres\": {\n \"connectionString\": \"${perUserConnectionString()}\",\n \"maxConnections\": 5,\n \"connectionTimeoutMs\": 5000\n },\n \"embeddingModel\": \"Snowflake/snowflake-arctic-embed-m-v1.5\",\n \"logLevel\": \"info\",\n \"vaultPath\": \"\",\n \"vaultProjectId\": 0,\n \"search\": {\n \"mode\": \"keyword\",\n \"rerank\": true,\n \"recencyBoostDays\": 90,\n \"defaultLimit\": 10,\n \"snippetLength\": 200\n }\n}\n`;\n}\n\n// ---------------------------------------------------------------------------\n// Path helpers\n// ---------------------------------------------------------------------------\n\n/** Expand a leading ~ to the real home directory */\nexport function expandHome(p: string): string {\n if (p === \"~\" || p.startsWith(\"~/\") || p.startsWith(\"~\\\\\")) {\n return join(homedir(), p.slice(1));\n }\n return p;\n}\n\nexport const CONFIG_DIR = join(homedir(), \".config\", \"pai\");\nexport const CONFIG_FILE = join(CONFIG_DIR, \"config.json\");\n\n// ---------------------------------------------------------------------------\n// Deep merge (handles nested objects, not arrays)\n// ---------------------------------------------------------------------------\n\nfunction deepMerge<T extends object>(\n target: T,\n source: Record<string, unknown>\n): T {\n const result = { ...target };\n for (const key of Object.keys(source)) {\n const srcVal = source[key];\n if (srcVal === undefined || srcVal === null) continue;\n const tgtVal = (target as Record<string, unknown>)[key];\n if (\n typeof srcVal === \"object\" &&\n !Array.isArray(srcVal) &&\n typeof tgtVal === \"object\" &&\n tgtVal !== null &&\n !Array.isArray(tgtVal)\n ) {\n (result as Record<string, unknown>)[key] = deepMerge(\n tgtVal as object,\n srcVal as Record<string, unknown>\n );\n } else {\n (result as Record<string, unknown>)[key] = srcVal;\n }\n }\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Config loader\n// ---------------------------------------------------------------------------\n\n/**\n * Load configuration from ~/.config/pai/config.json.\n * Returns defaults merged with any values found in the file.\n */\nexport function loadConfig(): PaiDaemonConfig {\n if (!existsSync(CONFIG_FILE)) {\n return { ...DEFAULTS };\n }\n\n let raw: string;\n try {\n raw = readFileSync(CONFIG_FILE, \"utf-8\");\n } catch (e) {\n process.stderr.write(\n `[pai-daemon] Could not read config file at ${CONFIG_FILE}: ${e}\\n`\n );\n return { ...DEFAULTS };\n }\n\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(raw) as Record<string, unknown>;\n } catch (e) {\n process.stderr.write(\n `[pai-daemon] Config file is not valid JSON: ${e}\\n`\n );\n return { ...DEFAULTS };\n }\n\n // Compat: config.json may use \"obsidianVaultPath\" (legacy key) instead of \"vaultPath\".\n // Map it across so the daemon picks it up correctly.\n if (parsed.obsidianVaultPath && !parsed.vaultPath) {\n parsed.vaultPath = parsed.obsidianVaultPath;\n process.stderr.write(\n `[pai-daemon] Config: mapped obsidianVaultPath → vaultPath (${parsed.vaultPath})\\n`\n );\n }\n\n return deepMerge(DEFAULTS, parsed);\n}\n\n/**\n * Ensure ~/.config/pai/ exists and write a default config.json template\n * if none exists yet. Call this only from the `serve` command.\n */\nexport function ensureConfigDir(): void {\n if (!existsSync(CONFIG_DIR)) {\n mkdirSync(CONFIG_DIR, { recursive: true });\n process.stderr.write(\n `[pai-daemon] Created config directory: ${CONFIG_DIR}\\n`\n );\n }\n\n if (!existsSync(CONFIG_FILE)) {\n try {\n writeFileSync(CONFIG_FILE, configTemplate(), \"utf-8\");\n process.stderr.write(\n `[pai-daemon] Wrote default config to: ${CONFIG_FILE}\\n`\n );\n } catch (e) {\n process.stderr.write(\n `[pai-daemon] Could not write default config: ${e}\\n`\n );\n }\n }\n}\n"],"mappings":"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"}
|