@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
@@ -0,0 +1,191 @@
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+ import { z } from "zod";
3
+ import { decryptSecret, getEncryptionKey } from "../be/crypto";
4
+ import { deleteKv, deleteSwarmConfigByKey, getDbClient, getKeyStatuses, getKv } from "../be/db";
5
+ import { retirePoolKeyStatus } from "../be/db/api-keys";
6
+ import { deriveCodexKeySuffix } from "../providers/codex-oauth/auth-json";
7
+ import { decodeJwt, extractChatgptPlanType } from "../providers/codex-oauth/flow";
8
+ import { MAX_CODEX_OAUTH_SLOT } from "../providers/codex-oauth/storage";
9
+ import {
10
+ CODEX_AUTH_FAILURE_BENCH_THRESHOLD,
11
+ CODEX_AUTH_WATCH_NAMESPACE,
12
+ codexAuthBenchMarkerKey,
13
+ } from "../utils/codex-auth-failure";
14
+ import { ensureConfigAdmin, requestSecretReadScope, runConfigWriteSideEffects } from "./config";
15
+ import { route } from "./route-def";
16
+ import { jsonError } from "./utils";
17
+
18
+ export const CodexAccountSchema = z.object({
19
+ slot: z.number().int().min(0),
20
+ configKey: z.string(),
21
+ legacy: z.boolean(),
22
+ suffix: z.string().nullable(),
23
+ plan: z.string().nullable(),
24
+ email: z.string().nullable(),
25
+ status: z.enum(["ok", "needs_login", "unchecked"]),
26
+ });
27
+ export type CodexAccount = z.infer<typeof CodexAccountSchema>;
28
+
29
+ export const CodexAccountsResponseSchema = z.object({ accounts: z.array(CodexAccountSchema) });
30
+ export type CodexAccountsResponse = z.infer<typeof CodexAccountsResponseSchema>;
31
+
32
+ const listAccounts = route({
33
+ method: "get",
34
+ path: "/api/codex-oauth/accounts",
35
+ pattern: ["api", "codex-oauth", "accounts"],
36
+ summary: "List stored Codex ChatGPT accounts",
37
+ tags: ["Codex OAuth"],
38
+ responses: {
39
+ 200: { description: "Stored account metadata", schema: CodexAccountsResponseSchema },
40
+ 403: { description: "Caller cannot read all secrets" },
41
+ },
42
+ });
43
+
44
+ const deleteAccount = route({
45
+ method: "delete",
46
+ path: "/api/codex-oauth/accounts/{slot}",
47
+ pattern: ["api", "codex-oauth", "accounts", null],
48
+ summary: "Remove a stored Codex ChatGPT account",
49
+ tags: ["Codex OAuth"],
50
+ rbac: { permission: "config.delete.any" },
51
+ params: z.object({ slot: z.coerce.number().int().min(0).max(MAX_CODEX_OAUTH_SLOT) }),
52
+ responses: {
53
+ 200: { description: "Remaining accounts", schema: CodexAccountsResponseSchema },
54
+ 403: { description: "Config administration and permission to read all secrets required" },
55
+ 404: { description: "Account slot does not exist" },
56
+ },
57
+ });
58
+
59
+ type AccountRow = { key: string; value: string; encrypted: number };
60
+
61
+ async function accountRows(): Promise<Array<AccountRow & { slot: number; legacy: boolean }>> {
62
+ const rows = await getDbClient().query<AccountRow>(
63
+ "SELECT key, value, encrypted FROM swarm_config WHERE scope = 'global' AND (key = 'codex_oauth' OR key GLOB 'codex_oauth_[0-9]*')",
64
+ );
65
+ const hasSlotZero = rows.some((row) => row.key === "codex_oauth_0");
66
+ return rows
67
+ .flatMap((row) => {
68
+ if (row.key === "codex_oauth") {
69
+ return hasSlotZero ? [] : [{ ...row, slot: 0, legacy: true }];
70
+ }
71
+ const match = /^codex_oauth_(0|[1-9]\d*)$/.exec(row.key);
72
+ if (!match) return [];
73
+ const slot = Number(match[1]);
74
+ return slot <= MAX_CODEX_OAUTH_SLOT ? [{ ...row, slot, legacy: false }] : [];
75
+ })
76
+ .sort((a, b) => a.slot - b.slot);
77
+ }
78
+
79
+ const AccountCredentialsSchema = z.object({
80
+ access: z.string().min(1),
81
+ refresh: z.string().min(1),
82
+ expires: z.number().finite(),
83
+ accountId: z.string().min(1),
84
+ });
85
+
86
+ function readCredentials(row: AccountRow): z.infer<typeof AccountCredentialsSchema> | null {
87
+ try {
88
+ const value = row.encrypted ? decryptSecret(row.value, getEncryptionKey()) : row.value;
89
+ return AccountCredentialsSchema.parse(JSON.parse(value));
90
+ } catch {
91
+ return null;
92
+ }
93
+ }
94
+
95
+ function accountSuffix(row: AccountRow): string | null {
96
+ const credentials = readCredentials(row);
97
+ return credentials ? deriveCodexKeySuffix(credentials.access, credentials.accountId) : null;
98
+ }
99
+
100
+ async function accountsResponse(): Promise<CodexAccountsResponse> {
101
+ const rows = await accountRows();
102
+ const statuses = await getKeyStatuses("CODEX_OAUTH", "global", null);
103
+ const accounts: CodexAccount[] = [];
104
+ for (const row of rows) {
105
+ const account: CodexAccount = {
106
+ slot: row.slot,
107
+ configKey: row.key,
108
+ legacy: row.legacy,
109
+ suffix: null,
110
+ plan: null,
111
+ email: null,
112
+ status: "needs_login",
113
+ };
114
+ const credentials = readCredentials(row);
115
+ if (!credentials) {
116
+ accounts.push(account);
117
+ continue;
118
+ }
119
+ account.suffix = deriveCodexKeySuffix(credentials.access, credentials.accountId);
120
+ account.plan = extractChatgptPlanType(credentials.access);
121
+ const profile = decodeJwt(credentials.access)?.["https://api.openai.com/profile"];
122
+ if (profile && typeof profile === "object" && "email" in profile) {
123
+ account.email = typeof profile.email === "string" && profile.email ? profile.email : null;
124
+ }
125
+ const status = statuses.find((entry) => entry.keySuffix === account.suffix);
126
+ const marker = await getKv(CODEX_AUTH_WATCH_NAMESPACE, codexAuthBenchMarkerKey(account.suffix));
127
+ const statusBenched =
128
+ status &&
129
+ status.consecutiveAuthFailures >= CODEX_AUTH_FAILURE_BENCH_THRESHOLD &&
130
+ status.status === "rate_limited";
131
+ account.status = marker || statusBenched ? "needs_login" : status ? "ok" : "unchecked";
132
+ accounts.push(account);
133
+ }
134
+ return { accounts };
135
+ }
136
+
137
+ /** Both responses list account emails and plans, so both routes need this gate. */
138
+ async function ensureSecretRead(req: IncomingMessage, res: ServerResponse): Promise<boolean> {
139
+ if ((await requestSecretReadScope(req)) === "all") return true;
140
+ jsonError(res, "Reading Codex accounts requires access to all secrets", 403);
141
+ return false;
142
+ }
143
+
144
+ export async function handleCodexOAuthAccounts(
145
+ req: IncomingMessage,
146
+ res: ServerResponse,
147
+ pathSegments: string[],
148
+ queryParams: URLSearchParams,
149
+ ): Promise<boolean> {
150
+ if (listAccounts.match(req.method, pathSegments)) {
151
+ const parsed = await listAccounts.parse(req, res, pathSegments, queryParams);
152
+ if (!parsed) return true;
153
+ if (!(await ensureSecretRead(req, res))) return true;
154
+ listAccounts.respond(res, 200, await accountsResponse());
155
+ return true;
156
+ }
157
+
158
+ if (deleteAccount.match(req.method, pathSegments)) {
159
+ const parsed = await deleteAccount.parse(req, res, pathSegments, queryParams);
160
+ if (!parsed) return true;
161
+ if (!(await ensureConfigAdmin(req, res, "config.delete.any"))) return true;
162
+ if (!(await ensureSecretRead(req, res))) return true;
163
+ const deletedKey = await getDbClient().transaction(async () => {
164
+ const rows = await accountRows();
165
+ const account = rows.find((row) => row.slot === parsed.params.slot);
166
+ if (!account || !(await deleteSwarmConfigByKey("global", null, account.key))) return null;
167
+ // A new sign-in takes the first free slot, so it must not inherit this
168
+ // account's auth bench (keyed by slot) or its bench marker.
169
+ const suffix = accountSuffix(account);
170
+ if (suffix) {
171
+ const remaining = rows.flatMap((row) => {
172
+ const keySuffix = row === account ? null : accountSuffix(row);
173
+ return keySuffix ? [{ keySuffix, keyIndex: row.slot }] : [];
174
+ });
175
+ await retirePoolKeyStatus("CODEX_OAUTH", suffix, remaining);
176
+ if (!remaining.some((row) => row.keySuffix === suffix)) {
177
+ await deleteKv(CODEX_AUTH_WATCH_NAMESPACE, codexAuthBenchMarkerKey(suffix));
178
+ }
179
+ }
180
+ return account.key;
181
+ });
182
+ if (deletedKey === null) {
183
+ jsonError(res, "Codex account slot does not exist", 404);
184
+ return true;
185
+ }
186
+ await runConfigWriteSideEffects("global", deletedKey, "");
187
+ deleteAccount.respond(res, 200, await accountsResponse());
188
+ return true;
189
+ }
190
+ return false;
191
+ }
package/src/http/comb.ts CHANGED
@@ -2,7 +2,12 @@ import type { IncomingMessage, ServerResponse } from "node:http";
2
2
  import { z } from "zod";
