@tekmidian/pai 0.42.5 → 0.43.0
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/ARCHITECTURE.md +9 -9
- package/README.md +26 -8
- package/dist/{aibroker-client-BP74gGwD.mjs → aibroker-client-Bn9SnGMa.mjs} +3 -3
- package/dist/{aibroker-client-BP74gGwD.mjs.map → aibroker-client-Bn9SnGMa.mjs.map} +1 -1
- package/dist/{aibroker-client-C6m71p6O.mjs → aibroker-client-D5NT0S1N.mjs} +1 -1
- package/dist/{auto-route-_5zzJz5q.mjs → auto-route-CcD0OWWB.mjs} +4 -4
- package/dist/{auto-route-_5zzJz5q.mjs.map → auto-route-CcD0OWWB.mjs.map} +1 -1
- package/dist/{chain-DTII7xeL.mjs → chain-CEC1a5mX.mjs} +5 -4
- package/dist/{chain-DTII7xeL.mjs.map → chain-CEC1a5mX.mjs.map} +1 -1
- package/dist/cli/index.mjs +23 -21
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/program.d.mts.map +1 -1
- package/dist/cli/program.mjs +23 -21
- package/dist/{clusters-j0ZuNYp9.mjs → clusters-Cw_P6lO-.mjs} +2 -2
- package/dist/{clusters-j0ZuNYp9.mjs.map → clusters-Cw_P6lO-.mjs.map} +1 -1
- package/dist/{config-B64vFg14.mjs → config-DCu8THKi.mjs} +41 -9
- package/dist/{config-B64vFg14.mjs.map → config-DCu8THKi.mjs.map} +1 -1
- package/dist/config-DeqFqa1N.mjs +3 -0
- package/dist/config-DxMZjpvY.mjs +1289 -0
- package/dist/config-DxMZjpvY.mjs.map +1 -0
- package/dist/context-handover-cache-B9NKSjwR.mjs +3242 -0
- package/dist/context-handover-cache-B9NKSjwR.mjs.map +1 -0
- package/dist/daemon/index.mjs +23 -22
- package/dist/daemon/index.mjs.map +1 -1
- package/dist/{daemon-B8N1egl0.mjs → daemon-BhldpFvO.mjs} +52 -38
- package/dist/daemon-BhldpFvO.mjs.map +1 -0
- package/dist/daemon-QIvCXB5f.mjs +24 -0
- package/dist/daemon-mcp/index.mjs +144 -117
- package/dist/daemon-mcp/index.mjs.map +1 -1
- package/dist/db-BOBfewFH.mjs +3 -0
- package/dist/{db-a1ixZQjr.mjs → db-DJ8rk0VM.mjs} +15 -6
- package/dist/{db-a1ixZQjr.mjs.map → db-DJ8rk0VM.mjs.map} +1 -1
- package/dist/db-DbY2O8QO.mjs +3 -0
- package/dist/{db-Ca5qfsMC.mjs → db-Ddns80yf.mjs} +15 -6
- package/dist/{db-Ca5qfsMC.mjs.map → db-Ddns80yf.mjs.map} +1 -1
- package/dist/{detect-CdaA48EI.mjs → detect-Bf2z-oKB.mjs} +1 -1
- package/dist/{detect-CdaA48EI.mjs.map → detect-Bf2z-oKB.mjs.map} +1 -1
- package/dist/detector-CkwkXuGh.mjs +5 -0
- package/dist/{detector-C73Nxm94.mjs → detector-DWXkeTlk.mjs} +2 -2
- package/dist/{detector-C73Nxm94.mjs.map → detector-DWXkeTlk.mjs.map} +1 -1
- package/dist/{embeddings-CEBGrzwu.mjs → embeddings-ClqKtLZ8.mjs} +1 -1
- package/dist/{embeddings-DOLZnT1X.mjs → embeddings-DvRKcQyt.mjs} +1 -1
- package/dist/{embeddings-DOLZnT1X.mjs.map → embeddings-DvRKcQyt.mjs.map} +1 -1
- package/dist/factory-47Qf_df7.mjs +3 -0
- package/dist/{factory-DEb9NmHJ.mjs → factory-DNYAqjH0.mjs} +8 -8
- package/dist/{factory-DEb9NmHJ.mjs.map → factory-DNYAqjH0.mjs.map} +1 -1
- package/dist/{fallback-oJl3BaIN.mjs → fallback-BQJ8PbCK.mjs} +185 -123
- package/dist/fallback-BQJ8PbCK.mjs.map +1 -0
- package/dist/{helpers-IjZkXBhj.mjs → helpers-crDEr6S2.mjs} +1 -1
- package/dist/{helpers-IjZkXBhj.mjs.map → helpers-crDEr6S2.mjs.map} +1 -1
- package/dist/hooks/capture-all-events.mjs +117 -34
- package/dist/hooks/capture-all-events.mjs.map +4 -4
- package/dist/hooks/capture-session-summary.mjs +110 -33
- package/dist/hooks/capture-session-summary.mjs.map +4 -4
- package/dist/hooks/capture-tool-output.mjs +105 -28
- package/dist/hooks/capture-tool-output.mjs.map +4 -4
- package/dist/hooks/cleanup-session-files.mjs +54 -13
- package/dist/hooks/cleanup-session-files.mjs.map +2 -2
- package/dist/hooks/context-compression-hook.mjs +7565 -122
- package/dist/hooks/context-compression-hook.mjs.map +4 -4
- package/dist/hooks/initialize-session.mjs +55 -14
- package/dist/hooks/initialize-session.mjs.map +2 -2
- package/dist/hooks/inject-observations.mjs +79 -18
- package/dist/hooks/inject-observations.mjs.map +3 -3
- package/dist/hooks/load-core-context.mjs +55 -14
- package/dist/hooks/load-core-context.mjs.map +2 -2
- package/dist/hooks/load-project-context.mjs +207 -122
- package/dist/hooks/load-project-context.mjs.map +4 -4
- package/dist/hooks/mcp-deferred-gate.mjs +54 -13
- package/dist/hooks/mcp-deferred-gate.mjs.map +2 -2
- package/dist/hooks/mcp-deferred-reminder.mjs +9 -0
- package/dist/hooks/mcp-deferred-reminder.mjs.map +3 -3
- package/dist/hooks/observe.mjs +54 -13
- package/dist/hooks/observe.mjs.map +2 -2
- package/dist/hooks/post-compact-inject.mjs +7419 -16
- package/dist/hooks/post-compact-inject.mjs.map +4 -4
- package/dist/hooks/route-agents-to-worker.mjs +7778 -171
- package/dist/hooks/route-agents-to-worker.mjs.map +4 -4
- package/dist/hooks/route-edits-to-worker.mjs +90 -0
- package/dist/hooks/route-edits-to-worker.mjs.map +7 -0
- package/dist/hooks/security-validator.mjs +147 -6
- package/dist/hooks/security-validator.mjs.map +4 -4
- package/dist/hooks/session-commands.mjs +9 -0
- package/dist/hooks/session-commands.mjs.map +3 -3
- package/dist/hooks/stop-hook.mjs +182 -105
- package/dist/hooks/stop-hook.mjs.map +4 -4
- package/dist/hooks/subagent-stop-hook.mjs +3 -0
- package/dist/hooks/subagent-stop-hook.mjs.map +1 -1
- package/dist/hooks/sync-todo-to-md.mjs +54 -13
- package/dist/hooks/sync-todo-to-md.mjs.map +2 -2
- package/dist/hooks/update-tab-on-action.mjs +3 -0
- package/dist/hooks/update-tab-on-action.mjs.map +1 -1
- package/dist/hooks/update-tab-titles.mjs +3 -0
- package/dist/hooks/update-tab-titles.mjs.map +1 -1
- package/dist/hooks/whisper-reinject.mjs +11 -0
- package/dist/hooks/whisper-reinject.mjs.map +3 -3
- package/dist/hooks/whisper-rules.mjs +134 -29
- package/dist/hooks/whisper-rules.mjs.map +4 -4
- package/dist/hooks/worker-proxy.mjs +7737 -117
- package/dist/hooks/worker-proxy.mjs.map +4 -4
- package/dist/hooks/worker-status-line.mjs +7883 -169
- package/dist/hooks/worker-status-line.mjs.map +4 -4
- package/dist/hooks/worker-supervision.mjs +7721 -115
- package/dist/hooks/worker-supervision.mjs.map +4 -4
- package/dist/index.d.mts +4 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +10 -10
- package/dist/{indexer-backend-BPTzKFZU.mjs → indexer-backend-CdvCJCkD.mjs} +3 -3
- package/dist/{indexer-backend-BPTzKFZU.mjs.map → indexer-backend-CdvCJCkD.mjs.map} +1 -1
- package/dist/{ipc-client-BmypMNYk.mjs → ipc-client-C6NryMMb.mjs} +2 -2
- package/dist/{ipc-client-BmypMNYk.mjs.map → ipc-client-C6NryMMb.mjs.map} +1 -1
- package/dist/{kg-entity-r8duqhi9.mjs → kg-entity-BSjM5xZh.mjs} +1 -1
- package/dist/{kg-entity-r8duqhi9.mjs.map → kg-entity-BSjM5xZh.mjs.map} +1 -1
- package/dist/{latent-ideas-CtSca4qG.mjs → latent-ideas-BfL4nCQ3.mjs} +4 -4
- package/dist/{latent-ideas-CtSca4qG.mjs.map → latent-ideas-BfL4nCQ3.mjs.map} +1 -1
- package/dist/{link-boost-Bucnz3n8.mjs → link-boost-C5PaNdzm.mjs} +1 -1
- package/dist/{link-boost-Bucnz3n8.mjs.map → link-boost-C5PaNdzm.mjs.map} +1 -1
- package/dist/main-resolver-CnfnWa8C.mjs +7 -0
- package/dist/{main-resolver-DnkxPYkr.mjs → main-resolver-DvYu9jbl.mjs} +21 -11
- package/dist/main-resolver-DvYu9jbl.mjs.map +1 -0
- package/dist/{migrate-Cjzeefn9.mjs → migrate-CfERwXhY.mjs} +2 -2
- package/dist/{migrate-Cjzeefn9.mjs.map → migrate-CfERwXhY.mjs.map} +1 -1
- package/dist/{neighborhood-CM7Jgb51.mjs → neighborhood-CETR_P4y.mjs} +2 -2
- package/dist/{neighborhood-CM7Jgb51.mjs.map → neighborhood-CETR_P4y.mjs.map} +1 -1
- package/dist/{note-context-DCwE2A-M.mjs → note-context-DCcRWBoN.mjs} +1 -1
- package/dist/{note-context-DCwE2A-M.mjs.map → note-context-DCcRWBoN.mjs.map} +1 -1
- package/dist/pai-home-ChAOupZc.mjs +164 -0
- package/dist/pai-home-ChAOupZc.mjs.map +1 -0
- package/dist/{pai-marker-CHtbJMwJ.mjs → pai-marker-B20KqhA8.mjs} +1 -1
- package/dist/{pai-marker-CHtbJMwJ.mjs.map → pai-marker-B20KqhA8.mjs.map} +1 -1
- package/dist/{planner-D5Ui7DAm.mjs → planner-BlKGgpWI.mjs} +7 -6
- package/dist/{planner-D5Ui7DAm.mjs.map → planner-BlKGgpWI.mjs.map} +1 -1
- package/dist/{postgres-CtOzfoQU.mjs → postgres-CUSXWgTG.mjs} +2 -2
- package/dist/{postgres-CtOzfoQU.mjs.map → postgres-CUSXWgTG.mjs.map} +1 -1
- package/dist/{program-zpoYnJGz.mjs → program-C5jRQ1kg.mjs} +8880 -5913
- package/dist/program-C5jRQ1kg.mjs.map +1 -0
- package/dist/query-feedback-C7rSfiXU.mjs +3 -0
- package/dist/{query-feedback-Dqz-HC__.mjs → query-feedback-CYyOGi3y.mjs} +20 -6
- package/dist/query-feedback-CYyOGi3y.mjs.map +1 -0
- package/dist/{reranker-CwTCNsgA.mjs → reranker-B5qUTamW.mjs} +1 -1
- package/dist/{reranker-xPm04PXx.mjs → reranker-HHmhZk6v.mjs} +1 -1
- package/dist/{reranker-xPm04PXx.mjs.map → reranker-HHmhZk6v.mjs.map} +1 -1
- package/dist/router-CremHKTI.mjs +3 -0
- package/dist/{router-DD6D4j31.mjs → router-DBAZfE7X.mjs} +2 -2
- package/dist/{router-DD6D4j31.mjs.map → router-DBAZfE7X.mjs.map} +1 -1
- package/dist/{run-D7K5UALn.mjs → run-ByBKowN5.mjs} +980 -247
- package/dist/run-ByBKowN5.mjs.map +1 -0
- package/dist/{runtime-paths-rni52zHX.mjs → runtime-paths-B0P1TvUr.mjs} +1 -1
- package/dist/{runtime-paths-rni52zHX.mjs.map → runtime-paths-B0P1TvUr.mjs.map} +1 -1
- package/dist/{search-Rpk1cSBC.mjs → search-B69cQwDB.mjs} +3 -3
- package/dist/{search-Rpk1cSBC.mjs.map → search-B69cQwDB.mjs.map} +1 -1
- package/dist/{search-CfPpJAWQ.mjs → search-fb-5ovj4.mjs} +2 -2
- package/dist/{server-D4vAieF4.mjs → server-Ch99xt5K.mjs} +15 -14
- package/dist/server-Ch99xt5K.mjs.map +1 -0
- package/dist/skills/Advisor/SKILL.md +3 -3
- package/dist/skills/Providers/SKILL.md +30 -0
- package/dist/skills/Reconstruct/SKILL.md +1 -1
- package/dist/skills/VaultContext/SKILL.md +1 -1
- package/dist/skills/Whisper/SKILL.md +3 -3
- package/dist/skills/Worker/SKILL.md +1 -1
- package/dist/{sources-CS43CpuS.mjs → sources-KBGEA_E6.mjs} +2 -2
- package/dist/{sources-CS43CpuS.mjs.map → sources-KBGEA_E6.mjs.map} +1 -1
- package/dist/{sqlite-Cq3gJcq9.mjs → sqlite-By6r2YhZ.mjs} +3 -3
- package/dist/{sqlite-Cq3gJcq9.mjs.map → sqlite-By6r2YhZ.mjs.map} +1 -1
- package/dist/{state-CY6I-Hnc.mjs → state-DnYGRmS7.mjs} +1 -1
- package/dist/{state-BRdB-Bf1.mjs → state-PJrgfDLW.mjs} +1 -1
- package/dist/{state-BRdB-Bf1.mjs.map → state-PJrgfDLW.mjs.map} +1 -1
- package/dist/{stop-words-Hfu8u22w.mjs → stop-words-BaMEGVeY.mjs} +1 -1
- package/dist/{stop-words-Hfu8u22w.mjs.map → stop-words-BaMEGVeY.mjs.map} +1 -1
- package/dist/{sync-BWbe8JTg.mjs → sync-Btcb6xA4.mjs} +3 -3
- package/dist/{sync-BWbe8JTg.mjs.map → sync-Btcb6xA4.mjs.map} +1 -1
- package/dist/{themes-B7h9YDiy.mjs → themes-BVoAnLZj.mjs} +3 -3
- package/dist/{themes-B7h9YDiy.mjs.map → themes-BVoAnLZj.mjs.map} +1 -1
- package/dist/{tools-BW3NLMPk.mjs → tools-CAG2sUjf.mjs} +4 -3
- package/dist/{tools-LlzcV_eK.mjs → tools-DiRC5glA.mjs} +30 -233
- package/dist/tools-DiRC5glA.mjs.map +1 -0
- package/dist/{trace-u4hT-pLD.mjs → trace-Cy8quQTm.mjs} +1 -1
- package/dist/{trace-u4hT-pLD.mjs.map → trace-Cy8quQTm.mjs.map} +1 -1
- package/dist/{utils-DhMex3Ox.mjs → utils-BD8g8oSf.mjs} +1 -1
- package/dist/{utils-9Err2RBW.mjs → utils-CJymHkYm.mjs} +1 -1
- package/dist/{utils-9Err2RBW.mjs.map → utils-CJymHkYm.mjs.map} +1 -1
- package/dist/{vault-indexer-mata9S7b.mjs → vault-indexer-C5i-OAyr.mjs} +2 -2
- package/dist/{vault-indexer-mata9S7b.mjs.map → vault-indexer-C5i-OAyr.mjs.map} +1 -1
- package/dist/wakeup-BfElduEU.mjs +231 -0
- package/dist/wakeup-BfElduEU.mjs.map +1 -0
- package/dist/work-queue-worker-BA5IDBwQ.mjs +567 -0
- package/dist/work-queue-worker-BA5IDBwQ.mjs.map +1 -0
- package/dist/work-queue-worker-DLM2wlqM.mjs +14 -0
- package/dist/{zettelkasten-F0D_XfQ4.mjs → zettelkasten-BBjA7GcP.mjs} +4 -4
- package/dist/{zettelkasten-F0D_XfQ4.mjs.map → zettelkasten-BBjA7GcP.mjs.map} +1 -1
- package/docs/commands/README.md +32 -3
- package/docs/commands/audit.md +113 -0
- package/docs/commands/backup.md +2 -2
- package/docs/commands/clear-names.md +1 -1
- package/docs/commands/config.md +59 -0
- package/docs/commands/daemon.md +1 -1
- package/docs/commands/db.md +1 -1
- package/docs/commands/end.md +1 -1
- package/docs/commands/help.md +1 -1
- package/docs/commands/identity.md +1 -1
- package/docs/commands/kg.md +1 -1
- package/docs/commands/launch.md +45 -0
- package/docs/commands/mcp.md +1 -1
- package/docs/commands/memory.md +3 -3
- package/docs/commands/notify.md +1 -1
- package/docs/commands/observation.md +1 -1
- package/docs/commands/obsidian.md +1 -1
- package/docs/commands/pause.md +1 -1
- package/docs/commands/project.md +46 -1
- package/docs/commands/projects.md +46 -1
- package/docs/commands/registry.md +1 -1
- package/docs/commands/restore.md +1 -1
- package/docs/commands/session.md +1 -1
- package/docs/commands/sessions.md +1 -1
- package/docs/commands/setup.md +1 -1
- package/docs/commands/shell-init.md +1 -1
- package/docs/commands/skill.md +1 -1
- package/docs/commands/task.md +1 -1
- package/docs/commands/topic.md +1 -1
- package/docs/commands/update.md +1 -1
- package/docs/commands/worker.md +97 -6
- package/docs/commands/zettel.md +1 -1
- package/docs/provider-abstraction.md +1 -1
- package/docs/provider-independence-details.md +8 -5
- package/docs/task-bus.md +1 -1
- package/docs/worker.md +136 -83
- package/docs/workers-config.md +446 -0
- package/package.json +4 -2
- package/scripts/build-hooks.mjs +58 -3
- package/src/hooks/pre-compact.sh +5 -2
- package/src/hooks/session-stop.sh +29 -4
- package/src/hooks/ts/capture-all-events.ts +3 -3
- package/src/hooks/ts/lib/advisor-budget.test.ts +98 -0
- package/src/hooks/ts/lib/advisor-budget.ts +90 -0
- package/src/hooks/ts/lib/agent-gate.test.ts +101 -0
- package/src/hooks/ts/lib/agent-gate.ts +112 -0
- package/src/hooks/ts/lib/context-fill.ts +2 -2
- package/src/hooks/ts/lib/edit-gate.test.ts +52 -0
- package/src/hooks/ts/lib/edit-gate.ts +51 -0
- package/src/hooks/ts/lib/mcp-deferred-reminder.test.ts +8 -0
- package/src/hooks/ts/lib/pai-paths.test.ts +93 -0
- package/src/hooks/ts/lib/pai-paths.ts +168 -24
- package/src/hooks/ts/lib/project-utils/index.ts +1 -0
- package/src/hooks/ts/lib/project-utils/paths.ts +5 -4
- package/src/hooks/ts/lib/project-utils.ts +1 -0
- package/src/hooks/ts/post-tool-use/capture-tool-output.ts +2 -2
- package/src/hooks/ts/post-tool-use/whisper-reinject.test.ts +55 -0
- package/src/hooks/ts/post-tool-use/whisper-reinject.ts +3 -0
- package/src/hooks/ts/pre-compact/context-compression-hook.ts +1 -1
- package/src/hooks/ts/pre-tool-use/route-agents-to-worker.ts +46 -99
- package/src/hooks/ts/pre-tool-use/route-edits-to-worker.test.ts +47 -0
- package/src/hooks/ts/pre-tool-use/route-edits-to-worker.ts +96 -0
- package/src/hooks/ts/pre-tool-use/security-validator.ts +2 -1
- package/src/hooks/ts/session-end/capture-session-summary.ts +4 -4
- package/src/hooks/ts/session-start/initialize-session.ts +3 -3
- package/src/hooks/ts/session-start/inject-observations.test.ts +56 -0
- package/src/hooks/ts/session-start/inject-observations.ts +28 -8
- package/src/hooks/ts/session-start/load-core-context.ts +4 -4
- package/src/hooks/ts/session-start/load-project-context.ts +2 -2
- package/src/hooks/ts/session-start/mcp-deferred-reminder.ts +2 -0
- package/src/hooks/ts/session-start/session-commands.ts +4 -0
- package/src/hooks/ts/session-start/session-start-worker-guard.test.ts +36 -0
- package/src/hooks/ts/stop/stop-hook.ts +6 -11
- package/src/hooks/ts/user-prompt/whisper-rules.test.ts +49 -0
- package/src/hooks/ts/user-prompt/whisper-rules.ts +88 -47
- package/src/hooks/ztk-auto.sh +28 -0
- package/src/hooks/ztk-auto.test.ts +72 -0
- package/src/hooks/ztk-passthrough.mjs +71 -0
- package/statusline-command.sh +277 -116
- package/templates/agent-prefs.example.md +8 -8
- package/templates/claude-md.template.md +1 -1
- package/templates/voices.example.json +3 -3
- package/dist/config-C_ErGddD.mjs +0 -3
- package/dist/config-_lQI_W08.mjs +0 -640
- package/dist/config-_lQI_W08.mjs.map +0 -1
- package/dist/context-handover-cache-pNFdBNrU.mjs +0 -1548
- package/dist/context-handover-cache-pNFdBNrU.mjs.map +0 -1
- package/dist/daemon-B8N1egl0.mjs.map +0 -1
- package/dist/daemon-DHfyoTlk.mjs +0 -23
- package/dist/db-O-cyAPfS.mjs +0 -3
- package/dist/db-XEwJbuGO.mjs +0 -3
- package/dist/detector-C-7KRi42.mjs +0 -5
- package/dist/factory-Dbo8yNPi.mjs +0 -3
- package/dist/fallback-oJl3BaIN.mjs.map +0 -1
- package/dist/main-resolver-BAfOMppW.mjs +0 -7
- package/dist/main-resolver-DnkxPYkr.mjs.map +0 -1
- package/dist/program-zpoYnJGz.mjs.map +0 -1
- package/dist/query-feedback-DGoVRaWD.mjs +0 -3
- package/dist/query-feedback-Dqz-HC__.mjs.map +0 -1
- package/dist/router-BTIGZR2_.mjs +0 -3
- package/dist/run-D7K5UALn.mjs.map +0 -1
- package/dist/server-D4vAieF4.mjs.map +0 -1
- package/dist/tools-LlzcV_eK.mjs.map +0 -1
- package/dist/work-queue-worker-AEmfVE0F.mjs +0 -14
- package/dist/work-queue-worker-DIT-VLSh.mjs +0 -2136
- package/dist/work-queue-worker-DIT-VLSh.mjs.map +0 -1
- /package/dist/{indexer-D7MvSQPY.mjs → indexer-C5Le7XE7.mjs} +0 -0
|
@@ -1,12 +1,163 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { _ as
|
|
1
|
+
import { n as openRegistry, r as registryDbPath } from "./db-Ddns80yf.mjs";
|
|
2
|
+
import { _ as providerKeyPath, b as resolveProviderKey, c as classModelCapability, f as isModelCapability, o as WorkersConfigError, s as assertProviderRunnable, v as readWorkersSection, y as resolveModelCapability } from "./config-DxMZjpvY.mjs";
|
|
3
|
+
import { _ as workersLogDir, d as setCooldown, f as ensureNoMcpConfig, g as statusPath, h as panesDir, i as buildRunEnv, m as ledgerPath, n as ensureProxyRunning, o as isQuotaFailure, p as eventsPath, s as nextAutoProvider, t as DEFAULT_PROXY_PORT, u as resolveTarget } from "./server-Ch99xt5K.mjs";
|
|
3
4
|
import { appendFileSync, closeSync, existsSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
|
|
4
5
|
import { homedir, tmpdir } from "node:os";
|
|
5
6
|
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
6
|
-
import { connect, createServer } from "node:net";
|
|
7
7
|
import { execFileSync, spawn } from "node:child_process";
|
|
8
|
+
import { connect, createServer } from "node:net";
|
|
8
9
|
import { createInterface } from "node:readline";
|
|
9
10
|
|
|
11
|
+
//#region src/workers/mcp.ts
|
|
12
|
+
/**
|
|
13
|
+
* mcp.ts — the MCP allowlist for headless workers.
|
|
14
|
+
*
|
|
15
|
+
* Headless workers start with NO MCP servers: every server definition is a
|
|
16
|
+
* prompt-time tool inventory the model pays to know, and a worker that only
|
|
17
|
+
* reads and edits files needs none of it. A run may opt in with `--mcp
|
|
18
|
+
* name[,name…]`, a role carrying `"mcp": [...]`, or implicitly by naming
|
|
19
|
+
* `mcp__server__tool` in --allowedTools (a grant without its server loaded is
|
|
20
|
+
* a dead letter). Names may be single servers from ~/.claude.json's
|
|
21
|
+
* `mcpServers` or `workers.mcpSets` set names, which expand to their member
|
|
22
|
+
* list. The filtered config lands in
|
|
23
|
+
* `<logDir>/<id>.mcp.json` and is passed with `--strict-mcp-config
|
|
24
|
+
* --mcp-config` so exactly those servers load. MCP servers are chosen at
|
|
25
|
+
* launch only — a mid-run `say` cannot add any.
|
|
26
|
+
*
|
|
27
|
+
* One name in that space is not a server at all: `claude-in-chrome` is the
|
|
28
|
+
* Chrome native-host bridge, which no config file can load and which the
|
|
29
|
+
* `--chrome` flag switches on instead. It is recognised here so a grant
|
|
30
|
+
* naming it becomes that flag rather than an "unknown MCP server" rejection.
|
|
31
|
+
*/
|
|
32
|
+
/** ~/.claude.json — the user's MCP server definitions (top-level mcpServers). */
|
|
33
|
+
const CLAUDE_JSON = join(homedir(), ".claude.json");
|
|
34
|
+
/**
|
|
35
|
+
* The browser bridge is not an MCP server. It rides the Chrome native-host
|
|
36
|
+
* channel, so it never appears in `mcpServers`; a spawned claude has it off
|
|
37
|
+
* and the `--chrome` flag switches it on. Tool grants naming it must
|
|
38
|
+
* therefore neither resolve to a server nor be rejected as unknown — they
|
|
39
|
+
* select a flag. See `grantsChrome`.
|
|
40
|
+
*/
|
|
41
|
+
const CHROME_SERVER = "claude-in-chrome";
|
|
42
|
+
/**
|
|
43
|
+
* True for a real server definition, false for anything else that happens to
|
|
44
|
+
* sit under `mcpServers`.
|
|
45
|
+
*
|
|
46
|
+
* The vendor config is not ours and has been observed carrying non-server
|
|
47
|
+
* entries under that key — tool-usage records keyed by tool name
|
|
48
|
+
* (`Read`, `Bash`, `mcp__server__tool`, …). Those are not servers: handing
|
|
49
|
+
* one to `--mcp-config` yields "invalid MCP server config", and listing one
|
|
50
|
+
* as available invites a run that cannot work. A server has a `command` (or a
|
|
51
|
+
* `url` for remote transports); an `mcp__…` key is a tool name whatever its
|
|
52
|
+
* shape.
|
|
53
|
+
*/
|
|
54
|
+
function isServerDefinition(name, value) {
|
|
55
|
+
if (name.startsWith("mcp__")) return false;
|
|
56
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
57
|
+
const v = value;
|
|
58
|
+
return typeof v.command === "string" && v.command.length > 0 || typeof v.url === "string" && v.url.length > 0;
|
|
59
|
+
}
|
|
60
|
+
function readMcpServers(claudeJson = CLAUDE_JSON) {
|
|
61
|
+
try {
|
|
62
|
+
if (!existsSync(claudeJson)) return {};
|
|
63
|
+
const servers = JSON.parse(readFileSync(claudeJson, "utf8")).mcpServers;
|
|
64
|
+
if (typeof servers !== "object" || servers === null || Array.isArray(servers)) return {};
|
|
65
|
+
const out = {};
|
|
66
|
+
for (const [name, value] of Object.entries(servers)) if (isServerDefinition(name, value)) out[name] = value;
|
|
67
|
+
return out;
|
|
68
|
+
} catch {
|
|
69
|
+
return {};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* True when any `--mcp` name or `--allowedTools` grant asks for the browser
|
|
74
|
+
* bridge: the bare server name, `mcp__claude-in-chrome`, or any
|
|
75
|
+
* `mcp__claude-in-chrome__<tool>`. The runner turns this into `--chrome` on
|
|
76
|
+
* the claude argv.
|
|
77
|
+
*/
|
|
78
|
+
function grantsChrome(entries) {
|
|
79
|
+
for (const entry of entries) for (const name of entry.split(",").map((s) => s.trim()).filter(Boolean)) {
|
|
80
|
+
if (name === CHROME_SERVER) return true;
|
|
81
|
+
if (name === `mcp__${CHROME_SERVER}`) return true;
|
|
82
|
+
if (name.startsWith(`mcp__${CHROME_SERVER}__`)) return true;
|
|
83
|
+
}
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Split a `--mcp a,b,c` flag value (also accepts repeated flags already split
|
|
88
|
+
* by the caller) and expand set names from workers.mcpSets.
|
|
89
|
+
*/
|
|
90
|
+
function expandMcpNames(names, config, claudeJson = CLAUDE_JSON) {
|
|
91
|
+
const available = readMcpServers(claudeJson);
|
|
92
|
+
const out = [];
|
|
93
|
+
for (const raw of names) for (const name of raw.split(",").map((s) => s.trim()).filter(Boolean)) {
|
|
94
|
+
if (name === CHROME_SERVER) continue;
|
|
95
|
+
if (name in config.mcpSets) {
|
|
96
|
+
for (const member of config.mcpSets[name]) if (!out.includes(member)) out.push(member);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (!(name in available)) {
|
|
100
|
+
const sets = Object.keys(config.mcpSets);
|
|
101
|
+
throw new WorkersConfigError(`unknown MCP server "${name}". Available servers: ${Object.keys(available).join(", ") || "(none in ~/.claude.json)"}${sets.length ? `; sets: ${sets.join(", ")}` : ""}`);
|
|
102
|
+
}
|
|
103
|
+
if (!out.includes(name)) out.push(name);
|
|
104
|
+
}
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Derive server names from tool grants: every `mcp__<server>__<tool>` (or bare
|
|
109
|
+
* `mcp__<server>`, or `mcp__<server>__*`) in an --allowedTools list names a
|
|
110
|
+
* server the run expects to be loaded. A grant is a dead letter unless its
|
|
111
|
+
* server is in the filtered config, so the runner treats these as implicit
|
|
112
|
+
* --mcp names — and only these; non-mcp grants load nothing.
|
|
113
|
+
*/
|
|
114
|
+
function mcpServersFromToolGrants(tools) {
|
|
115
|
+
const out = [];
|
|
116
|
+
for (const entry of tools) for (const name of entry.split(",").map((s) => s.trim()).filter(Boolean)) {
|
|
117
|
+
if (!name.startsWith("mcp__")) continue;
|
|
118
|
+
const server = name.slice(5).split("__")[0];
|
|
119
|
+
if (server === CHROME_SERVER) continue;
|
|
120
|
+
if (server && !out.includes(server)) out.push(server);
|
|
121
|
+
}
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
/** Path of a run's filtered MCP config. */
|
|
125
|
+
function runMcpConfigPath(logDir, id) {
|
|
126
|
+
return join(logDir, `${id}.mcp.json`);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Write a config containing only `names` (already expanded) and return its
|
|
130
|
+
* path. Unknown names fail fast with the available list.
|
|
131
|
+
*/
|
|
132
|
+
function writeMcpConfig(logDir, id, names, claudeJson = CLAUDE_JSON) {
|
|
133
|
+
const available = readMcpServers(claudeJson);
|
|
134
|
+
const unknown = names.filter((n) => !(n in available));
|
|
135
|
+
if (unknown.length) throw new WorkersConfigError(`unknown MCP server(s): ${unknown.join(", ")}. Available: ${Object.keys(available).join(", ") || "(none in ~/.claude.json)"}`);
|
|
136
|
+
const servers = {};
|
|
137
|
+
for (const n of names) servers[n] = available[n];
|
|
138
|
+
const path = runMcpConfigPath(logDir, id);
|
|
139
|
+
if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true });
|
|
140
|
+
writeFileSync(path, JSON.stringify({ mcpServers: servers }, null, 2) + "\n", "utf8");
|
|
141
|
+
return path;
|
|
142
|
+
}
|
|
143
|
+
/** `pai worker mcp list`: the servers and sets a run could name. */
|
|
144
|
+
function describeMcp(config, claudeJson = CLAUDE_JSON) {
|
|
145
|
+
const servers = Object.keys(readMcpServers(claudeJson));
|
|
146
|
+
const lines = [];
|
|
147
|
+
if (servers.length) {
|
|
148
|
+
lines.push(`servers (~/.claude.json):`);
|
|
149
|
+
for (const s of servers) lines.push(` ${s}`);
|
|
150
|
+
} else lines.push(`no MCP servers defined in ~/.claude.json`);
|
|
151
|
+
const sets = Object.entries(config.mcpSets);
|
|
152
|
+
if (sets.length) {
|
|
153
|
+
lines.push(`sets (workers.mcpSets):`);
|
|
154
|
+
for (const [name, members] of sets) lines.push(` ${name} = ${members.join(", ")}`);
|
|
155
|
+
}
|
|
156
|
+
lines.push(`usage: pai worker run --mcp <name>[,<name>…] -p '<task>'`);
|
|
157
|
+
return lines;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
//#endregion
|
|
10
161
|
//#region src/workers/args.ts
|
|
11
162
|
function parseRunnerArgs(argv) {
|
|
12
163
|
let prompt = null;
|
|
@@ -16,6 +167,7 @@ function parseRunnerArgs(argv) {
|
|
|
16
167
|
let callerModel = false;
|
|
17
168
|
let callerMcpConfig = false;
|
|
18
169
|
let callerSystemPrompt = false;
|
|
170
|
+
let callerTools = false;
|
|
19
171
|
const mcp = [];
|
|
20
172
|
const allowedTools = [];
|
|
21
173
|
let i = 0;
|
|
@@ -61,6 +213,8 @@ function parseRunnerArgs(argv) {
|
|
|
61
213
|
if (a.startsWith("--mcp-config=")) callerMcpConfig = true;
|
|
62
214
|
if (a === "--append-system-prompt") callerSystemPrompt = true;
|
|
63
215
|
if (a.startsWith("--append-system-prompt=")) callerSystemPrompt = true;
|
|
216
|
+
if (a === "--tools") callerTools = true;
|
|
217
|
+
if (a.startsWith("--tools=")) callerTools = true;
|
|
64
218
|
if (prompt === null && !a.startsWith("-") && rest.length > 0 && (rest[rest.length - 1] === "-p" || rest[rest.length - 1] === "--print")) prompt = a;
|
|
65
219
|
rest.push(a);
|
|
66
220
|
}
|
|
@@ -74,6 +228,7 @@ function parseRunnerArgs(argv) {
|
|
|
74
228
|
callerModel,
|
|
75
229
|
callerMcpConfig,
|
|
76
230
|
callerSystemPrompt,
|
|
231
|
+
callerTools,
|
|
77
232
|
mcp,
|
|
78
233
|
allowedTools
|
|
79
234
|
};
|
|
@@ -94,82 +249,23 @@ function stripPromptValues(argv) {
|
|
|
94
249
|
}
|
|
95
250
|
return out;
|
|
96
251
|
}
|
|
252
|
+
/**
|
|
253
|
+
* A one-line stderr hint (never blocking) for an inline -p prompt long or
|
|
254
|
+
* multi-line enough to be a shell-quoting risk — write it to a file and pass
|
|
255
|
+
* --spec instead. null when the prompt is short/simple enough to be fine.
|
|
256
|
+
*/
|
|
257
|
+
function longInlinePromptHint(prompt) {
|
|
258
|
+
if (!prompt) return null;
|
|
259
|
+
const newlines = (prompt.match(/\n/g) ?? []).length;
|
|
260
|
+
if (prompt.length > 600 || newlines > 3) return "hint: long inline prompts break on shell quoting — write the spec to a file and use --spec <file>";
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
97
263
|
/** Collapse whitespace and cut to n chars with an ellipsis (label rendering). */
|
|
98
264
|
function shortText(s, n) {
|
|
99
265
|
const t = String(s ?? "").split(/\s+/).filter(Boolean).join(" ");
|
|
100
266
|
return t.length <= n ? t : t.slice(0, n - 1) + "…";
|
|
101
267
|
}
|
|
102
268
|
|
|
103
|
-
//#endregion
|
|
104
|
-
//#region src/workers/ledger.ts
|
|
105
|
-
/**
|
|
106
|
-
* ledger.ts — the routing ledger, one append-only log for every worker event.
|
|
107
|
-
*
|
|
108
|
-
* Line shapes (whitespace-aligned so `tail` reads as a table):
|
|
109
|
-
*
|
|
110
|
-
* 2026-09-17 12:00:00 WORKER-START id=<id> provider=<p> mode=headless model=<m> cwd=<cwd> label=<label>
|
|
111
|
-
* 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>
|
|
112
|
-
* 2026-09-17 12:00:20 WORKER-REROUTE from=<a> to=<b> reason=quota
|
|
113
|
-
* 2026-09-17 12:00:00 DENIED-ANTHROPIC-AGENT cwd=<cwd> desc=<desc>
|
|
114
|
-
* 2026-09-17 12:00:00 ALLOWED-ANTHROPIC-AGENT cwd=<cwd> desc=<desc>
|
|
115
|
-
*
|
|
116
|
-
* `pai worker log` (and glm-log before it) counts these; keep the tags stable.
|
|
117
|
-
*/
|
|
118
|
-
function ledgerStamp(d = /* @__PURE__ */ new Date()) {
|
|
119
|
-
const p = (n) => String(n).padStart(2, "0");
|
|
120
|
-
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
|
|
121
|
-
}
|
|
122
|
-
/** Parse `2026-09-17 12:00:00 TAG key=value …` into its parts. */
|
|
123
|
-
function parseLedgerLine(line) {
|
|
124
|
-
const m = line.match(/^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) (\S+)(?: (.*))?$/);
|
|
125
|
-
if (!m) return null;
|
|
126
|
-
const fields = {};
|
|
127
|
-
for (const part of (m[3] ?? "").split(" ")) {
|
|
128
|
-
if (!part) continue;
|
|
129
|
-
const eq = part.indexOf("=");
|
|
130
|
-
if (eq <= 0) continue;
|
|
131
|
-
fields[part.slice(0, eq)] = part.slice(eq + 1);
|
|
132
|
-
}
|
|
133
|
-
return {
|
|
134
|
-
stamp: m[1],
|
|
135
|
-
event: m[2],
|
|
136
|
-
fields
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
/** Append one ledger line. `kv` order is the caller's; values are flattened. */
|
|
140
|
-
function appendLedger(path, event, kv, now = /* @__PURE__ */ new Date()) {
|
|
141
|
-
const parts = [];
|
|
142
|
-
for (const [k, v] of Object.entries(kv)) {
|
|
143
|
-
if (v === void 0 || v === null) continue;
|
|
144
|
-
parts.push(`${k}=${String(v).replace(/\s+/g, " ")}`);
|
|
145
|
-
}
|
|
146
|
-
const dir = dirname(path);
|
|
147
|
-
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
148
|
-
const tail = parts.length ? " " + parts.join(" ") : "";
|
|
149
|
-
appendFileSync(path, `${ledgerStamp(now)} ${event}${tail}\n`, "utf8");
|
|
150
|
-
}
|
|
151
|
-
/** The counts `pai worker log` prints, over today's lines or the whole file. */
|
|
152
|
-
function ledgerSummary(path, scope, lastN = 15, now = /* @__PURE__ */ new Date()) {
|
|
153
|
-
if (!existsSync(path)) return null;
|
|
154
|
-
const text = readFileSync(path, "utf8");
|
|
155
|
-
const today = ledgerStamp(now).slice(0, 10);
|
|
156
|
-
const lines = text.split("\n").filter((l) => l.trim());
|
|
157
|
-
const parsed = (scope === "all" ? lines : lines.filter((l) => l.startsWith(today))).map(parseLedgerLine).filter((x) => x !== null);
|
|
158
|
-
const count = (ev) => parsed.filter((p) => p.event === ev).length;
|
|
159
|
-
const ends = parsed.filter((p) => p.event === "WORKER-END");
|
|
160
|
-
const ok = ends.filter((p) => p.fields.rc === "0").length;
|
|
161
|
-
return {
|
|
162
|
-
scope: scope === "all" ? "all time" : `today ${today}`,
|
|
163
|
-
started: count("WORKER-START"),
|
|
164
|
-
endedOk: ok,
|
|
165
|
-
endedFailed: ends.length - ok,
|
|
166
|
-
denied: count("DENIED-ANTHROPIC-AGENT"),
|
|
167
|
-
allowed: count("ALLOWED-ANTHROPIC-AGENT"),
|
|
168
|
-
reroutes: count("WORKER-REROUTE"),
|
|
169
|
-
lastLines: lines.slice(-lastN)
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
|
|
173
269
|
//#endregion
|
|
174
270
|
//#region src/workers/status.ts
|
|
175
271
|
/**
|
|
@@ -228,13 +324,47 @@ function newWorkerId(d = /* @__PURE__ */ new Date(), pid = process.pid) {
|
|
|
228
324
|
idSeq = 0;
|
|
229
325
|
return base;
|
|
230
326
|
}
|
|
327
|
+
let tmpSeq = 0;
|
|
328
|
+
/** A temp path only this call owns — never the same string twice. */
|
|
329
|
+
function statusTmpPath(logDir, id) {
|
|
330
|
+
return `${statusPath(logDir, id)}.${process.pid}.${tmpSeq++}.tmp`;
|
|
331
|
+
}
|
|
332
|
+
const firstWrittenLabel = /* @__PURE__ */ new Map();
|
|
231
333
|
/** Write status atomically (temp + rename) and stamp `updated`. */
|
|
232
334
|
function saveStatus(logDir, status, d = /* @__PURE__ */ new Date()) {
|
|
233
335
|
status.updated = nowStamp(d);
|
|
234
336
|
const path = statusPath(logDir, status.id);
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
337
|
+
const baseline = firstWrittenLabel.get(status.id);
|
|
338
|
+
if (baseline === void 0) firstWrittenLabel.set(status.id, status.label);
|
|
339
|
+
else if (status.label === baseline) {
|
|
340
|
+
const onDisk = loadStatus(logDir, status.id);
|
|
341
|
+
if (onDisk && onDisk.label !== status.label) status.label = onDisk.label;
|
|
342
|
+
}
|
|
343
|
+
const tmp = statusTmpPath(logDir, status.id);
|
|
344
|
+
if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true });
|
|
345
|
+
try {
|
|
346
|
+
writeFileSync(tmp, JSON.stringify(status), "utf8");
|
|
347
|
+
renameSync(tmp, path);
|
|
348
|
+
} catch (e) {
|
|
349
|
+
try {
|
|
350
|
+
rmSync(tmp, { force: true });
|
|
351
|
+
} catch {}
|
|
352
|
+
throw e;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Set a worker's goal (the operator's `--label`), atomically, through the
|
|
357
|
+
* same saveStatus path everything else writes with. Used by `pai worker
|
|
358
|
+
* goal` and the `--goal` option of `say`: a fresh call in a fresh process, so
|
|
359
|
+
* it always carries no baseline yet and always wins over the worker's own
|
|
360
|
+
* next periodic write (see saveStatus above).
|
|
361
|
+
*/
|
|
362
|
+
function setWorkerLabel(logDir, id, label) {
|
|
363
|
+
const status = loadStatus(logDir, id);
|
|
364
|
+
if (!status) throw new Error(`no worker named "${id}"`);
|
|
365
|
+
status.label = label;
|
|
366
|
+
saveStatus(logDir, status);
|
|
367
|
+
return status;
|
|
238
368
|
}
|
|
239
369
|
/** Load every status file in the logDir, oldest id first, skipping damage. */
|
|
240
370
|
function loadStatuses(logDir) {
|
|
@@ -257,6 +387,20 @@ function loadStatus(logDir, id) {
|
|
|
257
387
|
return null;
|
|
258
388
|
}
|
|
259
389
|
}
|
|
390
|
+
/**
|
|
391
|
+
* Wait for a worker to record its own terminal state (the run's signal
|
|
392
|
+
* handler writes killed/rc/secs). Returns that status, or null when it is
|
|
393
|
+
* still "running" after `timeoutMs` — the caller then writes the state itself.
|
|
394
|
+
*/
|
|
395
|
+
async function waitForTerminalStatus(logDir, id, timeoutMs = 2e3, stepMs = 50) {
|
|
396
|
+
const deadline = Date.now() + timeoutMs;
|
|
397
|
+
for (;;) {
|
|
398
|
+
const s = loadStatus(logDir, id);
|
|
399
|
+
if (s && s.state !== "running") return s;
|
|
400
|
+
if (Date.now() >= deadline) return null;
|
|
401
|
+
await new Promise((r) => setTimeout(r, stepMs));
|
|
402
|
+
}
|
|
403
|
+
}
|
|
260
404
|
function alive(pid) {
|
|
261
405
|
if (!pid || pid <= 0) return false;
|
|
262
406
|
try {
|
|
@@ -286,6 +430,76 @@ function describeTool(name, inp) {
|
|
|
286
430
|
return name;
|
|
287
431
|
}
|
|
288
432
|
|
|
433
|
+
//#endregion
|
|
434
|
+
//#region src/workers/ledger.ts
|
|
435
|
+
/**
|
|
436
|
+
* ledger.ts — the routing ledger, one append-only log for every worker event.
|
|
437
|
+
*
|
|
438
|
+
* Line shapes (whitespace-aligned so `tail` reads as a table):
|
|
439
|
+
*
|
|
440
|
+
* 2026-09-17 12:00:00 WORKER-START id=<id> provider=<p> mode=headless model=<m> cwd=<cwd> label=<label>
|
|
441
|
+
* 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>
|
|
442
|
+
* 2026-09-17 12:00:20 WORKER-REROUTE from=<a> to=<b> reason=quota
|
|
443
|
+
* 2026-09-17 12:00:00 DENIED-ANTHROPIC-AGENT cwd=<cwd> desc=<desc>
|
|
444
|
+
* 2026-09-17 12:00:00 ALLOWED-ANTHROPIC-AGENT cwd=<cwd> desc=<desc>
|
|
445
|
+
*
|
|
446
|
+
* `pai worker log` (and glm-log before it) counts these; keep the tags stable.
|
|
447
|
+
*/
|
|
448
|
+
function ledgerStamp(d = /* @__PURE__ */ new Date()) {
|
|
449
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
450
|
+
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
|
|
451
|
+
}
|
|
452
|
+
/** Parse `2026-09-17 12:00:00 TAG key=value …` into its parts. */
|
|
453
|
+
function parseLedgerLine(line) {
|
|
454
|
+
const m = line.match(/^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) (\S+)(?: (.*))?$/);
|
|
455
|
+
if (!m) return null;
|
|
456
|
+
const fields = {};
|
|
457
|
+
for (const part of (m[3] ?? "").split(" ")) {
|
|
458
|
+
if (!part) continue;
|
|
459
|
+
const eq = part.indexOf("=");
|
|
460
|
+
if (eq <= 0) continue;
|
|
461
|
+
fields[part.slice(0, eq)] = part.slice(eq + 1);
|
|
462
|
+
}
|
|
463
|
+
return {
|
|
464
|
+
stamp: m[1],
|
|
465
|
+
event: m[2],
|
|
466
|
+
fields
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
/** Append one ledger line. `kv` order is the caller's; values are flattened. */
|
|
470
|
+
function appendLedger(path, event, kv, now = /* @__PURE__ */ new Date()) {
|
|
471
|
+
const parts = [];
|
|
472
|
+
for (const [k, v] of Object.entries(kv)) {
|
|
473
|
+
if (v === void 0 || v === null) continue;
|
|
474
|
+
parts.push(`${k}=${String(v).replace(/\s+/g, " ")}`);
|
|
475
|
+
}
|
|
476
|
+
const dir = dirname(path);
|
|
477
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
478
|
+
const tail = parts.length ? " " + parts.join(" ") : "";
|
|
479
|
+
appendFileSync(path, `${ledgerStamp(now)} ${event}${tail}\n`, "utf8");
|
|
480
|
+
}
|
|
481
|
+
/** The counts `pai worker log` prints, over today's lines or the whole file. */
|
|
482
|
+
function ledgerSummary(path, scope, lastN = 15, now = /* @__PURE__ */ new Date()) {
|
|
483
|
+
if (!existsSync(path)) return null;
|
|
484
|
+
const text = readFileSync(path, "utf8");
|
|
485
|
+
const today = ledgerStamp(now).slice(0, 10);
|
|
486
|
+
const lines = text.split("\n").filter((l) => l.trim());
|
|
487
|
+
const parsed = (scope === "all" ? lines : lines.filter((l) => l.startsWith(today))).map(parseLedgerLine).filter((x) => x !== null);
|
|
488
|
+
const count = (ev) => parsed.filter((p) => p.event === ev).length;
|
|
489
|
+
const ends = parsed.filter((p) => p.event === "WORKER-END");
|
|
490
|
+
const ok = ends.filter((p) => p.fields.rc === "0").length;
|
|
491
|
+
return {
|
|
492
|
+
scope: scope === "all" ? "all time" : `today ${today}`,
|
|
493
|
+
started: count("WORKER-START"),
|
|
494
|
+
endedOk: ok,
|
|
495
|
+
endedFailed: ends.length - ok,
|
|
496
|
+
denied: count("DENIED-ANTHROPIC-AGENT"),
|
|
497
|
+
allowed: count("ALLOWED-ANTHROPIC-AGENT"),
|
|
498
|
+
reroutes: count("WORKER-REROUTE"),
|
|
499
|
+
lastLines: lines.slice(-lastN)
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
289
503
|
//#endregion
|
|
290
504
|
//#region src/workers/scope.ts
|
|
291
505
|
/**
|
|
@@ -1611,56 +1825,333 @@ function worktreeSystemPrompt(id, branch, dir) {
|
|
|
1611
1825
|
].join("\n");
|
|
1612
1826
|
}
|
|
1613
1827
|
|
|
1828
|
+
//#endregion
|
|
1829
|
+
//#region src/workers/agentish.ts
|
|
1830
|
+
/**
|
|
1831
|
+
* agentish.ts — Agentish v2 (AG2), the wire format a worker's final report
|
|
1832
|
+
* uses by default: a kind line plus one-letter k=v lines instead of JSON
|
|
1833
|
+
* prose. Spec and validator live in the `aibroker` CLI
|
|
1834
|
+
* (see /Users/i052341/Daten/Cloud/Development/ai/AIBroker/docs/agentish.md);
|
|
1835
|
+
* this module shells out to it when present and falls back to a frozen copy
|
|
1836
|
+
* of the spec text otherwise, so a worker still gets a contract when
|
|
1837
|
+
* `aibroker` is not installed on its box.
|
|
1838
|
+
*/
|
|
1839
|
+
/** Frozen copy of `aibroker agentish spec`'s first line, for when the CLI is absent. */
|
|
1840
|
+
const AG2_SPEC_FALLBACK = "AG2. msg=kind line+k=v lines. kinds T R S Q A X. keys i id g goal o own n forbid d steps p proof u out l limits r res c changes t tests G gate I inst m images # nums w worst x next z note(<200ch). sep |. outcomes + - ~ ? !. @n=path declared once then reused; @n:12=file:line. tests as Name+ Name-. no prose, no articles, never restate, unknown=?. r=+ only if all t +.";
|
|
1841
|
+
/** Frozen copy of the `y` (why) extension line. */
|
|
1842
|
+
const AG2_EXTENSIONS_FALLBACK = "y why(≤600ch)";
|
|
1843
|
+
let cachedSpec = null;
|
|
1844
|
+
/**
|
|
1845
|
+
* The AG2 spec + extensions lines: from `aibroker agentish spec` when the
|
|
1846
|
+
* CLI runs and its output looks right, else the frozen fallback. Memoised —
|
|
1847
|
+
* every caller in one process gets the same spec without re-spawning aibroker.
|
|
1848
|
+
*/
|
|
1849
|
+
function ag2Spec() {
|
|
1850
|
+
if (cachedSpec) return cachedSpec;
|
|
1851
|
+
try {
|
|
1852
|
+
const lines = execFileSync("aibroker", ["agentish", "spec"], {
|
|
1853
|
+
timeout: 3e3,
|
|
1854
|
+
stdio: [
|
|
1855
|
+
"ignore",
|
|
1856
|
+
"pipe",
|
|
1857
|
+
"ignore"
|
|
1858
|
+
],
|
|
1859
|
+
encoding: "utf8"
|
|
1860
|
+
}).split("\n").filter((l) => l.trim());
|
|
1861
|
+
if (lines[0]?.startsWith("AG2.")) {
|
|
1862
|
+
cachedSpec = {
|
|
1863
|
+
spec: lines[0],
|
|
1864
|
+
extensions: lines[1] ?? AG2_EXTENSIONS_FALLBACK,
|
|
1865
|
+
source: "aibroker"
|
|
1866
|
+
};
|
|
1867
|
+
return cachedSpec;
|
|
1868
|
+
}
|
|
1869
|
+
} catch {}
|
|
1870
|
+
cachedSpec = {
|
|
1871
|
+
spec: AG2_SPEC_FALLBACK,
|
|
1872
|
+
extensions: AG2_EXTENSIONS_FALLBACK,
|
|
1873
|
+
source: "builtin"
|
|
1874
|
+
};
|
|
1875
|
+
return cachedSpec;
|
|
1876
|
+
}
|
|
1877
|
+
/**
|
|
1878
|
+
* Parse one AG2 message: first non-empty line is the kind (T R S Q A X,
|
|
1879
|
+
* optionally followed by trailing text — only the first character is the
|
|
1880
|
+
* kind), later lines are `key=value` or `@n=path` symbol declarations.
|
|
1881
|
+
* ```-fenced lines are ignored so a message pasted inside a code fence still
|
|
1882
|
+
* parses. Returns null when the first non-empty line is not a known kind.
|
|
1883
|
+
*/
|
|
1884
|
+
function parseAg2Report(text) {
|
|
1885
|
+
const kinds = new Set([
|
|
1886
|
+
"T",
|
|
1887
|
+
"R",
|
|
1888
|
+
"S",
|
|
1889
|
+
"Q",
|
|
1890
|
+
"A",
|
|
1891
|
+
"X"
|
|
1892
|
+
]);
|
|
1893
|
+
const lines = (text ?? "").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("```"));
|
|
1894
|
+
if (!lines.length) return null;
|
|
1895
|
+
const kind = lines[0][0];
|
|
1896
|
+
if (!kinds.has(kind)) return null;
|
|
1897
|
+
const fields = {};
|
|
1898
|
+
const symbols = {};
|
|
1899
|
+
for (const line of lines.slice(1)) {
|
|
1900
|
+
const eq = line.indexOf("=");
|
|
1901
|
+
if (eq < 0) continue;
|
|
1902
|
+
const key = line.slice(0, eq).trim();
|
|
1903
|
+
const value = line.slice(eq + 1).trim();
|
|
1904
|
+
if (key.startsWith("@")) symbols[key.slice(1)] = value;
|
|
1905
|
+
else fields[key] = value;
|
|
1906
|
+
}
|
|
1907
|
+
return {
|
|
1908
|
+
kind,
|
|
1909
|
+
fields,
|
|
1910
|
+
symbols
|
|
1911
|
+
};
|
|
1912
|
+
}
|
|
1913
|
+
/** true iff the value ends with the AG2 pass outcome character. */
|
|
1914
|
+
function passed(entry) {
|
|
1915
|
+
return entry.endsWith("+");
|
|
1916
|
+
}
|
|
1917
|
+
/**
|
|
1918
|
+
* Map a parsed AG2 report onto the runner's WorkerReport shape, so the
|
|
1919
|
+
* viewer and `printResult` can render an AG2 `R` exactly like a JSON one.
|
|
1920
|
+
*/
|
|
1921
|
+
function ag2ToWorkerReport(parsed) {
|
|
1922
|
+
const f = parsed.fields;
|
|
1923
|
+
const out = { format: "ag2" };
|
|
1924
|
+
if (f.c) out.changed = f.c.split("|").map((s) => s.trim()).filter(Boolean).map((entry) => {
|
|
1925
|
+
const colon = entry.indexOf(":");
|
|
1926
|
+
const space = entry.indexOf(" ");
|
|
1927
|
+
const cut = colon >= 0 && (space < 0 || colon < space) ? colon : space;
|
|
1928
|
+
if (cut < 0) return {
|
|
1929
|
+
path: entry,
|
|
1930
|
+
summary: ""
|
|
1931
|
+
};
|
|
1932
|
+
return {
|
|
1933
|
+
path: entry.slice(0, cut).trim(),
|
|
1934
|
+
summary: entry.slice(cut + 1).trim()
|
|
1935
|
+
};
|
|
1936
|
+
});
|
|
1937
|
+
if (f.t) out.checks = f.t.split(/\s+/).filter(Boolean).map((entry) => ({
|
|
1938
|
+
name: entry.slice(0, -1),
|
|
1939
|
+
ok: passed(entry),
|
|
1940
|
+
detail: entry.slice(-1)
|
|
1941
|
+
}));
|
|
1942
|
+
if (f.p) out.commands = f.p.split("|").map((s) => s.trim()).filter(Boolean);
|
|
1943
|
+
const open = [];
|
|
1944
|
+
if (f.x) open.push(f.x);
|
|
1945
|
+
for (const [k, v] of Object.entries(f)) if ((v.endsWith("!") || v.endsWith("?")) && k !== "r" && k !== "res") open.push(`${k}: ${v}`);
|
|
1946
|
+
if (open.length) out.open = open;
|
|
1947
|
+
if (f.z) out.notes = f.z;
|
|
1948
|
+
const res = f.r ?? f.res;
|
|
1949
|
+
if (res === "+" || res === "-" || res === "~" || res === "?" || res === "!") out.result = res;
|
|
1950
|
+
if (f.y) out.why = f.y;
|
|
1951
|
+
return out;
|
|
1952
|
+
}
|
|
1953
|
+
/**
|
|
1954
|
+
* Validate an AG2 message with `aibroker agentish check - --json`. Never
|
|
1955
|
+
* fails the caller: a missing/broken CLI reports ok:true, validator "none" —
|
|
1956
|
+
* a worker's run is never blocked on a local tooling gap, only its
|
|
1957
|
+
* `reportValid` marker goes unset.
|
|
1958
|
+
*/
|
|
1959
|
+
function validateAg2(text) {
|
|
1960
|
+
try {
|
|
1961
|
+
const out = execFileSync("aibroker", [
|
|
1962
|
+
"agentish",
|
|
1963
|
+
"check",
|
|
1964
|
+
"-",
|
|
1965
|
+
"--json"
|
|
1966
|
+
], {
|
|
1967
|
+
input: text,
|
|
1968
|
+
timeout: 5e3,
|
|
1969
|
+
stdio: [
|
|
1970
|
+
"pipe",
|
|
1971
|
+
"pipe",
|
|
1972
|
+
"ignore"
|
|
1973
|
+
],
|
|
1974
|
+
encoding: "utf8"
|
|
1975
|
+
});
|
|
1976
|
+
const parsed = JSON.parse(out);
|
|
1977
|
+
const errors = (parsed.errors ?? []).map((e) => e.message ?? e.code ?? "unknown error");
|
|
1978
|
+
return {
|
|
1979
|
+
ok: parsed.ok !== false,
|
|
1980
|
+
errors,
|
|
1981
|
+
validator: "aibroker"
|
|
1982
|
+
};
|
|
1983
|
+
} catch (e) {
|
|
1984
|
+
const out = e.stdout;
|
|
1985
|
+
if (out) try {
|
|
1986
|
+
const parsed = JSON.parse(out.toString());
|
|
1987
|
+
const errors = (parsed.errors ?? []).map((er) => er.message ?? er.code ?? "unknown error");
|
|
1988
|
+
return {
|
|
1989
|
+
ok: parsed.ok !== false,
|
|
1990
|
+
errors,
|
|
1991
|
+
validator: "aibroker"
|
|
1992
|
+
};
|
|
1993
|
+
} catch {}
|
|
1994
|
+
return {
|
|
1995
|
+
ok: true,
|
|
1996
|
+
errors: [],
|
|
1997
|
+
validator: "none"
|
|
1998
|
+
};
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
|
|
1614
2002
|
//#endregion
|
|
1615
2003
|
//#region src/workers/report.ts
|
|
1616
2004
|
/**
|
|
1617
2005
|
* report.ts — the worker contract: terse, structured final reports.
|
|
1618
2006
|
*
|
|
1619
2007
|
* Headless workers run with an appended system prompt that fixes their output
|
|
1620
|
-
* shape: act, verify, then finish with ONE final message
|
|
1621
|
-
*
|
|
1622
|
-
* it as a compact block instead of a
|
|
2008
|
+
* shape: act, verify, then finish with ONE final message — AG2 (see
|
|
2009
|
+
* agentish.ts) by default, JSON as the pre-AG2 fallback. The runner parses it
|
|
2010
|
+
* out of the result; the viewer renders it as a compact block instead of a
|
|
2011
|
+
* wall of text.
|
|
1623
2012
|
*/
|
|
1624
2013
|
/** The marker the runner puts on operator messages the worker must answer. */
|
|
1625
2014
|
const OPERATOR_MARK = "[operator]";
|
|
1626
|
-
/**
|
|
1627
|
-
const
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
]
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
2015
|
+
/** Classes that get the short contract — cheap, bounded, single-answer tasks. */
|
|
2016
|
+
const SHORT_CONTRACT_CLASSES = new Set(["spotcheck", "simple"]);
|
|
2017
|
+
/**
|
|
2018
|
+
* The AG2 report block appended to a contract when format is "ag2" — field
|
|
2019
|
+
* set matches `aibroker agentish check`'s real R schema (verified 2026-09-20:
|
|
2020
|
+
* i/r/t/c are required, not optional as an early draft of this contract had
|
|
2021
|
+
* it; r=+ needs G=+ and non-empty p too, not just all-t-passing; R has no
|
|
2022
|
+
* `u`/out field at all — u is T-only — so the answer goes in z instead).
|
|
2023
|
+
*/
|
|
2024
|
+
function ag2ReportBlock() {
|
|
2025
|
+
const spec = ag2Spec();
|
|
2026
|
+
return [
|
|
2027
|
+
spec.spec,
|
|
2028
|
+
spec.extensions,
|
|
2029
|
+
"Final message: one AG2 `R` message only — no prose, no code fence, no markdown.",
|
|
2030
|
+
"R requires i (id), r (+ - ~ ? !), t (tests, Name+ Name-), c (path summary|path summary).",
|
|
2031
|
+
"r=+ requires G=+ (gate) and p non-empty (proof command), and every t entry +. z= the answer/note,",
|
|
2032
|
+
"one line, always present. x= what is left (omit if nothing). y= one line why when r is not +.",
|
|
2033
|
+
"Example:",
|
|
2034
|
+
"R",
|
|
2035
|
+
"i=count-audit-ts",
|
|
2036
|
+
"r=+",
|
|
2037
|
+
"G=+",
|
|
2038
|
+
"c=src/audit summary of files counted",
|
|
2039
|
+
"t=Count+",
|
|
2040
|
+
"p=wc -l src/audit/*.ts",
|
|
2041
|
+
"z=19 files, 2523 lines"
|
|
2042
|
+
];
|
|
2043
|
+
}
|
|
2044
|
+
/** The JSON report block appended to a contract when format is "json" (pre-AG2 fallback). */
|
|
2045
|
+
function jsonReportBlock(lead) {
|
|
2046
|
+
return [lead, "{\"changed\":[{\"path\":\"…\",\"summary\":\"…\"}],\"commands\":[\"…\"],\"checks\":[{\"name\":\"…\",\"ok\":true,\"detail\":\"…\"}],\"open\":[\"…\"],\"notes\":\"one line\"}"];
|
|
2047
|
+
}
|
|
2048
|
+
/** The two sentences that stop a worker from breaking on shell quoting — kept in both contracts. */
|
|
2049
|
+
function fileNotInlineBlock() {
|
|
2050
|
+
return [
|
|
2051
|
+
"NEVER put a multi-line or quoted payload inline in a shell command — no heredocs (<<EOF), no long",
|
|
2052
|
+
"-p '…' specs for child workers, no inline JSON, no inline scripts. ALWAYS write it to a file with",
|
|
2053
|
+
"the Write tool first and pass the file: -p \"$(cat spec.txt)\", --file spec.txt, or < spec.txt.",
|
|
2054
|
+
"Inline quoting fails silently or dies with a parse error and burns a worker launch; there are no",
|
|
2055
|
+
"exceptions.",
|
|
2056
|
+
"Never inline code one-liners (python3 -c, node -e, ruby -e and friends) — the permission layer",
|
|
2057
|
+
"denies them (\"ztk: command denied by permission rules\"); write the script to a file with the",
|
|
2058
|
+
"Write tool and run it (python3 script.py), or use jq for JSON inspection."
|
|
2059
|
+
];
|
|
2060
|
+
}
|
|
2061
|
+
/** The file-not-inline rule, condensed for the short contract's tight length budget. */
|
|
2062
|
+
function fileNotInlineBlockShort() {
|
|
2063
|
+
return [
|
|
2064
|
+
"NEVER put a multi-line or quoted payload inline in a shell command (no heredocs, no inline",
|
|
2065
|
+
"scripts): write it to a file with the Write tool and pass it, e.g. -p \"$(cat spec.txt)\".",
|
|
2066
|
+
"Never inline code (python3 -c and friends) — write a script file and run it, or use jq."
|
|
2067
|
+
];
|
|
2068
|
+
}
|
|
2069
|
+
/** spotcheck/simple: headless, bounded, no delegation language — under 1500 chars in ag2 format. */
|
|
2070
|
+
function shortContractPrompt(format) {
|
|
2071
|
+
const lines = [
|
|
2072
|
+
"You are a headless implementation worker, run non-interactively.",
|
|
2073
|
+
"This task is yours: answer the question or do the bounded task now. Run at most one command",
|
|
2074
|
+
"to confirm a result and never re-derive a number you already have. No child workers, no handoff.",
|
|
2075
|
+
...fileNotInlineBlockShort()
|
|
2076
|
+
];
|
|
2077
|
+
lines.push(...format === "ag2" ? ag2ReportBlock() : jsonReportBlock("Your ONE final message is a JSON object and nothing else — no prose before or after, no code fence:"));
|
|
2078
|
+
return lines.join("\n");
|
|
2079
|
+
}
|
|
2080
|
+
/** Every other class (and no class): the full worker/orchestrator contract. */
|
|
2081
|
+
function fullContractPrompt(format) {
|
|
2082
|
+
const lines = [
|
|
2083
|
+
"You are a headless implementation worker, run non-interactively by an orchestrating session.",
|
|
2084
|
+
"You are the worker: this task is yours to finish in this process. Never hand your whole task to a",
|
|
2085
|
+
"single child worker and step back — that is delegation, not work. Child workers are for two things",
|
|
2086
|
+
"only: (1) PARALLELISING independent parts of your task, each part a self-contained spec; (2) CHEAPER",
|
|
2087
|
+
"bounded sub-tasks — probes, renders, test runs, lookups — run one tier down with --class spotcheck or",
|
|
2088
|
+
"--class simple. Always pin --provider anthropic on children.",
|
|
2089
|
+
"You own your children's results: run them in the foreground or poll until each has finished, read",
|
|
2090
|
+
"their result, verify it, and fold it into your own report. When your turn ends this run ends and any",
|
|
2091
|
+
"running child is killed, so never end with a child still running.",
|
|
2092
|
+
"Ending your turn ends this run. There is no later wake-up: never call ScheduleWakeup, never say you",
|
|
2093
|
+
"will pick something up later, never leave a background command as your final action. Finish, then",
|
|
2094
|
+
"report.",
|
|
2095
|
+
"No narration, no timestamps, no greetings, no summaries of what you read; act, verify, then stop.",
|
|
2096
|
+
...fileNotInlineBlock(),
|
|
2097
|
+
"",
|
|
2098
|
+
`Operator messages: a user turn starting with ${OPERATOR_MARK} was typed by the operator while you`,
|
|
2099
|
+
"run (the prompt of a resumed run is the operator's too). Answer it FIRST, in one or two plain lines —",
|
|
2100
|
+
"a question gets its answer, an instruction gets one line naming what will change — then continue the",
|
|
2101
|
+
"task you were on.",
|
|
2102
|
+
"",
|
|
2103
|
+
"Sub-workers: you may start your own workers with `pai worker run --class <class> -p '<prompt>'`",
|
|
2104
|
+
"(Bash tool). Your children run on their own provider and report back to you automatically when",
|
|
2105
|
+
"they finish. Send a handoff UP instead of doing work yourself when a cheaper provider would",
|
|
2106
|
+
"suffice, the task is out of your scope, or a decision is needed:",
|
|
2107
|
+
"`pai worker handoff '{\"kind\":\"proposal\",\"text\":\"…\",\"data\":{…}}'` (kinds: proposal,",
|
|
2108
|
+
"question, blocker; results are sent for you when you finish). Sibling workers are not a",
|
|
2109
|
+
"coordination path — there is no sideways channel; everything goes up to your parent.",
|
|
2110
|
+
""
|
|
2111
|
+
];
|
|
2112
|
+
lines.push(...format === "ag2" ? ag2ReportBlock() : jsonReportBlock("Your ONE final message is a JSON object and nothing else — no prose before or after, no code fence:"));
|
|
2113
|
+
if (format === "json") lines.push("changed: files you touched (path + one-line summary). commands: the commands that verify the work.", "checks: each with ok true/false and the evidence in detail. open: what you could not finish, if anything.", "notes: one line, the headline a reviewer reads first.");
|
|
2114
|
+
return lines.join("\n");
|
|
2115
|
+
}
|
|
2116
|
+
/**
|
|
2117
|
+
* The system prompt appended to every headless run, chosen by task class and
|
|
2118
|
+
* report format. `spotcheck`/`simple` get a short contract (no delegation
|
|
2119
|
+
* language, under 1200 chars in json format / 1500 in ag2); every other class (and no class) gets the
|
|
2120
|
+
* full contract. Both end with the AG2 report block unless `format` is
|
|
2121
|
+
* "json", the pre-AG2 fallback.
|
|
2122
|
+
*/
|
|
2123
|
+
function workerContractPrompt(cls, format) {
|
|
2124
|
+
return SHORT_CONTRACT_CLASSES.has(cls ?? "") ? shortContractPrompt(format) : fullContractPrompt(format);
|
|
2125
|
+
}
|
|
2126
|
+
/**
|
|
2127
|
+
* The line appended to a headless prompt's own text (never the system
|
|
2128
|
+
* prompt): models weight an instruction at the end of the user turn more
|
|
2129
|
+
* than one buried in a long system-prompt paragraph, and this is the
|
|
2130
|
+
* difference between a worker that returns a valid AG2/JSON report and one
|
|
2131
|
+
* that answers in prose despite the contract (measured 2026-09-20 on both
|
|
2132
|
+
* Haiku and Sonnet with the AG2 contract alone).
|
|
2133
|
+
*/
|
|
2134
|
+
function promptTrailer(format) {
|
|
2135
|
+
return format === "ag2" ? "\n\nFinal message: one AG2 R message only, no prose (format in system prompt)." : "\n\nFinal message: a JSON object only, no prose before or after, no code fence (format in system prompt).";
|
|
2136
|
+
}
|
|
2137
|
+
/** The exact text sent to resend a final message that failed AG2 validation. */
|
|
2138
|
+
const AG2_REASK_TEXT = "Your final message was not a valid AG2 R message. Resend it now as a single AG2 R message only: first line R, then i=id r=result G=+ (gate, if r=+) t=tests(Name+) c=path summary p=proof z=the answer/note. No prose.";
|
|
2139
|
+
/** The full-contract AG2 prompt — the keepalive path's identical-construction baseline. */
|
|
2140
|
+
const WORKER_CONTRACT_PROMPT = fullContractPrompt("ag2");
|
|
2141
|
+
/**
|
|
2142
|
+
* Extract the report from a worker's final message: AG2 first (an `R`
|
|
2143
|
+
* message, see agentish.ts), then the JSON contract as a fallback for
|
|
2144
|
+
* workers still on the pre-AG2 shape. Accepts a bare JSON object, a
|
|
2145
|
+
* ```json fenced block, or an object embedded in surrounding text — only
|
|
2146
|
+
* objects that look like the contract count (at least one of changed /
|
|
1658
2147
|
* checks / notes) — any other JSON falls through to null so the raw text is
|
|
1659
2148
|
* kept as-is.
|
|
1660
2149
|
*/
|
|
1661
2150
|
function parseWorkerReport(text) {
|
|
1662
2151
|
const trimmed = (text ?? "").trim();
|
|
1663
2152
|
if (!trimmed) return null;
|
|
2153
|
+
const ag2 = parseAg2Report(trimmed);
|
|
2154
|
+
if (ag2 && ag2.kind === "R") return ag2ToWorkerReport(ag2);
|
|
1664
2155
|
const candidates = [];
|
|
1665
2156
|
const fence = trimmed.match(/```(?:json)?\s*(\{[\s\S]*?\})\s*```/);
|
|
1666
2157
|
if (fence) candidates.push(fence[1]);
|
|
@@ -1670,7 +2161,10 @@ function parseWorkerReport(text) {
|
|
|
1670
2161
|
if (first >= 0 && last > first) candidates.push(trimmed.slice(first, last + 1));
|
|
1671
2162
|
for (const cand of candidates) try {
|
|
1672
2163
|
const v = JSON.parse(cand);
|
|
1673
|
-
if (typeof v === "object" && v !== null && !Array.isArray(v) && looksLikeReport(v)) return
|
|
2164
|
+
if (typeof v === "object" && v !== null && !Array.isArray(v) && looksLikeReport(v)) return {
|
|
2165
|
+
...v,
|
|
2166
|
+
format: "json"
|
|
2167
|
+
};
|
|
1674
2168
|
} catch {}
|
|
1675
2169
|
return null;
|
|
1676
2170
|
}
|
|
@@ -1717,104 +2211,56 @@ function relShort(p, cwd) {
|
|
|
1717
2211
|
}
|
|
1718
2212
|
|
|
1719
2213
|
//#endregion
|
|
1720
|
-
//#region src/workers/
|
|
2214
|
+
//#region src/workers/project-config.ts
|
|
1721
2215
|
/**
|
|
1722
|
-
*
|
|
2216
|
+
* project-config.ts — the project registry's optional launch pin, read for
|
|
2217
|
+
* the interactive branch of run.ts only: which MCP servers and built-in
|
|
2218
|
+
* tools a project's supervisor session (`pai worker run`, no -p) loads. Set
|
|
2219
|
+
* with `pai project mcp` / `pai project tools` (src/cli/commands/project/
|
|
2220
|
+
* session-config.ts), stored in the same `projects.session_config` JSON
|
|
2221
|
+
* column as the rest of a project's launch config. Unset = today's
|
|
2222
|
+
* behavior: every server, every tool.
|
|
1723
2223
|
*
|
|
1724
|
-
*
|
|
1725
|
-
*
|
|
1726
|
-
* reads and edits files needs none of it. A run may opt in with `--mcp
|
|
1727
|
-
* name[,name…]`, a role carrying `"mcp": [...]`, or implicitly by naming
|
|
1728
|
-
* `mcp__server__tool` in --allowedTools (a grant without its server loaded is
|
|
1729
|
-
* a dead letter). Names may be single servers from ~/.claude.json's
|
|
1730
|
-
* `mcpServers` or `workers.mcpSets` set names, which expand to their member
|
|
1731
|
-
* list. The filtered config lands in
|
|
1732
|
-
* `<logDir>/<id>.mcp.json` and is passed with `--strict-mcp-config
|
|
1733
|
-
* --mcp-config` so exactly those servers load. MCP servers are chosen at
|
|
1734
|
-
* launch only — a mid-run `say` cannot add any.
|
|
2224
|
+
* A standalone SQL lookup rather than importing the CLI's detectProject:
|
|
2225
|
+
* workers/ is a dependency of cli/, not the other way around.
|
|
1735
2226
|
*/
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
if (!existsSync(claudeJson)) return {};
|
|
1741
|
-
const servers = JSON.parse(readFileSync(claudeJson, "utf8")).mcpServers;
|
|
1742
|
-
if (typeof servers !== "object" || servers === null || Array.isArray(servers)) return {};
|
|
1743
|
-
return servers;
|
|
1744
|
-
} catch {
|
|
1745
|
-
return {};
|
|
1746
|
-
}
|
|
2227
|
+
function asStringArray(v) {
|
|
2228
|
+
if (!Array.isArray(v)) return void 0;
|
|
2229
|
+
const out = v.filter((x) => typeof x === "string");
|
|
2230
|
+
return out.length ? out : void 0;
|
|
1747
2231
|
}
|
|
1748
2232
|
/**
|
|
1749
|
-
*
|
|
1750
|
-
*
|
|
2233
|
+
* The launch config of the project whose root_path is `cwd` or an ancestor
|
|
2234
|
+
* of it (longest match wins) — null when no registered project covers it,
|
|
2235
|
+
* or when the matching project has no mcp/tools pin set. `dbPath` is
|
|
2236
|
+
* injectable for tests; production callers take the default.
|
|
1751
2237
|
*/
|
|
1752
|
-
function
|
|
1753
|
-
const
|
|
1754
|
-
const
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
2238
|
+
function projectLaunchConfig(cwd, dbPath = registryDbPath()) {
|
|
2239
|
+
const target = resolve(cwd);
|
|
2240
|
+
const db = openRegistry(dbPath);
|
|
2241
|
+
try {
|
|
2242
|
+
const rows = db.prepare(`SELECT root_path, session_config FROM projects WHERE status != 'archived' ORDER BY LENGTH(root_path) DESC`).all();
|
|
2243
|
+
for (const row of rows) {
|
|
2244
|
+
const root = resolve(row.root_path);
|
|
2245
|
+
if (target !== root && !target.startsWith(root + "/")) continue;
|
|
2246
|
+
if (!row.session_config) return null;
|
|
2247
|
+
let parsed;
|
|
2248
|
+
try {
|
|
2249
|
+
parsed = JSON.parse(row.session_config);
|
|
2250
|
+
} catch {
|
|
2251
|
+
return null;
|
|
2252
|
+
}
|
|
2253
|
+
const mcp = asStringArray(parsed.mcp);
|
|
2254
|
+
const tools = asStringArray(parsed.tools);
|
|
2255
|
+
return mcp || tools ? {
|
|
2256
|
+
mcp,
|
|
2257
|
+
tools
|
|
2258
|
+
} : null;
|
|
1763
2259
|
}
|
|
1764
|
-
|
|
1765
|
-
}
|
|
1766
|
-
|
|
1767
|
-
}
|
|
1768
|
-
/**
|
|
1769
|
-
* Derive server names from tool grants: every `mcp__<server>__<tool>` (or bare
|
|
1770
|
-
* `mcp__<server>`, or `mcp__<server>__*`) in an --allowedTools list names a
|
|
1771
|
-
* server the run expects to be loaded. A grant is a dead letter unless its
|
|
1772
|
-
* server is in the filtered config, so the runner treats these as implicit
|
|
1773
|
-
* --mcp names — and only these; non-mcp grants load nothing.
|
|
1774
|
-
*/
|
|
1775
|
-
function mcpServersFromToolGrants(tools) {
|
|
1776
|
-
const out = [];
|
|
1777
|
-
for (const entry of tools) for (const name of entry.split(",").map((s) => s.trim()).filter(Boolean)) {
|
|
1778
|
-
if (!name.startsWith("mcp__")) continue;
|
|
1779
|
-
const server = name.slice(5).split("__")[0];
|
|
1780
|
-
if (server && !out.includes(server)) out.push(server);
|
|
1781
|
-
}
|
|
1782
|
-
return out;
|
|
1783
|
-
}
|
|
1784
|
-
/** Path of a run's filtered MCP config. */
|
|
1785
|
-
function runMcpConfigPath(logDir, id) {
|
|
1786
|
-
return join(logDir, `${id}.mcp.json`);
|
|
1787
|
-
}
|
|
1788
|
-
/**
|
|
1789
|
-
* Write a config containing only `names` (already expanded) and return its
|
|
1790
|
-
* path. Unknown names fail fast with the available list.
|
|
1791
|
-
*/
|
|
1792
|
-
function writeMcpConfig(logDir, id, names, claudeJson = CLAUDE_JSON) {
|
|
1793
|
-
const available = readMcpServers(claudeJson);
|
|
1794
|
-
const unknown = names.filter((n) => !(n in available));
|
|
1795
|
-
if (unknown.length) throw new WorkersConfigError(`unknown MCP server(s): ${unknown.join(", ")}. Available: ${Object.keys(available).join(", ") || "(none in ~/.claude.json)"}`);
|
|
1796
|
-
const servers = {};
|
|
1797
|
-
for (const n of names) servers[n] = available[n];
|
|
1798
|
-
const path = runMcpConfigPath(logDir, id);
|
|
1799
|
-
if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true });
|
|
1800
|
-
writeFileSync(path, JSON.stringify({ mcpServers: servers }, null, 2) + "\n", "utf8");
|
|
1801
|
-
return path;
|
|
1802
|
-
}
|
|
1803
|
-
/** `pai worker mcp list`: the servers and sets a run could name. */
|
|
1804
|
-
function describeMcp(config, claudeJson = CLAUDE_JSON) {
|
|
1805
|
-
const servers = Object.keys(readMcpServers(claudeJson));
|
|
1806
|
-
const lines = [];
|
|
1807
|
-
if (servers.length) {
|
|
1808
|
-
lines.push(`servers (~/.claude.json):`);
|
|
1809
|
-
for (const s of servers) lines.push(` ${s}`);
|
|
1810
|
-
} else lines.push(`no MCP servers defined in ~/.claude.json`);
|
|
1811
|
-
const sets = Object.entries(config.mcpSets);
|
|
1812
|
-
if (sets.length) {
|
|
1813
|
-
lines.push(`sets (workers.mcpSets):`);
|
|
1814
|
-
for (const [name, members] of sets) lines.push(` ${name} = ${members.join(", ")}`);
|
|
2260
|
+
return null;
|
|
2261
|
+
} finally {
|
|
2262
|
+
db.close();
|
|
1815
2263
|
}
|
|
1816
|
-
lines.push(`usage: pai worker run --mcp <name>[,<name>…] -p '<task>'`);
|
|
1817
|
-
return lines;
|
|
1818
2264
|
}
|
|
1819
2265
|
|
|
1820
2266
|
//#endregion
|
|
@@ -1855,7 +2301,7 @@ function buildCodexEnv(provider) {
|
|
|
1855
2301
|
delete env.ANTHROPIC_DEFAULT_SONNET_MODEL;
|
|
1856
2302
|
delete env.ANTHROPIC_DEFAULT_OPUS_MODEL;
|
|
1857
2303
|
const keyPath = providerKeyPath(provider);
|
|
1858
|
-
if (keyPath && existsSync(keyPath)) env.OPENAI_API_KEY =
|
|
2304
|
+
if (provider.key || keyPath && existsSync(keyPath)) env.OPENAI_API_KEY = resolveProviderKey(provider) ?? "";
|
|
1859
2305
|
if (provider.upstreamUrl) env.OPENAI_BASE_URL = provider.upstreamUrl;
|
|
1860
2306
|
for (const [k, v] of Object.entries(provider.env)) env[k] = v;
|
|
1861
2307
|
env.PAI_WORKER = "1";
|
|
@@ -2014,11 +2460,24 @@ function codexInstalled() {
|
|
|
2014
2460
|
* (context meter included), ledger lines, result printed in the caller's
|
|
2015
2461
|
* --output-format (json adds the parsed `report`), and the follow pane
|
|
2016
2462
|
* (unless --no-pane).
|
|
2017
|
-
* - interactive: no MCP restriction, no pane,
|
|
2463
|
+
* - interactive: no MCP/tool restriction by default, no pane,
|
|
2464
|
+
* ENABLE_TOOL_SEARCH=true. A project that pinned a list with `pai
|
|
2465
|
+
* project mcp` / `pai project tools` gets `--strict-mcp-config
|
|
2466
|
+
* --mcp-config <filtered>` / `--tools <list>` built the same way a
|
|
2467
|
+
* headless run's allowlist is; an explicit --mcp or --tools on this
|
|
2468
|
+
* command line still wins over the project's pin.
|
|
2018
2469
|
*
|
|
2019
2470
|
* Auto-routed runs that die of a quota error before the first tool call are
|
|
2020
2471
|
* restarted on the next provider in routing order (WORKER-REROUTE ledger line).
|
|
2021
2472
|
*/
|
|
2473
|
+
/**
|
|
2474
|
+
* ag2 unless the caller passed --report json or PAI_WORKER_REPORT=json is
|
|
2475
|
+
* set in the environment; an explicit flag always wins over the env default.
|
|
2476
|
+
*/
|
|
2477
|
+
function resolveReportFormat(flag, env = process.env) {
|
|
2478
|
+
if (flag) return flag;
|
|
2479
|
+
return env.PAI_WORKER_REPORT === "json" ? "json" : "ag2";
|
|
2480
|
+
}
|
|
2022
2481
|
/** A stream-json user message for the child's stdin. */
|
|
2023
2482
|
function stdinUserMessage(text) {
|
|
2024
2483
|
return JSON.stringify({
|
|
@@ -2077,6 +2536,81 @@ function resetContextTokensOnCompact(status, e) {
|
|
|
2077
2536
|
function isCompactBoundary(e) {
|
|
2078
2537
|
return e.type === "system" && (e.subtype === "compact_boundary" || e.subtype === "compact");
|
|
2079
2538
|
}
|
|
2539
|
+
/**
|
|
2540
|
+
* The model the init event announces, adopted into the status when the spawn
|
|
2541
|
+
* could not name one (a caller that passed `--model` itself in the claude
|
|
2542
|
+
* args, or a provider whose table has no entry for the capability). The run's
|
|
2543
|
+
* real model is then only knowable from the first event it sends. Never
|
|
2544
|
+
* overwrites an explicit model — a resolved or pinned model stays the
|
|
2545
|
+
* recorded truth.
|
|
2546
|
+
*/
|
|
2547
|
+
function adoptInitModel(status, e) {
|
|
2548
|
+
if (status.model) return;
|
|
2549
|
+
const m = (e.model ?? "").trim();
|
|
2550
|
+
if (m) status.model = m;
|
|
2551
|
+
}
|
|
2552
|
+
/**
|
|
2553
|
+
* The model a run goes out on: an explicit `--model` wins; else the class
|
|
2554
|
+
* target's alias ("glm/fast") names the capability; else the capability the
|
|
2555
|
+
* class implies (image → image model, spotcheck/simple → fast, everything
|
|
2556
|
+
* else → default), resolved against the provider's model table. The built-in
|
|
2557
|
+
* anthropic provider resolves through the same table as any other, so a
|
|
2558
|
+
* worker never inherits the orchestrator session's model.
|
|
2559
|
+
*/
|
|
2560
|
+
function resolveRunModel(target, className, modelFlag) {
|
|
2561
|
+
if (modelFlag) return modelFlag;
|
|
2562
|
+
const alias = target.modelAlias;
|
|
2563
|
+
const capability = alias && isModelCapability(alias) ? alias : classModelCapability(className);
|
|
2564
|
+
return resolveModelCapability(target.provider, capability);
|
|
2565
|
+
}
|
|
2566
|
+
/**
|
|
2567
|
+
* The `--model` part of the claude argv. Skipped when the caller already
|
|
2568
|
+
* pinned one in the claude args (it must not be clobbered) or when nothing
|
|
2569
|
+
* resolved — `--model ""` would break the spawn.
|
|
2570
|
+
*/
|
|
2571
|
+
function modelArgs(model, callerPinned) {
|
|
2572
|
+
return !callerPinned && model ? ["--model", model] : [];
|
|
2573
|
+
}
|
|
2574
|
+
/**
|
|
2575
|
+
* Whether the run's assembled cmd gets a forced --model. Headless workers
|
|
2576
|
+
* always take the resolved class/provider model — that table exists so
|
|
2577
|
+
* spawned subagents stay cheap. An interactive launch (`pai worker run` with
|
|
2578
|
+
* no -p) IS the chat pane, not a subagent: with no --model of the caller's
|
|
2579
|
+
* own it gets none here either, so Claude Code's own settings.json model
|
|
2580
|
+
* applies, exactly as launching `claude` by hand would (2026-09-20: an
|
|
2581
|
+
* interactive supervisor came up on the class model instead of the
|
|
2582
|
+
* settings.json [1m] variant, then needed a manual /model switch that
|
|
2583
|
+
* rebuilt the whole cache). A caller's own --model, headless or not, is
|
|
2584
|
+
* already in the passthrough args and must not be duplicated here.
|
|
2585
|
+
*/
|
|
2586
|
+
function modelFlagArgs(headless, model, callerPinned) {
|
|
2587
|
+
return headless ? modelArgs(model, callerPinned) : [];
|
|
2588
|
+
}
|
|
2589
|
+
/**
|
|
2590
|
+
* The prompt text actually sent to the worker and whether the trailer got
|
|
2591
|
+
* appended. An instruction at the END of the user turn outweighs one buried
|
|
2592
|
+
* in the system prompt (measured 2026-09-20: the AG2 contract alone was
|
|
2593
|
+
* ignored by both Haiku and Sonnet). Never applied to an interactive launch
|
|
2594
|
+
* (no -p) or when the caller brought its own --append-system-prompt — the
|
|
2595
|
+
* trailer's "format in system prompt" would then point at nothing.
|
|
2596
|
+
*/
|
|
2597
|
+
function headlessPromptText(prompt, headless, callerSystemPrompt, format) {
|
|
2598
|
+
const applied = headless && prompt !== null && !callerSystemPrompt;
|
|
2599
|
+
return {
|
|
2600
|
+
text: applied ? prompt + promptTrailer(format) : prompt,
|
|
2601
|
+
applied
|
|
2602
|
+
};
|
|
2603
|
+
}
|
|
2604
|
+
/**
|
|
2605
|
+
* Whether an invalid final report earns the one bounded re-ask: only when a
|
|
2606
|
+
* real validator caught the problem (a missing/broken aibroker must never
|
|
2607
|
+
* block the run further), the caller did not opt out, and there is a session
|
|
2608
|
+
* to resume into. Cap of exactly one is enforced by the caller never calling
|
|
2609
|
+
* this twice for the same run.
|
|
2610
|
+
*/
|
|
2611
|
+
function shouldRetryReport(validation, noReportRetry, hasSession) {
|
|
2612
|
+
return !validation.ok && validation.validator === "aibroker" && !noReportRetry && hasSession;
|
|
2613
|
+
}
|
|
2080
2614
|
/** Context window announced by the init event, when the endpoint sends one. */
|
|
2081
2615
|
function initContextWindow(e) {
|
|
2082
2616
|
if (typeof e.context_window === "number" && e.context_window > 0) return e.context_window;
|
|
@@ -2094,7 +2628,7 @@ async function runWorker(opts) {
|
|
|
2094
2628
|
const logDir = workersLogDir(config);
|
|
2095
2629
|
mkdirSync(logDir, { recursive: true });
|
|
2096
2630
|
if (opts.className === "plan" && !opts._planner) {
|
|
2097
|
-
const { runPlanner } = await import("./planner-
|
|
2631
|
+
const { runPlanner } = await import("./planner-BlKGgpWI.mjs");
|
|
2098
2632
|
return runPlanner(opts);
|
|
2099
2633
|
}
|
|
2100
2634
|
const parent = launchParent(opts.parent);
|
|
@@ -2106,9 +2640,8 @@ async function runWorker(opts) {
|
|
|
2106
2640
|
assertProviderRunnable(target.providerName, target.provider);
|
|
2107
2641
|
const parsed = parseRunnerArgs(opts.claudeArgs);
|
|
2108
2642
|
const label = opts.label ?? shortText(parsed.prompt ?? UNLABELED, 70);
|
|
2109
|
-
const
|
|
2110
|
-
const
|
|
2111
|
-
const model = opts.modelFlag ?? resolveModelCapability(target.provider, capability);
|
|
2643
|
+
const model = resolveRunModel(target, opts.className, opts.modelFlag);
|
|
2644
|
+
const reportFormat = resolveReportFormat(opts.reportFormatFlag);
|
|
2112
2645
|
try {
|
|
2113
2646
|
if (target.provider.engine === "codex") return await executeCodexRun({
|
|
2114
2647
|
config,
|
|
@@ -2120,13 +2653,15 @@ async function runWorker(opts) {
|
|
|
2120
2653
|
claudeArgs: opts.claudeArgs,
|
|
2121
2654
|
noPane: opts.noPane ?? false,
|
|
2122
2655
|
cwd: opts.cwd,
|
|
2656
|
+
specPath: opts.specPath,
|
|
2123
2657
|
parent: parent ?? void 0,
|
|
2124
2658
|
stage: opts.stage,
|
|
2125
2659
|
quiet: opts.quiet,
|
|
2126
2660
|
onWorkerStart: opts.onWorkerStart,
|
|
2127
2661
|
id: opts.id,
|
|
2128
2662
|
worktreeFlag: opts.worktreeFlag,
|
|
2129
|
-
className: opts.className
|
|
2663
|
+
className: opts.className,
|
|
2664
|
+
reportFormat
|
|
2130
2665
|
});
|
|
2131
2666
|
return await executeRun({
|
|
2132
2667
|
config,
|
|
@@ -2139,6 +2674,7 @@ async function runWorker(opts) {
|
|
|
2139
2674
|
noPane: opts.noPane ?? false,
|
|
2140
2675
|
mcpFlag: opts.mcpFlag,
|
|
2141
2676
|
cwd: opts.cwd,
|
|
2677
|
+
specPath: opts.specPath,
|
|
2142
2678
|
parent: parent ?? void 0,
|
|
2143
2679
|
stage: opts.stage,
|
|
2144
2680
|
quiet: opts.quiet,
|
|
@@ -2146,7 +2682,10 @@ async function runWorker(opts) {
|
|
|
2146
2682
|
id: opts.id,
|
|
2147
2683
|
worktreeFlag: opts.worktreeFlag,
|
|
2148
2684
|
className: opts.className,
|
|
2149
|
-
reroutes: opts._reroutes ?? 0
|
|
2685
|
+
reroutes: opts._reroutes ?? 0,
|
|
2686
|
+
printCmd: opts.printCmd,
|
|
2687
|
+
reportFormat,
|
|
2688
|
+
noReportRetry: opts.noReportRetry ?? false
|
|
2150
2689
|
});
|
|
2151
2690
|
} catch (e) {
|
|
2152
2691
|
if (e instanceof Error && e.message.startsWith("key file")) throw new Error(`provider "${target.providerName}": ${e.message}\nPut the token in that file (chmod 600) or point keyFile elsewhere.`);
|
|
@@ -2166,6 +2705,92 @@ const DEFAULT_WORKER_TOOLS = "Read,Edit,Write,Bash,Grep,Glob";
|
|
|
2166
2705
|
function headlessToolGrants(allowedTools) {
|
|
2167
2706
|
return allowedTools.length ? [] : ["--allowedTools", DEFAULT_WORKER_TOOLS];
|
|
2168
2707
|
}
|
|
2708
|
+
/**
|
|
2709
|
+
* `--tools` narrows which built-in tool *schemas* claude loads into the
|
|
2710
|
+
* system prompt — distinct from `--allowedTools`, which narrows which of
|
|
2711
|
+
* those loaded tools may run without asking. A headless worker with a small
|
|
2712
|
+
* grant (say, just Read) still pays for every built-in schema (Bash, Web*,
|
|
2713
|
+
* NotebookEdit, …) it never uses: ~9k tokens per spawn (29,270 -> 20,352,
|
|
2714
|
+
* measured 2026-09-20). Passing --tools built from the same grant removes
|
|
2715
|
+
* the unused schemas.
|
|
2716
|
+
*
|
|
2717
|
+
* MCP grants (`mcp__server__tool` / `mcp__server__*`) are left out of the
|
|
2718
|
+
* --tools list: they are not built-in tool names, --tools does not
|
|
2719
|
+
* recognize them, and the MCP server's tools load through --mcp-config
|
|
2720
|
+
* regardless. Tested 2026-09-20: with an --allowedTools grant of
|
|
2721
|
+
* `Read,mcp__pai__memory_search`, passing `--tools Read` (mcp name
|
|
2722
|
+
* omitted) still let the worker call memory_search successfully; the tool
|
|
2723
|
+
* loads from the filtered MCP config, not from --tools.
|
|
2724
|
+
*/
|
|
2725
|
+
function headlessToolsFlag(allowedTools) {
|
|
2726
|
+
const names = [];
|
|
2727
|
+
for (const entry of allowedTools) for (const raw of entry.split(",").map((s) => s.trim()).filter(Boolean)) {
|
|
2728
|
+
if (raw.startsWith("mcp__")) continue;
|
|
2729
|
+
const patterned = raw.match(/^([A-Za-z]+)\(.*\)$/);
|
|
2730
|
+
const name = patterned ? patterned[1] : raw;
|
|
2731
|
+
if (!names.includes(name)) names.push(name);
|
|
2732
|
+
}
|
|
2733
|
+
return names.length ? ["--tools", names.join(",")] : [];
|
|
2734
|
+
}
|
|
2735
|
+
/**
|
|
2736
|
+
* A `--tools` value with no ToolSearch silently disables MCP tool deferral
|
|
2737
|
+
* (measured 2026-09-20: a run with `--tools "Bash,Read,Grep,Glob,Agent"` paid
|
|
2738
|
+
* 65,300 first-turn tokens with 5 MCP servers, 230,448 with 16, versus
|
|
2739
|
+
* 24,889 / 37,133 with ToolSearch present). Applied to the fully-assembled
|
|
2740
|
+
* argv so it catches a `--tools` from any source — the computed
|
|
2741
|
+
* `headlessToolsFlag`/project-pin flag, or one the caller put directly on
|
|
2742
|
+
* the command line.
|
|
2743
|
+
*/
|
|
2744
|
+
function ensureToolSearch(cmd) {
|
|
2745
|
+
const flagIdx = cmd.findIndex((a) => a === "--tools");
|
|
2746
|
+
if (flagIdx >= 0 && flagIdx + 1 < cmd.length) {
|
|
2747
|
+
const value = cmd[flagIdx + 1];
|
|
2748
|
+
if (value && !value.split(",").includes("ToolSearch")) {
|
|
2749
|
+
const out = [...cmd];
|
|
2750
|
+
out[flagIdx + 1] = `${value},ToolSearch`;
|
|
2751
|
+
process.stderr.write("pai: added ToolSearch to --tools (keeps MCP tool deferral on)\n");
|
|
2752
|
+
return out;
|
|
2753
|
+
}
|
|
2754
|
+
return cmd;
|
|
2755
|
+
}
|
|
2756
|
+
const eqIdx = cmd.findIndex((a) => a.startsWith("--tools="));
|
|
2757
|
+
if (eqIdx >= 0) {
|
|
2758
|
+
const value = cmd[eqIdx].slice(8);
|
|
2759
|
+
if (value && !value.split(",").includes("ToolSearch")) {
|
|
2760
|
+
const out = [...cmd];
|
|
2761
|
+
out[eqIdx] = `--tools=${value},ToolSearch`;
|
|
2762
|
+
process.stderr.write("pai: added ToolSearch to --tools (keeps MCP tool deferral on)\n");
|
|
2763
|
+
return out;
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
return cmd;
|
|
2767
|
+
}
|
|
2768
|
+
/**
|
|
2769
|
+
* The claude flag that turns on the browser bridge, when the run asked for it.
|
|
2770
|
+
*
|
|
2771
|
+
* The bridge is not an MCP server — it rides the Chrome native-host channel,
|
|
2772
|
+
* is absent from `mcpServers`, and is off in a spawned claude until `--chrome`
|
|
2773
|
+
* is passed. A grant such as `mcp__claude-in-chrome__tabs_context_mcp` is
|
|
2774
|
+
* therefore a request for that flag, not for a server to load; without this
|
|
2775
|
+
* the grant names a tool that never exists. `rest` is the caller's own argv:
|
|
2776
|
+
* a `--chrome` they passed themselves is kept rather than duplicated.
|
|
2777
|
+
*/
|
|
2778
|
+
function chromeGrantArgs(wanted, rest = []) {
|
|
2779
|
+
if (rest.includes("--chrome")) return [];
|
|
2780
|
+
return grantsChrome(wanted) ? ["--chrome"] : [];
|
|
2781
|
+
}
|
|
2782
|
+
/**
|
|
2783
|
+
* Which MCP server names and --tools list an interactive launch (`pai
|
|
2784
|
+
* worker run`, no -p) uses: an explicit --mcp on this command line beats
|
|
2785
|
+
* the project's `pai project mcp` pin; an explicit --tools beats `pai
|
|
2786
|
+
* project tools`. Neither given: [] — today's "everything loads" default.
|
|
2787
|
+
*/
|
|
2788
|
+
function interactiveMcpTools(explicitMcp, callerTools, projectLaunch) {
|
|
2789
|
+
return {
|
|
2790
|
+
mcpNames: explicitMcp.length ? explicitMcp : projectLaunch?.mcp ?? [],
|
|
2791
|
+
tools: !callerTools && projectLaunch?.tools?.length ? projectLaunch.tools : []
|
|
2792
|
+
};
|
|
2793
|
+
}
|
|
2169
2794
|
async function executeRun(a) {
|
|
2170
2795
|
const { config, logDir, target, model, label, parsed, noPane } = a;
|
|
2171
2796
|
const headless = parsed.headless;
|
|
@@ -2178,7 +2803,7 @@ async function executeRun(a) {
|
|
|
2178
2803
|
const session = resolveSession(term);
|
|
2179
2804
|
const spawnerSession = resolveSpawnerSession(logDir, cwd);
|
|
2180
2805
|
let worktree = null;
|
|
2181
|
-
if (headless && worktreeWanted(a.worktreeFlag, {
|
|
2806
|
+
if (headless && !a.printCmd && worktreeWanted(a.worktreeFlag, {
|
|
2182
2807
|
cwd,
|
|
2183
2808
|
className: a.className,
|
|
2184
2809
|
prompt: parsed.prompt
|
|
@@ -2193,6 +2818,61 @@ async function executeRun(a) {
|
|
|
2193
2818
|
});
|
|
2194
2819
|
}
|
|
2195
2820
|
env.PAI_WORKER_ID = wid;
|
|
2821
|
+
const chromeArgs = chromeGrantArgs([
|
|
2822
|
+
...a.mcpFlag ? [a.mcpFlag] : [],
|
|
2823
|
+
...parsed.mcp,
|
|
2824
|
+
...target.classMcp ?? [],
|
|
2825
|
+
...parsed.allowedTools
|
|
2826
|
+
], parsed.rest);
|
|
2827
|
+
let mcpArgs = [];
|
|
2828
|
+
let toolsFlag = headless ? headlessToolsFlag(parsed.allowedTools) : [];
|
|
2829
|
+
if (headless && !parsed.callerMcpConfig) {
|
|
2830
|
+
const wanted = [
|
|
2831
|
+
...a.mcpFlag ? [a.mcpFlag] : [],
|
|
2832
|
+
...parsed.mcp,
|
|
2833
|
+
...target.classMcp ?? [],
|
|
2834
|
+
...mcpServersFromToolGrants(parsed.allowedTools)
|
|
2835
|
+
];
|
|
2836
|
+
if (wanted.length) mcpArgs = [
|
|
2837
|
+
"--strict-mcp-config",
|
|
2838
|
+
"--mcp-config",
|
|
2839
|
+
writeMcpConfig(logDir, wid, expandMcpNames(wanted, config))
|
|
2840
|
+
];
|
|
2841
|
+
else mcpArgs = [
|
|
2842
|
+
"--strict-mcp-config",
|
|
2843
|
+
"--mcp-config",
|
|
2844
|
+
ensureNoMcpConfig(logDir)
|
|
2845
|
+
];
|
|
2846
|
+
} else if (!headless && !parsed.callerMcpConfig) {
|
|
2847
|
+
const explicitMcp = [...a.mcpFlag ? [a.mcpFlag] : [], ...parsed.mcp];
|
|
2848
|
+
const projectLaunch = projectLaunchConfig(cwd);
|
|
2849
|
+
const picked = interactiveMcpTools(explicitMcp, parsed.callerTools, projectLaunch);
|
|
2850
|
+
if (picked.mcpNames.length) mcpArgs = [
|
|
2851
|
+
"--strict-mcp-config",
|
|
2852
|
+
"--mcp-config",
|
|
2853
|
+
writeMcpConfig(logDir, wid, expandMcpNames(picked.mcpNames, config))
|
|
2854
|
+
];
|
|
2855
|
+
if (picked.tools.length) toolsFlag = ["--tools", picked.tools.join(",")];
|
|
2856
|
+
}
|
|
2857
|
+
const restArgs = headless ? stripPromptValues(parsed.rest) : parsed.rest;
|
|
2858
|
+
const toolArgs = headless ? headlessToolGrants(parsed.allowedTools) : [];
|
|
2859
|
+
const { text: promptText, applied: trailerApplied } = headlessPromptText(parsed.prompt, headless, parsed.callerSystemPrompt, a.reportFormat);
|
|
2860
|
+
let cmd = ["claude"];
|
|
2861
|
+
cmd.push(...modelFlagArgs(headless, model, Boolean(parsed.callerModel)));
|
|
2862
|
+
cmd.push(...chromeArgs, ...mcpArgs, ...toolArgs, ...toolsFlag, ...restArgs);
|
|
2863
|
+
if (headless) {
|
|
2864
|
+
cmd.push("--output-format", "stream-json", "--verbose", "--input-format", "stream-json");
|
|
2865
|
+
if (!parsed.callerSystemPrompt) cmd.push("--append-system-prompt", workerContractPrompt(a.className, a.reportFormat));
|
|
2866
|
+
if (worktree) cmd.push("--append-system-prompt", worktreeSystemPrompt(wid, worktree.branch, worktree.dir));
|
|
2867
|
+
}
|
|
2868
|
+
cmd = ensureToolSearch(cmd);
|
|
2869
|
+
if (a.printCmd) {
|
|
2870
|
+
console.log(JSON.stringify({
|
|
2871
|
+
cmd,
|
|
2872
|
+
cwd: worktree?.dir ?? cwd
|
|
2873
|
+
}));
|
|
2874
|
+
return 0;
|
|
2875
|
+
}
|
|
2196
2876
|
const status = {
|
|
2197
2877
|
id: wid,
|
|
2198
2878
|
pid: process.pid,
|
|
@@ -2210,6 +2890,9 @@ async function executeRun(a) {
|
|
|
2210
2890
|
rc: null,
|
|
2211
2891
|
secs: null,
|
|
2212
2892
|
origin: headless ? "spawn" : "chat",
|
|
2893
|
+
outputFormat: parsed.outputFormat,
|
|
2894
|
+
...headless ? { reportFormat: a.reportFormat } : {},
|
|
2895
|
+
...trailerApplied ? { promptTrailer: true } : {},
|
|
2213
2896
|
...session ? { session } : {},
|
|
2214
2897
|
...spawnerSession ? { spawnerSession } : {},
|
|
2215
2898
|
...a.parent ? {
|
|
@@ -2220,7 +2903,8 @@ async function executeRun(a) {
|
|
|
2220
2903
|
worktreeDir: worktree.dir,
|
|
2221
2904
|
branch: worktree.branch,
|
|
2222
2905
|
worktreeBase: worktree.base
|
|
2223
|
-
} : {}
|
|
2906
|
+
} : {},
|
|
2907
|
+
...a.specPath ? { spec: a.specPath } : {}
|
|
2224
2908
|
};
|
|
2225
2909
|
saveStatus(logDir, status);
|
|
2226
2910
|
a.onWorkerStart?.(wid);
|
|
@@ -2231,38 +2915,10 @@ async function executeRun(a) {
|
|
|
2231
2915
|
mode: headless ? "headless" : "interactive",
|
|
2232
2916
|
model,
|
|
2233
2917
|
cwd,
|
|
2234
|
-
label
|
|
2918
|
+
label,
|
|
2919
|
+
...a.specPath ? { spec: a.specPath } : {}
|
|
2235
2920
|
});
|
|
2236
2921
|
if (headless && !noPane && config.pane.enabled && term && process.env.PAI_WORKER_AUTOPANE !== "0") openPaneForWorker(logDir, config, wid, term).catch(() => {});
|
|
2237
|
-
let mcpArgs = [];
|
|
2238
|
-
if (headless && !parsed.callerMcpConfig) {
|
|
2239
|
-
const wanted = [
|
|
2240
|
-
...a.mcpFlag ? [a.mcpFlag] : [],
|
|
2241
|
-
...parsed.mcp,
|
|
2242
|
-
...target.classMcp ?? [],
|
|
2243
|
-
...mcpServersFromToolGrants(parsed.allowedTools)
|
|
2244
|
-
];
|
|
2245
|
-
if (wanted.length) mcpArgs = [
|
|
2246
|
-
"--strict-mcp-config",
|
|
2247
|
-
"--mcp-config",
|
|
2248
|
-
writeMcpConfig(logDir, wid, expandMcpNames(wanted, config))
|
|
2249
|
-
];
|
|
2250
|
-
else mcpArgs = [
|
|
2251
|
-
"--strict-mcp-config",
|
|
2252
|
-
"--mcp-config",
|
|
2253
|
-
ensureNoMcpConfig(logDir)
|
|
2254
|
-
];
|
|
2255
|
-
}
|
|
2256
|
-
const restArgs = headless ? stripPromptValues(parsed.rest) : parsed.rest;
|
|
2257
|
-
const toolArgs = headless ? headlessToolGrants(parsed.allowedTools) : [];
|
|
2258
|
-
const cmd = ["claude"];
|
|
2259
|
-
if (!parsed.callerModel && model) cmd.push("--model", model);
|
|
2260
|
-
cmd.push(...mcpArgs, ...toolArgs, ...restArgs);
|
|
2261
|
-
if (headless) {
|
|
2262
|
-
cmd.push("--output-format", "stream-json", "--verbose", "--input-format", "stream-json");
|
|
2263
|
-
if (!parsed.callerSystemPrompt) cmd.push("--append-system-prompt", WORKER_CONTRACT_PROMPT);
|
|
2264
|
-
if (worktree) cmd.push("--append-system-prompt", worktreeSystemPrompt(wid, worktree.branch, worktree.dir));
|
|
2265
|
-
}
|
|
2266
2922
|
const t0 = Date.now();
|
|
2267
2923
|
const proc = spawn(cmd[0], cmd.slice(1), {
|
|
2268
2924
|
env,
|
|
@@ -2347,8 +3003,8 @@ async function executeRun(a) {
|
|
|
2347
3003
|
resultReport: null
|
|
2348
3004
|
};
|
|
2349
3005
|
if (headless) {
|
|
2350
|
-
if (
|
|
2351
|
-
proc.stdin.write(stdinUserMessage(
|
|
3006
|
+
if (promptText !== null) try {
|
|
3007
|
+
proc.stdin.write(stdinUserMessage(promptText) + "\n");
|
|
2352
3008
|
} catch {}
|
|
2353
3009
|
eventsFd = openSync(eventsPath(logDir, wid), "a");
|
|
2354
3010
|
createInterface({ input: proc.stdout }).on("line", (line) => {
|
|
@@ -2363,6 +3019,7 @@ async function executeRun(a) {
|
|
|
2363
3019
|
writeEvent(e);
|
|
2364
3020
|
if (e.type === "system" && e.subtype === "init") {
|
|
2365
3021
|
if (e.session_id) status.claudeSession = e.session_id;
|
|
3022
|
+
adoptInitModel(status, e);
|
|
2366
3023
|
const cw = initContextWindow(e);
|
|
2367
3024
|
if (cw) status.contextWindow = cw;
|
|
2368
3025
|
saveStatus(logDir, status);
|
|
@@ -2406,6 +3063,40 @@ async function executeRun(a) {
|
|
|
2406
3063
|
status.secs = secs;
|
|
2407
3064
|
if (resultEvent) status.last = shortText(resultEvent.result ?? "", 90);
|
|
2408
3065
|
if (ctx.resultReport?.notes) status.last = shortText(ctx.resultReport.notes, 90);
|
|
3066
|
+
if (headless && a.reportFormat === "ag2" && resultEvent?.result) {
|
|
3067
|
+
const v = validateAg2(resultEvent.result);
|
|
3068
|
+
status.reportValid = v.ok;
|
|
3069
|
+
status.reportErrors = v.errors;
|
|
3070
|
+
if (shouldRetryReport(v, a.noReportRetry ?? false, Boolean(status.claudeSession))) {
|
|
3071
|
+
status.reportRetried = true;
|
|
3072
|
+
saveStatus(logDir, status);
|
|
3073
|
+
appendLedger(ledger, "WORKER-REPORT-RETRY", {
|
|
3074
|
+
id: wid,
|
|
3075
|
+
reason: v.errors.join("; ") || "invalid AG2 message"
|
|
3076
|
+
});
|
|
3077
|
+
try {
|
|
3078
|
+
const reaskText = await reaskAg2Report({
|
|
3079
|
+
env,
|
|
3080
|
+
cwd: worktree?.dir ?? cwd,
|
|
3081
|
+
model,
|
|
3082
|
+
callerPinnedModel: Boolean(parsed.callerModel),
|
|
3083
|
+
chromeArgs,
|
|
3084
|
+
mcpArgs,
|
|
3085
|
+
toolArgs,
|
|
3086
|
+
toolsFlag,
|
|
3087
|
+
sessionId: status.claudeSession
|
|
3088
|
+
});
|
|
3089
|
+
const v2 = validateAg2(reaskText);
|
|
3090
|
+
status.reportValid = v2.ok;
|
|
3091
|
+
status.reportErrors = v2.errors;
|
|
3092
|
+
if (v2.ok) {
|
|
3093
|
+
resultEvent.result = reaskText;
|
|
3094
|
+
ctx.resultReport = parseWorkerReport(reaskText);
|
|
3095
|
+
if (ctx.resultReport?.notes) status.last = shortText(ctx.resultReport.notes, 90);
|
|
3096
|
+
}
|
|
3097
|
+
} catch {}
|
|
3098
|
+
}
|
|
3099
|
+
}
|
|
2409
3100
|
saveStatus(logDir, status);
|
|
2410
3101
|
appendLedger(ledger, "WORKER-END", {
|
|
2411
3102
|
id: wid,
|
|
@@ -2436,6 +3127,7 @@ async function executeRun(a) {
|
|
|
2436
3127
|
noPane: a.noPane,
|
|
2437
3128
|
mcpFlag: a.mcpFlag,
|
|
2438
3129
|
cwd: a.cwd,
|
|
3130
|
+
specPath: a.specPath,
|
|
2439
3131
|
parent: a.parent,
|
|
2440
3132
|
stage: a.stage,
|
|
2441
3133
|
claudeArgs: a.claudeArgs,
|
|
@@ -2473,6 +3165,44 @@ function worktreeExtras(s) {
|
|
|
2473
3165
|
commits: s.commits ?? 0
|
|
2474
3166
|
} : void 0;
|
|
2475
3167
|
}
|
|
3168
|
+
/**
|
|
3169
|
+
* The one bounded re-ask, cap exactly one: `claude --resume <session>` with
|
|
3170
|
+
* AG2_REASK_TEXT, single-shot `--output-format json` (one turn, no streaming
|
|
3171
|
+
* plumbing needed). Same env/model/mcp/tool flags as the original launch —
|
|
3172
|
+
* only --resume and the fixed re-ask prompt are new — so the resumed turn
|
|
3173
|
+
* sees the same grants the worker ran with.
|
|
3174
|
+
*/
|
|
3175
|
+
async function reaskAg2Report(a) {
|
|
3176
|
+
let cmd = ["claude"];
|
|
3177
|
+
cmd.push(...modelFlagArgs(true, a.model, a.callerPinnedModel));
|
|
3178
|
+
cmd.push(...a.chromeArgs, ...a.mcpArgs, ...a.toolArgs, ...a.toolsFlag);
|
|
3179
|
+
cmd.push("--resume", a.sessionId, "-p", AG2_REASK_TEXT, "--output-format", "json");
|
|
3180
|
+
cmd = ensureToolSearch(cmd);
|
|
3181
|
+
const proc = spawn(cmd[0], cmd.slice(1), {
|
|
3182
|
+
env: a.env,
|
|
3183
|
+
cwd: a.cwd,
|
|
3184
|
+
stdio: [
|
|
3185
|
+
"ignore",
|
|
3186
|
+
"pipe",
|
|
3187
|
+
"inherit"
|
|
3188
|
+
]
|
|
3189
|
+
});
|
|
3190
|
+
const timer = setTimeout(() => {
|
|
3191
|
+
try {
|
|
3192
|
+
proc.kill("SIGKILL");
|
|
3193
|
+
} catch {}
|
|
3194
|
+
}, a.timeoutMs ?? 6e4);
|
|
3195
|
+
let out = "";
|
|
3196
|
+
proc.stdout.on("data", (chunk) => {
|
|
3197
|
+
out += chunk.toString("utf8");
|
|
3198
|
+
});
|
|
3199
|
+
await new Promise((resolve) => {
|
|
3200
|
+
proc.on("error", () => resolve());
|
|
3201
|
+
proc.on("close", () => resolve());
|
|
3202
|
+
});
|
|
3203
|
+
clearTimeout(timer);
|
|
3204
|
+
return resultFromOutput(out);
|
|
3205
|
+
}
|
|
2476
3206
|
async function executeCodexRun(a) {
|
|
2477
3207
|
const { config, logDir, target, model, label, parsed, noPane } = a;
|
|
2478
3208
|
if (!parsed.headless || parsed.prompt === null) throw new Error(`provider "${target.providerName}" (engine codex) supports headless runs only: pass the task with -p '<prompt>'`);
|
|
@@ -2516,6 +3246,8 @@ async function executeCodexRun(a) {
|
|
|
2516
3246
|
rc: null,
|
|
2517
3247
|
secs: null,
|
|
2518
3248
|
origin: "spawn",
|
|
3249
|
+
outputFormat: parsed.outputFormat,
|
|
3250
|
+
reportFormat: "json",
|
|
2519
3251
|
...session ? { session } : {},
|
|
2520
3252
|
...spawnerSession ? { spawnerSession } : {},
|
|
2521
3253
|
...target.provider.contextWindow ? { contextWindow: target.provider.contextWindow } : {},
|
|
@@ -2527,7 +3259,8 @@ async function executeCodexRun(a) {
|
|
|
2527
3259
|
worktreeDir: worktree.dir,
|
|
2528
3260
|
branch: worktree.branch,
|
|
2529
3261
|
worktreeBase: worktree.base
|
|
2530
|
-
} : {}
|
|
3262
|
+
} : {},
|
|
3263
|
+
...a.specPath ? { spec: a.specPath } : {}
|
|
2531
3264
|
};
|
|
2532
3265
|
saveStatus(logDir, status);
|
|
2533
3266
|
a.onWorkerStart?.(wid);
|
|
@@ -2793,5 +3526,5 @@ function resultFromOutput(out) {
|
|
|
2793
3526
|
}
|
|
2794
3527
|
|
|
2795
3528
|
//#endregion
|
|
2796
|
-
export {
|
|
2797
|
-
//# sourceMappingURL=run-
|
|
3529
|
+
export { loadStatuses as A, expandMcpNames as B, UNLABELED as C, contextPercent as D, contextLabel as E, waitForTerminalStatus as F, longInlinePromptHint as I, parseRunnerArgs as L, nowStamp as M, saveStatus as N, isChatPane as O, setWorkerLabel as P, shortText as R, ledgerSummary as S, alive as T, itermForClaudeSession as _, parseWorkerReport as a, workerInScope as b, mergeWorker as c, sayToWorker as d, workerDepth as f, currentTabKey as g, openPaneForWorker as h, usageContextTokens as i, newWorkerId as j, loadStatus as k, handoffFromInside as l, openFollowPane as m, runWorker as n, renderReport as o, checkPaneForWorker as p, testProvider as r, discardWorker as s, printResult as t, readInbox as u, resolveSession as v, ageOf as w, appendLedger as x, sessionTag as y, describeMcp as z };
|
|
3530
|
+
//# sourceMappingURL=run-ByBKowN5.mjs.map
|