@tekmidian/pai 0.36.2 → 0.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/dist/{auto-route-Byf8ENXj.mjs → auto-route-DM7GhJ8y.mjs} +2 -2
  2. package/dist/{auto-route-Byf8ENXj.mjs.map → auto-route-DM7GhJ8y.mjs.map} +1 -1
  3. package/dist/cli/index.mjs +5 -4
  4. package/dist/cli/index.mjs.map +1 -1
  5. package/dist/cli/program.d.mts.map +1 -1
  6. package/dist/cli/program.mjs +5 -4
  7. package/dist/{clusters-wZgTCYCB.mjs → clusters-Do4tEGyc.mjs} +1 -1
  8. package/dist/{clusters-wZgTCYCB.mjs.map → clusters-Do4tEGyc.mjs.map} +1 -1
  9. package/dist/{context-handover-cache-PtNvj_8D.mjs → context-handover-cache-BpUojjsi.mjs} +2 -2
  10. package/dist/{context-handover-cache-PtNvj_8D.mjs.map → context-handover-cache-BpUojjsi.mjs.map} +1 -1
  11. package/dist/daemon/index.mjs +8 -8
  12. package/dist/{daemon-BZ93KRBo.mjs → daemon-CGg1VCbA.mjs} +25 -25
  13. package/dist/{daemon-BZ93KRBo.mjs.map → daemon-CGg1VCbA.mjs.map} +1 -1
  14. package/dist/daemon-DsGGiIJM.mjs +20 -0
  15. package/dist/daemon-mcp/index.mjs +549 -1
  16. package/dist/daemon-mcp/index.mjs.map +1 -1
  17. package/dist/{detector--Gg5JRN5.mjs → detector-CMap-9vw.mjs} +1 -1
  18. package/dist/{detector--Gg5JRN5.mjs.map → detector-CMap-9vw.mjs.map} +1 -1
  19. package/dist/detector-DtLExmHN.mjs +5 -0
  20. package/dist/factory-BXzqRYVZ.mjs +3 -0
  21. package/dist/{factory-Bsp7xOpO.mjs → factory-DD2T33C9.mjs} +5 -5
  22. package/dist/{factory-Bsp7xOpO.mjs.map → factory-DD2T33C9.mjs.map} +1 -1
  23. package/dist/hooks/context-compression-hook.mjs +164 -14
  24. package/dist/hooks/context-compression-hook.mjs.map +4 -4
  25. package/dist/hooks/initialize-session.mjs +6 -0
  26. package/dist/hooks/initialize-session.mjs.map +3 -3
  27. package/dist/hooks/load-core-context.mjs +6 -0
  28. package/dist/hooks/load-core-context.mjs.map +3 -3
  29. package/dist/hooks/load-project-context.mjs +6 -0
  30. package/dist/hooks/load-project-context.mjs.map +3 -3
  31. package/dist/hooks/post-compact-inject.mjs +6 -0
  32. package/dist/hooks/post-compact-inject.mjs.map +3 -3
  33. package/dist/hooks/route-agents-to-worker.mjs +518 -0
  34. package/dist/hooks/route-agents-to-worker.mjs.map +7 -0
  35. package/dist/hooks/status-line.mjs +400 -0
  36. package/dist/hooks/status-line.mjs.map +7 -0
  37. package/dist/hooks/stop-hook.mjs +6 -0
  38. package/dist/hooks/stop-hook.mjs.map +3 -3
  39. package/dist/hooks/subagent-stop-hook.mjs +6 -0
  40. package/dist/hooks/subagent-stop-hook.mjs.map +3 -3
  41. package/dist/hooks/sync-todo-to-md.mjs +6 -0
  42. package/dist/hooks/sync-todo-to-md.mjs.map +3 -3
  43. package/dist/hooks/update-tab-on-action.mjs +6 -0
  44. package/dist/hooks/update-tab-on-action.mjs.map +3 -3
  45. package/dist/hooks/update-tab-titles.mjs +6 -0
  46. package/dist/hooks/update-tab-titles.mjs.map +3 -3
  47. package/dist/hooks/worker-proxy.mjs +858 -0
  48. package/dist/hooks/worker-proxy.mjs.map +7 -0
  49. package/dist/hooks/worker-status-line.mjs +545 -0
  50. package/dist/hooks/worker-status-line.mjs.map +7 -0
  51. package/dist/{indexer-backend-nQZuEx6N.mjs → indexer-backend-Cox9BCo-.mjs} +1 -1
  52. package/dist/{indexer-backend-nQZuEx6N.mjs.map → indexer-backend-Cox9BCo-.mjs.map} +1 -1
  53. package/dist/{kg-entity-DbOMPdF9.mjs → kg-entity-r8duqhi9.mjs} +1 -1
  54. package/dist/{kg-entity-DbOMPdF9.mjs.map → kg-entity-r8duqhi9.mjs.map} +1 -1
  55. package/dist/{latent-ideas-Bn6A5-5P.mjs → latent-ideas-BC1oINZ-.mjs} +2 -2
  56. package/dist/{latent-ideas-Bn6A5-5P.mjs.map → latent-ideas-BC1oINZ-.mjs.map} +1 -1
  57. package/dist/{link-boost-fYjUnxCN.mjs → link-boost-QFLrJwD6.mjs} +1 -1
  58. package/dist/{link-boost-fYjUnxCN.mjs.map → link-boost-QFLrJwD6.mjs.map} +1 -1
  59. package/dist/{main-resolver-BAbhKpeX.mjs → main-resolver-DlaLOFBA.mjs} +1 -1
  60. package/dist/{main-resolver-BAbhKpeX.mjs.map → main-resolver-DlaLOFBA.mjs.map} +1 -1
  61. package/dist/{main-resolver-Dxh444GO.mjs → main-resolver-IhZo4pI0.mjs} +1 -1
  62. package/dist/{neighborhood-DpaEM991.mjs → neighborhood-D9MJ1c8f.mjs} +1 -1
  63. package/dist/{neighborhood-DpaEM991.mjs.map → neighborhood-D9MJ1c8f.mjs.map} +1 -1
  64. package/dist/{note-context-DrcY4cWm.mjs → note-context-d1wT_-GA.mjs} +1 -1
  65. package/dist/{note-context-DrcY4cWm.mjs.map → note-context-d1wT_-GA.mjs.map} +1 -1
  66. package/dist/{postgres-BVme6qX0.mjs → postgres--BjPtLa0.mjs} +1 -1
  67. package/dist/{postgres-BVme6qX0.mjs.map → postgres--BjPtLa0.mjs.map} +1 -1
  68. package/dist/{program-C-fUghPv.mjs → program-Y0hAiVy8.mjs} +851 -101
  69. package/dist/program-Y0hAiVy8.mjs.map +1 -0
  70. package/dist/providers-DUshcB-d.mjs +4031 -0
  71. package/dist/providers-DUshcB-d.mjs.map +1 -0
  72. package/dist/{query-feedback-C1T6kS18.mjs → query-feedback-BUJxgw5B.mjs} +1 -1
  73. package/dist/{query-feedback-C1T6kS18.mjs.map → query-feedback-BUJxgw5B.mjs.map} +1 -1
  74. package/dist/query-feedback-DhyLOe5S.mjs +3 -0
  75. package/dist/router-1zi8jiNF.mjs +3 -0
  76. package/dist/{router-CsDm7HvK.mjs → router-DK_sLsUL.mjs} +1 -1
  77. package/dist/{router-CsDm7HvK.mjs.map → router-DK_sLsUL.mjs.map} +1 -1
  78. package/dist/skills/Worker/SKILL.md +63 -0
  79. package/dist/{sources-D8ZdNfvK.mjs → sources-Bi7--33T.mjs} +1 -1
  80. package/dist/{sources-D8ZdNfvK.mjs.map → sources-Bi7--33T.mjs.map} +1 -1
  81. package/dist/{sqlite-D1IaR8Am.mjs → sqlite-DtaL1glm.mjs} +1 -1
  82. package/dist/{sqlite-D1IaR8Am.mjs.map → sqlite-DtaL1glm.mjs.map} +1 -1
  83. package/dist/{state-qtmrBWCm.mjs → state-8Hm9E4tW.mjs} +1 -1
  84. package/dist/{state-WaXhLr6R.mjs → state-BY2L6-vX.mjs} +1 -1
  85. package/dist/{state-WaXhLr6R.mjs.map → state-BY2L6-vX.mjs.map} +1 -1
  86. package/dist/{themes-XPkj_bfP.mjs → themes-BN0a2duq.mjs} +1 -1
  87. package/dist/{themes-XPkj_bfP.mjs.map → themes-BN0a2duq.mjs.map} +1 -1
  88. package/dist/{tools-DEt6YPfc.mjs → tools-CGPqpU3A.mjs} +1 -1
  89. package/dist/{tools-ceiy7ANX.mjs → tools-y2bJpKom.mjs} +14 -14
  90. package/dist/{tools-ceiy7ANX.mjs.map → tools-y2bJpKom.mjs.map} +1 -1
  91. package/dist/{trace-DfyGmMG_.mjs → trace-h23JCcFD.mjs} +1 -1
  92. package/dist/{trace-DfyGmMG_.mjs.map → trace-h23JCcFD.mjs.map} +1 -1
  93. package/dist/{vault-indexer-CFvlPUMB.mjs → vault-indexer-DgsPjMgs.mjs} +1 -1
  94. package/dist/{vault-indexer-CFvlPUMB.mjs.map → vault-indexer-DgsPjMgs.mjs.map} +1 -1
  95. package/dist/{work-queue-worker-B8W8_3Rn.mjs → work-queue-worker-Dva_v_pI.mjs} +4 -4
  96. package/dist/{work-queue-worker-B8W8_3Rn.mjs.map → work-queue-worker-Dva_v_pI.mjs.map} +1 -1
  97. package/dist/{work-queue-worker-HN2Ufg-L.mjs → work-queue-worker-gsKd2LJa.mjs} +4 -4
  98. package/dist/{zettelkasten-CvjmMghT.mjs → zettelkasten-vo7psPdT.mjs} +3 -3
  99. package/dist/{zettelkasten-CvjmMghT.mjs.map → zettelkasten-vo7psPdT.mjs.map} +1 -1
  100. package/docs/commands/README.md +29 -0
  101. package/docs/commands/backup.md +1 -1
  102. package/docs/commands/clear-names.md +1 -1
  103. package/docs/commands/daemon.md +1 -1
  104. package/docs/commands/db.md +1 -1
  105. package/docs/commands/end.md +1 -1
  106. package/docs/commands/help.md +1 -1
  107. package/docs/commands/identity.md +1 -1
  108. package/docs/commands/kg.md +1 -1
  109. package/docs/commands/mcp.md +1 -1
  110. package/docs/commands/memory.md +1 -1
  111. package/docs/commands/notify.md +1 -1
  112. package/docs/commands/observation.md +1 -1
  113. package/docs/commands/obsidian.md +1 -1
  114. package/docs/commands/pause.md +1 -1
  115. package/docs/commands/project.md +1 -1
  116. package/docs/commands/projects.md +1 -1
  117. package/docs/commands/registry.md +1 -1
  118. package/docs/commands/restore.md +1 -1
  119. package/docs/commands/session.md +1 -1
  120. package/docs/commands/sessions.md +1 -1
  121. package/docs/commands/setup.md +1 -1
  122. package/docs/commands/shell-init.md +1 -1
  123. package/docs/commands/skill.md +1 -1
  124. package/docs/commands/task.md +1 -1
  125. package/docs/commands/topic.md +1 -1
  126. package/docs/commands/update.md +1 -1
  127. package/docs/commands/worker.md +398 -0
  128. package/docs/commands/zettel.md +1 -1
  129. package/docs/worker.md +434 -0
  130. package/package.json +1 -1
  131. package/scripts/build-hooks.mjs +46 -1
  132. package/src/hooks/pre-compact.sh +1 -0
  133. package/src/hooks/session-autosave.sh +1 -0
  134. package/src/hooks/session-stop.sh +1 -0
  135. package/src/hooks/ts/lib/context-fill.test.ts +104 -0
  136. package/src/hooks/ts/lib/context-fill.ts +73 -0
  137. package/src/hooks/ts/lib/handover-evidence.ts +227 -0
  138. package/src/hooks/ts/lib/worker-session.test.ts +24 -0
  139. package/src/hooks/ts/lib/worker-session.ts +23 -0
  140. package/src/hooks/ts/post-tool-use/sync-todo-to-md.ts +2 -0
  141. package/src/hooks/ts/post-tool-use/update-tab-on-action.ts +2 -0
  142. package/src/hooks/ts/pre-compact/context-compression-hook.ts +17 -0
  143. package/src/hooks/ts/pre-tool-use/route-agents-to-worker.ts +128 -0
  144. package/src/hooks/ts/session-start/initialize-session.ts +2 -0
  145. package/src/hooks/ts/session-start/load-core-context.ts +2 -0
  146. package/src/hooks/ts/session-start/load-project-context.ts +2 -0
  147. package/src/hooks/ts/session-start/post-compact-inject.ts +2 -0
  148. package/src/hooks/ts/stop/stop-hook.ts +2 -0
  149. package/src/hooks/ts/subagent-stop/subagent-stop-hook.ts +2 -0
  150. package/src/hooks/ts/user-prompt/update-tab-titles.ts +2 -0
  151. package/statusline-command.sh +17 -0
  152. package/dist/daemon-DRdoA489.mjs +0 -20
  153. package/dist/detector-DGAk1iBR.mjs +0 -5
  154. package/dist/factory-CrokPMk2.mjs +0 -3
  155. package/dist/program-C-fUghPv.mjs.map +0 -1
  156. package/dist/query-feedback-BBMBp96K.mjs +0 -3
  157. package/dist/router-BMkOb62X.mjs +0 -3
