@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
@@ -4,6 +4,8 @@ import {
4
4
  MAX_RATE_LIMIT_RESET_MS,
5
5
  parseRateLimitWindowTelemetry,
6
6
  parseStderrForErrors,
7
+ redactRateLimitEvent,
8
+ redactRateLimitEventLine,
7
9
  SessionErrorTracker,
8
10
  trackErrorFromJson,
9
11
  } from "../utils/error-tracker";
@@ -449,6 +451,69 @@ describe("SessionErrorTracker — model-scoped rejection (Fable weekly window)",
449
451
  });
450
452
  });
451
453
 
454
+ // Verbatim fixture from task 91175100-349d-4284-a0c1-c41d17ddf8fa, session_logs line 4,
455
+ // 2026-09-30. Fable on a Claude Team standard seat: rejected, no rateLimitType,
456
+ // errorCode "credits_required".
457
+ const FIXTURE_CREDITS_REQUIRED = {
458
+ type: "rate_limit_event",
459
+ rate_limit_info: {
460
+ status: "rejected",
461
+ resetsAt: 1790812800,
462
+ overageDisabledReason: "member_zero_credit_limit",
463
+ isUsingOverage: false,
464
+ errorCode: "credits_required",
465
+ canUserPurchaseCredits: false,
466
+ hasChargeableSavedPaymentMethod: true,
467
+ },
468
+ uuid: "bff51e54-9777-458a-aceb-b50aaa4255b5",
469
+ session_id: "9d326326-0c59-4b00-b819-b9b7dd3bf99f",
470
+ };
471
+
472
+ describe("SessionErrorTracker — credits_required rejection", () => {
473
+ test("credits_required is a seat mismatch, never a key-wide or model-scoped limit", () => {
474
+ const tracker = new SessionErrorTracker();
475
+ tracker.processRateLimitEvent(FIXTURE_CREDITS_REQUIRED);
476
+ expect(tracker.getRateLimitResetAt()).toBeUndefined();
477
+ expect(tracker.getModelRateLimit()).toBeUndefined();
478
+ expect(tracker.getCreditsRequired()?.overageDisabledReason).toBe("member_zero_credit_limit");
479
+ });
480
+
481
+ test("a rejected event with no rateLimitType and no errorCode stays key-wide", () => {
482
+ const tracker = new SessionErrorTracker();
483
+ const { errorCode: _errorCode, ...info } = FIXTURE_CREDITS_REQUIRED.rate_limit_info;
484
+ tracker.processRateLimitEvent({ ...FIXTURE_CREDITS_REQUIRED, rate_limit_info: info });
485
+ expect(tracker.getRateLimitResetAt()).toBeDefined();
486
+ expect(tracker.getCreditsRequired()).toBeUndefined();
487
+ });
488
+ });
489
+
490
+ describe("redactRateLimitEvent — seat payload egress", () => {
491
+ test("drops the seat billing fields and keeps the rest", () => {
492
+ const redacted = redactRateLimitEvent(FIXTURE_CREDITS_REQUIRED);
493
+ expect(redacted.rate_limit_info).toEqual({
494
+ status: "rejected",
495
+ resetsAt: FIXTURE_CREDITS_REQUIRED.rate_limit_info.resetsAt,
496
+ isUsingOverage: false,
497
+ errorCode: "credits_required",
498
+ });
499
+ expect(redacted.uuid).toBe(FIXTURE_CREDITS_REQUIRED.uuid);
500
+ // The original message is not mutated.
501
+ expect(FIXTURE_CREDITS_REQUIRED.rate_limit_info.overageDisabledReason).toBe(
502
+ "member_zero_credit_limit",
503
+ );
504
+ });
505
+
506
+ test("the line form redacts JSON and returns other lines byte for byte", () => {
507
+ const line = redactRateLimitEventLine(JSON.stringify(FIXTURE_CREDITS_REQUIRED));
508
+ expect(line).not.toContain("overageDisabledReason");
509
+ expect(line).toContain('"errorCode":"credits_required"');
510
+ const other = '{"type":"assistant", "message":{}}';
511
+ expect(redactRateLimitEventLine(other)).toBe(other);
512
+ const plainEvent = '{"type":"rate_limit_event","rate_limit_info":{"status":"allowed"}}';
513
+ expect(redactRateLimitEventLine(plainEvent)).toBe(plainEvent);
514
+ });
515
+ });
516
+
452
517
  describe("trackErrorFromJson — rate_limit_event routing", () => {
453
518
  test("routes rate_limit_event to processRateLimitEvent, stashes reset time", () => {
454
519
  const tracker = new SessionErrorTracker();
@@ -5,6 +5,7 @@ import {
5
5
  buildFinalRateLimitWindows,
6
6
  classifyRateLimitOutcome,
7
7
  } from "../commands/rate-limit-outcome";
8
+ import { SessionErrorTracker } from "../utils/error-tracker";
8
9
 
9
10
  describe("classifyRateLimitOutcome", () => {
10
11
  test("event source: modelRateLimit set gives kind 'model' with source 'event'", () => {
@@ -323,3 +324,92 @@ describe("buildFinalRateLimitWindows — cross-worker recovery", () => {
323
324
  ).toEqual([0]);
324
325
  });
325
326
  });
327
+
328
+ describe("classifyRateLimitOutcome — seat mismatch", () => {
329
+ const nowMs = Date.parse("2026-09-30T12:00:00.000Z");
330
+ const creditsRequired = {
331
+ observedAt: "2026-09-30T11:59:00.000Z",
332
+ overageDisabledReason: "member_zero_credit_limit",
333
+ };
334
+ const observedText = "Fable 5.1 requires usage credits. Switch to another model to continue.";
335
+
336
+ test("the observed failure text is a seat outcome from text", () => {
337
+ expect(classifyRateLimitOutcome({}, observedText, nowMs)).toEqual({
338
+ kind: "seat",
339
+ model: "fable",
340
+ source: "text",
341
+ });
342
+ });
343
+
344
+ test("a credits_required event with the task model family is a seat outcome from event", () => {
345
+ expect(
346
+ classifyRateLimitOutcome({ creditsRequired }, undefined, nowMs, undefined, "fable"),
347
+ ).toEqual({ kind: "seat", model: "fable", source: "event" });
348
+ });
349
+
350
+ test("a credits_required event with no task model family and no text is not a seat outcome", () => {
351
+ expect(classifyRateLimitOutcome({ creditsRequired }, undefined, nowMs).kind).not.toBe("seat");
352
+ });
353
+
354
+ test("a seat outcome keeps an earlier key-wide rejection from the real tracker", () => {
355
+ // The tracker reads the real clock and clamps past resets, so use a real "now" here.
356
+ const nowMs = Date.now();
357
+ const tracker = new SessionErrorTracker();
358
+ const fiveHourResetsAtSec = Math.floor(nowMs / 1000) + 2 * 60 * 60;
359
+ tracker.processRateLimitEvent({
360
+ type: "rate_limit_event",
361
+ rate_limit_info: {
362
+ status: "rejected",
363
+ resetsAt: fiveHourResetsAtSec,
364
+ rateLimitType: "five_hour",
365
+ },
366
+ });
367
+ tracker.processRateLimitEvent({
368
+ type: "rate_limit_event",
369
+ rate_limit_info: {
370
+ status: "rejected",
371
+ resetsAt: 1790812800,
372
+ overageDisabledReason: "member_zero_credit_limit",
373
+ isUsingOverage: false,
374
+ errorCode: "credits_required",
375
+ },
376
+ });
377
+ const rateLimitResetAt = tracker.getRateLimitResetAt();
378
+ const creditsRequired = tracker.getCreditsRequired();
379
+ expect(rateLimitResetAt).toBeDefined();
380
+ expect(creditsRequired).toBeDefined();
381
+
382
+ const outcome = classifyRateLimitOutcome(
383
+ { rateLimitResetAt, creditsRequired },
384
+ undefined,
385
+ nowMs,
386
+ undefined,
387
+ "fable",
388
+ );
389
+ expect(outcome).toEqual({
390
+ kind: "seat",
391
+ model: "fable",
392
+ source: "event",
393
+ keyRateLimitedUntil: new Date(fiveHourResetsAtSec * 1000).toISOString(),
394
+ });
395
+ });
396
+
397
+ test("a seat outcome with no key-wide rejection carries no key reset", () => {
398
+ const outcome = classifyRateLimitOutcome({ creditsRequired }, observedText, nowMs);
399
+ expect(outcome).not.toHaveProperty("keyRateLimitedUntil");
400
+ expect(outcome).not.toHaveProperty("rateLimitedUntil");
401
+ });
402
+
403
+ test("buildFinalRateLimitWindows returns the session windows unchanged for a seat outcome", () => {
404
+ const windows = {
405
+ five_hour: { status: "allowed", utilization: 0.24, lastSeenAt: "2026-09-30T11:00:00.000Z" },
406
+ };
407
+ expect(
408
+ buildFinalRateLimitWindows(
409
+ windows,
410
+ { kind: "seat", model: "fable", source: "event" },
411
+ "2026-09-30T12:00:00.000Z",
412
+ ),
413
+ ).toBe(windows);
414
+ });
415
+ });
@@ -82,7 +82,11 @@ const LEAD_ONLY_VERBS: PermissionVerb[] = [
82
82
  ];
83
83
 
84
84
  const OPERATOR_ONLY_VERBS: PermissionVerb[] = [];
85
- const LEAD_OR_OPERATOR_VERBS: PermissionVerb[] = ["extension.write", "extension.activate"];
85
+ const LEAD_OR_OPERATOR_OR_USER_VERBS: PermissionVerb[] = ["extension.write", "extension.activate"];
86
+
87
+ const LEAD_OR_OPERATOR_VERBS: PermissionVerb[] = ["models.catalog.write"];
88
+
89
+ const AGENT_OR_OPERATOR_VERBS: PermissionVerb[] = ["models.harness-support.write"];
86
90
 
87
91
  const LEAD_OR_RESOURCE_OWNER_VERBS: PermissionVerb[] = [
88
92
  "memory.edit.any",
@@ -116,7 +120,11 @@ const REQUESTER_OWNS_TASK_VERBS: PermissionVerb[] = [
116
120
  "task.action.own",
117
121
  ];
118
122
 
119
- const COMPOSITE_VERBS: PermissionVerb[] = ["memory.delete.any", "task.fs.mutate"];
123
+ const COMPOSITE_VERBS: PermissionVerb[] = [
124
+ "memory.delete.any",
125
+ "task.fs.mutate",
126
+ "task.progress.write",
127
+ ];
120
128
 
121
129
  // ── Resource fixtures ────────────────────────────────────────────────────────
122
130
 
@@ -178,7 +186,9 @@ describe("verb-group partition", () => {
178
186
  ...REQUESTER_OWNS_TASK_VERBS,
179
187
  ...COMPOSITE_VERBS,
180
188
  ...OPERATOR_ONLY_VERBS,
189
+ ...LEAD_OR_OPERATOR_OR_USER_VERBS,
181
190
  ...LEAD_OR_OPERATOR_VERBS,
191
+ ...AGENT_OR_OPERATOR_VERBS,
182
192
  ];
183
193
  expect(new Set(grouped).size).toBe(grouped.length);
184
194
  expect(grouped.sort()).toEqual([...PERMISSION_VERBS].sort());
@@ -251,13 +261,47 @@ describe("lead-or-operator-or-user verbs", () => {
251
261
  foreignUser: true,
252
262
  operator: true,
253
263
  };
254
- for (const verb of LEAD_OR_OPERATOR_VERBS) {
264
+ for (const verb of LEAD_OR_OPERATOR_OR_USER_VERBS) {
255
265
  test(`${verb}: lead, operator, or user allowed`, () => {
256
266
  expectDecisions(verb, { kind: "none" }, expected);
257
267
  });
258
268
  }
259
269
  });
260
270
 
271
+ describe("agent-or-operator verbs", () => {
272
+ const expected: Expected = {
273
+ lead: true,
274
+ worker: true,
275
+ ownerWorker: true,
276
+ creatorWorker: true,
277
+ userRequester: false,
278
+ foreignUser: false,
279
+ operator: true,
280
+ };
281
+ for (const verb of AGENT_OR_OPERATOR_VERBS) {
282
+ test(`${verb}: any registered agent or the operator allowed, dashboard users denied`, () => {
283
+ expectDecisions(verb, { kind: "none" }, expected);
284
+ });
285
+ }
286
+ });
287
+
288
+ describe("lead-or-operator verbs", () => {
289
+ const expected: Expected = {
290
+ lead: true,
291
+ worker: false,
292
+ ownerWorker: false,
293
+ creatorWorker: false,
294
+ userRequester: false,
295
+ foreignUser: false,
296
+ operator: true,
297
+ };
298
+ for (const verb of LEAD_OR_OPERATOR_VERBS) {
299
+ test(`${verb}: only the lead or the operator allowed`, () => {
300
+ expectDecisions(verb, { kind: "none" }, expected);
301
+ });
302
+ }
303
+ });
304
+
261
305
  describe("extension ownership", () => {
262
306
  test("workers create extensions and write only their own existing resource", () => {
263
307
  const worker = { kind: "agent", agentId: "extension-owner", isLead: false } as const;
@@ -505,6 +549,36 @@ describe("task.fs.mutate composite (operator OR user OR lead OR assignee OR crea
505
549
  });
506
550
  });
507
551
 
552
+ describe("task.progress.write composite (operator OR user OR lead OR assignee OR unassigned)", () => {
553
+ test("the creator and an unrelated worker are denied on an assigned task", () => {
554
+ expectDecisions("task.progress.write", TASK_RESOURCE, {
555
+ lead: true,
556
+ worker: false,
557
+ ownerWorker: true, // assignee (task.agentId)
558
+ creatorWorker: false,
559
+ userRequester: true,
560
+ foreignUser: true,
561
+ operator: true,
562
+ });
563
+ });
564
+
565
+ test("any agent may write an unassigned task", () => {
566
+ expectDecisions(
567
+ "task.progress.write",
568
+ { kind: "task", taskId: "task-2", agentId: null },
569
+ {
570
+ lead: true,
571
+ worker: true,
572
+ ownerWorker: true,
573
+ creatorWorker: true,
574
+ userRequester: true,
575
+ foreignUser: true,
576
+ operator: true,
577
+ },
578
+ );
579
+ });
580
+ });
581
+
508
582
  // ── Audit sink seam ──────────────────────────────────────────────────────────
509
583
 
510
584
  describe("audit sink", () => {
@@ -9,11 +9,14 @@
9
9
  */
10
10
 
11
11
  import { describe, expect, test } from "bun:test";
12
+ import { reasoningLevelsForModel } from "@desplega/model-catalog";
12
13
  import {
13
14
  applyReasoningEffort,
14
15
  REASONING_EFFORT_LEVELS,
16
+ type ReasoningHarness,
15
17
  reasoningCapability,
16
18
  } from "../providers/reasoning-effort";
19
+ import { runtimeCatalogSection } from "../utils/runtime-model-catalog";
17
20
 
18
21
  describe("REASONING_EFFORT_LEVELS", () => {
19
22
  test("is the closed normalized enum", () => {
@@ -243,3 +246,109 @@ describe("applyReasoningEffort — opencode-options shape", () => {
243
246
  });
244
247
  });
245
248
  });
249
+
250
+ // The offered levels, the API's validation and the harness application all read the same
251
+ // answer. This matrix pins it per (harness, model) so the swarm app's pickers, the runtime
252
+ // PATCH and the run-time adapters cannot drift apart.
253
+ describe("harness × model → effort matrix", () => {
254
+ const matrix: {
255
+ harness: "claude" | "codex" | "pi" | "opencode";
256
+ model: string;
257
+ levels: string[];
258
+ }[] = [
259
+ { harness: "claude", model: "claude-opus-5-5", levels: ["low", "medium", "high", "xhigh"] },
260
+ { harness: "claude", model: "claude-sonnet-5-5", levels: ["low", "medium", "high", "xhigh"] },
261
+ { harness: "claude", model: "claude-haiku-4-5", levels: ["off", "low", "medium", "high"] },
262
+ {
263
+ harness: "codex",
264
+ model: "gpt-5.6-sol",
265
+ levels: ["off", "low", "medium", "high", "xhigh", "max"],
266
+ },
267
+ { harness: "codex", model: "gpt-6-astra", levels: ["low", "medium", "high", "xhigh", "max"] },
268
+ { harness: "codex", model: "gpt-5.1-codex", levels: ["low", "medium", "high"] },
269
+ { harness: "pi", model: "openrouter/deepseek/deepseek-v4.1-flash", levels: ["low", "high"] },
270
+ {
271
+ harness: "pi",
272
+ model: "openrouter/anthropic/claude-opus-5.5",
273
+ levels: ["low", "medium", "high", "xhigh"],
274
+ },
275
+ {
276
+ harness: "opencode",
277
+ model: "openrouter/deepseek/deepseek-v4.1-flash",
278
+ levels: ["low", "high"],
279
+ },
280
+ { harness: "pi", model: "openrouter/qwen/qwen3-coder-flash", levels: [] },
281
+ { harness: "claude", model: "totally-custom-model-xyz", levels: [] },
282
+ ];
283
+
284
+ for (const { harness, model, levels } of matrix) {
285
+ test(`${harness} ${model}: ${levels.length > 0 ? levels.join(", ") : "no effort"}`, () => {
286
+ expect(reasoningCapability(harness, model).levels).toEqual(levels);
287
+ // Every offered level is applied, every other level is dropped: what a picker offers is what
288
+ // reaches the harness.
289
+ for (const level of REASONING_EFFORT_LEVELS) {
290
+ const applied = applyReasoningEffort(harness, model, level);
291
+ const takes = levels.includes(level);
292
+ // opencode has no explicit off, so its `off` is a noop even when offered.
293
+ const expectNoop = !takes || (harness === "opencode" && level === "off");
294
+ expect(applied.kind === "noop").toBe(expectNoop);
295
+ }
296
+ });
297
+ }
298
+ });
299
+
300
+ describe("Claude CLI shortnames (the tier defaults) take effort like the model they stand for", () => {
301
+ test("opus, sonnet, haiku and fable resolve to their newest catalog model", () => {
302
+ expect(reasoningCapability("claude", "opus")).toEqual(
303
+ reasoningCapability("claude", "claude-opus-5-5"),
304
+ );
305
+ expect(reasoningCapability("claude", "sonnet")).toEqual(
306
+ reasoningCapability("claude", "claude-sonnet-5-5"),
307
+ );
308
+ expect(reasoningCapability("claude", "haiku").levels).toContain("off");
309
+ expect(reasoningCapability("claude", "fable").supported).toBe(true);
310
+ });
311
+
312
+ test("shortnames mean nothing to the other harnesses", () => {
313
+ expect(reasoningCapability("codex", "opus").supported).toBe(false);
314
+ expect(reasoningCapability("pi", "opus").supported).toBe(false);
315
+ });
316
+ });
317
+
318
+ // The evals app validates and lists efforts with `reasoningLevelsForModel` (packages/model-catalog);
319
+ // the worker applies them with `reasoningCapability`. They are two entry points to one rule, and
320
+ // this pins them together over the real snapshot so an eval can never accept a level the worker
321
+ // would drop (or hide one it would take).
322
+ describe("reasoningLevelsForModel agrees with reasoningCapability", () => {
323
+ const catalog = {
324
+ anthropic: { models: runtimeCatalogSection("anthropic") },
325
+ openai: { models: runtimeCatalogSection("openai") },
326
+ openrouter: { models: runtimeCatalogSection("openrouter") },
327
+ };
328
+ const pairs: [ReasoningHarness, string][] = [
329
+ ...Object.keys(catalog.anthropic.models).map((id): [ReasoningHarness, string] => [
330
+ "claude",
331
+ id,
332
+ ]),
333
+ ...["opus", "sonnet", "haiku", "fable"].map((id): [ReasoningHarness, string] => ["claude", id]),
334
+ ...Object.keys(catalog.openai.models).map((id): [ReasoningHarness, string] => ["codex", id]),
335
+ ...Object.keys(catalog.openrouter.models).flatMap((id): [ReasoningHarness, string][] => [
336
+ ["pi", `openrouter/${id}`],
337
+ ["opencode", `openrouter/${id}`],
338
+ ]),
339
+ ["pi", "anthropic/claude-opus-5-5"],
340
+ ["opencode", "openai/gpt-5.6-sol"],
341
+ ["claude", "not-a-model"],
342
+ ["pi", "no-provider-prefix"],
343
+ ];
344
+
345
+ test("gives the same levels for every catalog model on every harness", () => {
346
+ expect(pairs.length).toBeGreaterThan(100);
347
+ const mismatches = pairs.filter(
348
+ ([harness, model]) =>
349
+ JSON.stringify(reasoningLevelsForModel(harness, model, catalog)) !==
350
+ JSON.stringify(reasoningCapability(harness, model).levels),
351
+ );
352
+ expect(mismatches).toEqual([]);
353
+ });
354
+ });
@@ -5,7 +5,7 @@
5
5
 
6
6
  import { describe, expect, test } from "bun:test";
7
7
  import { carryForwardDelistedModels } from "../../scripts/refresh-modelsdev-pricing";
8
- import { FALLBACK_CODEX_MODEL_PRICING } from "../providers/codex-models";
8
+ import { getCodexModelPricing } from "../providers/codex-models";
9
9
 
10
10
  describe("carryForwardDelistedModels", () => {
11
11
  test("models missing from the fresh fetch are carried forward; fresh data wins otherwise", () => {
@@ -42,12 +42,12 @@ describe("carryForwardDelistedModels", () => {
42
42
 
43
43
  describe("FALLBACK_CODEX_MODEL_PRICING (advisory table)", () => {
44
44
  test("gpt-6 Astra and gpt-5.6 Sol carry the published rates", () => {
45
- expect(FALLBACK_CODEX_MODEL_PRICING["gpt-6-astra"]).toEqual({
45
+ expect(getCodexModelPricing("gpt-6-astra")).toEqual({
46
46
  inputPerMillion: 10,
47
47
  cachedInputPerMillion: 1,
48
48
  outputPerMillion: 50,
49
49
  });
50
- expect(FALLBACK_CODEX_MODEL_PRICING["gpt-5.6-sol"]).toEqual({
50
+ expect(getCodexModelPricing("gpt-5.6-sol")).toEqual({
51
51
  inputPerMillion: 4,
52
52
  cachedInputPerMillion: 0.4,
53
53
  outputPerMillion: 20,
@@ -57,12 +57,12 @@ describe("FALLBACK_CODEX_MODEL_PRICING (advisory table)", () => {
57
57
  test("gpt-5.6 terra/luna carry the published rates", () => {
58
58
  // Direct assertions: through computeCodexCostUsd the models.dev snapshot
59
59
  // shadows this table, so drift here would otherwise go unnoticed.
60
- expect(FALLBACK_CODEX_MODEL_PRICING["gpt-5.6-terra"]).toEqual({
60
+ expect(getCodexModelPricing("gpt-5.6-terra")).toEqual({
61
61
  inputPerMillion: 2.0,
62
62
  cachedInputPerMillion: 0.2,
63
63
  outputPerMillion: 12.0,
64
64
  });
65
- expect(FALLBACK_CODEX_MODEL_PRICING["gpt-5.6-luna"]).toEqual({
65
+ expect(getCodexModelPricing("gpt-5.6-luna")).toEqual({
66
66
  inputPerMillion: 0.2,
67
67
  cachedInputPerMillion: 0.02,
68
68
  outputPerMillion: 1.2,
@@ -0,0 +1,112 @@
1
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
2
+ import { reportKeyCompletionOutcome } from "../commands/runner";
3
+
4
+ // A fake API that records the order in which it FINISHES handling credential
5
+ // reports. The success reset is slow, so a fire-and-forget caller would let
6
+ // the later auth-failure reports land first and have their bench erased.
7
+ const handled: string[] = [];
8
+ let clearDelayMs = 0;
9
+ let server: ReturnType<typeof Bun.serve>;
10
+ let apiUrl: string;
11
+
12
+ const credential = { keyType: "CODEX_OAUTH", keySuffix: "d3Ove", keyIndex: 2 };
13
+ const authFailure =
14
+ "[auth-error] Codex authentication failed — check OPENAI_API_KEY or ChatGPT login. Original error: workspace routing discovery unauthorized (401)";
15
+
16
+ beforeAll(() => {
17
+ server = Bun.serve({
18
+ port: 0,
19
+ async fetch(req) {
20
+ const path = new URL(req.url).pathname;
21
+ const body = (await req.json()) as { clearAuthBench?: boolean };
22
+ if (path === "/api/keys/clear-rate-limit") {
23
+ await Bun.sleep(clearDelayMs);
24
+ handled.push(`clear:${body.clearAuthBench === true}`);
25
+ return Response.json({ success: true, cleared: true });
26
+ }
27
+ if (path === "/api/keys/report-auth-failure") {
28
+ handled.push("auth-failure");
29
+ return Response.json({
30
+ success: true,
31
+ consecutiveAuthFailures: handled.filter((h) => h === "auth-failure").length,
32
+ benched: false,
33
+ rateLimitedUntil: null,
34
+ });
35
+ }
36
+ return new Response("not found", { status: 404 });
37
+ },
38
+ });
39
+ apiUrl = `http://localhost:${server.port}`;
40
+ });
41
+
42
+ afterAll(() => {
43
+ server.stop(true);
44
+ });
45
+
46
+ beforeEach(() => {
47
+ handled.length = 0;
48
+ clearDelayMs = 0;
49
+ });
50
+
51
+ describe("reportKeyCompletionOutcome", () => {
52
+ test("a delayed success reset lands before later auth failures of the same login", async () => {
53
+ clearDelayMs = 200;
54
+ const completions = [
55
+ { exitCode: 0, failureReason: undefined },
56
+ { exitCode: 1, failureReason: authFailure },
57
+ { exitCode: 1, failureReason: authFailure },
58
+ ];
59
+ // Same shape as checkCompletedProcesses: one awaited report per completion.
60
+ for (const [i, c] of completions.entries()) {
61
+ await reportKeyCompletionOutcome({
62
+ apiUrl,
63
+ apiKey: "test-key",
64
+ credential,
65
+ taskId: `00000000-0000-4000-8000-00000000000${i}`,
66
+ ...c,
67
+ });
68
+ }
69
+ expect(handled).toEqual(["clear:true", "auth-failure", "auth-failure"]);
70
+ });
71
+
72
+ test("returns only after the success reset is handled", async () => {
73
+ clearDelayMs = 100;
74
+ await reportKeyCompletionOutcome({
75
+ apiUrl,
76
+ apiKey: "test-key",
77
+ credential,
78
+ taskId: "00000000-0000-4000-8000-000000000009",
79
+ exitCode: 0,
80
+ failureReason: undefined,
81
+ });
82
+ expect(handled).toEqual(["clear:true"]);
83
+ });
84
+
85
+ test("a non-auth failure sends no report", async () => {
86
+ await reportKeyCompletionOutcome({
87
+ apiUrl,
88
+ apiKey: "test-key",
89
+ credential,
90
+ taskId: "00000000-0000-4000-8000-00000000000b",
91
+ exitCode: 1,
92
+ failureReason: "[rate-limit] Codex API rate limit hit. Original error: 429",
93
+ });
94
+ expect(handled).toEqual([]);
95
+ });
96
+
97
+ // Last: its handler finishes after the test, so nothing may assert on `handled` later.
98
+ test("a hung success reset is bounded by the timeout and never throws", async () => {
99
+ clearDelayMs = 2_000;
100
+ const started = Date.now();
101
+ await reportKeyCompletionOutcome({
102
+ apiUrl,
103
+ apiKey: "test-key",
104
+ credential,
105
+ taskId: "00000000-0000-4000-8000-00000000000a",
106
+ exitCode: 0,
107
+ failureReason: undefined,
108
+ timeoutMs: 100,
109
+ });
110
+ expect(Date.now() - started).toBeLessThan(1_500);
111
+ });
112
+ });