@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
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* whisper-rules.ts
|
|
5
5
|
*
|
|
6
6
|
* UserPromptSubmit hook that injects:
|
|
7
|
-
* 1. User-defined whisper rules from ~/.claude/whisper-rules.md
|
|
8
|
-
* 2. Budget-aware model tiering guidance from ~/.claude/advisor-mode.json
|
|
7
|
+
* 1. User-defined whisper rules from ~/.claude/pai/whisper-rules.md
|
|
8
|
+
* 2. Budget-aware model tiering guidance from ~/.claude/pai/advisor-mode.json
|
|
9
9
|
*
|
|
10
10
|
* The advisor mode implements the "advisor strategy" pattern:
|
|
11
11
|
* - Normal (budget < 70%): use any model freely
|
|
@@ -13,16 +13,25 @@
|
|
|
13
13
|
* - Strict (85-95%): haiku only for subagents, main context stays on current model
|
|
14
14
|
* - Critical (>95%): minimize all subagent spawning, essential work only
|
|
15
15
|
*
|
|
16
|
-
* Budget percentage is written by the statusline or manually to advisor-mode.json
|
|
17
|
-
* If the file doesn't exist,
|
|
16
|
+
* Budget percentage is written by the statusline or manually to advisor-mode.json,
|
|
17
|
+
* stamped with the epoch second it was read. If the file doesn't exist, or its
|
|
18
|
+
* reading is too old to still describe the current window, no advisor guidance
|
|
19
|
+
* is injected — see lib/advisor-budget.ts for why that direction is the safe one.
|
|
18
20
|
*/
|
|
19
21
|
|
|
20
22
|
import { existsSync, readFileSync, unlinkSync } from "node:fs";
|
|
21
23
|
import { join } from "node:path";
|
|
22
|
-
import {
|
|
24
|
+
import { tmpdir } from "node:os";
|
|
25
|
+
import { resolveAdvisorMode, type AdvisorConfig } from "../lib/advisor-budget.js";
|
|
26
|
+
import { whisperRulesPath, advisorModePath } from "../../../config/pai-files.js";
|
|
23
27
|
|
|
24
|
-
const WHISPER_FILE =
|
|
25
|
-
const ADVISOR_FILE =
|
|
28
|
+
const WHISPER_FILE = whisperRulesPath();
|
|
29
|
+
const ADVISOR_FILE = advisorModePath();
|
|
30
|
+
|
|
31
|
+
/** A "# N. TITLE" section header: the start of a new section, tag reset. */
|
|
32
|
+
function isSectionHeader(line: string): boolean {
|
|
33
|
+
return /^#\s*\d+\.\s/.test(line);
|
|
34
|
+
}
|
|
26
35
|
|
|
27
36
|
/**
|
|
28
37
|
* Read the rule file, dropping everything that is there for the author rather
|
|
@@ -35,28 +44,42 @@ const ADVISOR_FILE = join(homedir(), ".claude", "advisor-mode.json");
|
|
|
35
44
|
* maintainable, and none of it reaches the prompt. Only the rules do.
|
|
36
45
|
*
|
|
37
46
|
* A line whose rule text legitimately starts with "#" can be escaped as "\\#".
|
|
47
|
+
*
|
|
48
|
+
* Section tags: this file is injected into every claude process, spawned
|
|
49
|
+
* workers included, and some rules (e.g. "delegate to workers") are true only
|
|
50
|
+
* for the interactive orchestrating session. A comment line "# @orchestrator"
|
|
51
|
+
* marks every following rule as orchestrator-only, "# @worker" marks
|
|
52
|
+
* worker-only, until the next "# N. TITLE" section header resets the tag (a
|
|
53
|
+
* section's own header/divider box, which the tag line sits inside, does not
|
|
54
|
+
* count as the next section). An untagged section reaches both. `isWorker`
|
|
55
|
+
* selects which side of the tag this process is on — see PAI_WORKER in
|
|
56
|
+
* run-env.ts.
|
|
38
57
|
*/
|
|
39
|
-
function getWhisperRules(): string {
|
|
58
|
+
function getWhisperRules(isWorker: boolean): string {
|
|
40
59
|
if (!existsSync(WHISPER_FILE)) return "";
|
|
41
60
|
try {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
.
|
|
48
|
-
.
|
|
61
|
+
const lines = readFileSync(WHISPER_FILE, "utf-8").split("\n");
|
|
62
|
+
const out: string[] = [];
|
|
63
|
+
let tag: "orchestrator" | "worker" | null = null;
|
|
64
|
+
for (const raw of lines) {
|
|
65
|
+
const line = raw.trim();
|
|
66
|
+
if (line.length === 0) continue;
|
|
67
|
+
if (line.startsWith("#")) {
|
|
68
|
+
if (isSectionHeader(line)) tag = null;
|
|
69
|
+
else if (line === "# @orchestrator") tag = "orchestrator";
|
|
70
|
+
else if (line === "# @worker") tag = "worker";
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (tag === "orchestrator" && isWorker) continue;
|
|
74
|
+
if (tag === "worker" && !isWorker) continue;
|
|
75
|
+
out.push(line.startsWith("\\#") ? line.slice(1) : line);
|
|
76
|
+
}
|
|
77
|
+
return out.join("\n").trim();
|
|
49
78
|
} catch {
|
|
50
79
|
return "";
|
|
51
80
|
}
|
|
52
81
|
}
|
|
53
82
|
|
|
54
|
-
interface AdvisorConfig {
|
|
55
|
-
weeklyBudgetPercent?: number; // 0-100, written by statusline or manually
|
|
56
|
-
mode?: "normal" | "conservative" | "strict" | "critical" | "auto";
|
|
57
|
-
forceModel?: string; // override: always use this model for subagents
|
|
58
|
-
}
|
|
59
|
-
|
|
60
83
|
function getAdvisorGuidance(): string {
|
|
61
84
|
if (!existsSync(ADVISOR_FILE)) return "";
|
|
62
85
|
|
|
@@ -67,24 +90,12 @@ function getAdvisorGuidance(): string {
|
|
|
67
90
|
return "";
|
|
68
91
|
}
|
|
69
92
|
|
|
70
|
-
// Determine mode
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
// that quietly does nothing is worse than none, because you believe you have
|
|
77
|
-
// one. Treat anything that is not a recognised mode as "auto".
|
|
78
|
-
const VALID = ["normal", "conservative", "strict", "critical", "auto"] as const;
|
|
79
|
-
let mode: string =
|
|
80
|
-
config.mode && (VALID as readonly string[]).includes(config.mode) ? config.mode : "auto";
|
|
81
|
-
if (mode === "auto" && typeof config.weeklyBudgetPercent === "number") {
|
|
82
|
-
const pct = config.weeklyBudgetPercent;
|
|
83
|
-
if (pct < 60) mode = "normal";
|
|
84
|
-
else if (pct < 80) mode = "conservative";
|
|
85
|
-
else if (pct < 92) mode = "strict";
|
|
86
|
-
else mode = "critical";
|
|
87
|
-
}
|
|
93
|
+
// Determine mode: the manual one if set, else derived from a budget reading
|
|
94
|
+
// that is current. No current reading and no manual mode means say nothing —
|
|
95
|
+
// guessing is how a wrong number gets obeyed for a day and a half. See
|
|
96
|
+
// lib/advisor-budget.ts.
|
|
97
|
+
const { mode, percent: pct } = resolveAdvisorMode(config, Math.floor(Date.now() / 1000));
|
|
98
|
+
if (mode === undefined) return "";
|
|
88
99
|
|
|
89
100
|
// Force model override
|
|
90
101
|
if (config.forceModel) {
|
|
@@ -97,7 +108,7 @@ function getAdvisorGuidance(): string {
|
|
|
97
108
|
|
|
98
109
|
case "conservative":
|
|
99
110
|
return [
|
|
100
|
-
`ADVISOR MODE (conservative — weekly budget at ${
|
|
111
|
+
`ADVISOR MODE (conservative — weekly budget at ${pct ?? "?"}%):`,
|
|
101
112
|
"Main context is opus (most expensive — 20x haiku, 5x sonnet). Delegate aggressively to subagents.",
|
|
102
113
|
"Default subagents to SONNET (Agent tool, model: sonnet). Use haiku for simple lookups/verification.",
|
|
103
114
|
"For substantial tasks, use swarm mode: spawn a sonnet orchestrator that delegates to haiku workers.",
|
|
@@ -106,7 +117,7 @@ function getAdvisorGuidance(): string {
|
|
|
106
117
|
|
|
107
118
|
case "strict":
|
|
108
119
|
return [
|
|
109
|
-
`ADVISOR MODE (strict — weekly budget at ${
|
|
120
|
+
`ADVISOR MODE (strict — weekly budget at ${pct ?? "?"}%):`,
|
|
110
121
|
"Main context is opus (most expensive — 20x haiku, 5x sonnet). Minimize work done here.",
|
|
111
122
|
"Default subagents to SONNET (Agent tool, model: sonnet) for implementation and research. Use haiku for simple tasks.",
|
|
112
123
|
"For any substantial task, use swarm mode: spawn ONE sonnet orchestrator that delegates to haiku workers.",
|
|
@@ -116,7 +127,7 @@ function getAdvisorGuidance(): string {
|
|
|
116
127
|
|
|
117
128
|
case "critical":
|
|
118
129
|
return [
|
|
119
|
-
`ADVISOR MODE (critical — weekly budget at ${
|
|
130
|
+
`ADVISOR MODE (critical — weekly budget at ${pct ?? "?"}%):`,
|
|
120
131
|
"MINIMIZE ALL TOKEN USAGE. Main context is opus — the most expensive model (20x haiku, 5x sonnet).",
|
|
121
132
|
"For ANY non-trivial task, immediately spawn a sonnet orchestrator agent and let it handle everything.",
|
|
122
133
|
"Main context should only send the task and receive the final result — do not do work here.",
|
|
@@ -153,24 +164,54 @@ function currentLocalTime(): string {
|
|
|
153
164
|
* true after the first turn — a reminder that misstates its own trigger is one
|
|
154
165
|
* the reader learns to discount.
|
|
155
166
|
*/
|
|
156
|
-
function resetReinjectCounter(): void {
|
|
167
|
+
function resetReinjectCounter(sessionId: string): void {
|
|
157
168
|
try {
|
|
158
|
-
const raw = readFileSync(0, "utf-8");
|
|
159
|
-
const sessionId = raw.trim() ? (JSON.parse(raw) as { session_id?: string }).session_id ?? "" : "";
|
|
160
169
|
const safe = sessionId.replace(/[^A-Za-z0-9_-]/g, "") || "nosession";
|
|
161
170
|
const f = join(tmpdir(), "pai-whisper-reinject", `${safe}.count`);
|
|
162
171
|
if (existsSync(f)) unlinkSync(f);
|
|
163
172
|
} catch { /* best effort — a stale count is not worth failing the hook over */ }
|
|
164
173
|
}
|
|
165
174
|
|
|
175
|
+
/**
|
|
176
|
+
* True for prompts this hook never authored a human at the keyboard: a
|
|
177
|
+
* worker-completion relay ("[Session: …] worker … finished …"), a clickr
|
|
178
|
+
* control-handover line ("your controls." / "my controls."), or a harness
|
|
179
|
+
* notification (a "[SYSTEM NOTIFICATION" preamble, or a background task
|
|
180
|
+
* completion carrying a <task-notification> block). All of these fire
|
|
181
|
+
* several times per turn during worker-heavy work, so injecting the ~5KB rule
|
|
182
|
+
* block on each one was landing it 3-5 times in a single turn.
|
|
183
|
+
*/
|
|
184
|
+
export function isRelayedPrompt(prompt: string): boolean {
|
|
185
|
+
return (
|
|
186
|
+
/^\s*\[Session:[^\]]+\]/.test(prompt) ||
|
|
187
|
+
/^\s*(your|my) controls\b/i.test(prompt) ||
|
|
188
|
+
/^\s*\[SYSTEM NOTIFICATION/.test(prompt) ||
|
|
189
|
+
prompt.includes("<task-notification>")
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
166
193
|
function main() {
|
|
167
|
-
|
|
194
|
+
let sessionId = "";
|
|
195
|
+
let prompt = "";
|
|
196
|
+
try {
|
|
197
|
+
const raw = readFileSync(0, "utf-8");
|
|
198
|
+
if (raw.trim()) {
|
|
199
|
+
const input = JSON.parse(raw) as { session_id?: string; prompt?: string };
|
|
200
|
+
sessionId = input.session_id ?? "";
|
|
201
|
+
prompt = input.prompt ?? "";
|
|
202
|
+
}
|
|
203
|
+
} catch { /* malformed/empty stdin — treat as no session, no prompt */ }
|
|
204
|
+
|
|
205
|
+
resetReinjectCounter(sessionId);
|
|
206
|
+
|
|
207
|
+
if (isRelayedPrompt(prompt)) return;
|
|
168
208
|
|
|
209
|
+
const isWorker = process.env.PAI_WORKER === "1";
|
|
169
210
|
const parts: string[] = [];
|
|
170
211
|
|
|
171
212
|
parts.push(`CURRENT LOCAL TIME: ${currentLocalTime()} — use this verbatim for any [YYYY-MM-DD HH:MM] stamp; never estimate or increment it.`);
|
|
172
213
|
|
|
173
|
-
const rules = getWhisperRules();
|
|
214
|
+
const rules = getWhisperRules(isWorker);
|
|
174
215
|
if (rules) parts.push(rules);
|
|
175
216
|
|
|
176
217
|
const advisor = getAdvisorGuidance();
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Wrapper around ztk rewrite that auto-approves and passes through commands
|
|
3
|
+
# whose output ztk would compact lossily (plain-text tools, interpreters,
|
|
4
|
+
# pipelines already tailed/headed/counted, or anything requesting JSON output).
|
|
5
|
+
# Changes permissionDecision from "ask" to "allow" for everything else.
|
|
6
|
+
# Also handles empty responses from ztk (passthrough).
|
|
7
|
+
|
|
8
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
+
stdin="$(cat)"
|
|
10
|
+
|
|
11
|
+
command="$(printf '%s' "$stdin" | jq -r '.tool_input.command // empty' 2>/dev/null)"
|
|
12
|
+
|
|
13
|
+
if [ -n "$command" ]; then
|
|
14
|
+
decision="$(node "$script_dir/ztk-passthrough.mjs" "$command" 2>/dev/null)"
|
|
15
|
+
if [ "$decision" = "yes" ]; then
|
|
16
|
+
exit 0
|
|
17
|
+
fi
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
result=$(printf '%s' "$stdin" | ztk rewrite 2>/dev/null)
|
|
21
|
+
|
|
22
|
+
if [ -z "$result" ]; then
|
|
23
|
+
# ztk returned nothing - allow the command as-is
|
|
24
|
+
echo '{"permissionDecision":"allow"}'
|
|
25
|
+
else
|
|
26
|
+
# ztk returned something - change ask to allow
|
|
27
|
+
echo "$result" | sed 's/"permissionDecision":"ask"/"permissionDecision":"allow"/'
|
|
28
|
+
fi
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { mkdtempSync, writeFileSync, chmodSync, rmSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
|
|
7
|
+
// ztk-auto.sh reads the PreToolUse payload from stdin, decides whether the
|
|
8
|
+
// command should bypass ztk's (lossy, for short/structured output) rewrite
|
|
9
|
+
// step via ztk-passthrough.mjs, and otherwise shells out to the real `ztk`
|
|
10
|
+
// binary. A pass-through decision prints nothing and exits 0 — Claude Code
|
|
11
|
+
// treats a silent PreToolUse hook as "no opinion", i.e. the command runs
|
|
12
|
+
// untouched. A non-pass-through decision always produces JSON output.
|
|
13
|
+
describe("ztk-auto.sh", () => {
|
|
14
|
+
const SCRIPT = "src/hooks/ztk-auto.sh";
|
|
15
|
+
let fakeBinDir: string;
|
|
16
|
+
|
|
17
|
+
// Stub `ztk` so the test doesn't depend on the real binary being installed
|
|
18
|
+
// or on its exact output shape — only that ztk-auto.sh calls it and
|
|
19
|
+
// forwards its (sed-patched) output for non-pass-through commands.
|
|
20
|
+
beforeAll(() => {
|
|
21
|
+
fakeBinDir = mkdtempSync(join(tmpdir(), "ztk-auto-test-bin-"));
|
|
22
|
+
const fakeZtk = join(fakeBinDir, "ztk");
|
|
23
|
+
writeFileSync(
|
|
24
|
+
fakeZtk,
|
|
25
|
+
[
|
|
26
|
+
"#!/bin/bash",
|
|
27
|
+
"cat > /dev/null",
|
|
28
|
+
'echo \'{"permissionDecision":"ask","hookSpecificOutput":{"updatedInput":{"command":"ztk run npm test"}}}\'',
|
|
29
|
+
].join("\n"),
|
|
30
|
+
);
|
|
31
|
+
chmodSync(fakeZtk, 0o755);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
afterAll(() => {
|
|
35
|
+
rmSync(fakeBinDir, { recursive: true, force: true });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
function runHook(command: string): string {
|
|
39
|
+
return execFileSync("bash", [SCRIPT], {
|
|
40
|
+
input: JSON.stringify({
|
|
41
|
+
hook_event_name: "PreToolUse",
|
|
42
|
+
tool_name: "Bash",
|
|
43
|
+
tool_input: { command },
|
|
44
|
+
}),
|
|
45
|
+
encoding: "utf8",
|
|
46
|
+
timeout: 15_000,
|
|
47
|
+
env: { ...process.env, PATH: `${fakeBinDir}:${process.env.PATH}` },
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
it("passes through a plain ls (empty stdout, command runs untouched)", () => {
|
|
52
|
+
expect(runHook("ls -la src")).toBe("");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("sends an unrecognised command to ztk and rewrites it", () => {
|
|
56
|
+
const stdout = runHook("npm test");
|
|
57
|
+
expect(stdout).toContain("ztk run");
|
|
58
|
+
expect(stdout).toContain('"permissionDecision":"allow"');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("passes through a cd-prefixed wc command", () => {
|
|
62
|
+
expect(runHook("cd /x && wc -l a")).toBe("");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("passes through a pipeline whose last stage is tail", () => {
|
|
66
|
+
expect(runHook("cargo build 2>&1 | tail -20")).toBe("");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("passes through a command requesting --json output", () => {
|
|
70
|
+
expect(runHook("pai worker ps --json")).toBe("");
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ztk-passthrough — decide whether a Bash command's output should skip
|
|
4
|
+
* ztk's rewrite/compaction step.
|
|
5
|
+
*
|
|
6
|
+
* ztk compacts aggressively and is lossy for short, already-structured
|
|
7
|
+
* output: a plain `ls` came back with a file misreported as a directory,
|
|
8
|
+
* and `wc -l ... | tail -1` came back summarised instead of verbatim.
|
|
9
|
+
* Interpreters, text-processing tools, anything already piped through
|
|
10
|
+
* head/tail/wc, and anything asking for machine-readable JSON output must
|
|
11
|
+
* keep their exact stdout, so they bypass ztk entirely.
|
|
12
|
+
*
|
|
13
|
+
* Prints "yes" (pass through, skip ztk) or "no" (send to ztk) to stdout.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const PASSTHROUGH_COMMANDS = new Set([
|
|
17
|
+
"ls", "wc", "echo", "printf", "pwd", "which", "whoami", "date",
|
|
18
|
+
"basename", "dirname", "readlink", "stat", "du", "df",
|
|
19
|
+
"head", "tail", "cat", "jq", "sed", "awk", "sort", "uniq", "tr", "cut",
|
|
20
|
+
"true", "test", "[",
|
|
21
|
+
"python3", "node", "bun",
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
const LAST_STAGE_PASSTHROUGH = new Set(["head", "tail", "wc"]);
|
|
25
|
+
|
|
26
|
+
const VAR_ASSIGN = /^[A-Za-z_][A-Za-z0-9_]*=(?:'[^']*'|"[^"]*"|\S*)\s+/;
|
|
27
|
+
const CD_PREFIX = /^cd\s+(?:'[^']*'|"[^"]*"|\S+)\s*&&\s*/;
|
|
28
|
+
const ENV_PREFIX = /^env\s+/;
|
|
29
|
+
const TIMEOUT_PREFIX = /^timeout\s+\S+\s+/;
|
|
30
|
+
|
|
31
|
+
/** Strip leading `cd … &&`, `env …`, `timeout N`, and variable assignments, in any order. */
|
|
32
|
+
function stripLeaders(command) {
|
|
33
|
+
let cur = command.trimStart();
|
|
34
|
+
for (let i = 0; i < 20; i++) {
|
|
35
|
+
const before = cur;
|
|
36
|
+
cur = cur.replace(CD_PREFIX, "").replace(ENV_PREFIX, "").replace(TIMEOUT_PREFIX, "").replace(VAR_ASSIGN, "");
|
|
37
|
+
cur = cur.trimStart();
|
|
38
|
+
if (cur === before) break;
|
|
39
|
+
}
|
|
40
|
+
return cur;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function firstWord(command) {
|
|
44
|
+
return command.trim().split(/\s+/)[0] ?? "";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function lastPipelineStage(command) {
|
|
48
|
+
const stages = command.split("|");
|
|
49
|
+
return stages[stages.length - 1] ?? "";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function shouldPassThrough(rawCommand) {
|
|
53
|
+
const command = rawCommand ?? "";
|
|
54
|
+
if (!command.trim()) return false;
|
|
55
|
+
|
|
56
|
+
if (command.includes("--output-format json") || command.includes("--json")) return true;
|
|
57
|
+
|
|
58
|
+
const lastStageFirstWord = firstWord(lastPipelineStage(command));
|
|
59
|
+
if (LAST_STAGE_PASSTHROUGH.has(lastStageFirstWord)) return true;
|
|
60
|
+
|
|
61
|
+
const stripped = stripLeaders(command);
|
|
62
|
+
const head = firstWord(stripped);
|
|
63
|
+
return PASSTHROUGH_COMMANDS.has(head);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function main() {
|
|
67
|
+
const command = process.argv[2] ?? "";
|
|
68
|
+
console.log(shouldPassThrough(command) ? "yes" : "no");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
main();
|