@desplega.ai/agent-swarm 2.5.3 → 2.6.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 (349) hide show
  1. package/README.md +2 -2
  2. package/dist/{acp-adapter-gr2fcyyj.js → acp-adapter-dgfakfpw.js} +9 -9
  3. package/dist/{actions-wv766ghf.js → actions-fbqma8aa.js} +21 -21
  4. package/dist/{amp-adapter-speb906t.js → amp-adapter-en0jy6hs.js} +4 -4
  5. package/dist/{app-ag29n14f.js → app-k8a5cgpp.js} +3 -3
  6. package/dist/{assistant-3x8bz3eg.js → assistant-a16433gr.js} +30 -26
  7. package/dist/{boot-reembed-jsmfrpzf.js → boot-reembed-6c1sjfnr.js} +1 -1
  8. package/dist/{boot-reembed-5b3d2eea.js → boot-reembed-f4nn561n.js} +1 -1
  9. package/dist/{boot-scrub-sweep-s73gerxy.js → boot-scrub-sweep-d4w9gajf.js} +4 -4
  10. package/dist/{boot-seeds-5zmax2wr.js → boot-seeds-9bmkspyw.js} +4 -4
  11. package/dist/{claude-adapter-hswdnm3t.js → claude-adapter-nys1kjxa.js} +9 -9
  12. package/dist/{claude-managed-adapter-cvbzj1h6.js → claude-managed-adapter-55207973.js} +3 -3
  13. package/dist/{claude-managed-setup-3k8m3hhc.js → claude-managed-setup-3cm35gey.js} +11 -4
  14. package/dist/{claude-sdk-session-fkbyk66x.js → claude-sdk-session-nw5yd8cp.js} +9313 -9260
  15. package/dist/{cli-210xd1f4.js → cli-03gg9pd1.js} +4 -4
  16. package/dist/{cli-kxpfx2xr.js → cli-0z1xjzwa.js} +2 -2
  17. package/dist/cli-1tem6ns9.js +894 -0
  18. package/dist/{cli-25j01dpj.js → cli-24j1jeeb.js} +3 -3
  19. package/dist/{cli-ta8bd7th.js → cli-2sjhpsns.js} +2 -2
  20. package/dist/{cli-p859hcc8.js → cli-39aet655.js} +2 -2
  21. package/dist/{cli-g1cw7xtt.js → cli-39rrw8yp.js} +1 -1
  22. package/dist/{cli-e2ar4bp1.js → cli-3aqd4jaa.js} +121 -47
  23. package/dist/{cli-jjznk3f7.js → cli-3fwqy71j.js} +69 -35
  24. package/dist/{cli-p9rafdrb.js → cli-3gfvmzht.js} +7 -7
  25. package/dist/{cli-03vpq45j.js → cli-3ndmpxxv.js} +2 -2
  26. package/dist/{cli-aecdcr77.js → cli-4hxrf0p4.js} +228 -74
  27. package/dist/{cli-mz9afxxf.js → cli-4rf0f5vg.js} +15 -3
  28. package/dist/{cli-qzme5949.js → cli-5rj83bam.js} +284 -202
  29. package/dist/{cli-fsdprwrg.js → cli-5sckpqcx.js} +2 -2
  30. package/dist/{cli-cz5nmdxt.js → cli-683y0b3a.js} +2 -2
  31. package/dist/{cli-3ngdk5te.js → cli-6y51ryva.js} +2 -2
  32. package/dist/{cli-n4c8e3s5.js → cli-708wdy32.js} +1 -1
  33. package/dist/{cli-pasyvjmm.js → cli-737pyqxr.js} +1 -1
  34. package/dist/{cli-gx4rm0qx.js → cli-7enjwya9.js} +1 -1
  35. package/dist/{cli-6x9ma5w5.js → cli-803188pb.js} +1 -1
  36. package/dist/{cli-avdvgcay.js → cli-9abdgn5t.js} +28 -9
  37. package/dist/{cli-99xgw26r.js → cli-a2rr0fmg.js} +1 -1
  38. package/dist/{cli-e5bxnjps.js → cli-a34pr3w1.js} +7 -7
  39. package/dist/{cli-502eqv93.js → cli-abkm71vr.js} +1 -1
  40. package/dist/{cli-mmhj8vd7.js → cli-ag2yrnt5.js} +11 -2
  41. package/dist/{cli-n45mr7zh.js → cli-aj8djyqt.js} +5 -5
  42. package/dist/{cli-1fc85mvt.js → cli-art7c1zh.js} +5 -5
  43. package/dist/{cli-40jsnrjm.js → cli-az25fn5f.js} +10 -8
  44. package/dist/{cli-9s05422w.js → cli-bqztfyve.js} +1 -1
  45. package/dist/{cli-bn0rewsy.js → cli-c081bj3b.js} +2 -2
  46. package/dist/{cli-svrj71pg.js → cli-c2habvsf.js} +12 -12
  47. package/dist/{cli-s49hpn37.js → cli-ca6banwz.js} +85 -19
  48. package/dist/{cli-hhxjnjzs.js → cli-cz37jqss.js} +12 -10
  49. package/dist/{cli-6n8axjv1.js → cli-d2dpf95n.js} +2 -2
  50. package/dist/{cli-tx00c0h6.js → cli-dqygeeh5.js} +17 -1
  51. package/dist/{cli-z21whrrg.js → cli-emhjcj2y.js} +17 -17
  52. package/dist/{cli-e8jyq7d8.js → cli-esakngh8.js} +7 -3
  53. package/dist/{cli-ha9rmwgq.js → cli-fb8m699c.js} +2 -2
  54. package/dist/{cli-35gtk1p6.js → cli-ffvsj2qw.js} +44 -13
  55. package/dist/{cli-ce0ak9ms.js → cli-g20jjwt4.js} +26 -26
  56. package/dist/{cli-kxawjrxg.js → cli-g8ka5j4j.js} +772 -194
  57. package/dist/{cli-vwn81jbs.js → cli-gf95q3bm.js} +5 -5
  58. package/dist/{cli-s53vp709.js → cli-gnsb1zxa.js} +2 -2
  59. package/dist/{cli-nwyw3zhg.js → cli-gxee0bqa.js} +1 -1
  60. package/dist/{cli-gpv60278.js → cli-hfeb68z4.js} +7 -7
  61. package/dist/{cli-0ebey9hg.js → cli-j6rktc91.js} +1 -1
  62. package/dist/{cli-ewm3pwbq.js → cli-k21gx5ht.js} +46 -23
  63. package/dist/{cli-egjggehm.js → cli-kf4jree9.js} +2 -2
  64. package/dist/{cli-dabehxhp.js → cli-kt907da6.js} +1 -1
  65. package/dist/{cli-p0rzjkya.js → cli-m5a9m0ak.js} +32 -13
  66. package/dist/{cli-fzcn3d87.js → cli-m70bajzr.js} +3 -3
  67. package/dist/{cli-w1zwctj2.js → cli-mtnj4fwa.js} +11 -5
  68. package/dist/{cli-gpsz7g88.js → cli-mvjz5an3.js} +16 -12
  69. package/dist/{cli-hcpj2xqz.js → cli-n7asmce6.js} +225 -3
  70. package/dist/{cli-y561h5a9.js → cli-nn6vx2mq.js} +5 -5
  71. package/dist/cli-nq677csy.js +140 -0
  72. package/dist/{cli-a5ebc95n.js → cli-pfk92eqz.js} +18 -15
  73. package/dist/{cli-p6zs9h4y.js → cli-rr7zx19j.js} +767 -249
  74. package/dist/{cli-qgf8emrn.js → cli-sqma6vw1.js} +10 -7
  75. package/dist/{cli-0xk505e8.js → cli-sxgtf5fw.js} +1 -1
  76. package/dist/{cli-nzfnt2gs.js → cli-tf1n0r0b.js} +1 -1
  77. package/dist/{cli-qqzmjxkf.js → cli-v49fkghp.js} +11 -6
  78. package/dist/{cli-x77gen2p.js → cli-w2656ka1.js} +7 -7
  79. package/dist/{cli-5kqnemen.js → cli-yvy48785.js} +1 -1
  80. package/dist/{cli-8s5wx3nz.js → cli-zqne1qcj.js} +44 -17
  81. package/dist/cli.js +22 -22
  82. package/dist/{codex-adapter-n8ka66vf.js → codex-adapter-d95q4qsn.js} +7 -7
  83. package/dist/{codex-hook-e2he7aca.js → codex-hook-brpnycn5.js} +1 -1
  84. package/dist/{codex-login-rn7c5gxt.js → codex-login-8cg0vhf2.js} +1 -1
  85. package/dist/{codex-session-runner-fsebts37.js → codex-session-runner-gdwd8h0c.js} +8 -8
  86. package/dist/{commands-t04rt2pc.js → commands-jqmgk54b.js} +2 -2
  87. package/dist/{controller-rgna04r2.js → controller-z3jq589f.js} +30 -4
  88. package/dist/{controllers-connect-bqm1rtft.js → controllers-connect-r2za39ef.js} +23 -23
  89. package/dist/{cursor-adapter-swr2k95y.js → cursor-adapter-4mr4jgkw.js} +2 -3
  90. package/dist/{db-a4m7haw0.js → db-ppv2tze7.js} +3 -3
  91. package/dist/{db-24ck09xy.js → db-w36wwb5g.js} +4 -2
  92. package/dist/{devin-adapter-4ycy4vrf.js → devin-adapter-byen2cxt.js} +1 -1
  93. package/dist/{dispatcher-nwf9ss0q.js → dispatcher-pv5h77vf.js} +20 -20
  94. package/dist/{docker-nfy2gc8t.js → docker-fdzyewpz.js} +4 -4
  95. package/dist/{dsh-adapter-kpdnhjst.js → dsh-adapter-d4pdpb3r.js} +2 -3
  96. package/dist/{e2b-9gkdhm3c.js → e2b-222hsajc.js} +2 -2
  97. package/dist/{e2b-cjtf8k1z.js → e2b-fn3zacvt.js} +2 -2
  98. package/dist/{easter-egg-3d-e4qv8dxj.js → easter-egg-3d-4sp4dvmc.js} +1 -1
  99. package/dist/{event-bus-3z78ep5n.js → event-bus-pghg8hqy.js} +1 -1
  100. package/dist/{execute-0xpz8950.js → execute-jev839y1.js} +1 -1
  101. package/dist/{external-channel-j1e2p34p.js → external-channel-y6nkzhnn.js} +2 -2
  102. package/dist/{grok-adapter-qtjz0whk.js → grok-adapter-t08b9ntm.js} +10 -10
  103. package/dist/handlers-eqsnmv95.js +41 -0
  104. package/dist/{heartbeat-vtesda5d.js → heartbeat-vn31xwdk.js} +23 -23
  105. package/dist/{hook-b2hhj6rd.js → hook-6d0zbvvt.js} +6 -5
  106. package/dist/{hook-8wqaszda.js → hook-cpjs8q51.js} +13 -7
  107. package/dist/{http-mxcqhhcs.js → http-9c6medda.js} +1085 -136
  108. package/dist/{index-spwxjgay.js → index-38t35h0n.js} +20 -20
  109. package/dist/{index-1r8wfpzs.js → index-g6k9tctj.js} +1 -1
  110. package/dist/{index-srg522m6.js → index-k0yt1tvq.js} +20 -20
  111. package/dist/{index-31ceyf42.js → index-pkma1yf8.js} +1 -1
  112. package/dist/{index-n1vbvta9.js → index-qhjf2htm.js} +29 -24
  113. package/dist/index-yqgbfgaj.js +43 -0
  114. package/dist/{keepalive-dsd8rtgx.js → keepalive-em0117bp.js} +4 -4
  115. package/dist/lead-23e7z59t.js +79 -0
  116. package/dist/{maintenance-jqmqwnet.js → maintenance-kbme3qy9.js} +1 -1
  117. package/dist/{oauth-refresh-sweep-q2c3bnvm.js → oauth-refresh-sweep-xysj4yse.js} +3 -3
  118. package/dist/{onboard-w4yd7s8t.js → onboard-p6q0vz97.js} +3 -3
  119. package/dist/{opencode-adapter-9tppvxar.js → opencode-adapter-esv3k9yn.js} +7 -7
  120. package/dist/{otel-impl-f36byzbd.js → otel-impl-54qhqh1z.js} +2 -2
  121. package/dist/{pi-mono-adapter-tqsge1ay.js → pi-mono-adapter-mymbwhj0.js} +41 -14
  122. package/dist/{pricing-refresh-xvak5p4v.js → pricing-refresh-yb75wv33.js} +3 -3
  123. package/dist/{rbac-roles-gfn30fb8.js → rbac-roles-1vp2mw4p.js} +2 -2
  124. package/dist/{rbac-roles-682k2mt5.js → rbac-roles-dexhc9x8.js} +1 -1
  125. package/dist/render-v2-tjay5hhf.js +16 -0
  126. package/dist/{resume-2dvq348g.js → resume-8ayf37g5.js} +11 -9
  127. package/dist/{runtime-bt5pkpm1.js → runtime-hqe5cqcd.js} +1 -1
  128. package/dist/{session-access-7f21ekq9.js → session-access-fdhp987s.js} +4 -4
  129. package/dist/session-source-backfill-qgtc86z1.js +110 -0
  130. package/dist/{setup-epffxtbk.js → setup-4rrmqcq3.js} +1 -1
  131. package/dist/{virtual-modules-mnfbh5mr.js → virtual-modules-52km00d6.js} +2 -2
  132. package/dist/{work-objects-xz1a5rqk.js → work-objects-p28fgd70.js} +23 -23
  133. package/dist/worker-e3w7vye1.js +79 -0
  134. package/dist/{x-pbpezr0s.js → x-wxrzvzx8.js} +2 -2
  135. package/openapi.json +1323 -16
  136. package/package.json +5 -5
  137. package/plugin/commands/create-pr.md +2 -2
  138. package/plugin/pi-skills/create-pr/SKILL.md +2 -2
  139. package/src/assets/source-key.ts +95 -0
  140. package/src/be/db/api-keys.ts +80 -0
  141. package/src/be/db/approvals.ts +48 -0
  142. package/src/be/db/session-labels.ts +28 -0
  143. package/src/be/db/session-moves.ts +5 -6
  144. package/src/be/db/session-tree.ts +153 -14
  145. package/src/be/db/tasks/read.ts +11 -0
  146. package/src/be/db/tasks/write.ts +21 -6
  147. package/src/be/db/workflow-idempotency.ts +183 -0
  148. package/src/be/db/workflow-runs.ts +500 -7
  149. package/src/be/db-retention.ts +61 -6
  150. package/src/be/db.ts +126 -131
  151. package/src/be/memory/providers/openai-embedding.ts +5 -1
  152. package/src/be/migrations/242_workflow_idempotency_keys.sql +40 -0
  153. package/src/be/migrations/244_api_key_validation.sql +4 -0
  154. package/src/be/onboarding.ts +4 -0
  155. package/src/be/scripts/typecheck.ts +1 -1
  156. package/src/be/sealed-json.ts +1 -0
  157. package/src/be/seed-scripts/catalog/compound-insights.inline.ts +2 -2
  158. package/src/be/seed-scripts/catalog/compound-insights.ts +2 -2
  159. package/src/be/seed-skills/bundled-files.generated.json +1 -1
  160. package/src/be/session-source-backfill.ts +163 -0
  161. package/src/be/sql-connections.ts +7 -1
  162. package/src/be/swarm-config-guard.ts +3 -0
  163. package/src/be/users.ts +9 -0
  164. package/src/comb/review-batch.ts +28 -1
  165. package/src/comb/templates.ts +7 -2
  166. package/src/commands/claude-managed-setup.ts +7 -1
  167. package/src/commands/controller.ts +33 -0
  168. package/src/commands/credential-refresh.ts +76 -1
  169. package/src/commands/credential-wait.ts +2 -2
  170. package/src/commands/provider-credentials.ts +210 -170
  171. package/src/commands/runner.ts +31 -6
  172. package/src/extensions/apply-task-create.ts +64 -11
  173. package/src/extensions/contract-types.generated.ts +1 -1
  174. package/src/extensions/contract.ts +30 -0
  175. package/src/extensions/dispatcher.ts +4 -0
  176. package/src/extensions/post-bridge.ts +8 -0
  177. package/src/hooks/context-bound-note.ts +196 -0
  178. package/src/hooks/hook.ts +12 -0
  179. package/src/http/all-routes.ts +2 -0
  180. package/src/http/api-keys.ts +53 -0
  181. package/src/http/assets.ts +15 -0
  182. package/src/http/codex-oauth-accounts.ts +191 -0
  183. package/src/http/comb.ts +15 -2
  184. package/src/http/config.ts +49 -17
  185. package/src/http/core.ts +6 -0
  186. package/src/http/fleet-benchmark.ts +5 -2
  187. package/src/http/idempotency-key.ts +117 -0
  188. package/src/http/index.ts +11 -0
  189. package/src/http/integrations.ts +5 -1
  190. package/src/http/kv.ts +8 -3
  191. package/src/http/onboarding.ts +6 -1
  192. package/src/http/pool-keys.ts +354 -0
  193. package/src/http/sessions.ts +37 -12
  194. package/src/http/stats.ts +1 -0
  195. package/src/http/status.ts +26 -12
  196. package/src/http/tasks.ts +3 -0
  197. package/src/http/used-by.ts +7 -2
  198. package/src/http/workflows.ts +207 -33
  199. package/src/integrations/chat-sdk/inbound.ts +27 -0
  200. package/src/kv-reserved-namespaces.ts +19 -0
  201. package/src/prompts/context-bound-note.ts +48 -0
  202. package/src/prompts/session-templates.ts +25 -1
  203. package/src/providers/acp-targets.ts +1 -1
  204. package/src/providers/amp-adapter.ts +30 -8
  205. package/src/providers/claude-adapter.ts +34 -5
  206. package/src/providers/claude-managed-adapter.ts +42 -9
  207. package/src/providers/claude-sdk-session.ts +10 -0
  208. package/src/providers/claude-session-events.ts +13 -0
  209. package/src/providers/codex-adapter.ts +83 -11
  210. package/src/providers/opencode-adapter.ts +18 -0
  211. package/src/providers/pi-mono-adapter.ts +35 -0
  212. package/src/runtime-providers/docker.ts +6 -2
  213. package/src/scheduler/scheduler.ts +3 -2
  214. package/src/scripts-runtime/swarm-sdk.ts +7 -1
  215. package/src/scripts-runtime/types/stdlib.d.ts +1 -1
  216. package/src/scripts-runtime/types/swarm-extension.d.ts +52 -8
  217. package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -1
  218. package/src/slack/assistant.ts +7 -0
  219. package/src/slack/channel-labels.ts +74 -0
  220. package/src/slack/handlers.ts +8 -0
  221. package/src/slack/inbound-files.ts +5 -2
  222. package/src/slack/thread-buffer.ts +4 -0
  223. package/src/tasks/sibling-awareness.ts +12 -3
  224. package/src/telemetry-context.ts +5 -6
  225. package/src/telemetry-identity.ts +3 -3
  226. package/src/tests/amp-adapter.test.ts +97 -0
  227. package/src/tests/api-keys-validation.test.ts +170 -0
  228. package/src/tests/approval-requests.test.ts +10 -7
  229. package/src/tests/approval-sweeps.test.ts +12 -6
  230. package/src/tests/base-prompt.test.ts +1 -1
  231. package/src/tests/claude-hook-output.test.ts +79 -1
  232. package/src/tests/claude-managed-adapter.test.ts +321 -0
  233. package/src/tests/claude-routes-e2e.test.ts +7 -2
  234. package/src/tests/codex-adapter.test.ts +163 -0
  235. package/src/tests/codex-credential-pools.test.ts +120 -0
  236. package/src/tests/codex-gateway-route.test.ts +165 -0
  237. package/src/tests/codex-oauth-accounts.test.ts +357 -0
  238. package/src/tests/comb-review-batch.test.ts +63 -2
  239. package/src/tests/config-pool-keys.test.ts +533 -0
  240. package/src/tests/context-bound-note.test.ts +269 -0
  241. package/src/tests/credential-check.test.ts +290 -2
  242. package/src/tests/credential-refresh.test.ts +172 -1
  243. package/src/tests/db-retention.test.ts +9 -3
  244. package/src/tests/db-task-read-contract.test.ts +1 -0
  245. package/src/tests/db-task-write-contract.test.ts +1 -3
  246. package/src/tests/docs-index-footprint.test.js +3 -3
  247. package/src/tests/extensions-pre-task.test.ts +325 -0
  248. package/src/tests/fetch-resolved-env.test.ts +17 -0
  249. package/src/tests/fixtures/extensions/record-blocks/hooks.ts +10 -0
  250. package/src/tests/fixtures/extensions/record-blocks/manifest.json +7 -0
  251. package/src/tests/fixtures/extensions/record-task-create/hooks.ts +9 -0
  252. package/src/tests/fixtures/extensions/record-task-create/manifest.json +7 -0
  253. package/src/tests/fixtures/fake-amp.ts +22 -8
  254. package/src/tests/fixtures/held-template-render.ts +42 -0
  255. package/src/tests/fixtures/workflow-idempotency-trigger.child.ts +73 -0
  256. package/src/tests/fleet-benchmark.test.ts +8 -0
  257. package/src/tests/get-workflow-run-tool.test.ts +98 -0
  258. package/src/tests/integrations-http.test.ts +32 -2
  259. package/src/tests/internal-ai/complete-structured.test.ts +37 -1
  260. package/src/tests/internal-ai/credentials.test.ts +23 -0
  261. package/src/tests/kv-http.test.ts +32 -0
  262. package/src/tests/kv-tool.test.ts +25 -0
  263. package/src/tests/multi-runtime-registration.test.ts +12 -12
  264. package/src/tests/onboarding.test.ts +108 -1
  265. package/src/tests/openai-embedding-credentials.test.ts +35 -0
  266. package/src/tests/openapi-route-headers.test.ts +2 -0
  267. package/src/tests/opencode-adapter.test.ts +134 -0
  268. package/src/tests/openrouter-attribution-call-sites.test.ts +2 -2
  269. package/src/tests/org-website.test.ts +36 -0
  270. package/src/tests/pi-mono-adapter.test.ts +105 -0
  271. package/src/tests/prompt-template-session.test.ts +4 -3
  272. package/src/tests/provider-key-probes.test.ts +181 -0
  273. package/src/tests/rbac-e2e-helpers.test.ts +88 -1
  274. package/src/tests/rbac-e2e-helpers.ts +36 -11
  275. package/src/tests/runner-push-delivery.test.ts +25 -0
  276. package/src/tests/runtime-controller-liveness.test.ts +133 -0
  277. package/src/tests/runtime-providers-docker.test.ts +2 -0
  278. package/src/tests/script-connections.test.ts +5 -0
  279. package/src/tests/script-run-config-scope.test.ts +91 -3
  280. package/src/tests/script-runs-retention.test.ts +212 -0
  281. package/src/tests/sdk-allowlist.test.ts +39 -0
  282. package/src/tests/session-logs.test.ts +70 -0
  283. package/src/tests/session-source-backfill.test.ts +429 -0
  284. package/src/tests/session-source-keys.test.ts +236 -0
  285. package/src/tests/session-source-labels.test.ts +419 -0
  286. package/src/tests/sessions-detail-batch.test.ts +2 -0
  287. package/src/tests/sessions-move.test.ts +131 -0
  288. package/src/tests/sessions-tree.test.ts +336 -2
  289. package/src/tests/slack-inbound-files.test.ts +103 -23
  290. package/src/tests/status.test.ts +101 -18
  291. package/src/tests/tasks-workflow-run-filter.test.ts +146 -0
  292. package/src/tests/test-net.ts +31 -0
  293. package/src/tests/transcription.test.ts +8 -0
  294. package/src/tests/used-by-route.test.ts +11 -0
  295. package/src/tests/workflow-cascade-cancel.test.ts +1341 -0
  296. package/src/tests/workflow-foreach-supersede-retry.test.ts +488 -0
  297. package/src/tests/workflow-http-v2.test.ts +131 -0
  298. package/src/tests/workflow-idempotency.test.ts +915 -0
  299. package/src/tests/workflow-step-order.test.ts +92 -0
  300. package/src/tests/workflow-sub-workflow.test.ts +30 -0
  301. package/src/tools/get-tasks.ts +8 -0
  302. package/src/tools/kv/kv-read-auth.ts +4 -2
  303. package/src/tools/task-action.ts +1 -1
  304. package/src/tools/workflows/cancel-workflow-run.ts +1 -1
  305. package/src/tools/workflows/get-workflow-run.ts +14 -4
  306. package/src/types.ts +24 -0
  307. package/src/utils/company-profile.ts +51 -1
  308. package/src/utils/context-window.ts +8 -6
  309. package/src/utils/credentials.ts +5 -2
  310. package/src/utils/internal-ai/complete-structured.ts +5 -0
  311. package/src/utils/internal-ai/credentials.ts +4 -3
  312. package/src/utils/provider-key-probes.ts +328 -0
  313. package/src/utils/transcription.ts +4 -1
  314. package/src/workflows/approval-cancel.ts +15 -13
  315. package/src/workflows/approval-notifications.ts +5 -1
  316. package/src/workflows/checkpoint.ts +6 -3
  317. package/src/workflows/engine.ts +30 -8
  318. package/src/workflows/executors/foreach.ts +2 -2
  319. package/src/workflows/executors/sub-workflow.ts +8 -1
  320. package/src/workflows/foreach-driver.ts +10 -3
  321. package/src/workflows/foreach-join.ts +29 -6
  322. package/src/workflows/recovery.ts +6 -1
  323. package/src/workflows/resume.ts +168 -56
  324. package/src/workflows/retry-poller.ts +49 -3
  325. package/src/workflows/task-step-routing.ts +29 -7
  326. package/templates/ai-toolbox.manifest.json +8 -8
  327. package/templates/community/open-prompt-showreel/PLAYBOOK.md +5 -6
  328. package/templates/community/open-prompt-showreel/lead-prompt.md +1 -1
  329. package/templates/community/open-prompt-showreel/skills/open-prompt-showreel/SKILL.md +4 -4
  330. package/templates/skills/agent-browser/SKILL.md +4 -4
  331. package/templates/skills/agent-browser/content.md +4 -4
  332. package/templates/skills/code-quality/config.json +1 -1
  333. package/templates/skills/code-quality/content.md +2 -2
  334. package/templates/skills/delegate-work/content.md +29 -25
  335. package/templates/skills/delegate-work/files/scripts/codex-exec.sh +5 -5
  336. package/templates/skills/qa/content.md +2 -2
  337. package/templates/skills/researching/content.md +4 -0
  338. package/templates/skills/swarm-scripts/SKILL.md +1 -0
  339. package/templates/skills/swarm-scripts/content.md +1 -0
  340. package/templates/skills/work-on-task/SKILL.md +1 -1
  341. package/templates/skills/work-on-task/content.md +1 -1
  342. package/dist/cli-7421jzsw.js +0 -84
  343. package/dist/cli-e45e4m6b.js +0 -413
  344. package/dist/cli-zw88zjba.js +0 -307
  345. package/dist/handlers-19xr6dnz.js +0 -41
  346. package/dist/index-kw2mmat7.js +0 -43
  347. package/dist/lead-zyerk1x2.js +0 -79
  348. package/dist/render-v2-hgytz8nn.js +0 -12
  349. package/dist/worker-42t8emjt.js +0 -79
