@tekmidian/pai 0.36.1 → 0.37.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.
Files changed (157) hide show
  1. package/dist/{auto-route-Byf8ENXj.mjs → auto-route-DM7GhJ8y.mjs} +2 -2
  2. package/dist/{auto-route-Byf8ENXj.mjs.map → auto-route-DM7GhJ8y.mjs.map} +1 -1
  3. package/dist/cli/index.mjs +5 -4
  4. package/dist/cli/index.mjs.map +1 -1
  5. package/dist/cli/program.d.mts.map +1 -1
  6. package/dist/cli/program.mjs +5 -4
  7. package/dist/{clusters-wZgTCYCB.mjs → clusters-Do4tEGyc.mjs} +1 -1
  8. package/dist/{clusters-wZgTCYCB.mjs.map → clusters-Do4tEGyc.mjs.map} +1 -1
  9. package/dist/{context-handover-cache-PtNvj_8D.mjs → context-handover-cache-BpUojjsi.mjs} +2 -2
  10. package/dist/{context-handover-cache-PtNvj_8D.mjs.map → context-handover-cache-BpUojjsi.mjs.map} +1 -1
  11. package/dist/daemon/index.mjs +8 -8
  12. package/dist/{daemon-BZ93KRBo.mjs → daemon-CGg1VCbA.mjs} +25 -25
  13. package/dist/{daemon-BZ93KRBo.mjs.map → daemon-CGg1VCbA.mjs.map} +1 -1
  14. package/dist/daemon-DsGGiIJM.mjs +20 -0
  15. package/dist/daemon-mcp/index.mjs +428 -1
  16. package/dist/daemon-mcp/index.mjs.map +1 -1
  17. package/dist/{detector--Gg5JRN5.mjs → detector-CMap-9vw.mjs} +1 -1
  18. package/dist/{detector--Gg5JRN5.mjs.map → detector-CMap-9vw.mjs.map} +1 -1
  19. package/dist/detector-DtLExmHN.mjs +5 -0
  20. package/dist/factory-BXzqRYVZ.mjs +3 -0
  21. package/dist/{factory-Bsp7xOpO.mjs → factory-DD2T33C9.mjs} +5 -5
  22. package/dist/{factory-Bsp7xOpO.mjs.map → factory-DD2T33C9.mjs.map} +1 -1
  23. package/dist/hooks/context-compression-hook.mjs +164 -14
  24. package/dist/hooks/context-compression-hook.mjs.map +4 -4
  25. package/dist/hooks/initialize-session.mjs +6 -0
  26. package/dist/hooks/initialize-session.mjs.map +3 -3
  27. package/dist/hooks/load-core-context.mjs +6 -0
  28. package/dist/hooks/load-core-context.mjs.map +3 -3
  29. package/dist/hooks/load-project-context.mjs +6 -0
  30. package/dist/hooks/load-project-context.mjs.map +3 -3
  31. package/dist/hooks/post-compact-inject.mjs +48 -7
  32. package/dist/hooks/post-compact-inject.mjs.map +4 -4
  33. package/dist/hooks/route-agents-to-worker.mjs +456 -0
  34. package/dist/hooks/route-agents-to-worker.mjs.map +7 -0
  35. package/dist/hooks/status-line.mjs +400 -0
  36. package/dist/hooks/status-line.mjs.map +7 -0
  37. package/dist/hooks/stop-hook.mjs +6 -0
  38. package/dist/hooks/stop-hook.mjs.map +3 -3
  39. package/dist/hooks/subagent-stop-hook.mjs +6 -0
  40. package/dist/hooks/subagent-stop-hook.mjs.map +3 -3
  41. package/dist/hooks/sync-todo-to-md.mjs +6 -0
  42. package/dist/hooks/sync-todo-to-md.mjs.map +3 -3
  43. package/dist/hooks/update-tab-on-action.mjs +6 -0
  44. package/dist/hooks/update-tab-on-action.mjs.map +3 -3
  45. package/dist/hooks/update-tab-titles.mjs +6 -0
  46. package/dist/hooks/update-tab-titles.mjs.map +3 -3
  47. package/dist/hooks/worker-proxy.mjs +796 -0
  48. package/dist/hooks/worker-proxy.mjs.map +7 -0
  49. package/dist/hooks/worker-status-line.mjs +483 -0
  50. package/dist/hooks/worker-status-line.mjs.map +7 -0
  51. package/dist/{indexer-backend-nQZuEx6N.mjs → indexer-backend-Cox9BCo-.mjs} +1 -1
  52. package/dist/{indexer-backend-nQZuEx6N.mjs.map → indexer-backend-Cox9BCo-.mjs.map} +1 -1
  53. package/dist/{kg-entity-DbOMPdF9.mjs → kg-entity-r8duqhi9.mjs} +1 -1
  54. package/dist/{kg-entity-DbOMPdF9.mjs.map → kg-entity-r8duqhi9.mjs.map} +1 -1
  55. package/dist/{latent-ideas-Bn6A5-5P.mjs → latent-ideas-BC1oINZ-.mjs} +2 -2
  56. package/dist/{latent-ideas-Bn6A5-5P.mjs.map → latent-ideas-BC1oINZ-.mjs.map} +1 -1
  57. package/dist/{link-boost-fYjUnxCN.mjs → link-boost-QFLrJwD6.mjs} +1 -1
  58. package/dist/{link-boost-fYjUnxCN.mjs.map → link-boost-QFLrJwD6.mjs.map} +1 -1
  59. package/dist/{main-resolver-BAbhKpeX.mjs → main-resolver-DlaLOFBA.mjs} +1 -1
  60. package/dist/{main-resolver-BAbhKpeX.mjs.map → main-resolver-DlaLOFBA.mjs.map} +1 -1
  61. package/dist/{main-resolver-Dxh444GO.mjs → main-resolver-IhZo4pI0.mjs} +1 -1
  62. package/dist/{neighborhood-DpaEM991.mjs → neighborhood-D9MJ1c8f.mjs} +1 -1
  63. package/dist/{neighborhood-DpaEM991.mjs.map → neighborhood-D9MJ1c8f.mjs.map} +1 -1
  64. package/dist/{note-context-DrcY4cWm.mjs → note-context-d1wT_-GA.mjs} +1 -1
  65. package/dist/{note-context-DrcY4cWm.mjs.map → note-context-d1wT_-GA.mjs.map} +1 -1
  66. package/dist/{postgres-BVme6qX0.mjs → postgres--BjPtLa0.mjs} +1 -1
  67. package/dist/{postgres-BVme6qX0.mjs.map → postgres--BjPtLa0.mjs.map} +1 -1
  68. package/dist/{program-C-fUghPv.mjs → program-CEIHn_Ma.mjs} +693 -101
  69. package/dist/program-CEIHn_Ma.mjs.map +1 -0
  70. package/dist/providers-sXcK5bDZ.mjs +3067 -0
  71. package/dist/providers-sXcK5bDZ.mjs.map +1 -0
  72. package/dist/{query-feedback-C1T6kS18.mjs → query-feedback-BUJxgw5B.mjs} +1 -1
  73. package/dist/{query-feedback-C1T6kS18.mjs.map → query-feedback-BUJxgw5B.mjs.map} +1 -1
  74. package/dist/query-feedback-DhyLOe5S.mjs +3 -0
  75. package/dist/router-1zi8jiNF.mjs +3 -0
  76. package/dist/{router-CsDm7HvK.mjs → router-DK_sLsUL.mjs} +1 -1
  77. package/dist/{router-CsDm7HvK.mjs.map → router-DK_sLsUL.mjs.map} +1 -1
  78. package/dist/skills/Worker/SKILL.md +41 -0
  79. package/dist/{sources-D8ZdNfvK.mjs → sources-Bi7--33T.mjs} +1 -1
  80. package/dist/{sources-D8ZdNfvK.mjs.map → sources-Bi7--33T.mjs.map} +1 -1
  81. package/dist/{sqlite-D1IaR8Am.mjs → sqlite-DtaL1glm.mjs} +1 -1
  82. package/dist/{sqlite-D1IaR8Am.mjs.map → sqlite-DtaL1glm.mjs.map} +1 -1
  83. package/dist/{state-qtmrBWCm.mjs → state-8Hm9E4tW.mjs} +1 -1
  84. package/dist/{state-WaXhLr6R.mjs → state-BY2L6-vX.mjs} +1 -1
  85. package/dist/{state-WaXhLr6R.mjs.map → state-BY2L6-vX.mjs.map} +1 -1
  86. package/dist/{themes-XPkj_bfP.mjs → themes-BN0a2duq.mjs} +1 -1
  87. package/dist/{themes-XPkj_bfP.mjs.map → themes-BN0a2duq.mjs.map} +1 -1
  88. package/dist/{tools-DEt6YPfc.mjs → tools-CGPqpU3A.mjs} +1 -1
  89. package/dist/{tools-ceiy7ANX.mjs → tools-y2bJpKom.mjs} +14 -14
  90. package/dist/{tools-ceiy7ANX.mjs.map → tools-y2bJpKom.mjs.map} +1 -1
  91. package/dist/{trace-DfyGmMG_.mjs → trace-h23JCcFD.mjs} +1 -1
  92. package/dist/{trace-DfyGmMG_.mjs.map → trace-h23JCcFD.mjs.map} +1 -1
  93. package/dist/{vault-indexer-CFvlPUMB.mjs → vault-indexer-DgsPjMgs.mjs} +1 -1
  94. package/dist/{vault-indexer-CFvlPUMB.mjs.map → vault-indexer-DgsPjMgs.mjs.map} +1 -1
  95. package/dist/{work-queue-worker-B8W8_3Rn.mjs → work-queue-worker-Dva_v_pI.mjs} +4 -4
  96. package/dist/{work-queue-worker-B8W8_3Rn.mjs.map → work-queue-worker-Dva_v_pI.mjs.map} +1 -1
  97. package/dist/{work-queue-worker-HN2Ufg-L.mjs → work-queue-worker-gsKd2LJa.mjs} +4 -4
  98. package/dist/{zettelkasten-CvjmMghT.mjs → zettelkasten-vo7psPdT.mjs} +3 -3
  99. package/dist/{zettelkasten-CvjmMghT.mjs.map → zettelkasten-vo7psPdT.mjs.map} +1 -1
  100. package/docs/commands/README.md +28 -0
  101. package/docs/commands/backup.md +1 -1
  102. package/docs/commands/clear-names.md +1 -1
  103. package/docs/commands/daemon.md +1 -1
  104. package/docs/commands/db.md +1 -1
  105. package/docs/commands/end.md +1 -1
  106. package/docs/commands/help.md +1 -1
  107. package/docs/commands/identity.md +1 -1
  108. package/docs/commands/kg.md +1 -1
  109. package/docs/commands/mcp.md +1 -1
  110. package/docs/commands/memory.md +1 -1
  111. package/docs/commands/notify.md +1 -1
  112. package/docs/commands/observation.md +1 -1
  113. package/docs/commands/obsidian.md +1 -1
  114. package/docs/commands/pause.md +1 -1
  115. package/docs/commands/project.md +1 -1
  116. package/docs/commands/projects.md +1 -1
  117. package/docs/commands/registry.md +1 -1
  118. package/docs/commands/restore.md +1 -1
  119. package/docs/commands/session.md +1 -1
  120. package/docs/commands/sessions.md +1 -1
  121. package/docs/commands/setup.md +1 -1
  122. package/docs/commands/shell-init.md +1 -1
  123. package/docs/commands/skill.md +1 -1
  124. package/docs/commands/task.md +1 -1
  125. package/docs/commands/topic.md +1 -1
  126. package/docs/commands/update.md +1 -1
  127. package/docs/commands/worker.md +361 -0
  128. package/docs/commands/zettel.md +1 -1
  129. package/docs/worker.md +303 -0
  130. package/package.json +1 -1
  131. package/scripts/build-hooks.mjs +46 -1
  132. package/src/hooks/pre-compact.sh +1 -0
  133. package/src/hooks/session-autosave.sh +1 -0
  134. package/src/hooks/session-stop.sh +1 -0
  135. package/src/hooks/ts/lib/context-fill.test.ts +104 -0
  136. package/src/hooks/ts/lib/context-fill.ts +73 -0
  137. package/src/hooks/ts/lib/handover-evidence.ts +227 -0
  138. package/src/hooks/ts/lib/worker-session.test.ts +24 -0
  139. package/src/hooks/ts/lib/worker-session.ts +23 -0
  140. package/src/hooks/ts/post-tool-use/sync-todo-to-md.ts +2 -0
  141. package/src/hooks/ts/post-tool-use/update-tab-on-action.ts +2 -0
  142. package/src/hooks/ts/pre-compact/context-compression-hook.ts +17 -0
  143. package/src/hooks/ts/pre-tool-use/route-agents-to-worker.ts +128 -0
  144. package/src/hooks/ts/session-start/initialize-session.ts +2 -0
  145. package/src/hooks/ts/session-start/load-core-context.ts +2 -0
  146. package/src/hooks/ts/session-start/load-project-context.ts +2 -0
  147. package/src/hooks/ts/session-start/post-compact-inject.ts +13 -0
  148. package/src/hooks/ts/stop/stop-hook.ts +2 -0
  149. package/src/hooks/ts/subagent-stop/subagent-stop-hook.ts +2 -0
  150. package/src/hooks/ts/user-prompt/update-tab-titles.ts +2 -0
  151. package/statusline-command.sh +17 -0
  152. package/dist/daemon-DRdoA489.mjs +0 -20
  153. package/dist/detector-DGAk1iBR.mjs +0 -5
  154. package/dist/factory-CrokPMk2.mjs +0 -3
  155. package/dist/program-C-fUghPv.mjs.map +0 -1
  156. package/dist/query-feedback-BBMBp96K.mjs +0 -3
  157. package/dist/router-BMkOb62X.mjs +0 -3
