@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
@@ -0,0 +1,446 @@
1
+ # workers.yaml
2
+
3
+ One hand-editable file: which providers PAI can run workers on, the model
4
+ each provider uses per role, and which provider every `--class` goes to.
5
+ Before this, that lived inside `~/.config/pai/config.json`'s `workers`
6
+ section — JSON, unrelated to the rest of that file, and undocumented in
7
+ place. Adding a provider meant reading code to find out what fields existed.
8
+
9
+ ## Where it lives
10
+
11
+ `~/.claude/pai/workers.yaml` — under the PAI_HOME namespace dir, beside
12
+ PAI's other per-user files (`config.json`, `whisper-rules.md`,
13
+ `advisor-mode.json`), so nothing PAI writes can collide with a file Claude
14
+ Code itself introduces under `~/.claude`. Set `PAI_WORKERS_YAML=<path>` to
15
+ override this — tests and power users only, and `PAI_HOME=<dir>` to move
16
+ the whole namespace. Only providers, model roles, class routing and MCP
17
+ sets live here. Everything else worker-related — the follow-pane profile,
18
+ log dir, routing cooldown, sub-worker caps, cache-keepalive cadence, the
19
+ machine-wide fallback switch — stays in `config.json`'s `workers` section;
20
+ none of it is provider-specific enough to want hand comments.
21
+
22
+ This file is per-user state: it can hold API keys (`key:`, below), is kept
23
+ mode 0600, and is never committed or shared.
24
+
25
+ Before 2026-09-19 it lived at `~/.config/pai/workers.yaml`, and briefly at
26
+ `~/.claude/workers.yaml` in between. If yours is still at either old
27
+ location, `pai worker config check` and `pai worker providers` say so;
28
+ `pai worker config migrate` (or `pai config migrate`) moves it — a
29
+ byte-for-byte copy, no JSON involved, with the old file renamed aside as
30
+ `workers.yaml.migrated-<date>` rather than deleted.
31
+
32
+ ## The file
33
+
34
+ ```yaml
35
+ # PAI worker configuration.
36
+ # Providers PAI can run workers on, the model each role uses, and which
37
+ # provider every --class goes to. Edit by hand; `pai worker providers` shows
38
+ # the effective result. Comments are preserved when PAI writes this file.
39
+ #
40
+ # Per-user state, kept 0600: this file can hold API keys (`key:`, below).
41
+ # Never commit it or share it. `key_file: <path>` keeps a secret in its own
42
+ # 0600 file instead, if you'd rather not put it here.
43
+
44
+ active: anthropic # provider for `pai worker run` without --provider or --class
45
+
46
+ providers:
47
+ anthropic:
48
+ builtin: true # Claude Code's own login: no url, no key file
49
+ models:
50
+ default: claude-sonnet-5
51
+ fast: claude-haiku-4-5-20251001
52
+ glm:
53
+ url: https://api.z.ai/api/anthropic
54
+ key: "<your-api-key>" # or key_file: <path to a 0600 file>
55
+ tier: 3
56
+ models:
57
+ default: glm-5.3[1m]
58
+ fast: glm-5.3-flash
59
+ image: example-paint
60
+ kimi:
61
+ url: https://api.kimi.ai/coding/
62
+ key: "<your-api-key>" # or key_file: <path to a 0600 file>
63
+ tier: 3
64
+ models:
65
+ default: k3[1m]
66
+ fast: kimi-for-coding[1m]
67
+
68
+ # A class names a provider, or provider/role to pick a non-default model.
69
+ # Workers exist to parallelise and to save cost: the default is Sonnet,
70
+ # Haiku for the mechanical classes, and nothing here inherits the
71
+ # orchestrating session's model.
72
+ classes:
73
+ implement: anthropic
74
+ draft: anthropic
75
+ research: anthropic
76
+ complex: anthropic
77
+ plan: anthropic
78
+ review: anthropic
79
+ spotcheck: anthropic/fast
80
+ simple: anthropic/fast
81
+ image: glm/image
82
+
83
+ mcp_sets:
84
+ desktop: [clickr]
85
+ ```
86
+
87
+ This exact file is `examples/workers.yaml`, and is what `pai worker config
88
+ init` writes on a machine with no workers.yaml yet.
89
+
90
+ - **`active`** — the provider a bare `pai worker run` (no `--provider`, no
91
+ `--class`) uses. `auto` walks `routing.order` from the JSON config instead.
92
+ - **`providers.<name>`** — one entry per provider. `anthropic` is reserved
93
+ for Claude Code's own OAuth/Max-plan login: no `url`, no `key_file`, just
94
+ `builtin: true` and an optional `models` override (defaults to Sonnet /
95
+ Haiku if omitted — see "Model hierarchy" below).
96
+ - **`providers.<name>.url`** — the Anthropic-compatible Messages API base.
97
+ Omit only when `protocol: openai` (below).
98
+ - **`providers.<name>.key`** — the API token inline, quoted. Wins over
99
+ `key_file` when both are set (`pai worker config check` notices this).
100
+ Never printed back — `pai worker providers` and `config check` render it
101
+ as `key ****`+ the last 4 characters.
102
+ - **`providers.<name>.key_file`** — path to a 0600 file holding the API
103
+ token instead, if you'd rather keep it out of this file. Omit both for a
104
+ local server with no auth (the runner sends the placeholder token
105
+ `"local"`). `pai worker config inline-keys` moves an existing `key_file`
106
+ inline.
107
+ - **`providers.<name>.tier`** — cost/quality tier, 1 (cheapest) … 5 (most
108
+ expensive), default 3. Classes can constrain auto-routing to a max tier.
109
+ - **`providers.<name>.models`** — model id per capability. `default` is
110
+ required; `fast` and `image` are optional and fall back to `default` when
111
+ a class asks for them and the provider has none.
112
+ - **`classes.<name>`** — `provider` or `provider/role` (`role` one of
113
+ `default`, `fast`, `image`). An object form
114
+ (`{provider, mcp, maxCostTier, requireTags, order}`) is also accepted for
115
+ advanced per-class MCP allowlists or routing constraints — see
116
+ `docs/worker.md`.
117
+ - **`mcp_sets.<name>`** — named MCP server lists; `--mcp <name>` and a
118
+ class's `mcp` field expand these.
119
+
120
+ ### Optional advanced provider fields
121
+
122
+ Not in the starter, but read when present (same names `pai worker providers
123
+ add` accepts, snake_cased): `enabled: false` (skip in auto-routing without
124
+ deleting the entry), `protocol: openai` + `upstream_url` (routes through the
125
+ built-in Anthropic↔OpenAI proxy), `engine: codex` (runs the provider on the
126
+ Codex CLI instead of Claude Code), `env` (extra environment for runs through
127
+ this provider), `note` (shown in `pai worker providers`), `quota_probe` +
128
+ `quota_skip_at`, `context_window`, `tags`, `usage`, `model_tiers`. Full field
129
+ semantics: `src/workers/config.ts`.
130
+
131
+ ## Provider protocol shapes
132
+
133
+ Every provider is one of three shapes. Which one is a YAML edit, never code —
134
+ the loader, writer and `config check` all round-trip `protocol`, `engine` and
135
+ `upstream_url` the same way (snake_cased in YAML, matching `url`/`key_file`);
136
+ see `src/workers/config.ts` and `src/workers/workers-config.ts`.
137
+
138
+ **(a) Anthropic-compatible endpoint** — `glm`/`kimi` above: a `url` (the
139
+ Messages API base) and a `key`/`key_file`. No `protocol` field — `anthropic`
140
+ is the default and Claude Code talks to the endpoint directly.
141
+
142
+ **(b) OpenAI-compatible remote** — `protocol: openai` routes the provider
143
+ through the built-in Anthropic↔OpenAI proxy (`docs/worker.md`, "The proxy");
144
+ `upstream_url` is the Chat Completions base instead of `url`:
145
+
146
+ ```yaml
147
+ providers:
148
+ openai:
149
+ protocol: openai
150
+ upstream_url: https://api.openai.com/v1
151
+ key: "<your-api-key>"
152
+ tier: 3
153
+ models:
154
+ default: gpt-5.2 # placeholder — check current OpenAI model ids
155
+ fast: gpt-5.2-mini # placeholder
156
+ ```
157
+
158
+ **(c) Local model** — same `protocol: openai` shape, pointed at a loopback
159
+ server with no `key`/`key_file` (the runner sends the placeholder token
160
+ `"local"`):
161
+
162
+ ```yaml
163
+ providers:
164
+ ollama:
165
+ protocol: openai
166
+ upstream_url: http://localhost:11434/v1
167
+ tier: 1
168
+ models:
169
+ default: llama3.3 # placeholder — whatever you've pulled locally
170
+ ```
171
+
172
+ LM Studio and llama.cpp's server mode speak the same OpenAI-compatible
173
+ Chat Completions API — the same shape works, just with that tool's own port
174
+ in `upstream_url` (LM Studio defaults to `1234`, llama.cpp's `server` binary
175
+ to `8080`).
176
+
177
+ `engine: codex` (either protocol shape) runs the provider on the Codex CLI
178
+ instead of Claude Code — see `docs/worker.md`, "The codex engine".
179
+
180
+ Shapes (b) and (c) validated, each written to its own file and checked with
181
+ an isolated `$HOME` so nothing touched the real config:
182
+
183
+ ```
184
+ $ HOME=/tmp/pai-isolated-home pai worker config check /tmp/openai-remote-check.yaml
185
+ /tmp/openai-remote-check.yaml OK — 1 provider(s), 3 class(es)
186
+ warning: /tmp/openai-remote-check.yaml contains a key: field but is mode 644 — chmod 600 /tmp/openai-remote-check.yaml
187
+
188
+ $ HOME=/tmp/pai-isolated-home pai worker config check /tmp/local-model-check.yaml
189
+ /tmp/local-model-check.yaml OK — 1 provider(s), 3 class(es)
190
+ ```
191
+
192
+ Both exit 0. The mode warning on (b) is `config check` doing its job — a
193
+ file with an inline `key:` gets flagged when it isn't 0600; (c) has no key,
194
+ so no warning. Neither run was launched against — these are shape checks
195
+ only, no worker spawned on either.
196
+
197
+ ## Adding a provider
198
+
199
+ Three edits, worked through for `kimi`:
200
+
201
+ 1. **`providers` block** — add the entry:
202
+
203
+ ```yaml
204
+ kimi:
205
+ url: https://api.kimi.ai/coding/
206
+ key_file: ~/.config/kimi/api_key
207
+ tier: 3
208
+ models:
209
+ default: k3[1m]
210
+ fast: kimi-for-coding[1m]
211
+ ```
212
+
213
+ 2. **Key file** — put the token where `key_file` points, mode 0600:
214
+
215
+ ```
216
+ mkdir -p ~/.config/kimi && chmod 700 ~/.config/kimi
217
+ echo "sk-…" > ~/.config/kimi/api_key && chmod 600 ~/.config/kimi/api_key
218
+ ```
219
+
220
+ 3. **Optional: route a class to it** — e.g. point `research` at kimi's fast
221
+ model:
222
+
223
+ ```yaml
224
+ classes:
225
+ research: kimi/fast
226
+ ```
227
+
228
+ Or the same three edits from the CLI, which writes workers.yaml directly
229
+ (comments elsewhere in the file are untouched). `--key-file` (above) and
230
+ `--key` are alternatives — `--key <token>` writes the token inline as
231
+ `key:` (quoted) instead of a file path:
232
+
233
+ ```
234
+ pai worker providers add kimi \
235
+ --url https://api.kimi.ai/coding/ \
236
+ --key sk-… \
237
+ --model 'k3[1m]' --fast-model 'kimi-for-coding[1m]' \
238
+ --cost-tier 3
239
+ pai worker classes set research kimi/fast
240
+ ```
241
+
242
+ `pai worker providers use kimi` makes it the default target for
243
+ `pai worker run` with no `--provider`/`--class`.
244
+
245
+ ## Starting the harness on another provider
246
+
247
+ `pai worker providers`/`classes` route delegated subagent work. `pai launch`
248
+ is the analogous door for the harness itself — the interactive session you
249
+ are typing into, not a worker it spawns.
250
+
251
+ A running `claude` process cannot change provider: its base URL and auth
252
+ token are fixed for the life of the process. So "switch provider" always
253
+ means "start a new session", and `pai launch` is the one place that turns
254
+ "start on `<provider>[/<model>]`" into a fresh `claude` process, reading the
255
+ same `workers.yaml` as everything else — adding a provider there makes it
256
+ available here with no other change.
257
+
258
+ ```
259
+ pai launch # numbered table in a terminal; pick one
260
+ pai launch --list # print the table and exit (also non-TTY)
261
+ pai launch --provider glm # that provider's default model
262
+ pai launch --provider glm --model glm-5.3-flash # a specific model
263
+ pai launch --provider glm -- --resume <id> # extra args pass to claude
264
+ pai launch --provider glm --dry-run # print argv/env (key masked), don't exec
265
+ ```
266
+
267
+ The table lists the built-in `anthropic` provider first, then every
268
+ **enabled** configured provider, one row per model capability
269
+ (default/fast/image) it declares — a disabled provider does not appear, and
270
+ neither does a capability the provider has not set. `--model` must name one
271
+ of the values shown for that provider; a provider name has to be one of the
272
+ rows too. Either mistake exits 2 and names the valid ones instead of
273
+ guessing.
274
+
275
+ **Why this exists:** Claude Code's own `/model` command only ever lists the
276
+ models of whatever endpoint the CURRENT process is already talking to —
277
+ started plainly it shows Anthropic models, started against a provider's base
278
+ URL it shows that provider's, and there is no way to ask it about a provider
279
+ the process was not started with. From inside a running session, the
280
+ `/providers` skill (`pai launch --list --current`) shows the full table
281
+ instead, with the row matching the session's own provider/model marked when
282
+ it can be determined from the environment.
283
+
284
+ The `glm`/`kimi` shell shims (installed by `pai worker install`) are thin
285
+ aliases of `pai launch --provider glm`/`kimi` — typing `glm` has always meant
286
+ "an interactive session pinned to the glm endpoint," not whatever provider
287
+ happens to be `active` right now.
288
+
289
+ ## Changing the model hierarchy
290
+
291
+ Workers exist to parallelise and to save cost: the default tier is Sonnet,
292
+ Haiku serves the mechanical classes (`spotcheck`, `simple`), and nothing a
293
+ worker runs on inherits the orchestrating session's model — a headless
294
+ `claude` with no `--model` takes the interactive session's current model,
295
+ which has come up expensive before. To change what a class uses, point it
296
+ at a different `provider/role`:
297
+
298
+ ```yaml
299
+ classes:
300
+ spotcheck: anthropic/fast # Haiku (default)
301
+ implement: anthropic # Sonnet (default)
302
+ ```
303
+
304
+ To change what the built-in provider's `default`/`fast` ids actually are,
305
+ edit `providers.anthropic.models` (the `builtin: true` entry) — everything
306
+ that resolves `anthropic` or `anthropic/fast` picks up the override.
307
+
308
+ ## Load order
309
+
310
+ 1. `workers.yaml`, if it exists — providers, classes, mcp_sets and active
311
+ all come from here, validated as a whole (an unknown provider named by a
312
+ class is a load error naming the file and line, not a spawn-time
313
+ surprise).
314
+ 2. Else the JSON `workers` section in `config.json` (`providers`, `classes`
315
+ — or the pre-rename `roles` — `mcpSets`, `active`), unchanged from
316
+ before this file existed. Nothing breaks on a machine that has not
317
+ migrated yet.
318
+ 3. Else built-in defaults (workers off, no providers, the four MCP `desktop`
319
+ default).
320
+
321
+ Every write (`pai worker providers add|use|remove|enable|disable`, class
322
+ changes, `pai worker off`/`on`, the `worker_providers`/`worker_model`/
323
+ `worker_toggle`/`worker_fallback` MCP tools) targets whichever source is
324
+ active: workers.yaml once it exists, else the JSON section. A write is
325
+ re-validated before it is committed; on failure the previous file is left
326
+ untouched and the error is reported instead.
327
+
328
+ ## Migrating from the JSON config, or from the old workers.yaml location
329
+
330
+ ```
331
+ pai worker config path # where workers.yaml resolves to
332
+ pai worker config init # first machine, no config yet: write the starter
333
+ pai worker config migrate # JSON workers section → workers.yaml, or old location → new
334
+ pai worker config migrate --dry-run # print the plan, change nothing
335
+ pai worker config check # validate the current workers.yaml (file:line on error)
336
+ pai worker config inline-keys # move every key_file's contents inline as key: (quoted)
337
+ ```
338
+
339
+ `migrate` does one of two things, chosen from what is actually on disk:
340
+
341
+ - **workers.yaml still at an old location** (`~/.claude/workers.yaml`, or
342
+ older still `~/.config/pai/workers.yaml`): moved byte-for-byte to
343
+ `~/.claude/pai/workers.yaml` — no JSON involved, nothing transformed, and
344
+ the old file is renamed aside as `workers.yaml.migrated-<date>` rather
345
+ than deleted.
346
+ - **no workers.yaml yet**: reads the JSON `workers` section, writes
347
+ workers.yaml with the same header/section comments as the starter, backs
348
+ the pre-migration JSON section up to `workers.json.migrated-<date>` next
349
+ to `config.json`, and strips `providers`/`classes`/`roles`/`mcpSets`/
350
+ `active` from the JSON (everything else in that section — pane, log dir,
351
+ routing, tree, cache-keepalive, fallback — stays).
352
+
353
+ Either way it refuses to run a second time unless passed `--force`, so it
354
+ is safe to attempt again after checking the result.
355
+
356
+ `inline-keys` is separate: for a workers.yaml that already exists, it reads
357
+ each provider's `key_file` and rewrites it as an inline, quoted `key:`,
358
+ leaving the key file itself on disk (it prints the paths so you can remove
359
+ them yourself). `--dry-run` prints which providers would be touched without
360
+ writing anything.
361
+
362
+ ## Where PAI lives
363
+
364
+ PAI is not a Claude Code plugin — it is a self-contained system that Claude
365
+ Code happens to be the first harness for. `~/.claude/pai/` (`PAI_HOME`) is
366
+ the single home for everything PAI owns: `config.json`, `workers.yaml`,
367
+ `whisper-rules.md`, `advisor-mode.json`, `session-state/`, `queries/`,
368
+ `logs/workers/` and the rest of PAI's per-user state and caches. `pai config
369
+ path` prints where every one of these currently resolves to; `pai config
370
+ migrate` moves whatever is still at an old location into PAI_HOME (byte-for-
371
+ byte, old copy renamed `.migrated-<date>` rather than deleted, never a
372
+ second time without `--force`).
373
+
374
+ `pai backup`/`pai restore` snapshots live under `PAI_HOME/backups/` (falling back to the pre-2026-09-19 `~/.pai/backups/`), with pre-PAI_HOME runs kept under `PAI_HOME/backups/legacy-pai-backups/`.
375
+
376
+ `~/.claude/` itself is a **harness adapter**, not a second home. It holds
377
+ only what Claude Code requires at fixed, hardcoded paths — and every one of
378
+ those entries is either a symlink into the PAI repo/`dist/` (hooks under
379
+ `Hooks/`, skills under `Skills/`, `statusline-command.sh`,
380
+ `tab-color-command.sh`, the `worker-status-line.mjs` standalone script), a
381
+ symlink into `PAI_HOME` (`Agents/` → `PAI_HOME/agents/`, `Commands/` →
382
+ `PAI_HOME/commands/` — see below), or, for the handful of files Claude Code
383
+ has no build step for and that are deployed once by hand
384
+ (`Hooks/block-taskoutput-wait.mjs`, `Hooks/post-compact-workers.mjs`,
385
+ `Hooks/route-edits-to-worker.mjs`, the `pre-commit*` templates,
386
+ `Hooks/ztk-auto.sh`), a real file with no PAI-owned copy elsewhere to
387
+ symlink from. Nothing else of PAI's is meant to sit loose in `~/.claude` —
388
+ anything found there outside this adapter set is a leftover from before
389
+ PAI_HOME existed, not a second storage location. `Skills/` is left as a
390
+ harness-only dir in this pass — not folded into `PAI_HOME/skills/`.
391
+
392
+ A future harness (OpenCode or otherwise) would need its own adapter — a
393
+ different directory, its own fixed hook/skill registration points — but it
394
+ would point at the *same* `PAI_HOME`, so switching harnesses never means
395
+ migrating PAI's own state.
396
+
397
+ ### PAI_DIR → ADAPTER_DIR, and the rest of the 2026-09-19 fold
398
+
399
+ `~/.claude/Hooks/lib/pai-paths.ts` used to define `PAI_DIR` (default
400
+ `~/.claude`) as both "where the adapter lives" and, via `HISTORY_DIR =
401
+ join(PAI_DIR, 'History')`, an anchor for PAI's own captured state — the two
402
+ concepts this whole doc distinguishes, collapsed into one name. `PAI_DIR` is
403
+ now **`ADAPTER_DIR`** (same default, same env-var override); `PAI_DIR` keeps
404
+ working as a deprecated alias for one release, with a one-time stderr notice
405
+ on every process that sets it. State that used to hang off `PAI_DIR` now
406
+ resolves under `PAI_HOME`, with the same old-path fallback + notice used
407
+ everywhere else in this file:
408
+
409
+ - **`History/`** (`historyDir()`), **`agent-sessions.json`**
410
+ (`agentSessionsPath()`), **`session-routing.json`**
411
+ (`sessionRoutingPath()`) and **`History/security/security-events.jsonl`**
412
+ (`securityEventsPath()`) — all in `src/hooks/ts/lib/pai-paths.ts`. These are
413
+ written by hooks on **every** session's every turn, not just spawned
414
+ workers, so unlike the rest of this file the live move is never automatic:
415
+ `pai config migrate --history` refuses while any spawned worker other than
416
+ the interactive session is RUNNING (`pai worker ps`), and that guard still
417
+ cannot see *other* interactive `claude` processes on the machine — confirm
418
+ none are active (`ps aux | grep claude`) before passing the flag. As of this
419
+ writing the move is deferred; the fold landed as code + fallback only.
420
+ - **`registry.db`** — `src/registry/db.ts`'s `registryDbPath()`, folded in
421
+ from the third per-user location it lived at, `~/.pai/registry.db`. Moved
422
+ live as part of a plain `pai config migrate` (same open-fd-and-restart-the-
423
+ daemon caution as `federation.db` above), with an extra WAL checkpoint
424
+ before the copy and a `PRAGMA integrity_check` after it (when the `sqlite3`
425
+ CLI is present) — a SQLite file in WAL mode can have unflushed writes
426
+ sitting in a `-wal` sidecar that a plain byte-copy of the `.db` file alone
427
+ would silently drop.
428
+ - **`agents/`, `commands/`** — the operator's authored `~/.claude/Agents/*.md`
429
+ and `~/.claude/Commands/*.md`, PAI content with no PAI-owned copy before
430
+ now. `pai config migrate` moves their contents into `PAI_HOME/agents/` and
431
+ `PAI_HOME/commands/`, then leaves a directory symlink at the old
432
+ `~/.claude/Agents` / `~/.claude/Commands` path — confirmed empirically that
433
+ Claude Code follows a directory symlink for `.claude/agents/` (a project-
434
+ level symlinked `agents/` dir was picked up by a fresh `claude -p` run
435
+ listing its available subagent types) before relying on it here. `bun run
436
+ build`'s `--sync` step (`scripts/build-hooks.mjs`) keeps both symlinks
437
+ current on every build, the same way it already does for `Hooks/` and
438
+ `Skills/` — but only once `PAI_HOME/agents/` and `PAI_HOME/commands/` exist
439
+ on a given machine; it never touches a real, un-migrated `Agents/`/
440
+ `Commands/` directory.
441
+
442
+ Deferred live move, to run once no other `claude` process is active:
443
+
444
+ ```
445
+ pai config migrate --history
446
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekmidian/pai",
3
- "version": "0.42.4",
3
+ "version": "0.42.6",
4
4
  "description": "PAI Knowledge OS — Personal AI Infrastructure with federated memory and project management",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",
@@ -71,7 +71,9 @@
71
71
  "better-sqlite3": "^12.6.2",
72
72
  "chalk": "^5.6.2",
73
73
  "commander": "^14.0.3",
74
- "pg": "^8.13.3"
74
+ "js-tiktoken": "^1.0.21",
75
+ "pg": "^8.13.3",
76
+ "yaml": "^2.9.1"
75
77
  },
76
78
  "devDependencies": {
77
79
  "@types/better-sqlite3": "^7.6.13",
@@ -6,8 +6,10 @@
6
6
  * Output: dist/hooks/<name>.mjs with #!/usr/bin/env node shebang.
7
7
  *
8
8
  * With --sync: also creates/updates symlinks (or copies on Windows) from
9
- * ~/.claude/Hooks/ and ~/.claude/ to the built/source files. This ensures
10
- * that `bun run build` is the only step needed to deploy hook updates.
9
+ * ~/.claude/Hooks/ and ~/.claude/ to the built/source files, plus the
10
+ * PAI_HOME/agents and PAI_HOME/commands adapter symlinks at ~/.claude/Agents
11
+ * and ~/.claude/Commands (once `pai config migrate` has populated them once).
12
+ * This ensures that `bun run build` is the only step needed to deploy updates.
11
13
  */