@@ -19,7 +19,6 @@
19
19
  */
20
20
 
21
21
  import {
22
- type OpenAICompatibleProvider,
23
22
  openAICompatibleKeyVars,
24
23
  openAICompatibleProviders,
25
24
  providerBaseUrl,
@@ -45,13 +44,35 @@ import type {
45
44
  ProviderName,
46
45
  ReasoningEffort,
47
46
  } from "../types";
48
- import { CLAUDE_CREDENTIALS_HINT } from "../utils/credentials";
49
47
  import {
50
- getOpenRouterAttributionHeaders,
51
- getOpenRouterBaseUrl,
52
- } from "../utils/openrouter-base-url";
48
+ CLAUDE_CREDENTIALS_HINT,
49
+ CREDENTIAL_POOL_VARS,
50
+ splitCredentialPool,
51
+ } from "../utils/credentials";
52
+ import {
53
+ checkAnthropicApiKey,
54
+ checkDeepSeekApiKey,
55
+ checkOpenAICompatibleProvider,
56
+ checkOpenAiApiKey,
57
+ checkOpenRouter,
58
+ checkXaiApiKey,
59
+ type LiveKeyResult,
60
+ type LiveValidationResult,
61
+ timedFetch,
62
+ } from "../utils/provider-key-probes";
53
63
  import { scrubSecrets } from "../utils/secret-scrubber";
