@desplega.ai/agent-swarm 1.158.0 → 1.159.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 (272) hide show
  1. package/dist/{acp-adapter-xaq4n6pc.js → acp-adapter-qhch0n2v.js} +1 -1
  2. package/dist/{actions-p9hz5yat.js → actions-dtsh1mfb.js} +33 -29
  3. package/dist/{anthropic-messages-51pz26qe.js → anthropic-messages-f2h06k64.js} +8 -3
  4. package/dist/{app-g26wdcz6.js → app-y1b9srva.js} +10 -9
  5. package/dist/{assistant-wydfqkb3.js → assistant-31hwy5xj.js} +34 -30
  6. package/dist/{azure-openai-responses-8gksqtmz.js → azure-openai-responses-0k1mfax5.js} +12 -13
  7. package/dist/{boot-reembed-se8zmdcp.js → boot-reembed-48d2qpkt.js} +11 -11
  8. package/dist/{boot-reembed-g5ckg9yb.js → boot-reembed-w0bryhkr.js} +12 -12
  9. package/dist/{boot-scrub-logs-wfxqan61.js → boot-scrub-logs-t65wbrn7.js} +9 -8
  10. package/dist/{boot-seeds-s3q01xj3.js → boot-seeds-h7c32hvm.js} +12 -11
  11. package/dist/{claude-adapter-tv2x4f36.js → claude-adapter-1t6611ne.js} +26 -21
  12. package/dist/{claude-managed-adapter-41b2s7fd.js → claude-managed-adapter-werp659j.js} +4 -2
  13. package/dist/{claude-managed-setup-60g8aazx.js → claude-managed-setup-y6rdgbc1.js} +11 -2
  14. package/dist/claude-sdk-session-zhkyk8yw.js +29319 -0
  15. package/dist/{cli-vj6h9kc2.js → cli-0m7wct0d.js} +59 -1
  16. package/dist/{cli-wscdvcqz.js → cli-0n2e4x49.js} +1 -1
  17. package/dist/cli-0xqde53b.js +176 -0
  18. package/dist/{cli-xberxa1r.js → cli-10ym38qc.js} +2 -2
  19. package/dist/{cli-5b6b62tz.js → cli-13hw4z91.js} +1 -1
  20. package/dist/{cli-rqz8qbw7.js → cli-20035trh.js} +50 -18
  21. package/dist/{cli-k0g1sjzm.js → cli-31nkw3g3.js} +3 -3
  22. package/dist/cli-3gvrr8vj.js +1422 -0
  23. package/dist/{cli-c07r9a2m.js → cli-48527fpw.js} +9 -8
  24. package/dist/{cli-24k806sc.js → cli-4aj25szq.js} +2 -2
  25. package/dist/{cli-7gtvvpbh.js → cli-4tvgz99m.js} +7 -7
  26. package/dist/cli-503ya656.js +455 -0
  27. package/dist/{cli-cw9zck8j.js → cli-5f2hq128.js} +8 -4
  28. package/dist/{cli-s8eet254.js → cli-68bmme5p.js} +11078 -27833
  29. package/dist/{cli-ftfvggzx.js → cli-79aq9cgb.js} +794 -563
  30. package/dist/cli-7cxw8b60.js +14910 -0
  31. package/dist/cli-7kyk9jr3.js +22 -0
  32. package/dist/cli-7zttzetj.js +324 -0
  33. package/dist/{cli-y95wp3tg.js → cli-96qd5psa.js} +1 -1
  34. package/dist/{cli-g8djp6qe.js → cli-9yg65c1j.js} +22 -0
  35. package/dist/{cli-3gmcwfyx.js → cli-ardpzhjm.js} +5 -5
  36. package/dist/{cli-7s3syk22.js → cli-b6thtzac.js} +5 -5
  37. package/dist/{cli-00w2c66p.js → cli-bgv70kym.js} +13 -2
  38. package/dist/{cli-j55n64b6.js → cli-bv0a38v9.js} +163 -44
  39. package/dist/cli-ca2w7zv7.js +115 -0
  40. package/dist/cli-cpvsaqsw.js +333 -0
  41. package/dist/cli-cygyqj3b.js +754 -0
  42. package/dist/{cli-x24pshtq.js → cli-dvjpkkjd.js} +6 -6
  43. package/dist/{cli-5d10vd2m.js → cli-e0ndxhtk.js} +4 -4
  44. package/dist/{cli-4198qc9v.js → cli-e4xnc0qp.js} +15 -3
  45. package/dist/{cli-rjehvrts.js → cli-eg7cdg68.js} +8 -8
  46. package/dist/cli-ejpygnqz.js +3815 -0
  47. package/dist/{cli-fgqxwrpv.js → cli-fjtp3463.js} +1631 -127
  48. package/dist/{cli-zt1227k1.js → cli-fkqhbg8g.js} +5 -3
  49. package/dist/{cli-47m6dkd3.js → cli-g9df2m37.js} +409 -168
  50. package/dist/cli-gzyh0j0g.js +8455 -0
  51. package/dist/cli-hgqfttfc.js +183 -0
  52. package/dist/cli-hj65wsts.js +17755 -0
  53. package/dist/cli-j9z8mfx3.js +476 -0
  54. package/dist/{cli-aehm1rbq.js → cli-kc3dp84d.js} +1135 -61
  55. package/dist/{cli-h38x0qhg.js → cli-kdnehe0t.js} +4 -3
  56. package/dist/{cli-20cj37vt.js → cli-m4excttz.js} +136 -2216
  57. package/dist/{cli-8qmme6vd.js → cli-mg24hkt4.js} +2 -2
  58. package/dist/{cli-xhaeg2nc.js → cli-mzkqpxee.js} +1 -1
  59. package/dist/{cli-q67y4wzm.js → cli-phsaj4xq.js} +24 -2
  60. package/dist/{cli-pw9rj5t9.js → cli-pqetb83f.js} +1 -176
  61. package/dist/{cli-rj19hc34.js → cli-qjxy52w7.js} +1 -1
  62. package/dist/{cli-53sz8gkz.js → cli-qv4jv8bx.js} +1 -1
  63. package/dist/cli-qvnnx4ct.js +1051 -0
  64. package/dist/cli-rb9m6rpe.js +4980 -0
  65. package/dist/{cli-tt9k8r35.js → cli-rfezr056.js} +476 -909
  66. package/dist/{cli-ggm92wcn.js → cli-rgjprct3.js} +1 -0
  67. package/dist/{cli-9httdpyq.js → cli-rrqmqr1s.js} +1 -1
  68. package/dist/cli-rykfmgwg.js +102 -0
  69. package/dist/{cli-eetrvv91.js → cli-t3p77qk1.js} +1 -1
  70. package/dist/{cli-68w09sjw.js → cli-vn0qv9cj.js} +25 -88
  71. package/dist/{cli-987hg0aa.js → cli-vwbbaecp.js} +2 -2
  72. package/dist/cli-wd9hz40d.js +69 -0
  73. package/dist/{cli-j57qkrpq.js → cli-wgjx0q33.js} +13 -13
  74. package/dist/cli-xgpf00sk.js +2149 -0
  75. package/dist/{cli-mcnhgnn1.js → cli-ykk5jaax.js} +184 -26
  76. package/dist/{cli-aky8tctm.js → cli-z9x0sh1t.js} +3 -3
  77. package/dist/{cli-1hkrch3k.js → cli-zw65mab6.js} +5 -5
  78. package/dist/cli.js +38 -29
  79. package/dist/cloudflare-workers-ai-system-one-1sg586qv.js +46 -0
  80. package/dist/{codex-adapter-cnm8wk11.js → codex-adapter-jqfqqa21.js} +21 -14
  81. package/dist/{codex-hook-9hptmdhj.js → codex-hook-5eys3dya.js} +2 -2
  82. package/dist/{codex-session-runner-gfg547zj.js → codex-session-runner-zd341dsm.js} +21 -14
  83. package/dist/{commands-e3bn0p75.js → commands-vfsysz4s.js} +9 -8
  84. package/dist/{db-03v9kzr2.js → db-s3znhvbn.js} +15 -10
  85. package/dist/{dispatcher-nrmg8yht.js → dispatcher-5xn0c7rf.js} +32 -28
  86. package/dist/{dsh-adapter-k03nfqa6.js → dsh-adapter-894mgfvs.js} +3 -3
  87. package/dist/{e2b-93x1k0j5.js → e2b-ez07znam.js} +4 -3
  88. package/dist/execute-71ysgv35.js +965 -0
  89. package/dist/{google-generative-ai-bxmv50nc.js → google-generative-ai-1nfcdphp.js} +5 -4
  90. package/dist/{google-vertex-809af514.js → google-vertex-1tqcd7b1.js} +5 -4
  91. package/dist/{handlers-aqcedbyb.js → handlers-5w4jwycr.js} +35 -31
  92. package/dist/{hook-7vkskkre.js → hook-853bxrr6.js} +16 -13
  93. package/dist/{hook-ybvmrmhq.js → hook-z0hbxj85.js} +16 -13
  94. package/dist/{http-2eqa8t24.js → http-akc3wy3m.js} +600 -123
  95. package/dist/{index-f6cryxwe.js → index-4a5njnm1.js} +2 -59
  96. package/dist/{index-a3vn96n8.js → index-5sz16ksh.js} +187 -47
  97. package/dist/{index-c6zsv1bd.js → index-bjmjh9h4.js} +11 -11
  98. package/dist/{index-chgdkq36.js → index-cd151qkq.js} +32 -28
  99. package/dist/{index-x71bvq0p.js → index-k661dzzr.js} +33 -29
  100. package/dist/{index-1yks3qcr.js → index-ttpbhnjg.js} +32 -28
  101. package/dist/{keepalive-vtkf5n99.js → keepalive-8p9rpvz5.js} +11 -10
  102. package/dist/{lead-h3sp357x.js → lead-jp69z5h3.js} +49 -45
  103. package/dist/llama-cpp-classify-t2tccams.js +335 -0
  104. package/dist/{maintenance-jzeqtk3q.js → maintenance-fwx4ev0k.js} +12 -12
  105. package/dist/{mistral-conversations-zx0kn554.js → mistral-conversations-bhvwqcnk.js} +14 -17
  106. package/dist/{oauth-refresh-sweep-qp3dzmdp.js → oauth-refresh-sweep-cwcq1ctx.js} +10 -9
  107. package/dist/{onboard-tmsp0qbz.js → onboard-2b4q0t84.js} +6 -5
  108. package/dist/{openai-codex-responses-4vjqzvjp.js → openai-codex-responses-nekthgks.js} +21 -8
  109. package/dist/{openai-completions-9hjrqbw8.js → openai-completions-hxtzwj0s.js} +8 -11
  110. package/dist/{openai-responses-7b1pn0xt.js → openai-responses-w4r118w7.js} +22 -17
  111. package/dist/{opencode-adapter-x6x2da1z.js → opencode-adapter-zz7xvjj6.js} +6 -4
  112. package/dist/{openrouter-images-9f9ek4rw.js → openrouter-images-b6sqqjgn.js} +4 -6
  113. package/dist/{otel-impl-9mz73q65.js → otel-impl-g8rs4vwr.js} +1 -1
  114. package/dist/{pi-messages-txpjj39q.js → pi-messages-vxdt8v7j.js} +2 -1
  115. package/dist/{pi-mono-adapter-m6dvv0dz.js → pi-mono-adapter-n0me28wt.js} +29 -17
  116. package/dist/pricing-refresh-0v77385v.js +39 -0
  117. package/dist/{rbac-roles-c9wqz83x.js → rbac-roles-krjyazbj.js} +10 -9
  118. package/dist/{render-v2-gryrh9h8.js → render-v2-3wjsp9aw.js} +10 -9
  119. package/dist/runtime-ktjysacx.js +24 -0
  120. package/dist/{setup-q9720bt6.js → setup-yeqryke6.js} +6 -5
  121. package/dist/typesafe-system-one-96960jxp.js +27 -0
  122. package/dist/{virtual-modules-3htpgn8x.js → virtual-modules-dzdeq1sf.js} +21 -13
  123. package/dist/{work-objects-b76x7deb.js → work-objects-3pakpkw0.js} +35 -31
  124. package/dist/{worker-6vrxbv41.js → worker-85testgj.js} +49 -45
  125. package/openapi.json +863 -14
  126. package/package.json +9 -8
  127. package/src/be/db/agents.ts +20 -0
  128. package/src/be/db/tasks/human-free.ts +442 -0
  129. package/src/be/db/tasks/read.ts +56 -1
  130. package/src/be/db/tasks/write.ts +107 -11
  131. package/src/be/db.ts +89 -165
  132. package/src/be/harness-model-support.ts +182 -0
  133. package/src/be/human-free-drain.ts +67 -0
  134. package/src/be/migrations/182_task_human_free_flag.sql +63 -0
  135. package/src/be/migrations/183_human_free_reclassify_queue.sql +25 -0
  136. package/src/be/migrations/184_model_catalog.sql +120 -0
  137. package/src/be/model-catalog-store.ts +644 -0
  138. package/src/be/model-tier-keys.ts +31 -0
  139. package/src/be/model-tier-resolution.ts +489 -0
  140. package/src/be/model-validation.ts +112 -0
  141. package/src/be/models-catalog.ts +61 -7
  142. package/src/be/modelsdev-cache.json +82 -0
  143. package/src/be/modelsdev-cache.ts +2 -0
  144. package/src/be/pricing-refresh.ts +154 -12
  145. package/src/be/scripts/typecheck.ts +15 -0
  146. package/src/be/seed-scripts/catalog/task-failure-audit.ts +61 -9
  147. package/src/be/swarm-config-guard.ts +6 -0
  148. package/src/cli.tsx +10 -0
  149. package/src/commands/claude-managed-setup.ts +3 -1
  150. package/src/commands/runner.ts +76 -2
  151. package/src/extensions/contract-types.generated.ts +1 -1
  152. package/src/heartbeat/heartbeat.ts +64 -15
  153. package/src/heartbeat/templates.ts +2 -2
  154. package/src/http/agents.ts +49 -1
  155. package/src/http/index.ts +8 -0
  156. package/src/http/models-catalog.ts +357 -8
  157. package/src/http/poll.ts +45 -2
  158. package/src/http/schedules.ts +36 -6
  159. package/src/http/session-data.ts +2 -5
  160. package/src/http/stats.ts +6 -8
  161. package/src/http/tasks.ts +26 -1
  162. package/src/http/usage-cache.ts +56 -15
  163. package/src/http/workflows.ts +35 -8
  164. package/src/prompts/session-templates.ts +1 -0
  165. package/src/providers/claude-managed-models.ts +47 -161
  166. package/src/providers/codex-adapter.ts +4 -4
  167. package/src/providers/codex-models.ts +65 -198
  168. package/src/providers/reasoning-effort.ts +44 -140
  169. package/src/rbac/legacy-policy.ts +25 -0
  170. package/src/rbac/permissions.ts +10 -0
  171. package/src/scripts-runtime/sdk-allowlist.ts +4 -0
  172. package/src/scripts-runtime/types/stdlib.d.ts +15 -0
  173. package/src/scripts-runtime/types/swarm-extension.d.ts +3 -0
  174. package/src/scripts-runtime/types/swarm-sdk.d.ts +15 -0
  175. package/src/server.ts +6 -0
  176. package/src/slack/message-text.ts +56 -2
  177. package/src/slack/thread-buffer.ts +10 -2
  178. package/src/tests/agents-list-model-display.test.ts +4 -6
  179. package/src/tests/base-prompt.test.ts +3 -0
  180. package/src/tests/claude-adapter.test.ts +10 -0
  181. package/src/tests/claude-managed-adapter.test.ts +12 -18
  182. package/src/tests/codex-adapter.test.ts +2 -1
  183. package/src/tests/codex-gpt6-release.test.ts +66 -5
  184. package/src/tests/context-window.test.ts +7 -5
  185. package/src/tests/db-extraction-contract.test.ts +1 -0
  186. package/src/tests/db-task-read-contract.test.ts +5 -1
  187. package/src/tests/db-task-write-contract.test.ts +4 -1
  188. package/src/tests/docs-index-footprint.test.js +3 -3
  189. package/src/tests/explicit-model-validation.test.ts +332 -0
  190. package/src/tests/harness-model-support.test.ts +374 -0
  191. package/src/tests/heartbeat-checklist-config.test.ts +71 -0
  192. package/src/tests/heartbeat-checklist.test.ts +4 -2
  193. package/src/tests/heartbeat-orphan-resume.test.ts +48 -0
  194. package/src/tests/human-free-flag.test.ts +279 -0
  195. package/src/tests/human-free-oracle.ts +57 -0
  196. package/src/tests/human-free-reclassify.test.ts +669 -0
  197. package/src/tests/migration-046-budgets.test.ts +3 -3
  198. package/src/tests/model-catalog-migration.test.ts +134 -0
  199. package/src/tests/model-catalog-table.test.ts +249 -0
  200. package/src/tests/model-catalog-write-access.test.ts +387 -0
  201. package/src/tests/model-tier-resolution.test.ts +519 -0
  202. package/src/tests/models-catalog.test.ts +1 -1
  203. package/src/tests/multi-runtime-enabled-stats.test.ts +65 -1
  204. package/src/tests/package-publish.test.ts +137 -36
  205. package/src/tests/pricing-routes.test.ts +2 -2
  206. package/src/tests/rbac-engine.test.ts +42 -2
  207. package/src/tests/reasoning-effort.test.ts +109 -0
  208. package/src/tests/refresh-modelsdev-carryforward.test.ts +5 -5
  209. package/src/tests/runtime-model-catalog.test.ts +86 -0
  210. package/src/tests/script-connections.test.ts +5 -0
  211. package/src/tests/seed-scripts.test.ts +89 -0
  212. package/src/tests/session-costs.test.ts +14 -11
  213. package/src/tests/slack-message-text.test.ts +101 -0
  214. package/src/tests/slack-thread-buffer-context.test.ts +93 -0
  215. package/src/tests/task-cascade-fail.test.ts +148 -2
  216. package/src/tests/tool-annotations.test.ts +1 -1
  217. package/src/tests/usage-cache.test.ts +166 -0
  218. package/src/tests/workflow-executors.test.ts +5 -2
  219. package/src/tests/workflow-system-one-decision-review.test.ts +1021 -0
  220. package/src/tests/workflow-system-one-decision-save-warnings.test.ts +365 -0
  221. package/src/tests/workflow-system-one-decision.test.ts +2063 -0
  222. package/src/tools/model-catalog/index.ts +137 -0
  223. package/src/tools/schedules/create-schedule.ts +16 -1
  224. package/src/tools/schedules/patch-schedule.ts +17 -0
  225. package/src/tools/schedules/update-schedule.ts +17 -0
  226. package/src/tools/send-task.ts +14 -0
  227. package/src/tools/task-action.ts +12 -0
  228. package/src/tools/tool-config.ts +3 -1
  229. package/src/tools/workflows/create-workflow.ts +9 -2
  230. package/src/tools/workflows/patch-workflow-node.ts +17 -7
  231. package/src/tools/workflows/patch-workflow.ts +9 -2
  232. package/src/tools/workflows/update-workflow.ts +9 -2
  233. package/src/types.ts +30 -1
  234. package/src/utils/context-window.ts +47 -53
  235. package/src/utils/harness-cli-version.ts +115 -0
  236. package/src/utils/harness-model-error.ts +33 -0
  237. package/src/utils/runtime-model-catalog.ts +137 -0
  238. package/src/workflows/approval-resolution.ts +39 -0
  239. package/src/workflows/definition.ts +7 -0
  240. package/src/workflows/engine.ts +69 -1
  241. package/src/workflows/executors/agent-task.ts +9 -0
  242. package/src/workflows/executors/base.ts +53 -0
  243. package/src/workflows/executors/human-in-the-loop.ts +8 -8
  244. package/src/workflows/executors/index.ts +1 -0
  245. package/src/workflows/executors/registry.ts +2 -0
  246. package/src/workflows/executors/system-one-decision.ts +1232 -0
  247. package/src/workflows/executors/system-one-providers.ts +301 -0
  248. package/src/workflows/executors/workflow-llm.ts +13 -1
  249. package/src/workflows/readiness.ts +86 -0
  250. package/src/workflows/recovery.ts +12 -12
  251. package/src/workflows/resume.ts +23 -9
  252. package/templates/schedules/daily-blocker-digest/content.md +4 -5
  253. package/templates/skills/agent-browser/SKILL.md +6 -5
  254. package/templates/skills/agent-browser/content.md +6 -5
  255. package/templates/skills/heartbeat-runbook/config.json +1 -1
  256. package/templates/skills/heartbeat-runbook/content.md +2 -2
  257. package/templates/skills/swarm-scripts/SKILL.md +1 -1
  258. package/templates/skills/swarm-scripts/content.md +1 -1
  259. package/templates/skills/workflow-iterate/content.md +81 -1
  260. package/templates/workflows/claude-code-changelog-watch/content.md +2 -0
  261. package/templates/workflows/pr-review-status-sweep/content.md +2 -0
  262. package/dist/claude-sdk-session-t442qz6f.js +0 -39108
  263. package/dist/cli-0xjkj1j7.js +0 -61
  264. package/dist/cli-65hjmev5.js +0 -1141
  265. package/dist/cli-aw1jwp1r.js +0 -14075
  266. package/dist/cli-h967nb6r.js +0 -6603
  267. package/dist/cli-vapyk8sw.js +0 -20
  268. package/dist/cli-ws6rqycm.js +0 -247
  269. package/dist/pricing-refresh-v1cbr9te.js +0 -27
  270. package/src/providers/modelsdev-reasoning.json +0 -6453
  271. package/dist/{codex-login-x3jk4etn.js → codex-login-he9vrn90.js} +3 -3
  272. package/dist/{index-f24115ge.js → index-2wsdsnmm.js} +3 -3
