@desplega.ai/agent-swarm 1.158.0 → 1.160.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 (378) hide show
  1. package/README.md +1 -1
  2. package/dist/{acp-adapter-xaq4n6pc.js → acp-adapter-2mjwcsfh.js} +124 -27
  3. package/dist/{actions-p9hz5yat.js → actions-e43rxskr.js} +35 -30
  4. package/dist/{anthropic-messages-51pz26qe.js → anthropic-messages-5qx80j9k.js} +106 -16
  5. package/dist/{app-g26wdcz6.js → app-mc7xfagn.js} +12 -10
  6. package/dist/{assistant-wydfqkb3.js → assistant-sedkzv3b.js} +42 -31
  7. package/dist/{azure-openai-responses-8gksqtmz.js → azure-openai-responses-nfb21z9a.js} +13 -14
  8. package/dist/{boot-reembed-se8zmdcp.js → boot-reembed-cqz9swn5.js} +13 -12
  9. package/dist/{boot-reembed-g5ckg9yb.js → boot-reembed-m3656j2n.js} +14 -13
  10. package/dist/{boot-scrub-logs-wfxqan61.js → boot-scrub-logs-xbxf5d7w.js} +11 -9
  11. package/dist/boot-seeds-gsetbs5y.js +28 -0
  12. package/dist/{claude-adapter-tv2x4f36.js → claude-adapter-dc6289mc.js} +28 -23
  13. package/dist/{claude-managed-adapter-41b2s7fd.js → claude-managed-adapter-naaw51me.js} +4 -3
  14. package/dist/{claude-managed-setup-60g8aazx.js → claude-managed-setup-eacvchx0.js} +12 -3
  15. package/dist/claude-sdk-session-bqp2xvym.js +29722 -0
  16. package/dist/{cli-987hg0aa.js → cli-0jwe9034.js} +2 -2
  17. package/dist/{cli-vj6h9kc2.js → cli-0m7wct0d.js} +59 -1
  18. package/dist/{cli-c07r9a2m.js → cli-0q3avq3s.js} +12 -11
  19. package/dist/{cli-19jenss3.js → cli-0wgxmwf6.js} +31 -10
  20. package/dist/cli-0x01kwrd.js +491 -0
  21. package/dist/cli-0xqde53b.js +176 -0
  22. package/dist/cli-10nhj6xq.js +8355 -0
  23. package/dist/{cli-tt9k8r35.js → cli-12tszpk9.js} +479 -910
  24. package/dist/{cli-5b6b62tz.js → cli-13hw4z91.js} +1 -1
  25. package/dist/{cli-cqt19wg6.js → cli-1a2j50m0.js} +11 -2
  26. package/dist/{cli-xberxa1r.js → cli-25fcq4b4.js} +2 -2
  27. package/dist/{cli-npssxgaz.js → cli-2pdaysab.js} +52 -2
  28. package/dist/cli-3qhyh31j.js +166 -0
  29. package/dist/cli-447dt1h3.js +3828 -0
  30. package/dist/cli-4derpgyk.js +115 -0
  31. package/dist/{cli-00w2c66p.js → cli-523679eh.js} +13 -2
  32. package/dist/{cli-y95wp3tg.js → cli-58e8yr0d.js} +1 -1
  33. package/dist/{cli-24k806sc.js → cli-5nbmpy8w.js} +3 -3
  34. package/dist/{cli-53sz8gkz.js → cli-6cs6wzxt.js} +1 -1
  35. package/dist/cli-741yb0xx.js +1027 -0
  36. package/dist/cli-74h9hscv.js +69 -0
  37. package/dist/cli-7cxw8b60.js +14910 -0
  38. package/dist/{cli-xhaeg2nc.js → cli-7e398j1h.js} +1 -1
  39. package/dist/cli-7kyk9jr3.js +22 -0
  40. package/dist/cli-89s4ngw3.js +183 -0
  41. package/dist/{cli-9b13ggd1.js → cli-89v6kyq7.js} +1 -0
  42. package/dist/{cli-ftfvggzx.js → cli-9hsgwz4w.js} +1045 -581
  43. package/dist/{cli-rqz8qbw7.js → cli-a8dvhzrt.js} +59 -20
  44. package/dist/{cli-j57qkrpq.js → cli-azhgzetg.js} +395 -56
  45. package/dist/cli-br0qdrnp.js +2150 -0
  46. package/dist/{cli-rj19hc34.js → cli-btfv8y88.js} +1 -1
  47. package/dist/{cli-8qmme6vd.js → cli-c8a36vcc.js} +2 -2
  48. package/dist/cli-cpvsaqsw.js +333 -0
  49. package/dist/cli-d5czme6m.js +17805 -0
  50. package/dist/{cli-h38x0qhg.js → cli-et8m9xsv.js} +4 -3
  51. package/dist/{cli-x24pshtq.js → cli-evvtk35y.js} +6 -6
  52. package/dist/{cli-7s3syk22.js → cli-f8gy6r2r.js} +5 -5
  53. package/dist/{cli-zt1227k1.js → cli-fkqhbg8g.js} +5 -3
  54. package/dist/{cli-9httdpyq.js → cli-g2whhdrf.js} +1 -1
  55. package/dist/cli-gt4hxb8p.js +99 -0
  56. package/dist/{cli-s8eet254.js → cli-jajnsm8q.js} +37184 -53838
  57. package/dist/{cli-7gtvvpbh.js → cli-jayjpjmb.js} +21 -12
  58. package/dist/{cli-68w09sjw.js → cli-jyk0ynw2.js} +27 -89
  59. package/dist/{cli-aehm1rbq.js → cli-jz4w45qt.js} +1152 -61
  60. package/dist/cli-kmcpfwn2.js +333856 -0
  61. package/dist/{cli-4198qc9v.js → cli-ktmyrtpn.js} +19 -3
  62. package/dist/{cli-j55n64b6.js → cli-mfe5btp9.js} +505 -189
  63. package/dist/{cli-q67y4wzm.js → cli-phsaj4xq.js} +24 -2
  64. package/dist/{cli-1hkrch3k.js → cli-pm1rst0h.js} +46 -7
  65. package/dist/{cli-pw9rj5t9.js → cli-pqetb83f.js} +1 -176
  66. package/dist/{cli-ggm92wcn.js → cli-ptr8h7kp.js} +20 -1
  67. package/dist/{cli-s8s499xy.js → cli-qbksc292.js} +3 -2
  68. package/dist/{cli-47m6dkd3.js → cli-qydf22qq.js} +597 -346
  69. package/dist/{cli-3gmcwfyx.js → cli-r1mxd1xh.js} +13 -6
  70. package/dist/cli-rb9m6rpe.js +4980 -0
  71. package/dist/cli-rykfmgwg.js +102 -0
  72. package/dist/{cli-5d10vd2m.js → cli-shkxm1vn.js} +6 -5
  73. package/dist/{cli-eetrvv91.js → cli-t3p77qk1.js} +1 -1
  74. package/dist/cli-tfky2838.js +756 -0
  75. package/dist/{cli-g8djp6qe.js → cli-tmqt7dpj.js} +40 -0
  76. package/dist/{cli-fgqxwrpv.js → cli-tpq83z89.js} +1631 -127
  77. package/dist/cli-trc24wk2.js +143 -0
  78. package/dist/{cli-aky8tctm.js → cli-tzx2ke1v.js} +3 -3
  79. package/dist/cli-v7fn5gms.js +1425 -0
  80. package/dist/{cli-wscdvcqz.js → cli-vctdqs5h.js} +1 -1
  81. package/dist/{cli-cw9zck8j.js → cli-xjcywfsf.js} +8 -4
  82. package/dist/{cli-k0g1sjzm.js → cli-ybgv4kw2.js} +18 -11
  83. package/dist/{cli-rjehvrts.js → cli-ydmcgve1.js} +8 -8
  84. package/dist/{cli-vhjwh6cz.js → cli-ygwm91nt.js} +1 -1
  85. package/dist/{cli-mcnhgnn1.js → cli-ykk5jaax.js} +184 -26
  86. package/dist/{cli-de1396j2.js → cli-ywybs5as.js} +7 -1
  87. package/dist/cli-zpep9q5e.js +491 -0
  88. package/dist/cli.js +41 -32
  89. package/dist/cloudflare-workers-ai-system-one-k6fbxw9d.js +46 -0
  90. package/dist/{codex-adapter-cnm8wk11.js → codex-adapter-nmxcatww.js} +26 -17
  91. package/dist/{codex-hook-9hptmdhj.js → codex-hook-20me6byw.js} +2 -2
  92. package/dist/{codex-login-x3jk4etn.js → codex-login-w6fz4xbz.js} +50 -5
  93. package/dist/{codex-session-runner-gfg547zj.js → codex-session-runner-90aa80ev.js} +24 -17
  94. package/dist/{commands-e3bn0p75.js → commands-8wcq1kn7.js} +11 -9
  95. package/dist/{db-03v9kzr2.js → db-j40kc4se.js} +25 -11
  96. package/dist/{devin-adapter-8vgkwh29.js → devin-adapter-dpsv6hd9.js} +1 -1
  97. package/dist/{dispatcher-nrmg8yht.js → dispatcher-4zqy768d.js} +34 -29
  98. package/dist/{dsh-adapter-k03nfqa6.js → dsh-adapter-mpnzgztj.js} +3 -3
  99. package/dist/{e2b-93x1k0j5.js → e2b-ez07znam.js} +4 -3
  100. package/dist/execute-6e7qeyk4.js +1017 -0
  101. package/dist/{google-generative-ai-bxmv50nc.js → google-generative-ai-k6n5nbpp.js} +6 -5
  102. package/dist/{google-vertex-809af514.js → google-vertex-44xxtcvp.js} +6 -5
  103. package/dist/{handlers-aqcedbyb.js → handlers-kjdce1w5.js} +37 -32
  104. package/dist/{hook-ybvmrmhq.js → hook-czx13n12.js} +19 -15
  105. package/dist/{hook-7vkskkre.js → hook-r2t4bqfg.js} +18 -14
  106. package/dist/{http-2eqa8t24.js → http-3bbb19be.js} +900 -145
  107. package/dist/{index-f6cryxwe.js → index-4a5njnm1.js} +2 -59
  108. package/dist/{index-x71bvq0p.js → index-668me3f0.js} +35 -30
  109. package/dist/{index-c6zsv1bd.js → index-7aj368tw.js} +13 -12
  110. package/dist/{index-1yks3qcr.js → index-fg0dsp6c.js} +34 -29
  111. package/dist/{index-chgdkq36.js → index-kkv84jq9.js} +34 -29
  112. package/dist/{index-a3vn96n8.js → index-zg7vp46x.js} +198 -54
  113. package/dist/{keepalive-vtkf5n99.js → keepalive-yra8dy57.js} +13 -11
  114. package/dist/{lead-h3sp357x.js → lead-va0pad2c.js} +54 -49
  115. package/dist/llama-cpp-classify-tgj35k8d.js +335 -0
  116. package/dist/{maintenance-jzeqtk3q.js → maintenance-hqewtmyg.js} +14 -13
  117. package/dist/{mistral-conversations-zx0kn554.js → mistral-conversations-pc3ntnxt.js} +14 -17
  118. package/dist/{oauth-refresh-sweep-qp3dzmdp.js → oauth-refresh-sweep-7rdt1m9n.js} +12 -10
  119. package/dist/{onboard-tmsp0qbz.js → onboard-j6ms6d0x.js} +6 -5
  120. package/dist/{openai-codex-responses-4vjqzvjp.js → openai-codex-responses-bbancrr0.js} +21 -8
  121. package/dist/{openai-completions-9hjrqbw8.js → openai-completions-jpaj5g2b.js} +9 -12
  122. package/dist/{openai-responses-7b1pn0xt.js → openai-responses-200k1wx4.js} +23 -18
  123. package/dist/{opencode-adapter-x6x2da1z.js → opencode-adapter-89rabkw2.js} +8 -7
  124. package/dist/{openrouter-images-9f9ek4rw.js → openrouter-images-8yx3487g.js} +5 -7
  125. package/dist/{otel-impl-9mz73q65.js → otel-impl-a792rjz0.js} +1 -1
  126. package/dist/{pi-messages-txpjj39q.js → pi-messages-vxdt8v7j.js} +2 -1
  127. package/dist/{pi-mono-adapter-m6dvv0dz.js → pi-mono-adapter-g7qzvr3v.js} +295 -70
  128. package/dist/pricing-refresh-wx0tp7cb.js +39 -0
  129. package/dist/quickjs-r1pq2107.wasm +0 -0
  130. package/dist/{rbac-roles-c9wqz83x.js → rbac-roles-8tzawacv.js} +12 -10
  131. package/dist/{render-v2-gryrh9h8.js → render-v2-mm7zswb8.js} +12 -10
  132. package/dist/runtime-zyssgse0.js +24 -0
  133. package/dist/{setup-q9720bt6.js → setup-8y70ys6t.js} +6 -5
  134. package/dist/typesafe-system-one-2ef30pec.js +27 -0
  135. package/dist/{virtual-modules-3htpgn8x.js → virtual-modules-6nmk7h3n.js} +22 -13
  136. package/dist/{work-objects-b76x7deb.js → work-objects-fg6xkb0n.js} +37 -32
  137. package/dist/{worker-6vrxbv41.js → worker-wmwhxsph.js} +54 -49
  138. package/openapi.json +1314 -91
  139. package/package.json +12 -11
  140. package/src/be/db/agents.ts +20 -0
  141. package/src/be/db/api-keys.ts +70 -0
  142. package/src/be/db/tasks/human-free.ts +442 -0
  143. package/src/be/db/tasks/read.ts +56 -1
  144. package/src/be/db/tasks/write.ts +107 -11
  145. package/src/be/db.ts +274 -188
  146. package/src/be/harness-model-support.ts +182 -0
  147. package/src/be/human-free-drain.ts +67 -0
  148. package/src/be/memory/providers/sqlite-store.ts +51 -15
  149. package/src/be/migrations/182_task_human_free_flag.sql +63 -0
  150. package/src/be/migrations/183_human_free_reclassify_queue.sql +25 -0
  151. package/src/be/migrations/184_model_catalog.sql +120 -0
  152. package/src/be/migrations/185_api_key_auth_failures.sql +6 -0
  153. package/src/be/migrations/186_api_key_seat_mismatch.sql +8 -0
  154. package/src/be/model-catalog-store.ts +644 -0
  155. package/src/be/model-tier-keys.ts +46 -0
  156. package/src/be/model-tier-resolution.ts +534 -0
  157. package/src/be/model-validation.ts +188 -0
  158. package/src/be/models-catalog.ts +61 -7
  159. package/src/be/modelsdev-cache.json +82 -0
  160. package/src/be/modelsdev-cache.ts +2 -0
  161. package/src/be/pricing-refresh.ts +154 -12
  162. package/src/be/scripts/typecheck.ts +15 -0
  163. package/src/be/seed/runner.ts +18 -12
  164. package/src/be/seed/types.ts +1 -0
  165. package/src/be/seed-scripts/catalog/task-failure-audit.ts +61 -9
  166. package/src/be/swarm-config-guard.ts +8 -0
  167. package/src/cli.tsx +10 -0
  168. package/src/commands/claude-managed-setup.ts +3 -1
  169. package/src/commands/codex-login.ts +67 -0
  170. package/src/commands/credential-outcome-report.ts +191 -0
  171. package/src/commands/rate-limit-outcome.ts +34 -4
  172. package/src/commands/runner.ts +357 -186
  173. package/src/commands/seat-mismatch-report.ts +46 -0
  174. package/src/extensions/apply-task-create.ts +27 -1
  175. package/src/extensions/contract-types.generated.ts +1 -1
  176. package/src/heartbeat/heartbeat.ts +79 -17
  177. package/src/heartbeat/templates.ts +2 -2
  178. package/src/http/agents.ts +62 -1
  179. package/src/http/api-keys.ts +140 -2
  180. package/src/http/codex-oauth-keep-warm.ts +3 -5
  181. package/src/http/index.ts +8 -0
  182. package/src/http/memory.ts +12 -5
  183. package/src/http/models-catalog.ts +357 -8
  184. package/src/http/poll.ts +54 -3
  185. package/src/http/schedules.ts +51 -6
  186. package/src/http/script-runs.ts +12 -0
  187. package/src/http/session-data.ts +2 -5
  188. package/src/http/stats.ts +6 -8
  189. package/src/http/status.ts +15 -1
  190. package/src/http/tasks.ts +90 -7
  191. package/src/http/usage-cache.ts +56 -15
  192. package/src/http/workflows.ts +46 -8
  193. package/src/mcp-client/http-client.ts +5 -0
  194. package/src/memory/recall-query.ts +74 -0
  195. package/src/prompts/base-prompt.ts +15 -4
  196. package/src/prompts/session-templates.ts +23 -1
  197. package/src/providers/acp-adapter.ts +1 -0
  198. package/src/providers/claude-adapter.ts +14 -5
  199. package/src/providers/claude-managed-adapter.ts +1 -0
  200. package/src/providers/claude-managed-models.ts +47 -161
  201. package/src/providers/claude-sdk-session.ts +3 -1
  202. package/src/providers/codex-adapter.ts +14 -14
  203. package/src/providers/codex-models.ts +65 -198
  204. package/src/providers/devin-adapter.ts +1 -0
  205. package/src/providers/dsh-adapter.ts +2 -1
  206. package/src/providers/harness-version.ts +83 -7
  207. package/src/providers/opencode-adapter.ts +53 -1
  208. package/src/providers/pi-binary-root/src/extensions/codemode/worker.ts +11 -0
  209. package/src/providers/pi-codemode-runtime.ts +17 -0
  210. package/src/providers/pi-mono-adapter.ts +361 -83
  211. package/src/providers/pi-mono-extension.ts +16 -1
  212. package/src/providers/reasoning-effort.ts +44 -140
  213. package/src/providers/types.ts +8 -0
  214. package/src/rbac/legacy-policy.ts +43 -0
  215. package/src/rbac/permissions.ts +15 -0
  216. package/src/scripts-runtime/sdk-allowlist.ts +4 -0
  217. package/src/scripts-runtime/types/stdlib.d.ts +15 -0
  218. package/src/scripts-runtime/types/swarm-extension.d.ts +3 -0
  219. package/src/scripts-runtime/types/swarm-sdk.d.ts +15 -0
  220. package/src/server.ts +6 -0
  221. package/src/slack/assistant.ts +13 -0
  222. package/src/slack/message-text.ts +56 -2
  223. package/src/slack/render-v2.ts +87 -37
  224. package/src/slack/session-status.ts +541 -0
  225. package/src/slack/thread-buffer.ts +10 -2
  226. package/src/tasks/sibling-awareness.ts +3 -2
  227. package/src/telemetry.ts +5 -4
  228. package/src/templates.d.ts +6 -0
  229. package/src/tests/agents-list-model-display.test.ts +4 -6
  230. package/src/tests/api-key-auth-failure-route.test.ts +142 -0
  231. package/src/tests/api-key-tracking.test.ts +368 -1
  232. package/src/tests/base-prompt.test.ts +43 -2
  233. package/src/tests/claude-adapter.test.ts +68 -0
  234. package/src/tests/claude-managed-adapter.test.ts +12 -18
  235. package/src/tests/codex-adapter.test.ts +2 -1
  236. package/src/tests/codex-auth-failure.test.ts +52 -0
  237. package/src/tests/codex-gpt6-release.test.ts +66 -5
  238. package/src/tests/codex-login.test.ts +116 -0
  239. package/src/tests/context-window.test.ts +7 -5
  240. package/src/tests/credential-exhaustion-policy.test.ts +118 -3
  241. package/src/tests/credential-outcome-report.test.ts +171 -0
  242. package/src/tests/credential-status-api.test.ts +103 -2
  243. package/src/tests/db-extraction-contract.test.ts +1 -0
  244. package/src/tests/db-task-read-contract.test.ts +10 -1
  245. package/src/tests/db-task-write-contract.test.ts +535 -2
  246. package/src/tests/docs-index-footprint.test.js +3 -3
  247. package/src/tests/dokploy-deploy.test.ts +315 -0
  248. package/src/tests/dsh-adapter.test.ts +1 -1
  249. package/src/tests/explicit-model-validation.test.ts +583 -0
  250. package/src/tests/extensions-pre-task.test.ts +73 -0
  251. package/src/tests/fetch-resolved-env.test.ts +46 -0
  252. package/src/tests/hang-watchdog-worker.ts +47 -0
  253. package/src/tests/hang-watchdog.test.ts +45 -0
  254. package/src/tests/hang-watchdog.ts +30 -0
  255. package/src/tests/harness-model-guard.test.ts +123 -0
  256. package/src/tests/harness-model-support.test.ts +374 -0
  257. package/src/tests/harness-version.test.ts +70 -3
  258. package/src/tests/heartbeat-checklist-config.test.ts +71 -0
  259. package/src/tests/heartbeat-checklist.test.ts +4 -2
  260. package/src/tests/heartbeat-orphan-resume.test.ts +48 -0
  261. package/src/tests/http-task-write-ownership.test.ts +172 -0
  262. package/src/tests/human-free-flag.test.ts +279 -0
  263. package/src/tests/human-free-oracle.ts +57 -0
  264. package/src/tests/human-free-reclassify.test.ts +669 -0
  265. package/src/tests/memory-fts-populate.test.ts +139 -0
  266. package/src/tests/memory-http-recall-gating.test.ts +125 -1
  267. package/src/tests/migration-046-budgets.test.ts +3 -3
  268. package/src/tests/model-catalog-migration.test.ts +134 -0
  269. package/src/tests/model-catalog-table.test.ts +249 -0
  270. package/src/tests/model-catalog-write-access.test.ts +387 -0
  271. package/src/tests/model-rate-limit-windows.test.ts +19 -0
  272. package/src/tests/model-tier-resolution.test.ts +611 -0
  273. package/src/tests/models-catalog.test.ts +1 -1
  274. package/src/tests/multi-runtime-enabled-stats.test.ts +65 -1
  275. package/src/tests/opencode-adapter.test.ts +157 -2
  276. package/src/tests/package-publish.test.ts +137 -36
  277. package/src/tests/pi-codemode.test.ts +246 -0
  278. package/src/tests/pi-mono-adapter.test.ts +3 -0
  279. package/src/tests/pi-native-mcp.test.ts +193 -0
  280. package/src/tests/pi-tool-exposure.test.ts +374 -0
  281. package/src/tests/preload.ts +3 -0
  282. package/src/tests/pricing-routes.test.ts +2 -2
  283. package/src/tests/prompt-template-session.test.ts +5 -3
  284. package/src/tests/rate-limit-event.test.ts +65 -0
  285. package/src/tests/rate-limit-outcome.test.ts +90 -0
  286. package/src/tests/rbac-engine.test.ts +77 -3
  287. package/src/tests/reasoning-effort.test.ts +109 -0
  288. package/src/tests/refresh-modelsdev-carryforward.test.ts +5 -5
  289. package/src/tests/runner-key-completion-outcome.test.ts +112 -0
  290. package/src/tests/runner-key-outcome-late-reset.test.ts +224 -0
  291. package/src/tests/runner-registration-retry.test.ts +149 -0
  292. package/src/tests/runtime-model-catalog.test.ts +86 -0
  293. package/src/tests/script-connections.test.ts +9 -0
  294. package/src/tests/seat-mismatch-report.test.ts +85 -0
  295. package/src/tests/seed-scripts.test.ts +138 -0
  296. package/src/tests/seed.test.ts +25 -0
  297. package/src/tests/session-costs.test.ts +14 -11
  298. package/src/tests/slack-message-text.test.ts +101 -0
  299. package/src/tests/slack-render-v2.test.ts +96 -0
  300. package/src/tests/slack-session-status.test.ts +626 -0
  301. package/src/tests/slack-thread-buffer-context.test.ts +93 -0
  302. package/src/tests/status.test.ts +29 -0
  303. package/src/tests/steering-promotion.test.ts +51 -0
  304. package/src/tests/store-progress-ownership.test.ts +136 -0
  305. package/src/tests/subscription-plans.test.ts +28 -0
  306. package/src/tests/task-cascade-fail.test.ts +148 -2
  307. package/src/tests/task-citations.test.ts +3 -2
  308. package/src/tests/tool-annotations.test.ts +1 -1
  309. package/src/tests/usage-cache.test.ts +166 -0
  310. package/src/tests/workflow-agent-task.test.ts +30 -0
  311. package/src/tests/workflow-executors.test.ts +5 -2
  312. package/src/tests/workflow-system-one-decision-review.test.ts +1021 -0
  313. package/src/tests/workflow-system-one-decision-save-warnings.test.ts +365 -0
  314. package/src/tests/workflow-system-one-decision.test.ts +2063 -0
  315. package/src/tests/workflow-triggers-v2.test.ts +320 -0
  316. package/src/tools/model-catalog/index.ts +137 -0
  317. package/src/tools/schedules/create-schedule.ts +17 -2
  318. package/src/tools/schedules/patch-schedule.ts +31 -1
  319. package/src/tools/schedules/update-schedule.ts +31 -1
  320. package/src/tools/send-task.ts +18 -1
  321. package/src/tools/store-progress.ts +49 -4
  322. package/src/tools/task-action.ts +14 -1
  323. package/src/tools/tool-config.ts +3 -1
  324. package/src/tools/workflows/create-workflow.ts +17 -4
  325. package/src/tools/workflows/patch-workflow-node.ts +17 -7
  326. package/src/tools/workflows/patch-workflow.ts +9 -2
  327. package/src/tools/workflows/update-workflow.ts +17 -4
  328. package/src/types.ts +41 -1
  329. package/src/utils/codex-auth-failure.ts +34 -0
  330. package/src/utils/context-window.ts +47 -53
  331. package/src/utils/credentials.ts +72 -30
  332. package/src/utils/error-tracker.ts +80 -0
  333. package/src/utils/harness-cli-version.ts +115 -0
  334. package/src/utils/harness-model-error.ts +33 -0
  335. package/src/utils/model-rate-limit-windows.ts +12 -0
  336. package/src/utils/runtime-model-catalog.ts +152 -0
  337. package/src/utils/spawn-model-guard.ts +43 -0
  338. package/src/utils/subscription-plans.ts +22 -0
  339. package/src/workflows/approval-resolution.ts +39 -0
  340. package/src/workflows/definition.ts +7 -0
  341. package/src/workflows/engine.ts +69 -1
  342. package/src/workflows/executors/agent-task.ts +16 -6
  343. package/src/workflows/executors/base.ts +53 -0
  344. package/src/workflows/executors/human-in-the-loop.ts +8 -8
  345. package/src/workflows/executors/index.ts +1 -0
  346. package/src/workflows/executors/registry.ts +2 -0
  347. package/src/workflows/executors/system-one-decision.ts +1232 -0
  348. package/src/workflows/executors/system-one-providers.ts +301 -0
  349. package/src/workflows/executors/workflow-llm.ts +13 -1
  350. package/src/workflows/model-validation.ts +45 -0
  351. package/src/workflows/patch-definition.ts +5 -0
  352. package/src/workflows/readiness.ts +86 -0
  353. package/src/workflows/recovery.ts +12 -12
  354. package/src/workflows/resume.ts +23 -9
  355. package/src/workflows/triggers.ts +85 -0
  356. package/templates/schedules/daily-blocker-digest/content.md +4 -5
  357. package/templates/skills/agent-browser/SKILL.md +6 -5
  358. package/templates/skills/agent-browser/content.md +6 -5
  359. package/templates/skills/heartbeat-runbook/config.json +1 -1
  360. package/templates/skills/heartbeat-runbook/content.md +2 -2
  361. package/templates/skills/swarm-scripts/SKILL.md +1 -1
  362. package/templates/skills/swarm-scripts/content.md +1 -1
  363. package/templates/skills/workflow-iterate/content.md +82 -1
  364. package/templates/workflows/claude-code-changelog-watch/content.md +2 -0
  365. package/templates/workflows/pr-review-status-sweep/content.md +2 -0
  366. package/dist/boot-seeds-s3q01xj3.js +0 -26
  367. package/dist/claude-sdk-session-t442qz6f.js +0 -39108
  368. package/dist/cli-0xjkj1j7.js +0 -61
  369. package/dist/cli-20cj37vt.js +0 -335561
  370. package/dist/cli-2djtr51b.js +0 -48
  371. package/dist/cli-65hjmev5.js +0 -1141
  372. package/dist/cli-aw1jwp1r.js +0 -14075
  373. package/dist/cli-h967nb6r.js +0 -6603
  374. package/dist/cli-vapyk8sw.js +0 -20
  375. package/dist/cli-ws6rqycm.js +0 -247
  376. package/dist/pricing-refresh-v1cbr9te.js +0 -27
  377. package/src/providers/modelsdev-reasoning.json +0 -6453
  378. package/dist/{index-f24115ge.js → index-2wsdsnmm.js} +3 -3
