@tekmidian/pai 0.37.0 → 0.39.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 (85) hide show
  1. package/dist/{auto-route-DM7GhJ8y.mjs → auto-route-BlWOWT4z.mjs} +2 -2
  2. package/dist/{auto-route-DM7GhJ8y.mjs.map → auto-route-BlWOWT4z.mjs.map} +1 -1
  3. package/dist/{providers-sXcK5bDZ.mjs → chain-CTtHligI.mjs} +1040 -782
  4. package/dist/chain-CTtHligI.mjs.map +1 -0
  5. package/dist/cli/index.mjs +4 -3
  6. package/dist/cli/index.mjs.map +1 -1
  7. package/dist/cli/program.mjs +4 -3
  8. package/dist/{clusters-Do4tEGyc.mjs → clusters-wlK0w41E.mjs} +1 -1
  9. package/dist/{clusters-Do4tEGyc.mjs.map → clusters-wlK0w41E.mjs.map} +1 -1
  10. package/dist/daemon/index.mjs +7 -7
  11. package/dist/{daemon-CGg1VCbA.mjs → daemon-CTkN_Vn8.mjs} +25 -25
  12. package/dist/{daemon-CGg1VCbA.mjs.map → daemon-CTkN_Vn8.mjs.map} +1 -1
  13. package/dist/{daemon-DsGGiIJM.mjs → daemon-CmHHmKpg.mjs} +7 -7
  14. package/dist/daemon-mcp/index.mjs +207 -34
  15. package/dist/daemon-mcp/index.mjs.map +1 -1
  16. package/dist/{detector-CMap-9vw.mjs → detector-Bwk_4Pk4.mjs} +1 -1
  17. package/dist/{detector-CMap-9vw.mjs.map → detector-Bwk_4Pk4.mjs.map} +1 -1
  18. package/dist/detector-DO730Zq0.mjs +5 -0
  19. package/dist/{factory-DD2T33C9.mjs → factory-A9x-T2Kg.mjs} +5 -5
  20. package/dist/{factory-DD2T33C9.mjs.map → factory-A9x-T2Kg.mjs.map} +1 -1
  21. package/dist/factory-BD-np0Vd.mjs +3 -0
  22. package/dist/hooks/route-agents-to-worker.mjs +112 -20
  23. package/dist/hooks/route-agents-to-worker.mjs.map +2 -2
  24. package/dist/hooks/worker-proxy.mjs +110 -18
  25. package/dist/hooks/worker-proxy.mjs.map +2 -2
  26. package/dist/hooks/worker-status-line.mjs +176 -23
  27. package/dist/hooks/worker-status-line.mjs.map +4 -4
  28. package/dist/{indexer-backend-Cox9BCo-.mjs → indexer-backend-TG64CCQC.mjs} +1 -1
  29. package/dist/{indexer-backend-Cox9BCo-.mjs.map → indexer-backend-TG64CCQC.mjs.map} +1 -1
  30. package/dist/{latent-ideas-BC1oINZ-.mjs → latent-ideas-B7wq75Pt.mjs} +2 -2
  31. package/dist/{latent-ideas-BC1oINZ-.mjs.map → latent-ideas-B7wq75Pt.mjs.map} +1 -1
  32. package/dist/{link-boost-QFLrJwD6.mjs → link-boost-HkG7JWZR.mjs} +1 -1
  33. package/dist/{link-boost-QFLrJwD6.mjs.map → link-boost-HkG7JWZR.mjs.map} +1 -1
  34. package/dist/{neighborhood-D9MJ1c8f.mjs → neighborhood-lThN-MaQ.mjs} +1 -1
  35. package/dist/{neighborhood-D9MJ1c8f.mjs.map → neighborhood-lThN-MaQ.mjs.map} +1 -1
  36. package/dist/{note-context-d1wT_-GA.mjs → note-context-b6k0mAKi.mjs} +1 -1
  37. package/dist/{note-context-d1wT_-GA.mjs.map → note-context-b6k0mAKi.mjs.map} +1 -1
  38. package/dist/planner-BDI7bE9B.mjs +243 -0
  39. package/dist/planner-BDI7bE9B.mjs.map +1 -0
  40. package/dist/{postgres--BjPtLa0.mjs → postgres-mW1n7Vi1.mjs} +1 -1
  41. package/dist/{postgres--BjPtLa0.mjs.map → postgres-mW1n7Vi1.mjs.map} +1 -1
  42. package/dist/{program-CEIHn_Ma.mjs → program-DXBwJV7h.mjs} +274 -65
  43. package/dist/program-DXBwJV7h.mjs.map +1 -0
  44. package/dist/providers-FYkZjn_C.mjs +1405 -0
  45. package/dist/providers-FYkZjn_C.mjs.map +1 -0
  46. package/dist/{query-feedback-BUJxgw5B.mjs → query-feedback-BV4CcxqS.mjs} +1 -1
  47. package/dist/{query-feedback-BUJxgw5B.mjs.map → query-feedback-BV4CcxqS.mjs.map} +1 -1
  48. package/dist/query-feedback-DSVyHtrG.mjs +3 -0
  49. package/dist/router-Bk77E7hj.mjs +3 -0
  50. package/dist/{router-DK_sLsUL.mjs → router-DcHKnEPa.mjs} +1 -1
  51. package/dist/{router-DK_sLsUL.mjs.map → router-DcHKnEPa.mjs.map} +1 -1
  52. package/dist/skills/Worker/SKILL.md +34 -12
  53. package/dist/{sources-Bi7--33T.mjs → sources-kLnQsNrW.mjs} +1 -1
  54. package/dist/{sources-Bi7--33T.mjs.map → sources-kLnQsNrW.mjs.map} +1 -1
  55. package/dist/{sqlite-DtaL1glm.mjs → sqlite-BenGr3UP.mjs} +1 -1
  56. package/dist/{sqlite-DtaL1glm.mjs.map → sqlite-BenGr3UP.mjs.map} +1 -1
  57. package/dist/{state-BY2L6-vX.mjs → state-CAeyOdfq.mjs} +1 -1
  58. package/dist/{state-BY2L6-vX.mjs.map → state-CAeyOdfq.mjs.map} +1 -1
  59. package/dist/{state-8Hm9E4tW.mjs → state-Ca9F_sZc.mjs} +1 -1
  60. package/dist/{themes-BN0a2duq.mjs → themes-BI4GMRP9.mjs} +1 -1
  61. package/dist/{themes-BN0a2duq.mjs.map → themes-BI4GMRP9.mjs.map} +1 -1
  62. package/dist/{tools-CGPqpU3A.mjs → tools-Bp7hj6OW.mjs} +1 -1
  63. package/dist/{tools-y2bJpKom.mjs → tools-DGcB3o_S.mjs} +13 -13
  64. package/dist/{tools-y2bJpKom.mjs.map → tools-DGcB3o_S.mjs.map} +1 -1
  65. package/dist/{trace-h23JCcFD.mjs → trace-bobARFEX.mjs} +1 -1
  66. package/dist/{trace-h23JCcFD.mjs.map → trace-bobARFEX.mjs.map} +1 -1
  67. package/dist/{vault-indexer-DgsPjMgs.mjs → vault-indexer-C3OfXTNF.mjs} +1 -1
  68. package/dist/{vault-indexer-DgsPjMgs.mjs.map → vault-indexer-C3OfXTNF.mjs.map} +1 -1
  69. package/dist/{work-queue-worker-gsKd2LJa.mjs → work-queue-worker-DW8lz-Oo.mjs} +3 -3
  70. package/dist/{work-queue-worker-Dva_v_pI.mjs → work-queue-worker-R7UGIag_.mjs} +3 -3
  71. package/dist/{work-queue-worker-Dva_v_pI.mjs.map → work-queue-worker-R7UGIag_.mjs.map} +1 -1
  72. package/dist/{zettelkasten-vo7psPdT.mjs → zettelkasten-m5QPtb-L.mjs} +3 -3
  73. package/dist/{zettelkasten-vo7psPdT.mjs.map → zettelkasten-m5QPtb-L.mjs.map} +1 -1
  74. package/docs/commands/README.md +12 -7
  75. package/docs/commands/worker.md +110 -17
  76. package/docs/worker.md +244 -25
  77. package/package.json +1 -1
  78. package/src/hooks/ts/pre-tool-use/route-agents-to-worker.ts +2 -2
  79. package/dist/detector-DtLExmHN.mjs +0 -5
  80. package/dist/factory-BXzqRYVZ.mjs +0 -3
  81. package/dist/program-CEIHn_Ma.mjs.map +0 -1
  82. package/dist/providers-sXcK5bDZ.mjs.map +0 -1
  83. package/dist/query-feedback-DhyLOe5S.mjs +0 -3
  84. package/dist/router-1zi8jiNF.mjs +0 -3
  85. /package/dist/{main-resolver-IhZo4pI0.mjs → main-resolver-D6IImXvF.mjs} +0 -0
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { t as PaiClient } from "../ipc-client-BmypMNYk.mjs";
3
3
  import { o as loadConfig } from "../config-B64vFg14.mjs";
