@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
@@ -9,8 +9,8 @@ import {
9
9
  normalizeRepoUrl,
10
10
  } from "../providers/claude-managed-adapter";
11
11
  import {
12
- CLAUDE_MANAGED_MODEL_PRICING,
13
12
  computeClaudeManagedCostUsd,
13
+ getClaudeManagedModelPricing,
14
14
  } from "../providers/claude-managed-models";
15
15
  import type { ProviderEvent, ProviderSessionConfig } from "../providers/types";
16
16
 
@@ -885,58 +885,52 @@ describe("ClaudeManagedAdapter (Phase 4) — repo provisioning + cost data", ()
885
885
  expect(cost2).toBe(0);
886
886
  });
887
887
 
888
- test("CLAUDE_MANAGED_MODEL_PRICING covers sonnet, opus, haiku at minimum", () => {
889
- expect(CLAUDE_MANAGED_MODEL_PRICING["claude-fable-5-1"]).toEqual({
888
+ test("catalog prices managed models: covers sonnet, opus, haiku at minimum", () => {
889
+ expect(getClaudeManagedModelPricing("claude-fable-5-1")).toEqual({
890
890
  inputPerMillion: 10.0,
891
891
  outputPerMillion: 50.0,
892
892
  cacheReadPerMillion: 0.25,
893
893
  cacheWritePerMillion: 12.5,
894
894
  });
895
- expect(CLAUDE_MANAGED_MODEL_PRICING["claude-mythos-5-1"]).toEqual({
896
- inputPerMillion: 10.0,
897
- outputPerMillion: 50.0,
898
- cacheReadPerMillion: 0.25,
899
- cacheWritePerMillion: 12.5,
900
- });
901
- expect(CLAUDE_MANAGED_MODEL_PRICING["claude-opus-5-5"]).toEqual({
895
+ expect(getClaudeManagedModelPricing("claude-opus-5-5")).toEqual({
902
896
  inputPerMillion: 4.0,
903
897
  outputPerMillion: 20.0,
904
898
  cacheReadPerMillion: 0.2,
905
899
  cacheWritePerMillion: 5.0,
906
900
  });
907
- expect(CLAUDE_MANAGED_MODEL_PRICING["claude-opus-5"]).toEqual({
901
+ expect(getClaudeManagedModelPricing("claude-opus-5")).toEqual({
908
902
  inputPerMillion: 5.0,
909
903
  outputPerMillion: 25.0,
910
904
  cacheReadPerMillion: 0.5,
911
905
  cacheWritePerMillion: 6.25,
912
906
  });
913
- expect(CLAUDE_MANAGED_MODEL_PRICING["claude-fable-5"]).toEqual({
907
+ expect(getClaudeManagedModelPricing("claude-fable-5")).toEqual({
914
908
  inputPerMillion: 10.0,
915
909
  outputPerMillion: 50.0,
916
910
  cacheReadPerMillion: 1.0,
917
911
  cacheWritePerMillion: 12.5,
918
912
  });
919
- expect(CLAUDE_MANAGED_MODEL_PRICING["claude-mythos-5"]).toEqual({
913
+ expect(getClaudeManagedModelPricing("claude-mythos-5")).toEqual({
920
914
  inputPerMillion: 10.0,
921
915
  outputPerMillion: 50.0,
922
916
  cacheReadPerMillion: 1.0,
923
917
  cacheWritePerMillion: 12.5,
924
918
  });
925
- expect(CLAUDE_MANAGED_MODEL_PRICING["claude-sonnet-5-5"]).toEqual({
919
+ expect(getClaudeManagedModelPricing("claude-sonnet-5-5")).toEqual({
926
920
  inputPerMillion: 2.0,
927
921
  outputPerMillion: 10.0,
928
922
  cacheReadPerMillion: 0.2,
929
923
  cacheWritePerMillion: 2.5,
930
924
  });
931
- expect(CLAUDE_MANAGED_MODEL_PRICING["claude-sonnet-5"]).toEqual({
925
+ expect(getClaudeManagedModelPricing("claude-sonnet-5")).toEqual({
932
926
  inputPerMillion: 2.0,
933
927
  outputPerMillion: 10.0,
934
928
  cacheReadPerMillion: 0.2,
935
929
  cacheWritePerMillion: 2.5,
936
930
  });
937
- expect(CLAUDE_MANAGED_MODEL_PRICING["claude-sonnet-4-6"]).toBeDefined();
938
- expect(CLAUDE_MANAGED_MODEL_PRICING["claude-opus-4-7"]).toBeDefined();
939
- expect(CLAUDE_MANAGED_MODEL_PRICING["claude-haiku-4-5"]).toBeDefined();
931
+ expect(getClaudeManagedModelPricing("claude-sonnet-4-6")).toBeDefined();
932
+ expect(getClaudeManagedModelPricing("claude-opus-4-7")).toBeDefined();
933
+ expect(getClaudeManagedModelPricing("claude-haiku-4-5")).toBeDefined();
940
934
  });
941
935
 
942
936
  test("session totalCostUsd = token cost + (durationMs/3.6e6) × $0.08 runtime fee", async () => {
@@ -1914,12 +1914,13 @@ describe("buildCodexConfig — context-mode + features", () => {
1914
1914
  expect(mcp["context-mode"]).toBeUndefined();
1915
1915
  });
1916
1916
 
1917
- test("sets features.hooks and features.plugin_hooks to true", async () => {
1917
+ test("disables apps and enables hooks", async () => {
1918
1918
  delete process.env.CONTEXT_MODE_DISABLED;
1919
1919
  globalThis.fetch = stubFetch({ servers: [], total: 0 });
1920
1920
  const merged = await buildCodexConfig(cfg(), "gpt-5.4", () => {});
1921
1921
 
1922
1922
  const features = merged.features as Record<string, unknown>;
1923
+ expect(features.apps).toBe(false);
1923
1924
  expect(features.hooks).toBe(true);
1924
1925
  expect(features.plugin_hooks).toBe(true);
1925
1926
  });
@@ -0,0 +1,52 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { buildPoolRevalidationFailureReason } from "../providers/codex-adapter";
3
+ import { CodexOAuthRefreshError } from "../providers/codex-oauth/storage";
4
+ import { isCodexAuthFailureReason } from "../utils/codex-auth-failure";
5
+
6
+ describe("isCodexAuthFailureReason", () => {
7
+ test.each([
8
+ "[auth-error] Codex authentication failed — check OPENAI_API_KEY or ChatGPT login. Original error: workspace routing discovery unauthorized (401)",
9
+ "[auth-error] Codex authentication failed — check OPENAI_API_KEY or ChatGPT login. Original error: Failed to refresh token: 400 Bad Request: Invalid 'refresh_token': empty string",
10
+ '[auth-error] Codex pool slot 2 [...d3Ove] revalidation failed: refresh rejected (401 {"error":"refresh_token_invalidated"}) — credential likely revoked; re-run codex-login for this slot.',
11
+ ])("counts %s", (reason) => {
12
+ expect(isCodexAuthFailureReason(reason)).toBe(true);
13
+ });
14
+
15
+ test.each([
16
+ undefined,
17
+ null,
18
+ "",
19
+ "[rate-limit] Codex API rate limit hit. Original error: 429",
20
+ "[auth-error] Codex pool slot 2 [...d3Ove] revalidation failed: timed out waiting for the refresh lock — transient, will retry on next task.",
21
+ "[auth-error] Codex pool slot 2 [...d3Ove] revalidation failed: refresh rejected (429 Too Many Requests) — credential likely revoked; re-run codex-login for this slot.",
22
+ "[auth-error] Codex pool slot 2 [...d3Ove] revalidation failed: refresh rejected (503 Service Unavailable) — credential likely revoked; re-run codex-login for this slot.",
23
+ "[auth-error] Codex pool slot 2 [...d3Ove] revalidation failed: refresh rejected (unknown status ) — credential likely revoked; re-run codex-login for this slot.",
24
+ "[auth-error] Codex pool slot 2 [...d3Ove] revalidation failed: no credentials found in config store — the slot may have just been quarantined; the next task will pick another slot.",
25
+ ])("does not count %p", (reason) => {
26
+ expect(isCodexAuthFailureReason(reason)).toBe(false);
27
+ });
28
+
29
+ test.each([
30
+ ["network error", new TypeError("fetch failed")],
31
+ ["refresh-lock HTTP error", new Error("refresh lock acquire failed: HTTP 502")],
32
+ ["persistence error", new Error("Failed to store Codex OAuth credentials: 500")],
33
+ ["lock timeout", new CodexOAuthRefreshError(2, "lock_timeout", "d3Ove")],
34
+ ["throttled refresh", new CodexOAuthRefreshError(2, "refresh_rejected", "d3Ove", 429)],
35
+ ["upstream refresh error", new CodexOAuthRefreshError(2, "refresh_rejected", "d3Ove", 503)],
36
+ ])("a thrown %s during revalidation does not count", (_label, err) => {
37
+ const reason = buildPoolRevalidationFailureReason(err, 2);
38
+ expect(reason).toContain("[auth-error]");
39
+ expect(isCodexAuthFailureReason(reason)).toBe(false);
40
+ });
41
+
42
+ test("a confirmed refresh rejection during revalidation counts", () => {
43
+ const err = new CodexOAuthRefreshError(
44
+ 2,
45
+ "refresh_rejected",
46
+ "d3Ove",
47
+ 401,
48
+ '{"error":"refresh_token_invalidated"}',
49
+ );
50
+ expect(isCodexAuthFailureReason(buildPoolRevalidationFailureReason(err, 2))).toBe(true);
51
+ });
52
+ });
@@ -5,10 +5,10 @@ import cache from "../be/modelsdev-cache.json";
5
5
  import { buildModelsDevSeedRows } from "../be/seed-pricing";
6
6
  import {
7
7
  CODEX_DEFAULT_MODEL,
8
- CODEX_MODELS,
9
8
  computeCodexCostUsd,
10
- FALLBACK_CODEX_MODEL_PRICING,
11
9
  getCodexContextWindow,
10
+ getCodexModelPricing,
11
+ listCodexModels,
12
12
  resolveCodexModel,
13
13
  } from "../providers/codex-models";
14
14
  import { applyReasoningEffort, reasoningCapability } from "../providers/reasoning-effort";
@@ -21,11 +21,11 @@ const releases = [
21
21
  for (const release of releases) {
22
22
  describe(release.id, () => {
23
23
  test("uses the exact bare CLI ID with verified context and base costs", () => {
24
- expect(CODEX_MODELS).toContain(release.id);
24
+ expect(listCodexModels()).toContain(release.id);
25
25
  expect(resolveCodexModel(release.id.toUpperCase())).toBe(release.id);
26
26
  expect(getCodexContextWindow(release.id)).toBe(1_050_000);
27
27
  expect(cache.openai.models[release.id].limit.output).toBe(128_000);
28
- expect(FALLBACK_CODEX_MODEL_PRICING[release.id]).toEqual({
28
+ expect(getCodexModelPricing(release.id)).toEqual({
29
29
  inputPerMillion: release.input,
30
30
  cachedInputPerMillion: release.read,
31
31
  outputPerMillion: release.output,
@@ -79,7 +79,7 @@ for (const release of releases) {
79
79
  test("is available through the catalog and direct UI selector", () => {
80
80
  const model = buildModelsCatalog(cache).openai?.models[release.id];
81
81
  expect(model?.limit?.context).toBe(1_050_000);
82
- expect(model?.cost).toEqual({ input: release.input, output: release.output });
82
+ expect(model?.cost).toMatchObject({ input: release.input, output: release.output });
83
83
  const option = modelGroupsForHarness("codex", undefined, undefined)
84
84
  .flatMap((group) => group.models)
85
85
  .find((entry) => entry.id === release.id);
@@ -95,3 +95,64 @@ test("release additions preserve rollout-safe defaults and aliases", () => {
95
95
  expect(resolveCodexModel("haiku")).toBe("gpt-5.6-luna");
96
96
  expect(resolveCodexModel("opus")).toBe("gpt-5.6-sol");
97
97
  });
98
+
99
+ describe("gpt-6.1-sol", () => {
100
+ const id = "gpt-6.1-sol";
101
+
102
+ test("uses the exact bare CLI ID with verified context and base costs", () => {
103
+ expect(listCodexModels()).toContain(id);
104
+ expect(resolveCodexModel("GPT-6.1-SOL")).toBe(id);
105
+ expect(getCodexContextWindow(id)).toBe(1_050_000);
106
+ expect(cache.openai.models[id].limit.output).toBe(128_000);
107
+ expect(getCodexModelPricing(id)).toEqual({
108
+ inputPerMillion: 2,
109
+ cachedInputPerMillion: 0.1,
110
+ outputPerMillion: 10,
111
+ });
112
+ expect(computeCodexCostUsd(id, 1_000_000, 250_000, 100_000)).toBeCloseTo(
113
+ 2 * 0.75 + 0.1 * 0.25 + 10 * 0.1,
114
+ 8,
115
+ );
116
+ });
117
+
118
+ test("exposes low through max without an off level", () => {
119
+ expect(reasoningCapability("codex", id)).toEqual({
120
+ supported: true,
121
+ levels: ["low", "medium", "high", "xhigh", "max"],
122
+ default: "medium",
123
+ });
124
+ expect(applyReasoningEffort("codex", id, "max")).toEqual({
125
+ kind: "codex-config",
126
+ config: { model_reasoning_effort: "max" },
127
+ });
128
+ });
129
+
130
+ test("seeds all four token classes with long-context tiers", () => {
131
+ const rows = buildModelsDevSeedRows(cache).filter(
132
+ (row) => row.provider === "codex" && row.model === id,
133
+ );
134
+ expect(Object.fromEntries(rows.map((row) => [row.tokenClass, row.pricePerMillionUsd]))).toEqual(
135
+ { input: 2, output: 10, cached_input: 0.1, cache_write: 2.5 },
136
+ );
137
+ expect(cache.openai.models[id].cost.tiers).toEqual([
138
+ {
139
+ input: 4,
140
+ output: 15,
141
+ cache_read: 0.2,
142
+ cache_write: 5,
143
+ tier: { type: "context", size: 272000 },
144
+ },
145
+ ]);
146
+ });
147
+
148
+ test("is available through the catalog and direct UI selector", () => {
149
+ const model = buildModelsCatalog(cache).openai?.models[id];
150
+ expect(model?.limit?.context).toBe(1_050_000);
151
+ expect(model?.cost).toMatchObject({ input: 2, output: 10 });
152
+ const option = modelGroupsForHarness("codex", undefined, undefined)
153
+ .flatMap((group) => group.models)
154
+ .find((entry) => entry.id === id);
155
+ expect(option?.label).toBe("GPT-6.1 Sol");
156
+ expect(option?.reasoningLevels).toEqual(["low", "medium", "high", "xhigh", "max"]);
157
+ });
158
+ });
@@ -197,6 +197,8 @@ describe("runCodexLogin", () => {
197
197
  })),
198
198
  store,
199
199
  loadAllSlots: mock(async () => []),
200
+ clearAuthBench: mock(async () => false),
201
+ readAuthFence: mock(async () => 0),
200
202
  log: () => {},
201
203
  error: () => {},
202
204
  exit: () => {},
@@ -229,6 +231,8 @@ describe("runCodexLogin", () => {
229
231
  { slot: 0, creds: { access: "", refresh: "", expires: 0, accountId: "" } },
230
232
  { slot: 1, creds: { access: "", refresh: "", expires: 0, accountId: "" } },
231
233
  ]),
234
+ clearAuthBench: mock(async () => false),
235
+ readAuthFence: mock(async () => 0),
232
236
  log: () => {},
233
237
  error: () => {},
234
238
  exit: () => {},
@@ -285,6 +289,8 @@ describe("runCodexLogin", () => {
285
289
  })),
286
290
  store,
287
291
  loadAllSlots: mock(async () => []),
292
+ clearAuthBench: mock(async () => false),
293
+ readAuthFence: mock(async () => 0),
288
294
  log: () => {},
289
295
  error: () => {},
290
296
  exit: () => {},
@@ -322,4 +328,114 @@ describe("runCodexLogin", () => {
322
328
  expect(error).toHaveBeenCalledWith(expect.stringContaining("All credential slots"));
323
329
  expect(exit).toHaveBeenCalledWith(1);
324
330
  });
331
+
332
+ it("lifts the auth-failure bench on the re-logged key after storing", async () => {
333
+ const calls: string[] = [];
334
+ const store = mock(async () => {
335
+ calls.push("store");
336
+ });
337
+ const clearAuthBench = mock(
338
+ async (_apiUrl: string, _apiKey: string, keySuffix: string, slot: number, fence: number) => {
339
+ calls.push(`clear:${keySuffix}@${slot} fence ${fence}`);
340
+ return true;
341
+ },
342
+ );
343
+ const readAuthFence = mock(async () => {
344
+ calls.push("read-fence");
345
+ return 7;
346
+ });
347
+
348
+ await runCodexLogin([], {
349
+ resolveConfig: async () => ({
350
+ apiUrl: "http://localhost:3013",
351
+ apiKey: "example-test-key",
352
+ slot: 2,
353
+ }),
354
+ login: mock(async () => ({
355
+ access: "at_test",
356
+ refresh: "rt_test",
357
+ expires: Date.now() + 3600000,
358
+ accountId: "acc-test-d3Ove",
359
+ })),
360
+ store,
361
+ loadAllSlots: mock(async () => []),
362
+ clearAuthBench,
363
+ readAuthFence,
364
+ log: () => {},
365
+ error: () => {},
366
+ exit: () => {},
367
+ });
368
+
369
+ // at_test has no chatgpt_user_id claim, so the suffix falls back to accountId.
370
+ // The fence is read BEFORE the credential write, so failures on the fresh login survive.
371
+ expect(calls).toEqual(["read-fence", "store", "clear:d3Ove@2 fence 7"]);
372
+ });
373
+
374
+ it("skips the unfenced clear when the fence cannot be read", async () => {
375
+ const error = mock(() => {});
376
+ const exit = mock(() => {});
377
+ const store = mock(async () => {});
378
+ const clearAuthBench = mock(async () => true);
379
+
380
+ await runCodexLogin([], {
381
+ resolveConfig: async () => ({
382
+ apiUrl: "http://localhost:3013",
383
+ apiKey: "example-test-key",
384
+ slot: 2,
385
+ }),
386
+ login: mock(async () => ({
387
+ access: "at_test",
388
+ refresh: "rt_test",
389
+ expires: Date.now() + 3600000,
390
+ accountId: "acc-test-d3Ove",
391
+ })),
392
+ store,
393
+ loadAllSlots: mock(async () => []),
394
+ clearAuthBench,
395
+ readAuthFence: mock(async () => {
396
+ throw new Error("keys/available returned 503");
397
+ }),
398
+ log: () => {},
399
+ error,
400
+ exit,
401
+ });
402
+
403
+ expect(store).toHaveBeenCalled();
404
+ expect(clearAuthBench).not.toHaveBeenCalled();
405
+ expect(error).toHaveBeenCalledWith(expect.stringContaining("keys/available returned 503"));
406
+ expect(exit).not.toHaveBeenCalled();
407
+ });
408
+
409
+ it("keeps a successful login when lifting the bench fails", async () => {
410
+ const error = mock(() => {});
411
+ const exit = mock(() => {});
412
+
413
+ await runCodexLogin([], {
414
+ resolveConfig: async () => ({
415
+ apiUrl: "http://localhost:3013",
416
+ apiKey: "example-test-key",
417
+ slot: 2,
418
+ }),
419
+ login: mock(async () => ({
420
+ access: "at_test",
421
+ refresh: "rt_test",
422
+ expires: Date.now() + 3600000,
423
+ accountId: "acc-test-d3Ove",
424
+ })),
425
+ store: mock(async () => {}),
426
+ loadAllSlots: mock(async () => []),
427
+ clearAuthBench: mock(async () => {
428
+ throw new Error("clear-rate-limit returned 500");
429
+ }),
430
+ readAuthFence: mock(async () => 0),
431
+ log: () => {},
432
+ error,
433
+ exit,
434
+ });
435
+
436
+ expect(error).toHaveBeenCalledWith(
437
+ expect.stringContaining("could not lift the auth-failure bench"),
438
+ );
439
+ expect(exit).not.toHaveBeenCalled();
440
+ });
325
441
  });
@@ -8,13 +8,12 @@ import {
8
8
  } from "../utils/context-window";
9
9
 
10
10
  describe("getContextWindowSize", () => {
11
- test("returns 1M for fable and mythos models", () => {
11
+ // Windows come from the model catalog: a model the catalog lacks (mythos is
12
+ // not on models.dev) gets the 200K default until an overlay row adds it.
13
+ test("returns 1M for fable models", () => {
12
14
  expect(getContextWindowSize("claude-fable-5-1")).toBe(1_000_000);
13
- expect(getContextWindowSize("claude-mythos-5-1")).toBe(1_000_000);
14
15
  expect(getContextWindowSize("claude-fable-5")).toBe(1_000_000);
15
- expect(getContextWindowSize("claude-mythos-5")).toBe(1_000_000);
16
16
  expect(getContextWindowSize("fable")).toBe(1_000_000);
17
- expect(getContextWindowSize("mythos")).toBe(1_000_000);
18
17
  });
19
18
 
20
19
  test("returns 1M for opus models", () => {
@@ -38,8 +37,11 @@ describe("getContextWindowSize", () => {
38
37
  expect(getContextWindowSize("haiku")).toBe(200_000);
39
38
  });
40
39
 
40
+ test("reads OpenAI windows from the catalog", () => {
41
+ expect(getContextWindowSize("gpt-5")).toBe(400_000);
42
+ });
43
+
41
44
  test("returns 200K default for unknown models", () => {
42
- expect(getContextWindowSize("gpt-5")).toBe(200_000);
43
45
  expect(getContextWindowSize("unknown-model")).toBe(200_000);
44
46
  expect(getContextWindowSize("")).toBe(200_000);
45
47
  });
@@ -1,4 +1,4 @@
1
- import { afterAll, beforeAll, describe, expect, test } from "bun:test";
1
+ import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
2
2
  import { fetchResolvedEnv } from "../commands/runner";
3
3
  import {
4
4
  type ModelFamily,
@@ -18,6 +18,9 @@ describe("resolveCredentialPools — model window exhaustion policy", () => {
18
18
  let apiUrl: string;
19
19
  const earliestResetAt = "2026-09-27T00:00:00.000Z";
20
20
  let configResponse: { configs: Array<{ key: string; value: string }> } = { configs: [] };
21
+ /** Blocked indices returned for `model=fable`. The seat tests below override it. */
22
+ const windowOnlyFableBlocks = { modelBlockedIndices: [0, 1], seatBlockedIndices: [] as number[] };
23
+ let fableBlocks = windowOnlyFableBlocks;
21
24
 
22
25
  beforeAll(() => {
23
26
  server = Bun.serve({
@@ -34,8 +37,9 @@ describe("resolveCredentialPools — model window exhaustion policy", () => {
34
37
  success: true,
35
38
  availableIndices: [],
36
39
  totalKeys: 2,
37
- modelBlockedIndices: [0, 1],
38
- earliestModelResetAt: earliestResetAt,
40
+ modelBlockedIndices: fableBlocks.modelBlockedIndices,
41
+ seatBlockedIndices: fableBlocks.seatBlockedIndices,
42
+ earliestModelResetAt: fableBlocks.modelBlockedIndices.length ? earliestResetAt : null,
39
43
  });
40
44
  }
41
45
  // sonnet (or any other model / no model): pool is fully available.
@@ -175,4 +179,115 @@ describe("resolveCredentialPools — model window exhaustion policy", () => {
175
179
  expect(selections.length).toBe(1);
176
180
  expect(selections[0]!.isRateLimitFallback).toBe(false);
177
181
  });
182
+
183
+ describe("seat blocks", () => {
184
+ const fableTask = {
185
+ apiUrl: "",
186
+ apiKey: "key",
187
+ provider: "claude",
188
+ model: "claude-fable-5-1",
189
+ enforceModelCapacity: true,
190
+ };
191
+
192
+ afterEach(() => {
193
+ fableBlocks = windowOnlyFableBlocks;
194
+ });
195
+
196
+ test("default policy: throws and names seat blocks and window blocks", async () => {
197
+ fableBlocks = { modelBlockedIndices: [0], seatBlockedIndices: [1] };
198
+ const env: Record<string, string | undefined> = { CLAUDE_CODE_OAUTH_TOKEN: "tok-a,tok-b" };
199
+ const err = await resolveCredentialPools(env, { ...fableTask, apiUrl }).catch((e) => e);
200
+ expect(err).toBeInstanceOf(ModelWindowExhaustedError);
201
+ const typed = err as ModelWindowExhaustedError;
202
+ expect(typed.seatBlockedCount).toBe(1);
203
+ expect(typed.modelBlockedCount).toBe(1);
204
+ expect(typed.message).toBe(
205
+ "No CLAUDE_CODE_OAUTH_TOKEN key can run Fable: 1 keys are on a seat without Fable, 1 keys have the Fable window exhausted until 2026-09-27T00:00:00.000Z. Re-dispatch with another model or modelTier.",
206
+ );
207
+ });
208
+
209
+ test("MODEL_WINDOW_EXHAUSTED_POLICY=fallback: seat blocks still throw", async () => {
210
+ fableBlocks = { modelBlockedIndices: [], seatBlockedIndices: [0, 1] };
211
+ const env: Record<string, string | undefined> = {
212
+ CLAUDE_CODE_OAUTH_TOKEN: "tok-a,tok-b",
213
+ MODEL_WINDOW_EXHAUSTED_POLICY: "fallback",
214
+ };
215
+ const err = await resolveCredentialPools(env, { ...fableTask, apiUrl }).catch((e) => e);
216
+ expect(err).toBeInstanceOf(ModelWindowExhaustedError);
217
+ const typed = err as ModelWindowExhaustedError;
218
+ expect(typed.earliestResetAt).toBeNull();
219
+ expect(typed.message).toBe(
220
+ "No CLAUDE_CODE_OAUTH_TOKEN key can run Fable: 2 keys are on a seat without Fable, 0 keys have the Fable window exhausted. Re-dispatch with another model or modelTier.",
221
+ );
222
+ });
223
+
224
+ test("MODEL_WINDOW_EXHAUSTED_POLICY=fallback: window blocks alone still pick a key", async () => {
225
+ const env: Record<string, string | undefined> = {
226
+ CLAUDE_CODE_OAUTH_TOKEN: "tok-a,tok-b",
227
+ MODEL_WINDOW_EXHAUSTED_POLICY: "fallback",
228
+ };
229
+ const selections = await resolveCredentialPools(env, { ...fableTask, apiUrl });
230
+ expect(selections).toHaveLength(1);
231
+ expect(selections[0]!.isRateLimitFallback).toBe(true);
232
+ });
233
+
234
+ test("MODEL_WINDOW_EXHAUSTED_POLICY=fallback: a mixed pool picks the Fable-capable key", async () => {
235
+ fableBlocks = { modelBlockedIndices: [1], seatBlockedIndices: [0] };
236
+ const env: Record<string, string | undefined> = {
237
+ CLAUDE_CODE_OAUTH_TOKEN: "tok-a,tok-b",
238
+ MODEL_WINDOW_EXHAUSTED_POLICY: "fallback",
239
+ };
240
+ const selections = await resolveCredentialPools(env, { ...fableTask, apiUrl });
241
+ expect(selections).toHaveLength(1);
242
+ expect(selections[0]!.index).toBe(1);
243
+ expect(selections[0]!.isRateLimitFallback).toBe(true);
244
+ expect(env.CLAUDE_CODE_OAUTH_TOKEN).toBe("tok-b");
245
+ });
246
+
247
+ test("MODEL_WINDOW_EXHAUSTED_POLICY=fallback: every key without a seat block stays reachable", async () => {
248
+ fableBlocks = { modelBlockedIndices: [1, 2], seatBlockedIndices: [0] };
249
+ const picked = new Set<number>();
250
+ for (let run = 0; run < 50; run++) {
251
+ const env: Record<string, string | undefined> = {
252
+ CLAUDE_CODE_OAUTH_TOKEN: "tok-a,tok-b,tok-c",
253
+ MODEL_WINDOW_EXHAUSTED_POLICY: "fallback",
254
+ };
255
+ const selections = await resolveCredentialPools(env, { ...fableTask, apiUrl });
256
+ picked.add(selections[0]!.index);
257
+ }
258
+ expect([...picked].sort()).toEqual([1, 2]);
259
+ });
260
+
261
+ test("default policy: a key-wide rate limit next to a seat block picks the other key", async () => {
262
+ fableBlocks = { modelBlockedIndices: [], seatBlockedIndices: [0] };
263
+ const env: Record<string, string | undefined> = { CLAUDE_CODE_OAUTH_TOKEN: "tok-a,tok-b" };
264
+ const selections = await resolveCredentialPools(env, { ...fableTask, apiUrl });
265
+ expect(selections[0]!.index).toBe(1);
266
+ expect(selections[0]!.isRateLimitFallback).toBe(true);
267
+ });
268
+
269
+ test("a taskless configuration load never throws on seat blocks", async () => {
270
+ fableBlocks = { modelBlockedIndices: [], seatBlockedIndices: [0, 1] };
271
+ const env: Record<string, string | undefined> = { CLAUDE_CODE_OAUTH_TOKEN: "tok-a,tok-b" };
272
+ const selections = await resolveCredentialPools(env, {
273
+ ...fableTask,
274
+ apiUrl,
275
+ enforceModelCapacity: undefined,
276
+ });
277
+ expect(selections).toHaveLength(1);
278
+ expect(selections[0]!.seatBlockedIndices).toEqual([0, 1]);
279
+ });
280
+
281
+ test("a sonnet task on a seat-blocked Fable pool picks a key", async () => {
282
+ fableBlocks = { modelBlockedIndices: [], seatBlockedIndices: [0, 1] };
283
+ const env: Record<string, string | undefined> = { CLAUDE_CODE_OAUTH_TOKEN: "tok-a,tok-b" };
284
+ const selections = await resolveCredentialPools(env, {
285
+ ...fableTask,
286
+ apiUrl,
287
+ model: "claude-sonnet-5",
288
+ });
289
+ expect(selections).toHaveLength(1);
290
+ expect(selections[0]!.isRateLimitFallback).toBe(false);
291
+ });
292
+ });
178
293
  });