@tekmidian/pai 0.36.2 → 0.38.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 +549 -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 +6 -0
  32. package/dist/hooks/post-compact-inject.mjs.map +3 -3
  33. package/dist/hooks/route-agents-to-worker.mjs +518 -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 +858 -0
  48. package/dist/hooks/worker-proxy.mjs.map +7 -0
  49. package/dist/hooks/worker-status-line.mjs +545 -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-Y0hAiVy8.mjs} +851 -101
  69. package/dist/program-Y0hAiVy8.mjs.map +1 -0
  70. package/dist/providers-DUshcB-d.mjs +4031 -0
  71. package/dist/providers-DUshcB-d.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 +63 -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 +29 -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 +398 -0
  128. package/docs/commands/zettel.md +1 -1
  129. package/docs/worker.md +434 -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 +2 -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 ledgerPath, C as checkPaneForWorker, E as loadStatus, F as readJsonStrict, I as writeJsonAtomic, M as PROVIDER_TAGS, N as WorkersConfigError, O as parseRunnerArgs, P as readWorkersSection, S as describeMcp, T as openPaneForWorker, _ as testProvider, a as setClass, b as stopProxy, c as unsetClass, d as followWorkers, f as psOutput, g as runWorker, h as runChain, i as removeProvider, j as workersLogDir, k as eventsPath, l as updateProvider, m as statusLineOutput, n as classTargetText, o as setProviderEnabled, p as replayOutput, r as describeProviders, s as setWorkersEnabled, t as addProvider, u as useProvider, v as DEFAULT_PROXY_PORT, w as openFollowPane, x as sayToWorker, y as ensureProxyRunning } from "./providers-DUshcB-d.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
+ classes: {}
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 classes.");
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
+ classes: {}
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,549 @@ function registerSessionCommands(sessionCmd, getDb) {
13371
13577
  });
13372
13578
  }
13373
13579
 
