@desplega.ai/agent-swarm 2.5.4 → 2.7.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 (354) hide show
  1. package/README.md +4 -2
  2. package/dist/{acp-adapter-ek38cwsv.js → acp-adapter-ma1js5n7.js} +9 -9
  3. package/dist/{actions-92mepq03.js → actions-1vhmtvg9.js} +21 -21
  4. package/dist/{amp-adapter-9gaxg015.js → amp-adapter-sxqqvmdk.js} +4 -4
  5. package/dist/{app-ctn8gsgx.js → app-mwx1d5a9.js} +3 -3
  6. package/dist/{assistant-zyr5kz47.js → assistant-yx1p6g82.js} +30 -26
  7. package/dist/{boot-reembed-w75maga2.js → boot-reembed-5h37a2c2.js} +1 -1
  8. package/dist/{boot-reembed-dwzfttdr.js → boot-reembed-7h78vmvv.js} +2 -1
  9. package/dist/{boot-scrub-sweep-e9kq43kf.js → boot-scrub-sweep-8mawd4gw.js} +4 -4
  10. package/dist/{boot-seeds-x28v3fhb.js → boot-seeds-ca84dhnn.js} +4 -4
  11. package/dist/{claude-adapter-f0afqnne.js → claude-adapter-wqt4hfkq.js} +9 -9
  12. package/dist/{claude-managed-adapter-x81a7r65.js → claude-managed-adapter-6fjqtr2z.js} +2 -2
  13. package/dist/{claude-managed-setup-bqzwjd9x.js → claude-managed-setup-rqv98sea.js} +10 -3
  14. package/dist/{claude-sdk-session-7b7whkaq.js → claude-sdk-session-f29f3b2x.js} +11 -11
  15. package/dist/{cli-sxgtf5fw.js → cli-07q03xm2.js} +1 -1
  16. package/dist/{cli-kxpfx2xr.js → cli-0z1xjzwa.js} +2 -2
  17. package/dist/{cli-g9vd9ece.js → cli-1hhbzkbb.js} +14 -16
  18. package/dist/{cli-qqzmjxkf.js → cli-1m9x8dr5.js} +11 -6
  19. package/dist/{cli-jbnn4vxb.js → cli-2pvzmas1.js} +3 -3
  20. package/dist/{cli-x77gen2p.js → cli-2y7v133v.js} +7 -7
  21. package/dist/{cli-dew6x255.js → cli-39aet655.js} +2 -2
  22. package/dist/{cli-g1cw7xtt.js → cli-39rrw8yp.js} +1 -1
  23. package/dist/{cli-jb30dzvk.js → cli-48gxjmcz.js} +530 -190
  24. package/dist/{cli-pzatvw94.js → cli-54sj64fc.js} +2 -2
  25. package/dist/{cli-bvpgr9ny.js → cli-60sty27p.js} +5 -5
  26. package/dist/{cli-asq5ydm4.js → cli-6a9qfa8b.js} +411 -69
  27. package/dist/{cli-277c31dw.js → cli-6mgdba42.js} +225 -3
  28. package/dist/{cli-19202kew.js → cli-6srv653c.js} +5 -5
  29. package/dist/{cli-n4c8e3s5.js → cli-708wdy32.js} +1 -1
  30. package/dist/{cli-pasyvjmm.js → cli-737pyqxr.js} +1 -1
  31. package/dist/{cli-6x9ma5w5.js → cli-803188pb.js} +1 -1
  32. package/dist/{cli-hh5we352.js → cli-86ehn4s5.js} +7 -7
  33. package/dist/{cli-9s05422w.js → cli-8mbc9za9.js} +1 -1
  34. package/dist/{cli-ff1tm9n9.js → cli-8wmj18cz.js} +1 -1
  35. package/dist/{cli-1r5ad7j5.js → cli-9gsm2rzp.js} +12 -12
  36. package/dist/{cli-mz9afxxf.js → cli-9pshx6rd.js} +17 -3
  37. package/dist/{cli-s682zgqz.js → cli-a6ds2epe.js} +1 -1
  38. package/dist/{cli-6a1xg1p7.js → cli-a89zdqz9.js} +2 -2
  39. package/dist/{cli-03vpq45j.js → cli-ay40dnj3.js} +2 -2
  40. package/dist/{cli-tsma7p8v.js → cli-aypd09nf.js} +2 -2
  41. package/dist/{cli-ethgy7ha.js → cli-b3yzyc57.js} +2 -2
  42. package/dist/{cli-bn0rewsy.js → cli-c081bj3b.js} +2 -2
  43. package/dist/{cli-mmhj8vd7.js → cli-c668vvcf.js} +11 -2
  44. package/dist/{cli-sbwsxvtd.js → cli-d20qvpr2.js} +2 -2
  45. package/dist/{cli-c952c7ac.js → cli-e1595bgx.js} +3 -3
  46. package/dist/{cli-621ce7kw.js → cli-f12mr20z.js} +3 -3
  47. package/dist/{cli-dqygeeh5.js → cli-f1e0ptjj.js} +10 -1
  48. package/dist/{cli-xzfq4at3.js → cli-f4dbh7a5.js} +16 -12
  49. package/dist/{cli-ha9rmwgq.js → cli-fb8m699c.js} +2 -2
  50. package/dist/{cli-5bnsc0e1.js → cli-gsfk58nw.js} +5 -5
  51. package/dist/cli-gt4h8cxw.js +895 -0
  52. package/dist/{cli-nwyw3zhg.js → cli-gxee0bqa.js} +1 -1
  53. package/dist/{cli-25n3mv5d.js → cli-h5jeg46p.js} +6 -6
  54. package/dist/{cli-gn5af0x5.js → cli-he9gbdx0.js} +62 -12
  55. package/dist/{cli-gbfxp48k.js → cli-j0ppywdr.js} +42 -35
  56. package/dist/{cli-k4tjsdx9.js → cli-j3y0x2n4.js} +277 -199
  57. package/dist/{cli-7p4jxgtt.js → cli-j9bpmykh.js} +75 -27
  58. package/dist/{cli-dabehxhp.js → cli-kt907da6.js} +1 -1
  59. package/dist/{cli-wx2nj19m.js → cli-kv7sbc2c.js} +6 -6
  60. package/dist/{cli-abs8ndtr.js → cli-m30k9777.js} +26 -26
  61. package/dist/{cli-cz5nmdxt.js → cli-m5e802r7.js} +2 -2
  62. package/dist/{cli-0shx9j93.js → cli-pfk92eqz.js} +2 -2
  63. package/dist/{cli-xtrz6bzs.js → cli-ptfq202x.js} +5 -3
  64. package/dist/{cli-6n8axjv1.js → cli-qj442fws.js} +2 -2
  65. package/dist/{cli-c61mbdds.js → cli-rd5m70gj.js} +4 -4
  66. package/dist/{cli-eftzyh9y.js → cli-s09j9vp5.js} +2 -2
  67. package/dist/{cli-m4ecdxvz.js → cli-s0mgx214.js} +7 -7
  68. package/dist/{cli-wghjkr0e.js → cli-sgqm2582.js} +10 -7
  69. package/dist/{cli-2655k16q.js → cli-sjctjr48.js} +2 -2
  70. package/dist/{cli-nzfnt2gs.js → cli-tf1n0r0b.js} +1 -1
  71. package/dist/{cli-x0f2v69c.js → cli-twctnkng.js} +12 -7
  72. package/dist/{cli-j6rktc91.js → cli-v6shg7na.js} +1 -1
  73. package/dist/{cli-gx4rm0qx.js → cli-vz78s5wm.js} +1 -1
  74. package/dist/{cli-gwhjmh8g.js → cli-wqr7cea4.js} +5 -5
  75. package/dist/{cli-e42vss7b.js → cli-x37r7qwf.js} +16 -8
  76. package/dist/{cli-4b2z6n0m.js → cli-yd2nreqw.js} +17 -8
  77. package/dist/{cli-64mh9mf3.js → cli-ymjpkxgn.js} +69 -17
  78. package/dist/{cli-5kqnemen.js → cli-yvy48785.js} +1 -1
  79. package/dist/{cli-9j129brq.js → cli-yxbsmx56.js} +6 -6
  80. package/dist/cli.js +22 -20
  81. package/dist/{codex-adapter-wch12q99.js → codex-adapter-zk59x2zh.js} +7 -7
  82. package/dist/{codex-hook-brpnycn5.js → codex-hook-xyjhvxwd.js} +1 -1
  83. package/dist/{codex-login-rn7c5gxt.js → codex-login-8cg0vhf2.js} +1 -1
  84. package/dist/{codex-session-runner-41pfcr95.js → codex-session-runner-kh6s4j3n.js} +8 -8
  85. package/dist/{commands-9849dkj6.js → commands-68et1qv2.js} +2 -2
  86. package/dist/{controller-ja0sd95p.js → controller-fyrbnzg4.js} +4 -4
  87. package/dist/{controllers-connect-xp05dj6x.js → controllers-connect-nep9h2xv.js} +23 -23
  88. package/dist/{cursor-adapter-623qxqew.js → cursor-adapter-yss3pg7t.js} +2 -2
  89. package/dist/{db-w759qmx0.js → db-28gjyvxj.js} +4 -2
  90. package/dist/{db-47vj40dy.js → db-8ftwvzba.js} +3 -3
  91. package/dist/{devin-adapter-4ycy4vrf.js → devin-adapter-byen2cxt.js} +1 -1
  92. package/dist/{dispatcher-7b8e4z2j.js → dispatcher-hayexmkr.js} +20 -20
  93. package/dist/{docker-s84hjs91.js → docker-fdzyewpz.js} +2 -2
  94. package/dist/{dsh-adapter-2ezw3xea.js → dsh-adapter-46wawff9.js} +2 -2
  95. package/dist/{e2b-9gkdhm3c.js → e2b-222hsajc.js} +2 -2
  96. package/dist/{e2b-cjtf8k1z.js → e2b-fn3zacvt.js} +2 -2
  97. package/dist/{easter-egg-3d-e4qv8dxj.js → easter-egg-3d-8d1e0h4m.js} +1 -1
  98. package/dist/{event-bus-3z78ep5n.js → event-bus-pghg8hqy.js} +1 -1
  99. package/dist/{execute-0xpz8950.js → execute-cjrfxfnm.js} +1 -1
  100. package/dist/{external-channel-e2s028da.js → external-channel-xbmxcg09.js} +2 -2
  101. package/dist/{grok-adapter-ys7gaqee.js → grok-adapter-ag4wks2a.js} +10 -10
  102. package/dist/handlers-tzmevqb5.js +41 -0
  103. package/dist/{heartbeat-w3mntghj.js → heartbeat-psfvcgwp.js} +23 -23
  104. package/dist/{hook-g7kvv6x9.js → hook-8pz04697.js} +6 -6
  105. package/dist/{hook-3jjywnpb.js → hook-cyq4mryn.js} +8 -8
  106. package/dist/{http-tkp7d4kb.js → http-haj1nd6k.js} +921 -169
  107. package/dist/{index-q7m26kvs.js → index-jwt2bj6f.js} +265 -97
  108. package/dist/{index-3n8m9ea2.js → index-q52f47pe.js} +1 -1
  109. package/dist/{index-31ceyf42.js → index-syvhnvtw.js} +1 -1
  110. package/dist/index-y1ajx04a.js +43 -0
  111. package/dist/{index-1ef1myq2.js → index-z5h22pes.js} +20 -20
  112. package/dist/{index-xdaahthb.js → index-zyq8ct52.js} +20 -20
  113. package/dist/{keepalive-v4ta8wgg.js → keepalive-xdvsamvj.js} +4 -4
  114. package/dist/lead-hqg57kdp.js +79 -0
  115. package/dist/{maintenance-84n1ex21.js → maintenance-4y2xzh3a.js} +1 -1
  116. package/dist/{oauth-refresh-sweep-vjnnandw.js → oauth-refresh-sweep-jd4r7zbs.js} +9 -4
  117. package/dist/{onboard-sfmpe4np.js → onboard-18447q9q.js} +6 -6
  118. package/dist/{opencode-adapter-jmpst4q4.js → opencode-adapter-26b2frdf.js} +7 -7
  119. package/dist/{otel-impl-v6cc0zve.js → otel-impl-f9tgr8yw.js} +2 -2
  120. package/dist/{pi-mono-adapter-fk8fkcf6.js → pi-mono-adapter-qn6qbga1.js} +38 -17
  121. package/dist/{pricing-refresh-fsk1e1zc.js → pricing-refresh-dw2tgb53.js} +3 -3
  122. package/dist/{rbac-roles-prmkdnkw.js → rbac-roles-aygm2760.js} +2 -2
  123. package/dist/{rbac-roles-zxmxqa39.js → rbac-roles-s4nwj2m3.js} +1 -1
  124. package/dist/render-v2-yfvdfr0x.js +16 -0
  125. package/dist/{resume-frdtgfa0.js → resume-1bmvjbf3.js} +9 -9
  126. package/dist/{runtime-bt5pkpm1.js → runtime-f7tpzedy.js} +1 -1
  127. package/dist/{session-access-fpez4y5q.js → session-access-r5garej6.js} +4 -4
  128. package/dist/session-source-backfill-424wzv0r.js +110 -0
  129. package/dist/{setup-mq6m3m8q.js → setup-8tygvxkq.js} +1 -1
  130. package/dist/{virtual-modules-mnfbh5mr.js → virtual-modules-xsw323b3.js} +2 -2
  131. package/dist/{work-objects-kfahhjsq.js → work-objects-ggnghpgf.js} +23 -23
  132. package/dist/worker-sqjd1nqf.js +79 -0
  133. package/dist/{x-pbpezr0s.js → x-wxrzvzx8.js} +2 -2
  134. package/openapi.json +1324 -14
  135. package/package.json +5 -3
  136. package/src/assets/source-key.ts +95 -0
  137. package/src/be/automation-preflight.ts +2 -0
  138. package/src/be/db/api-keys.ts +89 -0
  139. package/src/be/db/session-labels.ts +28 -0
  140. package/src/be/db/session-moves.ts +5 -6
  141. package/src/be/db/session-tree.ts +153 -14
  142. package/src/be/db/tasks/read.ts +11 -0
  143. package/src/be/db/tasks/write.ts +6 -1
  144. package/src/be/db/workflow-runs.ts +219 -6
  145. package/src/be/db-retention.ts +61 -6
  146. package/src/be/db.ts +135 -132
  147. package/src/be/memory/providers/openai-embedding.ts +5 -1
  148. package/src/be/memory/task-learning.ts +155 -0
  149. package/src/be/migrations/244_api_key_validation.sql +4 -0
  150. package/src/be/migrations/245_script_executor.sql +3 -0
  151. package/src/be/oauth-refresh-sweep.ts +2 -1
  152. package/src/be/onboarding.ts +4 -0
  153. package/src/be/scripts/boot-reembed.ts +2 -0
  154. package/src/be/scripts/db.ts +14 -4
  155. package/src/be/scripts/embeddings.ts +2 -0
  156. package/src/be/scripts/run-saved.ts +1 -0
  157. package/src/be/scripts/typecheck.ts +10 -4
  158. package/src/be/seed-skills/index.ts +7 -0
  159. package/src/be/session-source-backfill.ts +163 -0
  160. package/src/be/swarm-config-guard.ts +1 -0
  161. package/src/comb/review-batch.ts +28 -1
  162. package/src/comb/templates.ts +7 -2
  163. package/src/commands/claude-managed-setup.ts +7 -1
  164. package/src/commands/credential-refresh.ts +76 -1
  165. package/src/commands/credential-wait.ts +2 -2
  166. package/src/commands/onboard/compose-generator.ts +5 -3
  167. package/src/commands/provider-credentials.ts +212 -171
  168. package/src/commands/runner.ts +25 -4
  169. package/src/extensions/apply-task-create.ts +8 -1
  170. package/src/extensions/contract-types.generated.ts +1 -1
  171. package/src/extensions/contract.ts +5 -0
  172. package/src/heartbeat/heartbeat.ts +14 -1
  173. package/src/heartbeat/script-runner-probe.ts +47 -0
  174. package/src/http/all-routes.ts +2 -0
  175. package/src/http/api-keys.ts +77 -9
  176. package/src/http/assets.ts +15 -0
  177. package/src/http/codex-oauth-accounts.ts +191 -0
  178. package/src/http/comb.ts +15 -2
  179. package/src/http/config.ts +1 -1
  180. package/src/http/core.ts +6 -0
  181. package/src/http/fleet-benchmark.ts +5 -2
  182. package/src/http/idempotency-key.ts +1 -1
  183. package/src/http/index.ts +14 -3
  184. package/src/http/integrations.ts +5 -1
  185. package/src/http/kv.ts +8 -3
  186. package/src/http/onboarding.ts +6 -1
  187. package/src/http/pages-public.ts +38 -5
  188. package/src/http/pool-keys.ts +354 -0
  189. package/src/http/scripts.ts +27 -0
  190. package/src/http/sessions.ts +37 -12
  191. package/src/http/stats.ts +1 -0
  192. package/src/http/status.ts +26 -12
  193. package/src/http/tasks.ts +3 -0
  194. package/src/http/used-by.ts +7 -2
  195. package/src/http/workflows.ts +88 -9
  196. package/src/http/x.ts +5 -0
  197. package/src/integrations/chat-sdk/inbound.ts +27 -0
  198. package/src/kv-reserved-namespaces.ts +19 -0
  199. package/src/prompts/base-prompt.ts +21 -1
  200. package/src/prompts/session-templates.ts +17 -3
  201. package/src/providers/acp-targets.ts +1 -1
  202. package/src/providers/amp-adapter.ts +1 -1
  203. package/src/providers/claude-adapter.ts +5 -1
  204. package/src/providers/claude-managed-adapter.ts +16 -9
  205. package/src/providers/codex-adapter.ts +71 -11
  206. package/src/providers/dsh-adapter.ts +1 -1
  207. package/src/providers/pi-mono-adapter.ts +41 -11
  208. package/src/scheduler/scheduler.ts +5 -3
  209. package/src/scripts-runtime/eval-harness.ts +8 -1
  210. package/src/scripts-runtime/executors/native.ts +22 -16
  211. package/src/scripts-runtime/executors/quickjs-prelude.ts +103 -1
  212. package/src/scripts-runtime/executors/quickjs-runner.ts +2 -1
  213. package/src/scripts-runtime/executors/quickjs.ts +7 -0
  214. package/src/scripts-runtime/executors/registry.ts +32 -0
  215. package/src/scripts-runtime/executors/types.ts +7 -0
  216. package/src/scripts-runtime/host-validated-args.ts +133 -0
  217. package/src/scripts-runtime/loader.ts +30 -3
  218. package/src/scripts-runtime/swarm-sdk.ts +16 -3
  219. package/src/scripts-runtime/types/stdlib.d.ts +10 -2
  220. package/src/scripts-runtime/types/swarm-extension.d.ts +17 -8
  221. package/src/scripts-runtime/types/swarm-sdk.d.ts +10 -2
  222. package/src/slack/assistant.ts +7 -0
  223. package/src/slack/channel-labels.ts +74 -0
  224. package/src/slack/handlers.ts +8 -0
  225. package/src/slack/inbound-files.ts +5 -2
  226. package/src/slack/thread-buffer.ts +4 -0
  227. package/src/tasks/sibling-awareness.ts +12 -3
  228. package/src/telemetry-context.ts +5 -6
  229. package/src/telemetry-identity.ts +3 -3
  230. package/src/tests/api-key-status-by-suffix.test.ts +242 -0
  231. package/src/tests/api-keys-validation.test.ts +170 -0
  232. package/src/tests/base-prompt.test.ts +70 -2
  233. package/src/tests/claude-managed-adapter.test.ts +46 -0
  234. package/src/tests/claude-routes-e2e.test.ts +7 -2
  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/community-linear-delivery-loop.test.ts +2099 -0
  240. package/src/tests/config-pool-keys.test.ts +533 -0
  241. package/src/tests/credential-check.test.ts +294 -4
  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/docs-index.test.ts +135 -0
  248. package/src/tests/dsh-adapter.test.ts +1 -1
  249. package/src/tests/extensions-pre-task.test.ts +53 -1
  250. package/src/tests/fetch-resolved-env.test.ts +16 -2
  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/fleet-benchmark.test.ts +8 -0
  254. package/src/tests/get-workflow-run-tool.test.ts +98 -0
  255. package/src/tests/heartbeat-checklist.test.ts +89 -0
  256. package/src/tests/heartbeat-script-runner-probe.test.ts +83 -0
  257. package/src/tests/integrations-http.test.ts +32 -2
  258. package/src/tests/internal-ai/complete-structured.test.ts +37 -1
  259. package/src/tests/internal-ai/credentials.test.ts +23 -0
  260. package/src/tests/kv-http.test.ts +32 -0
  261. package/src/tests/kv-tool.test.ts +25 -0
  262. package/src/tests/multi-runtime-registration.test.ts +12 -12
  263. package/src/tests/oauth-refresh-sweep.test.ts +34 -1
  264. package/src/tests/onboard-compose.test.ts +20 -0
  265. package/src/tests/onboarding.test.ts +108 -1
  266. package/src/tests/openai-embedding-credentials.test.ts +35 -0
  267. package/src/tests/opencode-adapter.test.ts +2 -1
  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/pages-frame-ancestors.test.ts +162 -0
  271. package/src/tests/pi-codemode.test.ts +2 -1
  272. package/src/tests/pi-mono-adapter.test.ts +54 -0
  273. package/src/tests/pi-native-mcp.test.ts +13 -1
  274. package/src/tests/pi-tool-exposure.test.ts +8 -8
  275. package/src/tests/probe-tool-schema-tokens.test.ts +144 -0
  276. package/src/tests/prompt-template-session.test.ts +4 -3
  277. package/src/tests/provider-key-probes.test.ts +181 -0
  278. package/src/tests/rbac-e2e-helpers.test.ts +88 -1
  279. package/src/tests/rbac-e2e-helpers.ts +36 -11
  280. package/src/tests/request-composition-derive.test.ts +329 -0
  281. package/src/tests/script-connections.test.ts +4 -0
  282. package/src/tests/script-executor-conformance.test.ts +36 -1
  283. package/src/tests/script-executor-quickjs-pool.test.ts +50 -0
  284. package/src/tests/script-executor-quickjs.test.ts +140 -0
  285. package/src/tests/script-host-validated-args-native.test.ts +196 -0
  286. package/src/tests/script-per-script-executor.test.ts +415 -0
  287. package/src/tests/script-runs-retention.test.ts +212 -0
  288. package/src/tests/scripts-external-api.test.ts +45 -0
  289. package/src/tests/sdk-allowlist.test.ts +42 -2
  290. package/src/tests/self-improvement.test.ts +4 -4
  291. package/src/tests/session-source-backfill.test.ts +429 -0
  292. package/src/tests/session-source-keys.test.ts +236 -0
  293. package/src/tests/session-source-labels.test.ts +419 -0
  294. package/src/tests/sessions-detail-batch.test.ts +2 -0
  295. package/src/tests/sessions-move.test.ts +131 -0
  296. package/src/tests/sessions-tree.test.ts +299 -2
  297. package/src/tests/slack-inbound-files.test.ts +103 -23
  298. package/src/tests/status.test.ts +101 -18
  299. package/src/tests/store-progress-learning.test.ts +288 -0
  300. package/src/tests/tasks-workflow-run-filter.test.ts +146 -0
  301. package/src/tests/test-net.ts +31 -0
  302. package/src/tests/transcription.test.ts +8 -0
  303. package/src/tests/ui-logs-parser.test.ts +180 -0
  304. package/src/tests/used-by-route.test.ts +11 -0
  305. package/src/tests/workflow-cascade-cancel.test.ts +172 -0
  306. package/src/tests/workflow-engine-v2.test.ts +116 -1
  307. package/src/tests/workflow-http-v2.test.ts +131 -0
  308. package/src/tests/workflow-mcp-trigger-schema.test.ts +209 -0
  309. package/src/tests/workflow-run-finished-event.test.ts +263 -0
  310. package/src/tests/workflow-step-order.test.ts +92 -0
  311. package/src/tests/workflow-sub-workflow.test.ts +30 -0
  312. package/src/tests/workflow-swarm-script.test.ts +34 -0
  313. package/src/tools/get-tasks.ts +8 -0
  314. package/src/tools/kv/kv-read-auth.ts +4 -2
  315. package/src/tools/script-common.ts +1 -0
  316. package/src/tools/script-run.ts +7 -0
  317. package/src/tools/script-upsert.ts +7 -0
  318. package/src/tools/store-progress.ts +67 -1
  319. package/src/tools/task-action.ts +1 -1
  320. package/src/tools/workflows/get-workflow-run.ts +14 -4
  321. package/src/tools/workflows/trigger-workflow.ts +104 -10
  322. package/src/types.ts +36 -1
  323. package/src/utils/company-profile.ts +51 -1
  324. package/src/utils/credentials.ts +10 -4
  325. package/src/utils/internal-ai/complete-structured.ts +5 -0
  326. package/src/utils/internal-ai/credentials.ts +4 -3
  327. package/src/utils/provider-key-probes.ts +328 -0
  328. package/src/utils/transcription.ts +4 -1
  329. package/src/workflows/engine.ts +18 -16
  330. package/src/workflows/executors/sub-workflow.ts +8 -1
  331. package/src/workflows/executors/swarm-script.ts +5 -0
  332. package/src/workflows/foreach-driver.ts +5 -11
  333. package/src/workflows/node-timeout.ts +30 -0
  334. package/templates/ai-toolbox.manifest.json +2 -2
  335. package/templates/community/linear-delivery-loop/PLAYBOOK.md +323 -0
  336. package/templates/community/linear-delivery-loop/README.md +38 -0
  337. package/templates/community/linear-delivery-loop/config.example.json +31 -0
  338. package/templates/community/linear-delivery-loop/lead-prompt.md +34 -0
  339. package/templates/community/linear-delivery-loop/schedule.json +10 -0
  340. package/templates/community/linear-delivery-loop/scripts/linear-delivery-setup.ts +148 -0
  341. package/templates/community/linear-delivery-loop/scripts/linear-delivery-tick.ts +1781 -0
  342. package/templates/skills/researching/content.md +2 -0
  343. package/templates/skills/swarm-docs/config.json +14 -0
  344. package/templates/skills/swarm-docs/content.md +144 -0
  345. package/templates/skills/swarm-scripts/SKILL.md +1 -0
  346. package/templates/skills/swarm-scripts/content.md +1 -0
  347. package/dist/cli-7421jzsw.js +0 -84
  348. package/dist/cli-e45e4m6b.js +0 -413
  349. package/dist/cli-zw88zjba.js +0 -307
  350. package/dist/handlers-wy1dr6ja.js +0 -41
  351. package/dist/index-nmm79qgr.js +0 -43
  352. package/dist/lead-sawnyqxk.js +0 -79
  353. package/dist/render-v2-pjwxvnkw.js +0 -12
  354. package/dist/worker-v5e5rxj3.js +0 -79
