@tekmidian/pai 0.49.0 → 0.50.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 (106) hide show
  1. package/dist/{aibroker-client-D5NT0S1N.mjs → aibroker-client-Bu72yPKj.mjs} +1 -1
  2. package/dist/{aibroker-client-Bn9SnGMa.mjs → aibroker-client-CDIXmq-e.mjs} +1 -1
  3. package/dist/{aibroker-client-Bn9SnGMa.mjs.map → aibroker-client-CDIXmq-e.mjs.map} +1 -1
  4. package/dist/{chain-CNSa0A3G.mjs → chain-BBbVbWVv.mjs} +4 -4
  5. package/dist/{chain-CNSa0A3G.mjs.map → chain-BBbVbWVv.mjs.map} +1 -1
  6. package/dist/cli/index.mjs +14 -14
  7. package/dist/cli/program.mjs +14 -14
  8. package/dist/config-Bn9jpblO.mjs +653 -0
  9. package/dist/config-Bn9jpblO.mjs.map +1 -0
  10. package/dist/{config-DxMZjpvY.mjs → config-DYXARgk8.mjs} +180 -151
  11. package/dist/config-DYXARgk8.mjs.map +1 -0
  12. package/dist/config-RjeVGJ7K.mjs +3 -0
  13. package/dist/{context-handover-cache-B9NKSjwR.mjs → context-handover-cache-yOuK9RIk.mjs} +5 -5
  14. package/dist/{context-handover-cache-B9NKSjwR.mjs.map → context-handover-cache-yOuK9RIk.mjs.map} +1 -1
  15. package/dist/daemon/index.mjs +13 -12
  16. package/dist/daemon/index.mjs.map +1 -1
  17. package/dist/{daemon-DZ4Q0jrs.mjs → daemon-CBTQYtHF.mjs} +49 -30
  18. package/dist/daemon-CBTQYtHF.mjs.map +1 -0
  19. package/dist/daemon-Vf7pHnjR.mjs +25 -0
  20. package/dist/daemon-mcp/index.mjs +186 -23
  21. package/dist/daemon-mcp/index.mjs.map +1 -1
  22. package/dist/factory-Ba8j9K7q.mjs +3 -0
  23. package/dist/{factory-DNYAqjH0.mjs → factory-Bt7TWQje.mjs} +3 -3
  24. package/dist/{factory-DNYAqjH0.mjs.map → factory-Bt7TWQje.mjs.map} +1 -1
  25. package/dist/{fallback-IeR2FrFn.mjs → fallback-CIGspCSi.mjs} +260 -10
  26. package/dist/fallback-CIGspCSi.mjs.map +1 -0
  27. package/dist/hooks/capture-all-events.mjs +80 -8
  28. package/dist/hooks/capture-all-events.mjs.map +4 -4
  29. package/dist/hooks/context-compression-hook.mjs +54 -39
  30. package/dist/hooks/context-compression-hook.mjs.map +4 -4
  31. package/dist/hooks/post-compact-inject.mjs +54 -39
  32. package/dist/hooks/post-compact-inject.mjs.map +4 -4
  33. package/dist/hooks/route-agents-to-worker.mjs +204 -109
  34. package/dist/hooks/route-agents-to-worker.mjs.map +4 -4
  35. package/dist/hooks/whisper-rules.mjs +7613 -9
  36. package/dist/hooks/whisper-rules.mjs.map +4 -4
  37. package/dist/hooks/worker-proxy.mjs +204 -109
  38. package/dist/hooks/worker-proxy.mjs.map +4 -4
  39. package/dist/hooks/worker-status-line.mjs +211 -116
  40. package/dist/hooks/worker-status-line.mjs.map +4 -4
  41. package/dist/hooks/worker-supervision.mjs +208 -113
  42. package/dist/hooks/worker-supervision.mjs.map +4 -4
  43. package/dist/{kg-entity-BSjM5xZh.mjs → kg-entity-BSW7ra8i.mjs} +1 -1
  44. package/dist/{kg-entity-BSjM5xZh.mjs.map → kg-entity-BSW7ra8i.mjs.map} +1 -1
  45. package/dist/main-resolver-BTastpp4.mjs +7 -0
  46. package/dist/{main-resolver-DvYu9jbl.mjs → main-resolver-BmZPCmgj.mjs} +3 -3
  47. package/dist/{main-resolver-DvYu9jbl.mjs.map → main-resolver-BmZPCmgj.mjs.map} +1 -1
  48. package/dist/{planner-Cn8HmT7l.mjs → planner-BiH9msqq.mjs} +6 -6
  49. package/dist/{planner-Cn8HmT7l.mjs.map → planner-BiH9msqq.mjs.map} +1 -1
  50. package/dist/{program-D9-wfmkZ.mjs → program-BX67-4I2.mjs} +233 -277
  51. package/dist/program-BX67-4I2.mjs.map +1 -0
  52. package/dist/query-feedback-Bm6XvCmg.mjs +3 -0
  53. package/dist/{query-feedback-CYyOGi3y.mjs → query-feedback-CTs_jx6i.mjs} +1 -1
  54. package/dist/{query-feedback-CYyOGi3y.mjs.map → query-feedback-CTs_jx6i.mjs.map} +1 -1
  55. package/dist/{run-Cls0YGgV.mjs → run-TChOnMO_.mjs} +1388 -1183
  56. package/dist/run-TChOnMO_.mjs.map +1 -0
  57. package/dist/{server-Ch99xt5K.mjs → server-R9UkCn3v.mjs} +42 -3
  58. package/dist/server-R9UkCn3v.mjs.map +1 -0
  59. package/dist/session-keepalive-BuG3wvik.mjs +579 -0
  60. package/dist/session-keepalive-BuG3wvik.mjs.map +1 -0
  61. package/dist/skills/VaultContext/SKILL.md +1 -1
  62. package/dist/{state-DnYGRmS7.mjs → state-Ca0ltLgI.mjs} +1 -1
  63. package/dist/{state-PJrgfDLW.mjs → state-DwrsOoMi.mjs} +1 -1
  64. package/dist/{state-PJrgfDLW.mjs.map → state-DwrsOoMi.mjs.map} +1 -1
  65. package/dist/{tools-B77WHeVZ.mjs → tools-BEqiJt_j.mjs} +13 -13
  66. package/dist/{tools-B77WHeVZ.mjs.map → tools-BEqiJt_j.mjs.map} +1 -1
  67. package/dist/{tools-NnR_n-Ja.mjs → tools-BwsOTmjh.mjs} +2 -2
  68. package/dist/{wakeup-UrhiW-KU.mjs → wakeup-Yk5lQ0jb.mjs} +1 -1
  69. package/dist/{wakeup-UrhiW-KU.mjs.map → wakeup-Yk5lQ0jb.mjs.map} +1 -1
  70. package/dist/{work-queue-worker-BA5IDBwQ.mjs → work-queue-worker-B9zVWImP.mjs} +3 -3
  71. package/dist/{work-queue-worker-BA5IDBwQ.mjs.map → work-queue-worker-B9zVWImP.mjs.map} +1 -1
  72. package/dist/work-queue-worker-D-DnQ7oh.mjs +14 -0
  73. package/dist/{zettelkasten-BBjA7GcP.mjs → zettelkasten-DezbXghl.mjs} +2 -2
  74. package/dist/{zettelkasten-BBjA7GcP.mjs.map → zettelkasten-DezbXghl.mjs.map} +1 -1
  75. package/docs/cache-keepalive.md +138 -0
  76. package/docs/commands/README.md +7 -0
  77. package/docs/commands/config.md +84 -0
  78. package/docs/commands/daemon.md +6 -0
  79. package/docs/commands/worker.md +37 -2
  80. package/docs/config.md +171 -0
  81. package/docs/model-catalogue.md +502 -0
  82. package/docs/provider-independence-details.md +1 -1
  83. package/docs/task-bus.md +1 -1
  84. package/docs/worker.md +33 -5
  85. package/docs/workers-config.md +105 -12
  86. package/package.json +1 -1
  87. package/src/hooks/ts/capture-all-events.test.ts +71 -0
  88. package/src/hooks/ts/capture-all-events.ts +10 -3
  89. package/src/hooks/ts/user-prompt/whisper-rules.test.ts +56 -1
  90. package/src/hooks/ts/user-prompt/whisper-rules.ts +24 -0
  91. package/statusline-command.sh +81 -6
  92. package/dist/config-DCu8THKi.mjs +0 -228
  93. package/dist/config-DCu8THKi.mjs.map +0 -1
  94. package/dist/config-DeqFqa1N.mjs +0 -3
  95. package/dist/config-DxMZjpvY.mjs.map +0 -1
  96. package/dist/daemon-CYpVy_Dw.mjs +0 -24
  97. package/dist/daemon-DZ4Q0jrs.mjs.map +0 -1
  98. package/dist/factory-47Qf_df7.mjs +0 -3
  99. package/dist/fallback-IeR2FrFn.mjs.map +0 -1
  100. package/dist/indexer-C5Le7XE7.mjs +0 -1
  101. package/dist/main-resolver-CnfnWa8C.mjs +0 -7
  102. package/dist/program-D9-wfmkZ.mjs.map +0 -1
  103. package/dist/query-feedback-C7rSfiXU.mjs +0 -3
  104. package/dist/run-Cls0YGgV.mjs.map +0 -1
  105. package/dist/server-Ch99xt5K.mjs.map +0 -1
  106. package/dist/work-queue-worker-DLM2wlqM.mjs +0 -14
