@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,142 @@
1
+ /**
2
+ * HTTP contract for the Codex pool auth-failure bench:
3
+ * POST /api/keys/report-auth-failure and the clearAuthBench flag on
4
+ * POST /api/keys/clear-rate-limit.
5
+ */
6
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
7
+ import { unlink } from "node:fs/promises";
8
+ import { createServer as createHttpServer, type Server } from "node:http";
9
+ import { closeDb, initDb } from "../be/db";
10
+ import { clearCodexAuthBench, readCodexAuthFailureFence } from "../commands/codex-login";
11
+ import { handleApiKeys } from "../http/api-keys";
12
+ import { listenOnFreePort } from "./test-net";
13
+
14
+ const TEST_DB = `./test-api-key-auth-failure-route-${Date.now()}.sqlite`;
15
+
16
+ describe("API key auth-failure routes", () => {
17
+ let server: Server;
18
+ let baseUrl = "";
19
+
20
+ const post = async (path: string, body: unknown) => {
21
+ const res = await fetch(`${baseUrl}${path}`, {
22
+ method: "POST",
23
+ headers: { "Content-Type": "application/json" },
24
+ body: JSON.stringify(body),
25
+ });
26
+ expect(res.status).toBe(200);
27
+ return (await res.json()) as Record<string, unknown>;
28
+ };
29
+ const get = async (path: string) => {
30
+ const res = await fetch(`${baseUrl}${path}`);
31
+ expect(res.status).toBe(200);
32
+ return (await res.json()) as Record<string, unknown>;
33
+ };
34
+ const key = { keyType: "CODEX_OAUTH", keySuffix: "qa001" };
35
+
36
+ beforeAll(async () => {
37
+ process.env.DB_PATH = TEST_DB;
38
+ initDb(TEST_DB);
39
+ server = createHttpServer(async (req, res) => {
40
+ const url = new URL(req.url || "/", "http://localhost");
41
+ const pathSegments = url.pathname.split("/").filter(Boolean);
42
+ const handled = await handleApiKeys(req, res, pathSegments, url.searchParams);
43
+ if (!handled) {
44
+ res.writeHead(404);
45
+ res.end();
46
+ }
47
+ });
48
+ const port = await listenOnFreePort(server);
49
+ baseUrl = `http://127.0.0.1:${port}`;
50
+ });
51
+
52
+ afterAll(async () => {
53
+ await new Promise<void>((resolve) => server.close(() => resolve()));
54
+ closeDb();
55
+ await unlink(TEST_DB).catch(() => {});
56
+ await unlink(`${TEST_DB}-wal`).catch(() => {});
57
+ await unlink(`${TEST_DB}-shm`).catch(() => {});
58
+ });
59
+
60
+ test("benches on the second report in a row", async () => {
61
+ const first = await post("/api/keys/report-auth-failure", { ...key, keyIndex: 1 });
62
+ expect(first).toMatchObject({ success: true, consecutiveAuthFailures: 1, benched: false });
63
+
64
+ const second = await post("/api/keys/report-auth-failure", { ...key, keyIndex: 1 });
65
+ expect(second).toMatchObject({ success: true, consecutiveAuthFailures: 2, benched: true });
66
+ expect(typeof second.rateLimitedUntil).toBe("string");
67
+
68
+ const available = await get("/api/keys/available?keyType=CODEX_OAUTH&totalKeys=3");
69
+ expect(available.availableIndices).toEqual([0, 2]);
70
+ });
71
+
72
+ test("clear-rate-limit without clearAuthBench leaves the auth bench", async () => {
73
+ const result = await post("/api/keys/clear-rate-limit", key);
74
+ expect(result).toMatchObject({ success: true, cleared: false });
75
+ });
76
+
77
+ test("clear-rate-limit with clearAuthBench but no authFence leaves the auth bench", async () => {
78
+ const result = await post("/api/keys/clear-rate-limit", { ...key, clearAuthBench: true });
79
+ expect(result).toMatchObject({ success: true, cleared: false });
80
+ });
81
+
82
+ test("clear-rate-limit with clearAuthBench and a current authFence lifts the bench", async () => {
83
+ const { authFailureFence } = await get("/api/keys/available?keyType=CODEX_OAUTH&totalKeys=3");
84
+ expect(typeof authFailureFence).toBe("number");
85
+ const result = await post("/api/keys/clear-rate-limit", {
86
+ ...key,
87
+ clearAuthBench: true,
88
+ authFence: authFailureFence,
89
+ });
90
+ expect(result).toMatchObject({ success: true, cleared: true });
91
+
92
+ const status = await get("/api/keys/status?keyType=CODEX_OAUTH");
93
+ const row = (status.keys as Array<Record<string, unknown>>).find(
94
+ (k) => k.keySuffix === "qa001",
95
+ );
96
+ expect(row).toMatchObject({ status: "available", consecutiveAuthFailures: 0 });
97
+ expect(row!.lastAuthFailureAt).not.toBeNull();
98
+ });
99
+
100
+ test("a codex-login re-login lifts the auth bench", async () => {
101
+ const relog = { keyType: "CODEX_OAUTH", keySuffix: "relog" };
102
+ await post("/api/keys/report-auth-failure", { ...relog, keyIndex: 4 });
103
+ const benched = await post("/api/keys/report-auth-failure", { ...relog, keyIndex: 4 });
104
+ expect(benched).toMatchObject({ benched: true });
105
+
106
+ const fence = await readCodexAuthFailureFence(baseUrl, "test-key");
107
+ expect(await clearCodexAuthBench(baseUrl, "test-key", "relog", 4, fence)).toBe(true);
108
+
109
+ const status = await get("/api/keys/status?keyType=CODEX_OAUTH");
110
+ const row = (status.keys as Array<Record<string, unknown>>).find(
111
+ (k) => k.keySuffix === "relog",
112
+ );
113
+ expect(row).toMatchObject({ status: "available", consecutiveAuthFailures: 0 });
114
+ });
115
+
116
+ test("a late codex-login clear keeps failures on the fresh login", async () => {
117
+ const relog = { keyType: "CODEX_OAUTH", keySuffix: "relo2" };
118
+ await post("/api/keys/report-auth-failure", { ...relog, keyIndex: 5 });
119
+ await post("/api/keys/report-auth-failure", { ...relog, keyIndex: 5 });
120
+ // codex-login reads the fence, then stores the fresh credentials.
121
+ const fence = await readCodexAuthFailureFence(baseUrl, "test-key");
122
+ // Two tasks fail on the fresh login before the clear lands.
123
+ await post("/api/keys/report-auth-failure", { ...relog, keyIndex: 5 });
124
+ await post("/api/keys/report-auth-failure", { ...relog, keyIndex: 5 });
125
+ expect(await clearCodexAuthBench(baseUrl, "test-key", "relo2", 5, fence)).toBe(false);
126
+
127
+ const status = await get("/api/keys/status?keyType=CODEX_OAUTH");
128
+ const row = (status.keys as Array<Record<string, unknown>>).find(
129
+ (k) => k.keySuffix === "relo2",
130
+ );
131
+ expect(row).toMatchObject({ status: "rate_limited", consecutiveAuthFailures: 4 });
132
+ });
133
+
134
+ test("rejects an invalid body", async () => {
135
+ const res = await fetch(`${baseUrl}/api/keys/report-auth-failure`, {
136
+ method: "POST",
137
+ headers: { "Content-Type": "application/json" },
138
+ body: JSON.stringify({ keyType: "CODEX_OAUTH", keySuffix: "qa001" }),
139
+ });
140
+ expect(res.status).toBe(400);
141
+ });
142
+ });
@@ -8,14 +8,22 @@ import {
8
8
  clearKeyRateLimit,
9
9
  closeDb,
10
10
  getAvailableKeyIndices,
11
+ getDbClient,
11
12
  getKeyStatuses,
13
+ getKv,
12
14
  initDb,
13
15
  markKeyRateLimited,
16
+ recordKeyAuthFailure,
14
17
  recordKeyRateLimitWindows,
15
18
  recordKeyUsage,
19
+ setApiKeyPlan,
16
20
  } from "../be/db";