@@ -1,12 +1,17 @@
1
+ import type { AgentCredStatus } from "../types";
2
+ import { CREDENTIAL_POOL_VARS } from "../utils/credentials";
3
+ import { scrubSecrets } from "../utils/secret-scrubber";
1
4
  import {
2
5
  buildCredStatusReport,
3
6
  isCredCheckDisabled,
7
+ type LiveKeyResult,
4
8
  sendCredStatusReport,
5
9
  shouldRefreshBedrockStatus,
6
10
  } from "./provider-credentials";
7
11
  import type { ApiConfig } from "./runner";
8
12
 
9
13
  export const CREDENTIAL_RETRY_INTERVAL_MS = 30_000;
14
+ export const KEY_RECHECK_INTERVAL_MS = 10 * 60_000;
10
15
  const BEDROCK_REFRESH_INTERVAL_MS = 5 * 60 * 1000;
11
16
 
12
17
  export interface CredentialRefreshState {
@@ -15,6 +20,64 @@ export interface CredentialRefreshState {
15
20
  ready: boolean | null;
16
21
  lastRefreshAt: number;
17
22
  inFlight: boolean;
23
+ fingerprint: string | null;
24
+ rejectedKeys: number;
25
+ }
26
+
27
+ export function credentialFingerprint(env: Record<string, string | undefined>): string {
28
+ return Bun.hash(
29
+ JSON.stringify(
30
+ [...CREDENTIAL_POOL_VARS, "ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_BASE_URL"].map(
31
+ (key) => env[key] ?? null,
32
+ ),
33
+ ),
34
+ ).toString();
35
+ }
36
+
37
+ /**
38
+ * Failed keys worth the periodic recheck. A Claude subscription token is left
39
+ * out: its check calls an undocumented Anthropic account endpoint, so it runs
40
+ * only once per token value (boot or change). A failed test with no per-key
41
+ * results counts as one.
42
+ */
43
+ export function recheckableFailures(liveTest: AgentCredStatus["liveTest"] | undefined): number {
44
+ const keys = liveTest?.keys;
45
+ if (keys?.length) {
46
+ return keys.filter((key) => !key.ok && key.keyType !== "CLAUDE_CODE_OAUTH_TOKEN").length;
47
+ }
48
+ return liveTest?.ok === false ? 1 : 0;
49
+ }
50
+
51
+ /** Report each pool separately. A telemetry failure must not stop credential checks. */
52
+ export async function reportKeyValidation(api: ApiConfig, keys: LiveKeyResult[]): Promise<void> {
53
+ for (const keyType of new Set(keys.map((key) => key.keyType))) {
54
+ try {
55
+ const response = await fetch(`${api.apiUrl}/api/keys/report-validation`, {
56
+ method: "POST",
57
+ headers: {
58
+ "Content-Type": "application/json",
59
+ Authorization: `Bearer ${api.apiKey}`,
60
+ "X-Agent-ID": api.agentId,
61
+ },
62
+ body: JSON.stringify({
63
+ keyType,
64
+ keys: keys
65
+ .filter((key) => key.keyType === keyType)
66
+ .map((key) => ({
67
+ keyIndex: key.index,
68
+ keySuffix: key.suffix,
69
+ ok: key.ok,
70
+ httpStatus: key.httpStatus,
71
+ })),
72
+ }),
73
+ signal: AbortSignal.timeout(5000),
74
+ });
75
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
76
+ } catch (err) {
77
+ const message = err instanceof Error ? err.message : String(err);
78
+ console.warn(scrubSecrets(`[credentials] Failed to report key validation: ${message}`));
79
+ }
80
+ }
18
81
  }
19
82
 
20
83
  /**
@@ -31,11 +94,17 @@ export async function refreshCredentialStatus(
31
94
  ): Promise<void> {
32
95
  if (isCredCheckDisabled(env) || state.inFlight) return;
33
96
  const providerChanged = state.harnessProvider !== harnessProvider;
97
+ const fingerprint = credentialFingerprint(env);
98
+ const fingerprintChanged = state.fingerprint !== fingerprint;
34
99
  const retryDue =
35
100
  state.ready !== true && now - state.lastRefreshAt >= CREDENTIAL_RETRY_INTERVAL_MS;
101
+ const keyRecheckDue =
102
+ state.rejectedKeys > 0 && now - state.lastRefreshAt >= KEY_RECHECK_INTERVAL_MS;
36
103
  if (
37
104
  !providerChanged &&
105
+ !fingerprintChanged &&
38
106
  !retryDue &&
107
+ !keyRecheckDue &&
39
108
  !shouldRefreshBedrockStatus({
40
109
  harnessProvider,
41
110
  env,
@@ -61,7 +130,13 @@ export async function refreshCredentialStatus(
61
130
  missing: snapshot.missing,
62
131
  credStatus: snapshot,
63
132
  });
64
- if (state.harnessProvider === harnessProvider) state.ready = snapshot.ready;
133
+ if (state.harnessProvider === harnessProvider) {
134
+ state.ready = snapshot.ready;
135
+ state.fingerprint = fingerprint;
136
+ state.rejectedKeys = recheckableFailures(snapshot.liveTest);
137
+ const keys = snapshot.liveTest?.keys;
138
+ if (keys?.length) await reportKeyValidation(api, keys);
139
+ }
65
140
  } finally {
66
141
  // Errors propagate to the runner's non-fatal logger, leaving ready unknown
67
142
  // so even a lost ready:true write is retried without a restart.
@@ -12,7 +12,7 @@
12
12
  * 3. Re-checks; if ready, returns.
13
13
  * 4. Logs a `[boot] waiting for …` line and invokes `onTick(status)` so
14
14
  * callers can report state to the API.
15
- * 5. Sleeps with exponential backoff (2s → 30s, cap configurable).
15
+ * 5. Sleeps with exponential backoff (2s to 10s, cap configurable).
16
16
  * 6. If `BOOT_MAX_WAIT_SECONDS` is set and exceeded, throws a
17
17
  * `BootMaxWaitExceededError` so the runner can exit with a distinct
18
18
  * code. Default 0 = wait forever.
@@ -96,7 +96,7 @@ function resolveBackoff(
96
96
  };
97
97
  return {
98
98
  initialMs: override?.initialMs ?? parsePositive(env.BOOT_INITIAL_BACKOFF_MS, 2000),
99
- maxMs: override?.maxMs ?? parsePositive(env.BOOT_MAX_BACKOFF_MS, 30000),
99
+ maxMs: override?.maxMs ?? parsePositive(env.BOOT_MAX_BACKOFF_MS, 10000),
100
100
  // 0 = wait forever — the runner can override with a finite ceiling per
101
101
  // worker if monitoring wants a "config never arrived" signal.
102
102
  maxWaitSeconds: override?.maxWaitSeconds ?? parsePositive(env.BOOT_MAX_WAIT_SECONDS, 0),
@@ -243,9 +243,11 @@ export function generateCompose(state: OnboardState): string {
243
243
  lines.push(` - "${port}:3000"`);
244
244
  lines.push("");
245
245
  lines.push(" volumes:");
246
- lines.push(" - swarm_logs:/app/logs");
247
- lines.push(" - swarm_shared:/app/shared");
248
- lines.push(` - swarm_${svc.sanitizedName}:/app/agent`);
246
+ // Same targets as docker-compose.example.yml: the worker logs to /logs and
247
+ // keeps its clones and files under /workspace.
248
+ lines.push(" - swarm_logs:/logs");
249
+ lines.push(" - swarm_shared:/workspace/shared");
250
+ lines.push(` - swarm_${svc.sanitizedName}:/workspace/personal`);
249
251
  if (state.provider === "bedrock" && state.awsProfile) {
250
252
  lines.push(engine === "podman" ? AWS_PROFILE_VOLUME_SELINUX : AWS_PROFILE_VOLUME);
251
253
  }
@@ -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,36 @@ 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 { isEnvFlagEnabled } from "../utils/env-flag";
53
+ import {
54
+ checkAnthropicApiKey,
55
+ checkDeepSeekApiKey,
56
+ checkOpenAICompatibleProvider,
57
+ checkOpenAiApiKey,
58
+ checkOpenRouter,
59
+ checkXaiApiKey,
60
+ type LiveKeyResult,
61
+ type LiveValidationResult,
62
+ timedFetch,
63
+ } from "../utils/provider-key-probes";
53
64
  import { scrubSecrets } from "../utils/secret-scrubber";
54
65
 
66
+ export {
67
+ checkAnthropicApiKey,
68
+ checkDeepSeekApiKey,
69
+ checkOpenAICompatibleProvider,
70
+ checkOpenAiApiKey,
71
+ checkOpenRouter,
72
+ checkXaiApiKey,
73
+ type LiveKeyResult,
74
+ type LiveValidationResult,
75
+ } from "../utils/provider-key-probes";
76
+
55
77
  export type SupportedProvider =
56
78
  | "claude"
57
79
  | "claude-managed"
@@ -215,46 +237,8 @@ export async function checkProviderCredentials(
215
237
  // Mirrors `checkProviderCredentials` but issues a real (cheapest possible)
216
238
  // upstream call so the user can flip the `harness` milestone to `verified`.
217
239
  //
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
- }
240
+ // Worker-only dispatch uses the shared probes in `provider-key-probes.ts`.
241
+ // The API imports those probes directly to avoid loading worker harnesses.
258
242
 
259
243
  function asLiveError(err: unknown, latency_ms: number): LiveValidationResult {
260
244
  const raw = err instanceof Error ? err.message : String(err);
@@ -265,6 +249,69 @@ function asLiveError(err: unknown, latency_ms: number): LiveValidationResult {
265
249
  };
266
250
  }
267
251
 
252
+ /**
253
+ * Run `check` on every member of a comma pool in parallel. The pool passes
254
+ * when one key works (the runner skips a rejected key); `warning` names the
255
+ * keys that failed, and the check fails only when every key fails.
256
+ */
257
+ export async function checkEachKey(
258
+ keyType: string,
259
+ value: string,
260
+ check: (key: string) => Promise<LiveValidationResult>,
261
+ ): Promise<LiveValidationResult> {
262
+ const members = splitCredentialPool(value);
263
+ if (members.length === 0) members.push(value.trim());
264
+ const results = await Promise.all(
265
+ members.map(async (member) => {
266
+ const startedAt = Date.now();
267
+ try {
268
+ return await check(member);
269
+ } catch (err) {
270
+ return asLiveError(err, Date.now() - startedAt);
271
+ }
272
+ }),
273
+ );
274
+ const latency_ms = Math.max(...results.map((result) => result.latency_ms));
275
+ if (results.every((result) => result.skipped)) return { ok: true, skipped: true, latency_ms };
276
+ const keys = results.map(
277
+ (result, index): LiveKeyResult => ({
278
+ keyType,
279
+ index,
280
+ suffix: members[index]!.slice(-5),
281
+ ok: result.ok,
282
+ ...(result.error !== undefined ? { error: result.error } : {}),
283
+ ...(result.httpStatus !== undefined ? { httpStatus: result.httpStatus } : {}),
284
+ latency_ms: result.latency_ms,
285
+ }),
286
+ );
287
+ const failed = keys.filter((key) => !key.ok);
288
+ if (failed.length === 0) return { ok: true, latency_ms, keys };
289
+ const failures = failed
290
+ .map(
291
+ (key) =>
292
+ `key ending ${key.suffix} (${key.httpStatus !== undefined ? `HTTP ${key.httpStatus}` : (key.error ?? "Unknown error")})`,
293
+ )
294
+ .join(", ");
295
+ if (failed.length < keys.length) {
296
+ return {
297
+ ok: true,
298
+ latency_ms,
299
+ keys,
300
+ warning: `${failed.length} of ${keys.length} keys failed: ${failures}.`,
301
+ };
302
+ }
303
+ if (keys.length === 1) {
304
+ return {
305
+ ok: false,
306
+ error: failed[0]!.error,
307
+ httpStatus: failed[0]!.httpStatus,
308
+ latency_ms,
309
+ keys,
310
+ };
311
+ }
312
+ return { ok: false, error: `All ${keys.length} keys failed: ${failures}.`, latency_ms, keys };
313
+ }
314
+
268
315
  // ─── Per-endpoint live-call helpers ──────────────────────────────────────────
269
316
  // Each helper accepts the auth material it needs and returns a normalized
270
317
  // `LiveValidationResult`. The harness-level dispatcher below picks which
@@ -272,22 +319,10 @@ function asLiveError(err: unknown, latency_ms: number): LiveValidationResult {
272
319
  // users no longer see "ANTHROPIC_API_KEY is not set" when their adapter is
273
320
  // happily running off `CLAUDE_CODE_OAUTH_TOKEN`.
274
321
 
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
322
  /**
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.
323
+ * Codex OAuth credentials (ChatGPT login) are treated as a presence check — we
324
+ * don't issue a live upstream call. (Claude subscription tokens get a real
325
+ * check through the route: see `claudeSubscriptionProvider`.)
291
326
  *
292
327
  * Why: OAuth flows include their own refresh logic (handled at adapter boot,
293
328
  * not here), the OAuth-bearer-with-/v1/models contract isn't a stable public
@@ -299,70 +334,6 @@ function presenceCheckOk(): LiveValidationResult {
299
334
  return { ok: true, latency_ms: 0 };
300
335
  }
301
336
 
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
337
  /**
367
338
  * Mirror of the disk check in `checkCodexCredentials` — true when the worker
368
339
  * has a materialised `~/.codex/auth.json`, which is the canonical "logged in"
@@ -402,14 +373,15 @@ function parseCodexOAuthAccess(blob: string | undefined): string | null {
402
373
  *
403
374
  * | Harness | Accepted credentials (in resolution order) | Endpoint |
404
375
  * |------------------|-------------------------------------------------------------------------|--------------------------------|
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 |
376
+ * | `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
377
  * | `claude-managed` | `ANTHROPIC_API_KEY` (managed agents always use API key + managed envs) | Anthropic `/v1/models` |
407
378
  * | `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` |
379
+ * | `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
380
  * | `pi` | auth.json (file) → provider API keys, including model-specific keys | file/model-specific key presence-only, otherwise provider's `/v1/models` |
410
381
  * | `acp` | target-specific (the ACP target process owns its own auth) | presence-only (validated by the target process) |
411
382
  * | `pi` (bedrock) | `MODEL_OVERRIDE=amazon-bedrock/*` → AWS SDK default credential chain | presence-only (real check is the worker-side Bedrock enumeration) |
412
383
  * | `devin` | `DEVIN_API_KEY` (+ `DEVIN_API_BASE_URL` override) | `${baseUrl}/v3/self` |
384
+ * | `dsh` | `DEEPSEEK_API_KEY` → `OPENROUTER_API_KEY` | DeepSeek `/models`, or OpenRouter `/key` |
413
385
  * | `amp` | `AMP_API_KEY` | `amp usage` (credit balance, no inference; 5s timeout) |
414
386
  *
415
387
  * Returns `{ok: true, latency_ms}` on 2xx, `{ok: false, error, latency_ms}`
@@ -429,8 +401,9 @@ export async function validateProviderCredentials(
429
401
  case "claude": {
430
402
  // The default route decides where the check goes: a gateway key is
431
403
  // 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).
404
+ // api.anthropic.com. A subscription token is checked against
405
+ // `/api/oauth/profile`; cloud routes have no free check and report
406
+ // `configured` (no live test).
434
407
  const route = deriveDefaultRoute("claude", env);
435
408
  if (!route) {
436
409
  return {
@@ -439,16 +412,40 @@ export async function validateProviderCredentials(
439
412
  latency_ms: Date.now() - startedAt,
440
413
  };
441
414
  }
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 };
415
+ const checkRoute = async (
416
+ routeEnv: Record<string, string | undefined>,
417
+ ): Promise<LiveValidationResult> => {
418
+ const start = Date.now();
419
+ const result = await validateRoute(route, routeEnv);
420
+ const latency_ms = Date.now() - start;
421
+ if (result.status === "verified") return { ok: true, latency_ms };
422
+ if (result.status === "configured") return { ok: true, skipped: true, latency_ms };
423
+ return {
424
+ ok: false,
425
+ error: scrubSecrets(result.reason),
426
+ httpStatus: result.httpStatus,
427
+ latency_ms,
428
+ };
429
+ };
430
+ const keyVar =
431
+ route.auth.kind === "subscription"
432
+ ? "CLAUDE_CODE_OAUTH_TOKEN"
433
+ : "secretKey" in route.auth
434
+ ? route.auth.secretKey
435
+ : undefined;
436
+ const value = keyVar ? env[keyVar] : undefined;
437
+ // Only pool vars are split: the runner never splits a gateway token.
438
+ if (keyVar && value && (CREDENTIAL_POOL_VARS as readonly string[]).includes(keyVar)) {
439
+ return checkEachKey(keyVar, value, (member) => checkRoute({ ...env, [keyVar]: member }));
440
+ }
441
+ return checkRoute(env);
447
442
  }
448
443
  case "claude-managed": {
449
444
  // Managed agents always run with an API key — OAuth not supported on
450
445
  // the managed-agents path today.
451
- if (env.ANTHROPIC_API_KEY) return checkAnthropicApiKey(env.ANTHROPIC_API_KEY);
446
+ if (env.ANTHROPIC_API_KEY) {
447
+ return checkEachKey("ANTHROPIC_API_KEY", env.ANTHROPIC_API_KEY, checkAnthropicApiKey);
448
+ }
452
449
  return {
453
450
  ok: false,
454
451
  error: "ANTHROPIC_API_KEY is not set.",
@@ -456,6 +453,22 @@ export async function validateProviderCredentials(
456
453
  };
457
454
  }
458
455
  case "codex": {
456
+ // A gateway route is checked against the gateway only (scoped fetch
457
+ // to its own `/models`), never against api.openai.com, and wins over
458
+ // every OAuth / auth.json / OPENAI_API_KEY check below.
459
+ const gateway = deriveDefaultRoute("codex", env);
460
+ if (gateway) {
461
+ const result = await validateRoute(gateway, env);
462
+ const latency_ms = Date.now() - startedAt;
463
+ if (result.status === "verified") return { ok: true, latency_ms };
464
+ if (result.status === "configured") return { ok: true, skipped: true, latency_ms };
465
+ return {
466
+ ok: false,
467
+ error: scrubSecrets(result.reason),
468
+ httpStatus: result.httpStatus,
469
+ latency_ms,
470
+ };
471
+ }
459
472
  // Resolution order matches `checkCodexCredentials`:
460
473
  // 1) `~/.codex/auth.json` on disk (canonical state once `codex login`
461
474
  // has run, or when the entrypoint pre-materialised it from
@@ -474,7 +487,9 @@ export async function validateProviderCredentials(
474
487
  if (codexAuthFileExists(env)) return presenceCheckOk();
475
488
  if (parseCodexOAuthAccess(env.CODEX_OAUTH)) return presenceCheckOk();
476
489
  if (hasCodexOAuthPoolSlots(env)) return presenceCheckOk();
477
- if (env.OPENAI_API_KEY) return checkOpenAiApiKey(env.OPENAI_API_KEY);
490
+ if (env.OPENAI_API_KEY) {
491
+ return checkEachKey("OPENAI_API_KEY", env.OPENAI_API_KEY, checkOpenAiApiKey);
492
+ }
478
493
  return {
479
494
  ok: false,
480
495
  error:
@@ -503,13 +518,21 @@ export async function validateProviderCredentials(
503
518
  // registry providers in declaration order (NaN, canonical key or
504
519
  // alias → Helmcode) at their `<ID>_BASE_URL`, so existing env
505
520
  // 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);
521
+ if (env.OPENROUTER_API_KEY) {
522
+ return checkEachKey("OPENROUTER_API_KEY", env.OPENROUTER_API_KEY, checkOpenRouter);
523
+ }
524
+ if (env.ANTHROPIC_API_KEY) {
525
+ return checkEachKey("ANTHROPIC_API_KEY", env.ANTHROPIC_API_KEY, checkAnthropicApiKey);
526
+ }
527
+ if (env.OPENAI_API_KEY) {
528
+ return checkEachKey("OPENAI_API_KEY", env.OPENAI_API_KEY, checkOpenAiApiKey);
529
+ }
509
530
  for (const direct of openAICompatibleProviders()) {
510
531
  const key = providerKeyFromEnv(direct, env);
511
532
  if (key) {
512
- return checkOpenAICompatibleProvider(direct, key, providerBaseUrl(direct, env));
533
+ return checkEachKey(direct.envVar, key, (member) =>
534
+ checkOpenAICompatibleProvider(direct, member, providerBaseUrl(direct, env)),
535
+ );
513
536
  }
514
537
  }
515
538
  // Model-specific keys (e.g. GEMINI_API_KEY) can satisfy readiness
@@ -534,28 +557,36 @@ export async function validateProviderCredentials(
534
557
  };
535
558
  }
536
559
  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
- },
560
+ return checkEachKey("DEVIN_API_KEY", apiKey, async (member) => {
561
+ const r = await timedFetch(`${baseUrl.replace(/\/+$/, "")}/v3/self`, {
562
+ method: "GET",
563
+ headers: {
564
+ Authorization: `Bearer ${member}`,
565
+ "Content-Type": "application/json",
566
+ },
567
+ });
568
+ if (r.ok) return { ok: true, latency_ms: r.latency_ms };
569
+ return {
570
+ ok: false,
571
+ error: scrubSecrets(`HTTP ${r.status}: ${r.bodyText.slice(0, 200)}`),
572
+ httpStatus: r.status,
573
+ latency_ms: r.latency_ms,
574
+ };
543
575
  });
544
- if (r.ok) return { ok: true, latency_ms: r.latency_ms };
576
+ }
577
+ case "dsh": {
578
+ // Same precedence as `checkDshCredentials`: the DeepSeek key, then OpenRouter.
579
+ const deepseekKey = env.DEEPSEEK_API_KEY?.trim();
580
+ if (deepseekKey) return checkEachKey("DEEPSEEK_API_KEY", deepseekKey, checkDeepSeekApiKey);
581
+ const openRouterKey = env.OPENROUTER_API_KEY?.trim();
582
+ if (openRouterKey)
583
+ return checkEachKey("OPENROUTER_API_KEY", openRouterKey, checkOpenRouter);
545
584
  return {
546
585
  ok: false,
547
- error: scrubSecrets(`HTTP ${r.status}: ${r.bodyText.slice(0, 200)}`),
548
- latency_ms: r.latency_ms,
586
+ error: "Set DEEPSEEK_API_KEY or OPENROUTER_API_KEY for dsh.",
587
+ latency_ms: Date.now() - startedAt,
549
588
  };
550
589
  }
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
590
  case "amp": {
560
591
  if (!checkAmpCredentials(env).ready) {
561
592
  return {
@@ -565,14 +596,17 @@ export async function validateProviderCredentials(
565
596
  };
566
597
  }
567
598
  // `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
- };
599
+ return checkEachKey("AMP_API_KEY", env.AMP_API_KEY!, async (member) => {
600
+ const start = Date.now();
601
+ const live = await liveTestAmpCredentials({ ...env, AMP_API_KEY: member });
602
+ return live.ok
603
+ ? { ok: true, latency_ms: Date.now() - start }
604
+ : {
605
+ ok: false,
606
+ error: live.error ?? "amp usage failed",
607
+ latency_ms: Date.now() - start,
608
+ };
609
+ });
576
610
  }
577
611
  case "cursor": {
578
612
  const apiKey = env.CURSOR_API_KEY?.trim();
@@ -584,16 +618,21 @@ export async function validateProviderCredentials(
584
618
  };
585
619
  }
586
620
  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 };
621
+ return checkEachKey("CURSOR_API_KEY", apiKey, async (member) => {
622
+ const start = Date.now();
623
+ const result = await liveTestCursorKey(member);
624
+ return result.ok
625
+ ? { ok: true, latency_ms: Date.now() - start }
626
+ : { ok: false, error: result.error, latency_ms: Date.now() - start };
627
+ });
591
628
  }
592
629
  case "grok": {
593
630
  const apiKey = env.XAI_API_KEY?.trim();
594
- if (apiKey) return checkXaiApiKey(apiKey);
631
+ if (apiKey) return checkEachKey("XAI_API_KEY", apiKey, checkXaiApiKey);
595
632
  const openRouterKey = env.OPENROUTER_API_KEY?.trim();
596
- if (openRouterKey) return checkOpenRouter(openRouterKey);
633
+ if (openRouterKey) {
634
+ return checkEachKey("OPENROUTER_API_KEY", openRouterKey, checkOpenRouter);
635
+ }
597
636
  return {
598
637
  ok: false,
599
638
  error: "Set XAI_API_KEY or OPENROUTER_API_KEY for grok.",
@@ -626,7 +665,7 @@ export async function validateProviderCredentials(
626
665
  * the dashboard surfaces "unreported".
627
666
  */
628
667
  export function isCredCheckDisabled(env: NodeJS.ProcessEnv): boolean {
629
- return env.CRED_CHECK_DISABLE === "1";
668
+ return isEnvFlagEnabled("CRED_CHECK_DISABLE", false, env);
630
669
  }
631
670
 
632
671
  /**
@@ -653,6 +692,8 @@ export async function buildCredStatusReport(
653
692
  error: live.error ?? null,
654
693
  latency_ms: live.latency_ms,
655
694
  testedAt: Date.now(),
695
+ ...(live.warning !== undefined ? { warning: live.warning } : {}),
696
+ ...(live.keys !== undefined ? { keys: live.keys } : {}),
656
697
  };
657
698
  }
658
699
  // Include the Bedrock enumeration block when the pi probe ran in Bedrock SDK