@tekmidian/pai 0.42.4 → 0.42.6

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.
Files changed (292) hide show
  1. package/ARCHITECTURE.md +9 -9
  2. package/README.md +26 -8
  3. package/dist/{aibroker-client-BP74gGwD.mjs → aibroker-client-Bn9SnGMa.mjs} +3 -3
  4. package/dist/{aibroker-client-BP74gGwD.mjs.map → aibroker-client-Bn9SnGMa.mjs.map} +1 -1
  5. package/dist/{aibroker-client-C6m71p6O.mjs → aibroker-client-D5NT0S1N.mjs} +1 -1
  6. package/dist/{auto-route-_5zzJz5q.mjs → auto-route-CcD0OWWB.mjs} +4 -4
  7. package/dist/{auto-route-_5zzJz5q.mjs.map → auto-route-CcD0OWWB.mjs.map} +1 -1
  8. package/dist/{chain-DTII7xeL.mjs → chain-BbcfP77K.mjs} +5 -4
  9. package/dist/{chain-DTII7xeL.mjs.map → chain-BbcfP77K.mjs.map} +1 -1
  10. package/dist/cli/index.mjs +23 -21
  11. package/dist/cli/index.mjs.map +1 -1
  12. package/dist/cli/program.d.mts.map +1 -1
  13. package/dist/cli/program.mjs +23 -21
  14. package/dist/{clusters-j0ZuNYp9.mjs → clusters-Cw_P6lO-.mjs} +2 -2
  15. package/dist/{clusters-j0ZuNYp9.mjs.map → clusters-Cw_P6lO-.mjs.map} +1 -1
  16. package/dist/{config-B64vFg14.mjs → config-DCu8THKi.mjs} +41 -9
  17. package/dist/{config-B64vFg14.mjs.map → config-DCu8THKi.mjs.map} +1 -1
  18. package/dist/config-DeqFqa1N.mjs +3 -0
  19. package/dist/config-DxMZjpvY.mjs +1289 -0
  20. package/dist/config-DxMZjpvY.mjs.map +1 -0
  21. package/dist/context-handover-cache-B9NKSjwR.mjs +3242 -0
  22. package/dist/context-handover-cache-B9NKSjwR.mjs.map +1 -0
  23. package/dist/daemon/index.mjs +23 -22
  24. package/dist/daemon/index.mjs.map +1 -1
  25. package/dist/daemon-BNjbVsQU.mjs +24 -0
  26. package/dist/{daemon-B8N1egl0.mjs → daemon-BppziPi6.mjs} +52 -38
  27. package/dist/daemon-BppziPi6.mjs.map +1 -0
  28. package/dist/daemon-mcp/index.mjs +144 -117
  29. package/dist/daemon-mcp/index.mjs.map +1 -1
  30. package/dist/db-BOBfewFH.mjs +3 -0
  31. package/dist/{db-a1ixZQjr.mjs → db-DJ8rk0VM.mjs} +15 -6
  32. package/dist/{db-a1ixZQjr.mjs.map → db-DJ8rk0VM.mjs.map} +1 -1
  33. package/dist/db-DbY2O8QO.mjs +3 -0
  34. package/dist/{db-Ca5qfsMC.mjs → db-Ddns80yf.mjs} +15 -6
  35. package/dist/{db-Ca5qfsMC.mjs.map → db-Ddns80yf.mjs.map} +1 -1
  36. package/dist/{detect-CdaA48EI.mjs → detect-Bf2z-oKB.mjs} +1 -1
  37. package/dist/{detect-CdaA48EI.mjs.map → detect-Bf2z-oKB.mjs.map} +1 -1
  38. package/dist/detector-CkwkXuGh.mjs +5 -0
  39. package/dist/{detector-C73Nxm94.mjs → detector-DWXkeTlk.mjs} +2 -2
  40. package/dist/{detector-C73Nxm94.mjs.map → detector-DWXkeTlk.mjs.map} +1 -1
  41. package/dist/{embeddings-CEBGrzwu.mjs → embeddings-ClqKtLZ8.mjs} +1 -1
  42. package/dist/{embeddings-DOLZnT1X.mjs → embeddings-DvRKcQyt.mjs} +1 -1
  43. package/dist/{embeddings-DOLZnT1X.mjs.map → embeddings-DvRKcQyt.mjs.map} +1 -1
  44. package/dist/factory-47Qf_df7.mjs +3 -0
  45. package/dist/{factory-DEb9NmHJ.mjs → factory-DNYAqjH0.mjs} +8 -8
  46. package/dist/{factory-DEb9NmHJ.mjs.map → factory-DNYAqjH0.mjs.map} +1 -1
  47. package/dist/{fallback-oJl3BaIN.mjs → fallback-Cwt4hmh5.mjs} +182 -122
  48. package/dist/fallback-Cwt4hmh5.mjs.map +1 -0
  49. package/dist/{helpers-IjZkXBhj.mjs → helpers-crDEr6S2.mjs} +1 -1
  50. package/dist/{helpers-IjZkXBhj.mjs.map → helpers-crDEr6S2.mjs.map} +1 -1
  51. package/dist/hooks/capture-all-events.mjs +117 -34
  52. package/dist/hooks/capture-all-events.mjs.map +4 -4
  53. package/dist/hooks/capture-session-summary.mjs +110 -33
  54. package/dist/hooks/capture-session-summary.mjs.map +4 -4
  55. package/dist/hooks/capture-tool-output.mjs +105 -28
  56. package/dist/hooks/capture-tool-output.mjs.map +4 -4
  57. package/dist/hooks/cleanup-session-files.mjs +54 -13
  58. package/dist/hooks/cleanup-session-files.mjs.map +2 -2
  59. package/dist/hooks/context-compression-hook.mjs +7565 -122
  60. package/dist/hooks/context-compression-hook.mjs.map +4 -4
  61. package/dist/hooks/initialize-session.mjs +55 -14
  62. package/dist/hooks/initialize-session.mjs.map +2 -2
  63. package/dist/hooks/inject-observations.mjs +79 -18
  64. package/dist/hooks/inject-observations.mjs.map +3 -3
  65. package/dist/hooks/load-core-context.mjs +55 -14
  66. package/dist/hooks/load-core-context.mjs.map +2 -2
  67. package/dist/hooks/load-project-context.mjs +185 -103
  68. package/dist/hooks/load-project-context.mjs.map +4 -4
  69. package/dist/hooks/mcp-deferred-gate.mjs +54 -13
  70. package/dist/hooks/mcp-deferred-gate.mjs.map +2 -2
  71. package/dist/hooks/mcp-deferred-reminder.mjs +9 -0
  72. package/dist/hooks/mcp-deferred-reminder.mjs.map +3 -3
  73. package/dist/hooks/observe.mjs +54 -13
  74. package/dist/hooks/observe.mjs.map +2 -2
  75. package/dist/hooks/post-compact-inject.mjs +7419 -16
  76. package/dist/hooks/post-compact-inject.mjs.map +4 -4
  77. package/dist/hooks/route-agents-to-worker.mjs +7778 -171
  78. package/dist/hooks/route-agents-to-worker.mjs.map +4 -4
  79. package/dist/hooks/route-edits-to-worker.mjs +90 -0
  80. package/dist/hooks/route-edits-to-worker.mjs.map +7 -0
  81. package/dist/hooks/security-validator.mjs +147 -6
  82. package/dist/hooks/security-validator.mjs.map +4 -4
  83. package/dist/hooks/session-commands.mjs +9 -0
  84. package/dist/hooks/session-commands.mjs.map +3 -3
  85. package/dist/hooks/stop-hook.mjs +182 -105
  86. package/dist/hooks/stop-hook.mjs.map +4 -4
  87. package/dist/hooks/subagent-stop-hook.mjs +3 -0
  88. package/dist/hooks/subagent-stop-hook.mjs.map +1 -1
  89. package/dist/hooks/sync-todo-to-md.mjs +54 -13
  90. package/dist/hooks/sync-todo-to-md.mjs.map +2 -2
  91. package/dist/hooks/update-tab-on-action.mjs +3 -0
  92. package/dist/hooks/update-tab-on-action.mjs.map +1 -1
  93. package/dist/hooks/update-tab-titles.mjs +3 -0
  94. package/dist/hooks/update-tab-titles.mjs.map +1 -1
  95. package/dist/hooks/whisper-reinject.mjs +3 -0
  96. package/dist/hooks/whisper-reinject.mjs.map +1 -1
  97. package/dist/hooks/whisper-rules.mjs +134 -29
  98. package/dist/hooks/whisper-rules.mjs.map +4 -4
  99. package/dist/hooks/worker-proxy.mjs +7737 -117
  100. package/dist/hooks/worker-proxy.mjs.map +4 -4
  101. package/dist/hooks/worker-status-line.mjs +7791 -144
  102. package/dist/hooks/worker-status-line.mjs.map +4 -4
  103. package/dist/hooks/worker-supervision.mjs +7721 -115
  104. package/dist/hooks/worker-supervision.mjs.map +4 -4
  105. package/dist/index.d.mts +4 -2
  106. package/dist/index.d.mts.map +1 -1
  107. package/dist/index.mjs +10 -10
  108. package/dist/{indexer-backend-BPTzKFZU.mjs → indexer-backend-CdvCJCkD.mjs} +3 -3
  109. package/dist/{indexer-backend-BPTzKFZU.mjs.map → indexer-backend-CdvCJCkD.mjs.map} +1 -1
  110. package/dist/{ipc-client-BmypMNYk.mjs → ipc-client-C6NryMMb.mjs} +2 -2
  111. package/dist/{ipc-client-BmypMNYk.mjs.map → ipc-client-C6NryMMb.mjs.map} +1 -1
  112. package/dist/{kg-entity-r8duqhi9.mjs → kg-entity-BSjM5xZh.mjs} +1 -1
  113. package/dist/{kg-entity-r8duqhi9.mjs.map → kg-entity-BSjM5xZh.mjs.map} +1 -1
  114. package/dist/{latent-ideas-CtSca4qG.mjs → latent-ideas-BfL4nCQ3.mjs} +4 -4
  115. package/dist/{latent-ideas-CtSca4qG.mjs.map → latent-ideas-BfL4nCQ3.mjs.map} +1 -1
  116. package/dist/{link-boost-Bucnz3n8.mjs → link-boost-C5PaNdzm.mjs} +1 -1
  117. package/dist/{link-boost-Bucnz3n8.mjs.map → link-boost-C5PaNdzm.mjs.map} +1 -1
  118. package/dist/main-resolver-CnfnWa8C.mjs +7 -0
  119. package/dist/{main-resolver-DnkxPYkr.mjs → main-resolver-DvYu9jbl.mjs} +21 -11
  120. package/dist/main-resolver-DvYu9jbl.mjs.map +1 -0
  121. package/dist/{migrate-Cjzeefn9.mjs → migrate-CfERwXhY.mjs} +2 -2
  122. package/dist/{migrate-Cjzeefn9.mjs.map → migrate-CfERwXhY.mjs.map} +1 -1
  123. package/dist/{neighborhood-CM7Jgb51.mjs → neighborhood-CETR_P4y.mjs} +2 -2
  124. package/dist/{neighborhood-CM7Jgb51.mjs.map → neighborhood-CETR_P4y.mjs.map} +1 -1
  125. package/dist/{note-context-DCwE2A-M.mjs → note-context-DCcRWBoN.mjs} +1 -1
  126. package/dist/{note-context-DCwE2A-M.mjs.map → note-context-DCcRWBoN.mjs.map} +1 -1
  127. package/dist/pai-home-ChAOupZc.mjs +164 -0
  128. package/dist/pai-home-ChAOupZc.mjs.map +1 -0
  129. package/dist/{pai-marker-CHtbJMwJ.mjs → pai-marker-B20KqhA8.mjs} +1 -1
  130. package/dist/{pai-marker-CHtbJMwJ.mjs.map → pai-marker-B20KqhA8.mjs.map} +1 -1
  131. package/dist/{planner-D5Ui7DAm.mjs → planner-pXRc-8yt.mjs} +7 -6
  132. package/dist/{planner-D5Ui7DAm.mjs.map → planner-pXRc-8yt.mjs.map} +1 -1
  133. package/dist/{postgres-CtOzfoQU.mjs → postgres-CUSXWgTG.mjs} +2 -2
  134. package/dist/{postgres-CtOzfoQU.mjs.map → postgres-CUSXWgTG.mjs.map} +1 -1
  135. package/dist/{program-zpoYnJGz.mjs → program-LLNPpxlj.mjs} +8308 -6065
  136. package/dist/program-LLNPpxlj.mjs.map +1 -0
  137. package/dist/query-feedback-C7rSfiXU.mjs +3 -0
  138. package/dist/{query-feedback-Dqz-HC__.mjs → query-feedback-CYyOGi3y.mjs} +20 -6
  139. package/dist/query-feedback-CYyOGi3y.mjs.map +1 -0
  140. package/dist/{reranker-CwTCNsgA.mjs → reranker-B5qUTamW.mjs} +1 -1
  141. package/dist/{reranker-xPm04PXx.mjs → reranker-HHmhZk6v.mjs} +1 -1
  142. package/dist/{reranker-xPm04PXx.mjs.map → reranker-HHmhZk6v.mjs.map} +1 -1
  143. package/dist/router-CremHKTI.mjs +3 -0
  144. package/dist/{router-DD6D4j31.mjs → router-DBAZfE7X.mjs} +2 -2
  145. package/dist/{router-DD6D4j31.mjs.map → router-DBAZfE7X.mjs.map} +1 -1
  146. package/dist/{run-D7K5UALn.mjs → run-DDdT9teG.mjs} +530 -210
  147. package/dist/run-DDdT9teG.mjs.map +1 -0
  148. package/dist/{runtime-paths-rni52zHX.mjs → runtime-paths-B0P1TvUr.mjs} +1 -1
  149. package/dist/{runtime-paths-rni52zHX.mjs.map → runtime-paths-B0P1TvUr.mjs.map} +1 -1
  150. package/dist/{search-Rpk1cSBC.mjs → search-B69cQwDB.mjs} +3 -3
  151. package/dist/{search-Rpk1cSBC.mjs.map → search-B69cQwDB.mjs.map} +1 -1
  152. package/dist/{search-CfPpJAWQ.mjs → search-fb-5ovj4.mjs} +2 -2
  153. package/dist/{server-D4vAieF4.mjs → server-Ch99xt5K.mjs} +15 -14
  154. package/dist/server-Ch99xt5K.mjs.map +1 -0
  155. package/dist/skills/Advisor/SKILL.md +3 -3
  156. package/dist/skills/Providers/SKILL.md +30 -0
  157. package/dist/skills/Reconstruct/SKILL.md +1 -1
  158. package/dist/skills/VaultContext/SKILL.md +1 -1
  159. package/dist/skills/Whisper/SKILL.md +3 -3
  160. package/dist/skills/Worker/SKILL.md +1 -1
  161. package/dist/{sources-CS43CpuS.mjs → sources-KBGEA_E6.mjs} +2 -2
  162. package/dist/{sources-CS43CpuS.mjs.map → sources-KBGEA_E6.mjs.map} +1 -1
  163. package/dist/{sqlite-Cq3gJcq9.mjs → sqlite-By6r2YhZ.mjs} +3 -3
  164. package/dist/{sqlite-Cq3gJcq9.mjs.map → sqlite-By6r2YhZ.mjs.map} +1 -1
  165. package/dist/{state-CY6I-Hnc.mjs → state-DnYGRmS7.mjs} +1 -1
  166. package/dist/{state-BRdB-Bf1.mjs → state-PJrgfDLW.mjs} +1 -1
  167. package/dist/{state-BRdB-Bf1.mjs.map → state-PJrgfDLW.mjs.map} +1 -1
  168. package/dist/{stop-words-Hfu8u22w.mjs → stop-words-BaMEGVeY.mjs} +1 -1
  169. package/dist/{stop-words-Hfu8u22w.mjs.map → stop-words-BaMEGVeY.mjs.map} +1 -1
  170. package/dist/{sync-BWbe8JTg.mjs → sync-Btcb6xA4.mjs} +3 -3
  171. package/dist/{sync-BWbe8JTg.mjs.map → sync-Btcb6xA4.mjs.map} +1 -1
  172. package/dist/{themes-B7h9YDiy.mjs → themes-BVoAnLZj.mjs} +3 -3
  173. package/dist/{themes-B7h9YDiy.mjs.map → themes-BVoAnLZj.mjs.map} +1 -1
  174. package/dist/{tools-BW3NLMPk.mjs → tools-BradfQk7.mjs} +4 -3
  175. package/dist/{tools-LlzcV_eK.mjs → tools-UrSHIe29.mjs} +30 -233
  176. package/dist/tools-UrSHIe29.mjs.map +1 -0
  177. package/dist/{trace-u4hT-pLD.mjs → trace-Cy8quQTm.mjs} +1 -1
  178. package/dist/{trace-u4hT-pLD.mjs.map → trace-Cy8quQTm.mjs.map} +1 -1
  179. package/dist/{utils-DhMex3Ox.mjs → utils-BD8g8oSf.mjs} +1 -1
  180. package/dist/{utils-9Err2RBW.mjs → utils-CJymHkYm.mjs} +1 -1
  181. package/dist/{utils-9Err2RBW.mjs.map → utils-CJymHkYm.mjs.map} +1 -1
  182. package/dist/{vault-indexer-mata9S7b.mjs → vault-indexer-C5i-OAyr.mjs} +2 -2
  183. package/dist/{vault-indexer-mata9S7b.mjs.map → vault-indexer-C5i-OAyr.mjs.map} +1 -1
  184. package/dist/wakeup-TnGjJY3I.mjs +218 -0
  185. package/dist/wakeup-TnGjJY3I.mjs.map +1 -0
  186. package/dist/work-queue-worker-BA5IDBwQ.mjs +567 -0
  187. package/dist/work-queue-worker-BA5IDBwQ.mjs.map +1 -0
  188. package/dist/work-queue-worker-DLM2wlqM.mjs +14 -0
  189. package/dist/{zettelkasten-F0D_XfQ4.mjs → zettelkasten-BBjA7GcP.mjs} +4 -4
  190. package/dist/{zettelkasten-F0D_XfQ4.mjs.map → zettelkasten-BBjA7GcP.mjs.map} +1 -1
  191. package/docs/commands/README.md +30 -3
  192. package/docs/commands/audit.md +88 -0
  193. package/docs/commands/backup.md +2 -2
  194. package/docs/commands/clear-names.md +1 -1
  195. package/docs/commands/config.md +59 -0
  196. package/docs/commands/daemon.md +1 -1
  197. package/docs/commands/db.md +1 -1
  198. package/docs/commands/end.md +1 -1
  199. package/docs/commands/help.md +1 -1
  200. package/docs/commands/identity.md +1 -1
  201. package/docs/commands/kg.md +1 -1
  202. package/docs/commands/launch.md +45 -0
  203. package/docs/commands/mcp.md +1 -1
  204. package/docs/commands/memory.md +3 -3
  205. package/docs/commands/notify.md +1 -1
  206. package/docs/commands/observation.md +1 -1
  207. package/docs/commands/obsidian.md +1 -1
  208. package/docs/commands/pause.md +1 -1
  209. package/docs/commands/project.md +46 -1
  210. package/docs/commands/projects.md +46 -1
  211. package/docs/commands/registry.md +1 -1
  212. package/docs/commands/restore.md +1 -1
  213. package/docs/commands/session.md +1 -1
  214. package/docs/commands/sessions.md +1 -1
  215. package/docs/commands/setup.md +1 -1
  216. package/docs/commands/shell-init.md +1 -1
  217. package/docs/commands/skill.md +1 -1
  218. package/docs/commands/task.md +1 -1
  219. package/docs/commands/topic.md +1 -1
  220. package/docs/commands/update.md +1 -1
  221. package/docs/commands/worker.md +95 -6
  222. package/docs/commands/zettel.md +1 -1
  223. package/docs/provider-abstraction.md +1 -1
  224. package/docs/provider-independence-details.md +8 -5
  225. package/docs/task-bus.md +1 -1
  226. package/docs/worker.md +136 -83
  227. package/docs/workers-config.md +446 -0
  228. package/package.json +4 -2
  229. package/scripts/build-hooks.mjs +56 -3
  230. package/src/hooks/pre-compact.sh +5 -2
  231. package/src/hooks/session-stop.sh +29 -4
  232. package/src/hooks/ts/capture-all-events.ts +3 -3
  233. package/src/hooks/ts/lib/advisor-budget.test.ts +98 -0
  234. package/src/hooks/ts/lib/advisor-budget.ts +90 -0
  235. package/src/hooks/ts/lib/agent-gate.test.ts +101 -0
  236. package/src/hooks/ts/lib/agent-gate.ts +112 -0
  237. package/src/hooks/ts/lib/context-fill.ts +2 -2
  238. package/src/hooks/ts/lib/edit-gate.test.ts +52 -0
  239. package/src/hooks/ts/lib/edit-gate.ts +51 -0
  240. package/src/hooks/ts/lib/mcp-deferred-reminder.test.ts +8 -0
  241. package/src/hooks/ts/lib/pai-paths.test.ts +93 -0
  242. package/src/hooks/ts/lib/pai-paths.ts +168 -24
  243. package/src/hooks/ts/lib/project-utils/index.ts +1 -0
  244. package/src/hooks/ts/lib/project-utils/paths.ts +5 -4
  245. package/src/hooks/ts/lib/project-utils.ts +1 -0
  246. package/src/hooks/ts/post-tool-use/capture-tool-output.ts +2 -2
  247. package/src/hooks/ts/pre-compact/context-compression-hook.ts +1 -1
  248. package/src/hooks/ts/pre-tool-use/route-agents-to-worker.ts +46 -99
  249. package/src/hooks/ts/pre-tool-use/route-edits-to-worker.test.ts +47 -0
  250. package/src/hooks/ts/pre-tool-use/route-edits-to-worker.ts +96 -0
  251. package/src/hooks/ts/pre-tool-use/security-validator.ts +2 -1
  252. package/src/hooks/ts/session-end/capture-session-summary.ts +4 -4
  253. package/src/hooks/ts/session-start/initialize-session.ts +3 -3
  254. package/src/hooks/ts/session-start/inject-observations.test.ts +56 -0
  255. package/src/hooks/ts/session-start/inject-observations.ts +28 -8
  256. package/src/hooks/ts/session-start/load-core-context.ts +4 -4
  257. package/src/hooks/ts/session-start/load-project-context.ts +2 -2
  258. package/src/hooks/ts/session-start/mcp-deferred-reminder.ts +2 -0
  259. package/src/hooks/ts/session-start/session-commands.ts +4 -0
  260. package/src/hooks/ts/session-start/session-start-worker-guard.test.ts +36 -0
  261. package/src/hooks/ts/stop/stop-hook.ts +6 -11
  262. package/src/hooks/ts/user-prompt/whisper-rules.test.ts +49 -0
  263. package/src/hooks/ts/user-prompt/whisper-rules.ts +88 -47
  264. package/statusline-command.sh +277 -116
  265. package/templates/agent-prefs.example.md +8 -8
  266. package/templates/claude-md.template.md +1 -1
  267. package/templates/voices.example.json +3 -3
  268. package/dist/config-C_ErGddD.mjs +0 -3
  269. package/dist/config-_lQI_W08.mjs +0 -640
  270. package/dist/config-_lQI_W08.mjs.map +0 -1
  271. package/dist/context-handover-cache-pNFdBNrU.mjs +0 -1548
  272. package/dist/context-handover-cache-pNFdBNrU.mjs.map +0 -1
  273. package/dist/daemon-B8N1egl0.mjs.map +0 -1
  274. package/dist/daemon-DHfyoTlk.mjs +0 -23
  275. package/dist/db-O-cyAPfS.mjs +0 -3
  276. package/dist/db-XEwJbuGO.mjs +0 -3
  277. package/dist/detector-C-7KRi42.mjs +0 -5
  278. package/dist/factory-Dbo8yNPi.mjs +0 -3
  279. package/dist/fallback-oJl3BaIN.mjs.map +0 -1
  280. package/dist/main-resolver-BAfOMppW.mjs +0 -7
  281. package/dist/main-resolver-DnkxPYkr.mjs.map +0 -1
  282. package/dist/program-zpoYnJGz.mjs.map +0 -1
  283. package/dist/query-feedback-DGoVRaWD.mjs +0 -3
  284. package/dist/query-feedback-Dqz-HC__.mjs.map +0 -1
  285. package/dist/router-BTIGZR2_.mjs +0 -3
  286. package/dist/run-D7K5UALn.mjs.map +0 -1
  287. package/dist/server-D4vAieF4.mjs.map +0 -1
  288. package/dist/tools-LlzcV_eK.mjs.map +0 -1
  289. package/dist/work-queue-worker-AEmfVE0F.mjs +0 -14
  290. package/dist/work-queue-worker-DIT-VLSh.mjs +0 -2136
  291. package/dist/work-queue-worker-DIT-VLSh.mjs.map +0 -1
  292. /package/dist/{indexer-D7MvSQPY.mjs → indexer-C5Le7XE7.mjs} +0 -0