@@ -0,0 +1,137 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import * as z from "zod";
3
+ import { getAgentById } from "@/be/db";
4
+ import { upsertModelCatalogOverlay } from "@/be/model-catalog-store";
5
+ import { requestModelCatalogRefresh } from "@/be/pricing-refresh";
6
+ import { can } from "@/rbac";
7
+ import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
8
+
9
+ async function denyUnlessCatalogWriter(agentId: string): Promise<string | null> {
10
+ const agent = await getAgentById(agentId);
11
+ const decision = can({
12
+ principal: { kind: "agent", agentId, isLead: agent?.isLead ?? false },
13
+ verb: "models.catalog.write",
14
+ resource: { kind: "none" },
15
+ source: "mcp",
16
+ });
17
+ return decision.allow ? null : "Writing the model catalog requires the lead agent.";
18
+ }
19
+
20
+ export const registerModelCatalogRefreshTool = (server: McpServer) => {
21
+ createToolRegistrar(server)(
22
+ "model-catalog-refresh",
23
+ {
24
+ title: "Refresh Model Catalog",
25
+ description:
26
+ "Refresh the swarm model catalog (and pricing rows) from models.dev, like `pi update --models`. Without force, skips the network when the last check is under 4h old. A forced refresh is accepted once per minute; a second one inside that window returns `skipped-cooldown` with `retryAfterMs`. Lead agent only. Returns the status, model count, and newly added provider/modelId keys.",
27
+ annotations: { idempotentHint: true, openWorldHint: true },
28
+ inputSchema: z.object({
29
+ force: z
30
+ .boolean()
31
+ .optional()
32
+ .describe(
33
+ "Fetch even if the last check is under 4h old (default false). Rate limited to one per minute.",
34
+ ),
35
+ }),
36
+ outputSchema: swarmToolOutputSchema({
37
+ status: z.string().optional(),
38
+ models: z.number().optional(),
39
+ added: z.array(z.string()).optional(),
40
+ checkedAt: z.number().nullable().optional(),
41
+ retryAfterMs: z.number().optional(),
42
+ }),
43
+ },
44
+ async ({ force }, requestInfo) => {
45
+ if (!requestInfo.agentId) {
46
+ return toolErr('Agent ID not found. Set the "X-Agent-ID" header.');
47
+ }
48
+ const denied = await denyUnlessCatalogWriter(requestInfo.agentId);
49
+ if (denied) return toolErr(denied);
50
+
51
+ const result = await requestModelCatalogRefresh({ force });
52
+ const data = {
53
+ status: result.status,
54
+ models: result.models,
55
+ added: result.added,
56
+ checkedAt: result.checkedAt,
57
+ retryAfterMs: result.retryAfterMs,
58
+ };
59
+ if (result.status === "error") {
60
+ return toolErr(`Model catalog refresh failed: ${result.error ?? "unknown error"}`, {
61
+ data,
62
+ });
63
+ }
64
+ const addedText = result.added.length > 0 ? ` Added: ${result.added.join(", ")}.` : "";
65
+ return toolOk(
66
+ `Model catalog ${result.status}: ${result.models} model(s), ${result.added.length} new.`,
67
+ {
68
+ details: `status=${result.status}; models=${result.models}; added=${result.added.length}.${addedText}`,
69
+ data,
70
+ },
71
+ );
72
+ },
73
+ );
74
+ };
75
+
76
+ export const registerModelCatalogOverlayUpsertTool = (server: McpServer) => {
77
+ createToolRegistrar(server)(
78
+ "model-catalog-overlay-upsert",
79
+ {
80
+ title: "Upsert Model Catalog Overlay",
81
+ description:
82
+ "Add or update hand-verified facts for one model (e.g. a launch models.dev has not listed yet). Overlay fields win over models.dev; overlay prices fill pricing-table gaps so cost recompute prices the model. The row auto-expires once models.dev matches every fact you set.",
83
+ annotations: { idempotentHint: true },
84
+ inputSchema: z.object({
85
+ provider: z
86
+ .string()
87
+ .min(1)
88
+ .describe(
89
+ "models.dev provider id: anthropic, openai, openrouter, amazon-bedrock, opencode.",
90
+ ),
91
+ modelId: z
92
+ .string()
93
+ .min(1)
94
+ .describe("Model id as models.dev keys it (e.g. claude-opus-5-5)."),
95
+ name: z.string().optional().describe("Display name."),
96
+ releaseDate: z.string().optional().describe("Release date, YYYY-MM-DD."),
97
+ contextWindow: z.number().int().positive().optional().describe("Context window, tokens."),
98
+ maxOutput: z.number().int().positive().optional().describe("Max output tokens."),
99
+ reasoningOptions: z
100
+ .array(z.object({ type: z.string(), values: z.array(z.string()).optional() }))
101
+ .optional()
102
+ .describe("Reasoning options, models.dev shape: [{type, values?}]."),
103
+ pricing: z
104
+ .object({
105
+ input: z.number().nonnegative().optional(),
106
+ output: z.number().nonnegative().optional(),
107
+ cache_read: z.number().nonnegative().optional(),
108
+ cache_write: z.number().nonnegative().optional(),
109
+ })
110
+ .optional()
111
+ .describe("USD per million tokens."),
112
+ reason: z.string().min(1).describe("Why this overlay exists (source of the facts)."),
113
+ verifiedBy: z.string().optional().describe("Who verified the facts (URL, person, agent)."),
114
+ }),
115
+ outputSchema: swarmToolOutputSchema({
116
+ overlay: z.looseObject({}).optional(),
117
+ }),
118
+ },
119
+ async (input, requestInfo) => {
120
+ if (!requestInfo.agentId) {
121
+ return toolErr('Agent ID not found. Set the "X-Agent-ID" header.');
122
+ }
123
+ const denied = await denyUnlessCatalogWriter(requestInfo.agentId);
124
+ if (denied) return toolErr(denied);
125
+ try {
126
+ const overlay = await upsertModelCatalogOverlay(input);
127
+ return toolOk(`Overlay for ${input.provider}/${input.modelId} saved.`, {
128
+ details: `Overlay for ${input.provider}/${input.modelId} saved (reason: ${input.reason}). It expires when models.dev matches every fact set.`,
129
+ data: { overlay },
130
+ });
131
+ } catch (error) {
132
+ const message = error instanceof Error ? error.message : "Unknown error";
133
+ return toolErr(`Failed to save overlay: ${message}`);
134
+ }
135
+ },
136
+ );
137
+ };
@@ -11,6 +11,7 @@ import {
11
11
  getWorkflow,
12
12
  isExtensionAgent,
13
13
  } from "@/be/db";