12
14
 
13
15
  import { buildSync } from "esbuild";
@@ -35,6 +37,22 @@ const HOOKS_SRC = "src/hooks/ts";
35
37
  const HOOKS_OUT = "dist/hooks";
36
38
  const doSync = process.argv.includes("--sync");
37
39
 
40
+ // Bundled CJS deps (e.g. yaml) call require() for Node builtins like
41
+ // "process". esbuild's ESM output leaves those calls as a `require` that
42
+ // only exists at runtime if the module defines it — plain .mjs run via node
43
+ // has no `require`, so the call throws "Dynamic require of ... is not
44
+ // supported". createRequire gives every bundle a real one.
45
+ //
46
+ // PAI_QUIET_NOTICES=1 silences one-time stderr notices (e.g. the PAI_DIR
47
+ // deprecation warning in pai-paths.ts) for every hook and standalone script
48
+ // built here — their stderr is machinery Claude Code inspects for errors and
49
+ // the statusline pipes through, never a human terminal, so it must stay
50
+ // clean. Set only if unset, so a caller that deliberately wants the notices
51
+ // (debugging) can still get them.
52
+ const REQUIRE_SHIM = {
53
+ js: "import { createRequire as __paiCreateRequire } from 'node:module';\nconst require = __paiCreateRequire(import.meta.url);\nif (!process.env.PAI_QUIET_NOTICES) process.env.PAI_QUIET_NOTICES = '1';",
54
+ };
55
+
38
56
  // Collect all .ts entry points (skip lib/ — those are bundled into each hook)