4
- import { C as loadStatus, D as workersLogDir, E as ledgerPath, a as setRole, c as useProvider, d as replayOutput, i as setProviderEnabled, k as readWorkersSection, m as testProvider, n as describeProviders, o as setWorkersEnabled, p as runWorker, r as removeProvider, s as unsetRole, t as addProvider, u as psOutput, v as sayToWorker, w as ledgerSummary } from "../providers-sXcK5bDZ.mjs";
4
+ import { B as workersLogDir, F as ledgerSummary, J as readWorkersSection, M as loadStatuses, a as testProvider, g as sayToWorker, h as readInbox, i as runWorker, j as loadStatus, k as alive, m as handoffFromInside, t as runChain, z as ledgerPath } from "../chain-CTtHligI.mjs";
5
+ import { a as setClass, c as unsetClass, f as psOutput, i as removeProvider, l as updateProvider, n as classTargetText, o as setProviderEnabled, p as replayOutput, r as describeProviders, s as setWorkersEnabled, t as addProvider, u as useProvider } from "../providers-FYkZjn_C.mjs";
5
6
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
7
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
8
 
@@ -1220,6 +1221,10 @@ const $ZodBoolean = /* @__PURE__ */ $constructor("$ZodBoolean", (inst, def) => {
1220
1221
  return payload;
1221
1222
  };
1222
1223
  });