14
+ import { explicitModelErrorForAgent } from "@/be/model-validation";
14
15
  import { getScript } from "@/be/scripts/db";
15
16
  import { calculateNextRun } from "@/scheduler";
16
17
  import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
@@ -109,6 +110,12 @@ export const createScheduleInputSchema = z.object({
109
110
  modelTier: ModelTierSchema.optional().describe(
110
111
  "Portable model tier for tasks created by this schedule: 'smol', 'regular', 'smart', or 'ultra'. Resolved by each assignee's harness/provider at run time.",
111
112
  ),
113
+ allowCustomModel: z
114
+ .boolean()
115
+ .optional()
116
+ .describe(
117
+ "Accept a `model` the model catalog does not list. Without it an unknown model id is rejected. Only for ids the catalog cannot know yet.",
118
+ ),
112
119
  });
113
120
 
114
121
  const scheduleDataShape = {
@@ -177,6 +184,7 @@ export const registerCreateScheduleTool = (server: McpServer) => {
177
184
  enabled,
178
185
  model,
179
186
  modelTier,
187
+ allowCustomModel,
180
188
  },
181
189
  requestInfo,
182
190
  _meta,
@@ -263,8 +271,15 @@ export const registerCreateScheduleTool = (server: McpServer) => {
263
271
  }
264
272
  }