@@ -1,6 +1,7 @@
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 { A as ledgerPath, D as ledgerSummary, E as loadStatus, P as readWorkersSection, _ as testProvider, a as setClass, c as unsetClass, f as psOutput, g as runWorker, h as runChain, i as removeProvider, j as workersLogDir, l as updateProvider, n as classTargetText, o as setProviderEnabled, p as replayOutput, r as describeProviders, s as setWorkersEnabled, t as addProvider, u as useProvider, x as sayToWorker } from "../providers-DUshcB-d.mjs";
4
5
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
6
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
7
 
@@ -1406,6 +1407,97 @@ function handleIntersectionResults(result, left, right) {
1406
1407
  result.value = merged.data;
1407
1408
  return result;
1408
1409
  }
1410
+ const $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
1411
+ $ZodType.init(inst, def);
1412
+ inst._zod.parse = (payload, ctx) => {
1413
+ const input = payload.value;
1414
+ if (!isPlainObject(input)) {
1415
+ payload.issues.push({
1416
+ expected: "record",
1417
+ code: "invalid_type",
1418
+ input,
1419
+ inst
1420
+ });
1421
+ return payload;
1422
+ }
1423
+ const proms = [];
1424
+ const values = def.keyType._zod.values;
1425
+ if (values) {
1426
+ payload.value = {};
1427
+ const recordKeys = /* @__PURE__ */ new Set();
1428
+ for (const key of values) if (typeof key === "string" || typeof key === "number" || typeof key === "symbol") {
1429
+ recordKeys.add(typeof key === "number" ? key.toString() : key);
1430
+ const result = def.valueType._zod.run({
1431
+ value: input[key],
1432
+ issues: []
1433
+ }, ctx);
1434
+ if (result instanceof Promise) proms.push(result.then((result) => {
1435
+ if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues));
1436
+ payload.value[key] = result.value;
1437
+ }));
1438
+ else {
1439
+ if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues));
1440
+ payload.value[key] = result.value;
1441
+ }
1442
+ }
1443
+ let unrecognized;
1444
+ for (const key in input) if (!recordKeys.has(key)) {
1445
+ unrecognized = unrecognized ?? [];
1446
+ unrecognized.push(key);
1447
+ }
1448
+ if (unrecognized && unrecognized.length > 0) payload.issues.push({
1449
+ code: "unrecognized_keys",
1450
+ input,
1451
+ inst,
1452
+ keys: unrecognized
1453
+ });
1454
+ } else {
1455
+ payload.value = {};
1456
+ for (const key of Reflect.ownKeys(input)) {
1457
+ if (key === "__proto__") continue;
1458
+ let keyResult = def.keyType._zod.run({
1459
+ value: key,
1460
+ issues: []
1461
+ }, ctx);
1462
+ if (keyResult instanceof Promise) throw new Error("Async schemas not supported in object keys currently");
1463
+ if (typeof key === "string" && number$1.test(key) && keyResult.issues.length) {
1464
+ const retryResult = def.keyType._zod.run({
1465
+ value: Number(key),
1466
+ issues: []
1467
+ }, ctx);
1468
+ if (retryResult instanceof Promise) throw new Error("Async schemas not supported in object keys currently");
1469
+ if (retryResult.issues.length === 0) keyResult = retryResult;
1470
+ }
1471
+ if (keyResult.issues.length) {
1472
+ if (def.mode === "loose") payload.value[key] = input[key];
1473
+ else payload.issues.push({
1474
+ code: "invalid_key",
1475
+ origin: "record",
1476
+ issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config())),
1477
+ input: key,
1478
+ path: [key],
1479
+ inst
1480
+ });
1481
+ continue;
1482
+ }
1483
+ const result = def.valueType._zod.run({
1484
+ value: input[key],
1485
+ issues: []
1486
+ }, ctx);
1487
+ if (result instanceof Promise) proms.push(result.then((result) => {
1488
+ if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues));
1489
+ payload.value[keyResult.value] = result.value;
1490
+ }));
1491
+ else {
1492
+ if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues));
1493
+ payload.value[keyResult.value] = result.value;
1494
+ }
1495
+ }
1496
+ }
1497
+ if (proms.length) return Promise.all(proms).then(() => payload);
1498
+ return payload;
1499
+ };
1500
+ });
1409
1501
  const $ZodEnum = /* @__PURE__ */ $constructor("$ZodEnum", (inst, def) => {
1410
1502
  $ZodType.init(inst, def);
1411
1503
  const values = getEnumValues(def.entries);
@@ -2614,6 +2706,39 @@ const intersectionProcessor = (schema, ctx, json, params) => {
2614
2706
  const isSimpleIntersection = (val) => "allOf" in val && Object.keys(val).length === 1;
2615
2707
  json.allOf = [...isSimpleIntersection(a) ? a.allOf : [a], ...isSimpleIntersection(b) ? b.allOf : [b]];
2616
2708
  };
2709
+ const recordProcessor = (schema, ctx, _json, params) => {
2710
+ const json = _json;
2711
+ const def = schema._zod.def;
2712
+ json.type = "object";
2713
+ const keyType = def.keyType;
2714
+ const patterns = keyType._zod.bag?.patterns;
2715
+ if (def.mode === "loose" && patterns && patterns.size > 0) {
2716
+ const valueSchema = process$1(def.valueType, ctx, {
2717
+ ...params,
2718
+ path: [
2719
+ ...params.path,
2720
+ "patternProperties",
2721
+ "*"
2722
+ ]
2723
+ });
2724
+ json.patternProperties = {};
2725
+ for (const pattern of patterns) json.patternProperties[pattern.source] = valueSchema;
2726
+ } else {
2727
+ if (ctx.target === "draft-07" || ctx.target === "draft-2020-12") json.propertyNames = process$1(def.keyType, ctx, {
2728
+ ...params,
2729
+ path: [...params.path, "propertyNames"]
2730
+ });
2731
+ json.additionalProperties = process$1(def.valueType, ctx, {
2732
+ ...params,
2733
+ path: [...params.path, "additionalProperties"]
2734
+ });
2735
+ }
2736
+ const keyValues = keyType._zod.values;
2737
+ if (keyValues) {
2738
+ const validKeyValues = [...keyValues].filter((v) => typeof v === "string" || typeof v === "number");
2739
+ if (validKeyValues.length > 0) json.required = validKeyValues;
2740
+ }
2741
+ };
2617
2742
  const nullableProcessor = (schema, ctx, json, params) => {
2618
2743
  const def = schema._zod.def;
2619
2744
  const inner = process$1(def.innerType, ctx, params);
@@ -3047,6 +3172,21 @@ function intersection(left, right) {
3047
3172
  right
3048
3173
  });
3049
3174
  }
3175
+ const ZodRecord = /* @__PURE__ */ $constructor("ZodRecord", (inst, def) => {
3176
+ $ZodRecord.init(inst, def);
3177
+ ZodType.init(inst, def);
3178
+ inst._zod.processJSONSchema = (ctx, json, params) => recordProcessor(inst, ctx, json, params);
3179
+ inst.keyType = def.keyType;
3180
+ inst.valueType = def.valueType;
3181
+ });
3182
+ function record(keyType, valueType, params) {
3183
+ return new ZodRecord({
3184
+ type: "record",
3185
+ keyType,
3186
+ valueType,
3187
+ ...normalizeParams(params)
3188
+ });
3189
+ }
3050
3190
  const ZodEnum = /* @__PURE__ */ $constructor("ZodEnum", (inst, def) => {
3051
3191
  $ZodEnum.init(inst, def);
3052
3192
  ZodType.init(inst, def);
@@ -3299,6 +3439,7 @@ Fetch the full skill instructions with: prompts/get { name: "<skill-name>" }
3299
3439
  | what's emerging, find patterns in vault | vault-emerge |
3300
3440
  | find orphans, orphaned notes | vault-orphans |
3301
3441
  | trace idea, how did X evolve | vault-trace |
3442
+ | run a worker, check workers, worker providers | worker |
3302
3443
 
3303
3444
  ### Reference Resources
3304
3445
 
@@ -4377,6 +4518,70 @@ Otherwise the open items live only in a session note nobody re-reads, which is t
4377
4518
  `
4378
4519
  };
4379
4520
 
4521
+ //#endregion
4522
+ //#region src/daemon-mcp/prompts/worker.ts
4523
+ const worker = {
4524
+ description: "Run, watch and route subagent workers on configured providers",
4525
+ content: `## Worker Skill
4526
+
4527
+ USE WHEN delegating subagent work, checking on running workers, or managing worker providers/classes from chat.
4528
+
4529
+ ### Delegating
4530
+
4531
+ The Agent tool is denied when workers are on — delegate with Bash instead:
4532
+
4533
+ \`\`\`
4534
+ pai worker run --label "short task label" --class research \\
4535
+ -p '<full, self-contained task spec>' \\
4536
+ --allowedTools 'Read,Edit,Write,Bash,Grep,Glob' --output-format json
4537
+ \`\`\`
4538
+
4539
+ - Always run in background (run_in_background: true) and with a timeout.
4540
+ - Classes: draft (spec writing), plan, implement (default quality), review,
4541
+ research (web: add WebSearch,WebFetch), spotcheck (cheap/fast model), simple,
4542
+ complex, image. \`--role\` still works as an alias of \`--class\`.
4543
+ - Anything larger than a one-file change goes through a chain:
4544
+ \`pai worker run --chain draft,implement …\` — the draft stage turns the brief
4545
+ into a spec file, implement runs with it, \`--chain draft,implement,review\`
4546
+ adds a review pass. \`--class spotcheck\` for verification runs.
4547
+ - The answer is in the \`result\` field of the JSON it prints. Review the diff yourself.
4548
+ - \`--no-pane\` suppresses the iTerm follow pane; panes open automatically otherwise.
4549
+ - \`--agent <name>\` runs a definition from ~/.claude/agents/<name>.md (the agent
4550
+ library runs on workers: body becomes the system prompt, tools the allowlist,
4551
+ model the class).
4552
+
4553
+ ### Watching
4554
+
4555
+ - \`worker_ps\` — running + last finished workers (chains show as trees).
4556
+ - \`worker_replay\` with id — the transcript of one worker.
4557
+ - CLI equivalents: \`pai worker ps\`, \`pai worker follow <id>\`.
4558
+
4559
+ ### Routing
4560
+
4561
+ - \`worker_status\` — on/off, active provider, providers, run tally.
4562
+ - \`worker_providers\` — list/add/update/remove/use/enable/disable/test. Adding
4563
+ needs name, base_url, model and a key (key file path, or a raw key which is
4564
+ parked in ~/.config/pai/keys/<name>, mode 0600). update changes cost_tier
4565
+ (1 cheapest … 5 most expensive) and tags (code, vision, image-gen,
4566
+ long-context, fast, reasoning).
4567
+ - \`worker_classes\` — list/set/unset class → provider[/fast], or constraints
4568
+ only (max_cost_tier, require_tags) so auto-routing picks a qualifying provider.
4569
+ - \`worker_run\` — start a worker or chain from chat; returns the id immediately.
4570
+ - \`worker_toggle\` — off makes Agent subagents run on Anthropic again.
4571
+
4572
+ ### Preferences from chat (map the phrase, use the tool, never mention files)
4573
+
4574
+ - "use <provider> for image generation" / "route research to <provider>" →
4575
+ worker_classes set <class>=<provider>. One line back.
4576
+ - "prefer the flash model for simple tasks" / "cheap only for drafts" →
4577
+ worker_classes set simple|draft=<provider>/fast (or a max_cost_tier). One line.
4578
+ - "reviews should use a reasoning model" → worker_classes set review with
4579
+ require_tags ["reasoning"] (drop the pin if one exists). One line.
4580
+ - "what handles reviews" / "show the routing table" → worker_classes list
4581
+ (plus worker_status for providers). One or two lines.
4582
+ - Never tell the user to edit a config file — the tools do it.`
4583
+ };
4584
+
4380
4585
  //#endregion
4381
4586
  //#region src/daemon-mcp/resources/aesthetic.ts
4382
4587
  const aesthetic = {
@@ -5261,7 +5466,8 @@ async function startShim() {
5261
5466
  "whisper": whisper,
5262
5467
  "consolidate": consolidate,
5263
5468
  "advisor": advisor,
5264
- "tasks": tasks
5469
+ "tasks": tasks,
5470
+ "worker": worker
5265
5471
  };
5266
5472
  for (const [promptName, skill] of Object.entries(SKILL_PROMPTS)) server.prompt(promptName, skill.description, () => ({ messages: [{
5267
5473
  role: "user",
@@ -5521,6 +5727,348 @@ async function startShim() {
5521
5727
  top_k: number().int().min(1).max(100).optional().describe("Maximum KG triples to return after re-ranking. Default: 20."),
5522
5728
  tenant_id: string().optional().describe("Tenant ID for entity lookup scoping. Default: 'default'.")
5523
5729
  }, async (args) => proxyTool("memory_kg_search", args));
5730
+ const workerText = (s) => ({ content: [{
5731
+ type: "text",
5732
+ text: s
5733
+ }] });
5734
+ const workerError = (e) => ({
5735
+ content: [{
5736
+ type: "text",
5737
+ text: e instanceof Error ? e.message : String(e)
5738
+ }],
5739
+ isError: true
5740
+ });
5741
+ server.tool("worker_status", [
5742
+ "Show the worker system state: on/off, active provider, configured providers,",
5743
+ "roles, and today's run tally from the ledger.",
5744
+ "",
5745
+ "Use this before delegating with `pai worker run` to see what routing will choose."
5746
+ ].join("\n"), {}, async () => {
5747
+ try {
5748
+ const { workers } = readWorkersSection();
5749
+ const s = ledgerSummary(ledgerPath(workersLogDir(workers)), "all", 10);
5750
+ 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"];
5751
+ return workerText([
5752
+ `workers: ${workers.enabled ? "on" : "off"}`,
5753
+ `active provider: ${workers.active ?? "(none)"}`,
5754
+ ...describeProviders(workers),
5755
+ "",
5756
+ ...tally
5757
+ ].join("\n"));
5758
+ } catch (e) {
5759
+ return workerError(e);
5760
+ }
5761
+ });
5762
+ server.tool("worker_providers", [
5763
+ "Manage worker providers (list, add, update, remove, use, enable, disable, test).",
5764
+ "",
5765
+ "action=list (default) shows providers (with cost tier and tags), classes and routing.",
5766
+ "action=add needs name, model — and either key_file or key, plus:",
5767
+ " base_url (anthropic protocol) or upstream_url (protocol=openai, runs",
5768
+ " through the local PAI proxy). A raw key is written to",
5769
+ " ~/.config/pai/keys/<name> (mode 0600); only the path lands in the config.",
5770
+ " engine=codex runs the Codex CLI instead of Claude Code.",
5771
+ "action=update changes cost_tier / tags of an existing provider.",
5772
+ "action=test runs a one-word pong probe through the provider",
5773
+ " (reports 'codex not installed' when that engine's CLI is missing)."
5774
+ ].join("\n"), {
5775
+ action: _enum([
5776
+ "list",
5777
+ "add",
5778
+ "update",
5779
+ "remove",
5780
+ "use",
5781
+ "enable",
5782
+ "disable",
5783
+ "test"
5784
+ ]).optional().describe("Default: list."),
5785
+ name: string().optional().describe("Provider name (required for every action but list)."),
5786
+ base_url: string().optional().describe("Anthropic-compatible API base URL (add)."),
5787
+ upstream_url: string().optional().describe("Chat Completions base URL (add, protocol=openai)."),
5788
+ protocol: _enum(["anthropic", "openai"]).optional().describe("Protocol (add). Default: anthropic."),
5789
+ engine: _enum(["claude", "codex"]).optional().describe("Runner engine (add). Default: claude."),
5790
+ context_window: number().int().positive().optional().describe("Context window for the meter (add). Default: 200000."),
5791
+ key_file: string().optional().describe("File holding the API token, 0600 (add)."),
5792
+ key: string().optional().describe("Raw API token (add) — parked in ~/.config/pai/keys/<name>."),
5793
+ model: string().optional().describe("Default model id (add)."),
5794
+ fast_model: string().optional().describe("Cheaper model for spotchecks (add, optional)."),
5795
+ env: record(string(), string()).optional().describe("Extra env for runs (add, optional)."),
5796
+ note: string().optional().describe("Human note shown in listings (add, optional)."),
5797
+ quota_probe: string().optional().describe("URL whose JSON first number is the quota percent (add, optional)."),
5798
+ cost_tier: number().int().min(1).max(5).optional().describe("Cost tier 1 (cheapest) … 5 (most expensive). Default 3. (add, update)"),
5799
+ tags: array(_enum([
5800
+ "code",
5801
+ "vision",
5802
+ "image-gen",
5803
+ "long-context",
5804
+ "fast",
5805
+ "reasoning"
5806
+ ])).optional().describe("Capability tags (add, update).")
5807
+ }, async (args) => {
5808
+ try {
5809
+ const action = args.action ?? "list";
5810
+ if (action === "list") return workerText(describeProviders(readWorkersSection().workers).join("\n"));
5811
+ if (!args.name) return workerError(/* @__PURE__ */ new Error("name is required for this action"));
5812
+ if (action === "update") {
5813
+ if (args.cost_tier === void 0 && args.tags === void 0) return workerError(/* @__PURE__ */ new Error("update needs cost_tier and/or tags"));
5814
+ updateProvider(args.name, {
5815
+ ...args.cost_tier !== void 0 ? { costTier: args.cost_tier } : {},
5816
+ ...args.tags !== void 0 ? { tags: args.tags } : {}
5817
+ });
5818
+ return workerText([`provider ${args.name} updated`, ...describeProviders(readWorkersSection().workers)].join("\n"));
5819
+ }
5820
+ if (action === "add") {
5821
+ if (!args.model) return workerError(/* @__PURE__ */ new Error("add needs model"));
5822
+ if (args.protocol !== "openai" && !args.base_url) return workerError(/* @__PURE__ */ new Error("add needs base_url (only protocol=openai goes without it)"));
5823
+ const workers = addProvider({
5824
+ name: args.name,
5825
+ baseUrl: args.base_url ?? "",
5826
+ keyFile: args.key_file ?? null,
5827
+ ...args.key !== void 0 ? { key: args.key } : {},
5828
+ model: args.model,
5829
+ ...args.fast_model ? { fastModel: args.fast_model } : {},
5830
+ env: args.env ?? {},
5831
+ ...args.note ? { note: args.note } : {},
5832
+ ...args.protocol ? { protocol: args.protocol } : {},
5833
+ ...args.upstream_url ? { upstreamUrl: args.upstream_url } : {},
5834
+ ...args.engine ? { engine: args.engine } : {},
5835
+ ...args.context_window ? { contextWindow: args.context_window } : {},
5836
+ ...args.quota_probe ? { quotaProbe: args.quota_probe } : {},
5837
+ ...args.cost_tier !== void 0 ? { costTier: args.cost_tier } : {},
5838
+ ...args.tags?.length ? { tags: args.tags } : {}
5839
+ });
5840
+ return workerText([`provider ${args.name} added; active: ${workers.active ?? "(none)"}; workers ${workers.enabled ? "on" : "off"}`, ...describeProviders(workers)].join("\n"));
5841
+ }
5842
+ if (action === "remove") {
5843
+ removeProvider(args.name);
5844
+ return workerText(`provider ${args.name} removed`);
5845
+ }
5846
+ if (action === "use") {
5847
+ useProvider(args.name);
5848
+ return workerText(`active provider: ${args.name}`);
5849
+ }
5850
+ if (action === "enable" || action === "disable") {
5851
+ setProviderEnabled(args.name, action === "enable");
5852
+ return workerText(`provider ${args.name} ${action}d`);
5853
+ }
5854
+ const { workers } = readWorkersSection();
5855
+ const p = workers.providers[args.name];
5856
+ if (!p) return workerError(/* @__PURE__ */ new Error(`no provider named "${args.name}"`));
5857
+ const r = await testProvider(args.name, p, workersLogDir(workers));
5858
+ if (r.skipped) return workerText(`${r.provider} ${r.model} ${r.skipped}\nreply: ${r.result.slice(0, 200)}`);
5859
+ return workerText(`${r.provider} ${r.model} ${(r.latencyMs / 1e3).toFixed(1)}s\nreply: ${r.result.slice(0, 200)}\n${r.ok ? "OK" : "FAILED"}`);
5860
+ } catch (e) {
5861
+ return workerError(e);
5862
+ }
5863
+ });
5864
+ server.tool("worker_classes", [
5865
+ "Manage worker classes (list, set, unset).",
5866
+ "",
5867
+ "A class maps a task class to a provider, optionally its fast model:",
5868
+ "implement=glm, research=glm, spotcheck=glm/fast.",
5869
+ "set can instead give routing constraints only (max_cost_tier,",
5870
+ "require_tags) — auto-routing then picks a qualifying provider.",
5871
+ "Runs pick the provider via --class first, then the active provider."
5872
+ ].join("\n"), {
5873
+ action: _enum([
5874
+ "list",
5875
+ "set",
5876
+ "unset"
5877
+ ]).optional().describe("Default: list."),
5878
+ class: string().optional().describe("Class name (set/unset), e.g. implement, spotcheck."),
5879
+ target: string().optional().describe("Provider or provider/fast (set)."),
5880
+ max_cost_tier: number().int().min(1).max(5).optional().describe("Auto-routing considers only providers up to this cost tier (set)."),
5881
+ require_tags: array(_enum([
5882
+ "code",
5883
+ "vision",
5884
+ "image-gen",
5885
+ "long-context",
5886
+ "fast",
5887
+ "reasoning"
5888
+ ])).optional().describe("Auto-routing needs these tags (set).")
5889
+ }, async (args) => {
5890
+ try {
5891
+ const action = args.action ?? "list";
5892
+ if (action === "list") {
5893
+ const { workers } = readWorkersSection();
5894
+ const entries = Object.entries(workers.classes);
5895
+ return workerText(entries.length ? entries.map(([cls, t]) => `${cls}: ${classTargetText(t)}`).join("\n") : "no classes set");
5896
+ }
5897
+ if (!args.class) return workerError(/* @__PURE__ */ new Error("class is required for this action"));
5898
+ if (action === "set") {
5899
+ const hasConstraints = args.max_cost_tier !== void 0 || args.require_tags !== void 0;
5900
+ if (!args.target && !hasConstraints) return workerError(/* @__PURE__ */ new Error("set needs target provider[/fast] and/or max_cost_tier/require_tags"));
5901
+ const target = args.target !== void 0 && !hasConstraints ? args.target : {
5902
+ ...args.target ? { provider: args.target.split("/")[0] } : {},
5903
+ ...args.max_cost_tier !== void 0 ? { maxCostTier: args.max_cost_tier } : {},
5904
+ ...args.require_tags !== void 0 ? { requireTags: args.require_tags } : {}
5905
+ };
5906
+ setClass(args.class, target);
5907
+ return workerText(`class ${args.class} → ${classTargetText(target)}`);
5908
+ }
5909
+ unsetClass(args.class);
5910
+ return workerText(`class ${args.class} removed`);
5911
+ } catch (e) {
5912
+ return workerError(e);
5913
+ }
5914
+ });
5915
+ server.tool("worker_run", [
5916
+ "Start a worker (or a chain) and return its id immediately.",
5917
+ "",
5918
+ "This is the chat-side face of `pai worker run`: prompt is required,",
5919
+ "chain (e.g. \"draft,implement\") runs spec-first stages, class picks the",
5920
+ "provider (default: active). Check on it with worker_ps / worker_replay,",
5921
+ "talk to it with worker_say."
5922
+ ].join("\n"), {
5923
+ prompt: string().min(1).describe("The task (the -p value)."),
5924
+ chain: string().optional().describe("Comma-separated stages, e.g. draft,implement or draft,implement,review."),
5925
+ class: string().optional().describe("Task class (draft, implement, review, research, spotcheck, simple, complex, image)."),
5926
+ label: string().optional().describe("Short task label shown in worker_ps."),
5927
+ cwd: string().optional().describe("Working directory (default: here)."),
5928
+ allowed_tools: string().optional().describe("Comma-separated tool allowlist passed to the worker."),
5929
+ mcp: string().optional().describe("MCP servers/sets the worker may use (comma-separated).")
5930
+ }, async (args) => {
5931
+ try {
5932
+ const claudeArgs = [
5933
+ "-p",
5934
+ args.prompt,
5935
+ ...args.allowed_tools ? ["--allowedTools", args.allowed_tools] : []
5936
+ ];
5937
+ let startedIdResolve;
5938
+ let startupFailure = null;
5939
+ const idArrived = new Promise((resolve) => {
5940
+ startedIdResolve = resolve;
5941
+ });
5942
+ const background = async () => {
5943
+ if (args.chain) return runChain({
5944
+ stages: args.chain.split(","),
5945
+ className: args.class,
5946
+ label: args.label,
5947
+ noPane: false,
5948
+ mcpFlag: args.mcp,
5949
+ brief: args.prompt,
5950
+ claudeArgs,
5951
+ ...args.cwd ? { cwd: args.cwd } : {},
5952
+ onChainStart: (id) => startedIdResolve(id),
5953
+ quiet: true
5954
+ });
5955
+ return runWorker({
5956
+ className: args.class,
5957
+ label: args.label,
5958
+ noPane: false,
5959
+ mcpFlag: args.mcp,
5960
+ claudeArgs,
5961
+ ...args.cwd ? { cwd: args.cwd } : {},
5962
+ onWorkerStart: (wid) => startedIdResolve(wid),
5963
+ quiet: true
5964
+ });
5965
+ };
5966
+ background().catch((e) => {
5967
+ startupFailure = e;
5968
+ startedIdResolve("");
5969
+ });
5970
+ const id = await Promise.race([idArrived, new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("worker did not start within 15s")), 15e3))]);
5971
+ if (!id) return workerError(startupFailure ?? /* @__PURE__ */ new Error("worker failed to start"));
5972
+ return workerText(`${id} started${args.chain ? ` (chain: ${args.chain})` : ""} — check on it with worker_ps, worker_replay, worker_say`);
5973
+ } catch (e) {
5974
+ return workerError(e);
5975
+ }
5976
+ });
5977
+ server.tool("worker_toggle", [
5978
+ "Turn worker routing on or off.",
5979
+ "",
5980
+ "off: the Agent-tool hook stops denying, subagents run on Anthropic.",
5981
+ "on: subagents are denied and rewritten to `pai worker run`."
5982
+ ].join("\n"), { enabled: boolean().describe("true = route subagents to workers, false = Anthropic.") }, async (args) => {
5983
+ try {
5984
+ const workers = setWorkersEnabled(args.enabled);
5985
+ return workerText(`workers ${workers.enabled ? "on" : "off"} — Agent subagents ${workers.enabled ? "denied and rewritten to `pai worker run`" : "run on Anthropic"}`);
5986
+ } catch (e) {
5987
+ return workerError(e);
5988
+ }
5989
+ });
5990
+ server.tool("worker_ps", [
5991
+ "List workers: running (id, provider, age, turns, current tool) and the last finished.",
5992
+ "",
5993
+ "Use this to check on delegated `pai worker run` calls; worker_replay shows",
5994
+ "the transcript of one worker."
5995
+ ].join("\n"), { all: boolean().optional().describe("Show workers of all sessions (default: this one's).") }, async (args) => {
5996
+ try {
5997
+ const { workers } = readWorkersSection();
5998
+ return workerText(psOutput(workersLogDir(workers), args.all === true, {}, false));
5999
+ } catch (e) {
6000
+ return workerError(e);
6001
+ }
6002
+ });
6003
+ server.tool("worker_replay", [
6004
+ "Replay the transcript of one worker: tool calls, short outputs, result.",
6005
+ "",
6006
+ "Plain text, no colors. Use tail to cap the output."
6007
+ ].join("\n"), {
6008
+ id: string().describe("Worker id (as shown by worker_ps)."),
6009
+ tail: number().int().min(1).max(2e3).optional().describe("Last N rendered lines. Default: all.")
6010
+ }, async (args) => {
6011
+ try {
6012
+ const { workers } = readWorkersSection();
6013
+ return workerText(replayOutput(workersLogDir(workers), args.id, false, args.tail));
6014
+ } catch (e) {
6015
+ return workerError(e);
6016
+ }
6017
+ });
6018
+ server.tool("worker_say", [
6019
+ "Send one message to a RUNNING worker: it lands on the worker's open stdin",
6020
+ "as a user message, mid-run, without breaking its stream.",
6021
+ "",
6022
+ "Fails with an explanation when the worker already finished — then use",
6023
+ "worker_resume (or `pai worker resume <id> \"text\"`) instead."
6024
+ ].join("\n"), {
6025
+ id: string().describe("Worker id (as shown by worker_ps)."),
6026
+ text: string().min(1).describe("The message to send (one line).")
6027
+ }, async (args) => {
6028
+ try {
6029
+ const { workers } = readWorkersSection();
6030
+ await sayToWorker(workersLogDir(workers), args.id, args.text);
6031
+ return workerText(`sent to ${args.id}`);
6032
+ } catch (e) {
6033
+ return workerError(e);
6034
+ }
6035
+ });
6036
+ server.tool("worker_resume", [
6037
+ "Continue a FINISHED worker with a follow-up message: claude --resume on the",
6038
+ "same provider, same Claude session, context intact.",
6039
+ "",
6040
+ "Returns the new worker id. Not for running workers — say to those instead."
6041
+ ].join("\n"), {
6042
+ id: string().describe("Worker id of the finished run (as shown by worker_ps)."),
6043
+ text: string().min(1).describe("The follow-up message.")
6044
+ }, async (args) => {
6045
+ try {
6046
+ const { workers } = readWorkersSection();
6047
+ const old = loadStatus(workersLogDir(workers), args.id);
6048
+ if (!old) return workerError(/* @__PURE__ */ new Error(`no worker named "${args.id}"`));
6049
+ if (old.state === "running") return workerError(/* @__PURE__ */ new Error(`worker ${args.id} is still running — send messages with worker_say`));
6050
+ if (!old.claudeSession) return workerError(/* @__PURE__ */ new Error(`worker ${args.id} recorded no Claude session id — it predates resume support or ran through an engine that does not expose one`));
6051
+ let newId = "";
6052
+ const rc = await runWorker({
6053
+ providerFlag: old.provider,
6054
+ modelFlag: old.model,
6055
+ label: `↩ ${old.label}`,
6056
+ noPane: true,
6057
+ claudeArgs: [
6058
+ "--resume",
6059
+ old.claudeSession,
6060
+ "-p",
6061
+ args.text
6062
+ ],
6063
+ onWorkerStart: (wid) => {
6064
+ newId = wid;
6065
+ }
6066
+ });
6067
+ return workerText(`resumed ${args.id} as ${newId || "(unknown id)"} — rc=${rc}\ncheck on it with worker_ps / worker_replay`);
6068
+ } catch (e) {
6069
+ return workerError(e);
6070
+ }
6071
+ });
5524
6072
  const transport = new StdioServerTransport();
5525
6073
  await server.connect(transport);
5526
6074
  }