@@ -7,13 +7,14 @@ import { i as indexProject, n as indexAll, t as embedChunks } from "./sync-BWbe8
7
7
  import { t as STOP_WORDS } from "./stop-words-Hfu8u22w.mjs";
8
8
  import { a as searchMemory, i as populateSlugs } from "./search-Rpk1cSBC.mjs";
9
9
  import { n as formatDetection, r as formatDetectionJson, t as detectProject } from "./detect-CdaA48EI.mjs";
10
- import { _ as findMovedProjects, a as findLatestNote, c as extractAndStoreTriples, f as resolveHome, g as claudeProjectsDir, h as upsertSession, i as applyContinue, l as cmdScan, m as upsertProject, o as findNotesDir$1, p as saveScanConfig, r as appendCheckpointToNote, s as readBodyFile, t as readContextHandoverCache, u as loadScanConfig, v as scanTranscriptFolders, y as transcriptFiles } from "./context-handover-cache-PtNvj_8D.mjs";
10
+ import { _ as findMovedProjects, a as findLatestNote, c as extractAndStoreTriples, f as resolveHome, g as claudeProjectsDir, h as upsertSession, i as applyContinue, l as cmdScan, m as upsertProject, o as findNotesDir$1, p as saveScanConfig, r as appendCheckpointToNote, s as readBodyFile, t as readContextHandoverCache, u as loadScanConfig, v as scanTranscriptFolders, y as transcriptFiles } from "./context-handover-cache-BpUojjsi.mjs";
11
11
  import { a as schedulerLogPath, i as paiSocketPath, n as daemonLogPath, r as daemonPidPath } from "./runtime-paths-rni52zHX.mjs";
12
12
  import { t as PaiClient } from "./ipc-client-BmypMNYk.mjs";
13
13
  import { a as expandHome, c as UNROUTED, i as ensureConfigDir, n as CONFIG_FILE$2, o as loadConfig, s as OWNER_LABEL_PREFIX, t as CONFIG_DIR } from "./config-B64vFg14.mjs";
