@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
@@ -36,7 +36,9 @@ import {
36
36
  import type { SteerDeliveryResult } from "../providers/types.ts";
37
37
  import { initTelemetry, telemetry } from "../telemetry.ts";
38
38
  import {
39
+ type ModelTierOverrides,
39
40
  type ProviderName,
41
+ parseWorkerModelTierOverrides,
40
42
  type ReasoningEffort,
41
43
  type RepoGuidelines,
42
44
  resolveTaskModelSelection,
@@ -44,6 +46,7 @@ import {
44
46
  } from "../types.ts";
45
47
  import { getApiKey } from "../utils/api-key.ts";
46
48
  import { computeBudgetBackoffMs } from "../utils/budget-backoff.ts";
49
+ import { isCodexAuthFailureReason } from "../utils/codex-auth-failure.ts";
47
50
  import { getMcpBaseUrl } from "../utils/constants.ts";
48
51
  import { getContextWindowSize } from "../utils/context-window.ts";
49
52
  import {
@@ -51,16 +54,16 @@ import {
51
54
  ModelWindowExhaustedError,
52
55
  resolveCredentialPools,
53
56
  } from "../utils/credentials.ts";
54
- import {
55
- type RateLimitWindowTelemetry,
56
- resolveCodexCreditsExhaustedCooldownMs,
57
- } from "../utils/error-tracker.ts";
57
+ import { resolveCodexCreditsExhaustedCooldownMs } from "../utils/error-tracker.ts";
58
+ import { probeHarnessCliVersion, reportHarnessModelOutcome } from "../utils/harness-cli-version.ts";
58
59
  import { resolveHarnessProvider } from "../utils/harness-provider.ts";
59
60
  import { prettyPrintLine, prettyPrintStderr } from "../utils/pretty-print.ts";
60
61
  import { terminateRegisteredProcessGroups } from "../utils/process-group.ts";
62
+ import { refreshRuntimeModelCatalog } from "../utils/runtime-model-catalog.ts";
61
63
  import { resolveScriptsOnlyMode } from "../utils/scripts-only-mode.ts";
62
64
  import { scrubSecrets } from "../utils/secret-scrubber.ts";
63
65
  import { refreshSkillsIfChanged } from "../utils/skills-refresh.ts";
66
+ import { guardSpawnModel } from "../utils/spawn-model-guard.ts";
64
67
  import { isSteeringEnabled } from "../utils/steering-enabled.ts";
65
68
  import { interpolate } from "../utils/template.ts";
66
69
  import { detectVcsProvider } from "../vcs/index.ts";
@@ -71,6 +74,7 @@ import {
71
74
  buildResumeContextPreamble,
72
75
  prependContextPreamble,
73
76
  } from "./context-preamble.ts";
77
+ import { reportCredentialOutcomeThenFinish } from "./credential-outcome-report.ts";
74
78
  import { type CredentialRefreshState, refreshCredentialStatus } from "./credential-refresh.ts";
75
79
  import {
76
80
  awaitCredentials,
@@ -95,7 +99,6 @@ import {
95
99
  reportLatestModel,
96
100
  sendCredStatusReport,
97
101
  } from "./provider-credentials.ts";
98
- import { buildFinalRateLimitWindows, classifyRateLimitOutcome } from "./rate-limit-outcome.ts";
99
102
  import {
100
103
  type ResumeSessionCandidate,
101
104
  type ResumeSessionResolution,
@@ -105,6 +108,7 @@ import {
105
108
  import "./templates.ts";
106
109
 
107
110
  export { buildAttachmentsSection } from "./attachments-section.ts";
111
+ export { reportKeyRateLimitWindows } from "./credential-outcome-report.ts";
108
112
 
109
113
  /** Throttle interval for progress updates (3 seconds). */
110
114
  const PROGRESS_THROTTLE_MS = 3000;
@@ -773,6 +777,16 @@ export interface ResolvedEnvResult {
773
777
  // Preserve the deployment value before config reloads mutate process.env.
774
778
  const deploymentMemoryRaters = process.env.MEMORY_RATERS;
775
779
 
780
+ /**
781
+ * Container value (undefined when unset) of each RELOADABLE_ENV_KEYS entry a
782
+ * swarm_config row has overwritten in process.env, recorded before the first
783
+ * overwrite. A reload starts from process.env, so without this a deleted row
784
+ * would leave its value in place until the container restarts. Keys no row
785
+ * ever overwrote are absent and keep the container value. MEMORY_RATERS keeps
786
+ * its own deployment snapshot above.
787
+ */
788
+ const rowOverriddenBootEnv = new Map<string, string | undefined>();
789
+
776
790
  export async function fetchResolvedEnv(
777
791
  apiUrl: string,
778
792
  apiKey: string,
@@ -823,6 +837,11 @@ export async function fetchResolvedEnv(
823
837
  // Only reset after a successful fetch so outages retain the current value.
824
838
  env.MEMORY_RATERS =
825
839
  baseEnv === process.env ? deploymentMemoryRaters : baseEnv.MEMORY_RATERS;
840
+ // Same for every other key a row overwrote: start from the container
841
+ // value, so a row that is gone no longer applies.
842
+ if (baseEnv === process.env) {
843
+ for (const [key, bootValue] of rowOverriddenBootEnv) env[key] = bootValue;
844
+ }
826
845
 
827
846
  if (data.configs?.length) {
828
847
  scriptsOnlyConfigValue = data.configs.find(
@@ -1045,7 +1064,7 @@ export async function provisionAgentFsAfterRegistration(opts: {
1045
1064
  *
1046
1065
  * - STEERING_ENABLED — read per-poll by `isSteeringEnabled()` and by the
1047
1066
  * system-prompt builder; flipping it mid-run just gates a feature.
1048
- * - ANONYMIZED_TELEMETRY — read per-event by `telemetry.isEnabled()`.
1067
+ * - ANONYMIZED_TELEMETRY — read per-event by `isTelemetryEnabled()`.
1049
1068
  * - MEMORY_RATERS — read per hook/prompt invocation.
1050
1069
  * - TEMPLATE_REGISTRY_URL — read per registry fetch.
1051
1070
  * - SLACK_DISABLE — read by the prompt builder to gate the Slack tool section.
@@ -1069,6 +1088,9 @@ export const RELOADABLE_ENV_KEYS: ReadonlySet<string> = new Set([
1069
1088
  "TEMPLATE_REGISTRY_URL",
1070
1089
  "SLACK_DISABLE",
1071
1090
  "SWARM_ORG_NAME",
1091
+ // pi reads these from process.env for its traits and its session.
1092
+ "PI_TOOL_DEFERRAL",
1093
+ "PI_CODEMODE",
1072
1094
  ]);
1073
1095
 
1074
1096
  /**
@@ -1105,11 +1127,20 @@ export function applyResolvedEnvToProcessEnv(
1105
1127
  const changed: string[] = [];
1106
1128
  for (const key of RELOADABLE_ENV_KEYS) {
1107
1129
  const next = freshEnv[key];
1108
- if (key === "MEMORY_RATERS" && next === undefined && process.env[key] !== undefined) {
1130
+ if (
1131
+ next === undefined &&
1132
+ process.env[key] !== undefined &&
1133
+ (key === "MEMORY_RATERS" || rowOverriddenBootEnv.has(key))
1134
+ ) {
1135
+ // A deleted row for a key the container never set. Keys no row
1136
+ // overwrote stay as the container set them.
1109
1137
  delete process.env[key];
1110
1138
  changed.push(key);
1111
1139
  } else if (next !== undefined && next !== process.env[key]) {
1112
1140
  const previous = process.env[key];
1141
+ if (key !== "MEMORY_RATERS" && !rowOverriddenBootEnv.has(key)) {
1142
+ rowOverriddenBootEnv.set(key, previous);
1143
+ }
1113
1144
  process.env[key] = next;
1114
1145
  changed.push(key);
1115
1146
  // Make a reload that blanks a previously-set value loud — silently
@@ -1220,6 +1251,8 @@ const SWARM_TOOL_LABELS: Record<string, string | null> = {
1220
1251
  "set-config": "⚙️ Setting config",
1221
1252
  "list-config": "⚙️ Listing config",
1222
1253
  "delete-config": "⚙️ Deleting config",
1254
+ "model-catalog-refresh": "🧠 Refreshing model catalog",
1255
+ "model-catalog-overlay-upsert": "🧠 Updating model catalog overlay",
1223
1256
  // Schedules
1224
1257
  "create-schedule": "📅 Creating schedule",
1225
1258
  "list-schedules": "📅 Listing schedules",
@@ -1773,14 +1806,19 @@ export async function resolveCodexOAuthCredentialInfo(
1773
1806
  const slots = await loadAllCodexOAuthSlots(apiUrl, apiKey);
1774
1807
  if (slots.length > 0) {
1775
1808
  let availableIndices: number[] | undefined;
1809
+ let authFailureFence: number | undefined;
1776
1810
  try {
1777
1811
  const resp = await fetch(
1778
1812
  `${apiUrl}/api/keys/available?keyType=CODEX_OAUTH&totalKeys=${slots.length}`,
1779
1813
  { headers: { Authorization: `Bearer ${apiKey}` } },
1780
1814
  );
1781
1815
  if (resp.ok) {
1782
- const data = (await resp.json()) as { availableIndices: number[] };
1816
+ const data = (await resp.json()) as {
1817
+ availableIndices: number[];
1818
+ authFailureFence?: number;
1819
+ };
1783
1820
  availableIndices = data.availableIndices;
1821
+ authFailureFence = data.authFailureFence;
1784
1822
  if (availableIndices.length < slots.length) {
1785
1823
  console.log(
1786
1824
  `[credentials] CODEX_OAUTH: ${availableIndices.length}/${slots.length} slots available (${slots.length - availableIndices.length} rate-limited)`,
@@ -1821,7 +1859,10 @@ export async function resolveCodexOAuthCredentialInfo(
1821
1859
  },
1822
1860
  last_refresh: new Date(slotEntry.creds.expires).toISOString(),
1823
1861
  };
1824
- const sel = authJsonToCredentialSelection(authJson, selectedSlot, slots.length);
1862
+ const sel = {
1863
+ ...authJsonToCredentialSelection(authJson, selectedSlot, slots.length),
1864
+ authFailureFence,
1865
+ };
1825
1866
  console.log(
1826
1867
  `[credentials] Selected CODEX_OAUTH slot ${selectedSlot + 1}/${slots.length} [...${sel.keySuffix}]`,
1827
1868
  );
@@ -1864,92 +1905,54 @@ export async function resolveCodexOAuthCredentialInfo(
1864
1905
  }
1865
1906
  }
1866
1907
 
1867
- /** Report a rate-limited key to the API (fire-and-forget) */
1868
- async function reportKeyRateLimit(
1908
+ /** Upper bound on each awaited credential-outcome report, so a slow API never stalls completions. */
1909
+ const KEY_OUTCOME_REPORT_TIMEOUT_MS = 5000;
1910
+
1911
+ /** Report a Codex pool auth failure; the API benches after 2 in a row. Awaited, never throws. */
1912
+ async function reportKeyAuthFailure(
1869
1913
  apiUrl: string,
1870
1914
  apiKey: string,
1871
1915
  keyType: string,
1872
1916
  keySuffix: string,
1873
1917
  keyIndex: number,
1874
- rateLimitedUntil: string,
1918
+ taskId: string,
1919
+ timeoutMs = KEY_OUTCOME_REPORT_TIMEOUT_MS,
1875
1920
  ): Promise<void> {
1876
1921
  try {
1877
- await fetch(`${apiUrl}/api/keys/report-rate-limit`, {
1922
+ const resp = await fetch(`${apiUrl}/api/keys/report-auth-failure`, {
1878
1923
  method: "POST",
1879
1924
  headers: {
1880
1925
  "Content-Type": "application/json",
1881
1926
  Authorization: `Bearer ${apiKey}`,
1882
1927
  },
1883
- body: JSON.stringify({
1884
- keyType,
1885
- keySuffix,
1886
- keyIndex,
1887
- rateLimitedUntil,
1888
- }),
1928
+ body: JSON.stringify({ keyType, keySuffix, keyIndex, taskId }),
1929
+ signal: AbortSignal.timeout(timeoutMs),
1889
1930
  });
1931
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
1932
+ const data = (await resp.json()) as {
1933
+ consecutiveAuthFailures: number;
1934
+ benched: boolean;
1935
+ rateLimitedUntil: string | null;
1936
+ };
1890
1937
  console.log(
1891
- `[credentials] Reported key ...${keySuffix} as rate-limited until ${rateLimitedUntil}`,
1892
- );
1893
- } catch {
1894
- // Non-blocking
1895
- }
1896
- }
1897
-
1898
- /**
1899
- * Reports rate-limit window telemetry for a key. Returns the underlying
1900
- * fetch promise (does not swallow errors) so a caller that needs the post to
1901
- * complete before the task finishes (a model-scoped block) can await it and
1902
- * decide how to handle a failure; a caller that wants the legacy
1903
- * fire-and-forget behavior appends `.catch(() => {})`.
1904
- *
1905
- * Throws on a non-2xx response so a failed persistence surfaces to the
1906
- * caller instead of logging success while only the in-process guard took
1907
- * effect — otherwise other workers redraw the same exhausted key at once.
1908
- *
1909
- * `logKeySuffix` defaults to true for the legacy full-telemetry call site;
1910
- * the model-scoped call site passes false since it already logs the model
1911
- * family and key index itself (see the `[credential] model window ...`
1912
- * log above the call).
1913
- */
1914
- export async function reportKeyRateLimitWindows(
1915
- apiUrl: string,
1916
- apiKey: string,
1917
- keyType: string,
1918
- keySuffix: string,
1919
- keyIndex: number,
1920
- windows: RateLimitWindowTelemetry,
1921
- logKeySuffix = true,
1922
- ): Promise<void> {
1923
- if (Object.keys(windows).length === 0) return;
1924
- const response = await fetch(`${apiUrl}/api/keys/report-rate-limit-windows`, {
1925
- method: "POST",
1926
- headers: {
1927
- "Content-Type": "application/json",
1928
- Authorization: `Bearer ${apiKey}`,
1929
- },
1930
- body: JSON.stringify({
1931
- keyType,
1932
- keySuffix,
1933
- keyIndex,
1934
- windows,
1935
- }),
1936
- });
1937
- if (!response.ok) {
1938
- throw new Error(
1939
- `Failed to report rate-limit windows for key #${keyIndex}: HTTP ${response.status}`,
1938
+ `[credentials] Auth failure on ...${keySuffix}: ${data.consecutiveAuthFailures} in a row${
1939
+ data.benched ? `; benched until ${data.rateLimitedUntil}` : ""
1940
+ }`,
1940
1941
  );
1941
- }
1942
- if (logKeySuffix) {
1943
- console.log(`[credentials] Reported rate-limit windows for key ...${keySuffix}`);
1942
+ } catch (err) {
1943
+ const message = err instanceof Error ? err.message : String(err);
1944
+ console.warn(scrubSecrets(`[credentials] Failed to report auth failure: ${message}`));
1944
1945
  }
1945
1946
  }
1946
1947
 
1947
- /** Clear a stale rate-limit record after a successful task (fire-and-forget) */
1948
+ /** Clear a stale rate-limit record after a successful task. Bounded, never throws. */
1948
1949
  async function reportKeyClearRateLimit(
1949
1950
  apiUrl: string,
1950
1951
  apiKey: string,
1951
1952
  keyType: string,
1952
1953
  keySuffix: string,
1954
+ authFence: number | undefined,
1955
+ timeoutMs = KEY_OUTCOME_REPORT_TIMEOUT_MS,
1953
1956
  ): Promise<void> {
1954
1957
  try {
1955
1958
  const resp = await fetch(`${apiUrl}/api/keys/clear-rate-limit`, {
@@ -1958,7 +1961,11 @@ async function reportKeyClearRateLimit(
1958
1961
  "Content-Type": "application/json",
1959
1962
  Authorization: `Bearer ${apiKey}`,
1960
1963
  },
1961
- body: JSON.stringify({ keyType, keySuffix }),
1964
+ // A task that exited 0 proves the login works, so it may lift an auth bench.
1965
+ // `authFence` (read before the task started) stops a late (timed-out) report,
1966
+ // from this or any other worker, from clearing failures recorded after it.
1967
+ body: JSON.stringify({ keyType, keySuffix, clearAuthBench: true, authFence }),
1968
+ signal: AbortSignal.timeout(timeoutMs),
1962
1969
  });
1963
1970
  if (resp.ok) {
1964
1971
  const data = (await resp.json()) as { cleared?: boolean };
@@ -1973,6 +1980,49 @@ async function reportKeyClearRateLimit(
1973
1980
  }
1974
1981
  }
1975
1982
 
1983
+ /**
1984
+ * Report a finished task's credential outcome: a success resets the auth-failure
1985
+ * count (and lifts an auth bench); a counted Codex auth failure adds to it.
1986
+ * Awaited by the caller before it processes the next completion, so the API sees
1987
+ * success and failure reports in completion order. A success carries the
1988
+ * `authFence` the task read before it started: when its report times out and
1989
+ * lands late, the API keeps every auth failure recorded after that fence.
1990
+ * Without a fence, a success clears only an ordinary rate limit. Bounded, never throws.
1991
+ */
1992
+ export async function reportKeyCompletionOutcome(opts: {
1993
+ apiUrl: string;
1994
+ apiKey: string;
1995
+ credential: { keyType: string; keySuffix: string; keyIndex: number; authFence?: number };
1996
+ taskId: string;
1997
+ exitCode: number;
1998
+ failureReason: string | undefined;
1999
+ timeoutMs?: number;
2000
+ }): Promise<void> {
2001
+ const { apiUrl, apiKey, credential, taskId, exitCode, failureReason, timeoutMs } = opts;
2002
+ if (exitCode === 0) {
2003
+ await reportKeyClearRateLimit(
2004
+ apiUrl,
2005
+ apiKey,
2006
+ credential.keyType,
2007
+ credential.keySuffix,
2008
+ credential.authFence,
2009
+ timeoutMs,
2010
+ );
2011
+ return;
2012
+ }
2013
+ if (credential.keyType === "CODEX_OAUTH" && isCodexAuthFailureReason(failureReason)) {
2014
+ await reportKeyAuthFailure(
2015
+ apiUrl,
2016
+ apiKey,
2017
+ credential.keyType,
2018
+ credential.keySuffix,
2019
+ credential.keyIndex,
2020
+ taskId,
2021
+ timeoutMs,
2022
+ );
2023
+ }
2024
+ }
2025
+
1976
2026
  /**
1977
2027
  * Supersede a task via the API (for graceful shutdown / context-limit /
1978
2028
  * operator-triggered). Returns `{ ok: true, resumeTaskId }` on success.
@@ -2342,7 +2392,7 @@ export interface RunnerOptions {
2342
2392
  }
2343
2393
 
2344
2394
  /** Running task state for parallel execution */
2345
- interface RunningTask {
2395
+ export interface RunningTask {
2346
2396
  taskId: string;
2347
2397
  session: ProviderSession;
2348
2398
  logFile: string;
@@ -2361,6 +2411,8 @@ interface RunningTask {
2361
2411
  keyType: string;
2362
2412
  keySuffix: string;
2363
2413
  keyIndex: number;
2414
+ /** `authFailureFence` read before the task started (Codex pool only). */
2415
+ authFence?: number;
2364
2416
  };
2365
2417
  /**
2366
2418
  * Harness provider this session was actually spawned/resumed on, snapshotted
@@ -2392,7 +2444,7 @@ interface RunningTask {
2392
2444
  }
2393
2445
 
2394
2446
  /** Runner state for tracking concurrent tasks */
2395
- interface RunnerState {
2447
+ export interface RunnerState {
2396
2448
  activeTasks: Map<string, RunningTask>;
2397
2449
  maxConcurrent: number;
2398
2450
  startedAt: number;
@@ -2909,6 +2961,16 @@ interface PollOptions {
2909
2961
  pollInterval: number;
2910
2962
  pollTimeout: number;
2911
2963
  since?: string; // Optional: for filtering finished tasks
2964
+ /** Live harness provider; keys the MODEL_TIER_* overrides sent with the poll. */
2965
+ harnessProvider?: ProviderName;
2966
+ }
2967
+
2968
+ /**
2969
+ * The worker's own MODEL_TIER_* overrides, parsed from its process env (not
2970
+ * the swarm_config-merged env: global config is already visible server-side).
2971
+ */
2972
+ function workerModelTierOverrides(provider: ProviderName): ModelTierOverrides {
2973
+ return parseWorkerModelTierOverrides(process.env, provider);
2912
2974
  }
2913
2975
 
2914
2976
  type RequesterProfile = NonNullable<Trigger["requestedBy"]>;
@@ -2940,6 +3002,86 @@ export async function buildRequesterProfilePrompt(
2940
3002
  return result.skipped ? "" : result.text.trim();
2941
3003
  }
2942
3004
 
3005
+ /** A non-2xx answer from `POST /api/agents`; carries the status for retry triage. */
3006
+ export class AgentRegistrationHttpError extends Error {
3007
+ readonly status: number;
3008
+ readonly body: string;
3009
+ constructor(status: number, body: string) {
3010
+ super(`Failed to register agent: ${status} ${body}`);
3011
+ this.name = "AgentRegistrationHttpError";
3012
+ this.status = status;
3013
+ this.body = body;
3014
+ }
3015
+ }
3016
+
3017
+ /**
3018
+ * Whether a boot registration failure is worth retrying. A freshly deployed
3019
+ * API can answer `500 {"error":"database is locked"}` for minutes while boot
3020
+ * work holds the SQLite write lock, and is unreachable while it restarts;
3021
+ * both clear on their own. A 4xx (bad key, bad payload) never will.
3022
+ */
3023
+ export function isRetryableRegistrationError(err: unknown): boolean {
3024
+ if (err instanceof AgentRegistrationHttpError) {
3025
+ return (
3026
+ err.status >= 500 ||
3027
+ err.status === 408 ||
3028
+ err.status === 429 ||
3029
+ /database is locked/i.test(err.body)
3030
+ );
3031
+ }
3032
+ // Anything else escaped fetch itself: connection refused, reset, DNS, timeout.
3033
+ return true;
3034
+ }
3035
+
3036
+ export const REGISTRATION_RETRY_BUDGET_MS = 5 * 60_000;
3037
+ const REGISTRATION_RETRY_BASE_DELAY_MS = 2_000;
3038
+ const REGISTRATION_RETRY_MAX_DELAY_MS = 30_000;
3039
+
3040
+ /**
3041
+ * Boot registration with bounded exponential backoff (2s doubling to 30s,
3042
+ * with jitter) inside a total wall-clock budget. Throws the last error on a
3043
+ * non-retryable failure or once the next wait would overrun the budget, so
3044
+ * the caller still exits and a restart policy can take over.
3045
+ */
3046
+ export async function registerAgentWithRetry(
3047
+ register: () => Promise<{ serverCapabilities?: string[] }>,
3048
+ opts: {
3049
+ label: string;
3050
+ budgetMs?: number;
3051
+ baseDelayMs?: number;
3052
+ maxDelayMs?: number;
3053
+ sleep?: (ms: number) => Promise<void>;
3054
+ now?: () => number;
3055
+ },
3056
+ ): Promise<{ serverCapabilities?: string[] }> {
3057
+ const budgetMs = opts.budgetMs ?? REGISTRATION_RETRY_BUDGET_MS;
3058
+ const baseDelayMs = opts.baseDelayMs ?? REGISTRATION_RETRY_BASE_DELAY_MS;
3059
+ const maxDelayMs = opts.maxDelayMs ?? REGISTRATION_RETRY_MAX_DELAY_MS;
3060
+ const sleep = opts.sleep ?? ((ms: number) => Bun.sleep(ms));
3061
+ const now = opts.now ?? Date.now;
3062
+ const startedAt = now();
3063
+ for (let attempt = 1; ; attempt++) {
3064
+ try {
3065
+ return await register();
3066
+ } catch (err) {
3067
+ if (!isRetryableRegistrationError(err)) throw err;
3068
+ const backoff = Math.min(maxDelayMs, baseDelayMs * 2 ** (attempt - 1));
3069
+ const delay = Math.round(backoff * (0.8 + Math.random() * 0.4));
3070
+ const elapsed = now() - startedAt;
3071
+ if (elapsed + delay > budgetMs) {
3072
+ console.error(
3073
+ `[${opts.label}] Registration still failing after ${attempt} attempts in ${Math.round(elapsed / 1000)}s; giving up`,
3074
+ );
3075
+ throw err;
3076
+ }
3077
+ console.warn(
3078
+ `[${opts.label}] Registration attempt ${attempt} failed (${err}); retrying in ${Math.round(delay / 1000)}s`,
3079
+ );
3080
+ await sleep(delay);
3081
+ }
3082
+ }
3083
+ }
3084
+
2943
3085
  /** Register agent via HTTP API. Exported so tests can exercise the real boot ordering. */
2944
3086
  export async function registerAgent(opts: {
2945
3087
  apiUrl: string;
@@ -2990,12 +3132,14 @@ export async function registerAgent(opts: {
2990
3132
  provider,
2991
3133
  harness_provider: harnessProvider,
2992
3134
  runtimeInstanceId: opts.runtimeInstanceId,
3135
+ modelTierOverrides: workerModelTierOverrides(harnessProvider),
3136
+ harnessCliVersion: (await probeHarnessCliVersion(harnessProvider)) ?? undefined,
2993
3137
  }),
2994
3138
  });
2995
3139
 
2996
3140
  if (!response.ok) {
2997
3141
  const error = await response.text();
2998
- throw new Error(`Failed to register agent: ${response.status} ${error}`);
3142
+ throw new AgentRegistrationHttpError(response.status, error);
2999
3143
  }
3000
3144
 
3001
3145
  // The register response carries the SERVER's enabled capability flags (which
@@ -3035,6 +3179,14 @@ async function pollForTrigger(opts: PollOptions): Promise<Trigger | null> {
3035
3179
 
3036
3180
  async function pollForTriggerOnce(opts: PollOptions): Promise<Trigger | null> {
3037
3181
  const startTime = Date.now();
3182
+ // Keep the process-wide model catalog fresh (TTL-cached, never throws) so
3183
+ // context windows, pricing and reasoning levels cover models added to the
3184
+ // API's catalog after this worker booted.
3185
+ void refreshRuntimeModelCatalog({
3186
+ apiUrl: opts.apiUrl,
3187
+ apiKey: opts.apiKey,
3188
+ agentId: opts.agentId,
3189
+ });
3038
3190
  const headers: Record<string, string> = {
3039
3191
  "X-Agent-ID": opts.agentId,
3040
3192
  };
@@ -3045,6 +3197,11 @@ async function pollForTriggerOnce(opts: PollOptions): Promise<Trigger | null> {
3045
3197
  headers.Authorization = `Bearer ${opts.apiKey}`;
3046
3198
  }
3047
3199
  injectTraceContext(headers);
3200
+ if (opts.harnessProvider) {
3201
+ headers["X-Model-Tier-Overrides"] = encodeURIComponent(
3202
+ JSON.stringify(workerModelTierOverrides(opts.harnessProvider)),
3203
+ );
3204
+ }
3048
3205
 
3049
3206
  while (Date.now() - startTime < opts.pollTimeout) {
3050
3207
  try {
@@ -3527,6 +3684,9 @@ async function spawnProviderProcess(
3527
3684
  taskId?: string;
3528
3685
  model?: string;
3529
3686
  modelTier?: string;
3687
+ /** Server claim-time resolution (task.resolvedModel); wins over the local one. */
3688
+ resolvedModel?: string;
3689
+ modelSource?: string;
3530
3690
  effort?: ReasoningEffort;
3531
3691
  resumeSessionId?: string;
3532
3692
  harnessProvider: ProviderName;
@@ -3562,7 +3722,7 @@ async function spawnProviderProcess(
3562
3722
  opts.apiKey,
3563
3723
  opts.agentId,
3564
3724
  process.env,
3565
- opts.model,
3725
+ opts.resolvedModel || opts.model,
3566
3726
  {
3567
3727
  repoId: sessionRepo?.id,
3568
3728
  provider: adapter.name as ProviderName,
@@ -3573,8 +3733,7 @@ async function spawnProviderProcess(
3573
3733
  ));
3574
3734
  } catch (err) {
3575
3735
  if (err instanceof ModelWindowExhaustedError && realTaskId) {
3576
- const modelLabel = err.model.charAt(0).toUpperCase() + err.model.slice(1);
3577
- const reason = `No ${err.keyType} key has ${modelLabel} capacity until ${err.earliestResetAt ?? "unknown"}. Re-dispatch with another model or modelTier.`;
3736
+ const reason = err.message;
3578
3737
  console.warn(`[${opts.role}] ${reason}`);
3579
3738
  await ensureTaskFinished(
3580
3739
  { apiUrl: opts.apiUrl, apiKey: opts.apiKey, agentId: opts.agentId },
@@ -3619,8 +3778,44 @@ async function spawnProviderProcess(
3619
3778
  harnessProvider: opts.harnessProvider,
3620
3779
  env: freshEnv,
3621
3780
  });
3622
- const taskModel = taskModelSelection.model || "";
3623
- const model = taskModel || configModel || "";
3781
+ // The server resolves the model at claim time (worker-env > tier-config >
3782
+ // tier-default, `latest:` aliases, guardrails) and records it on the task.
3783
+ // The local resolution stays as a check: a difference usually means the
3784
+ // server saw a stale MODEL_TIER_* override or a swarm_config tier value.
3785
+ if (
3786
+ opts.resolvedModel &&
3787
+ taskModelSelection.model &&
3788
+ taskModelSelection.model !== opts.resolvedModel
3789
+ ) {
3790
+ console.log(
3791
+ `[${opts.role}] model resolution mismatch for task ${opts.taskId ?? "?"}: server=${opts.resolvedModel} (${opts.modelSource ?? "?"}) local=${taskModelSelection.model}; using server value`,
3792
+ );
3793
+ }
3794
+ const taskModel = opts.resolvedModel || taskModelSelection.model || "";
3795
+ // Never start a CLI with a model from another harness family (runbooks/model-tiers.md).
3796
+ const spawnModel = guardSpawnModel({
3797
+ taskModel,
3798
+ configModel,
3799
+ harness: opts.harnessProvider,
3800
+ role: opts.role,
3801
+ });
3802
+ if (spawnModel.kind === "mismatch") {
3803
+ console.warn(`[${opts.role}] ${spawnModel.reason}`);
3804
+ if (realTaskId) {
3805
+ await ensureTaskFinished(
3806
+ { apiUrl: opts.apiUrl, apiKey: opts.apiKey, agentId: opts.agentId },
3807
+ opts.role,
3808
+ realTaskId,
3809
+ 1,
3810
+ spawnModel.reason,
3811
+ undefined,
3812
+ opts.harnessProvider,
3813
+ );
3814
+ }
3815
+ throw new Error(spawnModel.reason);
3816
+ }
3817
+ if (spawnModel.warning) console.warn(spawnModel.warning);
3818
+ const model = spawnModel.model;
3624
3819
 
3625
3820
  // Resolve Codex OAuth pool slot BEFORE building ProviderSessionConfig so we
3626
3821
  // can pass codexSlot through and the adapter writes token refreshes back to
@@ -4389,6 +4584,7 @@ async function spawnProviderProcess(
4389
4584
  keyType: primarySelection.keyType,
4390
4585
  keySuffix: primarySelection.keySuffix,
4391
4586
  keyIndex: primarySelection.index,
4587
+ authFence: primarySelection.authFailureFence,
4392
4588
  }
4393
4589
  : undefined;
4394
4590
 
@@ -4428,8 +4624,8 @@ async function spawnProviderProcess(
4428
4624
  return runningTask;
4429
4625
  }
4430
4626
 
4431
- /** Check for completed processes and remove them from active tasks */
4432
- async function checkCompletedProcesses(
4627
+ /** Check for completed processes and remove them from active tasks. Exported for tests. */
4628
+ export async function checkCompletedProcesses(
4433
4629
  state: RunnerState,
4434
4630
  role: string,
4435
4631
  apiConfig?: ApiConfig,
@@ -4517,77 +4713,26 @@ async function checkCompletedProcesses(
4517
4713
  console.log(`[${role}] Detected error for task ${taskId.slice(0, 8)}: ${failureReason}`);
4518
4714
  }
4519
4715
 
4520
- // If rate-limited and we know which key was used, report it.
4521
- // Codex adapter prefixes failure reasons with `[rate-limit]` /
4522
- // `[usage-limit]` (see codex-adapter.formatTerminalError); Claude
4523
- // surfaces "rate limit" / "hit your limit" via SessionErrorTracker.
4524
- //
4525
- // classifyRateLimitOutcome tests model-scoped windows (Fable/Opus/
4526
- // Sonnet weekly limits) before the legacy key-wide gate, so a
4527
- // model-scoped rejection blocks only that model family on this key —
4528
- // never the whole key — via report-rate-limit-windows instead of
4529
- // report-rate-limit. A key-wide rejection seen in the same session is
4530
- // still reported alongside it (windows are independent). The session's
4531
- // window telemetry and the classified model rejection go out as ONE
4532
- // payload, so an older `allowed` snapshot never overwrites the terminal
4533
- // rejection. The post is awaited so it completes before the task
4534
- // finishes.
4716
+ // Record whether this worker's CLI accepts the model (harness_model_support).
4717
+ void reportHarnessModelOutcome({
4718
+ apiUrl: apiConfig.apiUrl,
4719
+ apiKey: apiConfig.apiKey,
4720
+ agentId: apiConfig.agentId,
4721
+ harness: harnessProvider,
4722
+ model,
4723
+ exitCode: result.exitCode,
4724
+ failureReason,
4725
+ });
4726
+
4535
4727
  if (credentialInfo) {
4536
- const outcome = classifyRateLimitOutcome(
4537
- result,
4728
+ await reportKeyCompletionOutcome({
4729
+ apiUrl: apiConfig.apiUrl,
4730
+ apiKey: apiConfig.apiKey,
4731
+ credential: credentialInfo,
4732
+ taskId,
4733
+ exitCode: result.exitCode,
4538
4734
  failureReason,
4539
- Date.now(),
4540
- state.codexCreditsExhaustedCooldownMs,
4541
- );
4542
- const keyRateLimitedUntil =
4543
- outcome.kind === "key"
4544
- ? outcome.rateLimitedUntil
4545
- : outcome.kind === "model"
4546
- ? outcome.keyRateLimitedUntil
4547
- : undefined;
4548
- if (keyRateLimitedUntil) {
4549
- console.log(`[credentials] Rate limit reset: ${keyRateLimitedUntil}`);
4550
- reportKeyRateLimit(
4551
- apiConfig.apiUrl,
4552
- apiConfig.apiKey,
4553
- credentialInfo.keyType,
4554
- credentialInfo.keySuffix,
4555
- credentialInfo.keyIndex,
4556
- keyRateLimitedUntil,
4557
- ).catch(() => {});
4558
- }
4559
- if (outcome.kind === "model") {
4560
- const resetsAtIso = new Date(outcome.resetsAtSec * 1000).toISOString();
4561
- const blockKey = `${credentialInfo.keyType}:${credentialInfo.keyIndex}:${outcome.window}`;
4562
- state.modelWindowBlocks.set(blockKey, outcome.resetsAtSec * 1000);
4563
- console.log(
4564
- `[credential] ${outcome.model} weekly window exhausted (${outcome.window}) on key #${credentialInfo.keyIndex} until ${resetsAtIso}`,
4565
- );
4566
- }
4567
-
4568
- const finalWindows = buildFinalRateLimitWindows(
4569
- result.rateLimitWindows,
4570
- outcome,
4571
- new Date().toISOString(),
4572
- );
4573
- if (finalWindows) {
4574
- const report = reportKeyRateLimitWindows(
4575
- apiConfig.apiUrl,
4576
- apiConfig.apiKey,
4577
- credentialInfo.keyType,
4578
- credentialInfo.keySuffix,
4579
- credentialInfo.keyIndex,
4580
- finalWindows,
4581
- outcome.kind !== "model",
4582
- ).catch((err) => {
4583
- console.warn(
4584
- `[credential] Failed to report rate-limit windows: ${err instanceof Error ? err.message : String(err)}`,
4585
- );
4586
- });
4587
- // A model rejection gates admission on other workers: land it before
4588
- // the task finishes. Plain telemetry stays fire-and-forget.
4589
- if (outcome.kind === "model") await report;
4590
- }
4735
+ });
4591
4736
  }
4592
4737
  let bridgeDiagnostics: Awaited<ReturnType<typeof getBridgeFailureDiagnostics>> | undefined;
4593
4738
  if (result.exitCode !== 0 && harnessProvider === "claude" && workingDir) {
@@ -4603,19 +4748,34 @@ async function checkCompletedProcesses(
4603
4748
  bridgeDiagnostics?.paneTail != null
4604
4749
  ? `Claude bridge final tmux pane tail (${bridgeDiagnostics.artifactPath}):\n${bridgeDiagnostics.paneTail}`
4605
4750
  : undefined;
4606
- await ensureTaskFinished(
4607
- apiConfig,
4608
- role,
4609
- taskId,
4610
- result.exitCode,
4611
- failureReason,
4612
- // Runner-buffered last assistant text is a harness-agnostic fallback
4613
- // for adapters that never populate `ProviderResult.output` (Codex
4614
- // today, any future adapter). Empty buffer -> `undefined`, byte
4615
- // identical to pre-fix behavior.
4616
- resolveProviderOutput(result, assistantText),
4617
- harnessProvider,
4618
- bridgeFailureDiagnostics,
4751
+ // Reports that gate admission on other workers (a seat mismatch, a
4752
+ // model window rejection) land before the task finishes.
4753
+ await reportCredentialOutcomeThenFinish(
4754
+ {
4755
+ apiUrl: apiConfig.apiUrl,
4756
+ apiKey: apiConfig.apiKey,
4757
+ credentialInfo,
4758
+ result,
4759
+ failureReason,
4760
+ model,
4761
+ codexCreditsExhaustedCooldownMs: state.codexCreditsExhaustedCooldownMs,
4762
+ modelWindowBlocks: state.modelWindowBlocks,
4763
+ },
4764
+ () =>
4765
+ ensureTaskFinished(
4766
+ apiConfig,
4767
+ role,
4768
+ taskId,
4769
+ result.exitCode,
4770
+ failureReason,
4771
+ // Runner-buffered last assistant text is a harness-agnostic fallback
4772
+ // for adapters that never populate `ProviderResult.output` (Codex
4773
+ // today, any future adapter). Empty buffer -> `undefined`, byte
4774
+ // identical to pre-fix behavior.
4775
+ resolveProviderOutput(result, assistantText),
4776
+ harnessProvider,
4777
+ bridgeFailureDiagnostics,
4778
+ ),
4619
4779
  );
4620
4780
 
4621
4781
  telemetry.taskEvent("session_completed", {
@@ -4646,15 +4806,6 @@ async function checkCompletedProcesses(
4646
4806
  }
4647
4807
  state.tasksProcessed += 1;
4648
4808
 
4649
- if (result.exitCode === 0 && credentialInfo) {
4650
- reportKeyClearRateLimit(
4651
- apiConfig.apiUrl,
4652
- apiConfig.apiKey,
4653
- credentialInfo.keyType,
4654
- credentialInfo.keySuffix,
4655
- ).catch(() => {});
4656
- }
4657
-
4658
4809
  ensure({
4659
4810
  id: "worker_process_finished",
4660
4811
  flow: "task",
@@ -5271,18 +5422,22 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
5271
5422
  }
5272
5423
  };
5273
5424
  try {
5274
- const reg = await registerAgent({
5275
- apiUrl,
5276
- apiKey,
5277
- agentId,
5278
- name: agentName,
5279
- role,
5280
- isLead,
5281
- capabilities,
5282
- maxTasks: maxConcurrent,
5283
- harnessProvider: bootProvider,
5284
- runtimeInstanceId,
5285
- });
5425
+ const reg = await registerAgentWithRetry(
5426
+ () =>
5427
+ registerAgent({
5428
+ apiUrl,
5429
+ apiKey,
5430
+ agentId,
5431
+ name: agentName,
5432
+ role,
5433
+ isLead,
5434
+ capabilities,
5435
+ maxTasks: maxConcurrent,
5436
+ harnessProvider: bootProvider,
5437
+ runtimeInstanceId,
5438
+ }),
5439
+ { label: role },
5440
+ );
5286
5441
  lastServerCapsRefreshAt = Date.now();
5287
5442
  // Rebuilds the prompt immediately: the initial build above ran before
5288
5443
  // registration (serverCapabilities unknown), and the later identity
@@ -5911,6 +6066,8 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
5911
6066
  taskId: task.id,
5912
6067
  model: (task as { model?: string }).model,
5913
6068
  modelTier: (task as { modelTier?: string }).modelTier,
6069
+ resolvedModel: (task as { resolvedModel?: string }).resolvedModel,
6070
+ modelSource: (task as { modelSource?: string }).modelSource,
5914
6071
  effort: (task as { effort?: ReasoningEffort }).effort,
5915
6072
  harnessProvider: state.harnessProvider,
5916
6073
  cwd: resumeCwd,
@@ -6127,6 +6284,7 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
6127
6284
  pollInterval: PollIntervalMs,
6128
6285
  runtimeInstanceId,
6129
6286
  pollTimeout: effectiveTimeout,
6287
+ harnessProvider: state.harnessProvider,
6130
6288
  });
6131
6289
 
6132
6290
  if (trigger) {
@@ -6407,6 +6565,16 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
6407
6565
  .join("\n\n");
6408
6566
  const taskSystemPrompt = taskPromptParts + cwdWarning;
6409
6567
 
6568
+ // The server refused the task's explicit model for this worker's CLI
6569
+ // version (harness_model_support = unsupported): fail fast, no spawn.
6570
+ const modelUnsupported = (trigger.task as { modelUnsupported?: string } | undefined)
6571
+ ?.modelUnsupported;
6572
+ if (trigger.taskId && modelUnsupported) {
6573
+ console.log(`[${role}] ${modelUnsupported}`);
6574
+ await ensureTaskFinished(apiConfig, role, trigger.taskId, 1, modelUnsupported);
6575
+ continue;
6576
+ }
6577
+
6410
6578
  iteration++;
6411
6579
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
6412
6580
  const taskIdSlice = trigger.taskId?.slice(0, 8) || "notask";
@@ -6472,6 +6640,9 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
6472
6640
  taskId: trigger.taskId,
6473
6641
  model: taskModel,
6474
6642
  modelTier: taskModelTier,
6643
+ resolvedModel: (trigger.task as { resolvedModel?: string } | undefined)
6644
+ ?.resolvedModel,
6645
+ modelSource: (trigger.task as { modelSource?: string } | undefined)?.modelSource,
6475
6646
  effort: taskEffort,
6476
6647
  harnessProvider: state.harnessProvider,
6477
6648
  cwd: effectiveCwd,