17
21
  import type { CredentialSelection } from "../utils/credentials";
18
- import { resolveCredentialPools, selectCredential } from "../utils/credentials";
22
+ import {
23
+ ModelWindowExhaustedError,
24
+ resolveCredentialPools,
25
+ selectCredential,
26
+ } from "../utils/credentials";
19
27
 
20
28
  // ─── Credential Selection Unit Tests ────────────────────────────────────────
21
29
 
@@ -242,6 +250,258 @@ describe("API key tracking DB queries", () => {
242
250
  expect(cleared).toBe(false);
243
251
  });
244
252
 
253
+ const codexStatus = async (keySuffix: string) =>
254
+ (await getKeyStatuses("CODEX_OAUTH")).find((s) => s.keySuffix === keySuffix)!;
255
+ const DAY_MS = 24 * 60 * 60 * 1000;
256
+ /** The fence a runner reads with its key draw, or codex-login before its credential write. */
257
+ const readFence = async () => (await getAvailableKeyIndices("CODEX_OAUTH", 1)).authFailureFence;
258
+ const clearWithFence = async (keySuffix: string, authFence: number, keyIndex?: number) =>
259
+ clearKeyRateLimit("CODEX_OAUTH", keySuffix, "global", null, {
260
+ clearAuthBench: true,
261
+ authFence,
262
+ keyIndex,
263
+ });
264
+
265
+ test("recordKeyAuthFailure: 1 failure counts but does not bench", async () => {
266
+ const result = await recordKeyAuthFailure("CODEX_OAUTH", "cdx01", 0);
267
+ expect(result).toEqual({ consecutiveAuthFailures: 1, benched: false, rateLimitedUntil: null });
268
+
269
+ const row = await codexStatus("cdx01");
270
+ expect(row.consecutiveAuthFailures).toBe(1);
271
+ expect(row.lastAuthFailureAt).not.toBeNull();
272
+ expect(row.status).toBe("available");
273
+ const { availableIndices } = await getAvailableKeyIndices("CODEX_OAUTH", 3);
274
+ expect(availableIndices).toContain(0);
275
+ });
276
+
277
+ test("recordKeyAuthFailure: 2 failures in a row bench for 365 days", async () => {
278
+ const result = await recordKeyAuthFailure("CODEX_OAUTH", "cdx01", 0);
279
+ expect(result.consecutiveAuthFailures).toBe(2);
280
+ expect(result.benched).toBe(true);
281
+
282
+ const row = await codexStatus("cdx01");
283
+ expect(row.status).toBe("rate_limited");
284
+ expect(row.rateLimitedUntil).toBe(result.rateLimitedUntil);
285
+ expect(Math.abs(Date.parse(row.rateLimitedUntil!) - (Date.now() + 365 * DAY_MS))).toBeLessThan(
286
+ 60_000,
287
+ );
288
+ expect(row.lastRateLimitAt).not.toBeNull();
289
+
290
+ const marker = await getKv("codex-auth-watch", "bench:cdx01");
291
+ expect(marker).not.toBeNull();
292
+ expect(marker!.value).toMatchObject({
293
+ keyIndex: 0,
294
+ keyType: "CODEX_OAUTH",
295
+ benchedUntil: row.rateLimitedUntil,
296
+ source: "report-auth-failure",
297
+ });
298
+
299
+ const { availableIndices } = await getAvailableKeyIndices("CODEX_OAUTH", 3);
300
+ expect(availableIndices).not.toContain(0);
301
+ });
302
+
303
+ test("recordKeyAuthFailure only extends an existing longer bench", async () => {
304
+ const farUntil = new Date(Date.now() + 400 * DAY_MS).toISOString();
305
+ await markKeyRateLimited("CODEX_OAUTH", "cdx02", 1, farUntil);
306
+ await recordKeyAuthFailure("CODEX_OAUTH", "cdx02", 1);
307
+ const result = await recordKeyAuthFailure("CODEX_OAUTH", "cdx02", 1);
308
+ expect(result.benched).toBe(true);
309
+ expect(result.rateLimitedUntil).toBe(farUntil);
310
+ expect((await codexStatus("cdx02")).rateLimitedUntil).toBe(farUntil);
311
+ });
312
+
313
+ test("a success between auth failures resets the run", async () => {
314
+ await recordKeyAuthFailure("CODEX_OAUTH", "cdx03", 2);
315
+ await clearWithFence("cdx03", await readFence());
316
+ const result = await recordKeyAuthFailure("CODEX_OAUTH", "cdx03", 2);
317
+ expect(result.benched).toBe(false);
318
+
319
+ const row = await codexStatus("cdx03");
320
+ expect(row.status).toBe("available");
321
+ expect(row.consecutiveAuthFailures).toBe(1);
322
+ });
323
+
324
+ test("clearKeyRateLimit without clearAuthBench cannot lift an auth bench", async () => {
325
+ const cleared = await clearKeyRateLimit("CODEX_OAUTH", "cdx01");
326
+ expect(cleared).toBe(false);
327
+ expect((await codexStatus("cdx01")).status).toBe("rate_limited");
328
+ });
329
+
330
+ test("clearKeyRateLimit with clearAuthBench but no fence cannot lift an auth bench", async () => {
331
+ const cleared = await clearKeyRateLimit("CODEX_OAUTH", "cdx01", "global", null, {
332
+ clearAuthBench: true,
333
+ });
334
+ expect(cleared).toBe(false);
335
+ const row = await codexStatus("cdx01");
336
+ expect(row.status).toBe("rate_limited");
337
+ expect(row.consecutiveAuthFailures).toBe(2);
338
+ });
339
+
340
+ test("clearKeyRateLimit with clearAuthBench and a current fence lifts an auth bench", async () => {
341
+ const cleared = await clearWithFence("cdx01", await readFence());
342
+ expect(cleared).toBe(true);
343
+
344
+ const row = await codexStatus("cdx01");
345
+ expect(row.status).toBe("available");
346
+ expect(row.rateLimitedUntil).toBeNull();
347
+ expect(row.consecutiveAuthFailures).toBe(0);
348
+ expect(await getKv("codex-auth-watch", "bench:cdx01")).toBeNull();
349
+ });
350
+
351
+ test("clearKeyRateLimit without clearAuthBench still clears a plain rate limit", async () => {
352
+ const until = new Date(Date.now() + 300_000).toISOString();
353
+ await markKeyRateLimited("CODEX_OAUTH", "cdx04", 3, until);
354
+ const cleared = await clearKeyRateLimit("CODEX_OAUTH", "cdx04");
355
+ expect(cleared).toBe(true);
356
+ expect((await codexStatus("cdx04")).status).toBe("available");
357
+ });
358
+
359
+ test("an ordinary rate limit and its expiry do not undo an auth bench", async () => {
360
+ await recordKeyAuthFailure("CODEX_OAUTH", "cdx05", 4);
361
+ const benched = await recordKeyAuthFailure("CODEX_OAUTH", "cdx05", 4);
362
+ expect(benched.benched).toBe(true);
363
+
364
+ const expired = new Date(Date.now() - 1_000).toISOString();
365
+ await markKeyRateLimited("CODEX_OAUTH", "cdx05", 4, expired);
366
+
367
+ const row = await codexStatus("cdx05");
368
+ expect(row.status).toBe("rate_limited");
369
+ expect(row.rateLimitedUntil).toBe(benched.rateLimitedUntil);
370
+ const { availableIndices } = await getAvailableKeyIndices("CODEX_OAUTH", 5);
371
+ expect(availableIndices).not.toContain(4);
372
+ expect((await codexStatus("cdx05")).status).toBe("rate_limited");
373
+ });
374
+
375
+ test("an expired stored auth bench does not auto-clear", async () => {
376
+ await recordKeyAuthFailure("CODEX_OAUTH", "cdx06", 1);
377
+ expect((await recordKeyAuthFailure("CODEX_OAUTH", "cdx06", 1)).benched).toBe(true);
378
+ const past = new Date(Date.now() - 60_000).toISOString();
379
+ await getDbClient().run(
380
+ `UPDATE api_key_status SET rateLimitedUntil = ? WHERE keyType = 'CODEX_OAUTH' AND keySuffix = 'cdx06'`,
381
+ [past],
382
+ );
383
+
384
+ const { availableIndices } = await getAvailableKeyIndices("CODEX_OAUTH", 5);
385
+ expect(availableIndices).not.toContain(1);
386
+ const row = await codexStatus("cdx06");
387
+ expect(row.status).toBe("rate_limited");
388
+ expect(row.rateLimitedUntil).toBe(past);
389
+ expect(row.consecutiveAuthFailures).toBe(2);
390
+ expect(await getKv("codex-auth-watch", "bench:cdx06")).not.toBeNull();
391
+ });
392
+
393
+ test("each auth failure gets a higher server-side order than the last fence", async () => {
394
+ const before = await readFence();
395
+ await recordKeyAuthFailure("CODEX_OAUTH", "seq01", 4);
396
+ const afterFirst = await readFence();
397
+ await recordKeyAuthFailure("CODEX_OAUTH", "seq02", 4);
398
+ expect(afterFirst).toBeGreaterThan(before);
399
+ expect(await readFence()).toBeGreaterThan(afterFirst);
400
+ });
401
+
402
+ test("two runners: a late success fenced before the other runner's failures keeps the bench", async () => {
403
+ // Runner A draws the key (reads the fence) and later succeeds. Worker clocks play no
404
+ // part: whatever A's clock says, only the server-side order decides.
405
+ const fenceA = await readFence();
406
+ // Runner B then fails twice on the same login and benches it.
407
+ await recordKeyAuthFailure("CODEX_OAUTH", "cdx07", 2);
408
+ expect((await recordKeyAuthFailure("CODEX_OAUTH", "cdx07", 2)).benched).toBe(true);
409
+ // A's success reset lands last.
410
+ expect(await clearWithFence("cdx07", fenceA)).toBe(false);
411
+ let row = await codexStatus("cdx07");
412
+ expect(row.status).toBe("rate_limited");
413
+ expect(row.consecutiveAuthFailures).toBe(2);
414
+ expect(await getKv("codex-auth-watch", "bench:cdx07")).not.toBeNull();
415
+
416
+ // A success from a task drawn after the failures still lifts it.
417
+ expect(await clearWithFence("cdx07", await readFence())).toBe(true);
418
+ row = await codexStatus("cdx07");
419
+ expect(row.status).toBe("available");
420
+ expect(row.consecutiveAuthFailures).toBe(0);
421
+ });
422
+
423
+ test("a clock rollback (restart or skew) cannot reorder a success before later failures", async () => {
424
+ const staleFence = await readFence();
425
+ // The failures are recorded while every clock in the process reads an hour earlier.
426
+ const realNow = Date.now;
427
+ Date.now = () => realNow() - 3_600_000;
428
+ try {
429
+ await recordKeyAuthFailure("CODEX_OAUTH", "cdx09", 4);
430
+ await recordKeyAuthFailure("CODEX_OAUTH", "cdx09", 4);
431
+ } finally {
432
+ Date.now = realNow;
433
+ }
434
+ expect(await readFence()).toBeGreaterThan(staleFence);
435
+ expect(await clearWithFence("cdx09", staleFence)).toBe(false);
436
+ const row = await codexStatus("cdx09");
437
+ expect(row.status).toBe("rate_limited");
438
+ expect(row.consecutiveAuthFailures).toBe(2);
439
+ });
440
+
441
+ test("a stale success does not reset a count below the threshold", async () => {
442
+ const staleFence = await readFence();
443
+ await recordKeyAuthFailure("CODEX_OAUTH", "cdx08", 3);
444
+ await clearWithFence("cdx08", staleFence);
445
+ expect((await codexStatus("cdx08")).consecutiveAuthFailures).toBe(1);
446
+ });
447
+
448
+ test("a late re-login clear keeps failures recorded after the credential write", async () => {
449
+ // The login was benched before the re-login.
450
+ await recordKeyAuthFailure("CODEX_OAUTH", "rel01", 1);
451
+ await recordKeyAuthFailure("CODEX_OAUTH", "rel01", 1);
452
+ // codex-login reads the fence, then stores the fresh credentials.
453
+ const fence = await readFence();
454
+ // Two tasks fail on the fresh login before the clear request lands.
455
+ await recordKeyAuthFailure("CODEX_OAUTH", "rel01", 1);
456
+ await recordKeyAuthFailure("CODEX_OAUTH", "rel01", 1);
457
+ await clearWithFence("rel01", fence, 1);
458
+ const row = await codexStatus("rel01");
459
+ expect(row.status).toBe("rate_limited");
460
+ expect(row.consecutiveAuthFailures).toBe(4);
461
+ expect(await getKv("codex-auth-watch", "bench:rel01")).not.toBeNull();
462
+ });
463
+
464
+ test("a re-login clear lifts the bench recorded before the credential write", async () => {
465
+ await recordKeyAuthFailure("CODEX_OAUTH", "rel02", 2);
466
+ await recordKeyAuthFailure("CODEX_OAUTH", "rel02", 2);
467
+ expect(await clearWithFence("rel02", await readFence(), 2)).toBe(true);
468
+ const row = await codexStatus("rel02");
469
+ expect(row.status).toBe("available");
470
+ expect(row.consecutiveAuthFailures).toBe(0);
471
+ expect(await getKv("codex-auth-watch", "bench:rel02")).toBeNull();
472
+ });
473
+
474
+ test("a slot re-login with a different account retires the previous login's bench", async () => {
475
+ await recordKeyAuthFailure("CODEX_OAUTH", "old01", 0);
476
+ await recordKeyAuthFailure("CODEX_OAUTH", "old01", 0);
477
+ expect(await getKv("codex-auth-watch", "bench:old01")).not.toBeNull();
478
+
479
+ const cleared = await clearWithFence("new01", await readFence(), 0);
480
+ expect(cleared).toBe(true);
481
+ await recordKeyUsage("CODEX_OAUTH", "new01", 0, null);
482
+
483
+ const old = await codexStatus("old01");
484
+ expect(old.status).toBe("available");
485
+ expect(old.rateLimitedUntil).toBeNull();
486
+ expect(old.consecutiveAuthFailures).toBe(0);
487
+ expect(await getKv("codex-auth-watch", "bench:old01")).toBeNull();
488
+ const { availableIndices } = await getAvailableKeyIndices("CODEX_OAUTH", 5);
489
+ expect(availableIndices).toContain(0);
490
+ });
491
+
492
+ test("a clear without keyIndex leaves other logins at the same index alone", async () => {
493
+ await recordKeyAuthFailure("CODEX_OAUTH", "old02", 3);
494
+ await recordKeyAuthFailure("CODEX_OAUTH", "old02", 3);
495
+ await clearWithFence("new02", await readFence());
496
+ expect((await codexStatus("old02")).status).toBe("rate_limited");
497
+ });
498
+
499
+ test("an ordinary rate limit longer than the auth bench still applies", async () => {
500
+ const farUntil = new Date(Date.now() + 500 * DAY_MS).toISOString();
501
+ await markKeyRateLimited("CODEX_OAUTH", "cdx05", 4, farUntil);
502
+ expect((await codexStatus("cdx05")).rateLimitedUntil).toBe(farUntil);
503
+ });
504
+
245
505
  test("recordKeyRateLimitWindows persists latest provider windows", async () => {
246
506
  await recordKeyRateLimitWindows("ANTHROPIC_API_KEY", "aaa11", 0, {
247
507
  seven_day: {
@@ -474,6 +734,113 @@ describe("API key tracking DB queries", () => {
474
734
  const recovered = await getAvailableKeyIndices(KEY_TYPE, 1, "agent", scopeId, "fable");
475
735
  expect(recovered.availableIndices).toEqual([0]);
476
736
  });
737
+
738
+ describe("seat filtering", () => {
739
+ // Own scope so the rows above do not leak into these counts.
740
+ const scope = "agent";
741
+ const scopeId = "seat-filter";
742
+
743
+ beforeAll(async () => {
744
+ await recordKeyUsage(KEY_TYPE, "seat0", 0, null, scope, scopeId);
745
+ await setApiKeyPlan(KEY_TYPE, "seat0", "claude_team_standard");
746
+ await recordKeyUsage(KEY_TYPE, "seat1", 1, null, scope, scopeId);
747
+ });
748
+
749
+ test("a claude_team_standard key is seat-blocked for fable", async () => {
750
+ const result = await getAvailableKeyIndices(KEY_TYPE, 2, scope, scopeId, "fable");
751
+ expect(result.availableIndices).toEqual([1]);
752
+ expect(result.seatBlockedIndices).toEqual([0]);
753
+ });
754
+
755
+ test("the same key is available for opus and for no model", async () => {
756
+ const opus = await getAvailableKeyIndices(KEY_TYPE, 2, scope, scopeId, "opus");
757
+ expect(opus.availableIndices).toEqual([0, 1]);
758
+ expect(opus.seatBlockedIndices).toEqual([]);
759
+ const none = await getAvailableKeyIndices(KEY_TYPE, 2, scope, scopeId);
760
+ expect(none.availableIndices).toEqual([0, 1]);
761
+ expect(none.seatBlockedIndices).toEqual([]);
762
+ });
763
+
764
+ test("a key with no plan is available for fable", async () => {
765
+ const result = await getAvailableKeyIndices(KEY_TYPE, 2, scope, scopeId, "fable");
766
+ expect(result.availableIndices).toContain(1);
767
+ expect(result.seatBlockedIndices).not.toContain(1);
768
+ });
769
+
770
+ test("a rate-limited standard-seat key stays seat-blocked", async () => {
771
+ const otherScopeId = "seat-filter-rl";
772
+ await recordKeyUsage(KEY_TYPE, "seat2", 0, null, scope, otherScopeId);
773
+ await setApiKeyPlan(KEY_TYPE, "seat2", "claude_team_standard");
774
+ await markKeyRateLimited(
775
+ KEY_TYPE,
776
+ "seat2",
777
+ 0,
778
+ new Date(Date.now() + 3600_000).toISOString(),
779
+ scope,
780
+ otherScopeId,
781
+ );
782
+ const result = await getAvailableKeyIndices(KEY_TYPE, 1, scope, otherScopeId, "fable");
783
+ expect(result.availableIndices).toEqual([]);
784
+ expect(result.seatBlockedIndices).toEqual([0]);
785
+ expect(result.modelBlockedIndices).toEqual([]);
786
+
787
+ // Admission must not fall back to the key-wide-blocked seat.
788
+ await expect(
789
+ resolveCredentialPools(
790
+ { CLAUDE_CODE_OAUTH_TOKEN: "tok-seat2" },
791
+ {
792
+ provider: "claude",
793
+ model: "claude-fable-5-1",
794
+ availableIndicesMap: { CLAUDE_CODE_OAUTH_TOKEN: result },
795
+ enforceModelCapacity: true,
796
+ },
797
+ ),
798
+ ).rejects.toBeInstanceOf(ModelWindowExhaustedError);
799
+ });
800
+
801
+ test("a Fable-window-blocked standard-seat key stays seat-blocked", async () => {
802
+ const otherScopeId = "seat-filter-window";
803
+ await recordKeyUsage(KEY_TYPE, "seat3", 0, null, scope, otherScopeId);
804
+ await setApiKeyPlan(KEY_TYPE, "seat3", "claude_team_standard");
805
+ await recordKeyRateLimitWindows(
806
+ KEY_TYPE,
807
+ "seat3",
808
+ 0,
809
+ {
810
+ seven_day_overage_included: {
811
+ status: "rejected",
812
+ resetsAt: Math.floor(Date.now() / 1000) + 3600,
813
+ lastSeenAt: new Date().toISOString(),
814
+ },
815
+ },
816
+ scope,
817
+ otherScopeId,
818
+ );
819
+ const result = await getAvailableKeyIndices(KEY_TYPE, 1, scope, otherScopeId, "fable");
820
+ expect(result.availableIndices).toEqual([]);
821
+ expect(result.modelBlockedIndices).toEqual([0]);
822
+ expect(result.seatBlockedIndices).toEqual([0]);
823
+
824
+ // The window-block fallback policy never applies to a seat block.
825
+ await expect(
826
+ resolveCredentialPools(
827
+ { CLAUDE_CODE_OAUTH_TOKEN: "tok-seat3", MODEL_WINDOW_EXHAUSTED_POLICY: "fallback" },
828
+ {
829
+ provider: "claude",
830
+ model: "claude-fable-5-1",
831
+ availableIndicesMap: { CLAUDE_CODE_OAUTH_TOKEN: result },
832
+ enforceModelCapacity: true,
833
+ },
834
+ ),
835
+ ).rejects.toBeInstanceOf(ModelWindowExhaustedError);
836
+ });
837
+
838
+ test("earliestModelResetAt stays null when only seat blocks exist", async () => {
839
+ const result = await getAvailableKeyIndices(KEY_TYPE, 2, scope, scopeId, "fable");
840
+ expect(result.seatBlockedIndices).toEqual([0]);
841
+ expect(result.earliestModelResetAt).toBeNull();
842
+ });
843
+ });
477
844
  });
478
845
  });
479
846
 
@@ -482,6 +482,9 @@ describe("getBasePrompt: outputs section", () => {
482
482
  const result = await getBasePrompt({ ...minimalArgs, traits: localTraits });
483
483
  expect(result).toContain("## Outputs");
484
484
  expect(result).toContain(AGENT_FS_LINE);
485
+ expect(result).toContain(
486
+ "For a file anyone outside the swarm opens, use an `agent-fs share-create` link; `AGENT_FS_LIVE_URL` links are for signed-in teammates.",
487
+ );
485
488
  expect(result).not.toContain(NO_AGENT_FS_LINE);
486
489
  });
487
490
 
@@ -747,10 +750,11 @@ describe("getBasePrompt: tools and skills section", () => {
747
750
  expect(result).not.toContain("skills directory");
748
751
  });
749
752
 
750
- test("keeps the section with only the deferred-tools line when no skills are installed", async () => {
753
+ test("keeps the section with direct-tool guidance when no skills are installed", async () => {
751
754
  const result = await getBasePrompt({ ...minimalArgs, skillsSummary: [] });
752
755
  expect(result).toContain(HEADER);
753
- expect(result).toContain(DEFERRED_LINE);
756
+ expect(result).toContain("Swarm tools are already in your tool list.");
757
+ expect(result).not.toContain(DEFERRED_LINE);
754
758
  expect(result).not.toContain("Installed skills.");
755
759
  expect(result).not.toContain(DISCOVERY_LINE);
756
760
  });
@@ -1100,3 +1104,40 @@ describe("getBasePrompt: output hygiene", () => {
1100
1104
  });
1101
1105
  }
1102
1106
  });