3
3
  import { resolveHttpAuditUserId } from "../be/audit-user";
4
4
  import { getCombConfig } from "../comb/config";
5
- import { REVIEW_BATCH_MAX, ReviewBatchError, sendReviewBatch } from "../comb/review-batch";
5
+ import {
6
+ REVIEW_BATCH_MAX,
7
+ REVIEW_NOTE_MAX,
8
+ ReviewBatchError,
9
+ sendReviewBatch,
10
+ } from "../comb/review-batch";
6
11
  import { can } from "../rbac";
7
12
  import { TaskCreationBlockedError } from "../tasks/errors";
8
13
  import { scrubSecrets } from "../utils/secret-scrubber";
@@ -24,6 +29,12 @@ const resultSchema = z.object({
24
29
  skipped: z.array(skippedSchema),
25
30
  /** Comments of an earlier send whose missing "sent" reply this batch posted again. */
26
31
  repaired: z.array(z.object({ id: z.string(), taskId: z.string() })),
32
+ /**
33
+ * With a new task only. `online`: a lead got it. `offline`: every lead is offline,
34
+ * so the task waits for one. `none`: no lead, so the task waits
35
+ * unassigned in the pool.
36
+ */
37
+ lead: z.enum(["online", "offline", "none"]).optional(),
27
38
  });