14
- import { r as humanDuration, t as createStorageBackend } from "./factory-Bsp7xOpO.mjs";
15
- import { s as kgQuery } from "./kg-entity-DbOMPdF9.mjs";
16
- import { _ as scanSessions, a as renderDedupedSessions, c as probeResume, d as callAiBroker, f as fetchLiveSessions, g as resolveSessionByNameOrId, h as fmtAge, i as normalizeName$2, l as restoreTopLevel, m as sendToSession, o as hasConversation, p as revealItermSession, r as buildDeduped, s as launchInDir, t as cmdMain, u as printExitDir } from "./main-resolver-BAbhKpeX.mjs";
14
+ import { r as humanDuration, t as createStorageBackend } from "./factory-DD2T33C9.mjs";
15
+ import { A as readJsonStrict, C as loadStatus, D as workersLogDir, E as ledgerPath, O as WorkersConfigError, S as openPaneForWorker, T as eventsPath, _ as stopProxy, a as setRole, b as checkPaneForWorker, c as useProvider, d as replayOutput, f as statusLineOutput, g as ensureProxyRunning, h as DEFAULT_PROXY_PORT, i as setProviderEnabled, j as writeJsonAtomic, k as readWorkersSection, l as followWorkers, m as testProvider, n as describeProviders, o as setWorkersEnabled, p as runWorker, r as removeProvider, s as unsetRole, t as addProvider, u as psOutput, v as sayToWorker, x as openFollowPane, y as describeMcp } from "./providers-sXcK5bDZ.mjs";
16
+ import { s as kgQuery } from "./kg-entity-r8duqhi9.mjs";
17
+ import { _ as scanSessions, a as renderDedupedSessions, c as probeResume, d as callAiBroker, f as fetchLiveSessions, g as resolveSessionByNameOrId, h as fmtAge, i as normalizeName$2, l as restoreTopLevel, m as sendToSession, o as hasConversation, p as revealItermSession, r as buildDeduped, s as launchInDir, t as cmdMain, u as printExitDir } from "./main-resolver-DlaLOFBA.mjs";
17
18
  import { appendFileSync, chmodSync, copyFileSync, createReadStream, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
18
19
  import { homedir, platform, tmpdir } from "node:os";
19
20
  import { basename, dirname, join, relative, resolve, sep } from "node:path";
@@ -1888,7 +1889,7 @@ function cmdUnregister(db, slug, opts = {}) {
1888
1889
  //#region src/cli/commands/project/projects-index.ts
1889
1890
  function registerProjectsCommands(projectsCmd, getDb) {
1890
1891
  projectsCmd.command("list", { isDefault: true }).description("Show the unified deduped listing (same as `pai`).\nUse --all to include cold / zero-session / archived projects.").option("--all", "Show all entries including cold / zero-session / archived projects").option("-n, --n <count>", "Max rows to show", "20").action(async (opts) => {
1891
- const { cmdMain } = await import("./main-resolver-Dxh444GO.mjs");
1892
+ const { cmdMain } = await import("./main-resolver-IhZo4pI0.mjs");
1892
1893
  await cmdMain(getDb(), void 0, void 0, opts);
1893
1894
  });
1894
1895
  projectsCmd.command("list-raw").description("Raw registry listing with slug, path, status, last_active columns").option("--all", "Include archived projects (default: active only)").option("--status <status>", "Filter by status: active | archived").option("--tag <tag>", "Filter by tag").option("--type <type>", "Filter by type").action((opts) => {
@@ -2960,9 +2961,9 @@ function registerStatsCommands(memoryCmd, getDb) {
2960
2961
  //#region src/cli/commands/memory/sources-cmd.ts
2961
2962
  function registerSourcesCommand(memoryCmd) {
2962
2963
  memoryCmd.command("sources").description("Show what the indexer has taken in: composition by source, which roots\ncontent enters from, the heaviest single files, and how many chunks are\nrewritten per day (which distinguishes a finite backlog from a treadmill).").option("--limit <n>", "Rows per section (default 8)", "8").action(async (opts) => {
2963
- const { createStorageBackend } = await import("./factory-CrokPMk2.mjs");
2964
+ const { createStorageBackend } = await import("./factory-BXzqRYVZ.mjs");
2964
2965
  const { loadConfig } = await import("./config-C_ErGddD.mjs");
2965
- const { cmdMemorySources } = await import("./sources-D8ZdNfvK.mjs");
2966
+ const { cmdMemorySources } = await import("./sources-Bi7--33T.mjs");
2966
2967
  let backend;
2967
2968
  try {
2968
2969
  backend = await createStorageBackend(loadConfig());
@@ -3649,7 +3650,7 @@ function cmdLogs(opts) {
3649
3650
  }
3650
3651
  function registerDaemonCommands(daemonCmd) {
3651
3652
  daemonCmd.command("serve").description("Start the PAI daemon in the foreground").action(async () => {
3652
- const { serve } = await import("./daemon-DRdoA489.mjs");
3653
+ const { serve } = await import("./daemon-DsGGiIJM.mjs");
3653
3654
  const { loadConfig: lc, ensureConfigDir } = await import("./config-C_ErGddD.mjs");
3654
3655
  ensureConfigDir();
3655
3656
  await serve(lc());
@@ -4017,86 +4018,6 @@ function registerRestoreCommands(program) {
4017
4018
  });
4018
4019
  }
4019
4020
 
4020
- //#endregion
4021
- //#region src/config/json-store.ts
4022
- /**
4023
- * json-store.ts — read/write JSON config files without destroying them
4024
- *
4025
- * The failure this exists to prevent:
4026
- *
4027
- * try { return JSON.parse(read(path)); } catch { return {}; }
4028
- * ... later ...
4029
- * write(path, JSON.stringify(ourData));
4030
- *
4031
- * An unreadable file becomes an empty object, and the next write makes that
4032
- * permanent. Silently, exit code 0. This shape appeared three times in this
4033
- * repo against three different files, and twice in AIBroker.
4034
- *
4035
- * The distinction that matters is between *missing* and *unreadable*:
4036
- *
4037
- * missing — legitimate first run. Start fresh; writing is safe.
4038
- * unreadable — the file exists and we could not parse it. Those bytes are
4039
- * the only copy of something. Never overwrite them.
4040
- *
4041
- * Collapsing the second into the first is the bug.
4042
- *
4043
- * NOT everything deserves this guard. For a transient buffer — an undelivered
4044
- * message queue, a cache — starting fresh IS the correct recovery, and
4045
- * refusing to write would disable the feature permanently. Use `writeJsonAtomic`
4046
- * alone there: it still prevents a crash from truncating a good file, without
4047
- * blocking recovery. Reserve `readJsonStrict` for data a user cannot rebuild.
4048
- */
4049
- /**
4050
- * Read a JSON file, distinguishing "absent" from "damaged".
4051
- *
4052
- * @param path file to read
4053
- * @param label how to name it to the user, e.g. "~/.claude.json"
4054
- * @throws if the file exists but cannot be read or parsed
4055
- */
4056
- function readJsonStrict(path, label = path) {
4057
- if (!existsSync(path)) return {};
4058
- let raw;
4059
- try {
4060
- raw = readFileSync(path, "utf8");
4061
- } catch (e) {
4062
- throw new Error(`Could not read ${label}: ${e instanceof Error ? e.message : String(e)}\nRefusing to continue — writing now would replace its contents with ours alone.`);
4063
- }
4064
- try {
4065
- return JSON.parse(raw);
4066
- } catch (e) {
4067
- throw new Error(`${label} exists but is not valid JSON: ${e instanceof Error ? e.message : String(e)}\nRefusing to continue — overwriting it would destroy whatever it holds.\nRepair the file, or move it aside and re-run this command.`);
4068
- }
4069
- }
4070
- /**
4071
- * Write JSON without risking the existing file.
4072
- *
4073
- * Keeps a `.bak-pai` copy of the previous contents, then writes to a temp file
4074
- * and renames. Rename is atomic within a filesystem, so a crash mid-write
4075
- * leaves the original intact rather than truncated — which is how these files
4076
- * become corrupt in the first place.
4077
- */
4078
- function writeJsonAtomic(path, data, opts = {}) {
4079
- const { backup = true, label = path } = opts;
4080
- const serialized = JSON.stringify(data, null, 2) + "\n";
4081
- const dir = dirname(path);
4082
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
4083
- if (backup && existsSync(path)) try {
4084
- copyFileSync(path, `${path}.bak-pai`);
4085
- } catch (e) {
4086
- throw new Error(`Could not back up ${label}: ${e instanceof Error ? e.message : String(e)}\nRefusing to write without a backup.`);
4087
- }
4088
- const tmp = `${path}.tmp-pai-${process.pid}`;
4089
- try {
4090
- writeFileSync(tmp, serialized, "utf8");
4091
- renameSync(tmp, path);
4092
- } catch (e) {
4093
- try {
4094
- if (existsSync(tmp)) unlinkSync(tmp);
4095
- } catch {}
4096
- throw new Error(`Failed to write ${label}: ${e instanceof Error ? e.message : String(e)}\nThe original is unchanged.`);
4097
- }
4098
- }
4099
-
4100
4021
  //#endregion
4101
4022
  //#region src/cli/lib/exit.ts
4102
4023
  /**
@@ -6152,6 +6073,209 @@ async function stepTaskBus(rl) {
6152
6073
  } };
6153
6074
  }
6154
6075
 
6076
+ //#endregion
6077
+ //#region src/workers/install.ts
6078
+ /**
6079
+ * install.ts — `pai worker install`: one command, whole migration.
6080
+ *
6081
+ * Three things:
6082
+ *
6083
+ * 1. ~/.claude/settings.json: point the PreToolUse "Agent" hook at
6084
+ * ${PAI_DIR}/Hooks/route-agents-to-worker.mjs, replacing the old
6085
+ * route-agents-to-glm.sh registration if present.
6086
+ * 2. ~/.local/bin compatibility shims (glm, glm-run, glm-ps, glm-log) that
6087
+ * exec the pai equivalents, so every existing habit, alias and script
6088
+ * keeps working. Anything already at those paths is moved aside to
6089
+ * <name>.pre-pai once — never deleted.
6090
+ * 3. Remove the old route-agents-to-glm.sh from ~/.claude/Hooks (the
6091
+ * settings.json entry is gone, so the file is dead).
6092
+ *
6093
+ * Idempotent: running it twice changes nothing the second time.
6094
+ */
6095
+ const HOOKS_DIR = join(join(homedir(), ".claude"), "Hooks");
6096
+ const LOCAL_BIN = join(homedir(), ".local", "bin");
6097
+ const NEW_AGENT_HOOK = "${PAI_DIR}/Hooks/route-agents-to-worker.mjs";
6098
+ const OLD_AGENT_HOOK_STEM = "route-agents-to-glm";
6099
+ /** Absolute path of the pai executable to hard-wire into the shims. */
6100
+ function paiPath() {
6101
+ try {
6102
+ return execFileSync("/usr/bin/which", ["pai"], { encoding: "utf8" }).trim() || "pai";
6103
+ } catch {
6104
+ return "pai";
6105
+ }
6106
+ }
6107
+ function patchAgentHook(lines) {
6108
+ const settings = readSettingsJson();
6109
+ const hooks = typeof settings["hooks"] === "object" && settings["hooks"] !== null ? settings["hooks"] : {};
6110
+ const preToolUse = Array.isArray(hooks["PreToolUse"]) ? hooks["PreToolUse"] : [];
6111
+ let changed = false;
6112
+ let replaced = false;
6113
+ for (const rule of preToolUse) {
6114
+ if (rule["matcher"] !== "Agent") continue;
6115
+ const entries = Array.isArray(rule["hooks"]) ? rule["hooks"] : [];
6116
+ for (const entry of entries) {
6117
+ const cmd = typeof entry["command"] === "string" ? entry["command"] : "";
6118
+ if (cmd === NEW_AGENT_HOOK) replaced = true;
6119
+ if (cmd.includes(OLD_AGENT_HOOK_STEM)) {
6120
+ entry["command"] = NEW_AGENT_HOOK;
6121
+ lines.push(`settings.json: Agent hook migrated to ${NEW_AGENT_HOOK}`);
6122
+ changed = true;
6123
+ replaced = true;
6124
+ }
6125
+ }
6126
+ }
6127
+ if (!replaced) {
6128
+ preToolUse.push({
6129
+ matcher: "Agent",
6130
+ hooks: [{
6131
+ type: "command",
6132
+ command: NEW_AGENT_HOOK
6133
+ }]
6134
+ });
6135
+ lines.push(`settings.json: Agent hook added → ${NEW_AGENT_HOOK}`);
6136
+ changed = true;
6137
+ }
6138
+ if (changed) {
6139
+ hooks["PreToolUse"] = preToolUse;
6140
+ settings["hooks"] = hooks;
6141
+ writeSettingsJson(settings);
6142
+ } else lines.push(`settings.json: Agent hook already current`);
6143
+ return changed;
6144
+ }
6145
+ const GLM_PS_SHIM = (pai) => `# pai worker shim — replaces the python glm-ps of the same name
6146
+ case "\${1-}" in
6147
+ ""|"ps") exec ${pai} worker ps ;;
6148
+ follow|watch|pane|log) exec ${pai} worker "$@" ;;
6149
+ --status) shift; exec ${pai} worker status-line "\${ITERM_SESSION_ID:-}" "\${PWD:-$HOME}" ;;
6150
+ -*) exec ${pai} worker ps "$@" ;;
6151
+ *) exec ${pai} worker replay "$@" ;;
6152
+ esac
6153
+ `;
6154
+ function shim(name, pai) {
6155
+ if (name === "glm-ps") return GLM_PS_SHIM(pai);
6156
+ if (name === "glm-log") return `#!/bin/sh\n# pai worker shim — replaces the shell glm-log of the same name\nexec ${pai} worker log "$@"\n`;
6157
+ if (name === "worker-say") return `#!/bin/sh\n# pai worker shim — say one line to a running worker\nexec ${pai} worker say "$@"\n`;
6158
+ return `#!/bin/sh\n# pai worker shim — replaces the previous ${name} of the same name\nexec ${pai} worker run "$@"\n`;
6159
+ }
6160
+ function installShims(lines) {
6161
+ const pai = paiPath();
6162
+ let changed = false;
6163
+ for (const name of [
6164
+ "glm",
6165
+ "glm-run",
6166
+ "glm-ps",
6167
+ "glm-log",
6168
+ "worker-say"
6169
+ ]) {
6170
+ const path = join(LOCAL_BIN, name);
6171
+ const ours = `# pai worker shim`;
6172
+ let current = null;
6173
+ if (existsSync(path)) try {
6174
+ current = execFileSync("head", [
6175
+ "-c",
6176
+ "64",
6177
+ path
6178
+ ], { encoding: "utf8" });
6179
+ } catch {
6180
+ current = null;
6181
+ }
6182
+ if (current !== null && !current.includes(ours)) {
6183
+ const aside = join(LOCAL_BIN, `${name}.pre-pai`);
6184
+ if (!existsSync(aside)) {
6185
+ renameSync(path, aside);
6186
+ lines.push(`${name}: previous version kept as ${name}.pre-pai`);
6187
+ } else {
6188
+ unlinkSync(path);
6189
+ lines.push(`${name}: replaced (${name}.pre-pai already exists)`);
6190
+ }
6191
+ changed = true;
6192
+ } else if (current === null) changed = true;
6193
+ else {
6194
+ lines.push(`${name}: shim already current`);
6195
+ continue;
6196
+ }
6197
+ writeFileSync(path, shim(name, pai), {
6198
+ encoding: "utf8",
6199
+ mode: 493
6200
+ });
6201
+ try {
6202
+ chmodSync(path, 493);
6203
+ } catch {}
6204
+ lines.push(`${name}: shim installed → ${pai} worker …`);
6205
+ }
6206
+ return changed;
6207
+ }
6208
+ function removeOldHook(lines) {
6209
+ const old = join(HOOKS_DIR, "route-agents-to-glm.sh");
6210
+ if (!existsSync(old)) return false;
6211
+ unlinkSync(old);
6212
+ lines.push(`removed ${OLD_AGENT_HOOK_STEM}.sh (registration migrated above)`);
6213
+ return true;
6214
+ }
6215
+ function installWorkers() {
6216
+ const lines = [];
6217
+ let changed = patchAgentHook(lines);
6218
+ if (installShims(lines)) changed = true;
6219
+ if (removeOldHook(lines)) changed = true;
6220
+ if (!existsSync(LOCAL_BIN)) lines.push(`note: ${LOCAL_BIN} did not exist; make sure it is on PATH`);
6221
+ return {
6222
+ lines,
6223
+ changed
6224
+ };
6225
+ }
6226
+
6227
+ //#endregion
6228
+ //#region src/cli/commands/setup/steps/17-workers.ts
6229
+ /** Step 17: Worker providers — subagents on configured providers. */
6230
+ async function stepWorkers(rl) {
6231
+ section("Step 17: Worker Providers (Optional)");
6232
+ const current = readConfigRaw().workers;
6233
+ if (current?.providers && Object.keys(current.providers).length > 0) {
6234
+ if (current.enabled !== false) {
6235
+ console.log(c.ok("Worker providers already configured. Running install to refresh shims…"));
6236
+ for (const l of installWorkers().lines) line$1(` ${l}`);
6237
+ }
6238
+ return { workers: current };
6239
+ }
6240
+ line$1();
6241
+ line$1(" PAI can run every subagent (worker) on a provider you configure —");
6242
+ line$1(" a non-Anthropic endpoint via its Anthropic-compatible API.");
6243
+ line$1();
6244
+ line$1(" `pai worker run` is the runner; ps / follow / replay watch the runs;");
6245
+ line$1(" the Agent tool is denied and rewritten to it once providers exist.");
6246
+ line$1();
6247
+ line$1(" PAI works fully without this — Agent subagents then just run as before.");
6248
+ line$1();
6249
+ if (!await promptYesNo(rl, "Configure a worker provider now?", false)) {
6250
+ line$1();
6251
+ console.log(c.ok("Skipping worker providers."));
6252
+ console.log(c.dim(" Add later: pai worker providers add <name> … — see docs/worker.md"));
6253
+ return { workers: {
6254
+ enabled: false,
6255
+ providers: {},
6256
+ roles: {}
6257
+ } };
6258
+ }
6259
+ line$1();
6260
+ line$1(" Add the provider after setup finishes (this step only prepares the");
6261
+ line$1(" wiring — shims, hook registration, log dir):");
6262
+ line$1();
6263
+ line$1(c.dim(" pai worker providers add glm \\"));
6264
+ line$1(c.dim(" --base-url https://api.example.com/api/anthropic \\"));
6265
+ line$1(c.dim(" --key-file ~/.config/example/api_key \\"));
6266
+ line$1(c.dim(" --model example-4.7 --fast-model example-4.7-flash"));
6267
+ line$1();
6268
+ line$1(" The first provider turns workers on and seeds the roles.");
6269
+ line$1();
6270
+ const r = installWorkers();
6271
+ for (const l of r.lines) line$1(` ${l}`);
6272
+ return { workers: {
6273
+ enabled: false,
6274
+ providers: {},
6275
+ roles: {}
6276
+ } };
6277
+ }
6278
+
6155
6279
  //#endregion
6156
6280
  //#region src/cli/commands/setup/index.ts
6157
6281
  async function runSetup() {
@@ -6188,10 +6312,12 @@ async function runSetup() {
6188
6312
  const mcpRegistered = await stepMcp(rl);
6189
6313
  await stepDirectories(rl);
6190
6314
  const taskConfig = await stepTaskBus(rl);
6315
+ const workersConfig = await stepWorkers(rl);
6191
6316
  const allUpdates = {
6192
6317
  ...storageConfig,
6193
6318
  ...embeddingConfig,
6194
- ...taskConfig
6319
+ ...taskConfig,
6320
+ ...workersConfig
6195
6321
  };
6196
6322
  mergeConfig(allUpdates);
6197
6323
  line$1();
@@ -7102,7 +7228,7 @@ async function cmdExplore(note, opts) {
7102
7228
  const depth = parseInt(opts.depth ?? "3", 10);
7103
7229
  const direction = opts.direction ?? "both";
7104
7230
  const mode = opts.mode ?? "all";
7105
- const { zettelExplore } = await import("./zettelkasten-CvjmMghT.mjs");
7231
+ const { zettelExplore } = await import("./zettelkasten-vo7psPdT.mjs");
7106
7232
  const result = zettelExplore(getFedDb(), {
7107
7233
  startNote: note,
7108
7234
  depth,
@@ -7167,7 +7293,7 @@ async function cmdHealth(opts) {
7167
7293
  const projectPath = opts.project;
7168
7294
  const recentDays = parseInt(opts.days ?? "30", 10);
7169
7295
  const includeTypes = opts.include ? opts.include.split(",").map((s) => s.trim()) : void 0;
7170
- const { zettelHealth } = await import("./zettelkasten-CvjmMghT.mjs");
7296
+ const { zettelHealth } = await import("./zettelkasten-vo7psPdT.mjs");
7171
7297
  const result = zettelHealth(getFedDb(), {
7172
7298
  scope,
7173
7299
  projectPath,
@@ -7238,7 +7364,7 @@ async function cmdSurprise(note, opts) {
7238
7364
  const limit = parseInt(opts.limit ?? "10", 10);
7239
7365
  const minSimilarity = parseFloat(opts.minSimilarity ?? "0.3");
7240
7366
  const minGraphDistance = parseInt(opts.minDistance ?? "3", 10);
7241
- const { zettelSurprise } = await import("./zettelkasten-CvjmMghT.mjs");
7367
+ const { zettelSurprise } = await import("./zettelkasten-vo7psPdT.mjs");
7242
7368
  const db = getFedDb();
7243
7369
  console.log();
7244
7370
  console.log(header(" PAI Zettel Surprise"));
@@ -7293,7 +7419,7 @@ async function cmdSuggest(note, opts) {
7293
7419
  const vaultProjectId = parseInt(opts.vaultProjectId, 10);
7294
7420
  const limit = parseInt(opts.limit ?? "5", 10);
7295
7421
  const excludeLinked = opts.excludeLinked !== false;
7296
- const { zettelSuggest } = await import("./zettelkasten-CvjmMghT.mjs");
7422
+ const { zettelSuggest } = await import("./zettelkasten-vo7psPdT.mjs");
7297
7423
  const db = getFedDb();
7298
7424
  console.log();
7299
7425
  console.log(header(" PAI Zettel Suggest"));
@@ -7346,7 +7472,7 @@ async function cmdConverse(question, opts) {
7346
7472
  const vaultProjectId = parseInt(opts.vaultProjectId, 10);
7347
7473
  const depth = parseInt(opts.depth ?? "2", 10);
7348
7474
  const limit = parseInt(opts.limit ?? "15", 10);
7349
- const { zettelConverse } = await import("./zettelkasten-CvjmMghT.mjs");
7475
+ const { zettelConverse } = await import("./zettelkasten-vo7psPdT.mjs");
7350
7476
  const db = getFedDb();
7351
7477
  console.log();
7352
7478
  console.log(header(" PAI Zettel Converse"));
@@ -7409,7 +7535,7 @@ async function cmdThemes(opts) {
7409
7535
  const minClusterSize = parseInt(opts.minSize ?? "3", 10);
7410
7536
  const maxThemes = parseInt(opts.maxThemes ?? "10", 10);
7411
7537
  const similarityThreshold = parseFloat(opts.threshold ?? "0.65");
7412
- const { zettelThemes } = await import("./zettelkasten-CvjmMghT.mjs");
7538
+ const { zettelThemes } = await import("./zettelkasten-vo7psPdT.mjs");
7413
7539
  const db = getFedDb();
7414
7540
  console.log();
7415
7541
  console.log(header(" PAI Zettel Themes"));
@@ -8984,7 +9110,7 @@ async function escalate(task, headline, detail, state, now, opts) {
8984
9110
  state.alarmedAt[task.id] = now;
8985
9111
  let delivered = false;
8986
9112
  try {
8987
- const { routeNotification } = await import("./router-BMkOb62X.mjs");
9113
+ const { routeNotification } = await import("./router-1zi8jiNF.mjs");
8988
9114
  const { loadConfig } = await import("./config-C_ErGddD.mjs");
8989
9115
  delivered = (await routeNotification({
8990
9116
  event: "error",
@@ -11601,9 +11727,9 @@ function cmdActive(db, opts) {
11601
11727
  ], rows));
11602
11728
  }
11603
11729
  async function cmdAutoRoute(opts) {
11604
- const { autoRoute, formatAutoRoute, formatAutoRouteJson } = await import("./auto-route-Byf8ENXj.mjs");
11730
+ const { autoRoute, formatAutoRoute, formatAutoRouteJson } = await import("./auto-route-DM7GhJ8y.mjs");
11605
11731
  const { openRegistry } = await import("./db-XEwJbuGO.mjs");
11606
- const { createStorageBackend } = await import("./factory-CrokPMk2.mjs");
11732
+ const { createStorageBackend } = await import("./factory-BXzqRYVZ.mjs");
11607
11733
  const { loadConfig } = await import("./config-C_ErGddD.mjs");
11608
11734
  const config = loadConfig();
11609
11735
  const registryDb = openRegistry();
@@ -12412,6 +12538,40 @@ function listProjectTranscripts(cwd, projectsDir) {
12412
12538
  collect(join(projectDir, "sessions"));
12413
12539
  return paths;
12414
12540
  }
12541
+ /** Placeholder model id the platform writes on synthetic assistant turns. */
12542
+ const SYNTHETIC_MODEL = "<synthetic>";
12543
+ function modelFamily(model) {
12544
+ if (typeof model !== "string" || model === "" || model === SYNTHETIC_MODEL) return "unknown";
12545
+ return model.startsWith("claude-") ? "claude" : "foreign";
12546
+ }
12547
+ /**
12548
+ * The family of the LAST assistant model in a transcript — scanned from the
12549
+ * end so a long transcript costs one read and a few lines of parsing.
12550
+ * Unreadable or model-less transcripts are "unknown", never "foreign":
12551
+ * the callers that skip work on "foreign" must not skip it on doubt.
12552
+ */
12553
+ function transcriptModelFamily(path) {
12554
+ let raw;
12555
+ try {
12556
+ raw = readFileSync(path, "utf-8");
12557
+ } catch {
12558
+ return "unknown";
12559
+ }
12560
+ const lines = raw.split("\n");
12561
+ for (let i = lines.length - 1; i >= 0; i--) {
12562
+ const line = lines[i];
12563
+ if (!line.includes("\"model\"")) continue;
12564
+ try {
12565
+ const entry = JSON.parse(line);
12566
+ if (entry.type !== "assistant") continue;
12567
+ const model = entry.message?.model;
12568
+ if (typeof model === "string" && model !== SYNTHETIC_MODEL) return modelFamily(model);
12569
+ } catch {
12570
+ continue;
12571
+ }
12572
+ }
12573
+ return "unknown";
12574
+ }
12415
12575
  /**
12416
12576
  * Every DISTINCT compact_boundary sample found across ALL of a project's
12417
12577
  * transcripts (live + archived), newest first by the event's OWN timestamp
@@ -12428,6 +12588,8 @@ function readCompactBoundarySamples(cwd, projectsDir) {
12428
12588
  } catch {
12429
12589
  continue;
12430
12590
  }
12591
+ let lastModel = null;
12592
+ let foreignDiscards = 0;
12431
12593
  for (const line of raw.split("\n")) {
12432
12594
  if (!line.trim()) continue;
12433
12595
  let entry;
@@ -12436,11 +12598,20 @@ function readCompactBoundarySamples(cwd, projectsDir) {
12436
12598
  } catch {
12437
12599
  continue;
12438
12600
  }
12601
+ if (entry.type === "assistant") {
12602
+ const model = entry.message?.model;
12603
+ if (typeof model === "string" && model !== SYNTHETIC_MODEL) lastModel = model;
12604
+ continue;
12605
+ }
12439
12606
  if (entry.type !== "system" || entry.subtype !== "compact_boundary") continue;
12440
12607
  const preTokens = entry.compactMetadata?.preTokens;
12441
12608
  if (typeof preTokens !== "number" || !Number.isFinite(preTokens)) continue;
12442
12609
  const timestampMs = entry.timestamp ? Date.parse(entry.timestamp) : NaN;
12443
12610
  if (!Number.isFinite(timestampMs)) continue;
12611
+ if (modelFamily(lastModel) === "foreign") {
12612
+ foreignDiscards++;
12613
+ continue;
12614
+ }
12444
12615
  const key = entry.uuid ?? `${timestampMs}:${preTokens}`;
12445
12616
  if (!byKey.has(key)) byKey.set(key, {
12446
12617
  preTokens,
@@ -12449,6 +12620,7 @@ function readCompactBoundarySamples(cwd, projectsDir) {
12449
12620
  uuid: entry.uuid
12450
12621
  });
12451
12622
  }
12623
+ if (foreignDiscards > 0) console.error(`[context-fill] ignored ${foreignDiscards} compaction sample(s) from a non-Claude transcript (model=${lastModel}): ${path}`);
12452
12624
  }
12453
12625
  return [...byKey.values()].sort((a, b) => b.timestampMs - a.timestampMs);
12454
12626
  }
@@ -12592,6 +12764,32 @@ function isImmediate(usedTokens, thresholds) {
12592
12764
  return usedTokens >= thresholds.immediateTokens;
12593
12765
  }
12594
12766
 
12767
+ //#endregion
12768
+ //#region src/hooks/ts/lib/worker-session.ts
12769
+ /**
12770
+ * Worker-session detection.
12771
+ *
12772
+ * A disposable headless worker (a `claude -p` run started by an orchestrating
12773
+ * session, possibly against a different model provider with a different
12774
+ * context window) shares the project directory and the hook configuration
12775
+ * with the real session that spawned it. Left alone, the hooks treat it as a
12776
+ * session in its own right: they inject project context into it, create and
12777
+ * rename a numbered session note for it, autosave it, and enqueue a
12778
+ * model-written handover for it. Its compactions also land in the project's
12779
+ * transcript folder, where they are indistinguishable from a real session's
12780
+ * and drag the measured compaction trigger down (observed 2026-09-17: two
12781
+ * workers compacting at ~151k tokens pulled a project's trigger from ~784k
12782
+ * to ~151k, and the real session's handover fired at ~50k tokens).
12783
+ *
12784
+ * The launcher marks such sessions with `PAI_WORKER=1`. Every hook that does
12785
+ * per-session bookkeeping returns immediately when this predicate is true.
12786
+ * Deliberately NOT guarded: the security validator (a worker's shell
12787
+ * commands must still be checked) and observability capture.
12788
+ */
12789
+ function isWorkerSession(env = process.env) {
12790
+ return env.PAI_WORKER === "1";
12791
+ }
12792
+
12595
12793
  //#endregion
12596
12794
  //#region src/session/context-handover-trigger.ts
12597
12795
  /**
@@ -12705,6 +12903,14 @@ const NOTHING = {
12705
12903
  * either alone would have.
12706
12904
  */
12707
12905
  async function checkAndEnqueueContextHandover(input, deps = defaultDeps) {
12906
+ if (isWorkerSession()) {
12907
+ console.error(`[context-handover-trigger] session ${input.sessionId}: worker session (PAI_WORKER=1) — no handover.`);
12908
+ return NOTHING;
12909
+ }
12910
+ if (input.transcriptPath && transcriptModelFamily(input.transcriptPath) === "foreign") {
12911
+ console.error(`[context-handover-trigger] session ${input.sessionId}: transcript written by a non-Claude model — no handover.`);
12912
+ return NOTHING;
12913
+ }
12708
12914
  let state = deps.loadState(input.sessionId);
12709
12915
  if (ALL_THRESHOLDS.every((t) => state.confirmed.includes(t))) return NOTHING;
12710
12916
  let confirmedThisCheck = [];
@@ -13371,6 +13577,391 @@ function registerSessionCommands(sessionCmd, getDb) {
13371
13577
  });
13372
13578
  }
13373
13579
 
13580
+ //#endregion
13581
+ //#region src/cli/commands/worker/providers.ts
13582
+ function fail$1(e) {
13583
+ const msg = e instanceof Error ? e.message : String(e);
13584
+ console.error(err("pai worker: ") + msg);
13585
+ process.exitCode = 1;
13586
+ }
13587
+ /** `--env K=V` (repeatable) → record. Throws on a value without "=". */
13588
+ function collectEnv(pairs) {
13589
+ const out = {};
13590
+ for (const p of pairs ?? []) {
13591
+ const eq = p.indexOf("=");
13592
+ if (eq <= 0) throw new WorkersConfigError(`--env expects NAME=VALUE, got "${p}"`);
13593
+ out[p.slice(0, eq)] = p.slice(eq + 1);
13594
+ }
13595
+ return out;
13596
+ }
13597
+ function parseIntArg$1(v) {
13598
+ const n = parseInt(v, 10);
13599
+ if (Number.isNaN(n) || n <= 0) throw new WorkersConfigError(`expected a positive number, got "${v}"`);
13600
+ return n;
13601
+ }
13602
+ /** `glm/fast` | `{provider, mcp}` → one printable target line. */
13603
+ function roleTargetText(target) {
13604
+ if (typeof target === "string") return target;
13605
+ if (typeof target === "object" && target !== null) {
13606
+ const t = target;
13607
+ return `${t.provider ?? "?"}${t.mcp?.length ? ` +mcp(${t.mcp.join(",")})` : ""}`;
13608
+ }
13609
+ return String(target);
13610
+ }
13611
+ function registerWorkerProviderCommands(providersCmd) {
13612
+ providersCmd.description("Providers: list (default), add, remove, use, enable, disable, test").action(() => {
13613
+ const { workers } = readWorkersSection();
13614
+ console.log();
13615
+ for (const line of describeProviders(workers)) console.log(` ${line}`);
13616
+ console.log();
13617
+ console.log(dim$1(` workers are ${workers.enabled ? "on" : "off"} — pai worker ${workers.enabled ? "off" : "on"}`));
13618
+ console.log();
13619
+ });
13620
+ providersCmd.command("add <name>").description("Add a provider; the first one also turns workers on and seeds roles.\nExample: pai worker providers add glm --base-url https://…/anthropic \\\n --key-file ~/.config/zai/api_key --model glm-5.3 --fast-model glm-5.3-flash\nOpenAI-protocol: --protocol openai --upstream-url https://…/v1 (runs via the PAI proxy).\nCodex (ChatGPT plan): --engine codex — runs through the Codex CLI.").option("--base-url <url>", "Anthropic-compatible API base URL (required unless --protocol openai)").requiredOption("--model <model>", "Default model id for this provider").option("--key-file <path>", "File holding the API token (0600); omit for token \"local\"").option("--fast-model <model>", "Cheaper model for spotchecks and routing").option("--env <name=value>", "Extra env for runs (repeatable)", (v, acc) => [...acc, v], []).option("--note <text>", "Human note shown in `providers list`").option("--protocol <proto>", "anthropic (default) or openai — openai runs through the local PAI proxy").option("--upstream-url <url>", "Chat Completions base URL (required for --protocol openai)").option("--engine <engine>", "claude (default) or codex — codex runs `codex exec --json`").option("--context-window <tokens>", "Context window for the meter (default 200000; init event overrides)", parseIntArg$1).option("--quota-probe <url>", "URL whose JSON first number is the quota percent (0-100)").action((name, opts) => {
13621
+ try {
13622
+ const protocol = opts.protocol;
13623
+ const engine = opts.engine;
13624
+ if (protocol && protocol !== "anthropic" && protocol !== "openai") throw new WorkersConfigError(`--protocol must be anthropic or openai, got "${protocol}"`);
13625
+ if (engine && engine !== "claude" && engine !== "codex") throw new WorkersConfigError(`--engine must be claude or codex, got "${engine}"`);
13626
+ if (protocol !== "openai" && !opts.baseUrl) throw new WorkersConfigError("--base-url is required (only --protocol openai goes without it)");
13627
+ addProvider({
13628
+ name,
13629
+ baseUrl: opts.baseUrl ?? "",
13630
+ keyFile: opts.keyFile ?? null,
13631
+ model: opts.model,
13632
+ fastModel: opts.fastModel,
13633
+ env: collectEnv(opts.env),
13634
+ note: opts.note,
13635
+ ...protocol ? { protocol } : {},
13636
+ ...opts.upstreamUrl ? { upstreamUrl: opts.upstreamUrl } : {},
13637
+ ...engine ? { engine } : {},
13638
+ ...opts.contextWindow ? { contextWindow: opts.contextWindow } : {},
13639
+ quotaProbe: opts.quotaProbe
13640
+ });
13641
+ const { workers } = readWorkersSection();
13642
+ console.log(ok$1(`provider ${name} added`));
13643
+ if (workers.active === name) console.log(` active: ${name} — workers ${workers.enabled ? "on" : "off"}`);
13644
+ for (const line of describeProviders(workers)) console.log(dim$1(` ${line}`));
13645
+ } catch (e) {
13646
+ fail$1(e);
13647
+ }
13648
+ });
13649
+ providersCmd.command("remove <name>").description("Remove a provider and any roles pointing at it").action((name) => {
13650
+ try {
13651
+ removeProvider(name);
13652
+ console.log(ok$1(`provider ${name} removed`));
13653
+ } catch (e) {
13654
+ fail$1(e);
13655
+ }
13656
+ });
13657
+ providersCmd.command("use <name>").description("Make this provider the active one for runs without --provider/--role").action((name) => {
13658
+ try {
13659
+ useProvider(name);
13660
+ console.log(ok$1(`active provider: ${name}`));
13661
+ } catch (e) {
13662
+ fail$1(e);
13663
+ }
13664
+ });
13665
+ providersCmd.command("enable <name>").description("Enable a provider (also clears its cooldown)").action((name) => {
13666
+ try {
13667
+ setProviderEnabled(name, true);
13668
+ console.log(ok$1(`provider ${name} enabled`));
13669
+ } catch (e) {
13670
+ fail$1(e);
13671
+ }
13672
+ });
13673
+ providersCmd.command("disable <name>").description("Disable a provider (auto-routing skips it; --provider still works)").action((name) => {
13674
+ try {
13675
+ setProviderEnabled(name, false);
13676
+ console.log(ok$1(`provider ${name} disabled`));
13677
+ } catch (e) {
13678
+ fail$1(e);
13679
+ }
13680
+ });
13681
+ providersCmd.command("test [name]").description("One-word pong probe through a provider (default: the active one)").action(async (name) => {
13682
+ try {
13683
+ const { workers } = readWorkersSection();
13684
+ const pname = name ?? workers.active ?? "";
13685
+ if (!pname || !workers.providers[pname]) throw new WorkersConfigError(name ? `no provider named "${name}"` : "no active provider — name one explicitly");
13686
+ console.log(dim$1(` probing ${pname} (${workers.providers[pname].models.default}) …`));
13687
+ const r = await testProvider(pname, workers.providers[pname], workersLogDir(workers));
13688
+ const head = `${r.provider} ${r.model} ${(r.latencyMs / 1e3).toFixed(1)}s`;
13689
+ if (r.skipped) {
13690
+ console.log(dim$1(` ${head} ${r.skipped}`));
13691
+ console.log(` reply: ${r.result.slice(0, 200)}`);
13692
+ return;
13693
+ }
13694
+ console.log(r.ok ? ok$1(` ${head}`) : err(` ${head}`));
13695
+ console.log(` reply: ${r.result.slice(0, 200)}`);
13696
+ process.exitCode = r.ok ? 0 : 1;
13697
+ } catch (e) {
13698
+ fail$1(e);
13699
+ }
13700
+ });
13701
+ }
13702
+ /** `pai worker roles` — role → provider[/fast] assignment. */
13703
+ function registerWorkerRoleCommands(workerCmd) {
13704
+ const rolesCmd = workerCmd.command("roles").description("Roles: which provider serves implement / research / spotcheck").action(() => {
13705
+ const { workers } = readWorkersSection();
13706
+ const entries = Object.entries(workers.roles);
13707
+ if (!entries.length) {
13708
+ console.log(dim$1(" no roles set — runs use the active provider"));
13709
+ return;
13710
+ }
13711
+ for (const [role, target] of entries) console.log(` ${role.padEnd(12)} ${roleTargetText(target)}`);
13712
+ });
13713
+ rolesCmd.command("list").description("List roles and their providers (default action)").action(() => {
13714
+ const { workers } = readWorkersSection();
13715
+ const entries = Object.entries(workers.roles);
13716
+ if (!entries.length) {
13717
+ console.log(dim$1(" no roles set — runs use the active provider"));
13718
+ return;
13719
+ }
13720
+ for (const [role, target] of entries) console.log(` ${role.padEnd(12)} ${roleTargetText(target)}`);
13721
+ });
13722
+ rolesCmd.command("set <role> <provider[/alias]>").description("Point a role at a provider, optionally its fast model (e.g. glm/fast)").action((role, target) => {
13723
+ try {
13724
+ setRole(role, target);
13725
+ console.log(ok$1(`role ${role} → ${target}`));
13726
+ } catch (e) {
13727
+ fail$1(e);
13728
+ }
13729
+ });
13730
+ rolesCmd.command("unset <role>").description("Remove a role (runs then use the active provider)").action((role) => {
13731
+ try {
13732
+ unsetRole(role);
13733
+ console.log(ok$1(`role ${role} removed`));
13734
+ } catch (e) {
13735
+ fail$1(e);
13736
+ }
13737
+ });
13738
+ }
13739
+
13740
+ //#endregion
13741
+ //#region src/cli/commands/worker/index.ts
13742
+ function currentLogDir() {
13743
+ const { workers } = readWorkersSection();
13744
+ return workersLogDir(workers);
13745
+ }
13746
+ function fail(e) {
13747
+ const msg = e instanceof Error ? e.message : String(e);
13748
+ console.error(err("pai worker: ") + msg);
13749
+ process.exitCode = 1;
13750
+ }
13751
+ function registerWorkerCommands(workerCmd) {
13752
+ workerCmd.command("run").description("Run one claude-code worker through the configured provider.\nUnknown options are passed to claude verbatim (e.g. -p, --allowedTools);\n--output-format/--verbose are handled here.").allowUnknownOption(true).option("--provider <name>", "Provider to run on (default: active, else routing order)").option("--role <role>", "Use the provider of this role (implement, research, spotcheck, …)").option("--model <model>", "Override the provider's model for this run").option("--label <text>", "Short task label shown in ps / follow / status line").option("--mcp <names>", "MCP servers/sets this worker may use (comma-separated; see `pai worker mcp`)").option("--no-pane", "Do not open a follow pane for this worker").argument("[args...]", "claude arguments, e.g. -p '<task>' --allowedTools 'Read,Edit,Bash'").action(async (args, opts) => {
13753
+ try {
13754
+ const rc = await runWorker({
13755
+ providerFlag: opts.provider,
13756
+ role: opts.role,
13757
+ modelFlag: opts.model,
13758
+ label: opts.label,
13759
+ mcpFlag: opts.mcp,
13760
+ noPane: opts.pane === false,
13761
+ claudeArgs: args
13762
+ });
13763
+ process.exitCode = rc;
13764
+ } catch (e) {
13765
+ fail(e);
13766
+ }
13767
+ });
13768
+ workerCmd.command("ps").description("List workers of this session (RUNNING + FINISHED); --all for every worker").option("--all", "Show workers of all sessions, not just this terminal's").action((opts) => {
13769
+ try {
13770
+ console.log(psOutput(currentLogDir(), opts.all === true));
13771
+ } catch (e) {
13772
+ fail(e);
13773
+ }
13774
+ });
13775
+ workerCmd.command("follow [id]").description("Live transcript of one worker, or of this session's running workers").option("--all", "Follow workers of all sessions").option("--auto-exit [secs]", "Exit secs after the workers end (pane mode; default from config)").action(async (id, opts) => {
13776
+ try {
13777
+ const { workers } = readWorkersSection();
13778
+ const autoExit = opts.autoExit === void 0 || opts.autoExit === false ? 0 : opts.autoExit === true ? workers.pane.autoExitSecs : Number(opts.autoExit);
13779
+ await followWorkers(currentLogDir(), id ?? null, opts.all === true, autoExit);
13780
+ } catch (e) {
13781
+ fail(e);
13782
+ }
13783
+ });
13784
+ workerCmd.command("replay <id>").description("Print the transcript of one finished or running worker").option("--tail <n>", "Only the last n rendered lines", parseIntArg).action((id, opts) => {
13785
+ try {
13786
+ console.log(replayOutput(currentLogDir(), id, process.stdout.isTTY === true, opts.tail));
13787
+ } catch (e) {
13788
+ fail(e);
13789
+ }
13790
+ });
13791
+ workerCmd.command("watch").description("ps refreshed every 2 seconds (plain `watch`, colors kept)").action(() => {
13792
+ const proc = spawn("watch", [
13793
+ "-n",
13794
+ "2",
13795
+ "pai worker ps --all"
13796
+ ], { stdio: "inherit" });
13797
+ proc.on("error", (e) => fail(/* @__PURE__ */ new Error(`cannot run watch: ${e.message}`)));
13798
+ proc.on("close", (code) => {
13799
+ process.exitCode = code ?? 1;
13800
+ });
13801
+ });
13802
+ workerCmd.command("pane [id]").description("Open the follow pane for a worker (or one shared pane for this session)").option("--check", "Only report whether the pane is open, plus the profile file's path and font").action(async (id, opts) => {
13803
+ try {
13804
+ const { workers } = readWorkersSection();
13805
+ const logDir = currentLogDir();
13806
+ const term = process.env.ITERM_SESSION_ID ?? "";
13807
+ if (id) {
13808
+ const msg = opts.check ? await checkPaneForWorker(id, workers.pane.fontSize) : await openPaneForWorker(logDir, workers, id, term);
13809
+ console.log(msg);
13810
+ } else console.log(await openFollowPane(logDir, workers, term, opts.check === true));
13811
+ } catch (e) {
13812
+ fail(e);
13813
+ }
13814
+ });
13815
+ workerCmd.command("log [what]").description("all = ledger, tail = last ledger lines, <id> = raw event stream, none = list").action((what) => {
13816
+ try {
13817
+ const logDir = currentLogDir();
13818
+ const ledger = ledgerPath(logDir);
13819
+ if (what === "all") {
13820
+ console.log(existsSync(ledger) ? readFileSync(ledger, "utf8") : dim$1("(no ledger yet)"));
13821
+ return;
13822
+ }
13823
+ if (what === "tail") {
13824
+ if (!existsSync(ledger)) {
13825
+ console.log(dim$1("(no ledger yet)"));
13826
+ return;
13827
+ }
13828
+ const lines = readFileSync(ledger, "utf8").trim().split("\n");
13829
+ console.log(lines.slice(-20).join("\n"));
13830
+ return;
13831
+ }
13832
+ if (what) {
13833
+ const path = eventsPath(logDir, what);
13834
+ if (!existsSync(path)) {
13835
+ fail(/* @__PURE__ */ new Error(`no event log for ${what}`));
13836
+ return;
13837
+ }
13838
+ const lines = readFileSync(path, "utf8").trim().split("\n");
13839
+ console.log(lines.slice(-30).join("\n"));
13840
+ return;
13841
+ }
13842
+ if (!existsSync(logDir)) {
13843
+ console.log(dim$1("(no workers yet)"));
13844
+ return;
13845
+ }
13846
+ for (const f of readdirSync(logDir).filter((x) => x.endsWith(".jsonl")).sort()) {
13847
+ const st = statSync(`${logDir}/${f}`);
13848
+ const mb = (st.size / 1024 / 1024).toFixed(1);
13849
+ console.log(` ${f.replace(".jsonl", "")} ${mb} MB ${st.mtime.toISOString().slice(0, 19).replace("T", " ")}`);
13850
+ }
13851
+ } catch (e) {
13852
+ fail(e);
13853
+ }
13854
+ });
13855
+ workerCmd.command("say <id> <text>").description("Send one message to a running worker (forwarded to its open stdin)").action(async (id, text) => {
13856
+ try {
13857
+ await sayToWorker(currentLogDir(), id, text);
13858
+ console.log("ok");
13859
+ } catch (e) {
13860
+ fail(e);
13861
+ }
13862
+ });
13863
+ workerCmd.command("resume <id> <text>").description("Continue a finished worker on the same provider: claude --resume <session>").option("--print-id", "Print the new worker id on its own line (pane follow handoff)").option("--no-pane", "Do not open a follow pane for the resumed worker").action(async (id, text, opts) => {
13864
+ try {
13865
+ const old = loadStatus(currentLogDir(), id);
13866
+ if (!old) {
13867
+ fail(/* @__PURE__ */ new Error(`no worker named "${id}"`));
13868
+ return;
13869
+ }
13870
+ if (!old.claudeSession) {
13871
+ fail(/* @__PURE__ */ new Error(`worker ${id} recorded no Claude session id — it predates resume support or ran through an engine that does not expose one`));
13872
+ return;
13873
+ }
13874
+ let newId = "";
13875
+ const rc = await runWorker({
13876
+ providerFlag: old.provider,
13877
+ modelFlag: old.model,
13878
+ label: `↩ ${old.label}`,
13879
+ noPane: opts.pane === false,
13880
+ claudeArgs: [
13881
+ "--resume",
13882
+ old.claudeSession,
13883
+ "-p",
13884
+ text
13885
+ ],
13886
+ onWorkerStart: (wid) => {
13887
+ newId = wid;
13888
+ }
13889
+ });
13890
+ if (opts.printId === true && newId) console.log(newId);
13891
+ process.exitCode = rc;
13892
+ } catch (e) {
13893
+ fail(e);
13894
+ }
13895
+ });
13896
+ workerCmd.command("proxy [stop]").description("The local Anthropic↔OpenAI proxy (loopback only); started on demand by `run`,\n`proxy` starts/verifies it, `proxy stop` stops it again").option("--port <n>", `Port to listen on (default ${DEFAULT_PROXY_PORT})`, parseIntArg).action(async (stop, opts) => {
13897
+ try {
13898
+ const logDir = currentLogDir();
13899
+ if (stop === "stop") {
13900
+ console.log(stopProxy(logDir));
13901
+ return;
13902
+ }
13903
+ if (stop) {
13904
+ fail(/* @__PURE__ */ new Error(`unknown proxy argument "${stop}" (expected: stop)`));
13905
+ return;
13906
+ }
13907
+ const url = await ensureProxyRunning(opts.port ?? DEFAULT_PROXY_PORT, logDir);
13908
+ console.log(`proxy listening on ${url} — point openai providers at ${url}/<provider>`);
13909
+ } catch (e) {
13910
+ fail(e);
13911
+ }
13912
+ });
13913
+ workerCmd.command("mcp [list]").description("MCP servers workers may load via --mcp / roles, and the configured sets").action((what) => {
13914
+ if (what && what !== "list") {
13915
+ fail(/* @__PURE__ */ new Error(`unknown mcp argument "${what}" (expected: list)`));
13916
+ return;
13917
+ }
13918
+ try {
13919
+ const { workers } = readWorkersSection();
13920
+ console.log(describeMcp(workers));
13921
+ } catch (e) {
13922
+ fail(e);
13923
+ }
13924
+ });
13925
+ workerCmd.command("status-line [term] [cwd]").description("One-line worker summary for a status bar (empty when none in scope).\nCalled by statusline-command.sh with ITERM_SESSION_ID and the pane cwd.").action((term, cwd) => {
13926
+ try {
13927
+ const out = statusLineOutput(currentLogDir(), term ?? process.env.ITERM_SESSION_ID ?? "", cwd ?? process.cwd());
13928
+ if (out) console.log(out);
13929
+ } catch (e) {
13930
+ fail(e);
13931
+ }
13932
+ });
13933
+ workerCmd.command("on").description("Route Agent-tool subagents to workers (default when a provider exists)").action(() => {
13934
+ try {
13935
+ setWorkersEnabled(true);
13936
+ console.log("workers on — the Agent hook now denies and suggests `pai worker run`");
13937
+ } catch (e) {
13938
+ fail(e);
13939
+ }
13940
+ });
13941
+ workerCmd.command("off").description("Stop routing: Agent tool runs on Anthropic again").action(() => {
13942
+ try {
13943
+ setWorkersEnabled(false);
13944
+ console.log("workers off — Agent-tool subagents run on Anthropic");
13945
+ } catch (e) {
13946
+ fail(e);
13947
+ }
13948
+ });
13949
+ workerCmd.command("install").description("Migrate: Agent hook in settings.json, ~/.local/bin glm* shims, old script cleanup").action(() => {
13950
+ try {
13951
+ const r = installWorkers();
13952
+ for (const line of r.lines) console.log(` ${line}`);
13953
+ } catch (e) {
13954
+ fail(e);
13955
+ }
13956
+ });
13957
+ registerWorkerProviderCommands(workerCmd.command("providers").description("Providers: list, add, remove, use, enable, disable, test"));
13958
+ registerWorkerRoleCommands(workerCmd);
13959
+ }
13960
+ function parseIntArg(v) {
13961
+ const n = parseInt(v, 10);
13962
+ return Number.isNaN(n) ? 0 : n;
13963
+ }
13964
+
13374
13965
  //#endregion
13375
13966
  //#region src/cli/commands/session-cleanup/types.ts
13376
13967
  const TEMPLATE_INDICATORS = [
@@ -13735,7 +14326,7 @@ async function countVectorDbPaths(oldPaths) {
13735
14326
  if (oldPaths.length === 0) return 0;
13736
14327
  try {
13737
14328
  const { loadConfig } = await import("./config-C_ErGddD.mjs");
13738
- const { PostgresBackend } = await import("./postgres-BVme6qX0.mjs");
14329
+ const { PostgresBackend } = await import("./postgres--BjPtLa0.mjs");
13739
14330
  const config = loadConfig();
13740
14331
  if (config.storageBackend !== "postgres") return 0;
13741
14332
  const pgBackend = new PostgresBackend(config.postgres ?? {});
@@ -13756,7 +14347,7 @@ async function updateVectorDbPaths(moves) {
13756
14347
  if (moves.length === 0) return 0;
13757
14348
  try {
13758
14349
  const { loadConfig } = await import("./config-C_ErGddD.mjs");
13759
- const { PostgresBackend } = await import("./postgres-BVme6qX0.mjs");
14350
+ const { PostgresBackend } = await import("./postgres--BjPtLa0.mjs");
13760
14351
  const config = loadConfig();
13761
14352
  if (config.storageBackend !== "postgres") return 0;
13762
14353
  const pgBackend = new PostgresBackend(config.postgres ?? {});
@@ -15099,6 +15690,7 @@ Examples:
15099
15690
  const sessionCmd = program.command("session").description("Session management: list, info, checkpoint, handover, cleanup, slug, tag, route.\nShort forms: `pai pause`, `pai end`, `pai resume <name>`.");
15100
15691
  registerSessionCommands(sessionCmd, getDb);
15101
15692
  registerSessionCleanupCommand(sessionCmd, getDb);
15693
+ registerWorkerCommands(program.command("worker").description("Run subagents on configured worker providers: run, ps, follow, replay,\npane, log, status-line, providers, roles, on, off, install"));
15102
15694
  program.command("sessions").description("Alias for `pai` — show the unified deduped listing.").option("--all", "Show all entries including cold / zero-session / archived projects").option("-n, --n <count>", "Max candidates for history search", "20").action(async (opts) => {
15103
15695
  await cmdMain(getDb(), void 0, void 0, opts);
15104
15696
  });
@@ -15280,4 +15872,4 @@ claude() {
15280
15872
 
15281
15873
  //#endregion
15282
15874
  export { drainStdio as n, buildProgram as t };
15283
- //# sourceMappingURL=program-C-fUghPv.mjs.map
15875
+ //# sourceMappingURL=program-CEIHn_Ma.mjs.map