1224
+ const $ZodUnknown = /* @__PURE__ */ $constructor("$ZodUnknown", (inst, def) => {
1225
+ $ZodType.init(inst, def);
1226
+ inst._zod.parse = (payload) => payload;
1227
+ });
1223
1228
  function handleArrayResult(result, final, index) {
1224
1229
  if (result.issues.length) final.issues.push(...prefixIssues(index, result.issues));
1225
1230
  final.value[index] = result.value;
@@ -2084,6 +2089,10 @@ function _boolean(Class, params) {
2084
2089
  });
2085
2090
  }
2086
2091
  /* @__NO_SIDE_EFFECTS__ */
2092
+ function _unknown(Class) {
2093
+ return new Class({ type: "unknown" });
2094
+ }
2095
+ /* @__NO_SIDE_EFFECTS__ */
2087
2096
  function _lt(value, params) {
2088
2097
  return new $ZodCheckLessThan({
2089
2098
  check: "less_than",
@@ -2646,6 +2655,7 @@ const numberProcessor = (schema, ctx, _json, _params) => {
2646
2655
  const booleanProcessor = (_schema, _ctx, json, _params) => {
2647
2656
  json.type = "boolean";
2648
2657
  };
2658
+ const unknownProcessor = (_schema, _ctx, _json, _params) => {};
2649
2659
  const enumProcessor = (schema, _ctx, json, _params) => {
2650
2660
  const def = schema._zod.def;
2651
2661
  const values = getEnumValues(def.entries);
@@ -3133,6 +3143,14 @@ const ZodBoolean = /* @__PURE__ */ $constructor("ZodBoolean", (inst, def) => {
3133
3143
  function boolean(params) {
3134
3144
  return _boolean(ZodBoolean, params);
3135
3145
  }
3146
+ const ZodUnknown = /* @__PURE__ */ $constructor("ZodUnknown", (inst, def) => {
3147
+ $ZodUnknown.init(inst, def);
3148
+ ZodType.init(inst, def);
3149
+ inst._zod.processJSONSchema = (ctx, json, params) => unknownProcessor(inst, ctx, json, params);
3150
+ });
3151
+ function unknown() {
3152
+ return _unknown(ZodUnknown);
3153
+ }
3136
3154
  const ZodArray = /* @__PURE__ */ $constructor("ZodArray", (inst, def) => {
3137
3155
  $ZodArray.init(inst, def);
3138
3156
  ZodType.init(inst, def);
@@ -4524,40 +4542,62 @@ const worker = {
4524
4542
  description: "Run, watch and route subagent workers on configured providers",
4525
4543
  content: `## Worker Skill
4526
4544
 
4527
- USE WHEN delegating subagent work, checking on running workers, or managing worker providers.
4545
+ USE WHEN delegating subagent work, checking on running workers, or managing worker providers/classes from chat.
4528
4546
 
4529
4547
  ### Delegating
4530
4548
 
4531
4549
  The Agent tool is denied when workers are on — delegate with Bash instead:
4532
4550
 
4533
4551
  \`\`\`
4534
- pai worker run --label "short task label" --role research \\
4552
+ pai worker run --label "short task label" --class research \\
4535
4553
  -p '<full, self-contained task spec>' \\
4536
4554
  --allowedTools 'Read,Edit,Write,Bash,Grep,Glob' --output-format json
4537
4555
  \`\`\`
4538
4556
 
4539
4557
  - Always run in background (run_in_background: true) and with a timeout.
4540
- - Roles: implement (default quality), research (web: add WebSearch,WebFetch), spotcheck (cheap/fast model).
4558
+ - Classes: draft (spec writing), plan, implement (default quality), review,
4559
+ research (web: add WebSearch,WebFetch), spotcheck (cheap/fast model), simple,
4560
+ complex, image. \`--role\` still works as an alias of \`--class\`.
4561
+ - Anything larger than a one-file change goes through a chain:
4562
+ \`pai worker run --chain draft,implement …\` — the draft stage turns the brief
4563
+ into a spec file, implement runs with it, \`--chain draft,implement,review\`
4564
+ adds a review pass. \`--class spotcheck\` for verification runs.
4541
4565
  - The answer is in the \`result\` field of the JSON it prints. Review the diff yourself.
4542
4566
  - \`--no-pane\` suppresses the iTerm follow pane; panes open automatically otherwise.
4543
- - Panes run under the \`pai-worker\` dynamic profile (default profile's font
4544
- family at \`workers.pane.fontSize\`, default 13); \`pai worker pane <id> --check\`
4545
- reports the pane plus that profile's path, existence and font.
4567
+ - \`--agent <name>\` runs a definition from ~/.claude/agents/<name>.md (the agent
4568
+ library runs on workers: body becomes the system prompt, tools the allowlist,
4569
+ model the class).
4546
4570
 
4547
4571
  ### Watching
4548
4572
 
4549
- - \`worker_ps\` — running + last finished workers.
4573
+ - \`worker_ps\` — running + last finished workers (chains show as trees).
4550
4574
  - \`worker_replay\` with id — the transcript of one worker.
4551
4575
  - CLI equivalents: \`pai worker ps\`, \`pai worker follow <id>\`.
4552
4576
 
4553
4577
  ### Routing
4554
4578
 
4555
4579
  - \`worker_status\` — on/off, active provider, providers, run tally.
4556
- - \`worker_providers\` — list/add/remove/use/enable/disable/test. Adding needs
4557
- name, base_url, model and a key (key file path, or a raw key which is
4558
- parked in ~/.config/pai/keys/<name>, mode 0600).
4559
- - \`worker_roles\` list/set/unset role provider[/fast].
4560
- - \`worker_toggle\` — off makes Agent subagents run on Anthropic again.`
4580
+ - \`worker_providers\` — list/add/update/remove/use/enable/disable/test. Adding
4581
+ needs name, base_url, model and a key (key file path, or a raw key which is
4582
+ parked in ~/.config/pai/keys/<name>, mode 0600). update changes cost_tier
4583
+ (1 cheapest 5 most expensive) and tags (code, vision, image-gen,
4584
+ long-context, fast, reasoning).
4585
+ - \`worker_classes\` — list/set/unset class → provider[/fast], or constraints
4586
+ only (max_cost_tier, require_tags) so auto-routing picks a qualifying provider.
4587
+ - \`worker_run\` — start a worker or chain from chat; returns the id immediately.
4588
+ - \`worker_toggle\` — off makes Agent subagents run on Anthropic again.
4589
+
4590
+ ### Preferences from chat (map the phrase, use the tool, never mention files)
4591
+
4592
+ - "use <provider> for image generation" / "route research to <provider>" →
4593
+ worker_classes set <class>=<provider>. One line back.
4594
+ - "prefer the flash model for simple tasks" / "cheap only for drafts" →
4595
+ worker_classes set simple|draft=<provider>/fast (or a max_cost_tier). One line.
4596
+ - "reviews should use a reasoning model" → worker_classes set review with
4597
+ require_tags ["reasoning"] (drop the pin if one exists). One line.
4598
+ - "what handles reviews" / "show the routing table" → worker_classes list
4599
+ (plus worker_status for providers). One or two lines.
4600
+ - Never tell the user to edit a config file — the tools do it.`
4561
4601
  };
4562
4602
 
4563
4603
  //#endregion
@@ -5718,7 +5758,8 @@ async function startShim() {
5718
5758
  });
5719
5759
  server.tool("worker_status", [
5720
5760
  "Show the worker system state: on/off, active provider, configured providers,",
5721
- "roles, and today's run tally from the ledger.",
5761
+ "roles, today's run tally from the ledger, and the running workers with",
5762
+ "their inbox counts (◆N = handoffs waiting).",
5722
5763
  "",
5723
5764
  "Use this before delegating with `pai worker run` to see what routing will choose."
5724
5765
  ].join("\n"), {}, async () => {
@@ -5726,32 +5767,42 @@ async function startShim() {
5726
5767
  const { workers } = readWorkersSection();
5727
5768
  const s = ledgerSummary(ledgerPath(workersLogDir(workers)), "all", 10);
5728
5769
  const tally = s ? [`runs ${s.scope}: ${s.started} started, ${s.endedOk} ok, ${s.endedFailed} failed`, `agent hook: ${s.denied} denied, ${s.allowed} allowed, ${s.reroutes} reroutes`] : ["no runs recorded yet"];
5770
+ const logDir = workersLogDir(workers);
5771
+ const running = loadStatuses(logDir).filter((w) => w.state === "running" && alive(w.pid));
5772
+ const runningLines = running.length ? [
5773
+ "",
5774
+ `running (${running.length}):`,
5775
+ ...running.map((w) => ` ${w.id} ${w.label}${w.parent ? ` (sub-worker of ${w.parent})` : ""} · ${w.last}` + (readInbox(logDir, w.id).length ? ` · ◆${readInbox(logDir, w.id).length}` : ""))
5776
+ ] : [];
5729
5777
  return workerText([
5730
5778
  `workers: ${workers.enabled ? "on" : "off"}`,
5731
5779
  `active provider: ${workers.active ?? "(none)"}`,
5732
5780
  ...describeProviders(workers),
5733
5781
  "",
5734
- ...tally
5782
+ ...tally,
5783
+ ...runningLines
5735
5784
  ].join("\n"));
5736
5785
  } catch (e) {
5737
5786
  return workerError(e);
5738
5787
  }
5739
5788
  });
5740
5789
  server.tool("worker_providers", [
5741
- "Manage worker providers (list, add, remove, use, enable, disable, test).",
5790
+ "Manage worker providers (list, add, update, remove, use, enable, disable, test).",
5742
5791
  "",
5743
- "action=list (default) shows providers, roles and routing.",
5792
+ "action=list (default) shows providers (with cost tier and tags), classes and routing.",
5744
5793
  "action=add needs name, model — and either key_file or key, plus:",
5745
5794
  " base_url (anthropic protocol) or upstream_url (protocol=openai, runs",
5746
5795
  " through the local PAI proxy). A raw key is written to",
5747
5796
  " ~/.config/pai/keys/<name> (mode 0600); only the path lands in the config.",
5748
5797
  " engine=codex runs the Codex CLI instead of Claude Code.",
5798
+ "action=update changes cost_tier / tags of an existing provider.",
5749
5799
  "action=test runs a one-word pong probe through the provider",
5750
5800
  " (reports 'codex not installed' when that engine's CLI is missing)."
5751
5801
  ].join("\n"), {
5752
5802
  action: _enum([
5753
5803
  "list",
5754
5804
  "add",
5805
+ "update",
5755
5806
  "remove",
5756
5807
  "use",
5757
5808
  "enable",
@@ -5770,12 +5821,29 @@ async function startShim() {
5770
5821
  fast_model: string().optional().describe("Cheaper model for spotchecks (add, optional)."),
5771
5822
  env: record(string(), string()).optional().describe("Extra env for runs (add, optional)."),
5772
5823
  note: string().optional().describe("Human note shown in listings (add, optional)."),
5773
- quota_probe: string().optional().describe("URL whose JSON first number is the quota percent (add, optional).")
5824
+ quota_probe: string().optional().describe("URL whose JSON first number is the quota percent (add, optional)."),
5825
+ cost_tier: number().int().min(1).max(5).optional().describe("Cost tier 1 (cheapest) … 5 (most expensive). Default 3. (add, update)"),
5826
+ tags: array(_enum([
5827
+ "code",
5828
+ "vision",
5829
+ "image-gen",
5830
+ "long-context",
5831
+ "fast",
5832
+ "reasoning"
5833
+ ])).optional().describe("Capability tags (add, update).")
5774
5834
  }, async (args) => {
5775
5835
  try {
5776
5836
  const action = args.action ?? "list";
5777
5837
  if (action === "list") return workerText(describeProviders(readWorkersSection().workers).join("\n"));
5778
5838
  if (!args.name) return workerError(/* @__PURE__ */ new Error("name is required for this action"));
5839
+ if (action === "update") {
5840
+ if (args.cost_tier === void 0 && args.tags === void 0) return workerError(/* @__PURE__ */ new Error("update needs cost_tier and/or tags"));
5841
+ updateProvider(args.name, {
5842
+ ...args.cost_tier !== void 0 ? { costTier: args.cost_tier } : {},
5843
+ ...args.tags !== void 0 ? { tags: args.tags } : {}
5844
+ });
5845
+ return workerText([`provider ${args.name} updated`, ...describeProviders(readWorkersSection().workers)].join("\n"));
5846
+ }
5779
5847
  if (action === "add") {
5780
5848
  if (!args.model) return workerError(/* @__PURE__ */ new Error("add needs model"));
5781
5849
  if (args.protocol !== "openai" && !args.base_url) return workerError(/* @__PURE__ */ new Error("add needs base_url (only protocol=openai goes without it)"));
@@ -5792,7 +5860,9 @@ async function startShim() {
5792
5860
  ...args.upstream_url ? { upstreamUrl: args.upstream_url } : {},
5793
5861
  ...args.engine ? { engine: args.engine } : {},
5794
5862
  ...args.context_window ? { contextWindow: args.context_window } : {},
5795
- ...args.quota_probe ? { quotaProbe: args.quota_probe } : {}
5863
+ ...args.quota_probe ? { quotaProbe: args.quota_probe } : {},
5864
+ ...args.cost_tier !== void 0 ? { costTier: args.cost_tier } : {},
5865
+ ...args.tags?.length ? { tags: args.tags } : {}
5796
5866
  });
5797
5867
  return workerText([`provider ${args.name} added; active: ${workers.active ?? "(none)"}; workers ${workers.enabled ? "on" : "off"}`, ...describeProviders(workers)].join("\n"));
5798
5868
  }
@@ -5818,37 +5888,140 @@ async function startShim() {
5818
5888
  return workerError(e);
5819
5889
  }
5820
5890
  });
5821
- server.tool("worker_roles", [
5822
- "Manage worker roles (list, set, unset).",
5891
+ server.tool("worker_classes", [
5892
+ "Manage worker classes (list, set, unset).",
5823
5893
  "",
5824
- "A role maps a task class to a provider, optionally its fast model:",
5894
+ "A class maps a task class to a provider, optionally its fast model:",
5825
5895
  "implement=glm, research=glm, spotcheck=glm/fast.",
5826
- "Runs pick the provider via --role first, then the active provider."
5896
+ "set can instead give routing constraints only (max_cost_tier,",
5897
+ "require_tags) — auto-routing then picks a qualifying provider.",
5898
+ "Runs pick the provider via --class first, then the active provider."
5827
5899
  ].join("\n"), {
5828
5900
  action: _enum([
5829
5901
  "list",
5830
5902
  "set",
5831
5903
  "unset"
5832
5904
  ]).optional().describe("Default: list."),
5833
- role: string().optional().describe("Role name (set/unset)."),
5834
- target: string().optional().describe("Provider or provider/fast (set).")
5905
+ class: string().optional().describe("Class name (set/unset), e.g. implement, spotcheck."),
5906
+ target: string().optional().describe("Provider or provider/fast (set)."),
5907
+ max_cost_tier: number().int().min(1).max(5).optional().describe("Auto-routing considers only providers up to this cost tier (set)."),
5908
+ require_tags: array(_enum([
5909
+ "code",
5910
+ "vision",
5911
+ "image-gen",
5912
+ "long-context",
5913
+ "fast",
5914
+ "reasoning"
5915
+ ])).optional().describe("Auto-routing needs these tags (set).")
5835
5916
  }, async (args) => {
5836
5917
  try {
5837
5918
  const action = args.action ?? "list";
5838
5919
  if (action === "list") {
5839
5920
  const { workers } = readWorkersSection();
5840
- const entries = Object.entries(workers.roles);
5841
- const text = (t) => typeof t === "string" ? t : typeof t === "object" && t !== null ? `${t.provider ?? "?"}${t.mcp?.length ? ` +mcp(${t.mcp.join(",")})` : ""}` : String(t);
5842
- return workerText(entries.length ? entries.map(([r, t]) => `${r}: ${text(t)}`).join("\n") : "no roles set");
5921
+ const entries = Object.entries(workers.classes);
5922
+ return workerText(entries.length ? entries.map(([cls, t]) => `${cls}: ${classTargetText(t)}`).join("\n") : "no classes set");
5843
5923
  }
5844
- if (!args.role) return workerError(/* @__PURE__ */ new Error("role is required for this action"));
5924
+ if (!args.class) return workerError(/* @__PURE__ */ new Error("class is required for this action"));
5845
5925
  if (action === "set") {
5846
- if (!args.target) return workerError(/* @__PURE__ */ new Error("set needs target provider[/fast]"));
5847
- setRole(args.role, args.target);
5848
- return workerText(`role ${args.role} ${args.target}`);
5926
+ const hasConstraints = args.max_cost_tier !== void 0 || args.require_tags !== void 0;
5927
+ if (!args.target && !hasConstraints) return workerError(/* @__PURE__ */ new Error("set needs target provider[/fast] and/or max_cost_tier/require_tags"));
5928
+ const target = args.target !== void 0 && !hasConstraints ? args.target : {
5929
+ ...args.target ? { provider: args.target.split("/")[0] } : {},
5930
+ ...args.max_cost_tier !== void 0 ? { maxCostTier: args.max_cost_tier } : {},
5931
+ ...args.require_tags !== void 0 ? { requireTags: args.require_tags } : {}
5932
+ };
5933
+ setClass(args.class, target);
5934
+ return workerText(`class ${args.class} → ${classTargetText(target)}`);
5849
5935
  }
5850
- unsetRole(args.role);
5851
- return workerText(`role ${args.role} removed`);
5936
+ unsetClass(args.class);
5937
+ return workerText(`class ${args.class} removed`);
5938
+ } catch (e) {
5939
+ return workerError(e);
5940
+ }
5941
+ });
5942
+ server.tool("worker_run", [
5943
+ "Start a worker (or a chain) and return its id immediately.",
5944
+ "",
5945
+ "This is the chat-side face of `pai worker run`: prompt is required,",
5946
+ "chain (e.g. \"draft,implement\") runs spec-first stages, class picks the",
5947
+ "provider (default: active). Check on it with worker_ps / worker_replay,",
5948
+ "talk to it with worker_say."
5949
+ ].join("\n"), {
5950
+ prompt: string().min(1).describe("The task (the -p value)."),
5951
+ chain: string().optional().describe("Comma-separated stages, e.g. draft,implement or draft,implement,review."),
5952
+ class: string().optional().describe("Task class (draft, implement, review, research, spotcheck, simple, complex, image)."),
5953
+ label: string().optional().describe("Short task label shown in worker_ps."),
5954
+ cwd: string().optional().describe("Working directory (default: here)."),
5955
+ allowed_tools: string().optional().describe("Comma-separated tool allowlist passed to the worker."),
5956
+ mcp: string().optional().describe("MCP servers/sets the worker may use (comma-separated).")
5957
+ }, async (args) => {
5958
+ try {
5959
+ const claudeArgs = [
5960
+ "-p",
5961
+ args.prompt,
5962
+ ...args.allowed_tools ? ["--allowedTools", args.allowed_tools] : []
5963
+ ];
5964
+ let startedIdResolve;
5965
+ let startupFailure = null;
5966
+ const idArrived = new Promise((resolve) => {
5967
+ startedIdResolve = resolve;
5968
+ });
5969
+ const background = async () => {
5970
+ if (args.chain) return runChain({
5971
+ stages: args.chain.split(","),
5972
+ className: args.class,
5973
+ label: args.label,
5974
+ noPane: false,
5975
+ mcpFlag: args.mcp,
5976
+ brief: args.prompt,
5977
+ claudeArgs,
5978
+ ...args.cwd ? { cwd: args.cwd } : {},
5979
+ onChainStart: (id) => startedIdResolve(id),
5980
+ quiet: true
5981
+ });
5982
+ return runWorker({
5983
+ className: args.class,
5984
+ label: args.label,
5985
+ noPane: false,
5986
+ mcpFlag: args.mcp,
5987
+ claudeArgs,
5988
+ ...args.cwd ? { cwd: args.cwd } : {},
5989
+ onWorkerStart: (wid) => startedIdResolve(wid),
5990
+ quiet: true
5991
+ });
5992
+ };
5993
+ background().catch((e) => {
5994
+ startupFailure = e;
5995
+ startedIdResolve("");
5996
+ });
5997
+ const id = await Promise.race([idArrived, new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("worker did not start within 15s")), 15e3))]);
5998
+ if (!id) return workerError(startupFailure ?? /* @__PURE__ */ new Error("worker failed to start"));
5999
+ return workerText(`${id} started${args.chain ? ` (chain: ${args.chain})` : ""} — check on it with worker_ps, worker_replay, worker_say`);
6000
+ } catch (e) {
6001
+ return workerError(e);
6002
+ }
6003
+ });
6004
+ server.tool("worker_handoff", [
6005
+ "From inside a worker: send a handoff up to the parent worker.",
6006
+ "",
6007
+ "kind: proposal (do this instead of me), question, blocker, or result",
6008
+ "(results are sent for you when you finish — only send one yourself for",
6009
+ "mid-run findings). The handoff lands in the parent's inbox and is said to",
6010
+ "it when it is still running. Outside a worker this fails — there is no",
6011
+ "sideways or downward path."
6012
+ ].join("\n"), {
6013
+ kind: _enum([
6014
+ "proposal",
6015
+ "result",
6016
+ "question",
6017
+ "blocker"
6018
+ ]).describe("What the handoff carries."),
6019
+ text: string().min(1).describe("The message body, one paragraph."),
6020
+ data: record(string(), unknown()).optional().describe("Structured payload (optional).")
6021
+ }, async (args) => {
6022
+ try {
6023
+ const h = await handoffFromInside(workersLogDir(readWorkersSection().workers), process.env, args);
6024
+ return workerText(`ok → ${h.to} (${h.kind}); the parent sees it in its inbox`);
5852
6025
  } catch (e) {
5853
6026
  return workerError(e);
5854
6027
  }