54
64
 
65
+ export {
66
+ checkAnthropicApiKey,
67
+ checkDeepSeekApiKey,
68
+ checkOpenAICompatibleProvider,
69
+ checkOpenAiApiKey,
70
+ checkOpenRouter,
71
+ checkXaiApiKey,
72
+ type LiveKeyResult,
73
+ type LiveValidationResult,
74
+ } from "../utils/provider-key-probes";
75
+
55
76
  export type SupportedProvider =
56
77
  | "claude"
57
78
  | "claude-managed"
@@ -215,46 +236,8 @@ export async function checkProviderCredentials(
215
236
  // Mirrors `checkProviderCredentials` but issues a real (cheapest possible)
216
237
  // upstream call so the user can flip the `harness` milestone to `verified`.
217
238
  //
218
- // Pure function — no DB writes. Lives here (not on `ProviderAdapter`) because
219
- // adapters are runtime-loaded by workers, while this dispatcher is API-server
220
- // safe. All errors run through `scrubSecrets` so we never leak the user's
221
- // own key shape back through the JSON response.
222
-
223
- const LIVE_TEST_TIMEOUT_MS = 5_000;
224
-
225
- export interface LiveValidationResult {
226
- ok: boolean;
227
- error?: string;
228
- latency_ms: number;
229
- /** No free live check exists for this credential (cloud routes): report presence only. */
230
- skipped?: boolean;
231
- }
232
-
233
- async function timedFetch(
234
- url: string,
235
- init: RequestInit,
236
- ): Promise<{ ok: boolean; status: number; bodyText: string; latency_ms: number }> {
237
- const controller = new AbortController();
238
- const timer = setTimeout(() => controller.abort(), LIVE_TEST_TIMEOUT_MS);
239
- const start = Date.now();
240
- try {
241
- const res = await fetch(url, { ...init, signal: controller.signal });
242
- let bodyText = "";
243
- try {
244
- bodyText = await res.text();
245
- } catch {
246
- // ignore body read errors — status alone is enough for ok/not-ok
247
- }
248
- return {
249
- ok: res.ok,
250
- status: res.status,
251
- bodyText,
252
- latency_ms: Date.now() - start,
253
- };
254
- } finally {
255
- clearTimeout(timer);
256
- }
257
- }
239
+ // Worker-only dispatch uses the shared probes in `provider-key-probes.ts`.
240
+ // The API imports those probes directly to avoid loading worker harnesses.
258
241
 
259
242
  function asLiveError(err: unknown, latency_ms: number): LiveValidationResult {
260
243
  const raw = err instanceof Error ? err.message : String(err);
@@ -265,6 +248,69 @@ function asLiveError(err: unknown, latency_ms: number): LiveValidationResult {
265
248
  };
266
249
  }
267
250
 
251
+ /**
252
+ * Run `check` on every member of a comma pool in parallel. The pool passes
253
+ * when one key works (the runner skips a rejected key); `warning` names the
254
+ * keys that failed, and the check fails only when every key fails.
255
+ */
256
+ export async function checkEachKey(
257
+ keyType: string,
258
+ value: string,
259
+ check: (key: string) => Promise<LiveValidationResult>,
260
+ ): Promise<LiveValidationResult> {
261
+ const members = splitCredentialPool(value);
262
+ if (members.length === 0) members.push(value.trim());
263
+ const results = await Promise.all(
264
+ members.map(async (member) => {
265
+ const startedAt = Date.now();
266
+ try {
267
+ return await check(member);
268
+ } catch (err) {
269
+ return asLiveError(err, Date.now() - startedAt);
270
+ }
271
+ }),
272
+ );
273
+ const latency_ms = Math.max(...results.map((result) => result.latency_ms));
274
+ if (results.every((result) => result.skipped)) return { ok: true, skipped: true, latency_ms };
275
+ const keys = results.map(
276
+ (result, index): LiveKeyResult => ({
277
+ keyType,
278
+ index,
279
+ suffix: members[index]!.slice(-5),
280
+ ok: result.ok,
281
+ ...(result.error !== undefined ? { error: result.error } : {}),
282
+ ...(result.httpStatus !== undefined ? { httpStatus: result.httpStatus } : {}),
283
+ latency_ms: result.latency_ms,
284
+ }),
285
+ );
286
+ const failed = keys.filter((key) => !key.ok);
287
+ if (failed.length === 0) return { ok: true, latency_ms, keys };
288
+ const failures = failed
289
+ .map(
290
+ (key) =>
291
+ `key ending ${key.suffix} (${key.httpStatus !== undefined ? `HTTP ${key.httpStatus}` : (key.error ?? "Unknown error")})`,
292
+ )
293
+ .join(", ");
294
+ if (failed.length < keys.length) {
295
+ return {
296
+ ok: true,
297
+ latency_ms,
298
+ keys,
299
+ warning: `${failed.length} of ${keys.length} keys failed: ${failures}.`,
300
+ };
301
+ }
302
+ if (keys.length === 1) {
303
+ return {
304
+ ok: false,
305
+ error: failed[0]!.error,
306
+ httpStatus: failed[0]!.httpStatus,
307
+ latency_ms,
308
+ keys,
309
+ };
310
+ }
311
+ return { ok: false, error: `All ${keys.length} keys failed: ${failures}.`, latency_ms, keys };
312
+ }
313
+
268
314
  // ─── Per-endpoint live-call helpers ──────────────────────────────────────────
269
315
  // Each helper accepts the auth material it needs and returns a normalized
270
316
  // `LiveValidationResult`. The harness-level dispatcher below picks which
@@ -272,22 +318,10 @@ function asLiveError(err: unknown, latency_ms: number): LiveValidationResult {
272
318
  // users no longer see "ANTHROPIC_API_KEY is not set" when their adapter is
273
319
  // happily running off `CLAUDE_CODE_OAUTH_TOKEN`.
274
320
 
275
- async function checkAnthropicApiKey(apiKey: string): Promise<LiveValidationResult> {
276
- const r = await timedFetch("https://api.anthropic.com/v1/models", {
277
- method: "GET",
278
- headers: { "x-api-key": apiKey, "anthropic-version": "2023-06-01" },
279
- });
280
- if (r.ok) return { ok: true, latency_ms: r.latency_ms };
281
- return {
282
- ok: false,
283
- error: scrubSecrets(`HTTP ${r.status}: ${r.bodyText.slice(0, 200)}`),
284
- latency_ms: r.latency_ms,
285
- };
286
- }
287
-
288
321
  /**
289
- * OAuth credentials (Claude Pro/Max via `claude` CLI login, ChatGPT via Codex
290
- * OAuth) are treated as a presence check — we don't issue a live upstream call.
322
+ * Codex OAuth credentials (ChatGPT login) are treated as a presence check — we
323
+ * don't issue a live upstream call. (Claude subscription tokens get a real
324
+ * check through the route: see `claudeSubscriptionProvider`.)
291
325
  *
292
326
  * Why: OAuth flows include their own refresh logic (handled at adapter boot,
293
327
  * not here), the OAuth-bearer-with-/v1/models contract isn't a stable public
@@ -299,70 +333,6 @@ function presenceCheckOk(): LiveValidationResult {
299
333
  return { ok: true, latency_ms: 0 };
300
334
  }
301
335
 
302
- async function checkOpenAiApiKey(apiKey: string): Promise<LiveValidationResult> {
303
- const r = await timedFetch("https://api.openai.com/v1/models", {
304
- method: "GET",
305
- headers: { Authorization: `Bearer ${apiKey}` },
306
- });
307
- if (r.ok) return { ok: true, latency_ms: r.latency_ms };
308
- return {
309
- ok: false,
310
- error: scrubSecrets(`HTTP ${r.status}: ${r.bodyText.slice(0, 200)}`),
311
- latency_ms: r.latency_ms,
312
- };
313
- }
314
-
315
- /** `GET /v1/models` is the call the Grok CLI makes first; it runs no inference. */
316
- async function checkXaiApiKey(apiKey: string): Promise<LiveValidationResult> {
317
- const r = await timedFetch("https://api.x.ai/v1/models", {
318
- method: "GET",
319
- headers: { Authorization: `Bearer ${apiKey}` },
320
- });
321
- if (r.ok) return { ok: true, latency_ms: r.latency_ms };
322
- return {
323
- ok: false,
324
- error: scrubSecrets(`HTTP ${r.status}: ${r.bodyText.slice(0, 200)}`),
325
- latency_ms: r.latency_ms,
326
- };
327
- }
328
-
329
- async function checkOpenRouter(apiKey: string): Promise<LiveValidationResult> {
330
- const url = `${getOpenRouterBaseUrl()}/models`;
331
- const r = await timedFetch(url, {
332
- method: "GET",
333
- headers: { ...getOpenRouterAttributionHeaders(url), Authorization: `Bearer ${apiKey}` },
334
- });
335
- if (r.ok) return { ok: true, latency_ms: r.latency_ms };
336
- return {
337
- ok: false,
338
- error: scrubSecrets(`HTTP ${r.status}: ${r.bodyText.slice(0, 200)}`),
339
- latency_ms: r.latency_ms,
340
- };
341
- }
342
-
343
- /**
344
- * `GET <baseUrl>/models` with a Bearer key against an OpenAI-compatible
345
- * registry provider (NaN, Helmcode). Lists models, runs no inference.
346
- * `baseUrl` defaults to the registry entry; the probe passes the
347
- * `<ID>_BASE_URL` override, and tests inject a fake server.
348
- */
349
- export async function checkOpenAICompatibleProvider(
350
- provider: OpenAICompatibleProvider,
351
- apiKey: string,
352
- baseUrl: string = provider.baseUrl,
353
- ): Promise<LiveValidationResult> {
354
- const r = await timedFetch(`${baseUrl.replace(/\/+$/, "")}/models`, {
355
- method: "GET",
356
- headers: { Authorization: `Bearer ${apiKey}` },
357
- });
358
- if (r.ok) return { ok: true, latency_ms: r.latency_ms };
359
- return {
360
- ok: false,
361
- error: scrubSecrets(`HTTP ${r.status}: ${r.bodyText.slice(0, 200)}`),
362
- latency_ms: r.latency_ms,
363
- };
364
- }
365
-
366
336
  /**
367
337
  * Mirror of the disk check in `checkCodexCredentials` — true when the worker
368
338
  * has a materialised `~/.codex/auth.json`, which is the canonical "logged in"
@@ -402,14 +372,15 @@ function parseCodexOAuthAccess(blob: string | undefined): string | null {
402
372
  *
403
373
  * | Harness | Accepted credentials (in resolution order) | Endpoint |
404
374
  * |------------------|-------------------------------------------------------------------------|--------------------------------|
405
- * | `claude` | default route: Foundry / Bedrock / Vertex → gateway (`ANTHROPIC_BASE_URL` + `ANTHROPIC_AUTH_TOKEN` or `ANTHROPIC_API_KEY`) → `CLAUDE_CODE_OAUTH_TOKEN` → `ANTHROPIC_API_KEY` | the route's own `/v1/models`; OAuth presence-only; cloud routes skipped |
375
+ * | `claude` | default route: Foundry / Bedrock / Vertex → gateway (`ANTHROPIC_BASE_URL` + `ANTHROPIC_AUTH_TOKEN` or `ANTHROPIC_API_KEY`) → `CLAUDE_CODE_OAUTH_TOKEN` → `ANTHROPIC_API_KEY` | the route's own `/v1/models`; OAuth: `/api/oauth/profile` (403 scope = ok); cloud routes skipped |
406
376
  * | `claude-managed` | `ANTHROPIC_API_KEY` (managed agents always use API key + managed envs) | Anthropic `/v1/models` |
407
377
  * | `codex` | `~/.codex/auth.json` (file) → `CODEX_OAUTH` (env OAuth) → `OPENAI_API_KEY` | OpenAI `/v1/models` (api-key path only) |
408
- * | `opencode` | auth.json (file) → `OPENROUTER_API_KEY` → `ANTHROPIC_API_KEY` → `OPENAI_API_KEY` → each registry provider key (`NAN_API_KEY` or `HELMCODE_API_NAN`, `HELMCODE_API_KEY`) | file presence-only, otherwise provider's `/v1/models` |
378
+ * | `opencode` | auth.json (file) → `OPENROUTER_API_KEY` → `ANTHROPIC_API_KEY` → `OPENAI_API_KEY` → each registry provider key (`NAN_API_KEY` or `HELMCODE_API_NAN`, `HELMCODE_API_KEY`) | file presence-only, otherwise OpenRouter `/key` or the provider's `/v1/models` |
409
379
  * | `pi` | auth.json (file) → provider API keys, including model-specific keys | file/model-specific key presence-only, otherwise provider's `/v1/models` |
410
380
  * | `acp` | target-specific (the ACP target process owns its own auth) | presence-only (validated by the target process) |
411
381
  * | `pi` (bedrock) | `MODEL_OVERRIDE=amazon-bedrock/*` → AWS SDK default credential chain | presence-only (real check is the worker-side Bedrock enumeration) |
412
382
  * | `devin` | `DEVIN_API_KEY` (+ `DEVIN_API_BASE_URL` override) | `${baseUrl}/v3/self` |
383
+ * | `dsh` | `DEEPSEEK_API_KEY` → `OPENROUTER_API_KEY` | DeepSeek `/models`, or OpenRouter `/key` |
413
384
  * | `amp` | `AMP_API_KEY` | `amp usage` (credit balance, no inference; 5s timeout) |
414
385
  *
415
386
  * Returns `{ok: true, latency_ms}` on 2xx, `{ok: false, error, latency_ms}`
@@ -429,8 +400,9 @@ export async function validateProviderCredentials(
429
400
  case "claude": {
430
401
  // The default route decides where the check goes: a gateway key is
431
402
  // checked against the gateway only (scoped fetch), never against
432
- // api.anthropic.com. OAuth stays presence-only; cloud routes have no
433
- // free check and report `configured` (no live test).
403
+ // api.anthropic.com. A subscription token is checked against
404
+ // `/api/oauth/profile`; cloud routes have no free check and report
405
+ // `configured` (no live test).
434
406
  const route = deriveDefaultRoute("claude", env);
435
407
  if (!route) {
436
408
  return {
@@ -439,16 +411,40 @@ export async function validateProviderCredentials(
439
411
  latency_ms: Date.now() - startedAt,
440
412
  };
441
413
  }
442
- const result = await validateRoute(route, env);
443
- const latency_ms = Date.now() - startedAt;
444
- if (result.status === "verified") return { ok: true, latency_ms };
445
- if (result.status === "configured") return { ok: true, skipped: true, latency_ms };
446
- return { ok: false, error: scrubSecrets(result.reason), latency_ms };
414
+ const checkRoute = async (
415
+ routeEnv: Record<string, string | undefined>,
416
+ ): Promise<LiveValidationResult> => {
417
+ const start = Date.now();
418
+ const result = await validateRoute(route, routeEnv);
419
+ const latency_ms = Date.now() - start;
420
+ if (result.status === "verified") return { ok: true, latency_ms };
421
+ if (result.status === "configured") return { ok: true, skipped: true, latency_ms };
422
+ return {
423
+ ok: false,
424
+ error: scrubSecrets(result.reason),
425
+ httpStatus: result.httpStatus,
426
+ latency_ms,
427
+ };
428
+ };
429
+ const keyVar =
430
+ route.auth.kind === "subscription"
431
+ ? "CLAUDE_CODE_OAUTH_TOKEN"
432
+ : "secretKey" in route.auth
433
+ ? route.auth.secretKey
434
+ : undefined;
435
+ const value = keyVar ? env[keyVar] : undefined;
436
+ // Only pool vars are split: the runner never splits a gateway token.
437
+ if (keyVar && value && (CREDENTIAL_POOL_VARS as readonly string[]).includes(keyVar)) {
438
+ return checkEachKey(keyVar, value, (member) => checkRoute({ ...env, [keyVar]: member }));
439
+ }
440
+ return checkRoute(env);
447
441
  }
448
442
  case "claude-managed": {
449
443
  // Managed agents always run with an API key — OAuth not supported on
450
444
  // the managed-agents path today.
451
- if (env.ANTHROPIC_API_KEY) return checkAnthropicApiKey(env.ANTHROPIC_API_KEY);
445
+ if (env.ANTHROPIC_API_KEY) {
446
+ return checkEachKey("ANTHROPIC_API_KEY", env.ANTHROPIC_API_KEY, checkAnthropicApiKey);
447
+ }
452
448
  return {
453
449
  ok: false,
454
450
  error: "ANTHROPIC_API_KEY is not set.",
@@ -456,6 +452,22 @@ export async function validateProviderCredentials(
456
452
  };
457
453
  }
458
454
  case "codex": {
455
+ // A gateway route is checked against the gateway only (scoped fetch
456
+ // to its own `/models`), never against api.openai.com, and wins over
457
+ // every OAuth / auth.json / OPENAI_API_KEY check below.
458
+ const gateway = deriveDefaultRoute("codex", env);
459
+ if (gateway) {
460
+ const result = await validateRoute(gateway, env);
461
+ const latency_ms = Date.now() - startedAt;
462
+ if (result.status === "verified") return { ok: true, latency_ms };
463
+ if (result.status === "configured") return { ok: true, skipped: true, latency_ms };
464
+ return {
465
+ ok: false,
466
+ error: scrubSecrets(result.reason),
467
+ httpStatus: result.httpStatus,
468
+ latency_ms,
469
+ };
470
+ }
459
471
  // Resolution order matches `checkCodexCredentials`:
460
472
  // 1) `~/.codex/auth.json` on disk (canonical state once `codex login`
461
473
  // has run, or when the entrypoint pre-materialised it from
@@ -474,7 +486,9 @@ export async function validateProviderCredentials(
474
486
  if (codexAuthFileExists(env)) return presenceCheckOk();
475
487
  if (parseCodexOAuthAccess(env.CODEX_OAUTH)) return presenceCheckOk();
476
488
  if (hasCodexOAuthPoolSlots(env)) return presenceCheckOk();
477
- if (env.OPENAI_API_KEY) return checkOpenAiApiKey(env.OPENAI_API_KEY);
489
+ if (env.OPENAI_API_KEY) {
490
+ return checkEachKey("OPENAI_API_KEY", env.OPENAI_API_KEY, checkOpenAiApiKey);
491
+ }
478
492
  return {
479
493
  ok: false,
480
494
  error:
@@ -503,13 +517,21 @@ export async function validateProviderCredentials(
503
517
  // registry providers in declaration order (NaN, canonical key or
504
518
  // alias → Helmcode) at their `<ID>_BASE_URL`, so existing env
505
519
  // combinations keep their probe.
506
- if (env.OPENROUTER_API_KEY) return checkOpenRouter(env.OPENROUTER_API_KEY);
507
- if (env.ANTHROPIC_API_KEY) return checkAnthropicApiKey(env.ANTHROPIC_API_KEY);
508
- if (env.OPENAI_API_KEY) return checkOpenAiApiKey(env.OPENAI_API_KEY);
520
+ if (env.OPENROUTER_API_KEY) {
521
+ return checkEachKey("OPENROUTER_API_KEY", env.OPENROUTER_API_KEY, checkOpenRouter);
522
+ }
523
+ if (env.ANTHROPIC_API_KEY) {
524
+ return checkEachKey("ANTHROPIC_API_KEY", env.ANTHROPIC_API_KEY, checkAnthropicApiKey);
525
+ }
526
+ if (env.OPENAI_API_KEY) {
527
+ return checkEachKey("OPENAI_API_KEY", env.OPENAI_API_KEY, checkOpenAiApiKey);
528
+ }
509
529
  for (const direct of openAICompatibleProviders()) {
510
530
  const key = providerKeyFromEnv(direct, env);
511
531
  if (key) {
512
- return checkOpenAICompatibleProvider(direct, key, providerBaseUrl(direct, env));
532
+ return checkEachKey(direct.envVar, key, (member) =>
533
+ checkOpenAICompatibleProvider(direct, member, providerBaseUrl(direct, env)),
534
+ );
513
535
  }
514
536
  }
515
537
  // Model-specific keys (e.g. GEMINI_API_KEY) can satisfy readiness
@@ -534,28 +556,36 @@ export async function validateProviderCredentials(
534
556
  };
535
557
  }
536
558
  const baseUrl = env.DEVIN_API_BASE_URL ?? "https://api.devin.ai";
537
- const r = await timedFetch(`${baseUrl.replace(/\/+$/, "")}/v3/self`, {
538
- method: "GET",
539
- headers: {
540
- Authorization: `Bearer ${apiKey}`,
541
- "Content-Type": "application/json",
542
- },
559
+ return checkEachKey("DEVIN_API_KEY", apiKey, async (member) => {
560
+ const r = await timedFetch(`${baseUrl.replace(/\/+$/, "")}/v3/self`, {
561
+ method: "GET",
562
+ headers: {
563
+ Authorization: `Bearer ${member}`,
564
+ "Content-Type": "application/json",
565
+ },
566
+ });
567
+ if (r.ok) return { ok: true, latency_ms: r.latency_ms };
568
+ return {
569
+ ok: false,
570
+ error: scrubSecrets(`HTTP ${r.status}: ${r.bodyText.slice(0, 200)}`),
571
+ httpStatus: r.status,
572
+ latency_ms: r.latency_ms,
573
+ };
543
574
  });
544
- if (r.ok) return { ok: true, latency_ms: r.latency_ms };
575
+ }
576
+ case "dsh": {
577
+ // Same precedence as `checkDshCredentials`: the DeepSeek key, then OpenRouter.
578
+ const deepseekKey = env.DEEPSEEK_API_KEY?.trim();
579
+ if (deepseekKey) return checkEachKey("DEEPSEEK_API_KEY", deepseekKey, checkDeepSeekApiKey);
580
+ const openRouterKey = env.OPENROUTER_API_KEY?.trim();
581
+ if (openRouterKey)
582
+ return checkEachKey("OPENROUTER_API_KEY", openRouterKey, checkOpenRouter);
545
583
  return {
546
584
  ok: false,
547
- error: scrubSecrets(`HTTP ${r.status}: ${r.bodyText.slice(0, 200)}`),
548
- latency_ms: r.latency_ms,
585
+ error: "Set DEEPSEEK_API_KEY or OPENROUTER_API_KEY for dsh.",
586
+ latency_ms: Date.now() - startedAt,
549
587
  };
550
588
  }
551
- case "dsh":
552
- return checkDshCredentials(env).ready
553
- ? presenceCheckOk()
554
- : {
555
- ok: false,
556
- error: "Set DEEPSEEK_API_KEY or OPENROUTER_API_KEY for dsh.",
557
- latency_ms: Date.now() - startedAt,
558
- };
559
589
  case "amp": {
560
590
  if (!checkAmpCredentials(env).ready) {
561
591
  return {
@@ -565,14 +595,17 @@ export async function validateProviderCredentials(
565
595
  };
566
596
  }
567
597
  // `amp usage` reads the credit balance and runs no inference.
568
- const live = await liveTestAmpCredentials(env as Record<string, string | undefined>);
569
- return live.ok
570
- ? { ok: true, latency_ms: Date.now() - startedAt }
571
- : {
572
- ok: false,
573
- error: live.error ?? "amp usage failed",
574
- latency_ms: Date.now() - startedAt,
575
- };
598
+ return checkEachKey("AMP_API_KEY", env.AMP_API_KEY!, async (member) => {
599
+ const start = Date.now();
600
+ const live = await liveTestAmpCredentials({ ...env, AMP_API_KEY: member });
601
+ return live.ok
602
+ ? { ok: true, latency_ms: Date.now() - start }
603
+ : {
604
+ ok: false,
605
+ error: live.error ?? "amp usage failed",
606
+ latency_ms: Date.now() - start,
607
+ };
608
+ });
576
609
  }
577
610
  case "cursor": {
578
611
  const apiKey = env.CURSOR_API_KEY?.trim();
@@ -584,16 +617,21 @@ export async function validateProviderCredentials(
584
617
  };
585
618
  }
586
619
  const { liveTestCursorKey } = await import("../providers/cursor-adapter");
587
- const result = await liveTestCursorKey(apiKey);
588
- return result.ok
589
- ? { ok: true, latency_ms: Date.now() - startedAt }
590
- : { ok: false, error: result.error, latency_ms: Date.now() - startedAt };
620
+ return checkEachKey("CURSOR_API_KEY", apiKey, async (member) => {
621
+ const start = Date.now();
622
+ const result = await liveTestCursorKey(member);
623
+ return result.ok
624
+ ? { ok: true, latency_ms: Date.now() - start }
625
+ : { ok: false, error: result.error, latency_ms: Date.now() - start };
626
+ });
591
627
  }
592
628
  case "grok": {
593
629
  const apiKey = env.XAI_API_KEY?.trim();
594
- if (apiKey) return checkXaiApiKey(apiKey);
630
+ if (apiKey) return checkEachKey("XAI_API_KEY", apiKey, checkXaiApiKey);
595
631
  const openRouterKey = env.OPENROUTER_API_KEY?.trim();
596
- if (openRouterKey) return checkOpenRouter(openRouterKey);
632
+ if (openRouterKey) {
633
+ return checkEachKey("OPENROUTER_API_KEY", openRouterKey, checkOpenRouter);
634
+ }
597
635
  return {
598
636
  ok: false,
599
637
  error: "Set XAI_API_KEY or OPENROUTER_API_KEY for grok.",
@@ -653,6 +691,8 @@ export async function buildCredStatusReport(
653
691
  error: live.error ?? null,
654
692
  latency_ms: live.latency_ms,
655
693
  testedAt: Date.now(),
694
+ ...(live.warning !== undefined ? { warning: live.warning } : {}),
695
+ ...(live.keys !== undefined ? { keys: live.keys } : {}),
656
696
  };
657
697
  }
658
698
  // Include the Bedrock enumeration block when the pi probe ran in Bedrock SDK
@@ -1,5 +1,6 @@
1
1
  import { existsSync, statSync } from "node:fs";
2
2
  import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
3
+ import { deriveDefaultRoute } from "@desplega/model-routing";
3
4
  import type { TemplateResponse } from "../../templates/schema.ts";
4
5
  import {
5
6
  type Attributes,
@@ -118,7 +119,13 @@ import {
118
119
  prependContextPreamble,
119
120
  } from "./context-preamble.ts";
120
121
  import { reportCredentialOutcomeThenFinish } from "./credential-outcome-report.ts";
121
- import { type CredentialRefreshState, refreshCredentialStatus } from "./credential-refresh.ts";
122
+ import {
123
+ type CredentialRefreshState,
124
+ credentialFingerprint,
125
+ recheckableFailures,
126
+ refreshCredentialStatus,
127
+ reportKeyValidation,
128
+ } from "./credential-refresh.ts";
122
129
  import {
123
130
  awaitCredentials,
124
131
  BootMaxWaitExceededError,
@@ -831,6 +838,8 @@ export async function exitAfterTasksIfFinished(
831
838
  */
832
839
  export interface ResolvedEnvResult {
833
840
  env: Record<string, string | undefined>;
841
+ /** Full credential pools for live validation. Keep this environment in process only. */
842
+ credentialEnv: Record<string, string | undefined>;
834
843
  credentialSelections: CredentialSelection[];
835
844
  /** Raw config-row value, kept separate from the merged environment so an
836
845
  * actual worker environment variable retains precedence. */
@@ -1049,6 +1058,7 @@ export async function fetchResolvedEnv(
1049
1058
  });
1050
1059
  const effectiveModel = modelSelection.model || (env.MODEL_OVERRIDE as string | undefined) || "";
1051
1060
 
1061
+ const credentialEnv = { ...env };
1052
1062
  const credentialSelections = await resolveCredentialPools(env, {
1053
1063
  apiUrl,
1054
1064
  apiKey,
@@ -1065,7 +1075,7 @@ export async function fetchResolvedEnv(
1065
1075
  enforceModelCapacity: sessionContext?.enforceModelCapacity,
1066
1076
  });
1067
1077
 
1068
- return { env, credentialSelections, resolvedProvider, scriptsOnlyConfigValue };
1078
+ return { env, credentialEnv, credentialSelections, resolvedProvider, scriptsOnlyConfigValue };
1069
1079
  }
1070
1080
 
1071
1081
  /**
@@ -3352,6 +3362,8 @@ export interface PollOptions {
3352
3362
  pollTimeout: number;
3353
3363
  /** Push scheduling fetches once, then returns control to the runner. */
3354
3364
  singlePoll?: boolean;
3365
+ /** Stop an open poll window before its next claim request. */
3366
+ shouldStop?: () => boolean;
3355
3367
  /** Distinguishes a failed fetch from an empty successful response. */
3356
3368
  onPollFailure?: () => void;
3357
3369
  since?: string; // Optional: for filtering finished tasks
@@ -3669,6 +3681,7 @@ async function pollForTriggerOnce(opts: PollOptions): Promise<Trigger | null> {
3669
3681
  }
3670
3682
 
3671
3683
  while (Date.now() - startTime < opts.pollTimeout) {
3684
+ if (opts.shouldStop?.()) return null;
3672
3685
  try {
3673
3686
  // Build URL with optional since parameter
3674
3687
  let url = `${opts.apiUrl}/api/poll`;
@@ -4316,7 +4329,9 @@ async function spawnProviderProcess(
4316
4329
  // config-store slot — gates whether `codexSlot` is set below (see
4317
4330
  // `CodexOAuthCredentialInfo`).
4318
4331
  let oauthIsPoolBacked = false;
4319
- if (adapter.name === "codex") {
4332
+ // A gateway route uses no OAuth: no pool slot is picked, so no auth.json is
4333
+ // materialised for a session whose traffic goes to the gateway.
4334
+ if (adapter.name === "codex" && !deriveDefaultRoute("codex", freshEnv)) {
4320
4335
  const oauthInfo = await resolveCodexOAuthCredentialInfo(opts.apiUrl, opts.apiKey);
4321
4336
  oauthSelection = oauthInfo?.selection;
4322
4337
  oauthIsPoolBacked = oauthInfo?.isPoolBacked ?? false;
@@ -6016,7 +6031,10 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
6016
6031
  ready: null,
6017
6032
  lastRefreshAt: 0,
6018
6033
  inFlight: false,
6034
+ fingerprint: null,
6035
+ rejectedKeys: 0,
6019
6036
  };
6037
+ let latestCredentialEnv: Record<string, string | undefined> | undefined;
6020
6038
 
6021
6039
  // Throttle for live HARNESS_PROVIDER reconciliation. Each reconciliation
6022
6040
  // calls `fetchResolvedEnv` which also re-resolves credential pools — we
@@ -6410,6 +6428,10 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
6410
6428
  credentialRefreshState.harnessProvider = state.harnessProvider;
6411
6429
  credentialRefreshState.ready = bootCredSnapshot?.ready ?? true;
6412
6430
  credentialRefreshState.lastRefreshAt = Date.now();
6431
+ credentialRefreshState.fingerprint = credentialFingerprint(process.env);
6432
+ credentialRefreshState.rejectedKeys = recheckableFailures(bootCredSnapshot?.liveTest);
6433
+ const keys = bootCredSnapshot?.liveTest?.keys;
6434
+ if (keys?.length) await reportKeyValidation(apiConfig, keys);
6413
6435
  } catch (error) {
6414
6436
  console.error(`[${role}] Failed to report credential readiness after recovery: ${error}`);
6415
6437
  process.exit(1);
@@ -7012,9 +7034,11 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
7012
7034
  try {
7013
7035
  const {
7014
7036
  env: freshEnv,
7037
+ credentialEnv,
7015
7038
  resolvedProvider,
7016
7039
  scriptsOnlyConfigValue,
7017
7040
  } = await fetchResolvedEnv(apiUrl, apiKey, agentId);
7041
+ latestCredentialEnv = credentialEnv;
7018
7042
  const nextScriptsOnly = resolveScriptsOnlyMode({
7019
7043
  env: process.env.SCRIPTS_ONLY_MCP,
7020
7044
  configValue: scriptsOnlyConfigValue,
@@ -7046,7 +7070,7 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
7046
7070
  apiConfig,
7047
7071
  credentialRefreshState,
7048
7072
  state.harnessProvider,
7049
- process.env,
7073
+ latestCredentialEnv ?? process.env,
7050
7074
  ).catch((err) =>
7051
7075
  console.warn(`[${role}] cred_status post_task refresh failed (non-fatal): ${err}`),
7052
7076
  );
@@ -7099,7 +7123,7 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
7099
7123
  }
7100
7124
  }
7101
7125
 
7102
- // Only poll if we have capacity and the API is not draining
7126
+ // Only poll if we have capacity and neither the API nor worker is draining
7103
7127
  const schedule = pushDelivery.schedule({
7104
7128
  mode: state.deliveryMode ?? "poll",
7105
7129
  push: deliveryChecks.push,
@@ -7108,7 +7132,7 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
7108
7132
  exitAfterTasks === null
7109
7133
  ? state.maxConcurrent
7110
7134
  : Math.min(state.maxConcurrent, Math.max(0, exitAfterTasks - state.tasksProcessed)),
7111
- draining: state.apiDraining,
7135
+ draining: state.apiDraining || shuttingDown,
7112
7136
  lastPushPingAt,
7113
7137
  pollInterval: PollIntervalMs,
7114
7138
  });
@@ -7146,6 +7170,7 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
7146
7170
  runtimeInstanceId,
7147
7171
  pollTimeout: effectiveTimeout,
7148
7172
  singlePoll: schedule.singlePoll,
7173
+ shouldStop: () => shuttingDown,
7149
7174
  onPollFailure,
7150
7175
  harnessProvider: state.harnessProvider,
7151
7176
  onApiDrainSignal: (draining) => applyApiDrainSignal(state, role, draining),