13580
+ //#endregion
13581
+ //#region src/workers/agents.ts
13582
+ /**
13583
+ * agents.ts — run agent definitions (~/.claude/agents/<name>.md) as workers.
13584
+ *
13585
+ * The file format is the Claude Code agent one: YAML front matter (`model`,
13586
+ * `tools`, `description`) followed by a Markdown body that is the agent's
13587
+ * system prompt. `pai worker run --agent <name>` loads it and maps it onto
13588
+ * the worker runner:
13589
+ *
13590
+ * - body → --append-system-prompt (prepended, so an explicit caller
13591
+ * flag still wins);
13592
+ * - tools → --allowedTools;
13593
+ * - model → a task class (haiku→simple, sonnet→implement, opus→complex),
13594
+ * unless --class is given.
13595
+ */
13596
+ /** Directory the agent library lives in. */
13597
+ function agentsDir() {
13598
+ return join(homedir(), ".claude", "agents");
13599
+ }
13600
+ function agentPath(name) {
13601
+ return join(agentsDir(), `${name}.md`);
13602
+ }
13603
+ /** `key: value` (bare) or `key: [a, b]` / `key:\n - a\n - b` (list). */
13604
+ function parseFrontMatter(text) {
13605
+ const out = {};
13606
+ let key = "";
13607
+ for (const raw of text.split("\n")) {
13608
+ const listItem = /^\s+-\s+(.*)$/.exec(raw);
13609
+ if (listItem && key) {
13610
+ const cur = out[key];
13611
+ const item = listItem[1].trim().replace(/^["']|["']$/g, "");
13612
+ out[key] = Array.isArray(cur) ? [...cur, item] : [item];
13613
+ continue;
13614
+ }
13615
+ const kv = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(raw);
13616
+ if (!kv) continue;
13617
+ key = kv[1];
13618
+ const val = kv[2].trim();
13619
+ if (!val) out[key] = [];
13620
+ else if (val.startsWith("[") && val.endsWith("]")) out[key] = val.slice(1, -1).split(",").map((s) => s.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
13621
+ else out[key] = val.replace(/^["']|["']$/g, "");
13622
+ }
13623
+ return out;
13624
+ }
13625
+ /** Parse an agent file's text (front matter + body). Throws on empty body. */
13626
+ function parseAgentFile(name, text, path) {
13627
+ const m = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/.exec(text);
13628
+ if (!m) throw new Error(`${path}: expected YAML front matter (--- … ---) before the agent body`);
13629
+ const fm = parseFrontMatter(m[1]);
13630
+ const body = m[2].trim();
13631
+ if (!body) throw new Error(`${path}: the agent body (after the front matter) is empty`);
13632
+ const def = {
13633
+ name,
13634
+ body,
13635
+ path
13636
+ };
13637
+ if (typeof fm.model === "string" && fm.model) def.model = fm.model;
13638
+ if (Array.isArray(fm.tools) && fm.tools.length) def.tools = fm.tools;
13639
+ if (typeof fm.description === "string" && fm.description) def.description = fm.description;
13640
+ return def;
13641
+ }
13642
+ /** Load ~/.claude/agents/<name>.md; the error names the library when missing. */
13643
+ function loadAgent(name) {
13644
+ const path = agentPath(name);
13645
+ let text;
13646
+ try {
13647
+ text = readFileSync(path, "utf8");
13648
+ } catch {
13649
+ throw new Error(`no agent named "${name}" at ${path}. The agent library lives in ~/.claude/agents/<name>.md and runs on workers.`);
13650
+ }
13651
+ return parseAgentFile(name, text, path);
13652
+ }
13653
+ /** Map an agent's front-matter model to a worker class. */
13654
+ function modelToClass(model) {
13655
+ if (!model) return void 0;
13656
+ const m = model.toLowerCase();
13657
+ if (m.includes("haiku")) return "simple";
13658
+ if (m.includes("sonnet")) return "implement";
13659
+ if (m.includes("opus")) return "complex";
13660
+ }
13661
+ /** Extra claude args the definition contributes (before the caller's own). */
13662
+ function agentClaudeArgs(def) {
13663
+ const args = [];
13664
+ if (def.tools?.length) args.push("--allowedTools", def.tools.join(","));
13665
+ args.push("--append-system-prompt", def.body);
13666
+ return args;
13667
+ }
13668
+ /** `<agent>: <first 50 chars of prompt>` — the default label for a run. */
13669
+ function agentLabel(name, prompt) {
13670
+ return `${name}: ${prompt ?? "(no prompt)"}`.slice(0, name.length + 2 + 50);
13671
+ }
13672
+
13673
+ //#endregion
13674
+ //#region src/cli/commands/worker/providers.ts
13675
+ function fail$1(e) {
13676
+ const msg = e instanceof Error ? e.message : String(e);
13677
+ console.error(err("pai worker: ") + msg);
13678
+ process.exitCode = 1;
13679
+ }
13680
+ /** `--env K=V` (repeatable) → record. Throws on a value without "=". */
13681
+ function collectEnv(pairs) {
13682
+ const out = {};
13683
+ for (const p of pairs ?? []) {
13684
+ const eq = p.indexOf("=");
13685
+ if (eq <= 0) throw new WorkersConfigError(`--env expects NAME=VALUE, got "${p}"`);
13686
+ out[p.slice(0, eq)] = p.slice(eq + 1);
13687
+ }
13688
+ return out;
13689
+ }
13690
+ function parseIntArg$1(v) {
13691
+ const n = parseInt(v, 10);
13692
+ if (Number.isNaN(n) || n <= 0) throw new WorkersConfigError(`expected a positive number, got "${v}"`);
13693
+ return n;
13694
+ }
13695
+ function parseCostTier(v) {
13696
+ const n = parseInt(v, 10);
13697
+ if (!Number.isInteger(n) || n < 1 || n > 5) throw new WorkersConfigError(`cost tier must be an integer 1 (cheapest) … 5 (most expensive), got "${v}"`);
13698
+ return n;
13699
+ }
13700
+ /** `--tags a,b` (repeatable) → validated tag list. */
13701
+ function collectTags(vals) {
13702
+ const out = [];
13703
+ for (const v of vals ?? []) for (const t of v.split(",").map((s) => s.trim()).filter(Boolean)) {
13704
+ if (!PROVIDER_TAGS.includes(t)) throw new WorkersConfigError(`"${t}" is not a tag (from: ${PROVIDER_TAGS.join(", ")})`);
13705
+ if (!out.includes(t)) out.push(t);
13706
+ }
13707
+ return out;
13708
+ }
13709
+ /** Positional target + constraint flags → a ClassTarget for setClass. */
13710
+ function buildClassTarget(target, opts) {
13711
+ const hasConstraints = opts.provider !== void 0 || opts.mcp !== void 0 || opts.maxCostTier !== void 0 || opts.requireTags !== void 0 || opts.order !== void 0;
13712
+ if (target !== void 0 && !hasConstraints) return target;
13713
+ const obj = {};
13714
+ if (target !== void 0) obj.provider = target.split("/")[0];
13715
+ if (opts.provider !== void 0) obj.provider = opts.provider;
13716
+ if (opts.mcp !== void 0) obj.mcp = opts.mcp.split(",").map((s) => s.trim()).filter(Boolean);
13717
+ if (opts.maxCostTier !== void 0) obj.maxCostTier = opts.maxCostTier;
13718
+ if (opts.requireTags !== void 0) obj.requireTags = opts.requireTags;
13719
+ if (opts.order !== void 0) obj.order = opts.order.split(",").map((s) => s.trim()).filter(Boolean);
13720
+ if (target !== void 0 && target.includes("/") && obj.provider) {
13721
+ const alias = target.split("/")[1];
13722
+ if (alias && alias !== "default") throw new WorkersConfigError(`a provider alias ("${alias}") cannot be combined with constraint flags — use the plain "<target>" form for pinning`);
13723
+ }
13724
+ if (!Object.keys(obj).length) throw new WorkersConfigError("give a target (provider, provider/fast) or a constraint (--max-cost-tier, --require-tags, …)");
13725
+ return obj;
13726
+ }
13727
+ function registerWorkerProviderCommands(providersCmd) {
13728
+ providersCmd.description("Providers: list (default), add, remove, use, enable, disable, test").action(() => {
13729
+ const { workers } = readWorkersSection();
13730
+ console.log();
13731
+ for (const line of describeProviders(workers)) console.log(` ${line}`);
13732
+ console.log();
13733
+ console.log(dim$1(` workers are ${workers.enabled ? "on" : "off"} — pai worker ${workers.enabled ? "off" : "on"}`));
13734
+ console.log();
13735
+ });
13736
+ providersCmd.command("add <name>").description("Add a provider; the first one also turns workers on and seeds classes.\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)").option("--cost-tier <1-5>", "Cost tier 1 (cheapest) … 5 (most expensive; default 3)", parseCostTier).option("--tags <tags>", "Capability tags, comma-separated (from: " + PROVIDER_TAGS.join(", ") + ")", (v, acc) => [...acc, v], []).action((name, opts) => {
13737
+ try {
13738
+ const protocol = opts.protocol;
13739
+ const engine = opts.engine;
13740
+ if (protocol && protocol !== "anthropic" && protocol !== "openai") throw new WorkersConfigError(`--protocol must be anthropic or openai, got "${protocol}"`);
13741
+ if (engine && engine !== "claude" && engine !== "codex") throw new WorkersConfigError(`--engine must be claude or codex, got "${engine}"`);
13742
+ if (protocol !== "openai" && !opts.baseUrl) throw new WorkersConfigError("--base-url is required (only --protocol openai goes without it)");
13743
+ const tags = collectTags(opts.tags);
13744
+ addProvider({
13745
+ name,
13746
+ baseUrl: opts.baseUrl ?? "",
13747
+ keyFile: opts.keyFile ?? null,
13748
+ model: opts.model,
13749
+ fastModel: opts.fastModel,
13750
+ env: collectEnv(opts.env),
13751
+ note: opts.note,
13752
+ ...protocol ? { protocol } : {},
13753
+ ...opts.upstreamUrl ? { upstreamUrl: opts.upstreamUrl } : {},
13754
+ ...engine ? { engine } : {},
13755
+ ...opts.contextWindow ? { contextWindow: opts.contextWindow } : {},
13756
+ quotaProbe: opts.quotaProbe,
13757
+ ...opts.costTier !== void 0 ? { costTier: opts.costTier } : {},
13758
+ ...tags.length ? { tags } : {}
13759
+ });
13760
+ const { workers } = readWorkersSection();
13761
+ console.log(ok$1(`provider ${name} added`));
13762
+ if (workers.active === name) console.log(` active: ${name} — workers ${workers.enabled ? "on" : "off"}`);
13763
+ for (const line of describeProviders(workers)) console.log(dim$1(` ${line}`));
13764
+ } catch (e) {
13765
+ fail$1(e);
13766
+ }
13767
+ });
13768
+ providersCmd.command("update <name>").description("Change cost tier and tags of a provider (routing constraints use these)").option("--cost-tier <1-5>", "Cost tier 1 (cheapest) … 5 (most expensive)", parseCostTier).option("--tags <tags>", "Capability tags, comma-separated (from: " + PROVIDER_TAGS.join(", ") + "); --tags '' clears", (v, acc) => [...acc, v], []).action((name, opts) => {
13769
+ try {
13770
+ if (opts.costTier === void 0 && opts.tags === void 0) throw new WorkersConfigError("nothing to update — pass --cost-tier and/or --tags");
13771
+ const tags = opts.tags === void 0 ? void 0 : collectTags(opts.tags);
13772
+ updateProvider(name, {
13773
+ ...opts.costTier !== void 0 ? { costTier: opts.costTier } : {},
13774
+ ...tags !== void 0 ? { tags } : {}
13775
+ });
13776
+ console.log(ok$1(`provider ${name} updated`));
13777
+ const { workers } = readWorkersSection();
13778
+ for (const line of describeProviders(workers)) console.log(dim$1(` ${line}`));
13779
+ } catch (e) {
13780
+ fail$1(e);
13781
+ }
13782
+ });
13783
+ providersCmd.command("remove <name>").description("Remove a provider and any classes pointing at it").action((name) => {
13784
+ try {
13785
+ removeProvider(name);
13786
+ console.log(ok$1(`provider ${name} removed`));
13787
+ } catch (e) {
13788
+ fail$1(e);
13789
+ }
13790
+ });
13791
+ providersCmd.command("use <name>").description("Make this provider the active one for runs without --provider/--class").action((name) => {
13792
+ try {
13793
+ useProvider(name);
13794
+ console.log(ok$1(`active provider: ${name}`));
13795
+ } catch (e) {
13796
+ fail$1(e);
13797
+ }
13798
+ });
13799
+ providersCmd.command("enable <name>").description("Enable a provider (also clears its cooldown)").action((name) => {
13800
+ try {
13801
+ setProviderEnabled(name, true);
13802
+ console.log(ok$1(`provider ${name} enabled`));
13803
+ } catch (e) {
13804
+ fail$1(e);
13805
+ }
13806
+ });
13807
+ providersCmd.command("disable <name>").description("Disable a provider (auto-routing skips it; --provider still works)").action((name) => {
13808
+ try {
13809
+ setProviderEnabled(name, false);
13810
+ console.log(ok$1(`provider ${name} disabled`));
13811
+ } catch (e) {
13812
+ fail$1(e);
13813
+ }
13814
+ });
13815
+ providersCmd.command("test [name]").description("One-word pong probe through a provider (default: the active one)").action(async (name) => {
13816
+ try {
13817
+ const { workers } = readWorkersSection();
13818
+ const pname = name ?? workers.active ?? "";
13819
+ if (!pname || !workers.providers[pname]) throw new WorkersConfigError(name ? `no provider named "${name}"` : "no active provider — name one explicitly");
13820
+ console.log(dim$1(` probing ${pname} (${workers.providers[pname].models.default}) …`));
13821
+ const r = await testProvider(pname, workers.providers[pname], workersLogDir(workers));
13822
+ const head = `${r.provider} ${r.model} ${(r.latencyMs / 1e3).toFixed(1)}s`;
13823
+ if (r.skipped) {
13824
+ console.log(dim$1(` ${head} ${r.skipped}`));
13825
+ console.log(` reply: ${r.result.slice(0, 200)}`);
13826
+ return;
13827
+ }
13828
+ console.log(r.ok ? ok$1(` ${head}`) : err(` ${head}`));
13829
+ console.log(` reply: ${r.result.slice(0, 200)}`);
13830
+ process.exitCode = r.ok ? 0 : 1;
13831
+ } catch (e) {
13832
+ fail$1(e);
13833
+ }
13834
+ });
13835
+ }
13836
+ function printClasses() {
13837
+ const { workers } = readWorkersSection();
13838
+ const entries = Object.entries(workers.classes);
13839
+ if (!entries.length) {
13840
+ console.log(dim$1(" no classes set — runs use the active provider (or auto routing)"));
13841
+ return;
13842
+ }
13843
+ for (const [cls, target] of entries) console.log(` ${cls.padEnd(12)} ${classTargetText(target)}`);
13844
+ }
13845
+ function registerClassSubcommands(classesCmd) {
13846
+ classesCmd.command("list").description("List classes and their targets (default action)").action(() => printClasses());
13847
+ classesCmd.command("set <class> [target]").description("Point a class at a provider (or provider/fast), or give only constraints:\nclasses set research --max-cost-tier 2 --require-tags long-context,reasoning").option("--provider <name>", "Pin the class to this provider (object form)").option("--mcp <names>", "MCP servers/sets for runs of this class (comma-separated)").option("--max-cost-tier <1-5>", "Auto-routing considers only providers up to this cost tier", parseCostTier).option("--require-tags <tags>", "Auto-routing needs these tags (comma-separated)", (v) => v.split(",").map((s) => s.trim()).filter(Boolean)).option("--order <providers>", "Per-class routing order overriding workers.routing.order (comma-separated)").action((cls, target, opts) => {
13848
+ try {
13849
+ const built = buildClassTarget(target, opts);
13850
+ setClass(cls, built);
13851
+ console.log(ok$1(`class ${cls} → ${classTargetText(built)}`));
13852
+ } catch (e) {
13853
+ fail$1(e);
13854
+ }
13855
+ });
13856
+ classesCmd.command("unset <class>").description("Remove a class (runs then use the active provider)").action((cls) => {
13857
+ try {
13858
+ unsetClass(cls);
13859
+ console.log(ok$1(`class ${cls} removed`));
13860
+ } catch (e) {
13861
+ fail$1(e);
13862
+ }
13863
+ });
13864
+ }
13865
+ /** `pai worker classes` — class → target assignment (roles is the old name). */
13866
+ function registerWorkerClassCommands(workerCmd) {
13867
+ registerClassSubcommands(workerCmd.command("classes").description("Classes: which provider serves draft / implement / review / …").action(() => printClasses()));
13868
+ registerClassSubcommands(workerCmd.command("roles", { hidden: true }).description("Alias of `classes` (roles was renamed to classes)").action(() => printClasses()));
13869
+ }
13870
+
13871
+ //#endregion
13872
+ //#region src/cli/commands/worker/index.ts
13873
+ function currentLogDir() {
13874
+ const { workers } = readWorkersSection();
13875
+ return workersLogDir(workers);
13876
+ }
13877
+ function fail(e) {
13878
+ const msg = e instanceof Error ? e.message : String(e);
13879
+ console.error(err("pai worker: ") + msg);
13880
+ process.exitCode = 1;
13881
+ }
13882
+ function registerWorkerCommands(workerCmd) {
13883
+ 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.\n--chain draft,implement[,review] runs a spec-first pipeline;\n--agent <name> runs an agent definition from ~/.claude/agents.").allowUnknownOption(true).option("--provider <name>", "Provider to run on (default: active, else routing order)").option("--class <name>", "Use the provider of this class (draft, implement, review, research, spotcheck, simple, complex, image)").option("--role <name>", "Alias of --class (roles were renamed to classes)").option("--chain <stages>", "Comma-separated stage classes, e.g. draft,implement or draft,implement,review").option("--agent <name>", "Run the agent definition ~/.claude/agents/<name>.md on a worker").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) => {
13884
+ try {
13885
+ const className = opts.class ?? opts.role;
13886
+ let claudeArgs = args;
13887
+ let label = opts.label;
13888
+ let agentClass;
13889
+ if (opts.agent) {
13890
+ const def = loadAgent(opts.agent);
13891
+ claudeArgs = [...agentClaudeArgs(def), ...args];
13892
+ agentClass = modelToClass(def.model);
13893
+ if (!label) label = agentLabel(opts.agent, parseRunnerArgs(args).prompt);
13894
+ }
13895
+ if (opts.chain) {
13896
+ const brief = parseRunnerArgs(claudeArgs).prompt;
13897
+ if (!brief) throw new Error("--chain needs the task as -p '<brief>'");
13898
+ const rc = await runChain({
13899
+ stages: opts.chain.split(","),
13900
+ className: className ?? agentClass,
13901
+ providerFlag: opts.provider,
13902
+ modelFlag: opts.model,
13903
+ label,
13904
+ noPane: opts.pane === false,
13905
+ mcpFlag: opts.mcp,
13906
+ brief,
13907
+ claudeArgs
13908
+ });
13909
+ process.exitCode = rc;
13910
+ return;
13911
+ }
13912
+ const rc = await runWorker({
13913
+ providerFlag: opts.provider,
13914
+ className: className ?? agentClass,
13915
+ modelFlag: opts.model,
13916
+ label,
13917
+ mcpFlag: opts.mcp,
13918
+ noPane: opts.pane === false,
13919
+ claudeArgs
13920
+ });
13921
+ process.exitCode = rc;
13922
+ } catch (e) {
13923
+ fail(e);
13924
+ }
13925
+ });
13926
+ 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) => {
13927
+ try {
13928
+ console.log(psOutput(currentLogDir(), opts.all === true));
13929
+ } catch (e) {
13930
+ fail(e);
13931
+ }
13932
+ });
13933
+ 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) => {
13934
+ try {
13935
+ const { workers } = readWorkersSection();
13936
+ const autoExit = opts.autoExit === void 0 || opts.autoExit === false ? 0 : opts.autoExit === true ? workers.pane.autoExitSecs : Number(opts.autoExit);
13937
+ await followWorkers(currentLogDir(), id ?? null, opts.all === true, autoExit);
13938
+ } catch (e) {
13939
+ fail(e);
13940
+ }
13941
+ });
13942
+ 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) => {
13943
+ try {
13944
+ console.log(replayOutput(currentLogDir(), id, process.stdout.isTTY === true, opts.tail));
13945
+ } catch (e) {
13946
+ fail(e);
13947
+ }
13948
+ });
13949
+ workerCmd.command("watch").description("ps refreshed every 2 seconds (plain `watch`, colors kept)").action(() => {
13950
+ const proc = spawn("watch", [
13951
+ "-n",
13952
+ "2",
13953
+ "pai worker ps --all"
13954
+ ], { stdio: "inherit" });
13955
+ proc.on("error", (e) => fail(/* @__PURE__ */ new Error(`cannot run watch: ${e.message}`)));
13956
+ proc.on("close", (code) => {
13957
+ process.exitCode = code ?? 1;
13958
+ });
13959
+ });
13960
+ 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, font, and the hosting window's bounds").action(async (id, opts) => {
13961
+ try {
13962
+ const { workers } = readWorkersSection();
13963
+ const logDir = currentLogDir();
13964
+ const term = process.env.ITERM_SESSION_ID ?? "";
13965
+ if (id) {
13966
+ const msg = opts.check ? await checkPaneForWorker(id, workers.pane.fontSize, term) : await openPaneForWorker(logDir, workers, id, term);
13967
+ console.log(msg);
13968
+ } else console.log(await openFollowPane(logDir, workers, term, opts.check === true));
13969
+ } catch (e) {
13970
+ fail(e);
13971
+ }
13972
+ });
13973
+ workerCmd.command("log [what]").description("all = ledger, tail = last ledger lines, <id> = raw event stream, none = list").action((what) => {
13974
+ try {
13975
+ const logDir = currentLogDir();
13976
+ const ledger = ledgerPath(logDir);
13977
+ if (what === "all") {
13978
+ console.log(existsSync(ledger) ? readFileSync(ledger, "utf8") : dim$1("(no ledger yet)"));
13979
+ return;
13980
+ }
13981
+ if (what === "tail") {
13982
+ if (!existsSync(ledger)) {
13983
+ console.log(dim$1("(no ledger yet)"));
13984
+ return;
13985
+ }
13986
+ const lines = readFileSync(ledger, "utf8").trim().split("\n");
13987
+ console.log(lines.slice(-20).join("\n"));
13988
+ return;
13989
+ }
13990
+ if (what) {
13991
+ const path = eventsPath(logDir, what);
13992
+ if (!existsSync(path)) {
13993
+ fail(/* @__PURE__ */ new Error(`no event log for ${what}`));
13994
+ return;
13995
+ }
13996
+ const lines = readFileSync(path, "utf8").trim().split("\n");
13997
+ console.log(lines.slice(-30).join("\n"));
13998
+ return;
13999
+ }
14000
+ if (!existsSync(logDir)) {
14001
+ console.log(dim$1("(no workers yet)"));
14002
+ return;
14003
+ }
14004
+ for (const f of readdirSync(logDir).filter((x) => x.endsWith(".jsonl")).sort()) {
14005
+ const st = statSync(`${logDir}/${f}`);
14006
+ const mb = (st.size / 1024 / 1024).toFixed(1);
14007
+ console.log(` ${f.replace(".jsonl", "")} ${mb} MB ${st.mtime.toISOString().slice(0, 19).replace("T", " ")}`);
14008
+ }
14009
+ } catch (e) {
14010
+ fail(e);
14011
+ }
14012
+ });
14013
+ workerCmd.command("say <id> <text>").description("Send one message to a running worker (forwarded to its open stdin)").action(async (id, text) => {
14014
+ try {
14015
+ await sayToWorker(currentLogDir(), id, text);
14016
+ console.log("ok");
14017
+ } catch (e) {
14018
+ fail(e);
14019
+ }
14020
+ });
14021
+ 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) => {
14022
+ try {
14023
+ const old = loadStatus(currentLogDir(), id);
14024
+ if (!old) {
14025
+ fail(/* @__PURE__ */ new Error(`no worker named "${id}"`));
14026
+ return;
14027
+ }
14028
+ if (!old.claudeSession) {
14029
+ 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`));
14030
+ return;
14031
+ }
14032
+ let newId = "";
14033
+ const rc = await runWorker({
14034
+ providerFlag: old.provider,
14035
+ modelFlag: old.model,
14036
+ label: `↩ ${old.label}`,
14037
+ noPane: opts.pane === false,
14038
+ claudeArgs: [
14039
+ "--resume",
14040
+ old.claudeSession,
14041
+ "-p",
14042
+ text
14043
+ ],
14044
+ onWorkerStart: (wid) => {
14045
+ newId = wid;
14046
+ }
14047
+ });
14048
+ if (opts.printId === true && newId) console.log(newId);
14049
+ process.exitCode = rc;
14050
+ } catch (e) {
14051
+ fail(e);
14052
+ }
14053
+ });
14054
+ 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) => {
14055
+ try {
14056
+ const logDir = currentLogDir();
14057
+ if (stop === "stop") {
14058
+ console.log(stopProxy(logDir));
14059
+ return;
14060
+ }
14061
+ if (stop) {
14062
+ fail(/* @__PURE__ */ new Error(`unknown proxy argument "${stop}" (expected: stop)`));
14063
+ return;
14064
+ }
14065
+ const url = await ensureProxyRunning(opts.port ?? DEFAULT_PROXY_PORT, logDir);
14066
+ console.log(`proxy listening on ${url} — point openai providers at ${url}/<provider>`);
14067
+ } catch (e) {
14068
+ fail(e);
14069
+ }
14070
+ });
14071
+ workerCmd.command("mcp [list]").description("MCP servers workers may load via --mcp / roles, and the configured sets").action((what) => {
14072
+ if (what && what !== "list") {
14073
+ fail(/* @__PURE__ */ new Error(`unknown mcp argument "${what}" (expected: list)`));
14074
+ return;
14075
+ }
14076
+ try {
14077
+ const { workers } = readWorkersSection();
14078
+ console.log(describeMcp(workers));
14079
+ } catch (e) {
14080
+ fail(e);
14081
+ }
14082
+ });
14083
+ 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) => {
14084
+ try {
14085
+ const out = statusLineOutput(currentLogDir(), term ?? process.env.ITERM_SESSION_ID ?? "", cwd ?? process.cwd());
14086
+ if (out) console.log(out);
14087
+ } catch (e) {
14088
+ fail(e);
14089
+ }
14090
+ });
14091
+ workerCmd.command("on").description("Route Agent-tool subagents to workers (default when a provider exists)").action(() => {
14092
+ try {
14093
+ setWorkersEnabled(true);
14094
+ console.log("workers on — the Agent hook now denies and suggests `pai worker run`");
14095
+ } catch (e) {
14096
+ fail(e);
14097
+ }
14098
+ });
14099
+ workerCmd.command("off").description("Stop routing: Agent tool runs on Anthropic again").action(() => {
14100
+ try {
14101
+ setWorkersEnabled(false);
14102
+ console.log("workers off — Agent-tool subagents run on Anthropic");
14103
+ } catch (e) {
14104
+ fail(e);
14105
+ }
14106
+ });
14107
+ workerCmd.command("install").description("Migrate: Agent hook in settings.json, ~/.local/bin glm* shims, old script cleanup").action(() => {
14108
+ try {
14109
+ const r = installWorkers();
14110
+ for (const line of r.lines) console.log(` ${line}`);
14111
+ } catch (e) {
14112
+ fail(e);
14113
+ }
14114
+ });
14115
+ registerWorkerProviderCommands(workerCmd.command("providers").description("Providers: list, add, remove, use, enable, disable, test"));
14116
+ registerWorkerClassCommands(workerCmd);
14117
+ }
14118
+ function parseIntArg(v) {
14119
+ const n = parseInt(v, 10);
14120
+ return Number.isNaN(n) ? 0 : n;
14121
+ }
14122
+
13374
14123
  //#endregion
13375
14124
  //#region src/cli/commands/session-cleanup/types.ts
13376
14125
  const TEMPLATE_INDICATORS = [
@@ -13735,7 +14484,7 @@ async function countVectorDbPaths(oldPaths) {
13735
14484
  if (oldPaths.length === 0) return 0;
13736
14485
  try {
13737
14486
  const { loadConfig } = await import("./config-C_ErGddD.mjs");
13738
- const { PostgresBackend } = await import("./postgres-BVme6qX0.mjs");
14487
+ const { PostgresBackend } = await import("./postgres--BjPtLa0.mjs");
13739
14488
  const config = loadConfig();
13740
14489
  if (config.storageBackend !== "postgres") return 0;
13741
14490
  const pgBackend = new PostgresBackend(config.postgres ?? {});
@@ -13756,7 +14505,7 @@ async function updateVectorDbPaths(moves) {
13756
14505
  if (moves.length === 0) return 0;
13757
14506
  try {
13758
14507
  const { loadConfig } = await import("./config-C_ErGddD.mjs");
13759
- const { PostgresBackend } = await import("./postgres-BVme6qX0.mjs");
14508
+ const { PostgresBackend } = await import("./postgres--BjPtLa0.mjs");
13760
14509
  const config = loadConfig();
13761
14510
  if (config.storageBackend !== "postgres") return 0;
13762
14511
  const pgBackend = new PostgresBackend(config.postgres ?? {});
@@ -15099,6 +15848,7 @@ Examples:
15099
15848
  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
15849
  registerSessionCommands(sessionCmd, getDb);
15101
15850
  registerSessionCleanupCommand(sessionCmd, getDb);
15851
+ 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
15852
  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
15853
  await cmdMain(getDb(), void 0, void 0, opts);
15104
15854
  });
@@ -15280,4 +16030,4 @@ claude() {
15280
16030
 
15281
16031
  //#endregion
15282
16032
  export { drainStdio as n, buildProgram as t };
15283
- //# sourceMappingURL=program-C-fUghPv.mjs.map
16033
+ //# sourceMappingURL=program-Y0hAiVy8.mjs.map