39
57
  function collectEntryPoints(dir) {
40
58
  const entries = [];
@@ -43,7 +61,7 @@ function collectEntryPoints(dir) {
43
61
  if (name === "lib") continue;
44
62
  if (statSync(full).isDirectory()) {
45
63
  entries.push(...collectEntryPoints(full));
46
- } else if (name.endsWith(".ts")) {
64
+ } else if (name.endsWith(".ts") && !name.endsWith(".test.ts")) {
47
65
  entries.push(full);
48
66
  }
49
67
  }
@@ -81,6 +99,7 @@ for (const entry of entryPoints) {
81
99
  format: "esm",
82
100
  outfile: staged,
83
101
  sourcemap: true,
102
+ banner: REQUIRE_SHIM,
84
103
  });
85
104
 
86
105
  chmodSync(staged, 0o755);
@@ -124,6 +143,7 @@ for (const [entry, name] of STANDALONE_ENTRIES) {
124
143
  format: "esm",
125
144
  outfile: staged,
126
145
  sourcemap: true,
146
+ banner: REQUIRE_SHIM,
127
147
  });
128
148
  chmodSync(staged, 0o755);
129
149
  const stagedMap = `${staged}.map`;
@@ -236,6 +256,39 @@ if (doSync && isInsideWorkerWorktree(process.cwd())) {
236
256
  // 4. Standalone worker status-line: dist/worker-status-line.mjs → ~/.claude/
237
257
  syncFile(join(HOOKS_OUT, "worker-status-line.mjs"), join(claudeDir, "worker-status-line.mjs"));
238
258
 
259
+ // 5. Content dirs with an adapter symlink: PAI_HOME/agents, PAI_HOME/commands
260
+ // → ~/.claude/Agents, ~/.claude/Commands. Only acts once `pai config migrate`
261
+ // has populated the PAI_HOME copy on this machine — a real (non-symlink)
262
+ // Agents/Commands dir is the operator's un-migrated authored content and is
263
+ // never touched here, same rule as syncFile() above.
264
+ function syncContentDirSymlink(paiHomeSubdir, targetDir) {
265
+ if (!existsSync(paiHomeSubdir)) return; // not migrated yet — nothing to link to
266
+
267
+ try {
268
+ const stat = lstatSync(targetDir);
269
+ if (stat.isSymbolicLink()) {
270
+ if (resolve(readlinkSync(targetDir)) === resolve(paiHomeSubdir)) {
271
+ current++;
272
+ return;
273
+ }
274
+ unlinkSync(targetDir);
275
+ symlinkSync(resolve(paiHomeSubdir), targetDir);
276
+ updated++;
277
+ return;
278
+ }
279
+ // Real directory — leave it alone (`pai config migrate` is what moves it).
280
+ return;
281
+ } catch {
282
+ // Target doesn't exist — fresh symlink.
283
+ symlinkSync(resolve(paiHomeSubdir), targetDir);
284
+ created++;
285
+ }
286
+ }
287
+
288
+ const paiHomeDir = process.env.PAI_HOME || join(homedir(), ".claude", "pai");
289
+ syncContentDirSymlink(join(paiHomeDir, "agents"), join(claudeDir, "Agents"));
290
+ syncContentDirSymlink(join(paiHomeDir, "commands"), join(claudeDir, "Commands"));
291
+
239
292
  const parts = [];
240
293
  if (created > 0) parts.push(`${created} created`);
241
294
  if (updated > 0) parts.push(`${updated} updated`);
@@ -10,14 +10,17 @@
10
10
  PAI_OS="pai"
11
11
 
12
12
  # Set tab color to working state while compacting
13
- TAB_COLOR="${PAI_DIR:-$HOME/.claude}/tab-color-command.sh"
13
+ TAB_COLOR="${ADAPTER_DIR:-${PAI_DIR:-$HOME/.claude}}/tab-color-command.sh"
14
14
  [[ -x "$TAB_COLOR" ]] && "$TAB_COLOR" working
15
15
 
16
16
  # Bail gracefully if pai is not installed
17
17
  command -v "$PAI_OS" &>/dev/null || exit 0
18
18
  command -v sqlite3 &>/dev/null || exit 0
19
19
 
20
- REGISTRY_DB="$HOME/.pai/registry.db"
20
+ # PAI_HOME (~/.claude/pai by default — see src/config/pai-home.ts), falling
21
+ # back to the pre-2026-09-19 ~/.pai/registry.db.
22
+ REGISTRY_DB="${PAI_HOME:-$HOME/.claude/pai}/registry.db"
23
+ [ -f "$REGISTRY_DB" ] || REGISTRY_DB="$HOME/.pai/registry.db"
21
24
  [ -f "$REGISTRY_DB" ] || exit 0
22
25
 
23
26
  # ---------------------------------------------------------------------------
@@ -39,7 +39,10 @@ if [ ! -t 0 ]; then
39
39
  fi
40
40
  fi
41
41
 
42
- REGISTRY_DB="$HOME/.pai/registry.db"
42
+ # PAI_HOME (~/.claude/pai by default — see src/config/pai-home.ts), falling
43
+ # back to the pre-2026-09-19 ~/.pai/registry.db.
44
+ REGISTRY_DB="${PAI_HOME:-$HOME/.claude/pai}/registry.db"
45
+ [ -f "$REGISTRY_DB" ] || REGISTRY_DB="$HOME/.pai/registry.db"
43
46
  [ -f "$REGISTRY_DB" ] || exit 0
44
47
 
45
48
  # ---------------------------------------------------------------------------
@@ -60,9 +63,25 @@ REGISTRY_DB="$HOME/.pai/registry.db"
60
63
  # primitive available in POSIX sh. If the lock is held, skip entirely — the next
61
64
  # turn runs it, and every step here is idempotent and derives from current state
62
65
  # rather than accumulating.
63
- LOCK_DIR="$HOME/.config/pai/.session-stop.lock"
66
+ #
67
+ # Location: PAI_HOME (~/.claude/pai by default — see src/config/pai-home.ts).
68
+ # A shell script can't import that resolver, so this mirrors its two-path
69
+ # logic inline: new location, else the pre-2026-09-19 ~/.config/pai one if a
70
+ # fresh lock is already sitting there (an in-flight hook from before this
71
+ # script updated) — never both, that would defeat the mutex.
72
+ PAI_HOME_DIR="${PAI_HOME:-$HOME/.claude/pai}"
73
+ LOCK_DIR="$PAI_HOME_DIR/.session-stop.lock"
74
+ OLD_LOCK_DIR="$HOME/.config/pai/.session-stop.lock"
64
75
  mkdir -p "$(dirname "$LOCK_DIR")" 2>/dev/null || true
65
76
 
77
+ if [ -d "$OLD_LOCK_DIR" ] && [ ! -d "$LOCK_DIR" ]; then
78
+ OLD_LOCK_AGE=$(( $(date +%s) - $(stat -f %m "$OLD_LOCK_DIR" 2>/dev/null || echo 0) ))
79
+ if [ "$OLD_LOCK_AGE" -le 600 ]; then
80
+ echo "pai: honoring in-flight lock at old location $OLD_LOCK_DIR" >&2
81
+ LOCK_DIR="$OLD_LOCK_DIR"
82
+ fi
83
+ fi
84
+
66
85
  # A crashed run must not wedge this forever: treat a lock older than 10 minutes
67
86
  # as abandoned. Longer than any observed tail, shorter than a working session.
68
87
  if [ -d "$LOCK_DIR" ]; then
@@ -149,8 +168,14 @@ fi
149
168
  # (seconds, 0 disables the debounce and restores per-turn behaviour).
150
169
 
151
170
  HOUSEKEEP_INTERVAL="${PAI_HOUSEKEEPING_INTERVAL:-1800}"
152
- HOUSEKEEP_STAMP="$HOME/.config/pai/.last-housekeeping"
171
+ # Same PAI_HOME two-path logic as LOCK_DIR above.
172
+ HOUSEKEEP_STAMP="$PAI_HOME_DIR/.last-housekeeping"
173
+ OLD_HOUSEKEEP_STAMP="$HOME/.config/pai/.last-housekeeping"
153
174
  mkdir -p "$(dirname "$HOUSEKEEP_STAMP")" 2>/dev/null || true
175
+ if [ ! -f "$HOUSEKEEP_STAMP" ] && [ -f "$OLD_HOUSEKEEP_STAMP" ]; then
176
+ echo "pai: $OLD_HOUSEKEEP_STAMP is at an old location — run \`pai config migrate\` to move it to $HOUSEKEEP_STAMP" >&2
177
+ HOUSEKEEP_STAMP="$OLD_HOUSEKEEP_STAMP"
178
+ fi
154
179
 
155
180
  housekeeping_due() {
156
181
  [ "$HOUSEKEEP_INTERVAL" = "0" ] && return 0
@@ -205,7 +230,7 @@ else
205
230
  fi
206
231
 
207
232
  # Set tab color to completed state when session ends
208
- TAB_COLOR="${PAI_DIR:-$HOME/.claude}/tab-color-command.sh"
233
+ TAB_COLOR="${ADAPTER_DIR:-${PAI_DIR:-$HOME/.claude}}/tab-color-command.sh"
209
234
  [[ -x "$TAB_COLOR" ]] && "$TAB_COLOR" completed
210
235
 
211
236
  } >/dev/null 2>&1 &
@@ -8,7 +8,7 @@
8
8
 
9
9
  import { readFileSync, appendFileSync, mkdirSync, existsSync, writeFileSync } from 'fs';
10
10
  import { join } from 'path';
11
- import { PAI_DIR, HISTORY_DIR } from './lib/pai-paths';
11
+ import { historyDir, agentSessionsPath } from './lib/pai-paths';
12
12
  import { enrichEventWithAgentMetadata, isAgentSpawningCall } from './lib/metadata-extraction';
13
13
  import { isProbeSession } from './lib/project-utils';
14
14
 
@@ -46,7 +46,7 @@ function getEventsFilePath(): string {
46
46
  const month = String(localDate.getMonth() + 1).padStart(2, '0');
47
47
  const day = String(localDate.getDate()).padStart(2, '0');
48
48
 
49
- const monthDir = join(HISTORY_DIR, 'raw-outputs', `${year}-${month}`);
49
+ const monthDir = join(historyDir(), 'raw-outputs', `${year}-${month}`);
50
50
 
51
51
  // Ensure directory exists
52
52
  if (!existsSync(monthDir)) {
@@ -58,7 +58,7 @@ function getEventsFilePath(): string {
58
58
 
59
59
  // Session-to-agent mapping functions
60
60
  function getSessionMappingFile(): string {
61
- return join(PAI_DIR, 'agent-sessions.json');
61
+ return agentSessionsPath();
62
62
  }
63
63
 
64
64
  function getAgentForSession(sessionId: string): string {