1107
+
1108
+ describe("getBasePrompt: harness tool discovery", () => {
1109
+ const deferred =
1110
+ "Most swarm tools are deferred. Load one with your harness tool search before the first call.";
1111
+
1112
+ test("claude with native ToolSearch gets deferred-tool guidance", async () => {
1113
+ const result = await getBasePrompt({
1114
+ ...minimalArgs,
1115
+ provider: "claude",
1116
+ traits: { ...localTraits, hasToolSearch: true },
1117
+ });
1118
+ expect(result).toContain(deferred);
1119
+ expect(result).not.toContain("Swarm tools are already in your tool list.");
1120
+ });
1121
+
1122
+ test.each([
1123
+ "pi",
1124
+ "codex",
1125
+ "claude-managed",
1126
+ "opencode",
1127
+ "acp",
1128
+ ])("%s without tool search gets direct-tool guidance", async (provider) => {
1129
+ const result = await getBasePrompt({
1130
+ ...minimalArgs,
1131
+ provider,
1132
+ traits: { ...localTraits, hasToolSearch: false },
1133
+ });
1134
+ expect(result).not.toContain(deferred);
1135
+ expect(result).toContain("Swarm tools are already in your tool list.");
1136
+ });
1137
+
1138
+ test("omitted tool-search capability defaults to direct tools", async () => {
1139
+ const result = await getBasePrompt({ ...minimalArgs, traits: localTraits });
1140
+ expect(result).not.toContain(deferred);
1141
+ expect(result).toContain("Swarm tools are already in your tool list.");
1142
+ });
1143
+ });
@@ -124,6 +124,16 @@ describe("ClaudeSession spawn env — reasoning_effort", () => {
124
124
  expect(spawnedEnvs[0]?.CLAUDE_CODE_EFFORT_LEVEL).toBe("high");
125
125
  });