package/src/http/stats.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { IncomingMessage, ServerResponse } from "node:http";
2
2
  import { z } from "zod";
3
3
  import {
4
- getAllAgents,
4
+ getAgentStatusCounts,
5
5
  getAllLogs,
6
6
  getAllServices,
7
7
  getConcurrentContext,
@@ -15,6 +15,7 @@ import { getDbRetentionStats } from "../be/db-retention";
15
15
  import { isSteeringEnabled } from "../be/steering";
16
16
  import type { AgentLog } from "../types";
17
17
  import { AgentLogSchema, ScheduledTaskSchema, ServiceSchema } from "../types";
18
+ import { isEnvFlagEnabled } from "../utils/env-flag";
18
19
  import { isMultiRuntimeEnabled } from "../utils/multi-runtime";
19
20
  import { resolveHttpFavoriteOwner } from "./favorite-owner";
20
21
  import { route } from "./route-def";
@@ -51,6 +52,7 @@ const DashboardStatsSchema = z.object({
51
52
  tasks: TaskCountsSchema,
52
53
  steeringEnabled: z.boolean(),
53
54
  multiRuntimeEnabled: z.boolean(),
55
+ devMode: z.boolean(),
54
56
  });
55
57
 
56
58
  /** Mirrors `DbRetentionTableStats` (src/be/db-retention.ts). */
@@ -247,16 +249,11 @@ export async function handleStats(
247
249
  }
248
250
 
249
251
  if (getStats.match(req.method, pathSegments)) {
250
- const agents = await getAllAgents();
252
+ const agents = await getAgentStatusCounts();
251
253
  const taskStats = await getTaskStats();
252
254
 
253
255
  const stats = {
254
- agents: {
255
- total: agents.length,
256
- idle: agents.filter((a) => a.status === "idle").length,
257
- busy: agents.filter((a) => a.status === "busy").length,
258
- offline: agents.filter((a) => a.status === "offline").length,
259
- },
256
+ agents,
260
257
  tasks: {
261
258
  total: taskStats.total,
262
259
  unassigned: taskStats.unassigned,
@@ -271,6 +268,7 @@ export async function handleStats(
271
268
  // Authenticated home for the steering feature flag — deliberately NOT on
272
269
  // the unauthenticated /health endpoint (config must not leak).
273
270
  steeringEnabled: isSteeringEnabled(),
271
+ devMode: isEnvFlagEnabled("SWARM_DEV_MODE", false),
274
272
  // Same authenticated home, for the same reason. Read through the helper
275
273
  // the server itself branches on, and read PER REQUEST rather than
276
274
  // captured at module load, so the reported value tracks a `swarm_config`
@@ -37,6 +37,7 @@ import { getEmbeddingProvider } from "../be/memory";
37
37
  import { getFileStorageProvider } from "../fs/registry";
38
38
  import { getSlackConfiguration } from "../slack/config";
39
39
  import { getSlackConnectionState } from "../slack/connection-state";
40
+ import { isTelemetryEnabled } from "../telemetry";
40
41
  import { type AgentCredStatus, AutomationIntegrationIdSchema, ProviderNameSchema } from "../types";
41
42
  import { route } from "./route-def";
42
43
  import { json, jsonError } from "./utils";
@@ -119,6 +120,16 @@ export const StatusActivitySchema = z.object({
119
120
  });
120
121
  export type StatusActivity = z.infer<typeof StatusActivitySchema>;
121
122
 
123
+ /**
124
+ * Effective telemetry opt-out state (`ANONYMIZED_TELEMETRY`, on by default).
125
+ * The dashboard reads it to stop its own browser-side notification events when
126
+ * the operator opted out.
127
+ */
128
+ export const StatusTelemetrySchema = z.object({
129
+ enabled: z.boolean(),
130
+ });
131
+ export type StatusTelemetry = z.infer<typeof StatusTelemetrySchema>;
132
+
122
133
  export const StatusAgentFsSchema = z.object({
123
134
  configured: z.boolean(),
124
135
  base_url: z.string().nullable(),
@@ -171,6 +182,8 @@ export const StatusResponseSchema = z.object({
171
182
  automations: z.array(AutomationStatusSchema),
172
183
  /** Phase 2: rolled-up health for the always-on header badge. */
173
184
  health: StatusHealthSchema,
185
+ /** Additive: absent on servers that predate it, so consumers treat missing as enabled. */
186
+ telemetry: StatusTelemetrySchema.optional(),
174
187
  });
175
188
  export type StatusResponse = z.infer<typeof StatusResponseSchema>;
176
189
 
@@ -692,6 +705,7 @@ export async function buildStatusPayload(): Promise<StatusResponse> {
692
705
  },
693
706
  automations,
694
707
  health: computeHealth(setup),
708
+ telemetry: { enabled: isTelemetryEnabled() },
695
709
  };
696
710
  }
697
711
 
@@ -721,7 +735,7 @@ const getStatus = route({
721
735
  pattern: ["status"],
722
736
  summary: "Identity + setup readiness + live activity for the swarm dashboard",
723
737
  description:
724
- "Single source of truth consumed by the UI home page. Identity comes from SWARM_* envs; setup milestones each emit `unverified | configured | verified`; automations report `running | needs_setup` from the same runtime preflight used at dispatch; activity counts agents alive in the last 5 min and tasks created in the last 24h; agent_fs reports whether AGENT_FS_API_URL is set.",
738
+ "Single source of truth consumed by the UI home page. Identity comes from SWARM_* envs; setup milestones each emit `unverified | configured | verified`; automations report `running | needs_setup` from the same runtime preflight used at dispatch; activity counts agents alive in the last 5 min and tasks created in the last 24h; agent_fs reports whether AGENT_FS_API_URL is set; telemetry reports the effective ANONYMIZED_TELEMETRY opt-out state.",
725
739
  tags: ["Status"],
726
740
  responses: {
727
741
  200: { description: "Status payload", schema: StatusResponseSchema },
package/src/http/tasks.ts CHANGED
@@ -27,6 +27,7 @@ import {
27
27
  pauseTask,
28
28
  promoteDraftTask,
29
29
  resumeTask,
30
+ settleSupersededTaskDependents,
30
31
  supersedeTask,
31
32
  updateAgentStatusFromCapacity,
32
33
  updateTaskClaudeSessionId,
@@ -34,6 +35,7 @@ import {
34
35
  updateTaskTitle,
35
36
  updateTaskVcs,
36
37
  } from "../be/db";
38
+ import { explicitModelErrorForAgent } from "../be/model-validation";
37
39
  import {
38
40
  getTaskSteeringFields,
39
41
  markSteeringUndeliverable,
@@ -102,6 +104,9 @@ const AgentTaskSummarySchema = AgentTaskSchema.pick({
102
104
  scheduleId: true,
103
105
  model: true,
104
106
  modelTier: true,
107
+ resolvedModel: true,
108
+ modelSource: true,
109
+ modelAlias: true,
105
110
  effort: true,
106
111
  provider: true,
107
112
  requestedByUserId: true,
@@ -264,8 +269,18 @@ const createTask = route({
264
269
  outputSchema: z.record(z.string(), z.unknown()).optional(),
265
270
  contextKey: z.string().optional(),
266
271
  requestedByUserId: z.string().optional(),
267
- model: z.string().optional(),
272
+ model: z
273
+ .string()
274
+ .optional()
275
+ .describe(
276
+ "Concrete model override for this task, interpreted by the assignee's harness/provider. The model must run on the assignee's harness (an Anthropic model on a Claude agent, an OpenAI model on a Codex agent); a mismatch is rejected.",
277
+ ),
268
278
  modelTier: ModelTierSchema.optional(),
279
+ /**
280
+ * Accept a `model` the catalog does not list. Without it an unknown id is a 400; with it
281
+ * the id is stored as given (a fresh launch, a private deployment).
282
+ */
283
+ allowCustomModel: z.boolean().optional(),
269
284
  effort: ReasoningEffortSchema.optional(),
270
285
  /**
271
286
  * Create in `draft` status instead of the normal pending/unassigned/offered
@@ -287,7 +302,10 @@ const createTask = route({
287
302
  }),
288
303
  responses: {
289
304
  201: { description: "Task created", schema: AgentTaskSchema },
290
- 400: { description: "Validation error, or agentId/offeredTo targets an extension identity" },
305
+ 400: {
306
+ description:
307
+ "Validation error, an unknown `model` (set `allowCustomModel` to store a custom id), or agentId/offeredTo targets an extension identity",
308
+ },
291
309
  422: {
292
310
  description: "Task creation blocked by an extension",
293
311
  schema: z.object({
@@ -358,6 +376,7 @@ const cancelTaskRoute = route({
358
376
  responses: {
359
377
  200: { description: "Task cancelled", schema: TaskActionResultSchema },
360
378
  400: { description: "Cannot cancel terminal task" },
379
+ 403: { description: "Agent caller is neither a lead nor the task creator" },
361
380
  404: { description: "Task not found" },
362
381
  },
363
382
  });
@@ -524,6 +543,7 @@ const updateTaskProgressRoute = route({
524
543
  body: z.object({ progress: z.string().min(1) }),
525
544
  responses: {
526
545
  200: { description: "Progress updated", schema: z.object({ success: z.literal(true) }) },
546
+ 403: { description: "Task is assigned to another agent" },
527
547
  404: { description: "Task not found" },
528
548
  },
529
549
  });
@@ -734,6 +754,24 @@ async function canActOnOwnTask(
734
754
  return can({ principal, verb: "task.action.own", resource, source: "http" }).allow;
735
755
  }
736
756
 
757
+ /**
758
+ * Principal for task writes (progress, cancel). Workers share the swarm API
759
+ * key, so on an operator bearer the X-Agent-ID header is the caller's
760
+ * identity, as in POST /api/tasks/{id}/finish. A keyed call without it (the
761
+ * runner wrapper, the dashboard) stays the operator.
762
+ */
763
+ async function resolveTaskWritePrincipal(
764
+ req: IncomingMessage,
765
+ myAgentId: string | undefined,
766
+ ): Promise<RbacPrincipal> {
767
+ const auth = getRequestAuth(req);
768
+ if (auth?.kind === "user") return { kind: "user", userId: auth.userId };
769
+ const agentId = auth?.kind === "agent" ? auth.agentId : myAgentId;
770
+ if (!agentId) return { kind: "operator" };
771
+ const agent = await getAgentById(agentId);
772
+ return { kind: "agent", agentId, isLead: agent?.isLead === true };
773
+ }
774
+
737
775
  // ─── Handler ─────────────────────────────────────────────────────────────────
738
776
 
739
777
  export async function handleTasks(
@@ -860,6 +898,17 @@ export async function handleTasks(
860
898
  if (lead) defaultAgentId = lead.id;
861
899
  }
862
900
 
901
+ const modelError = await explicitModelErrorForAgent({
902
+ model: splitLegacyModelAlias({ model: parsed.body.model, modelTier: parsed.body.modelTier })
903
+ .model,
904
+ allowCustomModel: parsed.body.allowCustomModel,
905
+ agentId: defaultAgentId,
906
+ });
907
+ if (modelError) {
908
+ jsonError(res, modelError, 400);
909
+ return true;
910
+ }
911
+
863
912
  const parentTask = parsed.body.parentTaskId
864
913
  ? await getTaskById(parsed.body.parentTaskId)
865
914
  : null;
@@ -916,7 +965,7 @@ export async function handleTasks(
916
965
  }),
917
966
  effort: parsed.body.effort,
918
967
  },
919
- { origin: "rest" },
968
+ { origin: "rest", allowCustomModel: parsed.body.allowCustomModel },
920
969
  );
921
970
 
922
971
  ensure({
@@ -1011,6 +1060,22 @@ export async function handleTasks(
1011
1060
  return true;
1012
1061
  }
1013
1062
 
1063
+ // Humans may cancel any task. Agents get the MCP cancel-task policy:
1064
+ // lead or task creator.
1065
+ const principal = await resolveTaskWritePrincipal(req, myAgentId);
1066
+ if (
1067
+ principal.kind === "agent" &&
1068
+ !can({
1069
+ principal,
1070
+ verb: "task.cancel.any",
1071
+ resource: { kind: "task", taskId: task.id, creatorAgentId: task.creatorAgentId },
1072
+ source: "http",
1073
+ }).allow
1074
+ ) {
1075
+ jsonError(res, "Only the lead or task creator can cancel tasks.", 403);
1076
+ return true;
1077
+ }
1078
+
1014
1079
  if (isTerminalTaskStatus(task.status)) {
1015
1080
  jsonError(res, `Cannot cancel task with status '${task.status}'`, 400);
1016
1081
  return true;
@@ -1345,14 +1410,31 @@ export async function handleTasks(
1345
1410
  if (updateTaskProgressRoute.match(req.method, pathSegments)) {
1346
1411
  const parsed = await updateTaskProgressRoute.parse(req, res, pathSegments, queryParams);
1347
1412
  if (!parsed) return true;
1348
- const task = await getTaskById(parsed.params.id);
1413
+ const principal = await resolveTaskWritePrincipal(req, myAgentId);
1414
+
1415
+ // Check and write in one transaction so a reassignment cannot slip between.
1416
+ const status = await getDbClient().transaction(async (): Promise<200 | 403 | 404> => {
1417
+ const task = await getTaskById(parsed.params.id);
1418
+ if (!task) return 404;
1419
+ const decision = can({
1420
+ principal,
1421
+ verb: "task.progress.write",
1422
+ resource: { kind: "task", taskId: task.id, agentId: task.agentId },
1423
+ source: "http",
1424
+ });
1425
+ if (!decision.allow) return 403;
1426
+ await updateTaskProgress(parsed.params.id, parsed.body.progress);
1427
+ return 200;
1428
+ });
1349
1429
 
1350
- if (!task) {
1430
+ if (status === 404) {
1351
1431
  jsonError(res, "Task not found", 404);
1352
1432
  return true;
1353
1433
  }
1354
-
1355
- await updateTaskProgress(parsed.params.id, parsed.body.progress);
1434
+ if (status === 403) {
1435
+ jsonError(res, "Task is assigned to another agent", 403);
1436
+ return true;
1437
+ }
1356
1438
  updateTaskProgressRoute.respond(res, 200, { success: true });
1357
1439
  return true;
1358
1440
  }
@@ -1750,6 +1832,7 @@ export async function handleTasks(
1750
1832
  // `skipped` covers parent_not_found / lead_not_found edge cases — the
1751
1833
  // supersede already landed, so log + roll forward without a resume task.
1752
1834
  if (followUp.kind !== "created") {
1835
+ await settleSupersededTaskDependents(parsed.params.id, null);
1753
1836
  console.warn(
1754
1837
  `[Supersede] Task ${parsed.params.id.slice(0, 8)} superseded but resume creation skipped (${
1755
1838
  followUp.kind === "skipped" ? followUp.reason : followUp.kind
@@ -3,30 +3,71 @@
3
3
  * `/api/attribution/by-person`). Each open usage page polls them, and every
4
4
  * reply aggregates the whole window.
5
5
  *
6
- * A key holds the request filters plus a data version (see
7
- * `getUsageDataVersion`), so a new session or task misses the cache at once.
8
- * Changes to existing rows (a task re-attributed to another requester) show
9
- * after at most `USAGE_CACHE_TTL_MS`. A credential plan or name change
10
- * clears the cache.
6
+ * A key holds only the request filters. An entry is fresh for
7
+ * `USAGE_CACHE_TTL_MS` (the time bucket). Past that it is served as-is while one
8
+ * background load revalidates it, until its total age reaches
9
+ * `USAGE_CACHE_MAX_STALE_MS`; an older or missing entry blocks on a load.
10
+ * Concurrent misses for one key share that load.
11
+ *
12
+ * The key deliberately carries no data version. The old one changed on every
13
+ * new task or session, and the swarm creates one every few seconds, so it
14
+ * almost never hit. The cost is staleness: the first request past the bucket
15
+ * still gets the old value and only starts the refresh, so a new session shows
16
+ * up on the request after that. A client polling once per bucket sees it about
17
+ * one poll later. A credential plan or name change clears the cache.
11
18
  */
12
19
 
13
20
  export const USAGE_CACHE_TTL_MS = 30_000;
21
+ export const USAGE_CACHE_MAX_STALE_MS = 120_000;
14
22
  const MAX_ENTRIES = 100;
15
23
 
16
- const entries = new Map<string, { expiresAt: number; value: unknown }>();
24
+ const entries = new Map<string, { fetchedAt: number; value: unknown }>();
25
+ const loading = new Map<string, Promise<unknown>>();
26
+ // Bumped by `clearUsageCache`, so a load that started before the clear cannot
27
+ // write its (now outdated) result back.
28
+ let generation = 0;
29
+
30
+ function load<T>(key: string, loader: () => Promise<T>): Promise<T> {
31
+ const running = loading.get(key);
32
+ if (running) return running as Promise<T>;
33
+
34
+ const startedIn = generation;
35
+ const promise: Promise<T> = Promise.resolve()
36
+ .then(loader)
37
+ .then((value) => {
38
+ if (startedIn === generation) {
39
+ entries.delete(key);
40
+ // Map order is insertion order, so the first key is the oldest entry.
41
+ if (entries.size >= MAX_ENTRIES) entries.delete(entries.keys().next().value as string);
42
+ entries.set(key, { fetchedAt: Date.now(), value });
43
+ }
44
+ return value;
45
+ })
46
+ .finally(() => {
47
+ if (loading.get(key) === promise) loading.delete(key);
48
+ });
49
+ loading.set(key, promise);
50
+ return promise;
51
+ }
17
52
 
18
- export async function cachedUsageReport<T>(key: string, load: () => Promise<T>): Promise<T> {
19
- const now = Date.now();
53
+ export async function cachedUsageReport<T>(key: string, loader: () => Promise<T>): Promise<T> {
20
54
  const hit = entries.get(key);
21
- if (hit && hit.expiresAt > now) return hit.value as T;
22
- const value = await load();
23
- entries.delete(key);
24
- // Map order is insertion order, so the first key is the oldest entry.
25
- if (entries.size >= MAX_ENTRIES) entries.delete(entries.keys().next().value as string);
26
- entries.set(key, { expiresAt: now + USAGE_CACHE_TTL_MS, value });
27
- return value;
55
+ if (hit) {
56
+ const age = Date.now() - hit.fetchedAt;
57
+ if (age < USAGE_CACHE_TTL_MS) return hit.value as T;
58
+ if (age < USAGE_CACHE_MAX_STALE_MS) {
59
+ // Keep serving the stale value; a failed refresh retries on the next request.
60
+ load(key, loader).catch((error) => {
61
+ console.error(`[usage-cache] background refresh failed for ${key.split(":")[0]}:`, error);
62
+ });
63
+ return hit.value as T;
64
+ }
65
+ }
66
+ return load(key, loader);
28
67
  }
29
68
 
30
69
  export function clearUsageCache(): void {
70
+ generation++;
31
71
  entries.clear();
72
+ loading.clear();
32
73
  }
@@ -21,6 +21,7 @@ import {
21
21
  CooldownConfigSchema,
22
22
  InputValueSchema,
23
23
  TriggerConfigSchema,
24
+ type WorkflowDefinition,
24
25
  WorkflowDefinitionSchema,
25
26
  WorkflowEdgeSchema,
26
27
  WorkflowNodePatchSchema,
@@ -36,7 +37,9 @@ import { getExecutorRegistry, startWorkflowExecution } from "../workflows";
36
37
  import { definitionNodeIds, generateEdges, validateDefinition } from "../workflows/definition";
37
38
  import { TriggerSchemaError } from "../workflows/engine";
38
39
  import { validateJsonSchema } from "../workflows/json-schema-validator";
40
+ import { workflowModelErrors } from "../workflows/model-validation";
39
41
  import { patchWorkflowDefinition } from "../workflows/patch-definition";
42
+ import { workflowSaveWarnings } from "../workflows/readiness";
40
43
  import { cancelWorkflowRun, retryFailedRun } from "../workflows/resume";
41
44
  import { handleWebhookTrigger, WebhookError } from "../workflows/triggers";
42
45
  import { snapshotAndUpdateWorkflow } from "../workflows/version";
@@ -49,6 +52,14 @@ import { jsonError, parseBody, triggerSchemaErrorResponse } from "./utils";
49
52
  /** `Workflow` decorated with the caller-scoped favorite flag (always set once `withFavoriteFlags` runs). */
50
53
  const WorkflowWithFavoriteSchema = WorkflowSchema.extend({ favorite: z.boolean() });
51
54
 
55
+ /**
56
+ * `Workflow` returned by a save (create / update / patch). `warnings` is present only when the
57
+ * save succeeded but a node cannot run yet, e.g. a system-one-decision node whose provider key is not configured.
58
+ */
59
+ const WorkflowSaveResponseSchema = WorkflowSchema.extend({
60
+ warnings: z.array(z.string()).optional(),
61
+ });
62
+
52
63
  /** `/api/workflows` slim list item — mirrors `WorkflowSummary` in src/types.ts (no exported schema there). */
53
64
  const WorkflowSummarySchema = WorkflowSchema.omit({
54
65
  definition: true,
@@ -85,6 +96,14 @@ const WorkflowRunPageSchema = z.object({
85
96
 
86
97
  const SuccessResponseSchema = z.object({ success: z.literal(true) });
87
98
 
99
+ /** Adds `warnings` to a just-saved workflow when a node in it cannot run yet. */
100
+ async function withSaveWarnings<T extends { definition: WorkflowDefinition }>(
101
+ workflow: T,
102
+ ): Promise<T & { warnings?: string[] }> {
103
+ const warnings = await workflowSaveWarnings(workflow.definition, getExecutorRegistry());
104
+ return warnings.length > 0 ? { ...workflow, warnings } : workflow;
105
+ }
106
+
88
107
  // ─── Route Definitions ───────────────────────────────────────────────────────
89
108
 
90
109
  const listWorkflowsRoute = route({
@@ -137,7 +156,7 @@ const createWorkflowRoute = route({
137
156
  vcsRepo: z.string().min(1).optional(),
138
157
  }),
139
158
  responses: {
140
- 201: { description: "Workflow created", schema: WorkflowSchema },
159
+ 201: { description: "Workflow created", schema: WorkflowSaveResponseSchema },
141
160
  400: { description: "Invalid definition" },
142
161
  },
143
162
  });
@@ -188,7 +207,10 @@ const updateWorkflowRoute = route({
188
207
  enabled: z.boolean().optional(),
189
208
  }),
190
209
  responses: {
191
- 200: { description: "Workflow updated (version snapshot created)", schema: WorkflowSchema },
210
+ 200: {
211
+ description: "Workflow updated (version snapshot created)",
212
+ schema: WorkflowSaveResponseSchema,
213
+ },
192
214
  400: { description: "Invalid definition" },
193
215
  404: { description: "Workflow not found" },
194
216
  },
@@ -203,7 +225,10 @@ const patchWorkflowRoute = route({
203
225
  params: z.object({ id: z.string() }),
204
226
  body: WorkflowPatchSchema.extend({ key: AssetKeySchema.optional() }),
205
227
  responses: {
206
- 200: { description: "Workflow patched (version snapshot created)", schema: WorkflowSchema },
228
+ 200: {
229
+ description: "Workflow patched (version snapshot created)",
230
+ schema: WorkflowSaveResponseSchema,
231
+ },
207
232
  400: { description: "Invalid patch or resulting definition" },
208
233
  404: { description: "Workflow not found" },
209
234
  },
@@ -218,7 +243,10 @@ const patchWorkflowNodeRoute = route({
218
243
  params: z.object({ id: z.string(), nodeId: z.string() }),
219
244
  body: WorkflowNodePatchSchema,
220
245
  responses: {
221
- 200: { description: "Node patched (version snapshot created)", schema: WorkflowSchema },
246
+ 200: {
247
+ description: "Node patched (version snapshot created)",
248
+ schema: WorkflowSaveResponseSchema,
249
+ },
222
250
  400: { description: "Invalid patch or resulting definition" },
223
251
  404: { description: "Workflow or node not found" },
224
252
  },
@@ -548,6 +576,11 @@ export async function handleWorkflows(
548
576
  jsonError(res, `Invalid definition: ${validation.errors.join("; ")}`, 400);
549
577
  return true;
550
578
  }
579
+ const modelErrors = await workflowModelErrors(parsed.body.definition);
580
+ if (modelErrors.length > 0) {
581
+ jsonError(res, `Invalid definition: ${modelErrors.join("; ")}`, 400);
582
+ return true;
583
+ }
551
584
 
552
585
  const trustedUserId = await resolveHttpAuditUserId(req, myAgentId);
553
586
  let key: string | undefined;
@@ -583,7 +616,7 @@ export async function handleWorkflows(
583
616
  },
584
617
  "api",
585
618
  );
586
- createWorkflowRoute.respond(res, 201, workflow);
619
+ createWorkflowRoute.respond(res, 201, await withSaveWarnings(workflow));
587
620
  return true;
588
621
  }
589
622
 
@@ -638,7 +671,7 @@ export async function handleWorkflows(
638
671
  );
639
672
  return true;
640
673
  }
641
- patchWorkflowNodeRoute.respond(res, 200, result.workflow);
674
+ patchWorkflowNodeRoute.respond(res, 200, await withSaveWarnings(result.workflow));
642
675
  return true;
643
676
  }
644
677
 
@@ -695,7 +728,7 @@ export async function handleWorkflows(
695
728
  );
696
729
  return true;
697
730
  }
698
- patchWorkflowRoute.respond(res, 200, result.workflow);
731
+ patchWorkflowRoute.respond(res, 200, await withSaveWarnings(result.workflow));
699
732
  return true;
700
733
  }
701
734
 
@@ -722,6 +755,11 @@ export async function handleWorkflows(
722
755
  jsonError(res, `Invalid definition: ${validation.errors.join("; ")}`, 400);
723
756
  return true;
724
757
  }
758
+ const modelErrors = await workflowModelErrors(body.definition);
759
+ if (modelErrors.length > 0) {
760
+ jsonError(res, `Invalid definition: ${modelErrors.join("; ")}`, 400);
761
+ return true;
762
+ }
725
763
  }
726
764
 
727
765
  const updatedBy2 = (await resolveHttpAuditUserId(req, myAgentId)) ?? undefined;
@@ -766,7 +804,7 @@ export async function handleWorkflows(
766
804
  res.end();
767
805
  return true;
768
806
  }
769
- updateWorkflowRoute.respond(res, 200, workflow);
807
+ updateWorkflowRoute.respond(res, 200, await withSaveWarnings(workflow));
770
808
  return true;
771
809
  }
772
810
 
@@ -10,10 +10,15 @@ export interface McpTool {
10
10
  name: string;
11
11
  description?: string;
12
12
  inputSchema: Record<string, unknown>;
13
+ /** JSON Schema of `structuredContent`, when the server declares one. */
14
+ outputSchema?: Record<string, unknown>;
15
+ /** Server metadata, e.g. `anthropic/alwaysLoad` from task tool manifests. */
16
+ _meta?: Record<string, unknown>;
13
17
  }
14
18
 
15
19
  export interface McpToolCallResult {
16
20
  content: Array<{ type: string; text?: string }>;
21
+ structuredContent?: Record<string, unknown>;
17
22
  isError?: boolean;
18
23
  }
19
24
 
@@ -0,0 +1,74 @@
1
+ import { stripSiblingBlock } from "../tasks/sibling-block";
2
+
3
+ const COMPLETED_INSTRUCTIONS =
4
+ "\n\nIMPORTANT: Do NOT re-delegate or re-answer the original request. The worker has already handled it. Your job is ONLY to:\n1. Review the output above\n2. Do not relay the worker's raw output to Slack; the engine owns the thread tree and outcome card\n3. Complete this follow-up task\n\nUse";
5
+ const FAILED_INSTRUCTIONS = "\n\nDecide whether to reassign, retry, or handle the failure. Use";
6
+ const DETAILS_PREFIX = ' `get-task-details` with taskId "';
7
+ const DETAILS_SUFFIX = '" for full details.';
8
+
9
+ /** Shape only pre-task search text; never alter the task or persisted memory. */
10
+ export function buildRecallQuery(description: string): string {
11
+ // Bound every parsing operation, while leaving room for wrapper boilerplate.
12
+ let query = stripSiblingBlock(truncateUtf8(description, 65536));
13
+ // Only the fixed header uses a regex; content is parsed with linear searches.
14
+ const header = query.match(
15
+ /^Worker task (completed|failed) — (?:review|action) needed\.\n\nAgent: [^\n]*\nOriginal task created by agent [^\n]*\nTask: "/,
16
+ );
17
+ if (header) {
18
+ const taskStart = header[0].length;
19
+ const outputMarker = header[1] === "completed" ? '"\n\nOutput:\n' : '"\n\nFailure reason: ';
20
+ const taskEnd = query.indexOf(outputMarker, taskStart);
21
+ const outputStart = taskEnd + outputMarker.length;
22
+ const instructions = header[1] === "completed" ? COMPLETED_INSTRUCTIONS : FAILED_INSTRUCTIONS;
23
+ const outputEnd = query.indexOf(instructions + DETAILS_PREFIX, outputStart);
24
+ const idStart = outputEnd + instructions.length + DETAILS_PREFIX.length;
25
+ const idEnd = query.indexOf('"', idStart);
26
+ if (
27
+ taskEnd !== -1 &&
28
+ outputEnd !== -1 &&
29
+ idEnd !== -1 &&
30
+ !query.slice(idStart, idEnd).includes("\n") &&
31
+ query.startsWith(DETAILS_SUFFIX, idEnd)
32
+ ) {
33
+ const creatorInstructions = query.indexOf(
34
+ "\nAdditional instructions from the task creator:\n",
35
+ outputStart,
36
+ );
37
+ const contentEnd =
38
+ creatorInstructions !== -1 && creatorInstructions < outputEnd
39
+ ? creatorInstructions
40
+ : outputEnd;
41
+ const threadContext: string[] = [];
42
+ let cursor = idEnd + DETAILS_SUFFIX.length;
43
+ while (cursor < query.length) {
44
+ const start = query.indexOf("<thread_context>", cursor);
45
+ if (start === -1) break;
46
+ const end = query.indexOf("</thread_context>", start);
47
+ if (end === -1) break;
48
+ cursor = end + "</thread_context>".length;
49
+ threadContext.push(query.slice(start, cursor));
50
+ }
51
+ query = [
52
+ query.slice(taskStart, taskEnd),
53
+ query.slice(outputStart, contentEnd),
54
+ ...threadContext,
55
+ ].join("\n\n");
56
+ }
57
+ }
58
+ query = query.trim();
59
+ return truncateUtf8(query, 8191);
60
+ }
61
+
62
+ function truncateUtf8(query: string, maxBytes: number): string {
63
+ // OpenAI embeddings accept 8192 tokens. A UTF-8 byte budget of 8191 is
64
+ // conservative for the byte-level tokenizer, even for multilingual text.
65
+ // Iterate code points so truncation cannot split a surrogate pair.
66
+ let bytes = 0;
67
+ let end = 0;
68
+ for (const character of query) {
69
+ bytes += Buffer.byteLength(character, "utf8");
70
+ if (bytes > maxBytes) break;
71
+ end += character.length;
72
+ }
73
+ return query.slice(0, end);
74
+ }