@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
@@ -0,0 +1,224 @@
1
+ /**
2
+ * A success reset that times out on the worker can still finish on the API
3
+ * later. These tests run the production reporting code against the real
4
+ * api-keys handler and a real DB, delay the success handler, and check the
5
+ * state AFTER the late handler finishes: it must not clear auth failures
6
+ * recorded after the fence the task read with its key draw, from this runner
7
+ * or any other.
8
+ */
9
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
10
+ import { unlink } from "node:fs/promises";
11
+ import { createServer as createHttpServer, type Server } from "node:http";
12
+ import { closeDb, getKeyStatuses, getKv, initDb } from "../be/db";
13
+ import {
14
+ type ApiConfig,
15
+ checkCompletedProcesses,
16
+ type RunnerState,
17
+ type RunningTask,
18
+ reportKeyCompletionOutcome,
19
+ } from "../commands/runner";
20
+ import { handleApiKeys } from "../http/api-keys";
21
+ import type { ProviderResult } from "../providers/types";
22
+ import { listenOnFreePort } from "./test-net";
23
+
24
+ const TEST_DB = `./test-runner-key-outcome-late-reset-${Date.now()}.sqlite`;
25
+ const authFailure =
26
+ "[auth-error] Codex authentication failed — check OPENAI_API_KEY or ChatGPT login. Original error: workspace routing discovery unauthorized (401)";
27
+
28
+ let api: Server;
29
+ let proxy: Server;
30
+ let apiUrl = "";
31
+ let clearDelayMs = 0;
32
+ /** Credential reports in the order the API FINISHED handling them. */
33
+ const handled: string[] = [];
34
+ /** Success resets still in flight on the API side. */
35
+ let pendingClears: Promise<void>[] = [];
36
+
37
+ const status = async (keySuffix: string) =>
38
+ (await getKeyStatuses("CODEX_OAUTH")).find((s) => s.keySuffix === keySuffix)!;
39
+ /** The `authFailureFence` a runner reads with its key draw, before the task starts. */
40
+ const drawFence = async () => {
41
+ const resp = await fetch(`${apiUrl}/api/keys/available?keyType=CODEX_OAUTH&totalKeys=1`);
42
+ return ((await resp.json()) as { authFailureFence: number }).authFailureFence;
43
+ };
44
+
45
+ beforeAll(async () => {
46
+ process.env.DB_PATH = TEST_DB;
47
+ initDb(TEST_DB);
48
+ // The real api-keys handler on a real DB.
49
+ api = createHttpServer(async (req, res) => {
50
+ const url = new URL(req.url || "/", "http://localhost");
51
+ const pathSegments = url.pathname.split("/").filter(Boolean);
52
+ if (await handleApiKeys(req, res, pathSegments, url.searchParams)) {
53
+ if (url.pathname === "/api/keys/clear-rate-limit") handled.push("clear");
54
+ if (url.pathname === "/api/keys/report-auth-failure") handled.push("auth-failure");
55
+ return;
56
+ }
57
+ // Every other worker call (finish, active sessions, ...) is out of scope.
58
+ res.writeHead(200, { "Content-Type": "application/json" });
59
+ res.end("{}");
60
+ });
61
+ const apiPort = await listenOnFreePort(api);
62
+ // A proxy in front of it. It reads the whole request, then forwards it; a
63
+ // success reset is forwarded late. The worker's abort never cancels it.
64
+ proxy = createHttpServer(async (req, res) => {
65
+ const chunks: Buffer[] = [];
66
+ for await (const chunk of req) chunks.push(chunk as Buffer);
67
+ const forward = async () => {
68
+ if (req.url === "/api/keys/clear-rate-limit") await Bun.sleep(clearDelayMs);
69
+ const upstream = await fetch(`http://127.0.0.1:${apiPort}${req.url}`, {
70
+ method: req.method,
71
+ headers: { "Content-Type": "application/json" },
72
+ body: req.method === "GET" ? undefined : Buffer.concat(chunks),
73
+ });
74
+ const text = await upstream.text();
75
+ if (!res.destroyed) {
76
+ res.writeHead(upstream.status, { "Content-Type": "application/json" });
77
+ res.end(text);
78
+ }
79
+ };
80
+ const done = forward();
81
+ if (req.url === "/api/keys/clear-rate-limit") pendingClears.push(done);
82
+ await done;
83
+ });
84
+ const proxyPort = await listenOnFreePort(proxy);
85
+ apiUrl = `http://127.0.0.1:${proxyPort}`;
86
+ });
87
+
88
+ afterAll(async () => {
89
+ await Promise.all(pendingClears);
90
+ for (const s of [proxy, api]) await new Promise<void>((resolve) => s.close(() => resolve()));
91
+ closeDb();
92
+ await unlink(TEST_DB).catch(() => {});
93
+ await unlink(`${TEST_DB}-wal`).catch(() => {});
94
+ await unlink(`${TEST_DB}-shm`).catch(() => {});
95
+ });
96
+
97
+ beforeEach(() => {
98
+ handled.length = 0;
99
+ pendingClears = [];
100
+ clearDelayMs = 0;
101
+ });
102
+
103
+ describe("late success reset", () => {
104
+ test("a timed-out success reset that finishes late keeps the later auth bench", async () => {
105
+ const credential = { keyType: "CODEX_OAUTH", keySuffix: "late1", keyIndex: 0 };
106
+ const authFence = await drawFence();
107
+ const report = (i: number, exitCode: number, failureReason?: string, timeoutMs?: number) =>
108
+ reportKeyCompletionOutcome({
109
+ apiUrl,
110
+ apiKey: "test-key",
111
+ credential: { ...credential, authFence },
112
+ taskId: `00000000-0000-4000-8000-00000000010${i}`,
113
+ exitCode,
114
+ failureReason,
115
+ timeoutMs,
116
+ });
117
+
118
+ clearDelayMs = 400;
119
+ const started = Date.now();
120
+ await report(0, 0, undefined, 100);
121
+ expect(Date.now() - started).toBeLessThan(400);
122
+ await report(1, 1, authFailure);
123
+ await report(2, 1, authFailure);
124
+
125
+ let row = await status("late1");
126
+ expect(row.consecutiveAuthFailures).toBe(2);
127
+ expect(row.status).toBe("rate_limited");
128
+
129
+ // Let the late success handler finish on the API, then check again.
130
+ await Promise.all(pendingClears);
131
+ expect(handled).toEqual(["auth-failure", "auth-failure", "clear"]);
132
+ row = await status("late1");
133
+ expect(row.consecutiveAuthFailures).toBe(2);
134
+ expect(row.status).toBe("rate_limited");
135
+ expect(await getKv("codex-auth-watch", "bench:late1")).not.toBeNull();
136
+ });
137
+
138
+ test("two runners: A's late success cannot clear B's later failures, whatever A's clock says", async () => {
139
+ const credential = { keyType: "CODEX_OAUTH", keySuffix: "late3", keyIndex: 2 };
140
+ const report = (
141
+ runner: string,
142
+ authFence: number,
143
+ exitCode: number,
144
+ failureReason?: string,
145
+ timeoutMs?: number,
146
+ ) =>
147
+ reportKeyCompletionOutcome({
148
+ apiUrl,
149
+ apiKey: "test-key",
150
+ credential: { ...credential, authFence },
151
+ taskId: crypto.randomUUID(),
152
+ exitCode,
153
+ failureReason,
154
+ timeoutMs,
155
+ }).then(() => runner);
156
+
157
+ // Runner A draws the key first; its clock runs an hour ahead. Nothing it sends
158
+ // carries a clock value, so the skew cannot move its success after B's failures.
159
+ const fenceA = await drawFence();
160
+ const realNow = Date.now;
161
+ Date.now = () => realNow() + 3_600_000;
162
+ clearDelayMs = 400;
163
+ try {
164
+ await report("A", fenceA, 0, undefined, 100);
165
+ } finally {
166
+ Date.now = realNow;
167
+ }
168
+ // Runner B draws after A and fails twice on the same login.
169
+ const fenceB = await drawFence();
170
+ await report("B", fenceB, 1, authFailure);
171
+ await report("B", fenceB, 1, authFailure);
172
+
173
+ await Promise.all(pendingClears);
174
+ expect(handled).toEqual(["auth-failure", "auth-failure", "clear"]);
175
+ const row = await status("late3");
176
+ expect(row.consecutiveAuthFailures).toBe(2);
177
+ expect(row.status).toBe("rate_limited");
178
+ expect(await getKv("codex-auth-watch", "bench:late3")).not.toBeNull();
179
+ });
180
+
181
+ test("checkCompletedProcesses lands each outcome before it handles the next completion", async () => {
182
+ const credentialInfo = {
183
+ keyType: "CODEX_OAUTH",
184
+ keySuffix: "late2",
185
+ keyIndex: 1,
186
+ authFence: await drawFence(),
187
+ };
188
+ const running = (i: number, result: ProviderResult) =>
189
+ ({
190
+ taskId: `00000000-0000-4000-8000-00000000020${i}`,
191
+ startTime: new Date(),
192
+ result,
193
+ credentialInfo,
194
+ harnessProvider: "codex",
195
+ hasLocalEnvironment: false,
196
+ }) as unknown as RunningTask;
197
+ const state = {
198
+ activeTasks: new Map<string, RunningTask>(),
199
+ maxConcurrent: 3,
200
+ startedAt: Date.now(),
201
+ tasksProcessed: 0,
202
+ harnessProvider: "codex",
203
+ codexCreditsExhaustedCooldownMs: 7_200_000,
204
+ modelWindowBlocks: new Map(),
205
+ } as RunnerState;
206
+ const tasks = [
207
+ running(0, { exitCode: 0, isError: false } as ProviderResult),
208
+ running(1, { exitCode: 1, isError: true, failureReason: authFailure } as ProviderResult),
209
+ running(2, { exitCode: 1, isError: true, failureReason: authFailure } as ProviderResult),
210
+ ];
211
+ for (const t of tasks) state.activeTasks.set(t.taskId, t);
212
+ const apiConfig: ApiConfig = { apiUrl, apiKey: "test-key", agentId: crypto.randomUUID() };
213
+
214
+ clearDelayMs = 200;
215
+ await checkCompletedProcesses(state, "worker", apiConfig);
216
+
217
+ // Awaited in order: the slow success reset finished before the failures.
218
+ expect(handled).toEqual(["clear", "auth-failure", "auth-failure"]);
219
+ expect(state.activeTasks.size).toBe(0);
220
+ const row = await status("late2");
221
+ expect(row.consecutiveAuthFailures).toBe(2);
222
+ expect(row.status).toBe("rate_limited");
223
+ });
224
+ });
@@ -0,0 +1,149 @@
1
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
2
+ import { createServer, type Server } from "node:http";
3
+ import {
4
+ AgentRegistrationHttpError,
5
+ isRetryableRegistrationError,
6
+ registerAgent,
7
+ registerAgentWithRetry,
8
+ } from "../commands/runner";
9
+ import { getFreePort, listenOnFreePort } from "./test-net";
10
+
11
+ /**
12
+ * After the 2026-09-30 deploy every worker got `500 {"error":"database is
13
+ * locked"}` from `POST /api/agents` for ~4 minutes and exited on the first
14
+ * failure. Boot registration now retries transient failures inside a budget
15
+ * and still exits on a 4xx. These tests drive the real `registerAgent` against
16
+ * a fake API, with an injected clock so no real time passes.
17
+ */
18
+
19
+ const AGENT_ID = "3f0b7a52-8c1d-4e6a-9b27-5d4c1e8a7f10";
20
+
21
+ let server: Server;
22
+ let baseUrl: string;
23
+ /** Responses served in order; the last one repeats. */
24
+ let script: Array<{ status: number; body: unknown }>;
25
+ let calls: number;
26
+
27
+ beforeAll(async () => {
28
+ server = createServer((req, res) => {
29
+ req.resume();
30
+ const url = new URL(req.url ?? "/", "http://localhost");
31
+ if (req.method === "POST" && url.pathname === "/api/agents") {
32
+ const next = script[Math.min(calls, script.length - 1)] ?? { status: 200, body: {} };
33
+ calls++;
34
+ res.writeHead(next.status, { "Content-Type": "application/json" });
35
+ res.end(JSON.stringify(next.body));
36
+ return;
37
+ }
38
+ res.writeHead(404, { "Content-Type": "application/json" });
39
+ res.end(JSON.stringify({ error: "not found" }));
40
+ });
41
+ const port = await listenOnFreePort(server);
42
+ baseUrl = `http://127.0.0.1:${port}`;
43
+ });
44
+
45
+ afterAll(() => {
46
+ server.close();
47
+ });
48
+
49
+ beforeEach(() => {
50
+ script = [];
51
+ calls = 0;
52
+ });
53
+
54
+ function fakeClock() {
55
+ let t = 0;
56
+ const waits: number[] = [];
57
+ return {
58
+ waits,
59
+ now: () => t,
60
+ sleep: async (ms: number) => {
61
+ waits.push(ms);
62
+ t += ms;
63
+ },
64
+ };
65
+ }
66
+
67
+ function register(apiUrl = baseUrl) {
68
+ return registerAgent({
69
+ apiUrl,
70
+ apiKey: "example-test-swarm-key",
71
+ agentId: AGENT_ID,
72
+ name: "retry-test-worker",
73
+ isLead: false,
74
+ harnessProvider: "claude",
75
+ });
76
+ }
77
+
78
+ describe("boot registration retry", () => {
79
+ test("rides out 'database is locked' 500s, then registers", async () => {
80
+ script = [
81
+ { status: 500, body: { error: "database is locked" } },
82
+ { status: 500, body: { error: "database is locked" } },
83
+ { status: 503, body: { error: "unavailable" } },
84
+ { status: 200, body: { enabledCapabilities: ["core"] } },
85
+ ];
86
+ const clock = fakeClock();
87
+ const reg = await registerAgentWithRetry(() => register(), { label: "test", ...clock });
88
+ expect(reg.serverCapabilities).toEqual(["core"]);
89
+ expect(calls).toBe(4);
90
+ expect(clock.waits).toHaveLength(3);
91
+ // Backoff grows (2s, 4s, 8s nominal, +/-20% jitter).
92
+ expect(clock.waits[0]).toBeGreaterThanOrEqual(1_600);
93
+ expect(clock.waits[2]).toBeGreaterThanOrEqual(6_400);
94
+ });
95
+
96
+ test("exits on the first 4xx without retrying", async () => {
97
+ script = [{ status: 401, body: { error: "unauthorized" } }];
98
+ const clock = fakeClock();
99
+ const err = await registerAgentWithRetry(() => register(), { label: "test", ...clock }).catch(
100
+ (e: unknown) => e,
101
+ );
102
+ expect(err).toBeInstanceOf(AgentRegistrationHttpError);
103
+ expect((err as AgentRegistrationHttpError).status).toBe(401);
104
+ expect(calls).toBe(1);
105
+ expect(clock.waits).toHaveLength(0);
106
+ });
107
+
108
+ test("gives up once the budget is spent", async () => {
109
+ script = [{ status: 500, body: { error: "database is locked" } }];
110
+ const clock = fakeClock();
111
+ const err = await registerAgentWithRetry(() => register(), {
112
+ label: "test",
113
+ budgetMs: 60_000,
114
+ ...clock,
115
+ }).catch((e: unknown) => e);
116
+ expect(err).toBeInstanceOf(AgentRegistrationHttpError);
117
+ expect(clock.now()).toBeLessThanOrEqual(60_000);
118
+ // 2 + 4 + 8 + 16 + 30 nominal fits only partly in 60s; more than one try, bounded.
119
+ expect(calls).toBeGreaterThan(1);
120
+ expect(calls).toBeLessThan(10);
121
+ });
122
+
123
+ test("retries a connection error", async () => {
124
+ const deadPort = await getFreePort();
125
+ const clock = fakeClock();
126
+ let attempts = 0;
127
+ const reg = await registerAgentWithRetry(
128
+ () => {
129
+ attempts++;
130
+ return attempts < 3 ? register(`http://127.0.0.1:${deadPort}`) : register();
131
+ },
132
+ { label: "test", ...clock },
133
+ );
134
+ expect(reg).toEqual({});
135
+ expect(attempts).toBe(3);
136
+ expect(clock.waits).toHaveLength(2);
137
+ });
138
+
139
+ test("classifies statuses", () => {
140
+ const http = (status: number, body = "{}") => new AgentRegistrationHttpError(status, body);
141
+ expect(isRetryableRegistrationError(http(500))).toBe(true);
142
+ expect(isRetryableRegistrationError(http(502))).toBe(true);
143
+ expect(isRetryableRegistrationError(http(429))).toBe(true);
144
+ expect(isRetryableRegistrationError(http(400))).toBe(false);
145
+ expect(isRetryableRegistrationError(http(403))).toBe(false);
146
+ expect(isRetryableRegistrationError(http(409, '{"error":"database is locked"}'))).toBe(true);
147
+ expect(isRetryableRegistrationError(new TypeError("fetch failed"))).toBe(true);
148
+ });
149
+ });
@@ -0,0 +1,86 @@
1
+ import { afterEach, describe, expect, test } from "bun:test";
2
+ import {
3
+ getClaudeManagedModelPricing,
4
+ listClaudeManagedModels,
5
+ } from "../providers/claude-managed-models";
6
+ import {
7
+ getCodexContextWindow,
8
+ getCodexModelPricing,
9
+ listCodexModels,
10
+ } from "../providers/codex-models";
11
+ import {
12
+ refreshRuntimeModelCatalog,
13
+ resetRuntimeModelCatalogForTests,
14
+ setRuntimeModelCatalog,
15
+ } from "../utils/runtime-model-catalog";
16
+
17
+ const NEW_CODEX = "gpt-9-test";
18
+ const NEW_CLAUDE = "claude-opus-9";
19
+
20
+ const liveCatalog = {
21
+ openai: {
22
+ id: "openai",
23
+ models: {
24
+ [NEW_CODEX]: {
25
+ id: NEW_CODEX,
26
+ cost: { input: 3, output: 12 },
27
+ limit: { context: 2_000_000 },
28
+ release_date: "2099-01-01",
29
+ reasoning: true,
30
+ },
31
+ },
32
+ },
33
+ anthropic: {
34
+ id: "anthropic",
35
+ models: {
36
+ [NEW_CLAUDE]: {
37
+ id: NEW_CLAUDE,
38
+ cost: { input: 6, output: 30, cache_read: 0.6, cache_write: 7.5 },
39
+ limit: { context: 1_000_000 },
40
+ release_date: "2099-01-01",
41
+ },
42
+ },
43
+ },
44
+ };
45
+
46
+ afterEach(() => resetRuntimeModelCatalogForTests());
47
+
48
+ describe("runtime model catalog", () => {
49
+ test("a model unknown to the snapshot becomes listed, priced and windowed once the live catalog has it", () => {
50
+ expect(listCodexModels()).not.toContain(NEW_CODEX);
51
+ expect(getCodexModelPricing(NEW_CODEX)).toBeUndefined();
52
+
53
+ setRuntimeModelCatalog(liveCatalog);
54
+
55
+ expect(listCodexModels()[0]).toBe(NEW_CODEX);
56
+ expect(getCodexModelPricing(NEW_CODEX)).toMatchObject({
57
+ inputPerMillion: 3,
58
+ outputPerMillion: 12,
59
+ });
60
+ expect(getCodexContextWindow(NEW_CODEX)).toBe(2_000_000);
61
+ expect(listClaudeManagedModels()).toContain(NEW_CLAUDE);
62
+ expect(getClaudeManagedModelPricing(NEW_CLAUDE)).toEqual({
63
+ inputPerMillion: 6,
64
+ outputPerMillion: 30,
65
+ cacheReadPerMillion: 0.6,
66
+ cacheWritePerMillion: 7.5,
67
+ });
68
+ });
69
+
70
+ test("worker pull from GET /api/models-catalog installs the live layer; failures keep the snapshot", async () => {
71
+ const failing = (async () => new Response("nope", { status: 503 })) as unknown as typeof fetch;
72
+ expect(await refreshRuntimeModelCatalog({ apiUrl: "http://x", fetchImpl: failing })).toBe(
73
+ false,
74
+ );
75
+ expect(getCodexModelPricing(NEW_CODEX)).toBeUndefined();
76
+
77
+ let url = "";
78
+ const ok = (async (input: string | URL | Request) => {
79
+ url = String(input);
80
+ return Response.json({ source: "live", providers: liveCatalog });
81
+ }) as unknown as typeof fetch;
82
+ expect(await refreshRuntimeModelCatalog({ apiUrl: "http://x", fetchImpl: ok })).toBe(true);
83
+ expect(url).toBe("http://x/api/models-catalog");
84
+ expect(getCodexModelPricing(NEW_CODEX)?.inputPerMillion).toBe(3);
85
+ });
86
+ });
@@ -585,6 +585,15 @@ describe("script connections", () => {
585
585
  markMigrationApplied(database, "175_workflow_runs_list_index.sql");
586
586
  // 177 indexes events, which this migration-112-only fixture does not create.
587
587
  markMigrationApplied(database, "177_events_event_agent_created_index.sql");
588
+ // 182 alters agent_tasks, which this migration-112-only fixture does not create.
589
+ markMigrationApplied(database, "182_task_human_free_flag.sql");
590
+ // 184 alters agents and agent_tasks, which this migration-112-only fixture
591
+ // does not create.
592
+ markMigrationApplied(database, "184_model_catalog.sql");
593
+ // 186 alters api_key_status, which this migration-112-only fixture does not create.
594
+ markMigrationApplied(database, "186_api_key_seat_mismatch.sql");
595
+ // 185 alters api_key_status, which this migration-112-only fixture does not create.
596
+ markMigrationApplied(database, "185_api_key_auth_failures.sql");
588
597
 
589
598
  const id = crypto.randomUUID();
590
599
  const now = new Date().toISOString();
@@ -0,0 +1,85 @@
1
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
2
+ import { reportSeatMismatchOutcome } from "../commands/seat-mismatch-report";
3
+
4
+ /**
5
+ * Report helper for a `credits_required` seat mismatch: the report sends the
6
+ * credential and model family, resolves only after the API responds, and
7
+ * never throws. The completion ordering is covered by
8
+ * `credential-outcome-report.test.ts`.
9
+ */
10
+
11
+ interface RecordedRequest {
12
+ method: string;
13
+ path: string;
14
+ auth: string | null;
15
+ body: Record<string, unknown>;
16
+ }
17
+
18
+ const SEAT_REPORT_DELAY_MS = 150;
19
+ let server: ReturnType<typeof Bun.serve>;
20
+ let apiUrl: string;
21
+ let requests: RecordedRequest[] = [];
22
+ let respondedAt: number | undefined;
23
+ let status = 200;
24
+
25
+ beforeAll(() => {
26
+ server = Bun.serve({
27
+ port: 0,
28
+ async fetch(req) {
29
+ const url = new URL(req.url);
30
+ requests.push({
31
+ method: req.method,
32
+ path: url.pathname,
33
+ auth: req.headers.get("authorization"),
34
+ body: (await req.json()) as Record<string, unknown>,
35
+ });
36
+ await Bun.sleep(SEAT_REPORT_DELAY_MS);
37
+ respondedAt = Date.now();
38
+ return Response.json({ success: status === 200 }, { status });
39
+ },
40
+ });
41
+ apiUrl = server.url.toString().replace(/\/$/, "");
42
+ });
43
+
44
+ afterAll(() => {
45
+ server.stop(true);
46
+ });
47
+
48
+ beforeEach(() => {
49
+ requests = [];
50
+ respondedAt = undefined;
51
+ status = 200;
52
+ });
53
+
54
+ const credential = { keyType: "CLAUDE_CODE_OAUTH_TOKEN", keySuffix: "TJAAA", keyIndex: 3 };
55
+
56
+ describe("reportSeatMismatchOutcome", () => {
57
+ test("posts the seat mismatch and resolves only after the API responds", async () => {
58
+ await reportSeatMismatchOutcome(apiUrl, "example-test-key", credential, "fable");
59
+ const resolvedAt = Date.now();
60
+
61
+ expect(requests).toEqual([
62
+ {
63
+ method: "POST",
64
+ path: "/api/keys/report-seat-mismatch",
65
+ auth: "Bearer example-test-key",
66
+ body: {
67
+ keyType: "CLAUDE_CODE_OAUTH_TOKEN",
68
+ keySuffix: "TJAAA",
69
+ keyIndex: 3,
70
+ model: "fable",
71
+ },
72
+ },
73
+ ]);
74
+ expect(respondedAt).toBeDefined();
75
+ expect(resolvedAt).toBeGreaterThanOrEqual(respondedAt!);
76
+ });
77
+
78
+ test("a failed report is logged and never throws", async () => {
79
+ status = 500;
80
+ await expect(
81
+ reportSeatMismatchOutcome(apiUrl, "example-test-key", credential, "fable"),
82
+ ).resolves.toBeUndefined();
83
+ expect(requests).toHaveLength(1);
84
+ });
85
+ });