@@ -8,21 +8,22 @@ import { i as indexProject, n as indexAll, t as embedChunks } from "./sync-Btcb6
8
8
  import { t as STOP_WORDS } from "./stop-words-BaMEGVeY.mjs";
9
9
  import { a as searchMemory, i as populateSlugs } from "./search-B69cQwDB.mjs";
10
10
  import { n as formatDetection, r as formatDetectionJson, t as detectProject } from "./detect-Bf2z-oKB.mjs";
11
- import { B as claudeProjectsDir, C as findLatestNote, H as scanTranscriptFolders, I as resolveHome, L as saveScanConfig, M as cmdScan, N as loadScanConfig, P as migrateScanConfig, R as upsertProject, S as applyContinue, T as readBodyFile, U as transcriptFiles, V as findMovedProjects, d as migrateWorkQueue, g as migrateSummaryCooldowns, t as readContextHandoverCache, v as extractAndStoreTriples, w as findNotesDir$1, x as appendCheckpointToNote, z as upsertSession } from "./context-handover-cache-B9NKSjwR.mjs";
12
- import { a as expandHome, c as paiConfigFilePath, i as ensureConfigDir, l as OWNER_LABEL_PREFIX, n as CONFIG_FILE$2, o as loadConfig, s as migrateConfigFile, t as CONFIG_DIR, u as UNROUTED } from "./config-DCu8THKi.mjs";
11
+ import { B as claudeProjectsDir, C as findLatestNote, H as scanTranscriptFolders, I as resolveHome, L as saveScanConfig, M as cmdScan, N as loadScanConfig, P as migrateScanConfig, R as upsertProject, S as applyContinue, T as readBodyFile, U as transcriptFiles, V as findMovedProjects, d as migrateWorkQueue, g as migrateSummaryCooldowns, t as readContextHandoverCache, v as extractAndStoreTriples, w as findNotesDir$1, x as appendCheckpointToNote, z as upsertSession } from "./context-handover-cache-yOuK9RIk.mjs";
12
+ import { b as OWNER_LABEL_PREFIX, c as migrateConfigFile, d as readMainConfigRaw, f as writeMainConfigRaw, h as resolvedMainConfigPath, l as paiConfigFilePath, m as migrateMainConfigToYaml, n as CONFIG_FILE$2, o as expandHome, p as MainConfigError, s as loadConfig, t as CONFIG_DIR, u as paiConfigYamlFilePath, v as readJsonStrict, x as UNROUTED, y as writeJsonAtomic } from "./config-Bn9jpblO.mjs";
13
13
  import { a as schedulerLogPath, i as paiSocketPath, n as daemonLogPath, r as daemonPidPath } from "./runtime-paths-B0P1TvUr.mjs";
14
- import { A as DEFAULT_CONTEXT_WINDOW, C as inlineWorkersYamlKeys, D as relocateWorkersYaml, E as parseWorkersYamlDocument, M as stripModelVariant, N as readJsonStrict, O as workersYamlLegacyNotice, P as writeJsonAtomic, S as initWorkersYaml, T as needsWorkersYamlRelocation, d as getProviderOrNative, f as isModelCapability, h as nativeAnthropicProvider, i as PROVIDER_TAGS, j as contextWindowFromModelId, k as workersYamlPath, m as maskKey, o as WorkersConfigError, r as MODEL_CAPABILITIES, t as ANTHROPIC_NATIVE, u as expandHome$1, v as readWorkersSection, w as migrateWorkersToYaml } from "./config-DxMZjpvY.mjs";
15
- import { A as isLive, B as parseRunnerArgs, F as ownsPid, H as describeMcp, I as saveStatus, L as setWorkerLabel, M as loadStatuses, R as waitForTerminalStatus, S as appendLedger, U as expandMcpNames, a as projectLaunchConfig, c as discardWorker, f as sayToWorker, g as openPaneForWorker, h as openFollowPane, i as usageContextTokens, j as loadStatus, k as isChatPane, l as mergeWorker, m as checkPaneForWorker, n as runWorker, o as parseWorkerReport, r as testProvider, u as handoffFromInside, w as UNLABELED, z as longInlinePromptHint } from "./run-Cls0YGgV.mjs";
14
+ import { A as migrateWorkersToYaml, C as readWorkersSection, F as workersYamlPath, I as DEFAULT_CONTEXT_WINDOW, L as contextWindowFromModelId, M as parseWorkersYamlDocument, N as relocateWorkersYaml, O as initWorkersYaml, P as workersYamlLegacyNotice, R as stripModelVariant, _ as isModelCapability, b as nativeAnthropicProvider, c as MODEL_CAPABILITIES, d as WorkersConfigError, g as getProviderOrNative, h as expandHome$1, j as needsWorkersYamlRelocation, k as inlineWorkersYamlKeys, l as PROVIDER_TAGS, t as ANTHROPIC_NATIVE, y as maskKey } from "./config-DYXARgk8.mjs";
15
+ import { B as setWorkerLabel, C as mergeWorker, D as UNLABELED, F as loadStatuses, G as describeMcp, H as longInlinePromptHint, K as expandMcpNames, M as isChatPane, N as isLive, P as loadStatus, R as ownsPid, S as discardWorker, T as appendLedger, U as parseRunnerArgs, V as waitForTerminalStatus, a as projectLaunchConfig, c as handoffFromInside, f as checkPaneForWorker, i as usageContextTokens, m as openPaneForWorker, n as runWorker, o as parseWorkerReport, p as openFollowPane, r as testProvider, u as sayToWorker, z as saveStatus } from "./run-TChOnMO_.mjs";
16
+ import { a as totalUsageTokens, i as parseSessionUsage, r as sessionKeepaliveLedgerSummary, t as loadSessionKeepaliveState } from "./session-keepalive-BuG3wvik.mjs";
16
17
  import { t as PaiClient } from "./ipc-client-C6NryMMb.mjs";
