@tekmidian/pai 0.49.0 → 0.51.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 (109) 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-srUeFUfW.mjs} +5806 -5615
  51. package/dist/program-srUeFUfW.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/audit.md +1 -1
  78. package/docs/commands/config.md +84 -0
  79. package/docs/commands/daemon.md +6 -0
  80. package/docs/commands/worker.md +37 -2
  81. package/docs/config.md +171 -0
  82. package/docs/model-catalogue.md +502 -0
  83. package/docs/provider-independence-details.md +1 -1
  84. package/docs/task-bus.md +1 -1
  85. package/docs/worker.md +33 -5
  86. package/docs/workers-config.md +105 -12
  87. package/package.json +1 -1
  88. package/src/hooks/ts/capture-all-events.test.ts +71 -0
  89. package/src/hooks/ts/capture-all-events.ts +10 -3
  90. package/src/hooks/ts/lib/context-fill.test.ts +124 -46
  91. package/src/hooks/ts/lib/context-fill.ts +57 -14
  92. package/src/hooks/ts/user-prompt/whisper-rules.test.ts +56 -1
  93. package/src/hooks/ts/user-prompt/whisper-rules.ts +24 -0
  94. package/statusline-command.sh +103 -16
  95. package/dist/config-DCu8THKi.mjs +0 -228
  96. package/dist/config-DCu8THKi.mjs.map +0 -1
  97. package/dist/config-DeqFqa1N.mjs +0 -3
  98. package/dist/config-DxMZjpvY.mjs.map +0 -1
  99. package/dist/daemon-CYpVy_Dw.mjs +0 -24
  100. package/dist/daemon-DZ4Q0jrs.mjs.map +0 -1
  101. package/dist/factory-47Qf_df7.mjs +0 -3
  102. package/dist/fallback-IeR2FrFn.mjs.map +0 -1
  103. package/dist/indexer-C5Le7XE7.mjs +0 -1
  104. package/dist/main-resolver-CnfnWa8C.mjs +0 -7
  105. package/dist/program-D9-wfmkZ.mjs.map +0 -1
  106. package/dist/query-feedback-C7rSfiXU.mjs +0 -3
  107. package/dist/run-Cls0YGgV.mjs.map +0 -1
  108. package/dist/server-Ch99xt5K.mjs.map +0 -1
  109. package/dist/work-queue-worker-DLM2wlqM.mjs +0 -14
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import "../db-Ddns80yf.mjs";
3
- import { o as loadConfig } from "../config-DCu8THKi.mjs";
4
- import { r as MODEL_CAPABILITIES, v as readWorkersSection } from "../config-DxMZjpvY.mjs";
5
- import { C as ledgerSummary, E as alive, L as setWorkerLabel, M as loadStatuses, d as readInbox, f as sayToWorker, j as loadStatus, n as runWorker, r as testProvider, u as handoffFromInside } from "../run-Cls0YGgV.mjs";
3
+ import { p as MainConfigError, s as loadConfig } from "../config-Bn9jpblO.mjs";
4
+ import { C as readWorkersSection, c as MODEL_CAPABILITIES, n as CAPABILITY_NAME_RE } from "../config-DYXARgk8.mjs";
5
+ import { B as setWorkerLabel, E as ledgerSummary, F as loadStatuses, P as loadStatus, c as handoffFromInside, k as alive, l as readInbox, n as runWorker, r as testProvider, u as sayToWorker } from "../run-TChOnMO_.mjs";
6
6
  import { t as PaiClient } from "../ipc-client-C6NryMMb.mjs";
