@tekmidian/pai 0.42.5 → 0.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +9 -9
- package/README.md +26 -8
- package/dist/{aibroker-client-BP74gGwD.mjs → aibroker-client-Bn9SnGMa.mjs} +3 -3
- package/dist/{aibroker-client-BP74gGwD.mjs.map → aibroker-client-Bn9SnGMa.mjs.map} +1 -1
- package/dist/{aibroker-client-C6m71p6O.mjs → aibroker-client-D5NT0S1N.mjs} +1 -1
- package/dist/{auto-route-_5zzJz5q.mjs → auto-route-CcD0OWWB.mjs} +4 -4
- package/dist/{auto-route-_5zzJz5q.mjs.map → auto-route-CcD0OWWB.mjs.map} +1 -1
- package/dist/{chain-DTII7xeL.mjs → chain-CEC1a5mX.mjs} +5 -4
- package/dist/{chain-DTII7xeL.mjs.map → chain-CEC1a5mX.mjs.map} +1 -1
- package/dist/cli/index.mjs +23 -21
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/program.d.mts.map +1 -1
- package/dist/cli/program.mjs +23 -21
- package/dist/{clusters-j0ZuNYp9.mjs → clusters-Cw_P6lO-.mjs} +2 -2
- package/dist/{clusters-j0ZuNYp9.mjs.map → clusters-Cw_P6lO-.mjs.map} +1 -1
- package/dist/{config-B64vFg14.mjs → config-DCu8THKi.mjs} +41 -9
- package/dist/{config-B64vFg14.mjs.map → config-DCu8THKi.mjs.map} +1 -1
- package/dist/config-DeqFqa1N.mjs +3 -0
- package/dist/config-DxMZjpvY.mjs +1289 -0
- package/dist/config-DxMZjpvY.mjs.map +1 -0
- package/dist/context-handover-cache-B9NKSjwR.mjs +3242 -0
- package/dist/context-handover-cache-B9NKSjwR.mjs.map +1 -0
- package/dist/daemon/index.mjs +23 -22
- package/dist/daemon/index.mjs.map +1 -1
- package/dist/{daemon-B8N1egl0.mjs → daemon-BhldpFvO.mjs} +52 -38
- package/dist/daemon-BhldpFvO.mjs.map +1 -0
- package/dist/daemon-QIvCXB5f.mjs +24 -0
- package/dist/daemon-mcp/index.mjs +144 -117
- package/dist/daemon-mcp/index.mjs.map +1 -1
- package/dist/db-BOBfewFH.mjs +3 -0
- package/dist/{db-a1ixZQjr.mjs → db-DJ8rk0VM.mjs} +15 -6
- package/dist/{db-a1ixZQjr.mjs.map → db-DJ8rk0VM.mjs.map} +1 -1
- package/dist/db-DbY2O8QO.mjs +3 -0
- package/dist/{db-Ca5qfsMC.mjs → db-Ddns80yf.mjs} +15 -6
- package/dist/{db-Ca5qfsMC.mjs.map → db-Ddns80yf.mjs.map} +1 -1
- package/dist/{detect-CdaA48EI.mjs → detect-Bf2z-oKB.mjs} +1 -1
- package/dist/{detect-CdaA48EI.mjs.map → detect-Bf2z-oKB.mjs.map} +1 -1
- package/dist/detector-CkwkXuGh.mjs +5 -0
- package/dist/{detector-C73Nxm94.mjs → detector-DWXkeTlk.mjs} +2 -2
- package/dist/{detector-C73Nxm94.mjs.map → detector-DWXkeTlk.mjs.map} +1 -1
- package/dist/{embeddings-CEBGrzwu.mjs → embeddings-ClqKtLZ8.mjs} +1 -1
- package/dist/{embeddings-DOLZnT1X.mjs → embeddings-DvRKcQyt.mjs} +1 -1
- package/dist/{embeddings-DOLZnT1X.mjs.map → embeddings-DvRKcQyt.mjs.map} +1 -1
- package/dist/factory-47Qf_df7.mjs +3 -0
- package/dist/{factory-DEb9NmHJ.mjs → factory-DNYAqjH0.mjs} +8 -8
- package/dist/{factory-DEb9NmHJ.mjs.map → factory-DNYAqjH0.mjs.map} +1 -1
- package/dist/{fallback-oJl3BaIN.mjs → fallback-BQJ8PbCK.mjs} +185 -123
- package/dist/fallback-BQJ8PbCK.mjs.map +1 -0
- package/dist/{helpers-IjZkXBhj.mjs → helpers-crDEr6S2.mjs} +1 -1
- package/dist/{helpers-IjZkXBhj.mjs.map → helpers-crDEr6S2.mjs.map} +1 -1
- package/dist/hooks/capture-all-events.mjs +117 -34
- package/dist/hooks/capture-all-events.mjs.map +4 -4
- package/dist/hooks/capture-session-summary.mjs +110 -33
- package/dist/hooks/capture-session-summary.mjs.map +4 -4
- package/dist/hooks/capture-tool-output.mjs +105 -28
- package/dist/hooks/capture-tool-output.mjs.map +4 -4
- package/dist/hooks/cleanup-session-files.mjs +54 -13
- package/dist/hooks/cleanup-session-files.mjs.map +2 -2
- package/dist/hooks/context-compression-hook.mjs +7565 -122
- package/dist/hooks/context-compression-hook.mjs.map +4 -4
- package/dist/hooks/initialize-session.mjs +55 -14
- package/dist/hooks/initialize-session.mjs.map +2 -2
- package/dist/hooks/inject-observations.mjs +79 -18
- package/dist/hooks/inject-observations.mjs.map +3 -3
- package/dist/hooks/load-core-context.mjs +55 -14
- package/dist/hooks/load-core-context.mjs.map +2 -2
- package/dist/hooks/load-project-context.mjs +207 -122
- package/dist/hooks/load-project-context.mjs.map +4 -4
- package/dist/hooks/mcp-deferred-gate.mjs +54 -13
- package/dist/hooks/mcp-deferred-gate.mjs.map +2 -2
- package/dist/hooks/mcp-deferred-reminder.mjs +9 -0
- package/dist/hooks/mcp-deferred-reminder.mjs.map +3 -3
- package/dist/hooks/observe.mjs +54 -13
- package/dist/hooks/observe.mjs.map +2 -2
- package/dist/hooks/post-compact-inject.mjs +7419 -16
- package/dist/hooks/post-compact-inject.mjs.map +4 -4
- package/dist/hooks/route-agents-to-worker.mjs +7778 -171
- package/dist/hooks/route-agents-to-worker.mjs.map +4 -4
- package/dist/hooks/route-edits-to-worker.mjs +90 -0
- package/dist/hooks/route-edits-to-worker.mjs.map +7 -0
- package/dist/hooks/security-validator.mjs +147 -6
- package/dist/hooks/security-validator.mjs.map +4 -4
- package/dist/hooks/session-commands.mjs +9 -0
- package/dist/hooks/session-commands.mjs.map +3 -3
- package/dist/hooks/stop-hook.mjs +182 -105
- package/dist/hooks/stop-hook.mjs.map +4 -4
- package/dist/hooks/subagent-stop-hook.mjs +3 -0
- package/dist/hooks/subagent-stop-hook.mjs.map +1 -1
- package/dist/hooks/sync-todo-to-md.mjs +54 -13
- package/dist/hooks/sync-todo-to-md.mjs.map +2 -2
- package/dist/hooks/update-tab-on-action.mjs +3 -0
- package/dist/hooks/update-tab-on-action.mjs.map +1 -1
- package/dist/hooks/update-tab-titles.mjs +3 -0
- package/dist/hooks/update-tab-titles.mjs.map +1 -1
- package/dist/hooks/whisper-reinject.mjs +11 -0
- package/dist/hooks/whisper-reinject.mjs.map +3 -3
- package/dist/hooks/whisper-rules.mjs +134 -29
- package/dist/hooks/whisper-rules.mjs.map +4 -4
- package/dist/hooks/worker-proxy.mjs +7737 -117
- package/dist/hooks/worker-proxy.mjs.map +4 -4
- package/dist/hooks/worker-status-line.mjs +7883 -169
- package/dist/hooks/worker-status-line.mjs.map +4 -4
- package/dist/hooks/worker-supervision.mjs +7721 -115
- package/dist/hooks/worker-supervision.mjs.map +4 -4
- package/dist/index.d.mts +4 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +10 -10
- package/dist/{indexer-backend-BPTzKFZU.mjs → indexer-backend-CdvCJCkD.mjs} +3 -3
- package/dist/{indexer-backend-BPTzKFZU.mjs.map → indexer-backend-CdvCJCkD.mjs.map} +1 -1
- package/dist/{ipc-client-BmypMNYk.mjs → ipc-client-C6NryMMb.mjs} +2 -2
- package/dist/{ipc-client-BmypMNYk.mjs.map → ipc-client-C6NryMMb.mjs.map} +1 -1
- package/dist/{kg-entity-r8duqhi9.mjs → kg-entity-BSjM5xZh.mjs} +1 -1
- package/dist/{kg-entity-r8duqhi9.mjs.map → kg-entity-BSjM5xZh.mjs.map} +1 -1
- package/dist/{latent-ideas-CtSca4qG.mjs → latent-ideas-BfL4nCQ3.mjs} +4 -4
- package/dist/{latent-ideas-CtSca4qG.mjs.map → latent-ideas-BfL4nCQ3.mjs.map} +1 -1
- package/dist/{link-boost-Bucnz3n8.mjs → link-boost-C5PaNdzm.mjs} +1 -1
- package/dist/{link-boost-Bucnz3n8.mjs.map → link-boost-C5PaNdzm.mjs.map} +1 -1
- package/dist/main-resolver-CnfnWa8C.mjs +7 -0
- package/dist/{main-resolver-DnkxPYkr.mjs → main-resolver-DvYu9jbl.mjs} +21 -11
- package/dist/main-resolver-DvYu9jbl.mjs.map +1 -0
- package/dist/{migrate-Cjzeefn9.mjs → migrate-CfERwXhY.mjs} +2 -2
- package/dist/{migrate-Cjzeefn9.mjs.map → migrate-CfERwXhY.mjs.map} +1 -1
- package/dist/{neighborhood-CM7Jgb51.mjs → neighborhood-CETR_P4y.mjs} +2 -2
- package/dist/{neighborhood-CM7Jgb51.mjs.map → neighborhood-CETR_P4y.mjs.map} +1 -1
- package/dist/{note-context-DCwE2A-M.mjs → note-context-DCcRWBoN.mjs} +1 -1
- package/dist/{note-context-DCwE2A-M.mjs.map → note-context-DCcRWBoN.mjs.map} +1 -1
- package/dist/pai-home-ChAOupZc.mjs +164 -0
- package/dist/pai-home-ChAOupZc.mjs.map +1 -0
- package/dist/{pai-marker-CHtbJMwJ.mjs → pai-marker-B20KqhA8.mjs} +1 -1
- package/dist/{pai-marker-CHtbJMwJ.mjs.map → pai-marker-B20KqhA8.mjs.map} +1 -1
- package/dist/{planner-D5Ui7DAm.mjs → planner-BlKGgpWI.mjs} +7 -6
- package/dist/{planner-D5Ui7DAm.mjs.map → planner-BlKGgpWI.mjs.map} +1 -1
- package/dist/{postgres-CtOzfoQU.mjs → postgres-CUSXWgTG.mjs} +2 -2
- package/dist/{postgres-CtOzfoQU.mjs.map → postgres-CUSXWgTG.mjs.map} +1 -1
- package/dist/{program-zpoYnJGz.mjs → program-C5jRQ1kg.mjs} +8880 -5913
- package/dist/program-C5jRQ1kg.mjs.map +1 -0
- package/dist/query-feedback-C7rSfiXU.mjs +3 -0
- package/dist/{query-feedback-Dqz-HC__.mjs → query-feedback-CYyOGi3y.mjs} +20 -6
- package/dist/query-feedback-CYyOGi3y.mjs.map +1 -0
- package/dist/{reranker-CwTCNsgA.mjs → reranker-B5qUTamW.mjs} +1 -1
- package/dist/{reranker-xPm04PXx.mjs → reranker-HHmhZk6v.mjs} +1 -1
- package/dist/{reranker-xPm04PXx.mjs.map → reranker-HHmhZk6v.mjs.map} +1 -1
- package/dist/router-CremHKTI.mjs +3 -0
- package/dist/{router-DD6D4j31.mjs → router-DBAZfE7X.mjs} +2 -2
- package/dist/{router-DD6D4j31.mjs.map → router-DBAZfE7X.mjs.map} +1 -1
- package/dist/{run-D7K5UALn.mjs → run-ByBKowN5.mjs} +980 -247
- package/dist/run-ByBKowN5.mjs.map +1 -0
- package/dist/{runtime-paths-rni52zHX.mjs → runtime-paths-B0P1TvUr.mjs} +1 -1
- package/dist/{runtime-paths-rni52zHX.mjs.map → runtime-paths-B0P1TvUr.mjs.map} +1 -1
- package/dist/{search-Rpk1cSBC.mjs → search-B69cQwDB.mjs} +3 -3
- package/dist/{search-Rpk1cSBC.mjs.map → search-B69cQwDB.mjs.map} +1 -1
- package/dist/{search-CfPpJAWQ.mjs → search-fb-5ovj4.mjs} +2 -2
- package/dist/{server-D4vAieF4.mjs → server-Ch99xt5K.mjs} +15 -14
- package/dist/server-Ch99xt5K.mjs.map +1 -0
- package/dist/skills/Advisor/SKILL.md +3 -3
- package/dist/skills/Providers/SKILL.md +30 -0
- package/dist/skills/Reconstruct/SKILL.md +1 -1
- package/dist/skills/VaultContext/SKILL.md +1 -1
- package/dist/skills/Whisper/SKILL.md +3 -3
- package/dist/skills/Worker/SKILL.md +1 -1
- package/dist/{sources-CS43CpuS.mjs → sources-KBGEA_E6.mjs} +2 -2
- package/dist/{sources-CS43CpuS.mjs.map → sources-KBGEA_E6.mjs.map} +1 -1
- package/dist/{sqlite-Cq3gJcq9.mjs → sqlite-By6r2YhZ.mjs} +3 -3
- package/dist/{sqlite-Cq3gJcq9.mjs.map → sqlite-By6r2YhZ.mjs.map} +1 -1
- package/dist/{state-CY6I-Hnc.mjs → state-DnYGRmS7.mjs} +1 -1
- package/dist/{state-BRdB-Bf1.mjs → state-PJrgfDLW.mjs} +1 -1
- package/dist/{state-BRdB-Bf1.mjs.map → state-PJrgfDLW.mjs.map} +1 -1
- package/dist/{stop-words-Hfu8u22w.mjs → stop-words-BaMEGVeY.mjs} +1 -1
- package/dist/{stop-words-Hfu8u22w.mjs.map → stop-words-BaMEGVeY.mjs.map} +1 -1
- package/dist/{sync-BWbe8JTg.mjs → sync-Btcb6xA4.mjs} +3 -3
- package/dist/{sync-BWbe8JTg.mjs.map → sync-Btcb6xA4.mjs.map} +1 -1
- package/dist/{themes-B7h9YDiy.mjs → themes-BVoAnLZj.mjs} +3 -3
- package/dist/{themes-B7h9YDiy.mjs.map → themes-BVoAnLZj.mjs.map} +1 -1
- package/dist/{tools-BW3NLMPk.mjs → tools-CAG2sUjf.mjs} +4 -3
- package/dist/{tools-LlzcV_eK.mjs → tools-DiRC5glA.mjs} +30 -233
- package/dist/tools-DiRC5glA.mjs.map +1 -0
- package/dist/{trace-u4hT-pLD.mjs → trace-Cy8quQTm.mjs} +1 -1
- package/dist/{trace-u4hT-pLD.mjs.map → trace-Cy8quQTm.mjs.map} +1 -1
- package/dist/{utils-DhMex3Ox.mjs → utils-BD8g8oSf.mjs} +1 -1
- package/dist/{utils-9Err2RBW.mjs → utils-CJymHkYm.mjs} +1 -1
- package/dist/{utils-9Err2RBW.mjs.map → utils-CJymHkYm.mjs.map} +1 -1
- package/dist/{vault-indexer-mata9S7b.mjs → vault-indexer-C5i-OAyr.mjs} +2 -2
- package/dist/{vault-indexer-mata9S7b.mjs.map → vault-indexer-C5i-OAyr.mjs.map} +1 -1
- package/dist/wakeup-BfElduEU.mjs +231 -0
- package/dist/wakeup-BfElduEU.mjs.map +1 -0
- package/dist/work-queue-worker-BA5IDBwQ.mjs +567 -0
- package/dist/work-queue-worker-BA5IDBwQ.mjs.map +1 -0
- package/dist/work-queue-worker-DLM2wlqM.mjs +14 -0
- package/dist/{zettelkasten-F0D_XfQ4.mjs → zettelkasten-BBjA7GcP.mjs} +4 -4
- package/dist/{zettelkasten-F0D_XfQ4.mjs.map → zettelkasten-BBjA7GcP.mjs.map} +1 -1
- package/docs/commands/README.md +32 -3
- package/docs/commands/audit.md +113 -0
- package/docs/commands/backup.md +2 -2
- package/docs/commands/clear-names.md +1 -1
- package/docs/commands/config.md +59 -0
- package/docs/commands/daemon.md +1 -1
- package/docs/commands/db.md +1 -1
- package/docs/commands/end.md +1 -1
- package/docs/commands/help.md +1 -1
- package/docs/commands/identity.md +1 -1
- package/docs/commands/kg.md +1 -1
- package/docs/commands/launch.md +45 -0
- package/docs/commands/mcp.md +1 -1
- package/docs/commands/memory.md +3 -3
- package/docs/commands/notify.md +1 -1
- package/docs/commands/observation.md +1 -1
- package/docs/commands/obsidian.md +1 -1
- package/docs/commands/pause.md +1 -1
- package/docs/commands/project.md +46 -1
- package/docs/commands/projects.md +46 -1
- package/docs/commands/registry.md +1 -1
- package/docs/commands/restore.md +1 -1
- package/docs/commands/session.md +1 -1
- package/docs/commands/sessions.md +1 -1
- package/docs/commands/setup.md +1 -1
- package/docs/commands/shell-init.md +1 -1
- package/docs/commands/skill.md +1 -1
- package/docs/commands/task.md +1 -1
- package/docs/commands/topic.md +1 -1
- package/docs/commands/update.md +1 -1
- package/docs/commands/worker.md +97 -6
- package/docs/commands/zettel.md +1 -1
- package/docs/provider-abstraction.md +1 -1
- package/docs/provider-independence-details.md +8 -5
- package/docs/task-bus.md +1 -1
- package/docs/worker.md +136 -83
- package/docs/workers-config.md +446 -0
- package/package.json +4 -2
- package/scripts/build-hooks.mjs +58 -3
- package/src/hooks/pre-compact.sh +5 -2
- package/src/hooks/session-stop.sh +29 -4
- package/src/hooks/ts/capture-all-events.ts +3 -3
- package/src/hooks/ts/lib/advisor-budget.test.ts +98 -0
- package/src/hooks/ts/lib/advisor-budget.ts +90 -0
- package/src/hooks/ts/lib/agent-gate.test.ts +101 -0
- package/src/hooks/ts/lib/agent-gate.ts +112 -0
- package/src/hooks/ts/lib/context-fill.ts +2 -2
- package/src/hooks/ts/lib/edit-gate.test.ts +52 -0
- package/src/hooks/ts/lib/edit-gate.ts +51 -0
- package/src/hooks/ts/lib/mcp-deferred-reminder.test.ts +8 -0
- package/src/hooks/ts/lib/pai-paths.test.ts +93 -0
- package/src/hooks/ts/lib/pai-paths.ts +168 -24
- package/src/hooks/ts/lib/project-utils/index.ts +1 -0
- package/src/hooks/ts/lib/project-utils/paths.ts +5 -4
- package/src/hooks/ts/lib/project-utils.ts +1 -0
- package/src/hooks/ts/post-tool-use/capture-tool-output.ts +2 -2
- package/src/hooks/ts/post-tool-use/whisper-reinject.test.ts +55 -0
- package/src/hooks/ts/post-tool-use/whisper-reinject.ts +3 -0
- package/src/hooks/ts/pre-compact/context-compression-hook.ts +1 -1
- package/src/hooks/ts/pre-tool-use/route-agents-to-worker.ts +46 -99
- package/src/hooks/ts/pre-tool-use/route-edits-to-worker.test.ts +47 -0
- package/src/hooks/ts/pre-tool-use/route-edits-to-worker.ts +96 -0
- package/src/hooks/ts/pre-tool-use/security-validator.ts +2 -1
- package/src/hooks/ts/session-end/capture-session-summary.ts +4 -4
- package/src/hooks/ts/session-start/initialize-session.ts +3 -3
- package/src/hooks/ts/session-start/inject-observations.test.ts +56 -0
- package/src/hooks/ts/session-start/inject-observations.ts +28 -8
- package/src/hooks/ts/session-start/load-core-context.ts +4 -4
- package/src/hooks/ts/session-start/load-project-context.ts +2 -2
- package/src/hooks/ts/session-start/mcp-deferred-reminder.ts +2 -0
- package/src/hooks/ts/session-start/session-commands.ts +4 -0
- package/src/hooks/ts/session-start/session-start-worker-guard.test.ts +36 -0
- package/src/hooks/ts/stop/stop-hook.ts +6 -11
- package/src/hooks/ts/user-prompt/whisper-rules.test.ts +49 -0
- package/src/hooks/ts/user-prompt/whisper-rules.ts +88 -47
- package/src/hooks/ztk-auto.sh +28 -0
- package/src/hooks/ztk-auto.test.ts +72 -0
- package/src/hooks/ztk-passthrough.mjs +71 -0
- package/statusline-command.sh +277 -116
- package/templates/agent-prefs.example.md +8 -8
- package/templates/claude-md.template.md +1 -1
- package/templates/voices.example.json +3 -3
- package/dist/config-C_ErGddD.mjs +0 -3
- package/dist/config-_lQI_W08.mjs +0 -640
- package/dist/config-_lQI_W08.mjs.map +0 -1
- package/dist/context-handover-cache-pNFdBNrU.mjs +0 -1548
- package/dist/context-handover-cache-pNFdBNrU.mjs.map +0 -1
- package/dist/daemon-B8N1egl0.mjs.map +0 -1
- package/dist/daemon-DHfyoTlk.mjs +0 -23
- package/dist/db-O-cyAPfS.mjs +0 -3
- package/dist/db-XEwJbuGO.mjs +0 -3
- package/dist/detector-C-7KRi42.mjs +0 -5
- package/dist/factory-Dbo8yNPi.mjs +0 -3
- package/dist/fallback-oJl3BaIN.mjs.map +0 -1
- package/dist/main-resolver-BAfOMppW.mjs +0 -7
- package/dist/main-resolver-DnkxPYkr.mjs.map +0 -1
- package/dist/program-zpoYnJGz.mjs.map +0 -1
- package/dist/query-feedback-DGoVRaWD.mjs +0 -3
- package/dist/query-feedback-Dqz-HC__.mjs.map +0 -1
- package/dist/router-BTIGZR2_.mjs +0 -3
- package/dist/run-D7K5UALn.mjs.map +0 -1
- package/dist/server-D4vAieF4.mjs.map +0 -1
- package/dist/tools-LlzcV_eK.mjs.map +0 -1
- package/dist/work-queue-worker-AEmfVE0F.mjs +0 -14
- package/dist/work-queue-worker-DIT-VLSh.mjs +0 -2136
- package/dist/work-queue-worker-DIT-VLSh.mjs.map +0 -1
- /package/dist/{indexer-D7MvSQPY.mjs → indexer-C5Le7XE7.mjs} +0 -0
package/ARCHITECTURE.md
CHANGED
|
@@ -43,12 +43,12 @@ Claude Code Session
|
|
|
43
43
|
| Layer | Backend | Location | Purpose |
|
|
44
44
|
|-------|---------|----------|---------|
|
|
45
45
|
| **Registry** | SQLite (always) | `~/.pai/registry.db` | Projects, sessions, tags, aliases, links. Single-writer is fine — only the CLI and daemon write. Uses `better-sqlite3`. |
|
|
46
|
-
| **Memory / Embeddings** | Factory-switchable | PostgreSQL (full) or SQLite (simple) | Text chunks, vector embeddings, file metadata. Chosen at setup time via `~/.
|
|
46
|
+
| **Memory / Embeddings** | Factory-switchable | PostgreSQL (full) or SQLite (simple) | Text chunks, vector embeddings, file metadata. Chosen at setup time via `~/.claude/pai/config.json`. |
|
|
47
47
|
|
|
48
48
|
- **Simple mode (SQLite)**: Zero dependencies. Keyword search (BM25 via FTS5) works immediately. No Docker needed. Best for trying PAI or smaller setups.
|
|
49
49
|
- **Full mode (PostgreSQL + pgvector)**: Semantic search via HNSW vector indexes (768-dim, Snowflake Arctic). GIN indexes for full-text search. Runs in Docker (`pai-pgvector` container, `restart: unless-stopped`). Best for large knowledge bases (100K+ documents).
|
|
50
50
|
|
|
51
|
-
The storage backend is selected during `pai setup` and configured in `~/.
|
|
51
|
+
The storage backend is selected during `pai setup` and configured in `~/.claude/pai/config.json` (`storageBackend: "sqlite"` or `"postgres"`). The factory pattern (`src/storage/factory.ts`) instantiates the correct backend at runtime. Both backends implement the same `StorageInterface` (`src/storage/interface.ts`), so all higher-level code (indexer, search, MCP tools) is backend-agnostic.
|
|
52
52
|
|
|
53
53
|
**Embeddings** — Snowflake Arctic Embed produces 768-dimensional embeddings (PostgreSQL mode only). The daemon generates embeddings asynchronously in the background after initial text indexing, so keyword search is available immediately and semantic search follows within minutes. The embedding process runs at reduced CPU priority (`setPriority(pid, 10)`).
|
|
54
54
|
|
|
@@ -138,9 +138,9 @@ If both commands return healthy output, PAI is running. Open a new Claude Code s
|
|
|
138
138
|
obsidian-vault/ # Symlinked Obsidian vault (if configured)
|
|
139
139
|
backups/ # Timestamped backups from `pai backup`
|
|
140
140
|
|
|
141
|
-
~/.
|
|
141
|
+
~/.claude/pai/
|
|
142
142
|
config.json # Daemon runtime configuration
|
|
143
|
-
voices.json # Voice TTS configuration (optional)
|
|
143
|
+
voices.json # Voice TTS configuration (optional, currently unused)
|
|
144
144
|
|
|
145
145
|
/tmp/
|
|
146
146
|
pai.sock # Unix socket (daemon)
|
|
@@ -272,7 +272,7 @@ L0 and L1 are injected automatically via the `SessionStart` hook. L2 and L3 are
|
|
|
272
272
|
```
|
|
273
273
|
~/.pai/identity.txt # L0 identity — edit this to describe yourself
|
|
274
274
|
Notes/ # L1 source — the most recent session notes are read here
|
|
275
|
-
~/.
|
|
275
|
+
~/.claude/pai/config.json # wakeupL1Count: N controls how many notes are read for L1
|
|
276
276
|
```
|
|
277
277
|
|
|
278
278
|
### Temporal Knowledge Graph
|
|
@@ -343,7 +343,7 @@ The Stop hook normally fires once at the end of a session. The auto-save feature
|
|
|
343
343
|
PAI_AUTO_SAVE_INTERVAL=15 # default: 15 human messages between saves
|
|
344
344
|
```
|
|
345
345
|
|
|
346
|
-
Set in `~/.
|
|
346
|
+
Set in `~/.claude/pai/config.json` as `autoSaveInterval` or via the environment variable. Set to `0` to disable mid-session saves.
|
|
347
347
|
|
|
348
348
|
**Loop prevention:** The hook sets a `stop_hook_active` flag in the work item. The daemon checks this flag before re-queuing save work. This prevents an auto-save from triggering another auto-save in a feedback loop.
|
|
349
349
|
|
|
@@ -623,7 +623,7 @@ The daemon runs under the label `com.pai.pai-daemon`. The plist is installed to
|
|
|
623
623
|
|
|
624
624
|
## Work Queue and Session Summary Pipeline
|
|
625
625
|
|
|
626
|
-
The daemon owns a persistent work queue (`~/.
|
|
626
|
+
The daemon owns a persistent work queue (`~/.claude/pai/work-queue.json`) that decouples hook triggers from actual work. Hooks push lightweight work items to the queue and exit immediately. The daemon processes items sequentially from a background worker loop.
|
|
627
627
|
|
|
628
628
|
### Work Item Types
|
|
629
629
|
|
|
@@ -898,7 +898,7 @@ A publishable CLAUDE.md template containing generic agent orchestration patterns
|
|
|
898
898
|
|
|
899
899
|
### `templates/agent-prefs.example.md`
|
|
900
900
|
|
|
901
|
-
Personal preferences template covering identity, project mappings, notifications, voice, and git rules. Copy to `~/.
|
|
901
|
+
Personal preferences template covering identity, project mappings, notifications, voice, and git rules. Copy to `~/.claude/pai/agent-prefs.md` and customize.
|
|
902
902
|
|
|
903
903
|
### `templates/voices.example.json`
|
|
904
904
|
|
|
@@ -915,7 +915,7 @@ Voice configuration for TTS integration. Supports Kokoro (local, no API key) and
|
|
|
915
915
|
}
|
|
916
916
|
```
|
|
917
917
|
|
|
918
|
-
Copy to `~/.
|
|
918
|
+
Copy to `~/.claude/pai/voices.json` and configure your preferred backend. Currently unused — no code reads this file's contents yet.
|
|
919
919
|
|
|
920
920
|
---
|
|
921
921
|
|
package/README.md
CHANGED
|
@@ -98,8 +98,26 @@ Only the outer orchestrator session runs on Anthropic. Every worker PAI spawns
|
|
|
98
98
|
### How
|
|
99
99
|
|
|
100
100
|
- **Managed providers.** `pai worker providers add` registers one, `pai worker providers use <name>` switches the fleet, `pai worker off` disables routing entirely (the Agent tool runs on Anthropic again), `pai worker on` re-enables it. The reserved name `anthropic` needs no `add` step — it's Claude Code's own login; `pai worker providers use anthropic` switches straight to it.
|
|
101
|
+
- **Start the harness itself on any provider.** `pai launch` (numbered picker, or `--provider <name> [--model <model>]`) starts a fresh Claude Code session on any provider/model in `workers.yaml` — a running session can't switch providers (base URL and auth are fixed at start), so this always begins a new one. Claude Code's own `/model` only lists the current endpoint's models; `pai launch --list` (or the `/providers` skill, from inside a session) lists every provider configured here.
|
|
101
102
|
- **Classes route work to the right model.** `--class` picks the provider and model for the job: `draft`, `plan`, `implement`, `review`, `research`, `spotcheck`, `simple`, `complex`, `image`. `pai worker classes` shows and edits the mapping; `--provider` / `--model` override for a single run.
|
|
102
103
|
- **Every worker spawn stands alone.** The orchestrator's API key is stripped and the spawn gets the provider's base URL, token and model ids instead — proven live: a worker answers with the parent's credentials gone. No inherited billing, no fallback to the vendor login.
|
|
104
|
+
- **One file to configure it.** Providers, per-role model ids and class routing live in one hand-editable `workers.yaml` — adding a provider (Anthropic-compatible, OpenAI-compatible, or local) is a YAML edit, never code. Full reference: [docs/workers-config.md](docs/workers-config.md).
|
|
105
|
+
|
|
106
|
+
```yaml
|
|
107
|
+
active: anthropic
|
|
108
|
+
providers:
|
|
109
|
+
anthropic:
|
|
110
|
+
builtin: true # Claude Code's own login
|
|
111
|
+
models: { default: claude-sonnet-5, fast: claude-haiku-4-5-20251001 }
|
|
112
|
+
glm:
|
|
113
|
+
url: https://api.z.ai/api/anthropic
|
|
114
|
+
key: "<your-api-key>" # or key_file: <path to a 0600 file>
|
|
115
|
+
tier: 3
|
|
116
|
+
models: { default: glm-5.3[1m], fast: glm-5.3-flash }
|
|
117
|
+
classes:
|
|
118
|
+
implement: anthropic
|
|
119
|
+
spotcheck: anthropic/fast # cheap classes default to the fast model
|
|
120
|
+
```
|
|
103
121
|
|
|
104
122
|
### What
|
|
105
123
|
|
|
@@ -282,7 +300,7 @@ PAI tracks your weekly Claude usage and automatically adjusts subagent model sel
|
|
|
282
300
|
|
|
283
301
|
### How it works
|
|
284
302
|
|
|
285
|
-
The statusline reads your OAuth usage from the Anthropic API (5-hour and 7-day windows) and writes the weekly budget percentage to `~/.claude/advisor-mode.json`. A whisper-rules hook reads this file on every prompt and injects model-tiering guidance.
|
|
303
|
+
The statusline reads your OAuth usage from the Anthropic API (5-hour and 7-day windows) and writes the weekly budget percentage to `~/.claude/pai/advisor-mode.json`. A whisper-rules hook reads this file on every prompt and injects model-tiering guidance.
|
|
286
304
|
|
|
287
305
|
### Automatic thresholds
|
|
288
306
|
|
|
@@ -427,7 +445,7 @@ Run `pai` with no arguments to open a self-contained terminal selector (no `fzf`
|
|
|
427
445
|
project Glidr 2d …/apps/glidr claude pai research
|
|
428
446
|
|
|
429
447
|
────────────────────────────────────────
|
|
430
|
-
Chenarlier
|
|
448
|
+
Chenarlier ~/…/Raspi/Chenarlier
|
|
431
449
|
recent notes:
|
|
432
450
|
10 - Samba Setup/01 - Samba Server Setup.md 1mo
|
|
433
451
|
00 - Monster/00 - Monster.md 3mo
|
|
@@ -740,7 +758,7 @@ When a session ends, PAI generates a structured summary capturing what was reque
|
|
|
740
758
|
|
|
741
759
|
PAI provides a hook that injects user-defined rules into every prompt via `UserPromptSubmit`. Rules survive compaction, `/clear`, and session restarts — they fire on every single turn, making them the most reliable way to enforce behavioral constraints.
|
|
742
760
|
|
|
743
|
-
**PAI ships the mechanism. You provide the rules.** The file `~/.claude/whisper-rules.md` does not exist by default. Use the `/whisper` skill to manage your rules:
|
|
761
|
+
**PAI ships the mechanism. You provide the rules.** The file `~/.claude/pai/whisper-rules.md` does not exist by default. Use the `/whisper` skill to manage your rules:
|
|
744
762
|
|
|
745
763
|
```
|
|
746
764
|
/whisper — show current rules
|
|
@@ -749,7 +767,7 @@ PAI provides a hook that injects user-defined rules into every prompt via `UserP
|
|
|
749
767
|
/whisper list — list with line numbers
|
|
750
768
|
```
|
|
751
769
|
|
|
752
|
-
Or edit `~/.claude/whisper-rules.md` directly — one rule per line, plain text.
|
|
770
|
+
Or edit `~/.claude/pai/whisper-rules.md` directly — one rule per line, plain text.
|
|
753
771
|
|
|
754
772
|
**Keep rules focused.** Every rule is injected on every prompt. Too many rules dilute effectiveness and waste tokens. Reserve whisper rules for truly critical constraints that keep getting violated despite being in CLAUDE.md.
|
|
755
773
|
|
|
@@ -911,7 +929,7 @@ Recency boost is applied after cross-encoder reranking, so relevance is scored f
|
|
|
911
929
|
|
|
912
930
|
### Search Settings
|
|
913
931
|
|
|
914
|
-
All search defaults are configurable via `~/.
|
|
932
|
+
All search defaults are configurable via `~/.claude/pai/config.json` and can be viewed or changed from the command line.
|
|
915
933
|
|
|
916
934
|
```bash
|
|
917
935
|
# View all search settings
|
|
@@ -934,7 +952,7 @@ pai memory settings rerank false
|
|
|
934
952
|
| `defaultLimit` | `10` | Default number of results |
|
|
935
953
|
| `snippetLength` | `200` | Max characters per snippet in MCP results |
|
|
936
954
|
|
|
937
|
-
Settings live in the `search` section of `~/.
|
|
955
|
+
Settings live in the `search` section of `~/.claude/pai/config.json`. Per-call parameters (CLI flags or MCP tool arguments) always override config defaults.
|
|
938
956
|
|
|
939
957
|
### Using Search from Within Claude
|
|
940
958
|
|
|
@@ -978,7 +996,7 @@ You can ask Claude to adjust search behavior per-query:
|
|
|
978
996
|
| `rerank` | boolean | Cross-encoder reranking (default: true from config) |
|
|
979
997
|
| `recency_boost` | integer | Recency half-life in days (0 = off, default from config) |
|
|
980
998
|
|
|
981
|
-
All parameters except `query` are optional. Omitted values fall back to your `~/.
|
|
999
|
+
All parameters except `query` are optional. Omitted values fall back to your `~/.claude/pai/config.json` defaults.
|
|
982
1000
|
|
|
983
1001
|
**Changing defaults permanently:**
|
|
984
1002
|
|
|
@@ -990,7 +1008,7 @@ Tell Claude to change your search settings:
|
|
|
990
1008
|
"Change the recency boost to 60 days"
|
|
991
1009
|
```
|
|
992
1010
|
|
|
993
|
-
Claude runs `pai memory settings <key> <value>` to update `~/.
|
|
1011
|
+
Claude runs `pai memory settings <key> <value>` to update `~/.claude/pai/config.json`. Changes take effect on the next search — no restart needed.
|
|
994
1012
|
|
|
995
1013
|
---
|
|
996
1014
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { t as aibrokerSocketPath } from "./runtime-paths-
|
|
1
|
+
import { t as aibrokerSocketPath } from "./runtime-paths-B0P1TvUr.mjs";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
2
3
|
import { randomUUID } from "node:crypto";
|
|
3
4
|
import { connect } from "node:net";
|
|
4
|
-
import { spawnSync } from "node:child_process";
|
|
5
5
|
|
|
6
6
|
//#region src/cli/lib/aibroker-client.ts
|
|
7
7
|
/**
|
|
@@ -212,4 +212,4 @@ end tell`;
|
|
|
212
212
|
|
|
213
213
|
//#endregion
|
|
214
214
|
export { switchToSession as a, sendToSession as i, fetchLiveSessions as n, revealItermSession as r, callAiBroker as t };
|
|
215
|
-
//# sourceMappingURL=aibroker-client-
|
|
215
|
+
//# sourceMappingURL=aibroker-client-Bn9SnGMa.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aibroker-client-BP74gGwD.mjs","names":[],"sources":["../src/cli/lib/aibroker-client.ts"],"sourcesContent":["/**\n * aibroker-client.ts — Lightweight IPC client for AIBroker daemon.\n *\n * Connects to the AIBroker Unix Domain Socket, sends a JSON-RPC request,\n * and reads a single newline-terminated JSON response. No class needed —\n * just a thin async function matching the WatcherClient protocol.\n *\n * Socket path: /tmp/aibroker.sock (default; override via AIBROKER_SOCKET env).\n */\n\nimport { connect } from \"node:net\";\nimport { randomUUID } from \"node:crypto\";\nimport { spawnSync } from \"node:child_process\";\nimport { aibrokerSocketPath } from \"../../runtime-paths.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Lightweight session metadata returned by the AIBroker `sessions` IPC method.\n * Does NOT include scrollback content — use `session_content` for that.\n */\nexport interface AiBrokerSessionMeta {\n index: number;\n sessionId: string;\n /** iTerm2 tab title or profile name */\n name: string;\n /** PAI session name set via /Name; null for bare shells */\n paiName: string | null;\n atPrompt: boolean;\n /** \"claude\" for Claude Code panes, \"shell\" for bare terminals */\n kind: \"claude\" | \"shell\";\n /** Whether this is the currently focused pane */\n active: boolean;\n /** Last user prompt seen in scrollback (only populated by fetchLiveSessionsWithPrompts) */\n lastPrompt?: string;\n}\n\ninterface AiBrokerSessionsResult {\n sessions: AiBrokerSessionMeta[];\n}\n\n// ---------------------------------------------------------------------------\n// Core call\n// ---------------------------------------------------------------------------\n\n// AIBROKER_SOCKET stays first: it is AIBroker's own variable and a user who has\n// set it means it. The fallback goes through runtime-paths so the test guard\n// can redirect it — writing to a live socket from a test is the same hazard as\n// writing to live user state, and this one is not even ours.\nconst DEFAULT_SOCKET = process.env.AIBROKER_SOCKET ?? aibrokerSocketPath();\n\n/**\n * How long to wait for `send_to_session`, which must exceed the server's own\n * ack window or the caller gives up while the callee is still working.\n *\n * AIBroker waits up to SEND_ACK_TIMEOUT_MS (15s) for the submit confirmation —\n * the text leaving the input line — before answering. The generic client\n * timeout is 8s. So every send needing 8-15s to confirm reported\n * \"AIBroker IPC call timed out\" while succeeding, and the handler's eventual\n * `delivered: true` was written into a socket nobody was reading any more.\n *\n * Measured on `pai pause all` across 15 sessions: 9 reported failed, 8 of them\n * verifiably paused. A caller's deadline shorter than the callee's is not a\n * tuning question, it is a guaranteed false negative on every slow success.\n */\nconst SEND_TIMEOUT_MS = 30_000;\n\n/**\n * Call an AIBroker IPC method and return the result.\n *\n * Resolves with the `result` field of a successful response.\n * Rejects if the socket is not available, the call times out, or the\n * daemon returns an error.\n *\n * @param method IPC method name (e.g. \"session_content\", \"send_to_session\")\n * @param params Method parameters object\n * @param timeoutMs Connection + response timeout in milliseconds (default: 8 000)\n */\nexport function callAiBroker(\n method: string,\n params: Record<string, unknown> = {},\n timeoutMs = 8_000\n): Promise<Record<string, unknown>> {\n return new Promise((resolve, reject) => {\n const socketPath = DEFAULT_SOCKET;\n let done = false;\n let buffer = \"\";\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n function finish(err: Error | null, value?: Record<string, unknown>): void {\n if (done) return;\n done = true;\n if (timer !== null) {\n clearTimeout(timer);\n timer = null;\n }\n try {\n socket.destroy();\n } catch {\n /* ignore */\n }\n if (err) reject(err);\n else resolve(value!);\n }\n\n const socket = connect(socketPath, () => {\n const request = {\n id: randomUUID(),\n sessionId: process.env.TERM_SESSION_ID ?? \"pai-cli\",\n method,\n params,\n };\n const itermId = process.env.ITERM_SESSION_ID;\n if (itermId) Object.assign(request, { itermSessionId: itermId });\n socket.write(JSON.stringify(request) + \"\\n\");\n });\n\n socket.on(\"data\", (chunk: Buffer) => {\n buffer += chunk.toString(\"utf8\");\n const nl = buffer.indexOf(\"\\n\");\n if (nl === -1) return;\n const line = buffer.slice(0, nl);\n\n let response: { ok: boolean; result?: Record<string, unknown>; error?: string };\n try {\n response = JSON.parse(line);\n } catch {\n finish(new Error(`AIBroker IPC parse error: ${line.slice(0, 120)}`));\n return;\n }\n\n if (!response.ok) {\n finish(new Error(response.error ?? \"AIBroker IPC call failed\"));\n } else {\n finish(null, response.result ?? {});\n }\n });\n\n socket.on(\"error\", (e: NodeJS.ErrnoException) => {\n if (e.code === \"ENOENT\" || e.code === \"ECONNREFUSED\") {\n finish(new Error(\"AIBroker not running (socket not found).\"));\n } else {\n finish(e);\n }\n });\n\n socket.on(\"end\", () => {\n if (!done) finish(new Error(\"AIBroker IPC connection closed before response.\"));\n });\n\n timer = setTimeout(\n () => finish(new Error(\"AIBroker IPC call timed out.\")),\n timeoutMs\n );\n });\n}\n\n// ---------------------------------------------------------------------------\n// Typed helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Fetch all live iTerm2 session metadata from AIBroker via the `sessions` method.\n * Returns an empty array if AIBroker is not running.\n *\n * This is metadata-only (no scrollback). It is faster than `session_content`\n * and the correct source for listing/routing purposes.\n */\nexport async function fetchLiveSessions(): Promise<AiBrokerSessionMeta[]> {\n try {\n const result = await callAiBroker(\"sessions\", {});\n const sessions = (result as unknown as AiBrokerSessionsResult).sessions;\n if (!Array.isArray(sessions)) return [];\n return sessions;\n } catch {\n return [];\n }\n}\n\n/**\n * Fetch live sessions WITH the last user prompt extracted from terminal scrollback.\n *\n * Heavier than `fetchLiveSessions` (one IPC call returning full content for\n * every session), but yields a `lastPrompt` field useful for the unified\n * listing.\n */\nexport async function fetchLiveSessionsWithPrompts(): Promise<\n (AiBrokerSessionMeta & { lastPrompt?: string })[]\n> {\n // Get the basic metadata first (always works, fast).\n const metas = await fetchLiveSessions();\n if (metas.length === 0) return [];\n\n // Then enrich with last prompts via session_content (single call, all sessions).\n try {\n const contentResult = (await callAiBroker(\"session_content\", { lines: 60 })) as {\n sessions?: Array<{ sessionId: string; content?: string }>;\n };\n const contentMap = new Map<string, string>();\n for (const s of contentResult.sessions ?? []) {\n if (s.sessionId && typeof s.content === \"string\") {\n contentMap.set(s.sessionId, s.content);\n }\n }\n return metas.map((m) => {\n const content = contentMap.get(m.sessionId);\n const lastPrompt = content ? extractLastUserPrompt(content) : undefined;\n return { ...m, lastPrompt };\n });\n } catch {\n return metas.map((m) => ({ ...m }));\n }\n}\n\n/**\n * Extract the most-recent user prompt from a terminal scrollback string.\n *\n * Claude Code's TUI shows user input lines prefixed with `❯ `. We scan from\n * the bottom up, skipping the active input box (between the two horizontal\n * rule lines) and the statusline footer.\n */\nfunction extractLastUserPrompt(content: string): string | undefined {\n const lines = content.split(\"\\n\");\n // Walk bottom-up looking for the most recent user-typed line.\n // Claude Code marks user prompts with one of these prefixes:\n // ❯ <text> (current/recent prompt in the input box or scrollback)\n // > <text> (older variant)\n // The line MUST have non-empty content after the prompt symbol.\n // Skip the active input box (often empty `❯`) and statusline (👋 PAI CC...).\n for (let i = lines.length - 1; i >= 0; i--) {\n const line = lines[i].trim();\n // Match ❯ or > (followed by space) then content\n const m = line.match(/^[❯>]\\s+(.+?)\\s*$/);\n if (!m) continue;\n let text = m[1].trim();\n if (!text) continue;\n // Skip lines that are just box-drawing or known UI noise\n if (/^[─━═]+/.test(text)) continue;\n if (text.startsWith(\"👋\")) continue;\n // Strip ANSI escape sequences that may leak in\n text = text.replace(/\\x1B\\[[0-9;]*[A-Za-z]/g, \"\").trim();\n if (text) return text;\n }\n return undefined;\n}\n\n/**\n * Send text to a specific AIBroker session by its iTerm2 sessionId.\n *\n * The wire keys are `target` and `message`, and neither is negotiable — the\n * handler rejects anything else with \"target is required\" before it looks at\n * the rest. This was written as `{ target: sessionId, message: text }`, which is the shape of\n * THIS function's own parameters rather than the shape of the IPC, so every\n * call failed identically. `pai pause all` was the only caller, so the live\n * path had never once executed: its --dry-run branch returns before sending,\n * and that was the only branch anyone had exercised.\n *\n * The caller must NOT terminate `text` with a newline. The transport sends with\n * `enter: true` and appends the Enter itself, so a trailing \\n submits twice —\n * once for the text and once for an empty prompt.\n */\nexport async function sendToSession(\n sessionId: string,\n text: string,\n timeoutMs = SEND_TIMEOUT_MS\n): Promise<{ ok: boolean; error?: string; timedOut?: boolean }> {\n try {\n await callAiBroker(\"send_to_session\", { target: sessionId, message: text }, timeoutMs);\n return { ok: true };\n } catch (e) {\n const error = String(e);\n // A timeout is NOT a delivery failure, and conflating the two is expensive.\n // The handler deposits into the target's mailbox BEFORE it waits for the\n // submit ack, so a send that times out has still been delivered — only the\n // confirmation is missing. Reported as a plain failure, the natural response\n // is to send again, which is how `pai pause all` produced nested\n // \"carried forward\" blocks in checkpoints that had already been written.\n return { ok: false, error, timedOut: /timed out/i.test(error) };\n }\n}\n\n/**\n * Switch iTerm2 focus to the session identified by `target`.\n *\n * `target` can be a sessionId, paiName, or tab index number (as string).\n * After switching, activates the iTerm2 application itself so the window\n * comes to the foreground.\n *\n * Returns { ok: true } if AIBroker confirmed the switch, or\n * { ok: false, error } if AIBroker is not running or the session was not found.\n */\nexport async function switchToSession(target: string): Promise<{ ok: boolean; error?: string }> {\n try {\n await callAiBroker(\"switch\", { target });\n // Bring iTerm2 itself to the foreground (the IPC only selects the tab)\n const { spawnSync } = await import(\"node:child_process\");\n spawnSync(\"osascript\", [\"-e\", 'tell application \"iTerm\" to activate'], {\n stdio: \"ignore\",\n });\n return { ok: true };\n } catch (e) {\n return { ok: false, error: String(e) };\n }\n}\n\n/**\n * Bring the iTerm2 tab containing a specific session to the front.\n *\n * This is the mechanism AIBroker's screenshot path uses: match the session by\n * its iTerm2 session id (the `sessionId` returned by `fetchLiveSessions`, which\n * is iTerm's own `id of session`), then `select` its window, tab, and session.\n * Unlike the `switch` IPC (which only flips an internal index and never touches\n * iTerm), this actually reveals the tab.\n */\nexport function revealItermSession(itermSessionId: string): { ok: boolean; error?: string } {\n // Strip any \"iterm:\" style prefix, matching AIBroker's stripItermPrefix.\n const id = itermSessionId.replace(/^iterm:/i, \"\").trim();\n const script = `tell application \"iTerm2\"\n activate\n repeat with w in windows\n repeat with t in tabs of w\n repeat with s in sessions of t\n if id of s is \"${id}\" then\n select w\n select t\n select s\n return \"ok\"\n end if\n end repeat\n end repeat\n end repeat\n return \"not-found\"\nend tell`;\n try {\n const r = spawnSync(\"osascript\", [\"-e\", script], { encoding: \"utf8\" });\n if (r.status !== 0) {\n return { ok: false, error: (r.stderr || \"osascript failed\").trim() };\n }\n if ((r.stdout ?? \"\").trim() === \"ok\") return { ok: true };\n return { ok: false, error: \"session not found in any iTerm2 window\" };\n } catch (e) {\n return { ok: false, error: String(e) };\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"aibroker-client-Bn9SnGMa.mjs","names":[],"sources":["../src/cli/lib/aibroker-client.ts"],"sourcesContent":["/**\n * aibroker-client.ts — Lightweight IPC client for AIBroker daemon.\n *\n * Connects to the AIBroker Unix Domain Socket, sends a JSON-RPC request,\n * and reads a single newline-terminated JSON response. No class needed —\n * just a thin async function matching the WatcherClient protocol.\n *\n * Socket path: /tmp/aibroker.sock (default; override via AIBROKER_SOCKET env).\n */\n\nimport { connect } from \"node:net\";\nimport { randomUUID } from \"node:crypto\";\nimport { spawnSync } from \"node:child_process\";\nimport { aibrokerSocketPath } from \"../../runtime-paths.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Lightweight session metadata returned by the AIBroker `sessions` IPC method.\n * Does NOT include scrollback content — use `session_content` for that.\n */\nexport interface AiBrokerSessionMeta {\n index: number;\n sessionId: string;\n /** iTerm2 tab title or profile name */\n name: string;\n /** PAI session name set via /Name; null for bare shells */\n paiName: string | null;\n atPrompt: boolean;\n /** \"claude\" for Claude Code panes, \"shell\" for bare terminals */\n kind: \"claude\" | \"shell\";\n /** Whether this is the currently focused pane */\n active: boolean;\n /** Last user prompt seen in scrollback (only populated by fetchLiveSessionsWithPrompts) */\n lastPrompt?: string;\n}\n\ninterface AiBrokerSessionsResult {\n sessions: AiBrokerSessionMeta[];\n}\n\n// ---------------------------------------------------------------------------\n// Core call\n// ---------------------------------------------------------------------------\n\n// AIBROKER_SOCKET stays first: it is AIBroker's own variable and a user who has\n// set it means it. The fallback goes through runtime-paths so the test guard\n// can redirect it — writing to a live socket from a test is the same hazard as\n// writing to live user state, and this one is not even ours.\nconst DEFAULT_SOCKET = process.env.AIBROKER_SOCKET ?? aibrokerSocketPath();\n\n/**\n * How long to wait for `send_to_session`, which must exceed the server's own\n * ack window or the caller gives up while the callee is still working.\n *\n * AIBroker waits up to SEND_ACK_TIMEOUT_MS (15s) for the submit confirmation —\n * the text leaving the input line — before answering. The generic client\n * timeout is 8s. So every send needing 8-15s to confirm reported\n * \"AIBroker IPC call timed out\" while succeeding, and the handler's eventual\n * `delivered: true` was written into a socket nobody was reading any more.\n *\n * Measured on `pai pause all` across 15 sessions: 9 reported failed, 8 of them\n * verifiably paused. A caller's deadline shorter than the callee's is not a\n * tuning question, it is a guaranteed false negative on every slow success.\n */\nconst SEND_TIMEOUT_MS = 30_000;\n\n/**\n * Call an AIBroker IPC method and return the result.\n *\n * Resolves with the `result` field of a successful response.\n * Rejects if the socket is not available, the call times out, or the\n * daemon returns an error.\n *\n * @param method IPC method name (e.g. \"session_content\", \"send_to_session\")\n * @param params Method parameters object\n * @param timeoutMs Connection + response timeout in milliseconds (default: 8 000)\n */\nexport function callAiBroker(\n method: string,\n params: Record<string, unknown> = {},\n timeoutMs = 8_000\n): Promise<Record<string, unknown>> {\n return new Promise((resolve, reject) => {\n const socketPath = DEFAULT_SOCKET;\n let done = false;\n let buffer = \"\";\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n function finish(err: Error | null, value?: Record<string, unknown>): void {\n if (done) return;\n done = true;\n if (timer !== null) {\n clearTimeout(timer);\n timer = null;\n }\n try {\n socket.destroy();\n } catch {\n /* ignore */\n }\n if (err) reject(err);\n else resolve(value!);\n }\n\n const socket = connect(socketPath, () => {\n const request = {\n id: randomUUID(),\n sessionId: process.env.TERM_SESSION_ID ?? \"pai-cli\",\n method,\n params,\n };\n const itermId = process.env.ITERM_SESSION_ID;\n if (itermId) Object.assign(request, { itermSessionId: itermId });\n socket.write(JSON.stringify(request) + \"\\n\");\n });\n\n socket.on(\"data\", (chunk: Buffer) => {\n buffer += chunk.toString(\"utf8\");\n const nl = buffer.indexOf(\"\\n\");\n if (nl === -1) return;\n const line = buffer.slice(0, nl);\n\n let response: { ok: boolean; result?: Record<string, unknown>; error?: string };\n try {\n response = JSON.parse(line);\n } catch {\n finish(new Error(`AIBroker IPC parse error: ${line.slice(0, 120)}`));\n return;\n }\n\n if (!response.ok) {\n finish(new Error(response.error ?? \"AIBroker IPC call failed\"));\n } else {\n finish(null, response.result ?? {});\n }\n });\n\n socket.on(\"error\", (e: NodeJS.ErrnoException) => {\n if (e.code === \"ENOENT\" || e.code === \"ECONNREFUSED\") {\n finish(new Error(\"AIBroker not running (socket not found).\"));\n } else {\n finish(e);\n }\n });\n\n socket.on(\"end\", () => {\n if (!done) finish(new Error(\"AIBroker IPC connection closed before response.\"));\n });\n\n timer = setTimeout(\n () => finish(new Error(\"AIBroker IPC call timed out.\")),\n timeoutMs\n );\n });\n}\n\n// ---------------------------------------------------------------------------\n// Typed helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Fetch all live iTerm2 session metadata from AIBroker via the `sessions` method.\n * Returns an empty array if AIBroker is not running.\n *\n * This is metadata-only (no scrollback). It is faster than `session_content`\n * and the correct source for listing/routing purposes.\n */\nexport async function fetchLiveSessions(): Promise<AiBrokerSessionMeta[]> {\n try {\n const result = await callAiBroker(\"sessions\", {});\n const sessions = (result as unknown as AiBrokerSessionsResult).sessions;\n if (!Array.isArray(sessions)) return [];\n return sessions;\n } catch {\n return [];\n }\n}\n\n/**\n * Fetch live sessions WITH the last user prompt extracted from terminal scrollback.\n *\n * Heavier than `fetchLiveSessions` (one IPC call returning full content for\n * every session), but yields a `lastPrompt` field useful for the unified\n * listing.\n */\nexport async function fetchLiveSessionsWithPrompts(): Promise<\n (AiBrokerSessionMeta & { lastPrompt?: string })[]\n> {\n // Get the basic metadata first (always works, fast).\n const metas = await fetchLiveSessions();\n if (metas.length === 0) return [];\n\n // Then enrich with last prompts via session_content (single call, all sessions).\n try {\n const contentResult = (await callAiBroker(\"session_content\", { lines: 60 })) as {\n sessions?: Array<{ sessionId: string; content?: string }>;\n };\n const contentMap = new Map<string, string>();\n for (const s of contentResult.sessions ?? []) {\n if (s.sessionId && typeof s.content === \"string\") {\n contentMap.set(s.sessionId, s.content);\n }\n }\n return metas.map((m) => {\n const content = contentMap.get(m.sessionId);\n const lastPrompt = content ? extractLastUserPrompt(content) : undefined;\n return { ...m, lastPrompt };\n });\n } catch {\n return metas.map((m) => ({ ...m }));\n }\n}\n\n/**\n * Extract the most-recent user prompt from a terminal scrollback string.\n *\n * Claude Code's TUI shows user input lines prefixed with `❯ `. We scan from\n * the bottom up, skipping the active input box (between the two horizontal\n * rule lines) and the statusline footer.\n */\nfunction extractLastUserPrompt(content: string): string | undefined {\n const lines = content.split(\"\\n\");\n // Walk bottom-up looking for the most recent user-typed line.\n // Claude Code marks user prompts with one of these prefixes:\n // ❯ <text> (current/recent prompt in the input box or scrollback)\n // > <text> (older variant)\n // The line MUST have non-empty content after the prompt symbol.\n // Skip the active input box (often empty `❯`) and statusline (👋 PAI CC...).\n for (let i = lines.length - 1; i >= 0; i--) {\n const line = lines[i].trim();\n // Match ❯ or > (followed by space) then content\n const m = line.match(/^[❯>]\\s+(.+?)\\s*$/);\n if (!m) continue;\n let text = m[1].trim();\n if (!text) continue;\n // Skip lines that are just box-drawing or known UI noise\n if (/^[─━═]+/.test(text)) continue;\n if (text.startsWith(\"👋\")) continue;\n // Strip ANSI escape sequences that may leak in\n text = text.replace(/\\x1B\\[[0-9;]*[A-Za-z]/g, \"\").trim();\n if (text) return text;\n }\n return undefined;\n}\n\n/**\n * Send text to a specific AIBroker session by its iTerm2 sessionId.\n *\n * The wire keys are `target` and `message`, and neither is negotiable — the\n * handler rejects anything else with \"target is required\" before it looks at\n * the rest. This was written as `{ target: sessionId, message: text }`, which is the shape of\n * THIS function's own parameters rather than the shape of the IPC, so every\n * call failed identically. `pai pause all` was the only caller, so the live\n * path had never once executed: its --dry-run branch returns before sending,\n * and that was the only branch anyone had exercised.\n *\n * The caller must NOT terminate `text` with a newline. The transport sends with\n * `enter: true` and appends the Enter itself, so a trailing \\n submits twice —\n * once for the text and once for an empty prompt.\n */\nexport async function sendToSession(\n sessionId: string,\n text: string,\n timeoutMs = SEND_TIMEOUT_MS\n): Promise<{ ok: boolean; error?: string; timedOut?: boolean }> {\n try {\n await callAiBroker(\"send_to_session\", { target: sessionId, message: text }, timeoutMs);\n return { ok: true };\n } catch (e) {\n const error = String(e);\n // A timeout is NOT a delivery failure, and conflating the two is expensive.\n // The handler deposits into the target's mailbox BEFORE it waits for the\n // submit ack, so a send that times out has still been delivered — only the\n // confirmation is missing. Reported as a plain failure, the natural response\n // is to send again, which is how `pai pause all` produced nested\n // \"carried forward\" blocks in checkpoints that had already been written.\n return { ok: false, error, timedOut: /timed out/i.test(error) };\n }\n}\n\n/**\n * Switch iTerm2 focus to the session identified by `target`.\n *\n * `target` can be a sessionId, paiName, or tab index number (as string).\n * After switching, activates the iTerm2 application itself so the window\n * comes to the foreground.\n *\n * Returns { ok: true } if AIBroker confirmed the switch, or\n * { ok: false, error } if AIBroker is not running or the session was not found.\n */\nexport async function switchToSession(target: string): Promise<{ ok: boolean; error?: string }> {\n try {\n await callAiBroker(\"switch\", { target });\n // Bring iTerm2 itself to the foreground (the IPC only selects the tab)\n const { spawnSync } = await import(\"node:child_process\");\n spawnSync(\"osascript\", [\"-e\", 'tell application \"iTerm\" to activate'], {\n stdio: \"ignore\",\n });\n return { ok: true };\n } catch (e) {\n return { ok: false, error: String(e) };\n }\n}\n\n/**\n * Bring the iTerm2 tab containing a specific session to the front.\n *\n * This is the mechanism AIBroker's screenshot path uses: match the session by\n * its iTerm2 session id (the `sessionId` returned by `fetchLiveSessions`, which\n * is iTerm's own `id of session`), then `select` its window, tab, and session.\n * Unlike the `switch` IPC (which only flips an internal index and never touches\n * iTerm), this actually reveals the tab.\n */\nexport function revealItermSession(itermSessionId: string): { ok: boolean; error?: string } {\n // Strip any \"iterm:\" style prefix, matching AIBroker's stripItermPrefix.\n const id = itermSessionId.replace(/^iterm:/i, \"\").trim();\n const script = `tell application \"iTerm2\"\n activate\n repeat with w in windows\n repeat with t in tabs of w\n repeat with s in sessions of t\n if id of s is \"${id}\" then\n select w\n select t\n select s\n return \"ok\"\n end if\n end repeat\n end repeat\n end repeat\n return \"not-found\"\nend tell`;\n try {\n const r = spawnSync(\"osascript\", [\"-e\", script], { encoding: \"utf8\" });\n if (r.status !== 0) {\n return { ok: false, error: (r.stderr || \"osascript failed\").trim() };\n }\n if ((r.stdout ?? \"\").trim() === \"ok\") return { ok: true };\n return { ok: false, error: \"session not found in any iTerm2 window\" };\n } catch (e) {\n return { ok: false, error: String(e) };\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as switchToSession, i as sendToSession, n as fetchLiveSessions, r as revealItermSession, t as callAiBroker } from "./aibroker-client-
|
|
1
|
+
import { a as switchToSession, i as sendToSession, n as fetchLiveSessions, r as revealItermSession, t as callAiBroker } from "./aibroker-client-Bn9SnGMa.mjs";
|
|
2
2
|
|
|
3
3
|
export { sendToSession };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { r as readPaiMarker } from "./pai-marker-
|
|
2
|
-
import { t as detectProject } from "./detect-
|
|
1
|
+
import { r as readPaiMarker } from "./pai-marker-B20KqhA8.mjs";
|
|
2
|
+
import { t as detectProject } from "./detect-Bf2z-oKB.mjs";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import { dirname, resolve } from "node:path";
|
|
5
5
|
|
|
@@ -26,7 +26,7 @@ async function autoRoute(registryDb, federation, cwd, context) {
|
|
|
26
26
|
const markerResult = findMarkerUpward(registryDb, target);
|
|
27
27
|
if (markerResult) return markerResult;
|
|
28
28
|
if (context && context.trim().length > 0) {
|
|
29
|
-
const { detectTopicShift } = await import("./detector-
|
|
29
|
+
const { detectTopicShift } = await import("./detector-CkwkXuGh.mjs");
|
|
30
30
|
const topicResult = await detectTopicShift(registryDb, federation, {
|
|
31
31
|
context,
|
|
32
32
|
threshold: .5
|
|
@@ -83,4 +83,4 @@ function formatAutoRouteJson(result) {
|
|
|
83
83
|
|
|
84
84
|
//#endregion
|
|
85
85
|
export { autoRoute, formatAutoRouteJson };
|
|
86
|
-
//# sourceMappingURL=auto-route-
|
|
86
|
+
//# sourceMappingURL=auto-route-CcD0OWWB.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto-route-
|
|
1
|
+
{"version":3,"file":"auto-route-CcD0OWWB.mjs","names":[],"sources":["../src/session/auto-route.ts"],"sourcesContent":["/**\n * Auto-route: automatic project routing suggestion on session start.\n *\n * Given a working directory (and optional conversation context), determine\n * which registered project the session belongs to.\n *\n * Strategy (in priority order):\n * 1. Path match — exact or parent-directory match in the project registry\n * 2. Marker walk — walk up from cwd looking for Notes/PAI.md, resolve slug\n * 3. Topic match — BM25 keyword search against memory (requires context text)\n *\n * The function is stateless and works with direct DB access (no daemon\n * required), making it fast and safe to call during session startup.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport type { StorageBackend } from \"../storage/interface.js\";\nimport { resolve, dirname } from \"node:path\";\nimport { existsSync } from \"node:fs\";\nimport { readPaiMarker } from \"../registry/pai-marker.js\";\nimport { detectProject } from \"../cli/commands/detect.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type AutoRouteMethod = \"path\" | \"marker\" | \"topic\";\n\nexport interface AutoRouteResult {\n /** Project slug */\n slug: string;\n /** Human-readable project name */\n display_name: string;\n /** Absolute path to the project root */\n root_path: string;\n /** How the project was detected */\n method: AutoRouteMethod;\n /** Confidence [0,1]: 1.0 for path/marker matches, BM25 fraction for topic */\n confidence: number;\n}\n\n// ---------------------------------------------------------------------------\n// Core function\n// ---------------------------------------------------------------------------\n\n/**\n * Determine which project a session should be routed to.\n *\n * @param registryDb Open PAI registry database\n * @param federation Memory storage backend (needed only for topic fallback)\n * @param cwd Working directory to detect from (defaults to process.cwd())\n * @param context Optional conversation text for topic-based fallback\n * @returns Best project match, or null if nothing matched\n */\nexport async function autoRoute(\n registryDb: Database,\n federation: Database | StorageBackend,\n cwd?: string,\n context?: string\n): Promise<AutoRouteResult | null> {\n const target = resolve(cwd ?? process.cwd());\n\n // -------------------------------------------------------------------------\n // Strategy 1: Path match via registry\n // -------------------------------------------------------------------------\n\n const pathMatch = detectProject(registryDb, target);\n\n if (pathMatch) {\n return {\n slug: pathMatch.slug,\n display_name: pathMatch.display_name,\n root_path: pathMatch.root_path,\n method: \"path\",\n confidence: 1.0,\n };\n }\n\n // -------------------------------------------------------------------------\n // Strategy 2: PAI.md marker file walk\n //\n // Walk up from cwd, checking <dir>/Notes/PAI.md at each level.\n // Once found, resolve the slug against the registry to get full project info.\n // -------------------------------------------------------------------------\n\n const markerResult = findMarkerUpward(registryDb, target);\n if (markerResult) {\n return markerResult;\n }\n\n // -------------------------------------------------------------------------\n // Strategy 3: Topic detection (requires context text)\n // -------------------------------------------------------------------------\n\n if (context && context.trim().length > 0) {\n // Lazy import to avoid bundler pulling in daemon/index.mjs at module load time\n const { detectTopicShift } = await import(\"../topics/detector.js\");\n const topicResult = await detectTopicShift(registryDb, federation, {\n context,\n threshold: 0.5, // Lower threshold for initial routing (vs shift detection)\n });\n\n if (topicResult.suggestedProject && topicResult.confidence > 0) {\n // Look up the full project info from the registry\n const projectRow = registryDb\n .prepare(\n \"SELECT slug, display_name, root_path FROM projects WHERE slug = ? AND status != 'archived'\"\n )\n .get(topicResult.suggestedProject) as\n | { slug: string; display_name: string; root_path: string }\n | undefined;\n\n if (projectRow) {\n return {\n slug: projectRow.slug,\n display_name: projectRow.display_name,\n root_path: projectRow.root_path,\n method: \"topic\",\n confidence: topicResult.confidence,\n };\n }\n }\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Marker walk helper\n// ---------------------------------------------------------------------------\n\n/**\n * Walk up the directory tree from `startDir`, checking each level for a\n * `Notes/PAI.md` file. If found, read the slug and look up the project.\n *\n * Stops at the filesystem root or after 20 levels (safety guard).\n */\nfunction findMarkerUpward(\n registryDb: Database,\n startDir: string\n): AutoRouteResult | null {\n let current = startDir;\n let depth = 0;\n\n while (depth < 20) {\n const markerPath = `${current}/Notes/PAI.md`;\n\n if (existsSync(markerPath)) {\n const marker = readPaiMarker(current);\n\n if (marker && marker.status !== \"archived\") {\n // Resolve slug to full project info in the registry\n const projectRow = registryDb\n .prepare(\n \"SELECT slug, display_name, root_path FROM projects WHERE slug = ? AND status != 'archived'\"\n )\n .get(marker.slug) as\n | { slug: string; display_name: string; root_path: string }\n | undefined;\n\n if (projectRow) {\n return {\n slug: projectRow.slug,\n display_name: projectRow.display_name,\n root_path: projectRow.root_path,\n method: \"marker\",\n confidence: 1.0,\n };\n }\n }\n }\n\n const parent = dirname(current);\n if (parent === current) break; // Reached filesystem root\n current = parent;\n depth++;\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Format helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Format an AutoRouteResult as a human-readable string for CLI output.\n */\nexport function formatAutoRoute(result: AutoRouteResult): string {\n const lines: string[] = [\n `slug: ${result.slug}`,\n `display_name: ${result.display_name}`,\n `root_path: ${result.root_path}`,\n `method: ${result.method}`,\n `confidence: ${(result.confidence * 100).toFixed(0)}%`,\n ];\n return lines.join(\"\\n\");\n}\n\n/**\n * Format an AutoRouteResult as JSON for machine consumption.\n */\nexport function formatAutoRouteJson(result: AutoRouteResult): string {\n return JSON.stringify(result, null, 2);\n}\n"],"mappings":";;;;;;;;;;;;;;;AAsDA,eAAsB,UACpB,YACA,YACA,KACA,SACiC;CACjC,MAAM,SAAS,QAAQ,OAAO,QAAQ,KAAK,CAAC;CAM5C,MAAM,YAAY,cAAc,YAAY,OAAO;AAEnD,KAAI,UACF,QAAO;EACL,MAAM,UAAU;EAChB,cAAc,UAAU;EACxB,WAAW,UAAU;EACrB,QAAQ;EACR,YAAY;EACb;CAUH,MAAM,eAAe,iBAAiB,YAAY,OAAO;AACzD,KAAI,aACF,QAAO;AAOT,KAAI,WAAW,QAAQ,MAAM,CAAC,SAAS,GAAG;EAExC,MAAM,EAAE,qBAAqB,MAAM,OAAO;EAC1C,MAAM,cAAc,MAAM,iBAAiB,YAAY,YAAY;GACjE;GACA,WAAW;GACZ,CAAC;AAEF,MAAI,YAAY,oBAAoB,YAAY,aAAa,GAAG;GAE9D,MAAM,aAAa,WAChB,QACC,6FACD,CACA,IAAI,YAAY,iBAAiB;AAIpC,OAAI,WACF,QAAO;IACL,MAAM,WAAW;IACjB,cAAc,WAAW;IACzB,WAAW,WAAW;IACtB,QAAQ;IACR,YAAY,YAAY;IACzB;;;AAKP,QAAO;;;;;;;;AAaT,SAAS,iBACP,YACA,UACwB;CACxB,IAAI,UAAU;CACd,IAAI,QAAQ;AAEZ,QAAO,QAAQ,IAAI;AAGjB,MAAI,WAFe,GAAG,QAAQ,eAEJ,EAAE;GAC1B,MAAM,SAAS,cAAc,QAAQ;AAErC,OAAI,UAAU,OAAO,WAAW,YAAY;IAE1C,MAAM,aAAa,WAChB,QACC,6FACD,CACA,IAAI,OAAO,KAAK;AAInB,QAAI,WACF,QAAO;KACL,MAAM,WAAW;KACjB,cAAc,WAAW;KACzB,WAAW,WAAW;KACtB,QAAQ;KACR,YAAY;KACb;;;EAKP,MAAM,SAAS,QAAQ,QAAQ;AAC/B,MAAI,WAAW,QAAS;AACxB,YAAU;AACV;;AAGF,QAAO;;;;;AAwBT,SAAgB,oBAAoB,QAAiC;AACnE,QAAO,KAAK,UAAU,QAAQ,MAAM,EAAE"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { v as readWorkersSection } from "./config-DxMZjpvY.mjs";
|
|
2
|
+
import { R as shortText, j as newWorkerId, n as runWorker, x as appendLedger } from "./run-ByBKowN5.mjs";
|
|
3
|
+
import { _ as workersLogDir, m as ledgerPath } from "./server-Ch99xt5K.mjs";
|
|
4
4
|
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
|
|
@@ -125,6 +125,7 @@ async function runChain(opts, deps = {}) {
|
|
|
125
125
|
label: `${baseLabel} · ${stage}`,
|
|
126
126
|
noPane: opts.noPane,
|
|
127
127
|
mcpFlag: opts.mcpFlag,
|
|
128
|
+
specPath: opts.specPath,
|
|
128
129
|
claudeArgs: swapPromptArg(opts.claudeArgs, prompt),
|
|
129
130
|
cwd: opts.cwd,
|
|
130
131
|
parent: chainId,
|
|
@@ -161,4 +162,4 @@ async function runChain(opts, deps = {}) {
|
|
|
161
162
|
|
|
162
163
|
//#endregion
|
|
163
164
|
export { swapPromptArg as n, runChain as t };
|
|
164
|
-
//# sourceMappingURL=chain-
|
|
165
|
+
//# sourceMappingURL=chain-CEC1a5mX.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chain-DTII7xeL.mjs","names":[],"sources":["../src/workers/chain.ts"],"sourcesContent":["/**\n * chain.ts — draft-then-implement chains: `--chain draft,implement[,review]`.\n *\n * The chain runs each stage as its own worker (own id, own pane, `parent` set\n * to the chain id), so `ps` shows the chain as a tree and every stage can be\n * followed, replayed and said to like any other worker:\n *\n * - draft turns the operator's brief into a full spec file under\n * <logDir>/specs/<chain id>.md (goal, constraints, files likely\n * touched, acceptance checks, verification commands);\n * - any other stage (implement, plan, …) runs with that spec as its prompt\n * and the original brief attached;\n * - review reads the spec and the working-tree diff and produces the\n * structured report.\n *\n * A stage that fails (or a draft that produces no spec file) stops the chain;\n * the caller then writes the spec itself and re-runs without the draft stage.\n */\n\nimport { existsSync, mkdirSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { readWorkersSection } from \"./config.js\";\nimport { appendLedger } from \"./ledger.js\";\nimport { ledgerPath, workersLogDir } from \"./paths.js\";\nimport { newWorkerId } from \"./status.js\";\nimport { shortText } from \"./args.js\";\nimport { runWorker, type RunOptions } from \"./run.js\";\n\n/** Where a chain's spec file lives: <logDir>/specs/<chain id>.md. */\nexport function specPathFor(logDir: string, chainId: string): string {\n return join(logDir, \"specs\", `${chainId}.md`);\n}\n\n/**\n * Replace the caller's -p value with `prompt`, dropping every existing\n * -p/--print pair first (two -p flags on one claude command line are an error,\n * so the chain must never leave the brief in place when swapping prompts).\n */\nexport function swapPromptArg(claudeArgs: string[], prompt: string): string[] {\n const out: string[] = [];\n for (let i = 0; i < claudeArgs.length; i++) {\n const a = claudeArgs[i];\n if (a === \"-p\" || a === \"--print\") {\n if (i + 1 < claudeArgs.length && !claudeArgs[i + 1].startsWith(\"-\")) i += 1;\n continue;\n }\n out.push(a);\n }\n out.push(\"-p\", prompt);\n return out;\n}\n\nexport function draftPrompt(brief: string, specPath: string): string {\n return [\n \"You are the DRAFT stage of a worker chain. Turn the operator's brief below into a full implementation spec and write it to\",\n specPath,\n \"with the Write tool.\",\n \"\",\n \"Use exactly these five sections as Markdown headings:\",\n \"# Goal\",\n \"# Constraints\",\n \"# Files likely touched\",\n \"# Acceptance checks\",\n \"# Verification commands\",\n \"\",\n \"Read the repository first (Glob/Grep/Read) so the spec names real files and real commands. Do not implement anything.\",\n \"\",\n \"## Operator brief\",\n \"\",\n brief,\n ].join(\"\\n\");\n}\n\nexport function implementPrompt(brief: string, specPath: string | null, spec: string | null): string {\n const head = spec\n ? [\n \"You are the IMPLEMENT stage of a worker chain. The draft stage wrote the spec below (also at \" +\n specPath +\n \"). Implement it exactly, then run the spec's verification commands before finishing.\",\n \"\",\n \"## Spec\",\n \"\",\n spec,\n ]\n : [\"Implement the operator's brief below.\"];\n return [...head, \"\", \"## Operator brief\", \"\", brief].join(\"\\n\");\n}\n\nexport function reviewPrompt(brief: string, specPath: string | null, spec: string | null): string {\n const specPart = spec\n ? [\n \"\",\n \"## Spec (also at \" + specPath + \")\",\n \"\",\n spec,\n ]\n : [];\n return [\n \"You are the REVIEW stage of a worker chain. The implement stage just ran. Read the repository's diff (run `git diff` and `git status`; use `git diff --stat` for the overview) and check it against the spec's acceptance checks and verification commands — run the checks when they are cheap. Do not fix anything you find; report it.\",\n ...specPart,\n \"\",\n \"## Operator brief\",\n \"\",\n brief,\n ].join(\"\\n\");\n}\n\nexport interface ChainOptions {\n /** Class names, run in order: e.g. [\"draft\", \"implement\", \"review\"]. */\n stages: string[];\n /** Overrides the class of every stage when given (--class with --chain). */\n className?: string;\n providerFlag?: string;\n modelFlag?: string;\n label?: string;\n noPane?: boolean;\n mcpFlag?: string;\n /** The operator's brief — the -p value of the run. */\n brief: string;\n /** The caller's claude args (allowedTools etc.); the -p value is swapped. */\n claudeArgs: string[];\n cwd?: string;\n /** Internal: notified with the chain id once it exists (worker_run uses it). */\n onChainStart?: (chainId: string) => void;\n /** Internal: suppress result printing (the MCP shim's stdout is the RPC channel). */\n quiet?: boolean;\n}\n\nexport interface ChainDeps {\n /** Stage runner; tests inject a mock, production uses runWorker. */\n runStage?: (opts: RunOptions) => Promise<number>;\n /** logDir override for tests; default: the configured workers logDir. */\n logDir?: string;\n}\n\n/** Run a chain of stages; returns the exit code of the first failed stage, 0 when all pass. */\nexport async function runChain(opts: ChainOptions, deps: ChainDeps = {}): Promise<number> {\n const stages = opts.stages.map((s) => s.trim()).filter(Boolean);\n if (!stages.length) throw new Error(\"--chain needs at least one class, e.g. --chain draft,implement\");\n const runStage = deps.runStage ?? runWorker;\n const logDir = deps.logDir ?? workersLogDir(readWorkersSection().workers);\n const chainId = newWorkerId();\n const specPath = specPathFor(logDir, chainId);\n mkdirSync(join(logDir, \"specs\"), { recursive: true }); // the draft stage writes into it\n const baseLabel = opts.label ?? shortText(opts.brief, 40);\n\n appendLedger(ledgerPath(logDir), \"WORKER-CHAIN\", {\n chain: chainId,\n stages: stages.join(\",\"),\n label: baseLabel,\n });\n opts.onChainStart?.(chainId);\n\n let spec: string | null = null;\n for (let i = 0; i < stages.length; i++) {\n const stage = stages[i];\n if (stage === \"draft\") {\n spec = null; // a chain may legally start over; the draft rewrites it\n } else if (spec === null && existsSync(specPath)) {\n spec = readFileSync(specPath, \"utf8\");\n }\n const prompt =\n stage === \"draft\"\n ? draftPrompt(opts.brief, specPath)\n : stage === \"review\"\n ? reviewPrompt(opts.brief, spec ? specPath : null, spec)\n : implementPrompt(opts.brief, spec ? specPath : null, spec);\n process.stderr.write(\n `chain ${chainId}: stage ${i + 1}/${stages.length} ${stage} (spec: ${specPath})\\n`\n );\n const rc = await runStage({\n className: opts.className ?? stage,\n providerFlag: opts.providerFlag,\n modelFlag: opts.modelFlag,\n label: `${baseLabel} · ${stage}`,\n noPane: opts.noPane,\n mcpFlag: opts.mcpFlag,\n claudeArgs: swapPromptArg(opts.claudeArgs, prompt),\n cwd: opts.cwd,\n parent: chainId,\n stage,\n quiet: opts.quiet,\n });\n if (stage === \"draft\") {\n if (!existsSync(specPath)) {\n process.stderr.write(\n `chain ${chainId}: draft stage produced no spec at ${specPath} — stopping. ` +\n `Write the spec yourself and re-run without the draft stage.\\n`\n );\n appendLedger(ledgerPath(logDir), \"WORKER-CHAIN-END\", {\n chain: chainId,\n rc: rc !== 0 ? rc : 1,\n failed: \"draft\",\n });\n return rc !== 0 ? rc : 1;\n }\n spec = readFileSync(specPath, \"utf8\");\n }\n if (rc !== 0) {\n appendLedger(ledgerPath(logDir), \"WORKER-CHAIN-END\", {\n chain: chainId,\n rc,\n failed: stage,\n });\n return rc;\n }\n }\n appendLedger(ledgerPath(logDir), \"WORKER-CHAIN-END\", { chain: chainId, rc: 0 });\n return 0;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chain-CEC1a5mX.mjs","names":[],"sources":["../src/workers/chain.ts"],"sourcesContent":["/**\n * chain.ts — draft-then-implement chains: `--chain draft,implement[,review]`.\n *\n * The chain runs each stage as its own worker (own id, own pane, `parent` set\n * to the chain id), so `ps` shows the chain as a tree and every stage can be\n * followed, replayed and said to like any other worker:\n *\n * - draft turns the operator's brief into a full spec file under\n * <logDir>/specs/<chain id>.md (goal, constraints, files likely\n * touched, acceptance checks, verification commands);\n * - any other stage (implement, plan, …) runs with that spec as its prompt\n * and the original brief attached;\n * - review reads the spec and the working-tree diff and produces the\n * structured report.\n *\n * A stage that fails (or a draft that produces no spec file) stops the chain;\n * the caller then writes the spec itself and re-runs without the draft stage.\n */\n\nimport { existsSync, mkdirSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { readWorkersSection } from \"./config.js\";\nimport { appendLedger } from \"./ledger.js\";\nimport { ledgerPath, workersLogDir } from \"./paths.js\";\nimport { newWorkerId } from \"./status.js\";\nimport { shortText } from \"./args.js\";\nimport { runWorker, type RunOptions } from \"./run.js\";\n\n/** Where a chain's spec file lives: <logDir>/specs/<chain id>.md. */\nexport function specPathFor(logDir: string, chainId: string): string {\n return join(logDir, \"specs\", `${chainId}.md`);\n}\n\n/**\n * Replace the caller's -p value with `prompt`, dropping every existing\n * -p/--print pair first (two -p flags on one claude command line are an error,\n * so the chain must never leave the brief in place when swapping prompts).\n */\nexport function swapPromptArg(claudeArgs: string[], prompt: string): string[] {\n const out: string[] = [];\n for (let i = 0; i < claudeArgs.length; i++) {\n const a = claudeArgs[i];\n if (a === \"-p\" || a === \"--print\") {\n if (i + 1 < claudeArgs.length && !claudeArgs[i + 1].startsWith(\"-\")) i += 1;\n continue;\n }\n out.push(a);\n }\n out.push(\"-p\", prompt);\n return out;\n}\n\nexport function draftPrompt(brief: string, specPath: string): string {\n return [\n \"You are the DRAFT stage of a worker chain. Turn the operator's brief below into a full implementation spec and write it to\",\n specPath,\n \"with the Write tool.\",\n \"\",\n \"Use exactly these five sections as Markdown headings:\",\n \"# Goal\",\n \"# Constraints\",\n \"# Files likely touched\",\n \"# Acceptance checks\",\n \"# Verification commands\",\n \"\",\n \"Read the repository first (Glob/Grep/Read) so the spec names real files and real commands. Do not implement anything.\",\n \"\",\n \"## Operator brief\",\n \"\",\n brief,\n ].join(\"\\n\");\n}\n\nexport function implementPrompt(brief: string, specPath: string | null, spec: string | null): string {\n const head = spec\n ? [\n \"You are the IMPLEMENT stage of a worker chain. The draft stage wrote the spec below (also at \" +\n specPath +\n \"). Implement it exactly, then run the spec's verification commands before finishing.\",\n \"\",\n \"## Spec\",\n \"\",\n spec,\n ]\n : [\"Implement the operator's brief below.\"];\n return [...head, \"\", \"## Operator brief\", \"\", brief].join(\"\\n\");\n}\n\nexport function reviewPrompt(brief: string, specPath: string | null, spec: string | null): string {\n const specPart = spec\n ? [\n \"\",\n \"## Spec (also at \" + specPath + \")\",\n \"\",\n spec,\n ]\n : [];\n return [\n \"You are the REVIEW stage of a worker chain. The implement stage just ran. Read the repository's diff (run `git diff` and `git status`; use `git diff --stat` for the overview) and check it against the spec's acceptance checks and verification commands — run the checks when they are cheap. Do not fix anything you find; report it.\",\n ...specPart,\n \"\",\n \"## Operator brief\",\n \"\",\n brief,\n ].join(\"\\n\");\n}\n\nexport interface ChainOptions {\n /** Class names, run in order: e.g. [\"draft\", \"implement\", \"review\"]. */\n stages: string[];\n /** Overrides the class of every stage when given (--class with --chain). */\n className?: string;\n providerFlag?: string;\n modelFlag?: string;\n label?: string;\n noPane?: boolean;\n mcpFlag?: string;\n /** --spec value, resolved for display/recording (\"-\" for stdin). */\n specPath?: string;\n /** The operator's brief — the -p value of the run. */\n brief: string;\n /** The caller's claude args (allowedTools etc.); the -p value is swapped. */\n claudeArgs: string[];\n cwd?: string;\n /** Internal: notified with the chain id once it exists (worker_run uses it). */\n onChainStart?: (chainId: string) => void;\n /** Internal: suppress result printing (the MCP shim's stdout is the RPC channel). */\n quiet?: boolean;\n}\n\nexport interface ChainDeps {\n /** Stage runner; tests inject a mock, production uses runWorker. */\n runStage?: (opts: RunOptions) => Promise<number>;\n /** logDir override for tests; default: the configured workers logDir. */\n logDir?: string;\n}\n\n/** Run a chain of stages; returns the exit code of the first failed stage, 0 when all pass. */\nexport async function runChain(opts: ChainOptions, deps: ChainDeps = {}): Promise<number> {\n const stages = opts.stages.map((s) => s.trim()).filter(Boolean);\n if (!stages.length) throw new Error(\"--chain needs at least one class, e.g. --chain draft,implement\");\n const runStage = deps.runStage ?? runWorker;\n const logDir = deps.logDir ?? workersLogDir(readWorkersSection().workers);\n const chainId = newWorkerId();\n const specPath = specPathFor(logDir, chainId);\n mkdirSync(join(logDir, \"specs\"), { recursive: true }); // the draft stage writes into it\n const baseLabel = opts.label ?? shortText(opts.brief, 40);\n\n appendLedger(ledgerPath(logDir), \"WORKER-CHAIN\", {\n chain: chainId,\n stages: stages.join(\",\"),\n label: baseLabel,\n });\n opts.onChainStart?.(chainId);\n\n let spec: string | null = null;\n for (let i = 0; i < stages.length; i++) {\n const stage = stages[i];\n if (stage === \"draft\") {\n spec = null; // a chain may legally start over; the draft rewrites it\n } else if (spec === null && existsSync(specPath)) {\n spec = readFileSync(specPath, \"utf8\");\n }\n const prompt =\n stage === \"draft\"\n ? draftPrompt(opts.brief, specPath)\n : stage === \"review\"\n ? reviewPrompt(opts.brief, spec ? specPath : null, spec)\n : implementPrompt(opts.brief, spec ? specPath : null, spec);\n process.stderr.write(\n `chain ${chainId}: stage ${i + 1}/${stages.length} ${stage} (spec: ${specPath})\\n`\n );\n const rc = await runStage({\n className: opts.className ?? stage,\n providerFlag: opts.providerFlag,\n modelFlag: opts.modelFlag,\n label: `${baseLabel} · ${stage}`,\n noPane: opts.noPane,\n mcpFlag: opts.mcpFlag,\n specPath: opts.specPath,\n claudeArgs: swapPromptArg(opts.claudeArgs, prompt),\n cwd: opts.cwd,\n parent: chainId,\n stage,\n quiet: opts.quiet,\n });\n if (stage === \"draft\") {\n if (!existsSync(specPath)) {\n process.stderr.write(\n `chain ${chainId}: draft stage produced no spec at ${specPath} — stopping. ` +\n `Write the spec yourself and re-run without the draft stage.\\n`\n );\n appendLedger(ledgerPath(logDir), \"WORKER-CHAIN-END\", {\n chain: chainId,\n rc: rc !== 0 ? rc : 1,\n failed: \"draft\",\n });\n return rc !== 0 ? rc : 1;\n }\n spec = readFileSync(specPath, \"utf8\");\n }\n if (rc !== 0) {\n appendLedger(ledgerPath(logDir), \"WORKER-CHAIN-END\", {\n chain: chainId,\n rc,\n failed: stage,\n });\n return rc;\n }\n }\n appendLedger(ledgerPath(logDir), \"WORKER-CHAIN-END\", { chain: chainId, rc: 0 });\n return 0;\n}\n"],"mappings":"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"}
|
package/dist/cli/index.mjs
CHANGED
|
@@ -1,25 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "../db-
|
|
3
|
-
import { i as err } from "../utils-
|
|
4
|
-
import "../migrate-
|
|
5
|
-
import "../db-
|
|
6
|
-
import "../helpers-
|
|
7
|
-
import "../sync-
|
|
8
|
-
import "../embeddings-
|
|
9
|
-
import "../search-
|
|
10
|
-
import { n as drainStdio, t as buildProgram } from "../program-
|
|
11
|
-
import "../context-handover-cache-
|
|
12
|
-
import "../
|
|
13
|
-
import "../
|
|
14
|
-
import "../
|
|
15
|
-
import "../
|
|
16
|
-
import "../
|
|
17
|
-
import "../
|
|
18
|
-
import "../
|
|
19
|
-
import "../
|
|
20
|
-
import "../
|
|
21
|
-
import "../
|
|
22
|
-
import "../
|
|
2
|
+
import "../db-Ddns80yf.mjs";
|
|
3
|
+
import { i as err } from "../utils-CJymHkYm.mjs";
|
|
4
|
+
import "../migrate-CfERwXhY.mjs";
|
|
5
|
+
import "../db-DJ8rk0VM.mjs";
|
|
6
|
+
import "../helpers-crDEr6S2.mjs";
|
|
7
|
+
import "../sync-Btcb6xA4.mjs";
|
|
8
|
+
import "../embeddings-DvRKcQyt.mjs";
|
|
9
|
+
import "../search-B69cQwDB.mjs";
|
|
10
|
+
import { n as drainStdio, t as buildProgram } from "../program-C5jRQ1kg.mjs";
|
|
11
|
+
import "../context-handover-cache-B9NKSjwR.mjs";
|
|
12
|
+
import "../config-DCu8THKi.mjs";
|
|
13
|
+
import "../config-DxMZjpvY.mjs";
|
|
14
|
+
import "../run-ByBKowN5.mjs";
|
|
15
|
+
import "../indexer-C5Le7XE7.mjs";
|
|
16
|
+
import "../ipc-client-C6NryMMb.mjs";
|
|
17
|
+
import "../factory-DNYAqjH0.mjs";
|
|
18
|
+
import "../wakeup-BfElduEU.mjs";
|
|
19
|
+
import "../main-resolver-DvYu9jbl.mjs";
|
|
20
|
+
import "../server-Ch99xt5K.mjs";
|
|
21
|
+
import "../state-PJrgfDLW.mjs";
|
|
22
|
+
import "../aibroker-client-Bn9SnGMa.mjs";
|
|
23
|
+
import "../chain-CEC1a5mX.mjs";
|
|
24
|
+
import "../fallback-BQJ8PbCK.mjs";
|
|
23
25
|
import { CommanderError } from "commander";
|
|
24
26
|
|
|
25
27
|
//#region src/cli/index.ts
|
package/dist/cli/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * PAI Knowledge OS — CLI entry point.\n *\n * Thin entry: builds the Commander program (see ./program.ts) and parses argv.\n * All command construction lives in buildProgram() so the docs generator can\n * introspect the same tree that powers `--help`.\n *\n * Daily surface:\n * pai → deduped session listing (one row per name)\n * pai <name> → universal: switch live tab / resume / fresh\n * pai <uuid-prefix> → direct session resume via filesystem scan\n * pai pause [all] → save state (or mass-pause every live session)\n * pai end → finalize session\n * pai help [area] → rich man page for a command area\n */\n\nimport { CommanderError } from \"commander\";\nimport { buildProgram } from \"./program.js\";\nimport { err } from \"./utils.js\";\nimport { drainStdio } from \"./lib/exit.js\";\n\n// `parse()` does not await async command actions — Commander fires the\n// action and returns immediately, leaving its promise unawaited. When that\n// promise later rejects (e.g. a Todoist call inside `task done`/`task add`\n// throws), it becomes an unhandled rejection racing the process's natural\n// exit: depending on timing, Node either prints the trace and exits 1, or —\n// when nothing else is keeping the event loop alive — wins the race and\n// exits 0 first, silently swallowing both the error and any output the\n// action had queued. `parseAsync` makes `.parse` wait for the action, which\n// turns that race into a deterministic await: any rejection is caught here\n// and reported instead of disappearing.\nawait buildProgram()\n .parseAsync(process.argv)\n .catch((e) => {\n if (e instanceof CommanderError) {\n // program.ts's exitOverride() throws instead of calling process.exit()\n // so this catch is reached for --help, --version, and usage errors\n // too. Commander already wrote the help text or the formatted error\n // message itself (via outputHelp / configureOutput's writeErr) — just\n // adopt its exit code and let the process end naturally. A natural\n // exit flushes stdout/stderr even when either is a pipe; calling\n // process.exit() here would risk truncating output that's still\n // in-flight, same as the bug this replaces.\n process.exitCode = e.exitCode;\n return;\n }\n console.error(err(e instanceof Error ? e.message : String(e)));\n process.exitCode = 1;\n });\n\n// Belt-and-suspenders: explicitly wait for stdout/stderr to drain before this\n// module finishes and Node is free to decide the process is done. Avoiding\n// process.exit() everywhere (see lib/exit.ts) means every command path\n// relies on the event loop staying \"non-empty\" until a pending pipe write\n// completes — true by default, but not something to leave unverified when a\n// dependency's socket/timer/handle could in principle unref itself and let\n// Node consider the loop empty a tick early. This makes the wait explicit\n// and covers every command, not just the error/help paths above.\nawait drainStdio();\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * PAI Knowledge OS — CLI entry point.\n *\n * Thin entry: builds the Commander program (see ./program.ts) and parses argv.\n * All command construction lives in buildProgram() so the docs generator can\n * introspect the same tree that powers `--help`.\n *\n * Daily surface:\n * pai → deduped session listing (one row per name)\n * pai <name> → universal: switch live tab / resume / fresh\n * pai <uuid-prefix> → direct session resume via filesystem scan\n * pai pause [all] → save state (or mass-pause every live session)\n * pai end → finalize session\n * pai help [area] → rich man page for a command area\n */\n\nimport { CommanderError } from \"commander\";\nimport { buildProgram } from \"./program.js\";\nimport { err } from \"./utils.js\";\nimport { drainStdio } from \"./lib/exit.js\";\n\n// `parse()` does not await async command actions — Commander fires the\n// action and returns immediately, leaving its promise unawaited. When that\n// promise later rejects (e.g. a Todoist call inside `task done`/`task add`\n// throws), it becomes an unhandled rejection racing the process's natural\n// exit: depending on timing, Node either prints the trace and exits 1, or —\n// when nothing else is keeping the event loop alive — wins the race and\n// exits 0 first, silently swallowing both the error and any output the\n// action had queued. `parseAsync` makes `.parse` wait for the action, which\n// turns that race into a deterministic await: any rejection is caught here\n// and reported instead of disappearing.\nawait buildProgram()\n .parseAsync(process.argv)\n .catch((e) => {\n if (e instanceof CommanderError) {\n // program.ts's exitOverride() throws instead of calling process.exit()\n // so this catch is reached for --help, --version, and usage errors\n // too. Commander already wrote the help text or the formatted error\n // message itself (via outputHelp / configureOutput's writeErr) — just\n // adopt its exit code and let the process end naturally. A natural\n // exit flushes stdout/stderr even when either is a pipe; calling\n // process.exit() here would risk truncating output that's still\n // in-flight, same as the bug this replaces.\n process.exitCode = e.exitCode;\n return;\n }\n console.error(err(e instanceof Error ? e.message : String(e)));\n process.exitCode = 1;\n });\n\n// Belt-and-suspenders: explicitly wait for stdout/stderr to drain before this\n// module finishes and Node is free to decide the process is done. Avoiding\n// process.exit() everywhere (see lib/exit.ts) means every command path\n// relies on the event loop staying \"non-empty\" until a pending pipe write\n// completes — true by default, but not something to leave unverified when a\n// dependency's socket/timer/handle could in principle unref itself and let\n// Node consider the loop empty a tick early. This makes the wait explicit\n// and covers every command, not just the error/help paths above.\nawait drainStdio();\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,MAAM,cAAc,CACjB,WAAW,QAAQ,KAAK,CACxB,OAAO,MAAM;AACZ,KAAI,aAAa,gBAAgB;AAS/B,UAAQ,WAAW,EAAE;AACrB;;AAEF,SAAQ,MAAM,IAAI,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,CAAC,CAAC;AAC9D,SAAQ,WAAW;EACnB;AAUJ,MAAM,YAAY"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"program.d.mts","names":[],"sources":["../../src/cli/program.ts"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"program.d.mts","names":[],"sources":["../../src/cli/program.ts"],"mappings":";;;;;;;iBAwGgB,YAAA,CAAA,GAAgB,OAAA"}
|
package/dist/cli/program.mjs
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
|
-
import "../db-
|
|
2
|
-
import "../utils-
|
|
3
|
-
import "../migrate-
|
|
4
|
-
import "../db-
|
|
5
|
-
import "../helpers-
|
|
6
|
-
import "../sync-
|
|
7
|
-
import "../embeddings-
|
|
8
|
-
import "../search-
|
|
9
|
-
import { t as buildProgram } from "../program-
|
|
10
|
-
import "../context-handover-cache-
|
|
11
|
-
import "../
|
|
12
|
-
import "../
|
|
13
|
-
import "../
|
|
14
|
-
import "../
|
|
15
|
-
import "../
|
|
16
|
-
import "../
|
|
17
|
-
import "../
|
|
18
|
-
import "../
|
|
19
|
-
import "../
|
|
20
|
-
import "../
|
|
21
|
-
import "../
|
|
1
|
+
import "../db-Ddns80yf.mjs";
|
|
2
|
+
import "../utils-CJymHkYm.mjs";
|
|
3
|
+
import "../migrate-CfERwXhY.mjs";
|
|
4
|
+
import "../db-DJ8rk0VM.mjs";
|
|
5
|
+
import "../helpers-crDEr6S2.mjs";
|
|
6
|
+
import "../sync-Btcb6xA4.mjs";
|
|
7
|
+
import "../embeddings-DvRKcQyt.mjs";
|
|
8
|
+
import "../search-B69cQwDB.mjs";
|
|
9
|
+
import { t as buildProgram } from "../program-C5jRQ1kg.mjs";
|
|
10
|
+
import "../context-handover-cache-B9NKSjwR.mjs";
|
|
11
|
+
import "../config-DCu8THKi.mjs";
|
|
12
|
+
import "../config-DxMZjpvY.mjs";
|
|
13
|
+
import "../run-ByBKowN5.mjs";
|
|
14
|
+
import "../indexer-C5Le7XE7.mjs";
|
|
15
|
+
import "../ipc-client-C6NryMMb.mjs";
|
|
16
|
+
import "../factory-DNYAqjH0.mjs";
|
|
17
|
+
import "../wakeup-BfElduEU.mjs";
|
|
18
|
+
import "../main-resolver-DvYu9jbl.mjs";
|
|
19
|
+
import "../server-Ch99xt5K.mjs";
|
|
20
|
+
import "../state-PJrgfDLW.mjs";
|
|
21
|
+
import "../aibroker-client-Bn9SnGMa.mjs";
|
|
22
|
+
import "../chain-CEC1a5mX.mjs";
|
|
23
|
+
import "../fallback-BQJ8PbCK.mjs";
|
|
22
24
|
|
|
23
25
|
export { buildProgram };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as STOP_WORDS } from "./stop-words-
|
|
1
|
+
import { t as STOP_WORDS } from "./stop-words-BaMEGVeY.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/graph/clusters.ts
|
|
4
4
|
/**
|
|
@@ -198,4 +198,4 @@ async function handleGraphClusters(pool, backend, params) {
|
|
|
198
198
|
|
|
199
199
|
//#endregion
|
|
200
200
|
export { handleGraphClusters };
|
|
201
|
-
//# sourceMappingURL=clusters-
|
|
201
|
+
//# sourceMappingURL=clusters-Cw_P6lO-.mjs.map
|