@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.
- package/ARCHITECTURE.md +9 -9
- package/README.md +26 -8
- package/dist/{aibroker-client-BP74gGwD.mjs → aibroker-client-Bn9SnGMa.mjs} +3 -3
- package/dist/{aibroker-client-BP74gGwD.mjs.map → aibroker-client-Bn9SnGMa.mjs.map} +1 -1
- package/dist/{aibroker-client-C6m71p6O.mjs → aibroker-client-D5NT0S1N.mjs} +1 -1
- package/dist/{auto-route-_5zzJz5q.mjs → auto-route-CcD0OWWB.mjs} +4 -4
- package/dist/{auto-route-_5zzJz5q.mjs.map → auto-route-CcD0OWWB.mjs.map} +1 -1
- package/dist/{chain-DTII7xeL.mjs → chain-BbcfP77K.mjs} +5 -4
- package/dist/{chain-DTII7xeL.mjs.map → chain-BbcfP77K.mjs.map} +1 -1
- package/dist/cli/index.mjs +23 -21
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/program.d.mts.map +1 -1
- package/dist/cli/program.mjs +23 -21
- package/dist/{clusters-j0ZuNYp9.mjs → clusters-Cw_P6lO-.mjs} +2 -2
- package/dist/{clusters-j0ZuNYp9.mjs.map → clusters-Cw_P6lO-.mjs.map} +1 -1
- package/dist/{config-B64vFg14.mjs → config-DCu8THKi.mjs} +41 -9
- package/dist/{config-B64vFg14.mjs.map → config-DCu8THKi.mjs.map} +1 -1
- package/dist/config-DeqFqa1N.mjs +3 -0
- package/dist/config-DxMZjpvY.mjs +1289 -0
- package/dist/config-DxMZjpvY.mjs.map +1 -0
- package/dist/context-handover-cache-B9NKSjwR.mjs +3242 -0
- package/dist/context-handover-cache-B9NKSjwR.mjs.map +1 -0
- package/dist/daemon/index.mjs +23 -22
- package/dist/daemon/index.mjs.map +1 -1
- package/dist/daemon-BNjbVsQU.mjs +24 -0
- package/dist/{daemon-B8N1egl0.mjs → daemon-BppziPi6.mjs} +52 -38
- package/dist/daemon-BppziPi6.mjs.map +1 -0
- package/dist/daemon-mcp/index.mjs +144 -117
- package/dist/daemon-mcp/index.mjs.map +1 -1
- package/dist/db-BOBfewFH.mjs +3 -0
- package/dist/{db-a1ixZQjr.mjs → db-DJ8rk0VM.mjs} +15 -6
- package/dist/{db-a1ixZQjr.mjs.map → db-DJ8rk0VM.mjs.map} +1 -1
- package/dist/db-DbY2O8QO.mjs +3 -0
- package/dist/{db-Ca5qfsMC.mjs → db-Ddns80yf.mjs} +15 -6
- package/dist/{db-Ca5qfsMC.mjs.map → db-Ddns80yf.mjs.map} +1 -1
- package/dist/{detect-CdaA48EI.mjs → detect-Bf2z-oKB.mjs} +1 -1
- package/dist/{detect-CdaA48EI.mjs.map → detect-Bf2z-oKB.mjs.map} +1 -1
- package/dist/detector-CkwkXuGh.mjs +5 -0
- package/dist/{detector-C73Nxm94.mjs → detector-DWXkeTlk.mjs} +2 -2
- package/dist/{detector-C73Nxm94.mjs.map → detector-DWXkeTlk.mjs.map} +1 -1
- package/dist/{embeddings-CEBGrzwu.mjs → embeddings-ClqKtLZ8.mjs} +1 -1
- package/dist/{embeddings-DOLZnT1X.mjs → embeddings-DvRKcQyt.mjs} +1 -1
- package/dist/{embeddings-DOLZnT1X.mjs.map → embeddings-DvRKcQyt.mjs.map} +1 -1
- package/dist/factory-47Qf_df7.mjs +3 -0
- package/dist/{factory-DEb9NmHJ.mjs → factory-DNYAqjH0.mjs} +8 -8
- package/dist/{factory-DEb9NmHJ.mjs.map → factory-DNYAqjH0.mjs.map} +1 -1
- package/dist/{fallback-oJl3BaIN.mjs → fallback-Cwt4hmh5.mjs} +182 -122
- package/dist/fallback-Cwt4hmh5.mjs.map +1 -0
- package/dist/{helpers-IjZkXBhj.mjs → helpers-crDEr6S2.mjs} +1 -1
- package/dist/{helpers-IjZkXBhj.mjs.map → helpers-crDEr6S2.mjs.map} +1 -1
- package/dist/hooks/capture-all-events.mjs +117 -34
- package/dist/hooks/capture-all-events.mjs.map +4 -4
- package/dist/hooks/capture-session-summary.mjs +110 -33
- package/dist/hooks/capture-session-summary.mjs.map +4 -4
- package/dist/hooks/capture-tool-output.mjs +105 -28
- package/dist/hooks/capture-tool-output.mjs.map +4 -4
- package/dist/hooks/cleanup-session-files.mjs +54 -13
- package/dist/hooks/cleanup-session-files.mjs.map +2 -2
- package/dist/hooks/context-compression-hook.mjs +7565 -122
- package/dist/hooks/context-compression-hook.mjs.map +4 -4
- package/dist/hooks/initialize-session.mjs +55 -14
- package/dist/hooks/initialize-session.mjs.map +2 -2
- package/dist/hooks/inject-observations.mjs +79 -18
- package/dist/hooks/inject-observations.mjs.map +3 -3
- package/dist/hooks/load-core-context.mjs +55 -14
- package/dist/hooks/load-core-context.mjs.map +2 -2
- package/dist/hooks/load-project-context.mjs +185 -103
- package/dist/hooks/load-project-context.mjs.map +4 -4
- package/dist/hooks/mcp-deferred-gate.mjs +54 -13
- package/dist/hooks/mcp-deferred-gate.mjs.map +2 -2
- package/dist/hooks/mcp-deferred-reminder.mjs +9 -0
- package/dist/hooks/mcp-deferred-reminder.mjs.map +3 -3
- package/dist/hooks/observe.mjs +54 -13
- package/dist/hooks/observe.mjs.map +2 -2
- package/dist/hooks/post-compact-inject.mjs +7419 -16
- package/dist/hooks/post-compact-inject.mjs.map +4 -4
- package/dist/hooks/route-agents-to-worker.mjs +7778 -171
- package/dist/hooks/route-agents-to-worker.mjs.map +4 -4
- package/dist/hooks/route-edits-to-worker.mjs +90 -0
- package/dist/hooks/route-edits-to-worker.mjs.map +7 -0
- package/dist/hooks/security-validator.mjs +147 -6
- package/dist/hooks/security-validator.mjs.map +4 -4
- package/dist/hooks/session-commands.mjs +9 -0
- package/dist/hooks/session-commands.mjs.map +3 -3
- package/dist/hooks/stop-hook.mjs +182 -105
- package/dist/hooks/stop-hook.mjs.map +4 -4
- package/dist/hooks/subagent-stop-hook.mjs +3 -0
- package/dist/hooks/subagent-stop-hook.mjs.map +1 -1
- package/dist/hooks/sync-todo-to-md.mjs +54 -13
- package/dist/hooks/sync-todo-to-md.mjs.map +2 -2
- package/dist/hooks/update-tab-on-action.mjs +3 -0
- package/dist/hooks/update-tab-on-action.mjs.map +1 -1
- package/dist/hooks/update-tab-titles.mjs +3 -0
- package/dist/hooks/update-tab-titles.mjs.map +1 -1
- package/dist/hooks/whisper-reinject.mjs +3 -0
- package/dist/hooks/whisper-reinject.mjs.map +1 -1
- package/dist/hooks/whisper-rules.mjs +134 -29
- package/dist/hooks/whisper-rules.mjs.map +4 -4
- package/dist/hooks/worker-proxy.mjs +7737 -117
- package/dist/hooks/worker-proxy.mjs.map +4 -4
- package/dist/hooks/worker-status-line.mjs +7791 -144
- package/dist/hooks/worker-status-line.mjs.map +4 -4
- package/dist/hooks/worker-supervision.mjs +7721 -115
- package/dist/hooks/worker-supervision.mjs.map +4 -4
- package/dist/index.d.mts +4 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +10 -10
- package/dist/{indexer-backend-BPTzKFZU.mjs → indexer-backend-CdvCJCkD.mjs} +3 -3
- package/dist/{indexer-backend-BPTzKFZU.mjs.map → indexer-backend-CdvCJCkD.mjs.map} +1 -1
- package/dist/{ipc-client-BmypMNYk.mjs → ipc-client-C6NryMMb.mjs} +2 -2
- package/dist/{ipc-client-BmypMNYk.mjs.map → ipc-client-C6NryMMb.mjs.map} +1 -1
- package/dist/{kg-entity-r8duqhi9.mjs → kg-entity-BSjM5xZh.mjs} +1 -1
- package/dist/{kg-entity-r8duqhi9.mjs.map → kg-entity-BSjM5xZh.mjs.map} +1 -1
- package/dist/{latent-ideas-CtSca4qG.mjs → latent-ideas-BfL4nCQ3.mjs} +4 -4
- package/dist/{latent-ideas-CtSca4qG.mjs.map → latent-ideas-BfL4nCQ3.mjs.map} +1 -1
- package/dist/{link-boost-Bucnz3n8.mjs → link-boost-C5PaNdzm.mjs} +1 -1
- package/dist/{link-boost-Bucnz3n8.mjs.map → link-boost-C5PaNdzm.mjs.map} +1 -1
- package/dist/main-resolver-CnfnWa8C.mjs +7 -0
- package/dist/{main-resolver-DnkxPYkr.mjs → main-resolver-DvYu9jbl.mjs} +21 -11
- package/dist/main-resolver-DvYu9jbl.mjs.map +1 -0
- package/dist/{migrate-Cjzeefn9.mjs → migrate-CfERwXhY.mjs} +2 -2
- package/dist/{migrate-Cjzeefn9.mjs.map → migrate-CfERwXhY.mjs.map} +1 -1
- package/dist/{neighborhood-CM7Jgb51.mjs → neighborhood-CETR_P4y.mjs} +2 -2
- package/dist/{neighborhood-CM7Jgb51.mjs.map → neighborhood-CETR_P4y.mjs.map} +1 -1
- package/dist/{note-context-DCwE2A-M.mjs → note-context-DCcRWBoN.mjs} +1 -1
- package/dist/{note-context-DCwE2A-M.mjs.map → note-context-DCcRWBoN.mjs.map} +1 -1
- package/dist/pai-home-ChAOupZc.mjs +164 -0
- package/dist/pai-home-ChAOupZc.mjs.map +1 -0
- package/dist/{pai-marker-CHtbJMwJ.mjs → pai-marker-B20KqhA8.mjs} +1 -1
- package/dist/{pai-marker-CHtbJMwJ.mjs.map → pai-marker-B20KqhA8.mjs.map} +1 -1
- package/dist/{planner-D5Ui7DAm.mjs → planner-pXRc-8yt.mjs} +7 -6
- package/dist/{planner-D5Ui7DAm.mjs.map → planner-pXRc-8yt.mjs.map} +1 -1
- package/dist/{postgres-CtOzfoQU.mjs → postgres-CUSXWgTG.mjs} +2 -2
- package/dist/{postgres-CtOzfoQU.mjs.map → postgres-CUSXWgTG.mjs.map} +1 -1
- package/dist/{program-zpoYnJGz.mjs → program-LLNPpxlj.mjs} +8308 -6065
- package/dist/program-LLNPpxlj.mjs.map +1 -0
- package/dist/query-feedback-C7rSfiXU.mjs +3 -0
- package/dist/{query-feedback-Dqz-HC__.mjs → query-feedback-CYyOGi3y.mjs} +20 -6
- package/dist/query-feedback-CYyOGi3y.mjs.map +1 -0
- package/dist/{reranker-CwTCNsgA.mjs → reranker-B5qUTamW.mjs} +1 -1
- package/dist/{reranker-xPm04PXx.mjs → reranker-HHmhZk6v.mjs} +1 -1
- package/dist/{reranker-xPm04PXx.mjs.map → reranker-HHmhZk6v.mjs.map} +1 -1
- package/dist/router-CremHKTI.mjs +3 -0
- package/dist/{router-DD6D4j31.mjs → router-DBAZfE7X.mjs} +2 -2
- package/dist/{router-DD6D4j31.mjs.map → router-DBAZfE7X.mjs.map} +1 -1
- package/dist/{run-D7K5UALn.mjs → run-DDdT9teG.mjs} +530 -210
- package/dist/run-DDdT9teG.mjs.map +1 -0
- package/dist/{runtime-paths-rni52zHX.mjs → runtime-paths-B0P1TvUr.mjs} +1 -1
- package/dist/{runtime-paths-rni52zHX.mjs.map → runtime-paths-B0P1TvUr.mjs.map} +1 -1
- package/dist/{search-Rpk1cSBC.mjs → search-B69cQwDB.mjs} +3 -3
- package/dist/{search-Rpk1cSBC.mjs.map → search-B69cQwDB.mjs.map} +1 -1
- package/dist/{search-CfPpJAWQ.mjs → search-fb-5ovj4.mjs} +2 -2
- package/dist/{server-D4vAieF4.mjs → server-Ch99xt5K.mjs} +15 -14
- package/dist/server-Ch99xt5K.mjs.map +1 -0
- package/dist/skills/Advisor/SKILL.md +3 -3
- package/dist/skills/Providers/SKILL.md +30 -0
- package/dist/skills/Reconstruct/SKILL.md +1 -1
- package/dist/skills/VaultContext/SKILL.md +1 -1
- package/dist/skills/Whisper/SKILL.md +3 -3
- package/dist/skills/Worker/SKILL.md +1 -1
- package/dist/{sources-CS43CpuS.mjs → sources-KBGEA_E6.mjs} +2 -2
- package/dist/{sources-CS43CpuS.mjs.map → sources-KBGEA_E6.mjs.map} +1 -1
- package/dist/{sqlite-Cq3gJcq9.mjs → sqlite-By6r2YhZ.mjs} +3 -3
- package/dist/{sqlite-Cq3gJcq9.mjs.map → sqlite-By6r2YhZ.mjs.map} +1 -1
- package/dist/{state-CY6I-Hnc.mjs → state-DnYGRmS7.mjs} +1 -1
- package/dist/{state-BRdB-Bf1.mjs → state-PJrgfDLW.mjs} +1 -1
- package/dist/{state-BRdB-Bf1.mjs.map → state-PJrgfDLW.mjs.map} +1 -1
- package/dist/{stop-words-Hfu8u22w.mjs → stop-words-BaMEGVeY.mjs} +1 -1
- package/dist/{stop-words-Hfu8u22w.mjs.map → stop-words-BaMEGVeY.mjs.map} +1 -1
- package/dist/{sync-BWbe8JTg.mjs → sync-Btcb6xA4.mjs} +3 -3
- package/dist/{sync-BWbe8JTg.mjs.map → sync-Btcb6xA4.mjs.map} +1 -1
- package/dist/{themes-B7h9YDiy.mjs → themes-BVoAnLZj.mjs} +3 -3
- package/dist/{themes-B7h9YDiy.mjs.map → themes-BVoAnLZj.mjs.map} +1 -1
- package/dist/{tools-BW3NLMPk.mjs → tools-BradfQk7.mjs} +4 -3
- package/dist/{tools-LlzcV_eK.mjs → tools-UrSHIe29.mjs} +30 -233
- package/dist/tools-UrSHIe29.mjs.map +1 -0
- package/dist/{trace-u4hT-pLD.mjs → trace-Cy8quQTm.mjs} +1 -1
- package/dist/{trace-u4hT-pLD.mjs.map → trace-Cy8quQTm.mjs.map} +1 -1
- package/dist/{utils-DhMex3Ox.mjs → utils-BD8g8oSf.mjs} +1 -1
- package/dist/{utils-9Err2RBW.mjs → utils-CJymHkYm.mjs} +1 -1
- package/dist/{utils-9Err2RBW.mjs.map → utils-CJymHkYm.mjs.map} +1 -1
- package/dist/{vault-indexer-mata9S7b.mjs → vault-indexer-C5i-OAyr.mjs} +2 -2
- package/dist/{vault-indexer-mata9S7b.mjs.map → vault-indexer-C5i-OAyr.mjs.map} +1 -1
- package/dist/wakeup-TnGjJY3I.mjs +218 -0
- package/dist/wakeup-TnGjJY3I.mjs.map +1 -0
- package/dist/work-queue-worker-BA5IDBwQ.mjs +567 -0
- package/dist/work-queue-worker-BA5IDBwQ.mjs.map +1 -0
- package/dist/work-queue-worker-DLM2wlqM.mjs +14 -0
- package/dist/{zettelkasten-F0D_XfQ4.mjs → zettelkasten-BBjA7GcP.mjs} +4 -4
- package/dist/{zettelkasten-F0D_XfQ4.mjs.map → zettelkasten-BBjA7GcP.mjs.map} +1 -1
- package/docs/commands/README.md +30 -3
- package/docs/commands/audit.md +88 -0
- package/docs/commands/backup.md +2 -2
- package/docs/commands/clear-names.md +1 -1
- package/docs/commands/config.md +59 -0
- package/docs/commands/daemon.md +1 -1
- package/docs/commands/db.md +1 -1
- package/docs/commands/end.md +1 -1
- package/docs/commands/help.md +1 -1
- package/docs/commands/identity.md +1 -1
- package/docs/commands/kg.md +1 -1
- package/docs/commands/launch.md +45 -0
- package/docs/commands/mcp.md +1 -1
- package/docs/commands/memory.md +3 -3
- package/docs/commands/notify.md +1 -1
- package/docs/commands/observation.md +1 -1
- package/docs/commands/obsidian.md +1 -1
- package/docs/commands/pause.md +1 -1
- package/docs/commands/project.md +46 -1
- package/docs/commands/projects.md +46 -1
- package/docs/commands/registry.md +1 -1
- package/docs/commands/restore.md +1 -1
- package/docs/commands/session.md +1 -1
- package/docs/commands/sessions.md +1 -1
- package/docs/commands/setup.md +1 -1
- package/docs/commands/shell-init.md +1 -1
- package/docs/commands/skill.md +1 -1
- package/docs/commands/task.md +1 -1
- package/docs/commands/topic.md +1 -1
- package/docs/commands/update.md +1 -1
- package/docs/commands/worker.md +95 -6
- package/docs/commands/zettel.md +1 -1
- package/docs/provider-abstraction.md +1 -1
- package/docs/provider-independence-details.md +8 -5
- package/docs/task-bus.md +1 -1
- package/docs/worker.md +136 -83
- package/docs/workers-config.md +446 -0
- package/package.json +4 -2
- package/scripts/build-hooks.mjs +56 -3
- package/src/hooks/pre-compact.sh +5 -2
- package/src/hooks/session-stop.sh +29 -4
- package/src/hooks/ts/capture-all-events.ts +3 -3
- package/src/hooks/ts/lib/advisor-budget.test.ts +98 -0
- package/src/hooks/ts/lib/advisor-budget.ts +90 -0
- package/src/hooks/ts/lib/agent-gate.test.ts +101 -0
- package/src/hooks/ts/lib/agent-gate.ts +112 -0
- package/src/hooks/ts/lib/context-fill.ts +2 -2
- package/src/hooks/ts/lib/edit-gate.test.ts +52 -0
- package/src/hooks/ts/lib/edit-gate.ts +51 -0
- package/src/hooks/ts/lib/mcp-deferred-reminder.test.ts +8 -0
- package/src/hooks/ts/lib/pai-paths.test.ts +93 -0
- package/src/hooks/ts/lib/pai-paths.ts +168 -24
- package/src/hooks/ts/lib/project-utils/index.ts +1 -0
- package/src/hooks/ts/lib/project-utils/paths.ts +5 -4
- package/src/hooks/ts/lib/project-utils.ts +1 -0
- package/src/hooks/ts/post-tool-use/capture-tool-output.ts +2 -2
- package/src/hooks/ts/pre-compact/context-compression-hook.ts +1 -1
- package/src/hooks/ts/pre-tool-use/route-agents-to-worker.ts +46 -99
- package/src/hooks/ts/pre-tool-use/route-edits-to-worker.test.ts +47 -0
- package/src/hooks/ts/pre-tool-use/route-edits-to-worker.ts +96 -0
- package/src/hooks/ts/pre-tool-use/security-validator.ts +2 -1
- package/src/hooks/ts/session-end/capture-session-summary.ts +4 -4
- package/src/hooks/ts/session-start/initialize-session.ts +3 -3
- package/src/hooks/ts/session-start/inject-observations.test.ts +56 -0
- package/src/hooks/ts/session-start/inject-observations.ts +28 -8
- package/src/hooks/ts/session-start/load-core-context.ts +4 -4
- package/src/hooks/ts/session-start/load-project-context.ts +2 -2
- package/src/hooks/ts/session-start/mcp-deferred-reminder.ts +2 -0
- package/src/hooks/ts/session-start/session-commands.ts +4 -0
- package/src/hooks/ts/session-start/session-start-worker-guard.test.ts +36 -0
- package/src/hooks/ts/stop/stop-hook.ts +6 -11
- package/src/hooks/ts/user-prompt/whisper-rules.test.ts +49 -0
- package/src/hooks/ts/user-prompt/whisper-rules.ts +88 -47
- package/statusline-command.sh +277 -116
- package/templates/agent-prefs.example.md +8 -8
- package/templates/claude-md.template.md +1 -1
- package/templates/voices.example.json +3 -3
- package/dist/config-C_ErGddD.mjs +0 -3
- package/dist/config-_lQI_W08.mjs +0 -640
- package/dist/config-_lQI_W08.mjs.map +0 -1
- package/dist/context-handover-cache-pNFdBNrU.mjs +0 -1548
- package/dist/context-handover-cache-pNFdBNrU.mjs.map +0 -1
- package/dist/daemon-B8N1egl0.mjs.map +0 -1
- package/dist/daemon-DHfyoTlk.mjs +0 -23
- package/dist/db-O-cyAPfS.mjs +0 -3
- package/dist/db-XEwJbuGO.mjs +0 -3
- package/dist/detector-C-7KRi42.mjs +0 -5
- package/dist/factory-Dbo8yNPi.mjs +0 -3
- package/dist/fallback-oJl3BaIN.mjs.map +0 -1
- package/dist/main-resolver-BAfOMppW.mjs +0 -7
- package/dist/main-resolver-DnkxPYkr.mjs.map +0 -1
- package/dist/program-zpoYnJGz.mjs.map +0 -1
- package/dist/query-feedback-DGoVRaWD.mjs +0 -3
- package/dist/query-feedback-Dqz-HC__.mjs.map +0 -1
- package/dist/router-BTIGZR2_.mjs +0 -3
- package/dist/run-D7K5UALn.mjs.map +0 -1
- package/dist/server-D4vAieF4.mjs.map +0 -1
- package/dist/tools-LlzcV_eK.mjs.map +0 -1
- package/dist/work-queue-worker-AEmfVE0F.mjs +0 -14
- package/dist/work-queue-worker-DIT-VLSh.mjs +0 -2136
- package/dist/work-queue-worker-DIT-VLSh.mjs.map +0 -1
- /package/dist/{indexer-D7MvSQPY.mjs → indexer-C5Le7XE7.mjs} +0 -0
|
@@ -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.
|
|
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
|
-
"
|
|
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",
|
package/scripts/build-hooks.mjs
CHANGED
|
@@ -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
|
|
10
|
-
*
|
|
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`);
|
package/src/hooks/pre-compact.sh
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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(
|
|
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
|
|
61
|
+
return agentSessionsPath();
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
function getAgentForSession(sessionId: string): string {
|