28
39
 
29
40
  const batchErrorSchema = z.object({ error: z.string(), skipped: z.array(skippedSchema) });
@@ -41,7 +52,7 @@ const reviewBatchRoute = route({
41
52
  pattern: ["api", "comb", "review-batches"],
42
53
  summary: "Send agent-fs comments to the swarm as one lead task",
43
54
  description:
44
- "Comb's 'Send to swarm'. The server reads each comment again from agent-fs with its bootstrap key, skips replies, resolved comments, and comments already sent, and creates ONE task for the lead. Each sent comment gets a `[comb:sent task=<id>]` reply from the swarm service account. A comment is sent at most once. When an earlier send lost its reply, the batch posts that reply again (`repaired`). Answers 404 while COMB_ENABLED is off.",
55
+ "Comb's 'Send to swarm'. The server reads each comment again from agent-fs with its bootstrap key, skips replies, resolved comments, and comments already sent, and creates ONE task for the lead. Each sent comment gets a `[comb:sent task=<id>]` reply from the swarm service account. A comment is sent at most once. An optional `note` from the sender goes into the task text. When an earlier send lost its reply, the batch posts that reply again (`repaired`). Answers 404 while COMB_ENABLED is off.",
45
56
  tags: ["Comb"],
46
57
  body: z.object({
47
58
  orgId: z.string().min(1),
@@ -54,6 +65,8 @@ const reviewBatchRoute = route({
54
65
  .min(1)
55
66
  .max(1024)
56
67
  .refine(isDrivePath, "scopePath must start with '/' and have no '.' or '..' segment"),
68
+ /** The sender's note for the lead about the whole batch. */
69
+ note: z.string().max(REVIEW_NOTE_MAX).optional(),
57
70
  }),
58
71
  responses: {
59
72
  200: {
@@ -67,7 +67,7 @@ export function readsRepoSecrets(
67
67
  }).allow;
68
68
  }
69
69
 
70
- async function requestSecretReadScope(
70
+ export async function requestSecretReadScope(
71
71
  req: IncomingMessage,
72
72
  resolvedScope?: { agentId?: string; repoId?: string },
73
73
  ): Promise<"all" | "self" | "none"> {
package/src/http/core.ts CHANGED
@@ -95,6 +95,12 @@ import {
95
95
  */
96
96
  const injectedEnvOriginals = new Map<string, { original: string | undefined; injected: string }>();
97
97
 
98
+ /** Read the deployment value without a stored config's temporary env injection. */
99
+ export function getDeploymentEnvValue(key: string): string | undefined {
100
+ const tracked = injectedEnvOriginals.get(key);
101
+ return tracked && process.env[key] === tracked.injected ? tracked.original : process.env[key];
102
+ }
103
+
98
104
  export async function loadGlobalConfigsIntoEnv(override = false): Promise<string[]> {
99
105
  const globalConfigs = await getInjectableGlobalConfigs();
100
106
  const updated: string[] = [];
@@ -11,7 +11,7 @@ import {
11
11
  localTasksPerWeek,
12
12
  } from "../fleet-benchmark";
13
13
  import { isTelemetryEnabled } from "../telemetry";
14
- import { COMPANY_TEAM_SIZES, COMPANY_USE_CASES } from "../utils/company-profile";
14
+ import { COMPANY_TEAM_SIZES, COMPANY_USE_CASES, reportedUseCase } from "../utils/company-profile";
15
15
  import { normalizeOrgWebsite } from "../utils/org-website";
16
16
  import { globalConfig } from "./global-config";
17
17
  import { rejectGuest } from "./request-principal";
@@ -188,6 +188,9 @@ export async function buildFleetBenchmarkResponse(
188
188
  normalizeOrgWebsite(website) && team.success && use.success
189
189
  ? { teamSize: team.data, useCase: use.data }
190
190
  : null;
191
+ // The proxy answers 400 for a use case its catalog does not list yet, so
192
+ // that use case compares against the whole fleet.
193
+ const proxyPeerGroup = peerGroup && reportedUseCase(peerGroup.useCase) ? peerGroup : null;
191
194
  const empty = {
192
195
  level: peerGroup ? ("full" as const) : ("basic" as const),
193
196
  peerGroup: null,
@@ -215,7 +218,7 @@ export async function buildFleetBenchmarkResponse(
215
218
  if (finishedTasks < FLEET_MIN_FINISHED_TASKS)
216
219
  return { ...empty, state: "not_enough_tasks", local };
217
220
 
218
- const fleet = await fleetBenchmarks(now.getTime(), peerGroup);
221
+ const fleet = await fleetBenchmarks(now.getTime(), proxyPeerGroup);
219
222
  const responsePeerGroup =
220
223
  peerGroup && fleet?.peer_group
221
224
  ? {
@@ -8,7 +8,7 @@ import { getRequestAuth } from "../utils/request-auth-context";
8
8
  import { jsonError } from "./utils";
9
9
 
10
10
  /** Visible ASCII, at most 255 characters: room for a UUID, a ULID or a client's own scheme. */
11
- const IDEMPOTENCY_KEY_PATTERN = /^[\x21-\x7E]{1,255}$/;
11
+ export const IDEMPOTENCY_KEY_PATTERN = /^[\x21-\x7E]{1,255}$/;
12
12
 
13
13
  /** Set on a response answered from an earlier accepted request. */
14
14
  export const IDEMPOTENT_REPLAYED_HEADER = "Idempotent-Replayed";
package/src/http/index.ts CHANGED
@@ -76,6 +76,7 @@ import { handleApprovalRequests } from "./approval-requests";
76
76
  import { handleApps } from "./apps";
77
77
  import { handleAssets } from "./assets";
78
78
  import { handleBudgets } from "./budgets";
79
+ import { handleCodexOAuthAccounts } from "./codex-oauth-accounts";
79
80
  import { handleCodexOAuthDevice } from "./codex-oauth-device";
80
81
  import { handleCodexOAuthKeepWarm } from "./codex-oauth-keep-warm";
81
82
  import { handleComb } from "./comb";
@@ -115,6 +116,7 @@ import { handlePageProxy } from "./page-proxy";
115
116
  import { handlePages } from "./pages";
116
117
  import { handlePagesPublic } from "./pages-public";
117
118
  import { handlePoll } from "./poll";
119
+ import { handlePoolKeys } from "./pool-keys";
118
120
  import { handlePricing } from "./pricing";
119
121
  import { handlePromptTemplates } from "./prompt-templates";
120
122
  import { handleRanking } from "./ranking";
@@ -360,6 +362,7 @@ const httpServer = createHttpServer(async (req, res) => {
360
362
  () => handleStatus(req, res, pathSegments, queryParams),
361
363
  () => handleOnboarding(req, res, pathSegments, queryParams),
362
364
  () => handleCodexOAuthDevice(req, res, pathSegments, queryParams),
365
+ () => handleCodexOAuthAccounts(req, res, pathSegments, queryParams),
363
366
  () => handleActiveSessions(req, res, pathSegments, queryParams, myAgentId),
364
367
  () => handlePricing(req, res, pathSegments, queryParams, myAgentId),
365
368
  () => handleSchedules(req, res, pathSegments, queryParams, myAgentId),
@@ -367,6 +370,7 @@ const httpServer = createHttpServer(async (req, res) => {
367
370
  () => handleWorkflowEvents(req, res, pathSegments, queryParams),
368
371
  () => handleApprovalRequests(req, res, pathSegments, queryParams),
369
372
  () => handleApps(req, res, pathSegments, queryParams, myAgentId),
373
+ () => handlePoolKeys(req, res, pathSegments, queryParams),
370
374
  () => handleConfig(req, res, pathSegments, queryParams),
371
375
  () => handleFs(req, res, pathSegments, queryParams, myAgentId),
372
376
  () => handleComb(req, res, pathSegments, queryParams, myAgentId),
@@ -513,7 +517,7 @@ async function shutdown() {
513
517
  await stopChatSdk();
514
518
 
515
519
  // Stop OAuth keepalive
516
- if (process.env.OAUTH_KEEPALIVE_DISABLE !== "true") {
520
+ if (!isEnvFlagEnabled("OAUTH_KEEPALIVE_DISABLE", false)) {
517
521
  const { stopOAuthKeepalive } = await import("../oauth/keepalive");
518
522
  await stopOAuthKeepalive();
519
523
  }
@@ -843,7 +847,7 @@ httpServer
843
847
  startRuntimeSupervisor();
844
848
 
845
849
  // Start OAuth token keepalive (proactive refresh to prevent expiry)
846
- if (process.env.OAUTH_KEEPALIVE_DISABLE !== "true") {
850
+ if (!isEnvFlagEnabled("OAUTH_KEEPALIVE_DISABLE", false)) {
847
851
  const { startOAuthKeepalive } = await import("../oauth/keepalive");
848
852
  startOAuthKeepalive();
849
853
  }
@@ -851,7 +855,7 @@ httpServer
851
855
  // Start generic OAuth token refresh sweep (all oauth_apps providers,
852
856
  // 15-min tick, first run ~1 min after boot). Complements the tracker-only
853
857
  // keepalive above — see src/be/oauth-refresh-sweep.ts.
854
- if (process.env.OAUTH_REFRESH_SWEEP_DISABLE !== "true") {
858
+ if (!isEnvFlagEnabled("OAUTH_REFRESH_SWEEP_DISABLE", false)) {
855
859
  const { startOAuthRefreshSweep } = await import("../be/oauth-refresh-sweep");
856
860
  startOAuthRefreshSweep();
857
861
  }
@@ -898,6 +902,13 @@ httpServer
898
902
  .catch((err) => {
899
903
  console.error("[boot-scrub-sweep] startup scrub failed (non-fatal):", err);
900
904
  });
905
+
906
+ // Source key backfill is idempotent, resumable, and non-fatal.
907
+ import("../be/session-source-backfill")
908
+ .then(({ runSessionSourceBackfill }) => runSessionSourceBackfill())
909
+ .catch((err) => {
910
+ console.error("[session-source-backfill] startup run failed (non-fatal):", err);
911
+ });
901
912
  })
902
913
  .on("error", (err) => {
903
914
  console.error("HTTP Server Error:", err);
@@ -8,6 +8,7 @@ import {
8
8
  registerTelegramWebhook,
9
9
  } from "../integrations/chat-sdk/status";
10
10
  import { buildSlackManifest } from "../slack/manifest";
11
+ import { selectCredential } from "../utils/credentials";
11
12
  import { ensureHttpPermission } from "./config";
12
13
  import { resolveConfigValue, resolveMcpBaseUrl } from "./config-values";
13
14
  import { route } from "./route-def";
@@ -231,7 +232,10 @@ export function createIntegrationsHandler(deps: TestConnectionDeps = {}) {
231
232
  }
232
233
 
233
234
  try {
234
- const client = buildClient(apiKey);
235
+ // A comma pool is one key per call, as the runner does.
236
+ const client = buildClient(
237
+ selectCredential(apiKey, undefined, "ANTHROPIC_API_KEY").selected,
238
+ );
235
239
  const agent = await client.beta.agents.retrieve(agentId);
236
240
  // `agent.model` is `BetaManagedAgentsModelConfig` ({id, speed}). Flatten
237
241
  // to a string so the UI can render it directly without type guards.
package/src/http/kv.ts CHANGED
@@ -12,7 +12,11 @@ import {
12
12
  upsertKv,
13
13
  } from "../be/db";
14
14
  import { mcpOverflowAuthError } from "../kv-overflow";
15
- import { reservedNamespaceError, reservedRoomKeyError } from "../kv-reserved-namespaces";
15
+ import {
16
+ privateNamespaceError,
17
+ reservedNamespaceError,
18
+ reservedRoomKeyError,
19
+ } from "../kv-reserved-namespaces";
16
20
  import { hasKvViewArgs, resolveKvView } from "../kv-view";
17
21
  import { can } from "../rbac";
18
22
  import { agentContextKey, pageContextKey } from "../tasks/context-key";
@@ -405,8 +409,9 @@ function authorizeRead(
405
409
  namespace: string,
406
410
  ctx: AuthCtx,
407
411
  ): { status: number; message: string } | null {
408
- const overflowAuthErr = mcpOverflowAuthError(namespace, ctx.callerAgentId);
409
- return overflowAuthErr ? { status: 403, message: overflowAuthErr } : null;
412
+ const readErr =
413
+ privateNamespaceError(namespace) ?? mcpOverflowAuthError(namespace, ctx.callerAgentId);
414
+ return readErr ? { status: 403, message: readErr } : null;
410
415
  }
411
416
 
412
417
  function encodeValueOrError(
@@ -21,6 +21,7 @@ import {
21
21
  } from "../be/onboarding";
22
22
  import { assertUrlSafe, publicEndpointSsrfOptions } from "../oauth/mcp-wrapper";
23
23
  import { ProviderNameSchema } from "../types";
24
+ import { splitCredentialPool } from "../utils/credentials";
24
25
  import { getOpenRouterAttributionHeaders } from "../utils/openrouter-base-url";
25
26
  import { ensureConfigAdmin } from "./config";
26
27
  import { scheduleIntegrationsReload } from "./core";
@@ -36,11 +37,14 @@ async function buildSignals(state: OnboardingState): Promise<OnboardingSignals>
36
37
  const provider = ProviderNameSchema.safeParse(entry.provider);
37
38
  if (!provider.success) continue;
38
39
  const rollup = await rollupCredStatusForProvider(provider.data);
40
+ const live = rollup.latestLiveTest;
39
41
  providers.push({
40
42
  provider: provider.data,
41
43
  state: rollup.state,
42
44
  workers: rollup.workers,
43
45
  verifiedWorkers: rollup.verifiedWorkers,
46
+ error: live && !live.ok ? (live.error ?? null) : null,
47
+ warning: live?.ok ? (live.warning ?? null) : null,
44
48
  });
45
49
  }
46
50
 
@@ -241,7 +245,8 @@ async function handleMemoryProbe(body: OnboardingMemoryRequest): Promise<Onboard
241
245
  body.preset === "existing"
242
246
  ? (process.env.EMBEDDING_API_KEY ?? process.env.OPENAI_API_KEY)
243
247
  : undefined;
244
- const apiKey = explicitKey ?? reusedKey ?? implicitKey;
248
+ // Test and save the same pool member.
249
+ const apiKey = splitCredentialPool(explicitKey ?? reusedKey ?? implicitKey ?? "")[0];
245
250
 
246
251
  if (!apiKey) {
247
252
  return {
@@ -308,13 +308,45 @@ function getAppBaseUrl(): string {
308
308
  return getAppUrl(LOCAL_PAGE_APP_URL);
309
309
  }
310
310
 
311
+ // CSP host-source without a path: optional http(s) scheme, a host that may
312
+ // start with a `*.` wildcard label, and an optional port (digits or `*`).
313
+ // Bare `*`, keywords (`'none'`, `'self'`) and anything carrying `;` or a path
314
+ // are rejected so a bad env value cannot widen or break the policy.
315
+ const FRAME_ANCESTOR_SOURCE_RE =
316
+ /^(?:https?:\/\/)?(?:\*\.)?[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)*(?::(?:\d{1,5}|\*))?$/i;
317
+
318
+ let cachedFrameAncestors: { raw: string; origins: string[] } | null = null;
319
+
320
+ /**
321
+ * Parse `PAGES_FRAME_ANCESTORS`: extra origins allowed to iframe PUBLIC pages
322
+ * (for example a marketing site embedding a chart). Comma or space separated.
323
+ * Invalid entries are dropped with one warning per distinct env value; the
324
+ * parse is cached on the raw string so a config reload picks up changes
325
+ * without re-warning on every request.
326
+ */
327
+ export function getPageFrameAncestors(): string[] {
328
+ const raw = process.env.PAGES_FRAME_ANCESTORS ?? "";
329
+ if (cachedFrameAncestors?.raw === raw) return cachedFrameAncestors.origins;
330
+ const origins: string[] = [];
331
+ for (const entry of raw.split(/[\s,]+/)) {
332
+ const value = entry.replace(/\/+$/, "");
333
+ if (!value) continue;
334
+ if (FRAME_ANCESTOR_SOURCE_RE.test(value)) origins.push(value);
335
+ else console.warn(`[pages] ignoring invalid PAGES_FRAME_ANCESTORS entry: ${entry}`);
336
+ }
337
+ cachedFrameAncestors = { raw, origins };
338
+ return origins;
339
+ }
340
+
311
341
  /**
312
342
  * Build the `Content-Security-Policy` for the served HTML. Allows inline
313
343
  * scripts (required for `BROWSER_SDK_JS`) but locks down everything else to
314
344
  * `'self'`. The SPA iframes the page in step-6 with `sandbox="allow-scripts
315
- * allow-forms"`; the CSP is a defence-in-depth layer.
345
+ * allow-forms"`; the CSP is a defence-in-depth layer. `PAGES_FRAME_ANCESTORS`
346
+ * applies only to public pages: authed and password pages stay embeddable by
347
+ * the app origins alone.
316
348
  */
317
- function buildCsp(): string {
349
+ function buildCsp(page: Page): string {
318
350
  // `frame-ancestors` lists every origin allowed to iframe `/p/:id`. We must
319
351
  // include the SPA origin(s). Configured app URLs may be comma-separated so
320
352
  // portless dev (`https://ui.swarm.localhost`), a Vite port (`http://localhost:5274`),
@@ -333,7 +365,8 @@ function buildCsp(): string {
333
365
  "https://*.localhost",
334
366
  "http://*.localhost",
335
367
  ];
336
- const ancestors = Array.from(new Set([...configured, ...devFallbacks]));
368
+ const embedders = page.authMode === "public" ? getPageFrameAncestors() : [];
369
+ const ancestors = Array.from(new Set([...configured, ...devFallbacks, ...embedders]));
337
370
  // Allow Tailwind Play CDN (`cdn.tailwindcss.com`) for scripts, Google
338
371
  // Fonts (`fonts.googleapis.com` stylesheets + `fonts.gstatic.com` font
339
372
  // files) for the swarm default typography, and same-origin /@swarm/api/*
@@ -609,7 +642,7 @@ export async function handlePagesPublic(
609
642
  const headers: Record<string, string | string[]> = {
610
643
  "Content-Type": "text/html; charset=utf-8",
611
644
  "Cache-Control": "no-store",
612
- "Content-Security-Policy": buildCsp(),
645
+ "Content-Security-Policy": buildCsp(page),
613
646
  "X-Content-Type-Options": "nosniff",
614
647
  };
615
648
  if (inlineSetCookie.length > 0) headers["Set-Cookie"] = inlineSetCookie;
@@ -642,7 +675,7 @@ export async function handlePagesPublic(
642
675
  const headers: Record<string, string | string[]> = {
643
676
  "Content-Type": "text/html; charset=utf-8",
644
677
  "Cache-Control": "no-store",
645
- "Content-Security-Policy": buildCsp(),
678
+ "Content-Security-Policy": buildCsp(page),
646
679
  // Defence-in-depth: prevent MIME sniffing and clickjacking outside the SPA.
647
680
  "X-Content-Type-Options": "nosniff",
648
681
  };