@@ -1,12 +1,163 @@
1
- import { _ as readWorkersSection, c as classModelCapability, f as isModelCapability, g as providerKeyPath, o as WorkersConfigError, s as assertProviderRunnable, v as resolveModelCapability } from "./config-_lQI_W08.mjs";
2
- 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-D4vAieF4.mjs";
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 tmp = `${path}.tmp`;
236
- writeFileSync(tmp, JSON.stringify(status), "utf8");
237
- renameSync(tmp, path);
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
  /**
@@ -1626,14 +1840,28 @@ const OPERATOR_MARK = "[operator]";
1626
1840
  /** Appended to the caller's system prompt on every headless run (2h). */
1627
1841
  const WORKER_CONTRACT_PROMPT = [
1628
1842
  "You are a headless implementation worker, run non-interactively by an orchestrating session.",
1843
+ "You are the worker: this task is yours to finish in this process. Never hand your whole task to a",
1844
+ "single child worker and step back — that is delegation, not work. Child workers are for two things",
1845
+ "only: (1) PARALLELISING independent parts of your task, each part a self-contained spec; (2) CHEAPER",
1846
+ "bounded sub-tasks — probes, renders, test runs, lookups — run one tier down with --class spotcheck or",
1847
+ "--class simple. Always pin --provider anthropic on children.",
1848
+ "You own your children's results: run them in the foreground or poll until each has finished, read",
1849
+ "their result, verify it, and fold it into your own report. When your turn ends this run ends and any",
1850
+ "running child is killed, so never end with a child still running.",
1851
+ "Ending your turn ends this run. There is no later wake-up: never call ScheduleWakeup, never say you",
1852
+ "will pick something up later, never leave a background command as your final action. Finish, then",
1853
+ "report.",
1629
1854
  "No narration, no timestamps, no greetings, no summaries of what you read; act, verify, then stop.",
1630
1855
  "Your ONE final message is a JSON object and nothing else — no prose before or after, no code fence:",
1631
1856
  "{\"changed\":[{\"path\":\"…\",\"summary\":\"…\"}],\"commands\":[\"…\"],\"checks\":[{\"name\":\"…\",\"ok\":true,\"detail\":\"…\"}],\"open\":[\"…\"],\"notes\":\"one line\"}",
1632
1857
  "changed: files you touched (path + one-line summary). commands: the commands that verify the work.",
1633
1858
  "checks: each with ok true/false and the evidence in detail. open: what you could not finish, if anything.",
1634
1859
  "notes: one line, the headline a reviewer reads first.",
1635
- "Never write shell heredocs — write the script to a file with the Write tool, then run it",
1636
- "(heredocs fail silently or get swallowed).",
1860
+ "NEVER put a multi-line or quoted payload inline in a shell command — no heredocs (<<EOF), no long",
1861
+ "-p '…' specs for child workers, no inline JSON, no inline scripts. ALWAYS write it to a file with",
1862
+ "the Write tool first and pass the file: -p \"$(cat spec.txt)\", --file spec.txt, or < spec.txt.",
1863
+ "Inline quoting fails silently or dies with a parse error and burns a worker launch; there are no",
1864
+ "exceptions.",
1637
1865
  "Never inline code one-liners (python3 -c, node -e, ruby -e and friends) — the permission layer",
1638
1866
  "denies them (\"ztk: command denied by permission rules\"); write the script to a file with the",
1639
1867
  "Write tool and run it (python3 script.py), or use jq for JSON inspection.",
@@ -1717,104 +1945,56 @@ function relShort(p, cwd) {
1717
1945
  }
1718
1946
 
1719
1947
  //#endregion
1720
- //#region src/workers/mcp.ts
1948
+ //#region src/workers/project-config.ts
1721
1949
  /**
1722
- * mcp.ts — the MCP allowlist for headless workers.
1950
+ * project-config.ts — the project registry's optional launch pin, read for
1951
+ * the interactive branch of run.ts only: which MCP servers and built-in
1952
+ * tools a project's supervisor session (`pai worker run`, no -p) loads. Set
1953
+ * with `pai project mcp` / `pai project tools` (src/cli/commands/project/
1954
+ * session-config.ts), stored in the same `projects.session_config` JSON
1955
+ * column as the rest of a project's launch config. Unset = today's
1956
+ * behavior: every server, every tool.
1723
1957
  *
1724
- * Headless workers start with NO MCP servers: every server definition is a
1725
- * prompt-time tool inventory the model pays to know, and a worker that only
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.
1958
+ * A standalone SQL lookup rather than importing the CLI's detectProject:
1959
+ * workers/ is a dependency of cli/, not the other way around.
1735
1960
  */
1736
- /** ~/.claude.json — the user's MCP server definitions (top-level mcpServers). */
1737
- const CLAUDE_JSON = join(homedir(), ".claude.json");
1738
- function readMcpServers(claudeJson = CLAUDE_JSON) {
1739
- try {
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
- }
1961
+ function asStringArray(v) {
1962
+ if (!Array.isArray(v)) return void 0;
1963
+ const out = v.filter((x) => typeof x === "string");
1964
+ return out.length ? out : void 0;
1747
1965
  }
1748
1966
  /**
1749
- * Split a `--mcp a,b,c` flag value (also accepts repeated flags already split
1750
- * by the caller) and expand set names from workers.mcpSets.
1967
+ * The launch config of the project whose root_path is `cwd` or an ancestor
1968
+ * of it (longest match wins) — null when no registered project covers it,
1969
+ * or when the matching project has no mcp/tools pin set. `dbPath` is
1970
+ * injectable for tests; production callers take the default.
1751
1971
  */
1752
- function expandMcpNames(names, config, claudeJson = CLAUDE_JSON) {
1753
- const available = readMcpServers(claudeJson);
1754
- const out = [];
1755
- for (const raw of names) for (const name of raw.split(",").map((s) => s.trim()).filter(Boolean)) {
1756
- if (name in config.mcpSets) {
1757
- for (const member of config.mcpSets[name]) if (!out.includes(member)) out.push(member);
1758
- continue;
1759
- }
1760
- if (!(name in available)) {
1761
- const sets = Object.keys(config.mcpSets);
1762
- throw new WorkersConfigError(`unknown MCP server "${name}". Available servers: ${Object.keys(available).join(", ") || "(none in ~/.claude.json)"}${sets.length ? `; sets: ${sets.join(", ")}` : ""}`);
1972
+ function projectLaunchConfig(cwd, dbPath = registryDbPath()) {
1973
+ const target = resolve(cwd);
1974
+ const db = openRegistry(dbPath);
1975
+ try {
1976
+ const rows = db.prepare(`SELECT root_path, session_config FROM projects WHERE status != 'archived' ORDER BY LENGTH(root_path) DESC`).all();
1977
+ for (const row of rows) {
1978
+ const root = resolve(row.root_path);
1979
+ if (target !== root && !target.startsWith(root + "/")) continue;
1980
+ if (!row.session_config) return null;
1981
+ let parsed;
1982
+ try {
1983
+ parsed = JSON.parse(row.session_config);
1984
+ } catch {
1985
+ return null;
1986
+ }
1987
+ const mcp = asStringArray(parsed.mcp);
1988
+ const tools = asStringArray(parsed.tools);
1989
+ return mcp || tools ? {
1990
+ mcp,
1991
+ tools
1992
+ } : null;
1763
1993
  }
1764
- if (!out.includes(name)) out.push(name);
1765
- }
1766
- return out;
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(", ")}`);
1994
+ return null;
1995
+ } finally {
1996
+ db.close();
1815
1997
  }
1816
- lines.push(`usage: pai worker run --mcp <name>[,<name>…] -p '<task>'`);
1817
- return lines;
1818
1998
  }
1819
1999
 
1820
2000
  //#endregion
@@ -1855,7 +2035,7 @@ function buildCodexEnv(provider) {
1855
2035
  delete env.ANTHROPIC_DEFAULT_SONNET_MODEL;
1856
2036
  delete env.ANTHROPIC_DEFAULT_OPUS_MODEL;
1857
2037
  const keyPath = providerKeyPath(provider);
1858
- if (keyPath && existsSync(keyPath)) env.OPENAI_API_KEY = readFileSync(keyPath, "utf8").trim();
2038
+ if (provider.key || keyPath && existsSync(keyPath)) env.OPENAI_API_KEY = resolveProviderKey(provider) ?? "";
1859
2039
  if (provider.upstreamUrl) env.OPENAI_BASE_URL = provider.upstreamUrl;
1860
2040
  for (const [k, v] of Object.entries(provider.env)) env[k] = v;
1861
2041
  env.PAI_WORKER = "1";
@@ -2014,7 +2194,12 @@ function codexInstalled() {
2014
2194
  * (context meter included), ledger lines, result printed in the caller's
2015
2195
  * --output-format (json adds the parsed `report`), and the follow pane
2016
2196
  * (unless --no-pane).
2017
- * - interactive: no MCP restriction, no pane, ENABLE_TOOL_SEARCH=true.
2197
+ * - interactive: no MCP/tool restriction by default, no pane,
2198
+ * ENABLE_TOOL_SEARCH=true. A project that pinned a list with `pai
2199
+ * project mcp` / `pai project tools` gets `--strict-mcp-config
2200
+ * --mcp-config <filtered>` / `--tools <list>` built the same way a
2201
+ * headless run's allowlist is; an explicit --mcp or --tools on this
2202
+ * command line still wins over the project's pin.
2018
2203
  *
2019
2204
  * Auto-routed runs that die of a quota error before the first tool call are
2020
2205
  * restarted on the next provider in routing order (WORKER-REROUTE ledger line).
@@ -2077,6 +2262,56 @@ function resetContextTokensOnCompact(status, e) {
2077
2262
  function isCompactBoundary(e) {
2078
2263
  return e.type === "system" && (e.subtype === "compact_boundary" || e.subtype === "compact");
2079
2264
  }
2265
+ /**
2266
+ * The model the init event announces, adopted into the status when the spawn
2267
+ * could not name one (a caller that passed `--model` itself in the claude
2268
+ * args, or a provider whose table has no entry for the capability). The run's
2269
+ * real model is then only knowable from the first event it sends. Never
2270
+ * overwrites an explicit model — a resolved or pinned model stays the
2271
+ * recorded truth.
2272
+ */
2273
+ function adoptInitModel(status, e) {
2274
+ if (status.model) return;
2275
+ const m = (e.model ?? "").trim();
2276
+ if (m) status.model = m;
2277
+ }
2278
+ /**
2279
+ * The model a run goes out on: an explicit `--model` wins; else the class
2280
+ * target's alias ("glm/fast") names the capability; else the capability the
2281
+ * class implies (image → image model, spotcheck/simple → fast, everything
2282
+ * else → default), resolved against the provider's model table. The built-in
2283
+ * anthropic provider resolves through the same table as any other, so a
2284
+ * worker never inherits the orchestrator session's model.
2285
+ */
2286
+ function resolveRunModel(target, className, modelFlag) {
2287
+ if (modelFlag) return modelFlag;
2288
+ const alias = target.modelAlias;
2289
+ const capability = alias && isModelCapability(alias) ? alias : classModelCapability(className);
2290
+ return resolveModelCapability(target.provider, capability);
2291
+ }
2292
+ /**
2293
+ * The `--model` part of the claude argv. Skipped when the caller already
2294
+ * pinned one in the claude args (it must not be clobbered) or when nothing
2295
+ * resolved — `--model ""` would break the spawn.
2296
+ */
2297
+ function modelArgs(model, callerPinned) {
2298
+ return !callerPinned && model ? ["--model", model] : [];
2299
+ }
2300
+ /**
2301
+ * Whether the run's assembled cmd gets a forced --model. Headless workers
2302
+ * always take the resolved class/provider model — that table exists so
2303
+ * spawned subagents stay cheap. An interactive launch (`pai worker run` with
2304
+ * no -p) IS the chat pane, not a subagent: with no --model of the caller's
2305
+ * own it gets none here either, so Claude Code's own settings.json model
2306
+ * applies, exactly as launching `claude` by hand would (2026-09-20: an
2307
+ * interactive supervisor came up on the class model instead of the
2308
+ * settings.json [1m] variant, then needed a manual /model switch that
2309
+ * rebuilt the whole cache). A caller's own --model, headless or not, is
2310
+ * already in the passthrough args and must not be duplicated here.
2311
+ */
2312
+ function modelFlagArgs(headless, model, callerPinned) {
2313
+ return headless ? modelArgs(model, callerPinned) : [];
2314
+ }
2080
2315
  /** Context window announced by the init event, when the endpoint sends one. */
2081
2316
  function initContextWindow(e) {
2082
2317
  if (typeof e.context_window === "number" && e.context_window > 0) return e.context_window;
@@ -2094,7 +2329,7 @@ async function runWorker(opts) {
2094
2329
  const logDir = workersLogDir(config);
2095
2330
  mkdirSync(logDir, { recursive: true });
2096
2331
  if (opts.className === "plan" && !opts._planner) {
2097
- const { runPlanner } = await import("./planner-D5Ui7DAm.mjs");
2332
+ const { runPlanner } = await import("./planner-pXRc-8yt.mjs");
2098
2333
  return runPlanner(opts);
2099
2334
  }
2100
2335
  const parent = launchParent(opts.parent);
@@ -2106,9 +2341,7 @@ async function runWorker(opts) {
2106
2341
  assertProviderRunnable(target.providerName, target.provider);
2107
2342
  const parsed = parseRunnerArgs(opts.claudeArgs);
2108
2343
  const label = opts.label ?? shortText(parsed.prompt ?? UNLABELED, 70);
2109
- const alias = target.modelAlias;
2110
- const capability = alias && isModelCapability(alias) ? alias : classModelCapability(opts.className);
2111
- const model = opts.modelFlag ?? resolveModelCapability(target.provider, capability);
2344
+ const model = resolveRunModel(target, opts.className, opts.modelFlag);
2112
2345
  try {
2113
2346
  if (target.provider.engine === "codex") return await executeCodexRun({
2114
2347
  config,
@@ -2120,6 +2353,7 @@ async function runWorker(opts) {
2120
2353
  claudeArgs: opts.claudeArgs,
2121
2354
  noPane: opts.noPane ?? false,
2122
2355
  cwd: opts.cwd,
2356
+ specPath: opts.specPath,
2123
2357
  parent: parent ?? void 0,
2124
2358
  stage: opts.stage,
2125
2359
  quiet: opts.quiet,
@@ -2139,6 +2373,7 @@ async function runWorker(opts) {
2139
2373
  noPane: opts.noPane ?? false,
2140
2374
  mcpFlag: opts.mcpFlag,
2141
2375
  cwd: opts.cwd,
2376
+ specPath: opts.specPath,
2142
2377
  parent: parent ?? void 0,
2143
2378
  stage: opts.stage,
2144
2379
  quiet: opts.quiet,
@@ -2146,7 +2381,8 @@ async function runWorker(opts) {
2146
2381
  id: opts.id,
2147
2382
  worktreeFlag: opts.worktreeFlag,
2148
2383
  className: opts.className,
2149
- reroutes: opts._reroutes ?? 0
2384
+ reroutes: opts._reroutes ?? 0,
2385
+ printCmd: opts.printCmd
2150
2386
  });
2151
2387
  } catch (e) {
2152
2388
  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 +2402,59 @@ const DEFAULT_WORKER_TOOLS = "Read,Edit,Write,Bash,Grep,Glob";
2166
2402
  function headlessToolGrants(allowedTools) {
2167
2403
  return allowedTools.length ? [] : ["--allowedTools", DEFAULT_WORKER_TOOLS];
2168
2404
  }
2405
+ /**
2406
+ * `--tools` narrows which built-in tool *schemas* claude loads into the
2407
+ * system prompt — distinct from `--allowedTools`, which narrows which of
2408
+ * those loaded tools may run without asking. A headless worker with a small
2409
+ * grant (say, just Read) still pays for every built-in schema (Bash, Web*,
2410
+ * NotebookEdit, …) it never uses: ~9k tokens per spawn (29,270 -> 20,352,
2411
+ * measured 2026-09-20). Passing --tools built from the same grant removes
2412
+ * the unused schemas.
2413
+ *
2414
+ * MCP grants (`mcp__server__tool` / `mcp__server__*`) are left out of the
2415
+ * --tools list: they are not built-in tool names, --tools does not
2416
+ * recognize them, and the MCP server's tools load through --mcp-config
2417
+ * regardless. Tested 2026-09-20: with an --allowedTools grant of
2418
+ * `Read,mcp__pai__memory_search`, passing `--tools Read` (mcp name
2419
+ * omitted) still let the worker call memory_search successfully; the tool
2420
+ * loads from the filtered MCP config, not from --tools.
2421
+ */
2422
+ function headlessToolsFlag(allowedTools) {
2423
+ const names = [];
2424
+ for (const entry of allowedTools) for (const raw of entry.split(",").map((s) => s.trim()).filter(Boolean)) {
2425
+ if (raw.startsWith("mcp__")) continue;
2426
+ const patterned = raw.match(/^([A-Za-z]+)\(.*\)$/);
2427
+ const name = patterned ? patterned[1] : raw;
2428
+ if (!names.includes(name)) names.push(name);
2429
+ }
2430
+ return names.length ? ["--tools", names.join(",")] : [];
2431
+ }
2432
+ /**
2433
+ * The claude flag that turns on the browser bridge, when the run asked for it.
2434
+ *
2435
+ * The bridge is not an MCP server — it rides the Chrome native-host channel,
2436
+ * is absent from `mcpServers`, and is off in a spawned claude until `--chrome`
2437
+ * is passed. A grant such as `mcp__claude-in-chrome__tabs_context_mcp` is
2438
+ * therefore a request for that flag, not for a server to load; without this
2439
+ * the grant names a tool that never exists. `rest` is the caller's own argv:
2440
+ * a `--chrome` they passed themselves is kept rather than duplicated.
2441
+ */
2442
+ function chromeGrantArgs(wanted, rest = []) {
2443
+ if (rest.includes("--chrome")) return [];
2444
+ return grantsChrome(wanted) ? ["--chrome"] : [];
2445
+ }
2446
+ /**
2447
+ * Which MCP server names and --tools list an interactive launch (`pai
2448
+ * worker run`, no -p) uses: an explicit --mcp on this command line beats
2449
+ * the project's `pai project mcp` pin; an explicit --tools beats `pai
2450
+ * project tools`. Neither given: [] — today's "everything loads" default.
2451
+ */
2452
+ function interactiveMcpTools(explicitMcp, callerTools, projectLaunch) {
2453
+ return {
2454
+ mcpNames: explicitMcp.length ? explicitMcp : projectLaunch?.mcp ?? [],
2455
+ tools: !callerTools && projectLaunch?.tools?.length ? projectLaunch.tools : []
2456
+ };
2457
+ }
2169
2458
  async function executeRun(a) {
2170
2459
  const { config, logDir, target, model, label, parsed, noPane } = a;
2171
2460
  const headless = parsed.headless;
@@ -2178,7 +2467,7 @@ async function executeRun(a) {
2178
2467
  const session = resolveSession(term);
2179
2468
  const spawnerSession = resolveSpawnerSession(logDir, cwd);
2180
2469
  let worktree = null;
2181
- if (headless && worktreeWanted(a.worktreeFlag, {
2470
+ if (headless && !a.printCmd && worktreeWanted(a.worktreeFlag, {
2182
2471
  cwd,
2183
2472
  className: a.className,
2184
2473
  prompt: parsed.prompt
@@ -2193,6 +2482,59 @@ async function executeRun(a) {
2193
2482
  });
2194
2483
  }
2195
2484
  env.PAI_WORKER_ID = wid;
2485
+ const chromeArgs = chromeGrantArgs([
2486
+ ...a.mcpFlag ? [a.mcpFlag] : [],
2487
+ ...parsed.mcp,
2488
+ ...target.classMcp ?? [],
2489
+ ...parsed.allowedTools
2490
+ ], parsed.rest);
2491
+ let mcpArgs = [];
2492
+ let toolsFlag = headless ? headlessToolsFlag(parsed.allowedTools) : [];
2493
+ if (headless && !parsed.callerMcpConfig) {
2494
+ const wanted = [
2495
+ ...a.mcpFlag ? [a.mcpFlag] : [],
2496
+ ...parsed.mcp,
2497
+ ...target.classMcp ?? [],
2498
+ ...mcpServersFromToolGrants(parsed.allowedTools)
2499
+ ];
2500
+ if (wanted.length) mcpArgs = [
2501
+ "--strict-mcp-config",
2502
+ "--mcp-config",
2503
+ writeMcpConfig(logDir, wid, expandMcpNames(wanted, config))
2504
+ ];
2505
+ else mcpArgs = [
2506
+ "--strict-mcp-config",
2507
+ "--mcp-config",
2508
+ ensureNoMcpConfig(logDir)
2509
+ ];
2510
+ } else if (!headless && !parsed.callerMcpConfig) {
2511
+ const explicitMcp = [...a.mcpFlag ? [a.mcpFlag] : [], ...parsed.mcp];
2512
+ const projectLaunch = projectLaunchConfig(cwd);
2513
+ const picked = interactiveMcpTools(explicitMcp, parsed.callerTools, projectLaunch);
2514
+ if (picked.mcpNames.length) mcpArgs = [
2515
+ "--strict-mcp-config",
2516
+ "--mcp-config",
2517
+ writeMcpConfig(logDir, wid, expandMcpNames(picked.mcpNames, config))
2518
+ ];
2519
+ if (picked.tools.length) toolsFlag = ["--tools", picked.tools.join(",")];
2520
+ }
2521
+ const restArgs = headless ? stripPromptValues(parsed.rest) : parsed.rest;
2522
+ const toolArgs = headless ? headlessToolGrants(parsed.allowedTools) : [];
2523
+ const cmd = ["claude"];
2524
+ cmd.push(...modelFlagArgs(headless, model, Boolean(parsed.callerModel)));
2525
+ cmd.push(...chromeArgs, ...mcpArgs, ...toolArgs, ...toolsFlag, ...restArgs);
2526
+ if (headless) {
2527
+ cmd.push("--output-format", "stream-json", "--verbose", "--input-format", "stream-json");
2528
+ if (!parsed.callerSystemPrompt) cmd.push("--append-system-prompt", WORKER_CONTRACT_PROMPT);
2529
+ if (worktree) cmd.push("--append-system-prompt", worktreeSystemPrompt(wid, worktree.branch, worktree.dir));
2530
+ }
2531
+ if (a.printCmd) {
2532
+ console.log(JSON.stringify({
2533
+ cmd,
2534
+ cwd: worktree?.dir ?? cwd
2535
+ }));
2536
+ return 0;
2537
+ }
2196
2538
  const status = {
2197
2539
  id: wid,
2198
2540
  pid: process.pid,
@@ -2210,6 +2552,7 @@ async function executeRun(a) {
2210
2552
  rc: null,
2211
2553
  secs: null,
2212
2554
  origin: headless ? "spawn" : "chat",
2555
+ outputFormat: parsed.outputFormat,
2213
2556
  ...session ? { session } : {},
2214
2557
  ...spawnerSession ? { spawnerSession } : {},
2215
2558
  ...a.parent ? {
@@ -2220,7 +2563,8 @@ async function executeRun(a) {
2220
2563
  worktreeDir: worktree.dir,
2221
2564
  branch: worktree.branch,
2222
2565
  worktreeBase: worktree.base
2223
- } : {}
2566
+ } : {},
2567
+ ...a.specPath ? { spec: a.specPath } : {}
2224
2568
  };
2225
2569
  saveStatus(logDir, status);
2226
2570
  a.onWorkerStart?.(wid);
@@ -2231,38 +2575,10 @@ async function executeRun(a) {
2231
2575
  mode: headless ? "headless" : "interactive",
2232
2576
  model,
2233
2577
  cwd,
2234
- label
2578
+ label,
2579
+ ...a.specPath ? { spec: a.specPath } : {}
2235
2580
  });
2236
2581
  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
2582
  const t0 = Date.now();
2267
2583
  const proc = spawn(cmd[0], cmd.slice(1), {
2268
2584
  env,
@@ -2363,6 +2679,7 @@ async function executeRun(a) {
2363
2679
  writeEvent(e);
2364
2680
  if (e.type === "system" && e.subtype === "init") {
2365
2681
  if (e.session_id) status.claudeSession = e.session_id;
2682
+ adoptInitModel(status, e);
2366
2683
  const cw = initContextWindow(e);
2367
2684
  if (cw) status.contextWindow = cw;
2368
2685
  saveStatus(logDir, status);
@@ -2436,6 +2753,7 @@ async function executeRun(a) {
2436
2753
  noPane: a.noPane,
2437
2754
  mcpFlag: a.mcpFlag,
2438
2755
  cwd: a.cwd,
2756
+ specPath: a.specPath,
2439
2757
  parent: a.parent,
2440
2758
  stage: a.stage,
2441
2759
  claudeArgs: a.claudeArgs,
@@ -2516,6 +2834,7 @@ async function executeCodexRun(a) {
2516
2834
  rc: null,
2517
2835
  secs: null,
2518
2836
  origin: "spawn",
2837
+ outputFormat: parsed.outputFormat,
2519
2838
  ...session ? { session } : {},
2520
2839
  ...spawnerSession ? { spawnerSession } : {},
2521
2840
  ...target.provider.contextWindow ? { contextWindow: target.provider.contextWindow } : {},
@@ -2527,7 +2846,8 @@ async function executeCodexRun(a) {
2527
2846
  worktreeDir: worktree.dir,
2528
2847
  branch: worktree.branch,
2529
2848
  worktreeBase: worktree.base
2530
- } : {}
2849
+ } : {},
2850
+ ...a.specPath ? { spec: a.specPath } : {}
2531
2851
  };
2532
2852
  saveStatus(logDir, status);
2533
2853
  a.onWorkerStart?.(wid);
@@ -2793,5 +3113,5 @@ function resultFromOutput(out) {
2793
3113
  }
2794
3114
 
2795
3115
  //#endregion
2796
- export { nowStamp as A, alive as C, loadStatus as D, isChatPane as E, shortText as F, appendLedger as M, ledgerSummary as N, loadStatuses as O, parseRunnerArgs as P, ageOf as S, contextPercent 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, describeMcp as i, saveStatus as j, newWorkerId 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, contextLabel as w, UNLABELED as x, sessionTag as y };
2797
- //# sourceMappingURL=run-D7K5UALn.mjs.map
3116
+ export { newWorkerId as A, ageOf as C, isChatPane as D, contextPercent as E, longInlinePromptHint as F, parseRunnerArgs as I, shortText as L, saveStatus as M, setWorkerLabel as N, loadStatus as O, waitForTerminalStatus as P, describeMcp as R, UNLABELED as S, contextLabel as T, resolveSession as _, renderReport as a, appendLedger as b, handoffFromInside as c, workerDepth as d, checkPaneForWorker as f, itermForClaudeSession as g, currentTabKey as h, parseWorkerReport as i, nowStamp as j, loadStatuses as k, readInbox as l, openPaneForWorker as m, runWorker as n, discardWorker as o, openFollowPane as p, testProvider as r, mergeWorker as s, printResult as t, sayToWorker as u, sessionTag as v, alive as w, ledgerSummary as x, workerInScope as y, expandMcpNames as z };
3117
+ //# sourceMappingURL=run-DDdT9teG.mjs.map