7
- import { _ as workersLogDir, m as ledgerPath } from "../server-Ch99xt5K.mjs";
8
- import { t as runChain } from "../chain-CNSa0A3G.mjs";
9
- import { C as readSpecPrompt, _ 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 } from "../fallback-IeR2FrFn.mjs";
7
+ import { A as getConfigValueOp, C as psOutput, D as resolveSpecPath, E as readSpecPrompt, M as setConfigValueOp, N as unsetConfigValueOp, O as MainConfigOpsError, _ 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, 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";
8
+ import { g as ledgerPath, y as workersLogDir } from "../server-R9UkCn3v.mjs";
9
+ import { t as runChain } from "../chain-BBbVbWVv.mjs";
10
10
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11
11
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
12
12
 
@@ -3839,7 +3839,7 @@ USE WHEN user says 'load vault context', 'brief me from Obsidian', 'morning brie
3839
3839
 
3840
3840
  Reads: daily note → open TODOs → PAI index (active projects) → HOME.md (focus areas) → recent insights. Synthesizes into morning briefing with Suggested First Action.
3841
3841
 
3842
- All vault skills work with the vault configured in \`~/.claude/pai/config.json\` (\`vaultPath\` key).`
3842
+ All vault skills work with the vault configured in \`~/.claude/pai/config.yaml\` (\`vaultPath\` key, or \`config.json\` until you run \`pai config yaml\`).`
3843
3843
  };
3844
3844
 
3845
3845
  //#endregion
@@ -4709,7 +4709,7 @@ User
4709
4709
  | Memory | \${PAI_DIR}/projects/*/Notes/ | Session notes, todos |
4710
4710
  | Skills | ~/.claude/Skills/ | Workflow instructions |
4711
4711
  | History | \${PAI_HOME}/History/ | Automated capture |
4712
- | Config | ~/.claude/pai/ | Agent preferences (config.json, workers.yaml) |
4712
+ | Config | ~/.claude/pai/ | Agent preferences (config.yaml, workers.yaml, etc.) |
4713
4713
 
4714
4714
  ## Two-Tier MCP Strategy
4715
4715
 
@@ -5441,6 +5441,7 @@ const workerRunShape = {
5441
5441
  specPath: string().min(1).optional().describe("Path to a file (or \"-\" for stdin) whose contents become the prompt. Mutually exclusive with prompt."),
5442
5442
  chain: string().optional().describe("Comma-separated stages, e.g. draft,implement or draft,implement,review."),
5443
5443
  class: string().optional().describe("Task class (draft, implement, review, research, spotcheck, simple, complex, image)."),
5444
+ capability: string().optional().describe("Run on whichever provider serves this capability instead of the active one — e.g. \"image\" runs an engine=image provider directly (POSTs the prompt, writes a PNG under the worker's log dir) instead of spawning claude. Wins over class."),
5444
5445
  label: string().min(1).describe("Short task label shown in worker_ps / the status line and worker pane."),
5445
5446
  cwd: string().optional().describe("Working directory (default: here)."),
5446
5447
  allowed_tools: string().optional().describe("Comma-separated tool allowlist passed to the worker."),
@@ -5471,11 +5472,11 @@ function resolveWorkerRunPrompt(args, cwd) {
5471
5472
  * handler, index.ts registers it. The mutations themselves live in
5472
5473
  * src/workers/providers.ts, shared with the `pai worker model` CLI.
5473
5474
  */