265
273
 
274
+ const normalizedModel = splitLegacyModelAlias({ model, modelTier });
275
+ const modelError = await explicitModelErrorForAgent({
276
+ model: normalizedModel.model,
277
+ allowCustomModel,
278
+ agentId: targetAgentId,
279
+ });
280
+ if (modelError) return toolErr(modelError);
281
+
266
282
  try {
267
- const normalizedModel = splitLegacyModelAlias({ model, modelTier });
268
283
  // Calculate initial nextRunAt
269
284
  let nextRunAt: string | undefined;
270
285
  if (enabled === false) {
@@ -12,6 +12,7 @@ import {
12
12
  isExtensionAgent,
13
13
  updateScheduledTask,
14
14
  } from "@/be/db";
15
+ import { explicitModelErrorForAgent } from "@/be/model-validation";
15
16
  import { mergeScheduleTiming, validateRecurringTiming } from "@/be/schedules/validate";
16
17
  import { getScript } from "@/be/scripts/db";
17
18
  import { calculateNextRun } from "@/scheduler";
@@ -73,6 +74,12 @@ export const patchScheduleInputSchema = z.object({
73
74
  modelTier: ModelTierSchema.nullable()
74
75
  .optional()
75
76
  .describe("Portable model tier for tasks created by this schedule. Set to null to clear."),
77
+ allowCustomModel: z
78
+ .boolean()
79
+ .optional()
80
+ .describe(
81
+ "Accept a `model` the model catalog does not list. Without it an unknown model id is rejected. Only for ids the catalog cannot know yet.",
82
+ ),
76
83
  });
77
84
 
78
85
  const scheduleDataShape = {
@@ -140,6 +147,7 @@ export const registerPatchScheduleTool = (server: McpServer) => {
140
147
  enabled,
141
148
  model,
142
149
  modelTier,
150
+ allowCustomModel,
143
151
  },
144
152
  requestInfo,
145
153
  _meta,
@@ -249,6 +257,15 @@ export const registerPatchScheduleTool = (server: McpServer) => {
249
257
  if (enabled !== undefined) updateData.enabled = enabled;
250
258
  if (model !== undefined || modelTier !== undefined) {
251
259
  const normalizedModel = splitLegacyModelAlias({ model, modelTier });
260
+ // A model the schedule already stores is not re-judged, so an unrelated edit still saves.
261
+ if (normalizedModel.model !== schedule.model) {
262
+ const modelError = await explicitModelErrorForAgent({
263
+ model: normalizedModel.model,
264
+ allowCustomModel,
265
+ agentId: targetAgentId ?? schedule.targetAgentId,
266
+ });
267
+ if (modelError) return toolErr(modelError);
268
+ }
252
269
  if (model !== undefined) updateData.model = normalizedModel.model ?? null;
253
270
  if (modelTier !== undefined || normalizedModel.modelTier) {
254
271
  updateData.modelTier = normalizedModel.modelTier ?? null;
@@ -12,6 +12,7 @@ import {
12
12
  isExtensionAgent,
13
13
  updateScheduledTask,
14
14
  } from "@/be/db";
15
+ import { explicitModelErrorForAgent } from "@/be/model-validation";
15
16
  import { mergeScheduleTiming, validateRecurringTiming } from "@/be/schedules/validate";
16
17
  import { getScript } from "@/be/scripts/db";
17
18
  import { calculateNextRun } from "@/scheduler";
@@ -73,6 +74,12 @@ export const updateScheduleInputSchema = z.object({
73
74
  modelTier: ModelTierSchema.nullable()
74
75
  .optional()
75
76
  .describe("Portable model tier for tasks created by this schedule. Set to null to clear."),
77
+ allowCustomModel: z
78
+ .boolean()
79
+ .optional()
80
+ .describe(
81
+ "Accept a `model` the model catalog does not list. Without it an unknown model id is rejected. Only for ids the catalog cannot know yet.",
82
+ ),
76
83
  });
77
84
 
78
85
  const scheduleDataShape = {
@@ -139,6 +146,7 @@ export const registerUpdateScheduleTool = (server: McpServer) => {
139
146
  enabled,
140
147
  model,
141
148
  modelTier,
149
+ allowCustomModel,
142
150
  },
143
151
  requestInfo,
144
152
  _meta,
@@ -248,6 +256,15 @@ export const registerUpdateScheduleTool = (server: McpServer) => {
248
256
  if (enabled !== undefined) updateData.enabled = enabled;
249
257
  if (model !== undefined || modelTier !== undefined) {
250
258
  const normalizedModel = splitLegacyModelAlias({ model, modelTier });
259
+ // A model the schedule already stores is not re-judged, so an unrelated edit still saves.
260
+ if (normalizedModel.model !== schedule.model) {
261
+ const modelError = await explicitModelErrorForAgent({
262
+ model: normalizedModel.model,
263
+ allowCustomModel,
264
+ agentId: targetAgentId ?? schedule.targetAgentId,
265
+ });
266
+ if (modelError) return toolErr(modelError);
267
+ }
251
268
  if (model !== undefined) updateData.model = normalizedModel.model ?? null;
252
269
  if (modelTier !== undefined || normalizedModel.modelTier) {
253
270
  updateData.modelTier = normalizedModel.modelTier ?? null;
@@ -17,6 +17,7 @@ import {
17
17
  isExtensionAgent,
18
18
  } from "@/be/db";
19
19
  import { repointTrackerSyncBySwarmId } from "@/be/db-queries/tracker";
20
+ import { explicitModelErrorForAgent } from "@/be/model-validation";
20
21
  import { applyPreTaskCreate } from "@/extensions/apply-task-create";
21
22
  import { checkSlackRoutingCoherence } from "@/tasks/slack-routing";
22
23
  import { findDuplicateTask } from "@/tools/task-dedup";
@@ -136,6 +137,12 @@ export const sendTaskInputSchema = z
136
137
  modelTier: ModelTierSchema.optional().describe(
137
138
  "Portable model tier for this task: 'smol', 'regular', 'smart', or 'ultra'. Resolved at claim/run time using the assignee's harness/provider. Legacy model shortnames map as haiku→smol, sonnet→regular, opus→smart, fable→ultra.",
138
139
  ),
140
+ allowCustomModel: z
141
+ .boolean()
142
+ .optional()
143
+ .describe(
144
+ "Accept a `model` the model catalog does not list. Without it an unknown model id is rejected. Only for ids the catalog cannot know yet (a fresh launch, a private deployment).",
145
+ ),
139
146
  effort: ReasoningEffortSchema.optional().describe(
140
147
  "Reasoning effort for this task: 'off', 'low', 'medium', 'high', 'xhigh', or 'max'. If omitted, the assignee's REASONING_EFFORT_OVERRIDE/default applies.",
141
148
  ),
@@ -268,6 +275,7 @@ export async function sendTaskHandler(
268
275
  vcsRepo,
269
276
  model,
270
277
  modelTier,
278
+ allowCustomModel,
271
279
  effort,
272
280
  allowDuplicate,
273
281
  slackChannelId,
@@ -313,6 +321,12 @@ export async function sendTaskHandler(
313
321
 
314
322
  const effectiveVcsRepo = vcsRepo;
315
323
  const normalizedModel = splitLegacyModelAlias({ model, modelTier });
324
+ const modelError = await explicitModelErrorForAgent({
325
+ model: normalizedModel.model,
326
+ allowCustomModel,
327
+ agentId,
328
+ });
329
+ if (modelError) return toolErr(modelError, { data: { yourAgentId: creatorAgentId } });
316
330
 
317
331
  // Auto-default parentTaskId to caller's current task for tree tracking
318
332
  const effectiveParentTaskId = parentTaskId ?? sourceTaskId;
@@ -27,6 +27,7 @@ import {
27
27
  releaseTask,
28
28
  updateTaskClaudeSessionId,
29
29
  } from "@/be/db";
30
+ import { explicitModelErrorForAgent } from "@/be/model-validation";
30
31
  import { touchRuntimeInstance } from "@/be/multi-runtime";
31
32
  import { applyPreTaskCreate } from "@/extensions/apply-task-create";
32
33
  import { assertOwnsTask, ownerCtx, type ToolCtx } from "@/tools/task-tool-ctx";
@@ -98,6 +99,12 @@ export const taskActionInputSchema = z.object({
98
99
  modelTier: ModelTierSchema.optional().describe(
99
100
  "Portable model tier for the created task: 'smol', 'regular', 'smart', or 'ultra'. Resolved when a worker claims/runs the task. Only used with 'create' action.",
100
101
  ),
102
+ allowCustomModel: z
103
+ .boolean()
104
+ .optional()
105
+ .describe(
106
+ "Accept a `model` the model catalog does not list. Without it an unknown model id is rejected. Only used with 'create' action.",
107
+ ),
101
108
  effort: ReasoningEffortSchema.optional().describe(
102
109
  "Reasoning effort for the created task: 'off', 'low', 'medium', 'high', 'xhigh', or 'max'. Only used with 'create' action.",
103
110
  ),
@@ -275,6 +282,11 @@ export async function taskActionHandler(
275
282
  agentId,
276
283
  );
277
284
  }
285
+ const modelError = await explicitModelErrorForAgent({
286
+ model: normalizedModel.model,
287
+ allowCustomModel: input.allowCustomModel,
288
+ });
289
+ if (modelError) return taskActionResult({ success: false, message: modelError }, agentId);
278
290
  try {
279
291
  assetKey = key
280
292
  ? await authorizeAssetKeyWrite(key, await resolveTaskAuditUserId(ctx.sourceTaskId, agentId))
@@ -78,12 +78,14 @@ export const DEFERRED_TOOLS = new Set([
78
78
  "list-services",
79
79
  "update-service-status",
80
80
 
81
- // Config (5)
81
+ // Config (7)
82
82
  "set-config",
83
83
  "list-config",
84
84
  "delete-config",
85
85
  "credential-bindings",
86
86
  "script-connections",
87
+ "model-catalog-refresh",
88
+ "model-catalog-overlay-upsert",
87
89
 
88
90
  // Repos (1)
89
91
  "update-repo",
@@ -19,6 +19,7 @@ import {
19
19
  } from "@/types";
20
20
  import { getExecutorRegistry } from "@/workflows";
21
21
  import { validateDefinition } from "@/workflows/definition";
22
+ import { withSaveWarnings, workflowSaveWarnings } from "@/workflows/readiness";
22
23
 
23
24
  export const registerCreateWorkflowTool = (server: McpServer) => {
24
25
  createToolRegistrar(server)(
@@ -102,6 +103,7 @@ export const registerCreateWorkflowTool = (server: McpServer) => {
102
103
  outputSchema: swarmToolOutputSchema({
103
104
  yourAgentId: z.string().optional(),
104
105
  workflow: z.unknown().optional(),
106
+ warnings: z.array(z.string()).optional(),
105
107
  }),
106
108
  },
107
109
  async (
@@ -152,11 +154,16 @@ export const registerCreateWorkflowTool = (server: McpServer) => {
152
154
  "mcp",
153
155
  );
154
156
  const longScriptTimeoutHint = findLongScriptTimeoutHint(definition.nodes);
155
- return toolOk(`Created workflow "${workflow.name}".`, {
156
- details: `Created workflow "${workflow.name}" (${workflow.id}).`,
157
+ const warnings = await workflowSaveWarnings(workflow.definition, getExecutorRegistry());
158
+ return toolOk(withSaveWarnings(`Created workflow "${workflow.name}".`, warnings), {
159
+ details: withSaveWarnings(
160
+ `Created workflow "${workflow.name}" (${workflow.id}).`,
161
+ warnings,
162
+ ),
157
163
  data: {
158
164
  yourAgentId: requestInfo.agentId,
159
165
  workflow,
166
+ ...(warnings.length > 0 ? { warnings } : {}),
160
167
  ...(longScriptTimeoutHint ? { longScriptTimeoutHint } : {}),
161
168
  },
162
169
  });
@@ -11,6 +11,7 @@ import {
11
11
  import { WorkflowNodePatchSchema } from "@/types";
12
12
  import { getExecutorRegistry } from "@/workflows";
13
13
  import { patchWorkflowDefinition } from "@/workflows/patch-definition";
14
+ import { withSaveWarnings, workflowSaveWarnings } from "@/workflows/readiness";
14
15
 
15
16
  export const registerPatchWorkflowNodeTool = (server: McpServer) => {
16
17
  createToolRegistrar(server)(
@@ -30,6 +31,7 @@ export const registerPatchWorkflowNodeTool = (server: McpServer) => {
30
31
  outputSchema: swarmToolOutputSchema({
31
32
  workflow: z.unknown().optional(),
32
33
  versionCreated: z.number().optional(),
34
+ warnings: z.array(z.string()).optional(),
33
35
  }),
34
36
  },
35
37
  async ({ id, nodeId, ...nodeFields }, requestInfo) => {
@@ -59,14 +61,22 @@ export const registerPatchWorkflowNodeTool = (server: McpServer) => {
59
61
  { id: nodeId, type: patchedNode?.type, config: nodeFields.config },
60
62
  ]);
61
63
 
62
- return toolOk(`Patched node "${nodeId}" in workflow "${workflow.name}".`, {
63
- details: `Patched node "${nodeId}" in workflow "${workflow.name}" (${id}). Version ${version} snapshot created.`,
64
- data: {
65
- workflow,
66
- versionCreated: version ?? undefined,
67
- ...(longScriptTimeoutHint ? { longScriptTimeoutHint } : {}),
64
+ const warnings = await workflowSaveWarnings(result.definition, getExecutorRegistry());
65
+ return toolOk(
66
+ withSaveWarnings(`Patched node "${nodeId}" in workflow "${workflow.name}".`, warnings),
67
+ {
68
+ details: withSaveWarnings(
69
+ `Patched node "${nodeId}" in workflow "${workflow.name}" (${id}). Version ${version} snapshot created.`,
70
+ warnings,
71
+ ),
72
+ data: {
73
+ workflow,
74
+ versionCreated: version ?? undefined,
75
+ ...(warnings.length > 0 ? { warnings } : {}),
76
+ ...(longScriptTimeoutHint ? { longScriptTimeoutHint } : {}),
77
+ },
68
78
  },
69
- });
79
+ );
70
80
  } catch (err) {
71
81
  return toolErr(String(err));
72
82
  }
@@ -14,6 +14,7 @@ import type { WorkflowPatch } from "@/types";
14
14
  import { AssetKeySchema, WorkflowNodePatchSchema } from "@/types";
15
15
  import { getExecutorRegistry } from "@/workflows";
16
16
  import { patchWorkflowDefinition } from "@/workflows/patch-definition";
17
+ import { withSaveWarnings, workflowSaveWarnings } from "@/workflows/readiness";
17
18
 
18
19
  export const registerPatchWorkflowTool = (server: McpServer) => {
19
20
  createToolRegistrar(server)(
@@ -78,6 +79,7 @@ export const registerPatchWorkflowTool = (server: McpServer) => {
78
79
  nodesCreated: z.number().optional(),
79
80
  nodesUpdated: z.number().optional(),
80
81
  nodesDeleted: z.number().optional(),
82
+ warnings: z.array(z.string()).optional(),
81
83
  }),
82
84
  },
83
85
  async ({ id, key, update, delete: del, create, onNodeFailure, triggerSchema }, requestInfo) => {
@@ -133,14 +135,19 @@ export const registerPatchWorkflowTool = (server: McpServer) => {
133
135
  );
134
136
  const longScriptTimeoutHint = findLongScriptTimeoutHint(authoredFinalNodes);
135
137
 
136
- return toolOk(`Patched workflow "${workflow.name}".`, {
137
- details: `Patched workflow "${workflow.name}" (${id}). Version ${version} snapshot created.`,
138
+ const warnings = await workflowSaveWarnings(result.definition, getExecutorRegistry());
139
+ return toolOk(withSaveWarnings(`Patched workflow "${workflow.name}".`, warnings), {
140
+ details: withSaveWarnings(
141
+ `Patched workflow "${workflow.name}" (${id}). Version ${version} snapshot created.`,
142
+ warnings,
143
+ ),
138
144
  data: {
139
145
  workflow,
140
146
  versionCreated: version ?? undefined,
141
147
  nodesCreated: create?.length ?? 0,
142
148
  nodesUpdated: update?.length ?? 0,
143
149
  nodesDeleted: del?.length ?? 0,
150
+ ...(warnings.length > 0 ? { warnings } : {}),
144
151
  ...(longScriptTimeoutHint ? { longScriptTimeoutHint } : {}),
145
152
  },
146
153
  });
@@ -19,6 +19,7 @@ import {
19
19
  } from "@/types";
20
20
  import { getExecutorRegistry } from "@/workflows";
21
21
  import { definitionNodeIds, validateDefinition } from "@/workflows/definition";
22
+ import { withSaveWarnings, workflowSaveWarnings } from "@/workflows/readiness";
22
23
  import { snapshotAndUpdateWorkflow } from "@/workflows/version";
23
24
 
24
25
  export const registerUpdateWorkflowTool = (server: McpServer) => {
@@ -84,6 +85,7 @@ export const registerUpdateWorkflowTool = (server: McpServer) => {
84
85
  outputSchema: swarmToolOutputSchema({
85
86
  workflow: z.unknown().optional(),
86
87
  versionCreated: z.number().optional(),
88
+ warnings: z.array(z.string()).optional(),
87
89
  }),
88
90
  },
89
91
  async (
@@ -152,11 +154,16 @@ export const registerUpdateWorkflowTool = (server: McpServer) => {
152
154
  const longScriptTimeoutHint = definition
153
155
  ? findLongScriptTimeoutHint(definition.nodes)
154
156
  : undefined;
155
- return toolOk(`Updated workflow "${workflow.name}".`, {
156
- details: `Updated workflow "${workflow.name}" (${id}). Version ${version.version} snapshot created.`,
157
+ const warnings = await workflowSaveWarnings(workflow.definition, getExecutorRegistry());
158
+ return toolOk(withSaveWarnings(`Updated workflow "${workflow.name}".`, warnings), {
159
+ details: withSaveWarnings(
160
+ `Updated workflow "${workflow.name}" (${id}). Version ${version.version} snapshot created.`,
161
+ warnings,
162
+ ),
157
163
  data: {
158
164
  workflow,
159
165
  versionCreated: version.version,
166
+ ...(warnings.length > 0 ? { warnings } : {}),
160
167
  ...(longScriptTimeoutHint ? { longScriptTimeoutHint } : {}),
161
168
  },
162
169
  });
package/src/types.ts CHANGED
@@ -231,6 +231,25 @@ export function resolveModelTier(opts: {
231
231
  return DEFAULT_MODEL_TIER_MAP[opts.harnessProvider]?.[tier];
232
232
  }
233
233
 
234
+ export type ModelTierOverrides = Partial<Record<string, Partial<Record<ModelTier, string>>>>;
235
+
236
+ /**
237
+ * The worker's own MODEL_TIER_<TIER> / MODEL_TIER_MAP overrides, keyed by its
238
+ * harness provider, in the shape the worker sends on register and poll so the
239
+ * server can apply them at claim time. Values only (model ids), never secrets.
240
+ */
241
+ export function parseWorkerModelTierOverrides(
242
+ env: Record<string, string | undefined>,
243
+ harnessProvider: ProviderName,
244
+ ): ModelTierOverrides {
245
+ const tiers: Partial<Record<ModelTier, string>> = { ...parseTierMapJson(env.MODEL_TIER_MAP) };
246
+ for (const tier of MODEL_TIERS) {
247
+ const direct = env[`MODEL_TIER_${tier.toUpperCase()}`]?.trim();
248
+ if (direct) tiers[tier] = direct;
249
+ }
250
+ return Object.keys(tiers).length > 0 ? { [harnessProvider]: tiers } : {};
251
+ }
252
+
234
253
  export function resolveTaskModelSelection(opts: {
235
254
  model?: string | null;
236
255
  modelTier?: string | null;
@@ -612,6 +631,12 @@ export const AgentTaskSchema = z
612
631
  model: z.string().optional(),
613
632
  modelTier: ModelTierSchema.optional(),
614
633
  effort: ReasoningEffortSchema.optional(),
634
+ // Claim-time model resolution (server-side, see runbooks/model-tiers.md).
635
+ // resolvedModel is what the worker runs; modelSource says which layer won;
636
+ // modelAlias is the `latest:` alias the value came from, when any.
637
+ resolvedModel: z.string().optional(),
638
+ modelSource: z.string().optional(),
639
+ modelAlias: z.string().optional(),
615
640
 
616
641
  // Schedule linking (optional — set when task was created by a schedule).
617
642
  // z.string(): also reachable verbatim from the create-task query param.
@@ -1996,7 +2021,7 @@ export const WorkflowNodeSchema = z
1996
2021
  type: z
1997
2022
  .string()
1998
2023
  .describe(
1999
- "Executor type: 'agent-task', 'script', 'swarm-script', 'raw-llm', 'validate', 'property-match'",
2024
+ "Executor type: 'agent-task', 'script', 'swarm-script', 'raw-llm', 'system-one-decision', 'validate', 'property-match'",
2000
2025
  ),
2001
2026
  label: z.string().optional().describe("Human-readable label for UI display"),
2002
2027
  config: z
@@ -2005,6 +2030,7 @@ export const WorkflowNodeSchema = z
2005
2030
  "Executor-specific config. For agent-task: { template, outputSchema?, agentId?, routingReason?, routingNote?, tags?, priority?, dir?, vcsRepo?, model? }; configured agentId defaults routingReason to human_pinned. " +
2006
2031
  "For script: { runtime, script, args?, timeout? }. " +
2007
2032
  "For swarm-script: { scriptName, scope?, pinHash?, args?, fsMode?, timeoutMs? (1000-300000) }. " +
2033
+ "For system-one-decision (typed decisions, Jev by default): { provider? ('typesafe' default | 'openrouter' | 'laya'; a literal, never a {{token}}), state, questions: { <id>: { type: 'noul'|'choice'|'score', instructions, criteria? } }, returns: { <id>: { type } }, model? (provider-specific; unset = the provider's default, and laya has none so it sends no model and picks its own checkpoint), timeoutMs?, maxRetries? (0-3), humanReview? { band: { min, max } (0-1, inclusive), approvers: { users?, roles?, policy }, title?, timeout?, notifications? } }; each provider needs its own global secret (TYPESAFE_API_KEY, OPENROUTER_API_KEY, or LAYA_API_KEY; laya also needs the global config LAYA_URL), and a save warns and a run fails before any node executes when one is missing; system-one-decision nodes must not set retry or validation.retry. With humanReview, an answer whose confidence is inside the band waits for a person (human-in-the-loop approval), and next must map ports { approved, rejected?, timeout? }. " +
2008
2034
  "Agent-task templates and ordinary config values support {{interpolation}} from the node's inputs context, including trigger and declared upstream aliases. " +
2009
2035
  "SECURITY: executable source for script/swarm-script nodes does not interpolate trigger.* or upstream node outputs; only input/workflow/swarm/run values are allowed in inline script source, and named swarm-script source is not workflow-interpolated. " +
2010
2036
  "Pass dynamic values through config.args instead (inline script receives them as argv; swarm-script receives its args object). " +
@@ -2412,6 +2438,9 @@ export type AgentTaskSummary = Pick<
2412
2438
  | "scheduleId"
2413
2439
  | "model"
2414
2440
  | "modelTier"
2441
+ | "resolvedModel"
2442
+ | "modelSource"
2443
+ | "modelAlias"
2415
2444
  | "effort"
2416
2445
  | "provider"
2417
2446
  | "requestedByUserId"