126
126
 
127
+ test("reasoningEffort: 'high' on the tier-default shortname opus sets CLAUDE_CODE_EFFORT_LEVEL", async () => {
128
+ const adapter = new ClaudeAdapter(async () => {});
129
+ await adapter.createSession(
130
+ makeConfig({ model: "opus", reasoningEffort: "high", env: CLEAN_ENV }),
131
+ );
132
+
133
+ expect(spawnedEnvs).toHaveLength(1);
134
+ expect(spawnedEnvs[0]?.CLAUDE_CODE_EFFORT_LEVEL).toBe("high");
135
+ });
136
+
127
137
  test("reasoningEffort: 'off' on a legacy budget_tokens-capable model sets MAX_THINKING_TOKENS=0, no effort env", async () => {
128
138
  const adapter = new ClaudeAdapter(async () => {});
129
139
  await adapter.createSession(
@@ -291,6 +301,64 @@ describe("ClaudeSession processStreams — ProviderResult.output capture", () =>
291
301
  expect(summaryOpts!.env?.AGENT_SWARM_TASK_ID).toBe("test-task-id");
292
302
  });
293
303
 
304
+ test("a credits_required rate_limit_event reaches the log file and raw_log without the seat payload", async () => {
305
+ const seatEvent = JSON.stringify({
306
+ type: "rate_limit_event",
307
+ rate_limit_info: {
308
+ status: "rejected",
309
+ resetsAt: 1790812800,
310
+ overageDisabledReason: "member_zero_credit_limit",
311
+ isUsingOverage: false,
312
+ errorCode: "credits_required",
313
+ canUserPurchaseCredits: false,
314
+ hasChargeableSavedPaymentMethod: true,
315
+ },
316
+ uuid: "bff51e54-9777-458a-aceb-b50aaa4255b5",
317
+ session_id: "9d326326-0c59-4b00-b819-b9b7dd3bf99f",
318
+ });
319
+ spawnSpy.mockImplementation((() =>
320
+ makeStreamingFakeProc([
321
+ seatEvent,
322
+ assistantLine([{ type: "text", text: "done" }]),
323
+ ])) as typeof Bun.spawn);
324
+
325
+ const logDir = await mkdtemp(join(tmpdir(), "claude-seat-egress-"));
326
+ const logFile = join(logDir, "session.jsonl");
327
+ try {
328
+ const adapter = new ClaudeAdapter(async () => {});
329
+ const session = await adapter.createSession(makeConfig({ env: CLEAN_ENV, logFile }));
330
+ const rawLogs: string[] = [];
331
+ session.onEvent((event) => {
332
+ if (event.type === "raw_log") rawLogs.push(event.content);
333
+ });
334
+ const result = await session.waitForCompletion();
335
+
336
+ // Error tracking still reads the original event.
337
+ expect(result.creditsRequired?.overageDisabledReason).toBe("member_zero_credit_limit");
338
+
339
+ const fileLog = await Bun.file(logFile).text();
340
+ const egress = [fileLog, ...rawLogs];
341
+ for (const content of egress) {
342
+ expect(content).not.toContain("overageDisabledReason");
343
+ expect(content).not.toContain("member_zero_credit_limit");
344
+ expect(content).not.toContain("canUserPurchaseCredits");
345
+ expect(content).not.toContain("hasChargeableSavedPaymentMethod");
346
+ }
347
+ const loggedEvent = rawLogs.find((line) => line.includes('"rate_limit_event"'));
348
+ expect(loggedEvent).toBeDefined();
349
+ expect(JSON.parse(loggedEvent!).rate_limit_info).toEqual({
350
+ status: "rejected",
351
+ resetsAt: 1790812800,
352
+ isUsingOverage: false,
353
+ errorCode: "credits_required",
354
+ });
355
+ expect(fileLog).toContain('"errorCode":"credits_required"');
356
+ expect(fileLog).toContain('"text":"done"');
357
+ } finally {
358
+ await rm(logDir, { recursive: true, force: true });
359
+ }
360
+ });
361
+
294
362
  test("tool_use-only, thinking-only, and empty-text turns are skipped, not captured", async () => {
295
363
  const lines = [
296
364
  assistantLine([{ type: "text", text: "Real answer" }]),