5474
- const text = (s) => ({ content: [{
5475
+ const text$2 = (s) => ({ content: [{
5475
5476
  type: "text",
5476
5477
  text: s
5477
5478
  }] });
5478
- const error = (e) => ({
5479
+ const error$1 = (e) => ({
5479
5480
  content: [{
5480
5481
  type: "text",
5481
5482
  text: e instanceof Error ? e.message : String(e)
@@ -5490,22 +5491,128 @@ const error = (e) => ({
5490
5491
  function workerModel(args, configPath) {
5491
5492
  try {
5492
5493
  const action = args.action ?? "get";
5493
- if (action !== "get" && action !== "set") return error(/* @__PURE__ */ new Error(`unknown action "${String(action)}" (expected: get, set)`));
5494
- if (args.capability !== void 0 && args.slot !== void 0 && args.capability !== args.slot) return error(/* @__PURE__ */ new Error(`capability "${args.capability}" and slot "${args.slot}" disagree — pass one`));
5494
+ if (action !== "get" && action !== "set") return error$1(/* @__PURE__ */ new Error(`unknown action "${String(action)}" (expected: get, set)`));
5495
+ if (args.capability !== void 0 && args.slot !== void 0 && args.capability !== args.slot) return error$1(/* @__PURE__ */ new Error(`capability "${args.capability}" and slot "${args.slot}" disagree — pass one`));
5495
5496
  const capability = args.capability ?? args.slot ?? "default";
5496
5497
  const { workers } = readWorkersSection(configPath);
5497
5498
  if (action === "get") {
5498
5499
  if (args.provider) {
5499
5500
  const name = resolveProviderName(workers, args.provider);
5500
5501
  const p = workers.providers[name];
5501
- return p ? text(`${name} ${modelPrefsText(p)}`) : error(/* @__PURE__ */ new Error(`no provider named "${args.provider}"`));
5502
+ return p ? text$2(`${name} ${modelPrefsText(p)}`) : error$1(/* @__PURE__ */ new Error(`no provider named "${args.provider}"`));
5502
5503
  }
5503
- return text(describeModels(workers).join("\n"));
5504
+ return text$2(describeModels(workers).join("\n"));
5504
5505
  }
5505
- if (!args.model || !args.model.trim()) return error(/* @__PURE__ */ new Error("set needs a non-empty model id"));
5506
+ if (!args.model || !args.model.trim()) return error$1(/* @__PURE__ */ new Error("set needs a non-empty model id"));
5506
5507
  const name = resolveProviderName(workers, args.provider);
5507
5508
  const p = setProviderModel(name, capability, args.model, configPath).providers[name];
5508
- return text(`${name} ${capability} model: ${p?.models[capability] ?? "?"}`);
5509
+ return text$2(`${name} ${capability} model: ${p?.models[capability] ?? "?"}`);
5510
+ } catch (e) {
5511
+ return error$1(e);
5512
+ }
5513
+ }
5514
+
5515
+ //#endregion
5516
+ //#region src/daemon-mcp/tools/config.ts
5517
+ /**
5518
+ * MCP handlers for config_list/config_get/config_set/config_unset — thin
5519
+ * wrappers around src/config/main-config-ops.ts, the same functions
5520
+ * src/cli/commands/config.ts calls, so masking and validation cannot drift
5521
+ * between the CLI and MCP surfaces.
5522
+ */
5523
+ function text$1(s) {
5524
+ return { content: [{
5525
+ type: "text",
5526
+ text: s
5527
+ }] };
5528
+ }
5529
+ function errorText(e) {
5530
+ return {
5531
+ content: [{
5532
+ type: "text",
5533
+ text: e instanceof MainConfigOpsError || e instanceof MainConfigError ? e.message : e instanceof Error ? e.message : String(e)
5534
+ }],
5535
+ isError: true
5536
+ };
5537
+ }
5538
+ function configList(args) {
5539
+ try {
5540
+ const r = listConfigOp({ all: args.all });
5541
+ return text$1(args.json ? JSON.stringify(r.data, null, 2) : r.yaml.trimEnd());
5542
+ } catch (e) {
5543
+ return errorText(e);
5544
+ }
5545
+ }
5546
+ function configGet(args) {
5547
+ try {
5548
+ const r = getConfigValueOp(args.path);
5549
+ if (!r.found) return errorText(/* @__PURE__ */ new Error(`Not set: ${args.path}`));
5550
+ return text$1(typeof r.value === "object" ? JSON.stringify(r.value, null, 2) : String(r.value));
5551
+ } catch (e) {
5552
+ return errorText(e);
5553
+ }
5554
+ }
5555
+ function configSet(args) {
5556
+ try {
5557
+ const r = setConfigValueOp(args.path, args.value, { force: args.force });
5558
+ const lines = [];
5559
+ if (r.yamlCreated) lines.push(`created ${r.yamlPath}`);
5560
+ lines.push(`Set ${args.path} = ${typeof r.value === "object" ? JSON.stringify(r.value) : String(r.value)}`);
5561
+ return text$1(lines.join("\n"));
5562
+ } catch (e) {
5563
+ return errorText(e);
5564
+ }
5565
+ }
5566
+ function configUnset(args) {
5567
+ try {
5568
+ return text$1(unsetConfigValueOp(args.path).existed ? `Unset ${args.path}` : `${args.path} was not set`);
5569
+ } catch (e) {
5570
+ return errorText(e);
5571
+ }
5572
+ }
5573
+
5574
+ //#endregion
5575
+ //#region src/daemon-mcp/tools/worker-capability.ts
5576
+ /**
5577
+ * worker_capability — the MCP tool behind cross-provider capability
5578
+ * preference (the config behind `pai worker capability`).
5579
+ *
5580
+ * Extracted like worker-model.ts: index.ts starts its transport on import,
5581
+ * so its inline handlers cannot be imported by tests. The mutations
5582
+ * themselves live in src/workers/providers.ts, shared with the CLI.
5583
+ */
5584
+ const text = (s) => ({ content: [{
5585
+ type: "text",
5586
+ text: s
5587
+ }] });
5588
+ const error = (e) => ({
5589
+ content: [{
5590
+ type: "text",
5591
+ text: e instanceof Error ? e.message : String(e)
5592
+ }],
5593
+ isError: true
5594
+ });
5595
+ /**
5596
+ * list (default): every capability preference and what it resolves to right
5597
+ * now. set: which provider(s) serve a capability, first usable one wins.
5598
+ * unset: remove a preference.
5599
+ */
5600
+ function workerCapability(args, configPath) {
5601
+ try {
5602
+ const action = args.action ?? "list";
5603
+ if (action === "list") {
5604
+ const { workers } = readWorkersSection(configPath);
5605
+ return text(describeCapabilities(workers).join("\n"));
5606
+ }
5607
+ if (!args.capability) return error(/* @__PURE__ */ new Error(`capability is required for action "${action}"`));
5608
+ if (action === "unset") {
5609
+ unsetCapabilityPreference(args.capability, configPath);
5610
+ return text(`removed the capability preference for "${args.capability}"`);
5611
+ }
5612
+ if (action !== "set") return error(/* @__PURE__ */ new Error(`unknown action "${String(action)}" (expected: list, set, unset)`));
5613
+ if (!args.providers || !args.providers.length) return error(/* @__PURE__ */ new Error("set needs a non-empty providers list"));
5614
+ const workers = setCapabilityPreference(args.capability, args.providers, configPath);
5615
+ return text(describeCapabilities(workers).find((l) => l.startsWith(`${args.capability}:`)) ?? `${args.capability}: [${workers.capabilities[args.capability]?.join(", ")}]`);
5509
5616
  } catch (e) {
5510
5617
  return error(e);
5511
5618
  }
@@ -5636,7 +5743,7 @@ async function startShim() {
5636
5743
  "",
5637
5744
  "Recency boost optionally down-weights older results (recency_boost=90 means scores halve every 90 days).",
5638
5745
  "",
5639
- "Defaults come from ~/.claude/pai/config.json (search section). Per-call parameters override config defaults.",
5746
+ "Defaults come from ~/.claude/pai/config.yaml (search section). Per-call parameters override config defaults.",
5640
5747
  "",
5641
5748
  "Returns ranked snippets with project slug, file path, line range, and score.",
5642
5749
  "Higher score = more relevant.",
@@ -5898,7 +6005,9 @@ async function startShim() {
5898
6005
  " base_url (anthropic protocol) or upstream_url (protocol=openai, runs",
5899
6006
  " through the local PAI proxy). A raw key is written to",
5900
6007
  " ~/.claude/pai/keys/<name> (mode 0600); only the path lands in the config.",
5901
- " engine=codex runs the Codex CLI instead of Claude Code.",
6008
+ " engine=codex runs the Codex CLI instead of Claude Code; engine=image POSTs",
6009
+ " straight to {base_url}/images/generations instead of spawning a process —",
6010
+ " pair it with a worker_capability image preference to run it.",
5902
6011
  "action=update changes cost_tier / tags of an existing provider.",
5903
6012
  "action=test runs a one-word pong probe through the provider",
5904
6013
  " (reports 'codex not installed' when that engine's CLI is missing)."
@@ -5917,7 +6026,11 @@ async function startShim() {
5917
6026
  base_url: string().optional().describe("Anthropic-compatible API base URL (add)."),
5918
6027
  upstream_url: string().optional().describe("Chat Completions base URL (add, protocol=openai)."),
5919
6028
  protocol: _enum(["anthropic", "openai"]).optional().describe("Protocol (add). Default: anthropic."),
5920
- engine: _enum(["claude", "codex"]).optional().describe("Runner engine (add). Default: claude."),
6029
+ engine: _enum([
6030
+ "claude",
6031
+ "codex",
6032
+ "image"
6033
+ ]).optional().describe("Runner engine (add). Default: claude."),
5921
6034
  context_window: number().int().positive().optional().describe("Context window for the meter (add). No default: unset hides the meter unless the init event announces one."),
5922
6035
  key_file: string().optional().describe("File holding the API token, 0600 (add)."),
5923
6036
  key: string().optional().describe("Raw API token (add) — parked in ~/.claude/pai/keys/<name>."),
@@ -6086,14 +6199,34 @@ async function startShim() {
6086
6199
  "",
6087
6200
  "action=get (default): the model ids of one provider (provider given,",
6088
6201
  "default: the active one) or of every provider when provider is omitted.",
6089
- "action=set needs model; capability picks which one (default, fast, image)."
6202
+ "action=set needs model; capability picks which one — the set is open",
6203
+ `(well-known: ${MODEL_CAPABILITIES.join(", ")}; any other ^[a-z][a-z0-9-]*$ name works too).`
6090
6204
  ].join("\n"), {
6091
6205
  action: _enum(["get", "set"]).optional().describe("Default: get."),
6092
6206
  provider: string().optional().describe("Provider to read or change (default: the active one)."),
6093
- capability: _enum(MODEL_CAPABILITIES).optional().describe("Which model capability to set (default, fast, image). Default: default."),
6207
+ capability: string().regex(CAPABILITY_NAME_RE).optional().describe(`Which model capability to set (open set; well-known: ${MODEL_CAPABILITIES.join(", ")}). Default: default.`),
6094
6208
  slot: _enum(["default", "fast"]).optional().describe("Deprecated pre-capability alias of `capability`."),
6095
6209
  model: string().optional().describe("The new model id (required for set).")
6096
6210
  }, async (args) => workerModel(args));
6211
+ server.tool("worker_capability", [
6212
+ "Which provider(s) serve a capability across the whole config (the",
6213
+ "config behind `pai worker capability`) — distinct from worker_model,",
6214
+ "which sets a model id on one provider's own table.",
6215
+ "",
6216
+ "action=list (default): every preference and what it resolves to now.",
6217
+ "action=set needs capability, providers (first usable one wins).",
6218
+ "action=unset needs capability. E.g. set capability=image,",
6219
+ "providers=[\"pictures\"] to run worker_run with capability=image against",
6220
+ "an engine=image provider named \"pictures\"."
6221
+ ].join("\n"), {
6222
+ action: _enum([
6223
+ "list",
6224
+ "set",
6225
+ "unset"
6226
+ ]).optional().describe("Default: list."),
6227
+ capability: string().optional().describe("Capability name, e.g. image (required for set, unset)."),
6228
+ providers: array(string()).optional().describe("Preference list, first usable one wins (required for set).")
6229
+ }, async (args) => workerCapability(args));
6097
6230
  server.tool("worker_run", [
6098
6231
  "Start a worker (or a chain) and return its id immediately.",
6099
6232
  "",
@@ -6101,8 +6234,9 @@ async function startShim() {
6101
6234
  "exactly one of prompt/specPath (the -p value, or a file/stdin '-' to read",
6102
6235
  "it from — avoids quoting long prompts through JSON-RPC). chain (e.g.",
6103
6236
  "\"draft,implement\") runs spec-first stages, class picks the provider",
6104
- "(default: active). Check on it with worker_ps / worker_replay, talk to",
6105
- "it with worker_say."
6237
+ "(default: active); capability=image runs an engine=image provider",
6238
+ "directly instead, writing a PNG rather than spawning claude.",
6239
+ "Check on it with worker_ps / worker_replay, talk to it with worker_say."
6106
6240
  ].join("\n"), workerRunShape, async (args) => {
6107
6241
  try {
6108
6242
  const cwd = args.cwd ?? process.cwd();
@@ -6139,6 +6273,7 @@ async function startShim() {
6139
6273
  });
6140
6274
  return runWorker({
6141
6275
  className: args.class,
6276
+ capabilityFlag: args.capability,
6142
6277
  label: args.label,
6143
6278
  noPane: false,
6144
6279
  mcpFlag: args.mcp,
@@ -6283,6 +6418,34 @@ async function startShim() {
6283
6418
  return workerError(e);
6284
6419
  }
6285
6420
  });
6421
+ server.tool("config_list", [
6422
+ "Print the main PAI config as YAML (or JSON). Secrets (key/token/secret/",
6423
+ "password fields, and postgres.connectionString) are always masked to",
6424
+ "****<last4>. Defaults to only what the file explicitly sets; all=true",
6425
+ "includes every built-in default value too."
6426
+ ].join("\n"), {
6427
+ all: boolean().optional().describe("Include every default value, not just what the file sets. Default: false."),
6428
+ json: boolean().optional().describe("Return JSON instead of YAML. Default: false.")
6429
+ }, async (args) => configList(args));
6430
+ server.tool("config_get", [
6431
+ "Read one config value by dotted path (e.g. search.recencyBoostDays).",
6432
+ "Resolves against the defaults-merged effective config, so a value never",
6433
+ "set in the file still answers with its built-in default. Masked",
6434
+ "(****<last4>) if the path looks like a secret."
6435
+ ].join("\n"), { path: string().min(1).describe("Dotted config path, e.g. search.recencyBoostDays.") }, async (args) => configGet(args));
6436
+ server.tool("config_set", [
6437
+ "Write one config value by dotted path. WRITES THE FILE: creates",
6438
+ "config.yaml on first use (converting config.json if it exists), then",
6439
+ "writes comment-preserving. Value parsing: true/false, null, numbers,",
6440
+ "[...] / {...} as JSON, else a plain string. Refuses an unknown",
6441
+ "top-level key or a value whose type disagrees with the built-in",
6442
+ "default unless force=true."
6443
+ ].join("\n"), {
6444
+ path: string().min(1).describe("Dotted config path, e.g. search.recencyBoostDays."),
6445
+ value: string().describe("Value to set, as a string (parsed per the rules above)."),
6446
+ force: boolean().optional().describe("Allow an unknown top-level key or a type mismatch. Default: false.")
6447
+ }, async (args) => configSet(args));
6448
+ server.tool("config_unset", ["Remove one config value by dotted path, WRITING THE FILE — reverting it", "to the built-in default. No-ops when the path was never explicitly set."].join("\n"), { path: string().min(1).describe("Dotted config path, e.g. search.recencyBoostDays.") }, async (args) => configUnset(args));
6286
6449
  const transport = new StdioServerTransport();
6287
6450
  await server.connect(transport);
6288
6451
  }