17
- import { r as humanDuration, t as createStorageBackend } from "./factory-DNYAqjH0.mjs";
18
- import { n as migrateIdentityFile } from "./wakeup-UrhiW-KU.mjs";
19
- import { a as renderDedupedSessions, c as probeResume, d as fmtAge, f as migrateSessionScanCache, h as scanSessions, i as normalizeName$2, l as restoreTopLevel, m as scanCacheFilePath, o as hasConversation, p as resolveSessionByNameOrId, r as buildDeduped, s as launchInDir, t as cmdMain, u as printExitDir } from "./main-resolver-DvYu9jbl.mjs";
20
- import { n as queriesDirPath, t as migrateQueriesDir } from "./query-feedback-CYyOGi3y.mjs";
21
- import { s as kgQuery } from "./kg-entity-BSjM5xZh.mjs";
22
- import { _ as workersLogDir, f as ensureNoMcpConfig, i as buildRunEnv, m as ledgerPath, n as ensureProxyRunning, p as eventsPath, r as stopProxy, t as DEFAULT_PROXY_PORT } from "./server-Ch99xt5K.mjs";
23
- import { i as sendToSession, n as fetchLiveSessions, r as revealItermSession, t as callAiBroker } from "./aibroker-client-Bn9SnGMa.mjs";
24
- import { t as runChain } from "./chain-CNSa0A3G.mjs";
25
- import { C as readSpecPrompt, S as statusLineOutput, _ as updateProvider, a as addProvider, b as psOutput, c as describeProviders, d as resolveProviderName, f as setClass, g as unsetClass, h as setWorkersEnabled, i as fallbackStatusText, l as modelPrefsText, m as setProviderModel, n as fallbackOn, o as classTargetText, p as setProviderEnabled, r as fallbackStatus, s as describeModels, t as fallbackOff, u as removeProvider, v as useProvider, w as resolveSpecPath, x as replayOutput, y as followWorkers } from "./fallback-IeR2FrFn.mjs";
18
+ import { r as humanDuration, t as createStorageBackend } from "./factory-Bt7TWQje.mjs";
19
+ import { A as getConfigValueOp, C as psOutput, D as resolveSpecPath, E as readSpecPrompt, M as setConfigValueOp, N as unsetConfigValueOp, O as MainConfigOpsError, S as followWorkers, T as statusLineOutput, _ as setWorkersEnabled, a as addProvider, b as updateProvider, c as describeModels, d as removeProvider, f as resolveProviderName, g as setProviderModel, h as setProviderEnabled, i as fallbackStatusText, j as listConfigOp, k as formatConfigGetOutput, l as describeProviders, m as setClass, n as fallbackOn, o as classTargetText, p as setCapabilityPreference, r as fallbackStatus, s as describeCapabilities, t as fallbackOff, u as modelPrefsText, v as unsetCapabilityPreference, w as replayOutput, x as useProvider, y as unsetClass } from "./fallback-CIGspCSi.mjs";
20
+ import { n as migrateIdentityFile } from "./wakeup-Yk5lQ0jb.mjs";
21
+ import { a as renderDedupedSessions, c as probeResume, d as fmtAge, f as migrateSessionScanCache, h as scanSessions, i as normalizeName$2, l as restoreTopLevel, m as scanCacheFilePath, o as hasConversation, p as resolveSessionByNameOrId, r as buildDeduped, s as launchInDir, t as cmdMain, u as printExitDir } from "./main-resolver-BmZPCmgj.mjs";
22
+ import { n as queriesDirPath, t as migrateQueriesDir } from "./query-feedback-CTs_jx6i.mjs";
23
+ import { s as kgQuery } from "./kg-entity-BSW7ra8i.mjs";
24
+ import { g as ledgerPath, h as eventsPath, i as buildRunEnv, m as ensureNoMcpConfig, n as ensureProxyRunning, r as stopProxy, t as DEFAULT_PROXY_PORT, y as workersLogDir } from "./server-R9UkCn3v.mjs";
25
+ import { i as sendToSession, n as fetchLiveSessions, r as revealItermSession, t as callAiBroker } from "./aibroker-client-CDIXmq-e.mjs";
26
+ import { t as runChain } from "./chain-BBbVbWVv.mjs";
26
27
  import { appendFileSync, chmodSync, copyFileSync, cpSync, createReadStream, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
27
28
  import { homedir, platform, tmpdir } from "node:os";
28
29
  import { basename, dirname, join, relative, resolve, sep } from "node:path";
@@ -1974,7 +1975,7 @@ function cmdUnregister(db, slug, opts = {}) {
1974
1975
  //#region src/cli/commands/project/projects-index.ts
1975
1976
  function registerProjectsCommands(projectsCmd, getDb) {
1976
1977
  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) => {
1977
- const { cmdMain } = await import("./main-resolver-CnfnWa8C.mjs");
1978
+ const { cmdMain } = await import("./main-resolver-BTastpp4.mjs");
1978
1979
  await cmdMain(getDb(), void 0, void 0, opts);
1979
1980
  });
1980
1981
  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) => {
@@ -2976,7 +2977,7 @@ function registerStatsCommands(memoryCmd, getDb) {
2976
2977
  console.log(` ${bold$1("defaultLimit:")} ${search.defaultLimit}`);
2977
2978
  console.log(` ${bold$1("snippetLength:")} ${search.snippetLength}`);
2978
2979
  console.log();
2979
- console.log(dim$1(` Config file: ${CONFIG_FILE$2}`));
2980
+ console.log(dim$1(` Config file: ${paiConfigFilePath()}`));
2980
2981
  console.log(dim$1(` Edit directly or use: pai memory settings <key> <value>`));
2981
2982
  console.log();
2982
2983
  return;
@@ -3005,11 +3006,11 @@ function registerStatsCommands(memoryCmd, getDb) {
3005
3006
  process.exitCode = 1;
3006
3007
  return;
3007
3008
  }
3008
- let fileConfig = {};
3009
- if (existsSync(CONFIG_FILE$2)) try {
3010
- fileConfig = JSON.parse(readFileSync(CONFIG_FILE$2, "utf-8"));
3011
- } catch {
3012
- console.error(err(`Could not parse ${CONFIG_FILE$2}`));
3009
+ let fileConfig;
3010
+ try {
3011
+ fileConfig = readMainConfigRaw();
3012
+ } catch (e) {
3013
+ console.error(err(`Could not read config: ${e instanceof Error ? e.message : String(e)}`));
3013
3014
  process.exitCode = 1;
3014
3015
  return;
3015
3016
  }
@@ -3037,8 +3038,7 @@ function registerStatsCommands(memoryCmd, getDb) {
3037
3038
  }
3038
3039
  fileConfig.search[key] = parsed;
3039
3040
  try {
3040
- ensureConfigDir();
3041
- writeFileSync(CONFIG_FILE$2, JSON.stringify(fileConfig, null, 2) + "\n", "utf-8");
3041
+ writeMainConfigRaw(fileConfig);
3042
3042
  console.log(ok$1(`Set search.${key} = ${parsed}`));
3043
3043
  console.log(dim$1(` Restart daemon to apply: pai daemon restart`));
3044
3044
  } catch (e) {
@@ -3052,8 +3052,8 @@ function registerStatsCommands(memoryCmd, getDb) {
3052
3052
  //#region src/cli/commands/memory/sources-cmd.ts
3053
3053
  function registerSourcesCommand(memoryCmd) {
3054
3054
  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) => {
3055
- const { createStorageBackend } = await import("./factory-47Qf_df7.mjs");
3056
- const { loadConfig } = await import("./config-DeqFqa1N.mjs");
3055
+ const { createStorageBackend } = await import("./factory-Ba8j9K7q.mjs");
3056
+ const { loadConfig } = await import("./config-RjeVGJ7K.mjs");
3057
3057
  const { cmdMemorySources } = await import("./sources-KBGEA_E6.mjs");
3058
3058
  let backend;
3059
3059
  try {
@@ -3153,23 +3153,17 @@ function checkDeliveryReachability(deliverTo, sendingAccount, sendingAccountAlia
3153
3153
  //#endregion
3154
3154
  //#region src/cli/commands/identity.ts
3155
3155
  function readConfig() {
3156
- if (!existsSync(CONFIG_FILE$2)) return {};
3157
3156
  try {
3158
- return JSON.parse(readFileSync(CONFIG_FILE$2, "utf-8"));
3157
+ return readMainConfigRaw();
3159
3158
  } catch (e) {
3160
- console.error(err("pai identity: ") + `Could not parse ${CONFIG_FILE$2}: ${String(e)}`);
3159
+ console.error(err("pai identity: ") + `Could not read ${paiConfigFilePath()}: ${String(e)}`);
3161
3160
  process.exit(1);
3162
3161
  }
3163
3162
  }
3164
- /**
3165
- * Write via a temp file and rename, so an interrupted write cannot leave the
3166
- * config truncated. The daemon reads this file; a half-written one takes the
3167
- * whole system down rather than just this command.
3168
- */
3163
+ /** Writes through writeMainConfigRaw — comment-preserving into config.yaml
3164
+ * when it exists, else atomic into config.json. */
3169
3165
  function writeConfig(config) {
3170
- const tmp = `${CONFIG_FILE$2}.identity.tmp`;
3171
- writeFileSync(tmp, JSON.stringify(config, null, 2) + "\n", "utf-8");
3172
- renameSync(tmp, CONFIG_FILE$2);
3166
+ writeMainConfigRaw(config);
3173
3167
  }
3174
3168
  function identityOf(config) {
3175
3169
  return config.identity ?? { selfEmails: [] };
@@ -3263,6 +3257,33 @@ function registerIdentityCommands(identityCmd) {
3263
3257
  });
3264
3258
  }
3265
3259
 
3260
+ //#endregion
3261
+ //#region src/config/voices-config.ts
3262
+ /**
3263
+ * voices-config.ts — voices.json / voices.yaml, the same load-either /
3264
+ * migrate pattern as the main config (see main-config.ts) and workers.yaml.
3265
+ *
3266
+ * Nothing in src/ currently reads voices.json (see the note on
3267
+ * migrateVoicesJson in pai-files.ts) — this exists so the one place that
3268
+ * would ever load it has the YAML-preferring behaviour already in place,
3269
+ * and so `pai config yaml` has one JSON→YAML step to run for it, same as
3270
+ * every other PAI config file.
3271
+ */
3272
+ function voicesJsonPath() {
3273
+ return paiHomePath("voices.json");
3274
+ }
3275
+ function voicesYamlPath() {
3276
+ return paiHomePath("voices.yaml");
3277
+ }
3278
+ /** `pai config yaml`'s voices.json → voices.yaml step. */
3279
+ function migrateVoicesToYaml(opts = {}) {
3280
+ return migrateMainConfigToYaml(voicesJsonPath(), {
3281
+ ...opts,
3282
+ yamlPath: voicesYamlPath(),
3283
+ sectionComments: {}
3284
+ });
3285
+ }
3286
+
3266
3287
  //#endregion
3267
3288
  //#region src/tasks/dispatch.ts
3268
3289
  /**
@@ -3732,7 +3753,7 @@ async function escalate(task, headline, detail, state, now, opts) {
3732
3753
  let delivered = false;
3733
3754
  try {
3734
3755
  const { routeNotification } = await import("./router-CremHKTI.mjs");
3735
- const { loadConfig } = await import("./config-DeqFqa1N.mjs");
3756
+ const { loadConfig } = await import("./config-RjeVGJ7K.mjs");
3736
3757
  delivered = (await routeNotification({
3737
3758
  event: "error",
3738
3759
  title: `PAI: ${headline}`,
@@ -5153,42 +5174,38 @@ function checkHealth(vaultPath, db) {
5153
5174
  //#endregion
5154
5175
  //#region src/cli/commands/obsidian.ts
5155
5176
  /**
5156
- * Read obsidianVaultPath from CONFIG_FILE.
5157
- * Falls back to defaultVaultPath() if not set.
5177
+ * Read obsidianVaultPath from the main config (config.yaml if it exists,
5178
+ * else config.json). Falls back to defaultVaultPath() if not set.
5158
5179
  */
5159
5180
  function getVaultPath(override) {
5160
5181
  if (override) return expandHome(override);
5161
5182
  try {
5162
- const raw = readFileSync(CONFIG_FILE$2, "utf-8");
5163
- const cfg = JSON.parse(raw);
5183
+ const cfg = readMainConfigRaw();
5164
5184
  if (typeof cfg.obsidianVaultPath === "string" && cfg.obsidianVaultPath) return expandHome(cfg.obsidianVaultPath);
5165
5185
  } catch {}
5166
5186
  return defaultVaultPath();
5167
5187
  }
5168
5188
  /**
5169
- * Read obsidianVaultPath from CONFIG_FILE without falling back to
5189
+ * Read obsidianVaultPath from the main config without falling back to
5170
5190
  * defaultVaultPath() — undefined means "not set", distinct from getVaultPath's
5171
5191
  * "give me something usable". Used by `pai config migrate` to decide whether
5172
5192
  * the stored path still points at the pre-2026-09-19 default.
5173
5193
  */
5174
5194
  function getConfigObsidianVaultPathRaw() {
5175
5195
  try {
5176
- const raw = readFileSync(CONFIG_FILE$2, "utf-8");
5177
- const cfg = JSON.parse(raw);
5196
+ const cfg = readMainConfigRaw();
5178
5197
  if (typeof cfg.obsidianVaultPath === "string" && cfg.obsidianVaultPath) return expandHome(cfg.obsidianVaultPath);
5179
5198
  } catch {}
5180
5199
  }
5181
- /**
5182
- * Persist obsidianVaultPath into config.json so future syncs use the same path.
5183
- */
5200
+ /** Persist obsidianVaultPath into the main config so future syncs use the
5201
+ * same path (config.yaml if it exists, else config.json). */
5184
5202
  function saveVaultPath(vaultPath) {
5185
5203
  let cfg = {};
5186
5204
  try {
5187
- cfg = JSON.parse(readFileSync(CONFIG_FILE$2, "utf-8"));
5205
+ cfg = readMainConfigRaw();
5188
5206
  } catch {}
5189
5207
  cfg.obsidianVaultPath = vaultPath;
5190
- mkdirSync(dirname(CONFIG_FILE$2), { recursive: true });
5191
- writeFileSync(CONFIG_FILE$2, JSON.stringify(cfg, null, 2) + "\n", "utf-8");
5208
+ writeMainConfigRaw(cfg);
5192
5209
  }
5193
5210
  function cmdSync(db, opts) {
5194
5211
  const vaultPath = getVaultPath(opts.vault);
@@ -5956,15 +5973,17 @@ function migrateObsidianVaultPathConfig(dryRun) {
5956
5973
  function registerConfigCommands(configCmd) {
5957
5974
  configCmd.command("path").description("Print the PAI_HOME namespace dir and each resolved per-user file").action(() => {
5958
5975
  console.log(bold$1("PAI_HOME: ") + paiHomeDir());
5959
- console.log(` config.json: ${paiConfigFilePath()}`);
5976
+ console.log(` config.json / config.yaml: ${paiConfigFilePath()} / ${paiConfigYamlFilePath()}`);
5960
5977
  console.log(` workers.yaml: ${workersYamlPath()}`);
5961
5978
  console.log(` whisper-rules.md: ${whisperRulesPath()}`);
5962
5979
  console.log(` advisor-mode.json: ${advisorModePath()}`);
5963
5980
  console.log(` session-state/: ${sessionStateDir()}`);
5964
5981
  console.log(` session-scan-cache.json: ${scanCacheFilePath()}`);
5965
5982
  console.log(` queries/: ${queriesDirPath()}`);
5966
- console.log(` summary-cooldowns.json, work-queue.json, kg-backfill-state.json,`);
5967
- console.log(` voices.json, federation.db (orphan): all under PAI_HOME`);
5983
+ console.log(` summary-cooldowns.json, work-queue.json, kg-backfill-state.json:`);
5984
+ console.log(` all under PAI_HOME`);
5985
+ console.log(` voices.json / voices.yaml: ${voicesJsonPath()} / ${voicesYamlPath()}`);
5986
+ console.log(` federation.db (orphan): under PAI_HOME`);
5968
5987
  console.log(` logs/workers/: ${paiHomePath("logs", "workers")} (pass --logs to move)`);
5969
5988
  console.log(` History/: ${paiHomePath("History")} (pass --history to move)`);
5970
5989
  console.log(` agent-sessions.json, session-routing.json, History/security/security-events.jsonl:`);
@@ -5979,6 +5998,65 @@ function registerConfigCommands(configCmd) {
5979
5998
  console.log(` agents/: ${paiHomePath("agents")} (~/.claude/Agents symlinks here after migrate)`);
5980
5999
  console.log(` commands/: ${paiHomePath("commands")} (~/.claude/Commands symlinks here after migrate)`);
5981
6000
  });
6001
+ configCmd.command("yaml").description("Convert config.json → config.yaml (and voices.json → voices.yaml), with a short\n # comment above every top-level section and any \"_comment\"/\"_...Note\" JSON-\n workaround key re-attached as a real comment. Verifies the generated YAML\n re-parses to the exact same data before writing anything; the JSON is then\n renamed to <name>.migrated-<YYYY-MM-DD> (never deleted). Every writer keeps\n working unmodified afterwards — see docs/config.md.").option("--dry-run", "Print the plan without writing anything").option("--force", "Regenerate config.yaml (or voices.yaml) even if it already exists").action((opts) => {
6002
+ let hadError = false;
6003
+ const run = (label, fn) => {
6004
+ try {
6005
+ const r = fn();
6006
+ if (r.dryRun) console.log(dim$1(` ${label}: would write ${r.yamlPath}`));
6007
+ else console.log(ok$1(` ${label}: `) + `${r.backupPath} → ${r.yamlPath}`);
6008
+ } catch (e) {
6009
+ hadError = true;
6010
+ console.error(err(` ${label}: `) + (e instanceof MainConfigError ? e.message : String(e instanceof Error ? e.message : e)));
6011
+ }
6012
+ };
6013
+ run("config.yaml", () => migrateMainConfigToYaml(paiConfigFilePath(), {
6014
+ dryRun: opts.dryRun,
6015
+ force: opts.force
6016
+ }));
6017
+ run("voices.yaml", () => migrateVoicesToYaml({
6018
+ dryRun: opts.dryRun,
6019
+ force: opts.force
6020
+ }));
6021
+ if (hadError) process.exitCode = 1;
6022
+ });
6023
+ configCmd.command("list").description("Print the main PAI config as YAML (secrets masked). Defaults to what the file explicitly sets.").option("--all", "Include every default value, not just what the file sets").option("--json", "Print JSON instead of YAML").action((opts) => {
6024
+ const r = listConfigOp({ all: opts.all });
6025
+ console.log(opts.json ? JSON.stringify(r.data, null, 2) : r.yaml.trimEnd());
6026
+ });
6027
+ configCmd.command("get <path>").description("Print one config value (dotted path, e.g. search.recencyBoostDays), masked if it looks like a secret. An object/array subtree prints as YAML; pass --json for JSON.").option("--json", "Print an object/array value as JSON instead of YAML").action((path, opts) => {
6028
+ try {
6029
+ const r = getConfigValueOp(path);
6030
+ if (!r.found) {
6031
+ console.error(err(`Not set: ${path}`));
6032
+ process.exitCode = 1;
6033
+ return;
6034
+ }
6035
+ console.log(formatConfigGetOutput(r.value, { json: opts.json }));
6036
+ } catch (e) {
6037
+ console.error(err(e instanceof MainConfigOpsError ? e.message : String(e instanceof Error ? e.message : e)));
6038
+ process.exitCode = 1;
6039
+ }
6040
+ });
6041
+ configCmd.command("set <path> <value>").description("Set one config value (dotted path). Value parsing: true/false, null, numbers,\n [...] / {...} as JSON, else a string. Creates config.yaml (from config.json,\n if any) on first use, then writes comment-preserving.").option("--force", "Set an unknown top-level key, or a value of a different type than the default").action((path, value, opts) => {
6042
+ try {
6043
+ const r = setConfigValueOp(path, value, { force: opts.force });
6044
+ if (r.yamlCreated) console.log(dim$1(` created ${r.yamlPath}`));
6045
+ console.log(ok$1(`Set ${path} = `) + (typeof r.value === "object" ? JSON.stringify(r.value) : String(r.value)));
6046
+ } catch (e) {
6047
+ console.error(err(e instanceof MainConfigOpsError || e instanceof MainConfigError ? e.message : String(e instanceof Error ? e.message : e)));
6048
+ process.exitCode = 1;
6049
+ }
6050
+ });
6051
+ configCmd.command("unset <path>").description("Remove one config value (dotted path), reverting it to the built-in default").action((path) => {
6052
+ try {
6053
+ const r = unsetConfigValueOp(path);
6054
+ console.log(r.existed ? ok$1(`Unset ${path}`) : dim$1(` ${path} was not set`));
6055
+ } catch (e) {
6056
+ console.error(err(e instanceof MainConfigOpsError ? e.message : String(e instanceof Error ? e.message : e)));
6057
+ process.exitCode = 1;
6058
+ }
6059
+ });
5982
6060
  configCmd.command("migrate").description("Move config.json, workers.yaml, whisper-rules.md, advisor-mode.json, session-state/,\nsession-scan-cache.json, queries/, summary-cooldowns.json, work-queue.json,\nkg-backfill-state.json, voices.json, the orphaned federation.db,\n~/.pai/registry.db, ~/.pai/federation.db (live), ~/.pai/backups/,\n~/.pai/obsidian-vault/, ~/.pai/scheduler-state.json, and ~/.claude/Agents/,\n~/.claude/Commands/ into PAI_HOME.\nregistry.db and federation.db each get an extra WAL checkpoint and PRAGMA\nintegrity_check (sqlite3 CLI, if present) on top of the byte-identical copy.\nbackups/ is copied and sha256-verified file-by-file into\nbackups/legacy-pai-backups/ rather than merged in place. Agents/ and Commands/ each\nget a symlink left at the old ~/.claude path once their content has moved, so\nClaude Code still finds them at its fixed harness paths.\nEach file is copied, verified byte-identical, then the old file is renamed to\n<name>.migrated-<YYYYMMDD> (never deleted); directories move their contents, then\nthe emptied old directory is renamed the same way. Idempotent — entries already at\nthe new location, or with nothing old to migrate, are skipped.").option("--dry-run", "Print the plan without writing anything").option("--logs", "Also move ~/.claude/logs/workers into PAI_HOME: an atomic rename plus a\n symlink left at the old path, so any worker still writing there\n keeps landing in the new directory (falls back to copy+verify\n across volumes, refusing while a worker is RUNNING — see\n `pai worker ps` — only in that fallback case)").option("--history", "Also move ~/.claude/History/, agent-sessions.json, session-routing.json\n and history/security/security-events.jsonl into PAI_HOME. These are\n written by hooks on EVERY session's every turn, not just spawned\n workers — refuses while any worker other than the interactive\n session is RUNNING (see `pai worker ps`), but that guard cannot see\n OTHER interactive `claude` sessions on this machine. Confirm none\n are active (`ps aux | grep claude`, `aibroker sessions list`) before\n passing this flag.").action((opts) => {
5983
6061
  const dryRun = !!opts.dryRun;
5984
6062
  let hadError = false;
@@ -6578,6 +6656,35 @@ async function cmdMigrate(connectionString) {
6578
6656
  process.exitCode = result.status ?? 1;
6579
6657
  }
6580
6658
  }
6659
+ function cmdKeepalive() {
6660
+ const config = loadConfig().sessions.cacheKeepalive;
6661
+ console.log();
6662
+ console.log(bold$1(" Session cache keepalive"));
6663
+ console.log();
6664
+ console.log(config.enabled ? ok$1(` enabled`) : warn$1(` disabled`));
6665
+ console.log(dim$1(` idleMinutes: ${config.idleMinutes} maxBeats: ${config.maxBeats} activeHours: ${config.activeHours} minContextTokens: ${config.minContextTokens} prompt: "${config.prompt}"`));
6666
+ if (!config.enabled) {
6667
+ console.log();
6668
+ console.log(dim$1(` Enable it in ${resolvedMainConfigPath(paiConfigFilePath())}: sessions.cacheKeepalive.enabled: true`));
6669
+ console.log(dim$1(` pai config set sessions.cacheKeepalive.enabled true`));
6670
+ }
6671
+ const state = loadSessionKeepaliveState();
6672
+ const ids = Object.keys(state);
6673
+ console.log();
6674
+ if (ids.length === 0) console.log(dim$1(" No per-session beat counters recorded yet."));
6675
+ else {
6676
+ console.log(bold$1(" Per-session beat counters"));
6677
+ for (const id of ids) {
6678
+ const e = state[id];
6679
+ console.log(dim$1(` ${id}: beats=${e.beats} lastBeatAt=${e.lastBeatAt ?? "never"}`));
6680
+ }
6681
+ }
6682
+ const summary = sessionKeepaliveLedgerSummary();
6683
+ console.log();
6684
+ console.log(bold$1(` Ledger: ${summary.sent} sent, ${summary.skipped} skipped`));
6685
+ for (const line of summary.lastLines) console.log(dim$1(` ${line}`));
6686
+ console.log();
6687
+ }
6581
6688
  function cmdLogs(opts) {
6582
6689
  const lines = opts.lines ?? "50";
6583
6690
  if (!existsSync(DAEMON_LOG)) {
@@ -6598,8 +6705,8 @@ function cmdLogs(opts) {
6598
6705
  }
6599
6706
  function registerDaemonCommands(daemonCmd) {
6600
6707
  daemonCmd.command("serve").description("Start the PAI daemon in the foreground").action(async () => {
6601
- const { serve } = await import("./daemon-CYpVy_Dw.mjs");
6602
- const { loadConfig: lc, ensureConfigDir } = await import("./config-DeqFqa1N.mjs");
6708
+ const { serve } = await import("./daemon-Vf7pHnjR.mjs");
6709
+ const { loadConfig: lc, ensureConfigDir } = await import("./config-RjeVGJ7K.mjs");
6603
6710
  ensureConfigDir();
6604
6711
  await serve(lc());
6605
6712
  });
@@ -6618,6 +6725,9 @@ function registerDaemonCommands(daemonCmd) {
6618
6725
  daemonCmd.command("uninstall").description("Remove the launchd service and revert to direct MCP").action(() => {
6619
6726
  cmdUninstall();
6620
6727
  });
6728
+ daemonCmd.command("keepalive").description("Show interactive-session cache keepalive config, per-session beat counters, and recent ledger lines").action(() => {
6729
+ cmdKeepalive();
6730
+ });
6621
6731
  daemonCmd.command("logs").description(`Tail the daemon log (${DAEMON_LOG})`).option("-n, --lines <n>", "Number of lines to show", "50").option("-f, --follow", "Follow log output (like tail -f)").action((opts) => {
6622
6732
  cmdLogs(opts);
6623
6733
  });
@@ -6959,10 +7069,10 @@ async function promptYesNo(rl, question, defaultYes = true) {
6959
7069
  * current command happened to be setting.
6960
7070
  */
6961
7071
  function readConfigRaw() {
6962
- return readJsonStrict(CONFIG_FILE$2, CONFIG_FILE$2);
7072
+ return readMainConfigRaw();
6963
7073
  }
6964
7074
  function writeConfigRaw(data) {
6965
- writeJsonAtomic(CONFIG_FILE$2, data, { label: CONFIG_FILE$2 });
7075
+ writeMainConfigRaw(data);
6966
7076
  }
6967
7077
  function mergeConfig(updates) {
6968
7078
  const current = readConfigRaw();
@@ -9202,7 +9312,7 @@ async function cmdExplore(note, opts) {
9202
9312
  const depth = parseInt(opts.depth ?? "3", 10);
9203
9313
  const direction = opts.direction ?? "both";
9204
9314
  const mode = opts.mode ?? "all";
9205
- const { zettelExplore } = await import("./zettelkasten-BBjA7GcP.mjs");
9315
+ const { zettelExplore } = await import("./zettelkasten-DezbXghl.mjs");
9206
9316
  const result = zettelExplore(getFedDb(), {
9207
9317
  startNote: note,
9208
9318
  depth,
@@ -9267,7 +9377,7 @@ async function cmdHealth(opts) {
9267
9377
  const projectPath = opts.project;
9268
9378
  const recentDays = parseInt(opts.days ?? "30", 10);
9269
9379
  const includeTypes = opts.include ? opts.include.split(",").map((s) => s.trim()) : void 0;
9270
- const { zettelHealth } = await import("./zettelkasten-BBjA7GcP.mjs");
9380
+ const { zettelHealth } = await import("./zettelkasten-DezbXghl.mjs");
9271
9381
  const result = zettelHealth(getFedDb(), {
9272
9382
  scope,
9273
9383
  projectPath,
@@ -9338,7 +9448,7 @@ async function cmdSurprise(note, opts) {
9338
9448
  const limit = parseInt(opts.limit ?? "10", 10);
9339
9449
  const minSimilarity = parseFloat(opts.minSimilarity ?? "0.3");
9340
9450
  const minGraphDistance = parseInt(opts.minDistance ?? "3", 10);
9341
- const { zettelSurprise } = await import("./zettelkasten-BBjA7GcP.mjs");
9451
+ const { zettelSurprise } = await import("./zettelkasten-DezbXghl.mjs");
9342
9452
  const db = getFedDb();
9343
9453
  console.log();
9344
9454
  console.log(header(" PAI Zettel Surprise"));
@@ -9393,7 +9503,7 @@ async function cmdSuggest(note, opts) {
9393
9503
  const vaultProjectId = parseInt(opts.vaultProjectId, 10);
9394
9504
  const limit = parseInt(opts.limit ?? "5", 10);
9395
9505
  const excludeLinked = opts.excludeLinked !== false;
9396
- const { zettelSuggest } = await import("./zettelkasten-BBjA7GcP.mjs");
9506
+ const { zettelSuggest } = await import("./zettelkasten-DezbXghl.mjs");
9397
9507
  const db = getFedDb();
9398
9508
  console.log();
9399
9509
  console.log(header(" PAI Zettel Suggest"));
@@ -9446,7 +9556,7 @@ async function cmdConverse(question, opts) {
9446
9556
  const vaultProjectId = parseInt(opts.vaultProjectId, 10);
9447
9557
  const depth = parseInt(opts.depth ?? "2", 10);
9448
9558
  const limit = parseInt(opts.limit ?? "15", 10);
9449
- const { zettelConverse } = await import("./zettelkasten-BBjA7GcP.mjs");
9559
+ const { zettelConverse } = await import("./zettelkasten-DezbXghl.mjs");
9450
9560
  const db = getFedDb();
9451
9561
  console.log();
9452
9562
  console.log(header(" PAI Zettel Converse"));
@@ -9509,7 +9619,7 @@ async function cmdThemes(opts) {
9509
9619
  const minClusterSize = parseInt(opts.minSize ?? "3", 10);
9510
9620
  const maxThemes = parseInt(opts.maxThemes ?? "10", 10);
9511
9621
  const similarityThreshold = parseFloat(opts.threshold ?? "0.65");
9512
- const { zettelThemes } = await import("./zettelkasten-BBjA7GcP.mjs");
9622
+ const { zettelThemes } = await import("./zettelkasten-DezbXghl.mjs");
9513
9623
  const db = getFedDb();
9514
9624
  console.log();
9515
9625
  console.log(header(" PAI Zettel Themes"));
@@ -10028,200 +10138,6 @@ function auditHooks(cwd) {
10028
10138
  };
10029
10139
  }
10030
10140
 
10031
- //#endregion
10032
- //#region src/audit/session-usage.ts
10033
- /**
10034
- * session-usage.ts — parse the usage numbers out of a Claude Code session
10035
- * (or subagent, or pai-worker event-mirror) JSONL transcript.
10036
- *
10037
- * All three log shapes share the same assistant-message envelope
10038
- * ({ type: "assistant", message: { id, model, usage } }), so one parser
10039
- * covers `pai audit tokens session` and the per-log readings feeding
10040
- * `pai audit tokens spawn`.
10041
- *
10042
- * Streaming writes one JSONL line per content block of the same logical
10043
- * turn, repeating message.id and usage each time — summing every line would
10044
- * multiply usage by the block count. Keeping only the first line seen per
10045
- * message.id is what the reference script (and this parser) count instead.
10046
- */
10047
- const USAGE_KEYS = [
10048
- "cache_read_input_tokens",
10049
- "cache_creation_input_tokens",
10050
- "input_tokens",
10051
- "output_tokens"
10052
- ];
10053
- function emptyTotals() {
10054
- return {
10055
- cache_read_input_tokens: 0,
10056
- cache_creation_input_tokens: 0,
10057
- input_tokens: 0,
10058
- output_tokens: 0
10059
- };
10060
- }
10061
- /**
10062
- * `type:"system"` `subtype:"compact_boundary"` lines mark a compaction.
10063
- * turnIndex is the count of assistant turns already folded when it fired,
10064
- * so it lines up with the turn numbering the text/JSON report prints.
10065
- */
10066
- function isCompactBoundary(line) {
10067
- return line.type === "system" && line.subtype === "compact_boundary";
10068
- }
10069
- function parseCompactionEvent(line, turnIndex) {
10070
- const meta = line.compactMetadata;
10071
- if (!meta || typeof meta.preTokens !== "number") return null;
10072
- return {
10073
- trigger: meta.trigger ?? "unknown",
10074
- preTokens: meta.preTokens,
10075
- turnIndex
10076
- };
10077
- }
10078
- /**
10079
- * `type:"system"` `subtype:"model_refusal_fallback"` lines mark an
10080
- * automatic model switch fired by an API safety refusal (e.g. "cyber"
10081
- * category), not a user or config choice — worth flagging separately since
10082
- * it can rebuild the whole prompt cache mid-session.
10083
- */
10084
- function isModelFallback(line) {
10085
- return line.type === "system" && line.subtype === "model_refusal_fallback";
10086
- }
10087
- function parseFallbackEvent(line, turnIndex) {
10088
- return {
10089
- turnIndex,
10090
- from: line.originalModel ?? "unknown",
10091
- to: line.fallbackModel ?? "unknown",
10092
- category: line.apiRefusalCategory ?? "unknown",
10093
- scope: line.scope ?? "unknown"
10094
- };
10095
- }
10096
- /**
10097
- * Fold one already-parsed JSONL line into a report being accumulated.
10098
- * Exported separately so both the streaming file reader below and tests
10099
- * (which build fixtures as arrays of objects, not files) share one path.
10100
- * Returns the turn's context value when a new turn was counted, else null
10101
- * (non-assistant line, no usage, or a duplicate message.id already seen).
10102
- */
10103
- function foldAssistantLine(report, seenIds, line, threshold) {
10104
- if (line.type !== "assistant") return null;
10105
- const message = line.message;
10106
- const usage = message?.usage;
10107
- if (!usage) return null;
10108
- const id = message?.id ?? line.uuid;
10109
- if (id) {
10110
- if (seenIds.has(id)) return null;
10111
- seenIds.add(id);
10112
- }
10113
- report.turns++;
10114
- for (const key of USAGE_KEYS) {
10115
- const v = usage[key];
10116
- if (typeof v === "number") report.totals[key] += v;
10117
- }
10118
- const context = (Number(usage.cache_read_input_tokens) || 0) + (Number(usage.cache_creation_input_tokens) || 0) + (Number(usage.input_tokens) || 0);
10119
- if (report.firstTurnContext === null) {
10120
- report.firstTurnContext = context;
10121
- report.firstTurnAt = line.timestamp ?? null;
10122
- }
10123
- report.lastTurnContext = context;
10124
- report.maxContext = report.maxContext === null ? context : Math.max(report.maxContext, context);
10125
- if (context > threshold) report.turnsAboveThreshold++;
10126
- if ((Number(usage.cache_creation_input_tokens) || 0) > 2e4) report.cacheRebuildTurns++;
10127
- const model = message?.model ?? "unknown";
10128
- report.models[model] = (report.models[model] ?? 0) + 1;
10129
- if (report.lastModel !== null && report.lastModel !== model) report.modelSwitches.push({
10130
- turnIndex: report.turns,
10131
- from: report.lastModel,
10132
- to: model,
10133
- cacheRead: Number(usage.cache_read_input_tokens) || 0,
10134
- cacheCreation: Number(usage.cache_creation_input_tokens) || 0
10135
- });
10136
- report.lastModel = model;
10137
- const split = usage.cache_creation;
10138
- if (split) {
10139
- report.cacheCreationSplit.ephemeral5m += Number(split.ephemeral_5m_input_tokens) || 0;
10140
- report.cacheCreationSplit.ephemeral1h += Number(split.ephemeral_1h_input_tokens) || 0;
10141
- }
10142
- return context;
10143
- }
10144
- /**
10145
- * True for a real human-authored `type:"user"` prompt line: content is a
10146
- * plain string, or a content array with no `tool_result` block. Claude Code
10147
- * encodes tool results as `type:"user"` messages whose content array is
10148
- * entirely (or partly) tool_result blocks — those must not count as prompts.
10149
- */
10150
- function isRealUserPrompt(line) {
10151
- if (line.type !== "user") return false;
10152
- if (line.isMeta) return false;
10153
- if (line.origin?.kind && line.origin.kind !== "human") return false;
10154
- const content = line.message?.content;
10155
- if (typeof content === "string") return true;
10156
- if (Array.isArray(content)) return !content.some((block) => block?.type === "tool_result");
10157
- return false;
10158
- }
10159
- /** Parse a session/subagent/worker-event JSONL file into a usage report. */
10160
- async function parseSessionUsage(path, threshold = 2e5) {
10161
- const report = {
10162
- path,
10163
- sizeBytes: existsSync(path) ? readFileSync(path).byteLength : 0,
10164
- turns: 0,
10165
- totals: emptyTotals(),
10166
- models: {},
10167
- firstTurnContext: null,
10168
- lastTurnContext: null,
10169
- cacheCreationSplit: {
10170
- ephemeral5m: 0,
10171
- ephemeral1h: 0
10172
- },
10173
- avgContext: null,
10174
- maxContext: null,
10175
- turnsAboveThreshold: 0,
10176
- cacheRebuildTurns: 0,
10177
- userPrompts: 0,
10178
- promptExposure: 0,
10179
- compactions: [],
10180
- firstTurnAt: null,
10181
- lastModel: null,
10182
- modelSwitches: [],
10183
- fallbacks: []
10184
- };
10185
- const seenIds = /* @__PURE__ */ new Set();
10186
- let contextSum = 0;
10187
- const rl = createInterface({
10188
- input: createReadStream(path, "utf8"),
10189
- crlfDelay: Infinity
10190
- });
10191
- for await (const raw of rl) {
10192
- if (!raw.trim()) continue;
10193
- let obj;
10194
- try {
10195
- obj = JSON.parse(raw);
10196
- } catch {
10197
- continue;
10198
- }
10199
- if (obj.type === "user") {
10200
- if (isRealUserPrompt(obj)) report.userPrompts++;
10201
- continue;
10202
- }
10203
- if (isCompactBoundary(obj)) {
10204
- const event = parseCompactionEvent(obj, report.turns);
10205
- if (event) report.compactions.push(event);
10206
- continue;
10207
- }
10208
- if (isModelFallback(obj)) {
10209
- report.fallbacks.push(parseFallbackEvent(obj, report.turns));
10210
- continue;
10211
- }
10212
- const context = foldAssistantLine(report, seenIds, obj, threshold);
10213
- if (context !== null) {
10214
- contextSum += context;
10215
- report.promptExposure += report.userPrompts;
10216
- }
10217
- }
10218
- report.avgContext = report.turns > 0 ? Math.round(contextSum / report.turns) : null;
10219
- return report;
10220
- }
10221
- function totalUsageTokens(totals) {
10222
- return totals.cache_read_input_tokens + totals.cache_creation_input_tokens + totals.input_tokens + totals.output_tokens;
10223
- }
10224
-
10225
10141
  //#endregion
10226
10142
  //#region src/audit/first-turn.ts
10227
10143
  /**
@@ -10534,7 +10450,8 @@ function newestSessionLog() {
10534
10450
  async function auditSession(path, threshold) {
10535
10451
  const target = path ?? newestSessionLog();
10536
10452
  if (!target) return null;
10537
- const report = await parseSessionUsage(target, threshold);
10453
+ const keepaliveWord = loadConfig().sessions.cacheKeepalive.prompt;
10454
+ const report = await parseSessionUsage(target, threshold, keepaliveWord);
10538
10455
  const total = totalUsageTokens(report.totals) || 1;
10539
10456
  const percentages = {};
10540
10457
  for (const [key, value] of Object.entries(report.totals)) percentages[key] = 100 * value / total;
@@ -12086,6 +12003,7 @@ function printSession(report) {
12086
12003
  console.log(`safeguard fallbacks: ${report.fallbacks.length}`);
12087
12004
  for (const f of report.fallbacks) console.log(dim$1(` ${f.from} -> ${f.to} before turn ${f.turnIndex}: ${f.category} (${f.scope})`));
12088
12005
  }
12006
+ console.log(dim$1(`idle gaps > 60min: ${report.idleGapsOver60min}, keepalive beats: ${report.keepaliveBeats ?? "n/a"}`));
12089
12007
  }
12090
12008
  function printTurnBreakdown(tb) {
12091
12009
  console.log(header(`turn ${tb.turn} attribution (live lineage, cl100k)`));
@@ -15411,8 +15329,8 @@ function cmdActive(db, opts) {
15411
15329
  async function cmdAutoRoute(opts) {
15412
15330
  const { autoRoute, formatAutoRoute, formatAutoRouteJson } = await import("./auto-route-CcD0OWWB.mjs");
15413
15331
  const { openRegistry } = await import("./db-DbY2O8QO.mjs");
15414
- const { createStorageBackend } = await import("./factory-47Qf_df7.mjs");
15415
- const { loadConfig } = await import("./config-DeqFqa1N.mjs");
15332
+ const { createStorageBackend } = await import("./factory-Ba8j9K7q.mjs");
15333
+ const { loadConfig } = await import("./config-RjeVGJ7K.mjs");
15416
15334
  const config = loadConfig();
15417
15335
  const registryDb = openRegistry();
15418
15336
  const federation = await createStorageBackend(config);
@@ -17442,7 +17360,7 @@ function deriveLabel(prompt, klass) {
17442
17360
 
17443
17361
  //#endregion
17444
17362
  //#region src/cli/commands/worker/providers.ts
17445
- function fail$3(e) {
17363
+ function fail$4(e) {
17446
17364
  const msg = e instanceof Error ? e.message : String(e);
17447
17365
  console.error(err("pai worker: ") + msg);
17448
17366
  process.exitCode = 1;
@@ -17505,12 +17423,12 @@ function registerWorkerProviderCommands(providersCmd) {
17505
17423
  if (legacyNotice) console.log(dim$1(` ${legacyNotice}`));
17506
17424
  console.log();
17507
17425
  });
17508
- providersCmd.command("add <name>").description("Add a provider; the first one also turns workers on and seeds classes.\nExample: pai worker providers add glm --url https://…/anthropic \\\n --key sk-… --model glm-5.3 --fast-model glm-5.3-flash\n--key writes the token inline as `key:` in workers.yaml (quoted, file kept 0600);\n--key-file writes only a path to a 0600 file holding it — use one or the other.\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)").option("--url <url>", "Alias of --base-url").requiredOption("--model <model>", "Default model id for this provider").option("--key <token>", "API token, written inline as `key:` (quoted); use instead of --key-file").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) => {
17426
+ providersCmd.command("add <name>").description("Add a provider; the first one also turns workers on and seeds classes.\nExample: pai worker providers add glm --url https://…/anthropic \\\n --key sk-… --model glm-5.3 --fast-model glm-5.3-flash\n--key writes the token inline as `key:` in workers.yaml (quoted, file kept 0600);\n--key-file writes only a path to a 0600 file holding it — use one or the other.\nOpenAI-protocol: --protocol openai --upstream-url https://…/v1 (runs via the PAI proxy).\nCodex (ChatGPT plan): --engine codex — runs through the Codex CLI.\nImage generation: --engine image — `pai worker run --capability image` then POSTs\nstraight to {url}/images/generations instead of spawning claude.").option("--base-url <url>", "Anthropic-compatible API base URL (required unless --protocol openai)").option("--url <url>", "Alias of --base-url").requiredOption("--model <model>", "Default model id for this provider").option("--key <token>", "API token, written inline as `key:` (quoted); use instead of --key-file").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), codex, or image — codex runs `codex exec --json`, image POSTs {url}/images/generations").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) => {
17509
17427
  try {
17510
17428
  const protocol = opts.protocol;
17511
17429
  const engine = opts.engine;
17512
17430
  if (protocol && protocol !== "anthropic" && protocol !== "openai") throw new WorkersConfigError(`--protocol must be anthropic or openai, got "${protocol}"`);
17513
- if (engine && engine !== "claude" && engine !== "codex") throw new WorkersConfigError(`--engine must be claude or codex, got "${engine}"`);
17431
+ if (engine && engine !== "claude" && engine !== "codex" && engine !== "image") throw new WorkersConfigError(`--engine must be claude, codex, or image, got "${engine}"`);
17514
17432
  const baseUrl = opts.baseUrl ?? opts.url;
17515
17433
  if (protocol !== "openai" && !baseUrl) throw new WorkersConfigError("--base-url (or --url) is required (only --protocol openai goes without it)");
17516
17434
  if (opts.key && opts.keyFile) throw new WorkersConfigError("--key and --key-file are alternatives — pass one, not both");
@@ -17537,7 +17455,7 @@ function registerWorkerProviderCommands(providersCmd) {
17537
17455
  if (workers.active === name) console.log(` active: ${name} — workers ${workers.enabled ? "on" : "off"}`);
17538
17456
  for (const line of describeProviders(workers)) console.log(dim$1(` ${line}`));
17539
17457
  } catch (e) {
17540
- fail$3(e);
17458
+ fail$4(e);
17541
17459
  }
17542
17460
  });
17543
17461
  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) => {
@@ -17552,7 +17470,7 @@ function registerWorkerProviderCommands(providersCmd) {
17552
17470
  const { workers } = readWorkersSection();
17553
17471
  for (const line of describeProviders(workers)) console.log(dim$1(` ${line}`));
17554
17472
  } catch (e) {
17555
- fail$3(e);
17473
+ fail$4(e);
17556
17474
  }
17557
17475
  });
17558
17476
  providersCmd.command("remove <name>").description("Remove a provider and any classes pointing at it").action((name) => {
@@ -17560,7 +17478,7 @@ function registerWorkerProviderCommands(providersCmd) {
17560
17478
  removeProvider(name);
17561
17479
  console.log(ok$1(`provider ${name} removed`));
17562
17480
  } catch (e) {
17563
- fail$3(e);
17481
+ fail$4(e);
17564
17482
  }
17565
17483
  });
17566
17484
  providersCmd.command("use <name>").description("Make this provider the active one for runs without --provider/--class").action((name) => {
@@ -17568,7 +17486,7 @@ function registerWorkerProviderCommands(providersCmd) {
17568
17486
  useProvider(name);
17569
17487
  console.log(ok$1(`active provider: ${name}`));
17570
17488
  } catch (e) {
17571
- fail$3(e);
17489
+ fail$4(e);
17572
17490
  }
17573
17491
  });
17574
17492
  providersCmd.command("enable <name>").description("Enable a provider (also clears its cooldown)").action((name) => {
@@ -17576,7 +17494,7 @@ function registerWorkerProviderCommands(providersCmd) {
17576
17494
  setProviderEnabled(name, true);
17577
17495
  console.log(ok$1(`provider ${name} enabled`));
17578
17496
  } catch (e) {
17579
- fail$3(e);
17497
+ fail$4(e);
17580
17498
  }
17581
17499
  });
17582
17500
  providersCmd.command("disable <name>").description("Disable a provider (auto-routing skips it; --provider still works)").action((name) => {
@@ -17584,7 +17502,7 @@ function registerWorkerProviderCommands(providersCmd) {
17584
17502
  setProviderEnabled(name, false);
17585
17503
  console.log(ok$1(`provider ${name} disabled`));
17586
17504
  } catch (e) {
17587
- fail$3(e);
17505
+ fail$4(e);
17588
17506
  }
17589
17507
  });
17590
17508
  providersCmd.command("test [name]").description("One-word pong probe through a provider (default: the active one)").action(async (name) => {
@@ -17604,7 +17522,7 @@ function registerWorkerProviderCommands(providersCmd) {
17604
17522
  console.log(` reply: ${r.result.slice(0, 200)}`);
17605
17523
  process.exitCode = r.ok ? 0 : 1;
17606
17524
  } catch (e) {
17607
- fail$3(e);
17525
+ fail$4(e);
17608
17526
  }
17609
17527
  });
17610
17528
  }
@@ -17625,7 +17543,7 @@ function registerClassSubcommands(classesCmd) {
17625
17543
  setClass(cls, built);
17626
17544
  console.log(ok$1(`class ${cls} → ${classTargetText(built)}`));
17627
17545
  } catch (e) {
17628
- fail$3(e);
17546
+ fail$4(e);
17629
17547
  }
17630
17548
  });
17631
17549
  classesCmd.command("unset <class>").description("Remove a class (runs then use the active provider)").action((cls) => {
@@ -17633,7 +17551,7 @@ function registerClassSubcommands(classesCmd) {
17633
17551
  unsetClass(cls);
17634
17552
  console.log(ok$1(`class ${cls} removed`));
17635
17553
  } catch (e) {
17636
- fail$3(e);
17554
+ fail$4(e);
17637
17555
  }
17638
17556
  });
17639
17557
  }
@@ -17645,13 +17563,13 @@ function registerWorkerClassCommands(workerCmd) {
17645
17563
 
17646
17564
  //#endregion
17647
17565
  //#region src/cli/commands/worker/model.ts
17648
- function fail$2(e) {
17566
+ function fail$3(e) {
17649
17567
  const msg = e instanceof Error ? e.message : String(e);
17650
17568
  console.error(err("pai worker: ") + msg);
17651
17569
  process.exitCode = 1;
17652
17570
  }
17653
17571
  function registerWorkerModelCommand(workerCmd) {
17654
- workerCmd.command("model [what] [model]").description("Model ids per provider: no args lists them,\n`model <model-id>` sets the active provider's default model (back-compat),\n`model <capability>` shows one capability, `model <capability> <model-id>` sets it.\nCapabilities: default, fast, image. --provider targets another provider.").option("--provider <name>", "Provider to read or change (default: the active one)").action((what, model, opts) => {
17572
+ workerCmd.command("model [what] [model]").description("Model ids per provider: no args lists them,\n`model <model-id>` sets the active provider's default model (back-compat),\n`model <capability>` shows one capability, `model <capability> <model-id>` sets it.\nCapability names are open (default, fast, image, … — any ^[a-z][a-z0-9-]*$ name);\n`pai worker capability` picks which provider serves one across the whole config.\n--provider targets another provider.").option("--provider <name>", "Provider to read or change (default: the active one)").action((what, model, opts) => {
17655
17573
  try {
17656
17574
  const { workers } = readWorkersSection();
17657
17575
  if (what === void 0) {
@@ -17676,6 +17594,39 @@ function registerWorkerModelCommand(workerCmd) {
17676
17594
  console.log(ok$1(`${name} ${capability} model: ${p.models[capability]}`));
17677
17595
  console.log(dim$1(` ${modelPrefsText(p)}`));
17678
17596
  }
17597
+ } catch (e) {
17598
+ fail$3(e);
17599
+ }
17600
+ });
17601
+ }
17602
+
17603
+ //#endregion
17604
+ //#region src/cli/commands/worker/capability.ts
17605
+ function fail$2(e) {
17606
+ const msg = e instanceof Error ? e.message : String(e);
17607
+ console.error(err("pai worker: ") + msg);
17608
+ process.exitCode = 1;
17609
+ }
17610
+ function registerWorkerCapabilityCommand(workerCmd) {
17611
+ workerCmd.command("capability [name] [providers]").description("Which provider(s) serve a capability (e.g. image), independent of any one\nprovider's own model table: no args lists every preference and what it\nresolves to; `capability <name> <provider>[,<provider>…]` sets the\npreference list (first usable one wins); `capability <name> --unset`\nremoves it. An `engine: image` provider preferred for \"image\" runs\n`pai worker run --capability image` directly against its images API.").option("--unset", "Remove the preference for <name>").option("--json", "Machine-readable output").action((name, providers, opts) => {
17612
+ try {
17613
+ if (name === void 0) {
17614
+ const { workers } = readWorkersSection();
17615
+ const lines = describeCapabilities(workers);
17616
+ if (opts.json) console.log(JSON.stringify(lines));
17617
+ else for (const line of lines) console.log(` ${line}`);
17618
+ return;
17619
+ }
17620
+ if (opts.unset) {
17621
+ unsetCapabilityPreference(name);
17622
+ console.log(ok$1(`removed the capability preference for "${name}"`));
17623
+ return;
17624
+ }
17625
+ if (providers === void 0) throw new Error(`usage: pai worker capability ${name} <provider>[,<provider>…] (or --unset to remove it)`);
17626
+ const workers = setCapabilityPreference(name, providers.split(",").map((p) => p.trim()));
17627
+ const line = describeCapabilities(workers).find((l) => l.startsWith(`${name}:`));
17628
+ console.log(ok$1(`${name}: [${workers.capabilities[name]?.join(", ")}]`));
17629
+ if (line) console.log(` -> ${line.split(" -> ")[1]}`);
17679
17630
  } catch (e) {
17680
17631
  fail$2(e);
17681
17632
  }
@@ -17873,7 +17824,7 @@ function fail(e) {
17873
17824
  process.exitCode = 1;
17874
17825
  }
17875
17826
  function registerWorkerCommands(workerCmd) {
17876
- 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--label \"<goal>\" is optional — it is the row shown in ps / follow /\nthe status line; when absent it is derived from the prompt's first line\n(--chain/--agent derive their own instead).\n--spec <file> (or --spec -) reads the prompt from a file/stdin instead of\nan inline -p '<prompt>', which breaks on shell quoting; mutually\nexclusive with -p.\nGrant MCP tools by naming mcp__server__tool in --allowedTools (the server loads automatically);\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 model for this run. Headless (-p) workers default to the --class model; an interactive launch (no -p) with no --model uses the harness default model from settings.json.").option("--label <text>", "Short task label shown in ps / follow / status line (default: first line of the prompt)").option("--spec <path>", "Read the prompt from this file (or - for stdin) instead of -p; mutually exclusive with -p").option("--cwd <dir>", "Directory the worker runs in (default: this process's cwd)").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").option("--worktree", "Run in a git worktree on branch worker/<id> (default for implement/complex/plan in a git repo)").option("--no-worktree", "Run in place, no worktree").option("--print-cmd", "Print the assembled claude argv as JSON and exit, without spawning (audit tool)").option("--report <format>", "Final-report contract/parser: json or ag2 (default: ag2, or PAI_WORKER_REPORT)").option("--no-report-retry", "Skip the one bounded re-ask when the final AG2 message fails validation").argument("[args...]", "claude arguments, e.g. -p '<task>' --allowedTools 'Read,Edit,Bash'").action(async (args, opts) => {
17827
+ 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--label \"<goal>\" is optional — it is the row shown in ps / follow /\nthe status line; when absent it is derived from the prompt's first line\n(--chain/--agent derive their own instead).\n--spec <file> (or --spec -) reads the prompt from a file/stdin instead of\nan inline -p '<prompt>', which breaks on shell quoting; mutually\nexclusive with -p.\nGrant MCP tools by naming mcp__server__tool in --allowedTools (the server loads automatically);\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("--capability <name>", "Run on whichever configured provider serves this capability (see `pai worker capability`), not the active provider or --class's own one — e.g. --capability image runs an `engine: image` provider directly, POSTing a prompt and writing the PNG instead of spawning claude").option("--for <name>", "Alias of --capability").option("--out <file>", "Where an image-capability run writes its PNG (default: <logDir>/<worker-id>.png)").option("--size <WxH>", "Image size for an image-capability run, e.g. 1024x1024 (default: 1024x1024)").option("--timeout-ms <n>", "Timeout for an image-capability run's HTTP request (default: 120000)", parseIntArg).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 model for this run. Headless (-p) workers default to the --class model; an interactive launch (no -p) with no --model uses the harness default model from settings.json.").option("--label <text>", "Short task label shown in ps / follow / status line (default: first line of the prompt)").option("--spec <path>", "Read the prompt from this file (or - for stdin) instead of -p; mutually exclusive with -p").option("--cwd <dir>", "Directory the worker runs in (default: this process's cwd)").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").option("--worktree", "Run in a git worktree on branch worker/<id> (default for implement/complex/plan in a git repo)").option("--no-worktree", "Run in place, no worktree").option("--print-cmd", "Print the assembled claude argv as JSON and exit, without spawning (audit tool)").option("--report <format>", "Final-report contract/parser: json or ag2 (default: ag2, or PAI_WORKER_REPORT)").option("--no-report-retry", "Skip the one bounded re-ask when the final AG2 message fails validation").argument("[args...]", "claude arguments, e.g. -p '<task>' --allowedTools 'Read,Edit,Bash'").action(async (args, opts) => {
17877
17828
  try {
17878
17829
  const className = opts.class ?? opts.role;
17879
17830
  if (opts.report !== void 0 && opts.report !== "json" && opts.report !== "ag2") throw new Error(`--report: expected "json" or "ag2", got "${opts.report}"`);
@@ -17944,6 +17895,10 @@ function registerWorkerCommands(workerCmd) {
17944
17895
  printCmd: opts.printCmd,
17945
17896
  reportFormatFlag,
17946
17897
  noReportRetry: opts.reportRetry === false,
17898
+ capabilityFlag: opts.capability ?? opts.for,
17899
+ imageOut: opts.out,
17900
+ imageSize: opts.size,
17901
+ imageTimeoutMs: opts.timeoutMs,
17947
17902
  claudeArgs
17948
17903
  });
17949
17904
  process.exitCode = rc;
@@ -18286,6 +18241,7 @@ function registerWorkerCommands(workerCmd) {
18286
18241
  registerWorkerProviderCommands(workerCmd.command("providers").description("Providers: list, add, remove, use, enable, disable, test"));
18287
18242
  registerWorkerClassCommands(workerCmd);
18288
18243
  registerWorkerModelCommand(workerCmd);
18244
+ registerWorkerCapabilityCommand(workerCmd);
18289
18245
  registerWorkerConfigCommands(workerCmd);
18290
18246
  }
18291
18247
  function parseIntArg(v) {
@@ -18948,7 +18904,7 @@ function analyzeProject(db, project) {
18948
18904
  async function countVectorDbPaths(oldPaths) {
18949
18905
  if (oldPaths.length === 0) return 0;
18950
18906
  try {
18951
- const { loadConfig } = await import("./config-DeqFqa1N.mjs");
18907
+ const { loadConfig } = await import("./config-RjeVGJ7K.mjs");
18952
18908
  const { PostgresBackend } = await import("./postgres-CUSXWgTG.mjs");
18953
18909
  const config = loadConfig();
18954
18910
  if (config.storageBackend !== "postgres") return 0;
@@ -18969,7 +18925,7 @@ async function countVectorDbPaths(oldPaths) {
18969
18925
  async function updateVectorDbPaths(moves) {
18970
18926
  if (moves.length === 0) return 0;
18971
18927
  try {
18972
- const { loadConfig } = await import("./config-DeqFqa1N.mjs");
18928
+ const { loadConfig } = await import("./config-RjeVGJ7K.mjs");
18973
18929
  const { PostgresBackend } = await import("./postgres-CUSXWgTG.mjs");
18974
18930
  const config = loadConfig();
18975
18931
  if (config.storageBackend !== "postgres") return 0;
@@ -20510,4 +20466,4 @@ claude() {
20510
20466
 
20511
20467
  //#endregion
20512
20468
  export { drainStdio as n, buildProgram as t };
20513
- //# sourceMappingURL=program-D9-wfmkZ.mjs.map
20469
+ //# sourceMappingURL=program-BX67-4I2.mjs.map