@desplega.ai/agent-swarm 1.173.0 → 2.1.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 (511) hide show
  1. package/README.md +1 -1
  2. package/dist/{acp-adapter-ccm9xrep.js → acp-adapter-kr4abrsk.js} +17 -16
  3. package/dist/{actions-sgeyv08k.js → actions-eh4g6w2r.js} +39 -34
  4. package/dist/{amp-adapter-gpxp3kc9.js → amp-adapter-7h7wdt8q.js} +8 -7
  5. package/dist/{app-km9qnv2f.js → app-7k8s9w5p.js} +18 -14
  6. package/dist/{assistant-5r8tcvvd.js → assistant-wswt2y7p.js} +40 -35
  7. package/dist/{boot-reembed-16rv9843.js → boot-reembed-9apm9qw6.js} +10 -9
  8. package/dist/{boot-reembed-swwzdgd7.js → boot-reembed-whzbdthj.js} +10 -9
  9. package/dist/{boot-scrub-sweep-zkr6vkqb.js → boot-scrub-sweep-ndzmvmgt.js} +11 -10
  10. package/dist/{boot-seeds-d8sp49t2.js → boot-seeds-e9sta0yt.js} +16 -13
  11. package/dist/{claude-adapter-yzzzh8jp.js → claude-adapter-4mqd3r6r.js} +19 -18
  12. package/dist/{claude-managed-adapter-xgxhqrt2.js → claude-managed-adapter-9wswb52m.js} +7 -5
  13. package/dist/{claude-managed-setup-bnmjarjc.js → claude-managed-setup-tpe1effx.js} +6 -4
  14. package/dist/{claude-sdk-session-4whxere4.js → claude-sdk-session-q49ghmh9.js} +12402 -11850
  15. package/dist/{cli-ne7smj1b.js → cli-01xvj1rq.js} +10 -10
  16. package/dist/{cli-nwrdx840.js → cli-035vekcw.js} +3 -3
  17. package/dist/{cli-9xf7fdtp.js → cli-0pd8hzhc.js} +29 -6
  18. package/dist/{cli-tmhsxhb8.js → cli-0veb92a3.js} +28 -6
  19. package/dist/{cli-c3rk1v0x.js → cli-10jesr25.js} +3 -3
  20. package/dist/{cli-6thk4jz3.js → cli-1y7yywmj.js} +6 -6
  21. package/dist/{cli-6r2f7edk.js → cli-20krdhz5.js} +47 -19
  22. package/dist/{cli-rfs4vddv.js → cli-29rja109.js} +39 -20
  23. package/dist/cli-2m1ktfzz.js +17795 -0
  24. package/dist/{cli-na2pxe4f.js → cli-2pwa0q4d.js} +15 -4
  25. package/dist/{cli-7a93cv8c.js → cli-4mengw46.js} +441 -43
  26. package/dist/{cli-94b48jhk.js → cli-5twdke5x.js} +16 -14
  27. package/dist/{cli-7a0sde2x.js → cli-6cagzc96.js} +9 -0
  28. package/dist/cli-6cvfxs8r.js +47 -0
  29. package/dist/{cli-w1apjecb.js → cli-711vwbxn.js} +2 -2
  30. package/dist/{cli-r4fqfpwf.js → cli-78vv7446.js} +50 -27
  31. package/dist/{cli-srqq3gc5.js → cli-7n6s5man.js} +5 -5
  32. package/dist/{cli-m89t9jdf.js → cli-7x0g4sn1.js} +15416 -16024
  33. package/dist/{cli-2bw3w47j.js → cli-8wwxw8a3.js} +1 -1
  34. package/dist/{cli-yatxqynm.js → cli-9ha6hs2d.js} +1 -290
  35. package/dist/{cli-ywm1azg9.js → cli-9jzdj90z.js} +8 -4
  36. package/dist/cli-9traj3dn.js +638 -0
  37. package/dist/{cli-smcrrmhy.js → cli-agzebbd5.js} +9 -9
  38. package/dist/{cli-c5km5znr.js → cli-c8r3kzzb.js} +1 -1
  39. package/dist/cli-cvv9k0hh.js +10143 -0
  40. package/dist/{cli-anzdk0z4.js → cli-d9286m9h.js} +2 -2
  41. package/dist/cli-e3g5mbkz.js +776 -0
  42. package/dist/cli-f527ce74.js +781 -0
  43. package/dist/{cli-gmdajfw5.js → cli-gxzkg1de.js} +5020 -1729
  44. package/dist/{cli-451ks6aw.js → cli-h1arqn31.js} +1 -1
  45. package/dist/cli-hjwmhcg1.js +10622 -0
  46. package/dist/{cli-jpc4r224.js → cli-hqr88ew6.js} +19 -3
  47. package/dist/{cli-h7k0xtry.js → cli-hvqbtna4.js} +4 -1256
  48. package/dist/{cli-cd0hv9nh.js → cli-j67mh2vg.js} +2 -2
  49. package/dist/cli-jta2eyzc.js +619 -0
  50. package/dist/{cli-wd0hz6ah.js → cli-kbrt8gqa.js} +9 -91
  51. package/dist/cli-kceah1eh.js +92 -0
  52. package/dist/{cli-d603qf86.js → cli-ksphgh48.js} +10 -8
  53. package/dist/{cli-3nxdmjrc.js → cli-mgz4nbgg.js} +4 -4
  54. package/dist/{cli-saqyage0.js → cli-mz5as5ha.js} +179 -27
  55. package/dist/cli-n2wq4j7m.js +6985 -0
  56. package/dist/{cli-ppy4nnnh.js → cli-n8ndw8j7.js} +2 -2
  57. package/dist/{cli-1w0vj3gw.js → cli-naqzr17j.js} +7 -7
  58. package/dist/{cli-0a7mm3zp.js → cli-nm1p8jfr.js} +25 -21
  59. package/dist/{cli-wrh08zjd.js → cli-p3zbc1rw.js} +5 -18
  60. package/dist/{cli-hsem16hb.js → cli-pe05f8yk.js} +29 -6
  61. package/dist/{cli-5wpw4rab.js → cli-qmzsbkvy.js} +5 -5
  62. package/dist/{cli-bv2etw2y.js → cli-qnm4e9e4.js} +99 -590
  63. package/dist/{cli-1x7da0de.js → cli-qqy4q48b.js} +8 -8
  64. package/dist/{cli-r44t9wqc.js → cli-r106725k.js} +2 -2
  65. package/dist/{cli-5er5gjbm.js → cli-r35rw5de.js} +15 -15
  66. package/dist/{cli-v3zhnxsb.js → cli-r610p9bs.js} +2 -2
  67. package/dist/{cli-7jykv7ef.js → cli-rc6pf5kv.js} +1 -1
  68. package/dist/{cli-x119kdba.js → cli-rdxm77k3.js} +1 -1
  69. package/dist/cli-rhs9pz33.js +294 -0
  70. package/dist/{cli-0p3enk35.js → cli-s69nfyeh.js} +9 -1
  71. package/dist/{cli-0kxf81jj.js → cli-sawv63e8.js} +2 -2
  72. package/dist/{cli-e3pmyt39.js → cli-sg19gbf0.js} +1 -1
  73. package/dist/{cli-d161g69q.js → cli-shk7r5cs.js} +57 -6
  74. package/dist/{cli-7pz3b7x5.js → cli-sksetb2q.js} +36 -1
  75. package/dist/{cli-014vns2y.js → cli-stfs0k5s.js} +208 -525
  76. package/dist/{cli-db3m6zdt.js → cli-t02z59f2.js} +1 -1
  77. package/dist/{cli-b07hr3bt.js → cli-txqb9s38.js} +2 -2
  78. package/dist/{cli-s8z6x4b4.js → cli-v7g551te.js} +225 -113
  79. package/dist/{cli-rfz2ve55.js → cli-vgqzp4em.js} +3341 -955
  80. package/dist/{cli-7avy4v8w.js → cli-w1q7h2r2.js} +5 -3
  81. package/dist/{cli-rcp5aqdw.js → cli-w7m81tb7.js} +1 -1
  82. package/dist/cli-wfnemr1t.js +86 -0
  83. package/dist/cli-wmgrkscj.js +23254 -0
  84. package/dist/{cli-qm1448f9.js → cli-wms6x9db.js} +8 -8
  85. package/dist/{cli-tadfgd0t.js → cli-x1r545x8.js} +33 -4
  86. package/dist/{cli-xc7p7s3g.js → cli-xczsgshn.js} +6 -6
  87. package/dist/{cli-fmhj1ehj.js → cli-y3167wt1.js} +882 -271
  88. package/dist/{cli-r0byd61h.js → cli-yjmqpxt3.js} +17 -16
  89. package/dist/{cli-81r1wjyv.js → cli-z780ec7c.js} +1 -1
  90. package/dist/{cli-phcqp1b9.js → cli-zm9t2vde.js} +2 -2
  91. package/dist/cli.js +34 -49
  92. package/dist/{codex-adapter-r3dn4fp5.js → codex-adapter-4z16v9n0.js} +15 -14
  93. package/dist/{codex-login-841kv5fk.js → codex-login-pkepy2j1.js} +3 -2
  94. package/dist/{codex-session-runner-fwt793h4.js → codex-session-runner-rvms9y2x.js} +15 -14
  95. package/dist/{commands-z8fq9qna.js → commands-2a9rtyat.js} +11 -10
  96. package/dist/{controller-hq5ggpcp.js → controller-3k3madbn.js} +5 -4
  97. package/dist/{controllers-connect-3mf2btyj.js → controllers-connect-v9yn8zb9.js} +42 -37
  98. package/dist/{cursor-adapter-q8pgv5qa.js → cursor-adapter-98gbhyzn.js} +7 -6
  99. package/dist/{db-qfpq4bt7.js → db-eywwsnjw.js} +30 -9
  100. package/dist/{db-yyhqqrbf.js → db-ncypk9np.js} +12 -11
  101. package/dist/{devin-adapter-xgtj680j.js → devin-adapter-zeq8b9gz.js} +3 -2
  102. package/dist/{dispatcher-x0x7qfcq.js → dispatcher-w94kc575.js} +38 -33
  103. package/dist/{docker-f503k9p0.js → docker-b1e4cqab.js} +3 -2
  104. package/dist/{dsh-adapter-34etvb5m.js → dsh-adapter-47ztej36.js} +7 -6
  105. package/dist/{e2b-66xra4az.js → e2b-bbt8ey45.js} +4 -3
  106. package/dist/{e2b-xwhyj8cw.js → e2b-dp804sen.js} +4 -3
  107. package/dist/{event-bus-vjtv82gm.js → event-bus-j2jdg3qb.js} +3 -2
  108. package/dist/{external-channel-h1fjecwe.js → external-channel-zrkg8ar9.js} +11 -10
  109. package/dist/{google-generative-ai-szdxbpqg.js → google-generative-ai-g8db7x0f.js} +4 -2
  110. package/dist/{google-vertex-qzw3p36d.js → google-vertex-r2692q0r.js} +4 -2
  111. package/dist/{grok-adapter-e7mr287e.js → grok-adapter-s9ncp9vy.js} +18 -17
  112. package/dist/{handlers-znb9n7d4.js → handlers-kqftvjgw.js} +41 -36
  113. package/dist/{heartbeat-bm4qkn8d.js → heartbeat-9q96p8v9.js} +43 -38
  114. package/dist/{hook-254r4mza.js → hook-56qe10dp.js} +11 -23
  115. package/dist/{hook-xaa3q80s.js → hook-yy6rw42k.js} +12 -11
  116. package/dist/{http-3dgrgp8y.js → http-r2dnb8f8.js} +2411 -275
  117. package/dist/{index-fx3jqq1e.js → index-3gtmkhc6.js} +38 -33
  118. package/dist/{index-bdsqa799.js → index-3k5hkz7r.js} +43 -38
  119. package/dist/index-52j1aja4.js +3465 -0
  120. package/dist/{index-g2byveez.js → index-5e101240.js} +38 -33
  121. package/dist/{index-2ybk7khy.js → index-5wyfjc2c.js} +845 -683
  122. package/dist/index-9gw58n4k.js +193 -0
  123. package/dist/index-9j87mcm8.js +56305 -0
  124. package/dist/index-bsmcresk.js +79424 -0
  125. package/dist/index-mh85qhec.js +475 -0
  126. package/dist/{index-jd7sejm2.js → index-vxvhxxbn.js} +10 -9
  127. package/dist/index-we27kd31.js +12810 -0
  128. package/dist/{keepalive-6ywhpja1.js → keepalive-psj6j54c.js} +12 -11
  129. package/dist/{lead-cw45n21s.js → lead-hajz4688.js} +61 -56
  130. package/dist/{maintenance-ss1a1ge9.js → maintenance-ps02sb5m.js} +10 -9
  131. package/dist/{node-fkajaq96.js → node-mwb5r697.js} +2 -1
  132. package/dist/{oauth-refresh-sweep-3c6f8vyy.js → oauth-refresh-sweep-0284h93b.js} +11 -10
  133. package/dist/{onboard-8hzdhc1k.js → onboard-rbf6bg01.js} +11 -3
  134. package/dist/{opencode-adapter-qbchbcfz.js → opencode-adapter-13dcatgq.js} +18 -13
  135. package/dist/{otel-impl-e6mnvc4v.js → otel-impl-zvqcbhp7.js} +242 -868
  136. package/dist/{pi-mono-adapter-3c9g9snh.js → pi-mono-adapter-xxy47q4v.js} +244 -106
  137. package/dist/{pricing-refresh-gedkjdw0.js → pricing-refresh-kefx9v3t.js} +12 -11
  138. package/dist/{rbac-roles-dt4vh1yy.js → rbac-roles-3m36rxm2.js} +11 -10
  139. package/dist/{rbac-roles-eszdg6pm.js → rbac-roles-z76xv0hq.js} +12 -11
  140. package/dist/{render-v2-907cy6sa.js → render-v2-wz73bs25.js} +18 -14
  141. package/dist/{resume-42stcaz0.js → resume-k4wzge8w.js} +25 -21
  142. package/dist/{runtime-qfry88v2.js → runtime-sa2vr16g.js} +2 -2
  143. package/dist/{setup-adbc1c10.js → setup-2fp2pypp.js} +2 -2
  144. package/dist/{virtual-modules-rtmt494q.js → virtual-modules-9gkxqrap.js} +6 -5
  145. package/dist/{work-objects-wpj8ck95.js → work-objects-6k0ywz3m.js} +41 -36
  146. package/dist/{worker-bzdra9rf.js → worker-5v3rxsdh.js} +61 -56
  147. package/dist/{x-qfmygqqv.js → x-4mtdeym1.js} +3 -2
  148. package/openapi.json +1279 -329
  149. package/package.json +15 -15
  150. package/src/agentmail/handlers.ts +21 -11
  151. package/src/apps/definition.ts +16 -0
  152. package/src/azure-devops/api.ts +52 -2
  153. package/src/azure-devops/boards-sync.ts +50 -20
  154. package/src/azure-devops/index.ts +1 -0
  155. package/src/azure-devops/outbound.ts +95 -0
  156. package/src/be/boot-seeds.ts +11 -0
  157. package/src/be/config-refs.ts +60 -0
  158. package/src/be/db/model-profiles.ts +315 -0
  159. package/src/be/db/page-versions.ts +15 -0
  160. package/src/be/db/runtime.ts +35 -8
  161. package/src/be/db/skill-name-resolution.ts +36 -0
  162. package/src/be/db/tasks/read.ts +237 -0
  163. package/src/be/db/tasks/write.ts +7 -0
  164. package/src/be/db.ts +87 -36
  165. package/src/be/migrations/215_swarm_config_export_to_env.sql +34 -0
  166. package/src/be/migrations/216_task_resolved_effort.sql +5 -0
  167. package/src/be/migrations/217_chat_sdk_state.sql +104 -0
  168. package/src/be/migrations/219_relabel_pi_context_compactions.sql +22 -0
  169. package/src/be/migrations/220_model_profiles.sql +137 -0
  170. package/src/be/model-catalog-store.ts +5 -1
  171. package/src/be/model-tier-resolution.ts +155 -10
  172. package/src/be/model-validation.ts +12 -2
  173. package/src/be/models-catalog.ts +26 -1
  174. package/src/be/modelsdev-cache.json +198 -63
  175. package/src/be/modelsdev-cache.ts +9 -1
  176. package/src/be/script-credential-broker.ts +39 -2
  177. package/src/be/scripts/run-saved.ts +1 -0
  178. package/src/be/scripts/typecheck.ts +22 -8
  179. package/src/be/secret-registry.ts +6 -2
  180. package/src/be/seed/runner.ts +13 -1
  181. package/src/be/seed/state-db.ts +5 -0
  182. package/src/be/seed/types.ts +6 -0
  183. package/src/be/seed-model-profiles.ts +41 -0
  184. package/src/be/seed-pricing.ts +19 -2
  185. package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +368 -0
  186. package/src/be/seed-scripts/catalog/ops-catalog-audit.inline.ts +1 -1
  187. package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +1 -1
  188. package/src/be/seed-scripts/index.ts +9 -0
  189. package/src/be/seed-skills/bundled-files.generated.json +0 -18
  190. package/src/be/seed-skills/index.ts +104 -3
  191. package/src/be/swarm-config-guard.ts +53 -0
  192. package/src/cli.tsx +0 -16
  193. package/src/commands/onboard/non-interactive.ts +4 -2
  194. package/src/commands/provider-credentials.ts +52 -6
  195. package/src/commands/runner.ts +193 -71
  196. package/src/e2b/env.ts +5 -0
  197. package/src/extensions/catalog.generated.json +17 -0
  198. package/src/extensions/contract-types.generated.ts +1 -1
  199. package/src/extensions/contract.ts +45 -0
  200. package/src/extensions/dispatcher.ts +7 -0
  201. package/src/extensions/loader.ts +10 -5
  202. package/src/extensions/post-bridge.ts +40 -0
  203. package/src/github/handlers.ts +11 -4
  204. package/src/heartbeat/heartbeat.ts +2 -1
  205. package/src/heartbeat/templates.ts +8 -5
  206. package/src/hooks/hook.ts +0 -26
  207. package/src/http/agents.ts +49 -2
  208. package/src/http/all-routes.ts +1 -1
  209. package/src/http/apps.ts +30 -3
  210. package/src/http/config.ts +62 -19
  211. package/src/http/core.ts +8 -0
  212. package/src/http/index.ts +25 -3
  213. package/src/http/integrations.ts +88 -1
  214. package/src/http/mcp.ts +15 -5
  215. package/src/http/model-profiles.ts +303 -0
  216. package/src/http/page-proxy.ts +2 -4
  217. package/src/http/pages-public.ts +13 -7
  218. package/src/http/pages.ts +3 -13
  219. package/src/http/poll.ts +42 -71
  220. package/src/http/requested-by.ts +54 -0
  221. package/src/http/script-runs.ts +1 -1
  222. package/src/http/scripts.ts +26 -2
  223. package/src/http/stats.ts +1 -30
  224. package/src/http/tasks.ts +79 -5
  225. package/src/http/utils.ts +14 -0
  226. package/src/http/webhooks.ts +160 -3
  227. package/src/http/x.ts +1 -0
  228. package/src/integrations/chat-sdk/adapters.ts +129 -0
  229. package/src/integrations/chat-sdk/attachments.ts +232 -0
  230. package/src/integrations/chat-sdk/background.ts +36 -0
  231. package/src/integrations/chat-sdk/commands.ts +194 -0
  232. package/src/integrations/chat-sdk/config.ts +198 -0
  233. package/src/integrations/chat-sdk/discord-api.ts +63 -0
  234. package/src/integrations/chat-sdk/discord-gateway.ts +185 -0
  235. package/src/integrations/chat-sdk/handlers.ts +28 -0
  236. package/src/integrations/chat-sdk/inbound.ts +426 -0
  237. package/src/integrations/chat-sdk/limits.ts +48 -0
  238. package/src/integrations/chat-sdk/logger.ts +81 -0
  239. package/src/integrations/chat-sdk/outbound.ts +123 -0
  240. package/src/integrations/chat-sdk/relay.ts +172 -0
  241. package/src/integrations/chat-sdk/router.ts +169 -0
  242. package/src/integrations/chat-sdk/runtime.ts +351 -0
  243. package/src/integrations/chat-sdk/schema.ts +245 -0
  244. package/src/integrations/chat-sdk/state.ts +243 -0
  245. package/src/integrations/chat-sdk/status.ts +244 -0
  246. package/src/integrations/chat-sdk/steering.ts +58 -0
  247. package/src/integrations/chat-sdk/teams-origin.ts +77 -0
  248. package/src/integrations/chat-sdk/telegram-api.ts +56 -0
  249. package/src/integrations/chat-sdk/templates.ts +16 -0
  250. package/src/integrations/chat-sdk/test-hooks.ts +37 -0
  251. package/src/integrations/chat-sdk/thread-buffer.ts +100 -0
  252. package/src/integrations/chat-sdk/typing.ts +65 -0
  253. package/src/{artifact-sdk → pages}/feedback-overlay.ts +65 -6
  254. package/src/pages/version.ts +4 -5
  255. package/src/prompts/base-prompt.ts +111 -45
  256. package/src/providers/acp-target-catalog.ts +6 -0
  257. package/src/providers/acp-targets.ts +56 -3
  258. package/src/providers/amp-adapter.ts +1 -1
  259. package/src/providers/claude-adapter.ts +1 -1
  260. package/src/providers/claude-flag-settings.ts +17 -5
  261. package/src/providers/claude-managed-adapter.ts +1 -1
  262. package/src/providers/claude-session-events.ts +6 -1
  263. package/src/providers/dsh-adapter.ts +1 -1
  264. package/src/providers/opencode-adapter.ts +243 -26
  265. package/src/providers/opencode-swarm-plugin.ts +31 -0
  266. package/src/providers/pi-mono-adapter.ts +398 -153
  267. package/src/providers/pi-mono-extension.ts +17 -3
  268. package/src/providers/pricing-sources.md +33 -2
  269. package/src/providers/reasoning-effort.ts +37 -0
  270. package/src/providers/types.ts +6 -2
  271. package/src/rbac/legacy-policy.ts +16 -2
  272. package/src/rbac/permissions.ts +26 -0
  273. package/src/scheduler/deferred-task-waits.ts +18 -5
  274. package/src/scheduler/scheduler.ts +11 -3
  275. package/src/scripts-runtime/executors/quickjs-prelude.ts +75 -6
  276. package/src/scripts-runtime/executors/quickjs-runner.ts +85 -8
  277. package/src/scripts-runtime/executors/quickjs-web-shims.ts +1041 -0
  278. package/src/scripts-runtime/extract-args-schema.ts +9 -8
  279. package/src/scripts-runtime/loader.ts +19 -7
  280. package/src/scripts-runtime/quickjs-compat.ts +464 -0
  281. package/src/scripts-runtime/sdk-allowlist.ts +12 -6
  282. package/src/scripts-runtime/swarm-sdk.ts +0 -11
  283. package/src/scripts-runtime/types/stdlib.d.ts +39 -15
  284. package/src/scripts-runtime/types/swarm-extension.d.ts +66 -1
  285. package/src/scripts-runtime/types/swarm-sdk.d.ts +39 -15
  286. package/src/server.ts +62 -19
  287. package/src/slack/blocks.ts +4 -2
  288. package/src/slack/event-dedup.ts +1 -1
  289. package/src/slack/reaction-shortcode.ts +14 -0
  290. package/src/slack/render-v2.ts +12 -7
  291. package/src/slack/responses.ts +5 -4
  292. package/src/slack/thread-buffer.ts +41 -22
  293. package/src/tasks/additive-buffer.ts +4 -2
  294. package/src/tasks/context-key.ts +60 -1
  295. package/src/tasks/model-pin.ts +67 -0
  296. package/src/tasks/worker-follow-up.ts +15 -33
  297. package/src/tests/acp-adapter.test.ts +42 -0
  298. package/src/tests/agentmail-filters.test.ts +47 -0
  299. package/src/tests/api-drain-worker-e2e.test.ts +1 -6
  300. package/src/tests/apps-spike2.test.ts +35 -0
  301. package/src/tests/azure-devops-outbound.test.ts +420 -0
  302. package/src/tests/base-prompt.test.ts +58 -54
  303. package/src/tests/chat-sdk-commands.test.ts +191 -0
  304. package/src/tests/chat-sdk-config.test.ts +334 -0
  305. package/src/tests/chat-sdk-discord-api.test.ts +68 -0
  306. package/src/tests/chat-sdk-discord-gateway.test.ts +252 -0
  307. package/src/tests/chat-sdk-discord-webhook.test.ts +126 -0
  308. package/src/tests/chat-sdk-fields-sync.test.ts +50 -0
  309. package/src/tests/chat-sdk-harness.ts +146 -0
  310. package/src/tests/chat-sdk-inbound.test.ts +363 -0
  311. package/src/tests/chat-sdk-relay.test.ts +470 -0
  312. package/src/tests/chat-sdk-reload.test.ts +128 -0
  313. package/src/tests/chat-sdk-shutdown.test.ts +220 -0
  314. package/src/tests/chat-sdk-slack.test.ts +268 -0
  315. package/src/tests/chat-sdk-state.test.ts +163 -0
  316. package/src/tests/chat-sdk-status-route.test.ts +382 -0
  317. package/src/tests/chat-sdk-teams.test.ts +277 -0
  318. package/src/tests/chat-sdk-tools.test.ts +520 -0
  319. package/src/tests/chat-sdk-typing.test.ts +120 -0
  320. package/src/tests/chat-sdk-webhook-telegram.test.ts +200 -0
  321. package/src/tests/claude-adapter-binary.test.ts +9 -4
  322. package/src/tests/claude-flag-settings.test.ts +11 -3
  323. package/src/tests/claude-managed-adapter.test.ts +1 -1
  324. package/src/tests/claude-sdk-lifecycle.test.ts +9 -3
  325. package/src/tests/claude-thinking-tokens.test.ts +57 -0
  326. package/src/tests/claude-worker-parity.test.ts +2 -7
  327. package/src/tests/config-env-export-chat-sdk.test.ts +11 -0
  328. package/src/tests/config-refs.test.ts +66 -0
  329. package/src/tests/context-key.test.ts +19 -0
  330. package/src/tests/cors-allowlist.test.ts +25 -0
  331. package/src/tests/create-page-tool.test.ts +18 -0
  332. package/src/tests/credential-check.test.ts +139 -0
  333. package/src/tests/credentials.test.ts +21 -0
  334. package/src/tests/db-runtime.test.ts +61 -0
  335. package/src/tests/db-task-read-contract.test.ts +22 -0
  336. package/src/tests/db-task-write-contract.test.ts +11 -0
  337. package/src/tests/deferred-task-wake.test.ts +78 -1
  338. package/src/tests/docs-index-footprint.test.js +3 -3
  339. package/src/tests/dsh-adapter.test.ts +1 -1
  340. package/src/tests/enabled-capabilities.test.ts +20 -0
  341. package/src/tests/entrypoint-api-readiness.test.ts +14 -8
  342. package/src/tests/entrypoint-config-env-export.test.ts +5 -2
  343. package/src/tests/entrypoint-export-to-env.test.ts +78 -0
  344. package/src/tests/env-flag.test.ts +1 -0
  345. package/src/tests/explicit-model-validation.test.ts +105 -1
  346. package/src/tests/extensions-catalog.test.ts +1 -0
  347. package/src/tests/extensions-example-comms-adherence-gate.test.ts +413 -0
  348. package/src/tests/extensions-example-route-chat-channel.test.ts +154 -0
  349. package/src/tests/extensions-loader.test.ts +15 -0
  350. package/src/tests/extensions-post-bridge.test.ts +51 -0
  351. package/src/tests/fetch-resolved-env.test.ts +128 -0
  352. package/src/tests/fixtures/chat-sdk/telegram-private-text.json +10 -0
  353. package/src/tests/fixtures/extensions/route-chat-channel-to-agent/hooks.ts +27 -0
  354. package/src/tests/fixtures/extensions/route-chat-channel-to-agent/manifest.json +9 -0
  355. package/src/tests/fixtures/openai-compatible-registry-extension.child.ts +146 -0
  356. package/src/tests/fixtures/runner-legacy-resume-prompt.ts +122 -0
  357. package/src/tests/github-handlers.test.ts +35 -0
  358. package/src/tests/harness-provider-resolution.test.ts +12 -0
  359. package/src/tests/heartbeat-checklist.test.ts +3 -2
  360. package/src/tests/heartbeat-jev-gate-credentials.test.ts +168 -0
  361. package/src/tests/heartbeat-jev-gate.test.ts +385 -0
  362. package/src/tests/hook-runtime-close.test.ts +0 -2
  363. package/src/tests/http-api-integration.test.ts +0 -25
  364. package/src/tests/migration-046-budgets.test.ts +17 -16
  365. package/src/tests/migration-217-chat-sdk-state.test.ts +113 -0
  366. package/src/tests/model-profile-backcompat.test.ts +327 -0
  367. package/src/tests/model-profile-providers.test.ts +302 -0
  368. package/src/tests/model-profile-runtime-secrets.test.ts +223 -0
  369. package/src/tests/model-profiles-api.test.ts +614 -0
  370. package/src/tests/model-tier-resolution.test.ts +93 -0
  371. package/src/tests/models-catalog.test.ts +42 -1
  372. package/src/tests/openai-compatible-providers.test.ts +134 -0
  373. package/src/tests/openai-compatible-registry-extension.test.ts +42 -0
  374. package/src/tests/opencode-adapter.test.ts +216 -2
  375. package/src/tests/opencode-helmcode-sdk.test.ts +201 -0
  376. package/src/tests/opencode-shell-env.test.ts +145 -0
  377. package/src/tests/openrouter-base-url.test.ts +101 -151
  378. package/src/tests/page-proxy.test.ts +1 -1
  379. package/src/tests/pages-browser-sdk.test.ts +76 -0
  380. package/src/tests/pages-feedback-keys.test.ts +121 -0
  381. package/src/tests/pages-feedback.test.ts +4 -1
  382. package/src/tests/paused-tasks-requested-by.test.ts +102 -0
  383. package/src/tests/pi-context-compaction.test.ts +193 -0
  384. package/src/tests/pi-custom-provider-fake-server.test.ts +442 -0
  385. package/src/tests/pi-mono-adapter.test.ts +385 -5
  386. package/src/tests/pi-shell-env.test.ts +170 -0
  387. package/src/tests/profile-claim.test.ts +269 -0
  388. package/src/tests/prompt-template-remaining.test.ts +35 -0
  389. package/src/tests/quickjs-compat.test.ts +213 -0
  390. package/src/tests/rbac-engine.test.ts +32 -3
  391. package/src/tests/runner-exit-after-tasks.test.ts +0 -4
  392. package/src/tests/runner-legacy-resume-prompt.test.ts +64 -0
  393. package/src/tests/script-connections.test.ts +6 -0
  394. package/src/tests/script-executor-conformance.test.ts +252 -1
  395. package/src/tests/script-executor-quickjs.test.ts +96 -0
  396. package/src/tests/script-runs-http.test.ts +89 -0
  397. package/src/tests/scripts-http.test.ts +217 -1
  398. package/src/tests/scripts-only-gating.test.ts +2 -3
  399. package/src/tests/scripts-runtime.test.ts +58 -40
  400. package/src/tests/secret-scrubber.test.ts +96 -0
  401. package/src/tests/seed-model-profiles.test.ts +174 -0
  402. package/src/tests/seed-scripts.test.ts +2 -2
  403. package/src/tests/seed-skills-bundled-files.test.ts +45 -53
  404. package/src/tests/seed-skills-retire.test.ts +182 -0
  405. package/src/tests/send-task-requester-gate.test.ts +58 -0
  406. package/src/tests/session-cost-recompute-unit.test.ts +46 -0
  407. package/src/tests/skill-get-name-resolution.test.ts +137 -0
  408. package/src/tests/skill-invocations.test.ts +4 -2
  409. package/src/tests/slack-blocks.test.ts +36 -0
  410. package/src/tests/slack-inline-output.test.ts +14 -0
  411. package/src/tests/slack-reaction-config.test.ts +20 -0
  412. package/src/tests/slack-render-v2.test.ts +37 -0
  413. package/src/tests/slack-thread-buffer.test.ts +98 -1
  414. package/src/tests/sonnet-5-5-registry.test.ts +9 -1
  415. package/src/tests/swarm-config-export-to-env.test.ts +267 -0
  416. package/src/tests/swarm-config-reserved-keys.test.ts +16 -0
  417. package/src/tests/swarm-diff.test.ts +1 -1
  418. package/src/tests/task-model-effort-rerun.test.ts +164 -0
  419. package/src/tests/task-session-prompts.test.ts +132 -0
  420. package/src/tests/task-tool-manifest.test.ts +14 -0
  421. package/src/tests/task-tools-ctx.test.ts +2 -0
  422. package/src/tests/tool-annotations.test.ts +6 -6
  423. package/src/tests/ui-logs-parser.test.ts +43 -0
  424. package/src/tests/used-by-route.test.ts +6 -0
  425. package/src/tests/vercel-ai-gateway.test.ts +149 -0
  426. package/src/tests/workflow-llm.test.ts +61 -0
  427. package/src/tests/workflow-swarm-script.test.ts +38 -0
  428. package/src/tests/workflow-wait-filter.test.ts +5 -3
  429. package/src/tools/chat-access.ts +41 -0
  430. package/src/tools/chat-open-dm.ts +59 -0
  431. package/src/tools/chat-post.ts +74 -0
  432. package/src/tools/chat-read-thread.ts +100 -0
  433. package/src/tools/chat-reply.ts +75 -0
  434. package/src/tools/create-page.ts +3 -13
  435. package/src/tools/model-catalog/index.ts +2 -1
  436. package/src/tools/model-profiles/list-model-profiles.ts +54 -0
  437. package/src/tools/register-chat-channel.ts +139 -0
  438. package/src/tools/request-human-input.ts +11 -3
  439. package/src/tools/script-apis.ts +5 -1
  440. package/src/tools/script-upsert.ts +4 -1
  441. package/src/tools/send-task.ts +48 -1
  442. package/src/tools/skills/skill-get.ts +17 -9
  443. package/src/tools/swarm-config/get-config.ts +2 -1
  444. package/src/tools/swarm-config/list-config.ts +2 -1
  445. package/src/tools/swarm-config/set-config.ts +16 -3
  446. package/src/tools/system-one-decide.ts +104 -0
  447. package/src/tools/task-action.ts +22 -1
  448. package/src/tools/tool-config.ts +14 -6
  449. package/src/types.ts +48 -3
  450. package/src/utils/config-env-export.ts +74 -0
  451. package/src/utils/credentials.test.ts +92 -0
  452. package/src/utils/credentials.ts +52 -4
  453. package/src/utils/harness-provider.ts +15 -6
  454. package/src/utils/runtime-model-catalog.ts +11 -1
  455. package/src/utils/secret-scrubber.ts +86 -10
  456. package/src/utils/task-tool-manifest.ts +19 -2
  457. package/src/utils/vcs-repo-match.ts +29 -0
  458. package/src/workflows/executors/raw-llm.ts +8 -3
  459. package/src/workflows/executors/swarm-script.ts +1 -0
  460. package/src/workflows/executors/system-one-decision.ts +21 -11
  461. package/src/workflows/executors/workflow-llm.ts +22 -0
  462. package/src/workflows/index.ts +21 -12
  463. package/templates/extensions/README.md +1 -0
  464. package/templates/extensions/comms-adherence-gate/README.md +88 -0
  465. package/templates/extensions/comms-adherence-gate/hooks.ts +489 -0
  466. package/templates/extensions/comms-adherence-gate/manifest.json +8 -0
  467. package/templates/skills/agent-browser/SKILL.md +1 -1
  468. package/templates/skills/agent-browser/content.md +1 -1
  469. package/templates/skills/apps/SKILL.md +8 -1
  470. package/templates/skills/apps/content.md +8 -1
  471. package/templates/skills/chat-sdk/config.json +14 -0
  472. package/templates/skills/chat-sdk/content.md +84 -0
  473. package/templates/skills/composio-gmail/content.md +7 -0
  474. package/templates/skills/composio-google-docs/content.md +1 -1
  475. package/templates/skills/email-domain-health/config.json +14 -0
  476. package/templates/skills/email-domain-health/content.md +131 -0
  477. package/templates/skills/kv-storage/content.md +2 -2
  478. package/templates/skills/pages/content.md +8 -10
  479. package/templates/skills/swarm-extensions/content.md +2 -0
  480. package/templates/skills/swarm-scripts/SKILL.md +4 -2
  481. package/templates/skills/swarm-scripts/config.json +1 -1
  482. package/templates/skills/swarm-scripts/content.md +4 -2
  483. package/templates/skills/workflow-iterate/content.md +38 -0
  484. package/dist/artifact-eczsknc3.js +0 -3843
  485. package/dist/cli-42qhyvwx.js +0 -33
  486. package/dist/cli-bd957dgb.js +0 -153
  487. package/dist/cli-q51yprfq.js +0 -1104
  488. package/dist/cli-t1ze64h3.js +0 -811
  489. package/dist/cli-tcc4wd5d.js +0 -56471
  490. package/dist/index-9ch1n761.js +0 -1102
  491. package/src/artifact-sdk/index.ts +0 -2
  492. package/src/artifact-sdk/localtunnel.d.ts +0 -20
  493. package/src/artifact-sdk/port.ts +0 -12
  494. package/src/artifact-sdk/server.ts +0 -183
  495. package/src/artifact-sdk/tunnel.ts +0 -19
  496. package/src/commands/artifact.ts +0 -249
  497. package/src/http/ecosystem.ts +0 -77
  498. package/src/tests/artifact-commands.test.ts +0 -92
  499. package/src/tests/artifact-sdk.test.ts +0 -793
  500. package/src/tests/register-service-security.test.ts +0 -194
  501. package/src/tools/list-services.ts +0 -107
  502. package/src/tools/register-service.ts +0 -177
  503. package/src/tools/unregister-service.ts +0 -73
  504. package/src/tools/update-service-status.ts +0 -96
  505. package/templates/skills/artifacts/config.json +0 -14
  506. package/templates/skills/artifacts/content.md +0 -344
  507. package/templates/skills/artifacts/files/examples/approval-flow.ts +0 -34
  508. package/templates/skills/artifacts/files/examples/hono-dashboard.ts +0 -31
  509. package/templates/skills/artifacts/files/examples/multi-artifact.ts +0 -20
  510. package/templates/skills/artifacts/files/examples/static-report.sh +0 -17
  511. /package/src/{artifact-sdk → pages}/browser-sdk.ts +0 -0
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Whether a swarm_config row reaches an agent's process environment.
3
+ *
4
+ * Each row carries an `exportToEnv` flag (migration 215). Workers export a row
5
+ * into the harness env, and so into `env`, `printenv` and `/proc/<pid>/environ`
6
+ * of every agent shell, only when the flag is true. Rows with the flag off stay
7
+ * server-side: agents use them through a connection or a script credential
8
+ * binding instead.
9
+ *
10
+ * Keys below are read only by the API server (integration signing secrets, app
11
+ * private keys, OAuth client secrets, the API's own agent-fs key). They can
12
+ * never be exported: the config API rejects `exportToEnv: true` for them and
13
+ * every reader treats a stored true as false.
14
+ */
15
+ export const ENV_EXPORT_RESTRICTED_KEYS: ReadonlySet<string> = new Set([
16
+ "SLACK_APP_TOKEN",
17
+ "SLACK_SIGNING_SECRET",
18
+ "GITHUB_APP_PRIVATE_KEY",
19
+ "GITHUB_WEBHOOK_SECRET",
20
+ "GITLAB_WEBHOOK_SECRET",
21
+ "LINEAR_CLIENT_SECRET",
22
+ "LINEAR_SIGNING_SECRET",
23
+ "JIRA_CLIENT_SECRET",
24
+ "JIRA_WEBHOOK_TOKEN",
25
+ "AZURE_DEVOPS_WEBHOOK_SECRET",
26
+ "AGENTMAIL_WEBHOOK_SECRET",
27
+ "API_AGENT_FS_API_KEY",
28
+ ]);
29
+
30
+ /**
31
+ * Server-side secrets an operator may still export per row. A new row for one
32
+ * of these keys starts with the flag off unless the caller sends
33
+ * `exportToEnv: true`. Migration 215 applies the same default to rows that
34
+ * already existed.
35
+ */
36
+ export const ENV_EXPORT_DEFAULT_OFF_KEYS: ReadonlySet<string> = new Set([
37
+ "KAPSO_WEBHOOK_HMAC_SECRET",
38
+ "SENTRY_TEST_APP_WEBHOOK_SECRET",
39
+ "TELEMETRY_CH_AUTH",
40
+ "HACK_AGENT_FS_ADMIN_KEY",
41
+ "X_CLIENT_SECRET",
42
+ ]);
43
+
44
+ /** Script connection secrets (`connection.<slug>.secret`) are brokered server-side. */
45
+ const CONNECTION_SECRET_KEY = /^connection\..+\.secret$/;
46
+
47
+ /**
48
+ * Key prefixes whose rows start with the flag off: the API server reads them
49
+ * (Chat SDK bot tokens and adapter JSON), agents never need them in a shell.
50
+ */
51
+ const ENV_EXPORT_DEFAULT_OFF_PREFIXES = ["CHAT_SDK_"];
52
+
53
+ export function isEnvExportRestrictedKey(key: string): boolean {
54
+ return ENV_EXPORT_RESTRICTED_KEYS.has(key.toUpperCase()) || CONNECTION_SECRET_KEY.test(key);
55
+ }
56
+
57
+ /** The flag a new row gets when the caller does not send one. */
58
+ export function defaultExportToEnv(key: string): boolean {
59
+ const upper = key.toUpperCase();
60
+ return (
61
+ !isEnvExportRestrictedKey(key) &&
62
+ !ENV_EXPORT_DEFAULT_OFF_KEYS.has(upper) &&
63
+ !ENV_EXPORT_DEFAULT_OFF_PREFIXES.some((prefix) => upper.startsWith(prefix))
64
+ );
65
+ }
66
+
67
+ export function envExportRestrictedError(key: string): string {
68
+ return `Key '${key}' is read only by the API server and can never be exported to agent env; store it with exportToEnv: false.`;
69
+ }
70
+
71
+ /** The flag readers act on: a stored true never wins for a restricted key. */
72
+ export function effectiveExportToEnv(key: string, stored: boolean): boolean {
73
+ return stored && !isEnvExportRestrictedKey(key);
74
+ }
@@ -1,6 +1,8 @@
1
1
  import { describe, expect, it } from "bun:test";
2
+ import { OPENAI_COMPATIBLE_PROVIDERS } from "@desplega/model-catalog";
2
3
  import {
3
4
  CREDENTIAL_POOL_VARS,
5
+ deriveProviderFromKeyType,
4
6
  getModelAwareCredentialVars,
5
7
  resolveCredentialPools,
6
8
  selectRandomCredential,
@@ -123,6 +125,35 @@ describe("resolveCredentialPools", () => {
123
125
  expect(["example-oauth1", "example-oauth2"]).toContain(env.CLAUDE_CODE_OAUTH_TOKEN!);
124
126
  expect(["example-apikey1", "example-apikey2"]).toContain(env.ANTHROPIC_API_KEY!);
125
127
  });
128
+
129
+ it("pools NAN_API_KEY alone for an opencode nan/* model", async () => {
130
+ const env: Record<string, string | undefined> = {
131
+ NAN_API_KEY: "example-nan-a,example-nan-b,example-nan-c",
132
+ OPENROUTER_API_KEY: "example-or-a,example-or-b",
133
+ };
134
+ const selections = await resolveCredentialPools(env, {
135
+ provider: "opencode",
136
+ model: "nan/glm5.3-flash",
137
+ });
138
+ expect(["example-nan-a", "example-nan-b", "example-nan-c"]).toContain(env.NAN_API_KEY!);
139
+ expect(selections).toHaveLength(1);
140
+ expect(selections[0]?.keyType).toBe("NAN_API_KEY");
141
+ expect(selections[0]?.total).toBe(3);
142
+ // OpenRouter is not relevant to a nan/* model and stays untouched.
143
+ expect(env.OPENROUTER_API_KEY).toBe("example-or-a,example-or-b");
144
+ });
145
+
146
+ it("pools HELMCODE_API_KEY alone for a pi helmcode/* model", async () => {
147
+ const env: Record<string, string | undefined> = {
148
+ HELMCODE_API_KEY: "example-helm-a,example-helm-b",
149
+ };
150
+ const selections = await resolveCredentialPools(env, {
151
+ provider: "pi",
152
+ model: "helmcode/glm5.3",
153
+ });
154
+ expect(["example-helm-a", "example-helm-b"]).toContain(env.HELMCODE_API_KEY!);
155
+ expect(selections.map((s) => s.keyType)).toEqual(["HELMCODE_API_KEY"]);
156
+ });
126
157
  });
127
158
 
128
159
  describe("validateClaudeCredentials", () => {
@@ -198,6 +229,23 @@ describe("CREDENTIAL_POOL_VARS", () => {
198
229
  it("contains expected env var names", () => {
199
230
  expect(CREDENTIAL_POOL_VARS).toContain("CLAUDE_CODE_OAUTH_TOKEN");
200
231
  expect(CREDENTIAL_POOL_VARS).toContain("ANTHROPIC_API_KEY");
232
+ expect(CREDENTIAL_POOL_VARS).toContain("NAN_API_KEY");
233
+ expect(CREDENTIAL_POOL_VARS).toContain("HELMCODE_API_KEY");
234
+ });
235
+
236
+ it("pools only the canonical NaN var; the alias is normalized by the runner", () => {
237
+ expect(OPENAI_COMPATIBLE_PROVIDERS.nan.aliasEnvVars).toEqual(["HELMCODE_API_NAN"]);
238
+ expect(CREDENTIAL_POOL_VARS as readonly string[]).not.toContain("HELMCODE_API_NAN");
239
+ });
240
+ });
241
+
242
+ describe("deriveProviderFromKeyType", () => {
243
+ it("tags NaN and Helmcode keys as pi, like OpenRouter", () => {
244
+ for (const keyType of ["NAN_API_KEY", "HELMCODE_API_NAN", "HELMCODE_API_KEY"]) {
245
+ expect(deriveProviderFromKeyType(keyType)).toBe("pi");
246
+ }
247
+ expect(deriveProviderFromKeyType("OPENROUTER_API_KEY")).toBe("pi");
248
+ expect(deriveProviderFromKeyType("SOMETHING_ELSE")).toBe("claude");
201
249
  });
202
250
  });
203
251
 
@@ -266,4 +314,48 @@ describe("getModelAwareCredentialVars", () => {
266
314
  const vars = getModelAwareCredentialVars("opencode", "anthropic/claude-sonnet-4-20250514");
267
315
  expect(vars).not.toContain("OPENAI_API_KEY");
268
316
  });
317
+
318
+ it("routes nan/* and helmcode/* to their own key on pi and opencode", () => {
319
+ for (const provider of ["pi", "opencode"]) {
320
+ expect(getModelAwareCredentialVars(provider, "nan/glm5.3-flash")).toEqual(["NAN_API_KEY"]);
321
+ expect(getModelAwareCredentialVars(provider, "helmcode/glm5.3")).toEqual([
322
+ "HELMCODE_API_KEY",
323
+ ]);
324
+ // Only the exact prefix routes: a look-alike is any other provider/model.
325
+ const other = getModelAwareCredentialVars(provider, "other/x");
326
+ expect(getModelAwareCredentialVars(provider, "helmcodex/x")).toEqual(other);
327
+ expect(getModelAwareCredentialVars(provider, "nanx/y")).toEqual(other);
328
+ expect(other).not.toContain("NAN_API_KEY");
329
+ expect(other).not.toContain("HELMCODE_API_KEY");
330
+ }
331
+ });
332
+
333
+ it("keeps OpenRouter and bare-id routing byte-identical", () => {
334
+ for (const provider of ["pi", "opencode"]) {
335
+ expect(
336
+ getModelAwareCredentialVars(provider, "openrouter/deepseek/deepseek-v4-flash"),
337
+ ).toEqual(["OPENROUTER_API_KEY", "ANTHROPIC_API_KEY"]);
338
+ expect(getModelAwareCredentialVars(provider, "openai/gpt-5")).toEqual([
339
+ "OPENROUTER_API_KEY",
340
+ "ANTHROPIC_API_KEY",
341
+ ]);
342
+ expect(getModelAwareCredentialVars(provider, "gpt-4o")).toEqual([
343
+ "OPENROUTER_API_KEY",
344
+ "ANTHROPIC_API_KEY",
345
+ "OPENAI_API_KEY",
346
+ ]);
347
+ // A NaN model routed through OpenRouter is still an OpenRouter model.
348
+ expect(getModelAwareCredentialVars(provider, "openrouter/nan/glm5.3")).toEqual([
349
+ "OPENROUTER_API_KEY",
350
+ "ANTHROPIC_API_KEY",
351
+ ]);
352
+ }
353
+ });
354
+
355
+ it("does not apply the nan/helmcode prefix routing to other harnesses", () => {
356
+ expect(getModelAwareCredentialVars("claude", "nan/glm5.3")).toEqual([
357
+ "CLAUDE_CODE_OAUTH_TOKEN",
358
+ "ANTHROPIC_API_KEY",
359
+ ]);
360
+ });
269
361
  });
@@ -1,4 +1,10 @@
1
1
  import { existsSync } from "node:fs";
2
+ import {
3
+ openAICompatibleProviders,
4
+ providerForKeyVar,
5
+ providerForModel,
6
+ providerKeyVars,
7
+ } from "@desplega/model-catalog";
2
8
  import { deriveDefaultRoute, routeCredentialStatus } from "@desplega/model-routing";
3
9
  import { type ModelFamily, modelFamilyOf, windowForModelFamily } from "./model-rate-limit-windows";
4
10
 
@@ -14,8 +20,27 @@ export const CREDENTIAL_POOL_VARS = [
14
20
  "AMP_API_KEY",
15
21
  "CURSOR_API_KEY",
16
22
  "XAI_API_KEY",
23
+ // The canonical key var of every OpenAI-compatible registry provider
24
+ // (`@desplega/model-catalog`). Aliases are normalized into these by the
25
+ // runner (`normalizeProviderKeyAliases`), so they never pool.
26
+ ...openAICompatibleProviders().map((p) => p.envVar),
17
27
  ] as const;
18
28
 
29
+ /**
30
+ * Copy the first set alias of each registry provider into its canonical key
31
+ * var when the canonical var is empty (`HELMCODE_API_NAN` → `NAN_API_KEY`).
32
+ * The runner runs this once per task, so pooling, credential checks, pi's
33
+ * runtime key and the opencode key splice only read the canonical name. An
34
+ * explicit canonical value wins. Mutates `env`.
35
+ */
36
+ export function normalizeProviderKeyAliases(env: Record<string, string | undefined>): void {
37
+ for (const provider of openAICompatibleProviders()) {
38
+ if (env[provider.envVar]?.trim()) continue;
39
+ const alias = provider.aliasEnvVars.find((name) => env[name]?.trim());
40
+ if (alias) env[provider.envVar] = env[alias];
41
+ }
42
+ }
43
+
19
44
  /**
20
45
  * Which credential env vars are relevant for each harness provider. The
21
46
  * runner uses this map to filter `CREDENTIAL_POOL_VARS` so a codex worker
@@ -76,6 +101,10 @@ export function getModelAwareCredentialVars(provider: string, model?: string): r
76
101
  return model.startsWith("openrouter/") ? ["OPENROUTER_API_KEY"] : ["XAI_API_KEY"];
77
102
  }
78
103
  if (!SLASH_MODEL_PROVIDERS.has(provider) || !model) return base;
104
+ // OpenAI-compatible registry providers own their key: `nan/<id>` and
105
+ // `helmcode/<id>` never pool an OpenRouter or Anthropic key.
106
+ const direct = providerForModel(model);
107
+ if (direct) return [direct.envVar];
79
108
  if (model.includes("/")) {
80
109
  return base.filter((v) => v !== "OPENAI_API_KEY");
81
110
  }
@@ -91,6 +120,8 @@ export function getModelAwareCredentialVars(provider: string, model?: string): r
91
120
  * usage with the active worker's HARNESS_PROVIDER.
92
121
  */
93
122
  export function deriveProviderFromKeyType(keyType: string): string {
123
+ // OpenAI-compatible registry keys (canonical or alias) default to pi.
124
+ if (providerForKeyVar(keyType)) return "pi";
94
125
  switch (keyType) {
95
126
  case "CLAUDE_CODE_OAUTH_TOKEN":
96
127
  case "ANTHROPIC_API_KEY":
@@ -210,7 +241,7 @@ function isJsonObject(value: string): boolean {
210
241
  * Falls back to random selection from all credentials if no available indices match.
211
242
  * A JSON object value (the CODEX_OAUTH blob) is always one credential.
212
243
  */
213
- function splitCredentialPool(value: string): string[] {
244
+ export function splitCredentialPool(value: string): string[] {
214
245
  return isJsonObject(value)
215
246
  ? [value.trim()]
216
247
  : value
@@ -311,21 +342,38 @@ export function validateClaudeCredentials(env: Record<string, string | undefined
311
342
  return CLAUDE_CREDENTIAL_TYPE_BY_PROVIDER[route.provider] ?? route.provider;
312
343
  }
313
344
 
345
+ /**
346
+ * One label per OpenAI-compatible registry provider for hints and errors:
347
+ * `NAN_API_KEY (or HELMCODE_API_NAN)`, `HELMCODE_API_KEY`.
348
+ */
349
+ export function openAICompatibleKeyLabels(): string[] {
350
+ return openAICompatibleProviders().map((p) =>
351
+ p.aliasEnvVars.length > 0 ? `${p.envVar} (or ${p.aliasEnvVars.join(" or ")})` : p.envVar,
352
+ );
353
+ }
354
+
314
355
  /**
315
356
  * Validate that at least one opencode credential is available.
316
- * Priority: OPENROUTER_API_KEY → ANTHROPIC_API_KEY → OPENAI_API_KEY → ~/.local/share/opencode/auth.json.
357
+ * Priority: OPENROUTER_API_KEY → ANTHROPIC_API_KEY → OPENAI_API_KEY → each
358
+ * OpenAI-compatible registry provider's key (canonical or alias, e.g.
359
+ * NAN_API_KEY or HELMCODE_API_NAN → `nan_api_key`) →
360
+ * ~/.local/share/opencode/auth.json.
317
361
  * Returns the credential type found, or throws if none are available.
318
362
  */
319
363
  export function validateOpencodeCredentials(
320
364
  env: Record<string, string | undefined>,
321
- ): "openrouter_api_key" | "anthropic_api_key" | "openai_api_key" | "auth_file" {
365
+ ): "openrouter_api_key" | "anthropic_api_key" | "openai_api_key" | Lowercase<string> | "auth_file" {
322
366
  if (env.OPENROUTER_API_KEY) return "openrouter_api_key";
323
367
  if (env.ANTHROPIC_API_KEY) return "anthropic_api_key";
324
368
  if (env.OPENAI_API_KEY) return "openai_api_key";
369
+ const direct = openAICompatibleProviders().find((p) => providerKeyVars(p).some((n) => env[n]));
370
+ if (direct) return direct.envVar.toLowerCase() as Lowercase<string>;
325
371
  const authFile = `${process.env.HOME ?? "/root"}/.local/share/opencode/auth.json`;
326
372
  if (existsSync(authFile)) return "auth_file";
327
373
  throw new Error(
328
- "No opencode credentials found. Set OPENROUTER_API_KEY, ANTHROPIC_API_KEY, OPENAI_API_KEY, or provide ~/.local/share/opencode/auth.json.",
374
+ `No opencode credentials found. Set OPENROUTER_API_KEY, ANTHROPIC_API_KEY, OPENAI_API_KEY, ${openAICompatibleKeyLabels()
375
+ .map((label) => `${label}, `)
376
+ .join("")}or provide ~/.local/share/opencode/auth.json.`,
329
377
  );
330
378
  }
331
379
 
@@ -1,3 +1,4 @@
1
+ import { openAICompatibleKeyVars } from "@desplega/model-catalog";
1
2
  import { type ProviderName, ProviderNameSchema } from "../types";
2
3
 
3
4
  const SUPPORTED_PROVIDERS = ProviderNameSchema.options;
@@ -10,23 +11,31 @@ function hasEnvValue(
10
11
  return !!(resolvedEnv[key]?.trim() || fallbackEnv[key]?.trim());
11
12
  }
12
13
 
14
+ /**
15
+ * LLM keys that make "pi" the default harness when no Claude credential is
16
+ * set: OpenRouter plus every OpenAI-compatible registry key (canonical or alias).
17
+ */
18
+ const PI_DEFAULT_KEY_VARS = ["OPENROUTER_API_KEY", ...openAICompatibleKeyVars()];
19
+
13
20
  /**
14
21
  * Credential-aware default for when HARNESS_PROVIDER is unset or invalid.
15
22
  * Unconditionally defaulting to "claude" leaves an OpenRouter-only swarm —
16
23
  * e.g. every auto-deployed swarm, which is provisioned with
17
24
  * OPENROUTER_API_KEY and NEVER an Anthropic credential — unable to
18
- * authenticate at all. Prefer "pi" when an OpenRouter key is present and no
19
- * Claude credential is; otherwise keep the original "claude" default.
25
+ * authenticate at all. Prefer "pi" when an OpenRouter key or an
26
+ * OpenAI-compatible registry key (NaN, Helmcode; canonical or alias) is
27
+ * present and no Claude credential is; otherwise keep the original "claude"
28
+ * default.
20
29
  */
21
30
  function credentialAwareDefault(
22
31
  resolvedEnv: Record<string, string | undefined>,
23
32
  fallbackEnv: Record<string, string | undefined>,
24
33
  ): ProviderName {
25
- const hasOpenRouterKey = hasEnvValue("OPENROUTER_API_KEY", resolvedEnv, fallbackEnv);
34
+ const hasPiKey = PI_DEFAULT_KEY_VARS.some((key) => hasEnvValue(key, resolvedEnv, fallbackEnv));
26
35
  const hasClaudeCredential =
27
36
  hasEnvValue("ANTHROPIC_API_KEY", resolvedEnv, fallbackEnv) ||
28
37
  hasEnvValue("CLAUDE_CODE_OAUTH_TOKEN", resolvedEnv, fallbackEnv);
29
- return hasOpenRouterKey && !hasClaudeCredential ? "pi" : "claude";
38
+ return hasPiKey && !hasClaudeCredential ? "pi" : "claude";
30
39
  }
31
40
 
32
41
  /**
@@ -36,8 +45,8 @@ function credentialAwareDefault(
36
45
  * 1. `resolvedEnv.HARNESS_PROVIDER` — value coming from `swarm_config`
37
46
  * (overlay produced by `fetchResolvedEnv`, scoped repo > agent > global).
38
47
  * 2. `fallbackEnv.HARNESS_PROVIDER` — raw `process.env`.
39
- * 3. Credential-aware default — "pi" when an OpenRouter key is present and
40
- * no Anthropic credential is (see `credentialAwareDefault`), else
48
+ * 3. Credential-aware default — "pi" when an OpenRouter or registry
49
+ * provider key is present and no Anthropic credential is (see `credentialAwareDefault`), else
41
50
  * `"claude"`.
42
51
  *
43
52
  * Invalid values (anything outside `ProviderNameSchema`) log a warning and
@@ -17,6 +17,7 @@
17
17
  * NO database imports (worker-safe). Every lookup is synchronous; the HTTP
18
18
  * refresh is TTL-cached and never throws, so hot paths stay cheap.
19
19
  */
20
+ import { type CatalogChatRow, withRegistryCatalogSections } from "@desplega/model-catalog";
20
21
  import modelsDevSnapshot from "../be/modelsdev-cache.json";
21
22
 
22
23
  export interface RuntimeCatalogModel {
@@ -38,7 +39,16 @@ export interface RuntimeCatalogProvider {
38
39
 
39
40
  export type RuntimeCatalog = Record<string, RuntimeCatalogProvider | undefined>;
40
41
 
41
- const SNAPSHOT = modelsDevSnapshot as RuntimeCatalog;
42
+ // Plus the registry-synthesized sections models.dev does not list (`helmcode`).
43
+ const SNAPSHOT = withRegistryCatalogSections(modelsDevSnapshot as RuntimeCatalog);
44
+
45
+ /**
46
+ * One section's full snapshot rows (output limit, modalities, tool calling),
47
+ * which the slim live catalog drops. Harness config blocks read these.
48
+ */
49
+ export function snapshotCatalogRows(provider: string): Record<string, CatalogChatRow> {
50
+ return (SNAPSHOT[provider]?.models ?? {}) as Record<string, CatalogChatRow>;
51
+ }
42
52
 
43
53
  /** Default freshness window for the worker-side HTTP pull. */
44
54
  export const RUNTIME_CATALOG_TTL_MS = 10 * 60_000;
@@ -21,6 +21,7 @@
21
21
  * API↔worker DB boundary (scripts/check-db-boundary.sh).
22
22
  */
23
23
 
24
+ import { openAICompatibleKeyVars } from "@desplega/model-catalog";
24
25
  import { GITLEAKS_RULES } from "./secret-rules.generated";
25
26
 
26
27
  /**
@@ -35,9 +36,13 @@ import { GITLEAKS_RULES } from "./secret-rules.generated";
35
36
  * 5, the vendored gitleaks rule set, plus hand-written Resend, Google OAuth,
36
37
  * Discord webhook, xAI and bare Telegram shapes in pass 2; v6 = pass 5 resumes
37
38
  * after the secret, not the match, so adjacent secrets that share a delimiter
38
- * both redact, and sourcegraph-access-token drops its bare 40-hex branch.
39
+ * both redact, and sourcegraph-access-token drops its bare 40-hex branch;
40
+ * v7 = the OpenAI-compatible registry key vars (`NAN_API_KEY`,
41
+ * `HELMCODE_API_KEY`, and the `HELMCODE_API_NAN` alias no suffix matches).
42
+ * A new registry entry adds key vars, so it is a rule change too
43
+ * (`secret-scrubber.test.ts` pins the list to this number).
39
44
  */
40
- export const SCRUBBER_RULES_VERSION = 6;
45
+ export const SCRUBBER_RULES_VERSION = 8;
41
46
 
42
47
  /** Env-var names that are always considered secrets, even without suffix hints. */
43
48
  const SENSITIVE_KEY_EXACT = new Set<string>([
@@ -52,6 +57,9 @@ const SENSITIVE_KEY_EXACT = new Set<string>([
52
57
  "ANTHROPIC_API_KEY",
53
58
  "OPENAI_API_KEY",
54
59
  "OPENROUTER_API_KEY",
60
+ // OpenAI-compatible registry key vars, canonical and alias. An alias such
61
+ // as `HELMCODE_API_NAN` matches no SENSITIVE_KEY_SUFFIXES entry.
62
+ ...openAICompatibleKeyVars(),
55
63
  "SLACK_BOT_TOKEN",
56
64
  "SLACK_SIGNING_SECRET",
57
65
  "SLACK_CLIENT_SECRET",
@@ -800,6 +808,21 @@ function computeIsSensitiveKey(key: string): boolean {
800
808
  return false;
801
809
  }
802
810
 
811
+ /**
812
+ * Every form of a credential value a consumer may send: the raw value, its
813
+ * trimmed form, and each trimmed member of a comma pool. `selectCredential`
814
+ * trims the key it hands out, so a padded singleton reaches the provider (and
815
+ * its error echo) without the padding.
816
+ */
817
+ function credentialCandidates(value: string): string[] {
818
+ const candidates = new Set([value, value.trim()]);
819
+ if (value.includes(",")) {
820
+ for (const member of value.split(",")) candidates.add(member.trim());
821
+ }
822
+ candidates.delete("");
823
+ return [...candidates];
824
+ }
825
+
803
826
  function buildCache(): ScrubCache {
804
827
  const entries: EnvValueEntry[] = [];
805
828
  const seen = new Set<string>();
@@ -808,14 +831,7 @@ function buildCache(): ScrubCache {
808
831
  if (!rawValue) continue;
809
832
  if (!isSensitiveKey(key)) continue;
810
833
 
811
- // Credential pools: a single env var may hold a comma-separated list of
812
- // tokens that the runner rotates through. Scrub each component too.
813
- const candidates = rawValue.includes(",")
814
- ? [rawValue, ...rawValue.split(",").map((s) => s.trim())]
815
- : [rawValue];
816
-
817
- for (const candidate of candidates) {
818
- if (!candidate) continue;
834
+ for (const candidate of credentialCandidates(rawValue)) {
819
835
  if (candidate.length < MIN_VALUE_LENGTH) continue;
820
836
  for (const form of escapedForms(candidate)) {
821
837
  if (seen.has(form)) continue;
@@ -1005,6 +1021,52 @@ export function scrubObject<T>(value: T, seen = new WeakSet<object>()): T {
1005
1021
  return out as T;
1006
1022
  }
1007
1023
 
1024
+ export type ValueScrubber = {
1025
+ text(text: string): string;
1026
+ object<T>(value: T): T;
1027
+ };
1028
+
1029
+ /**
1030
+ * Redact exact occurrences of the given values, at any length, in one output
1031
+ * boundary that knows which secrets it handed out (a script run's secret
1032
+ * config). The global passes above keep their MIN_VALUE_LENGTH floor; this
1033
+ * one never registers anything swarm-wide. All forms match in a single regex
1034
+ * pass, longest first, so one value's marker is never rewritten by another
1035
+ * value's pass. Object keys are redacted too.
1036
+ */
1037
+ export function createValueScrubber(
1038
+ entries: Array<{ value: string; name: string }>,
1039
+ ): ValueScrubber {
1040
+ const names = new Map<string, string>();
1041
+ for (const { value, name } of entries) {
1042
+ if (!value) continue;
1043
+ for (const form of escapedForms(value)) if (!names.has(form)) names.set(form, name);
1044
+ }
1045
+ if (names.size === 0) return { text: (text) => text, object: (value) => value };
1046
+
1047
+ const pattern = [...names.keys()]
1048
+ .sort((a, b) => b.length - a.length)
1049
+ .map(escapeRegExp)
1050
+ .join("|");
1051
+ const re = new RegExp(pattern, "g");
1052
+ const text = (input: string) =>
1053
+ input.replace(re, (match) => `[REDACTED:${names.get(match) ?? "secret"}]`);
1054
+ const object = <T>(value: T, seen = new WeakSet<object>()): T => {
1055
+ if (typeof value === "string") return text(value) as T;
1056
+ if (value === null || typeof value !== "object") return value;
1057
+ if (seen.has(value)) return "[Circular]" as T;
1058
+ seen.add(value);
1059
+ if (Array.isArray(value)) return value.map((item) => object(item, seen)) as T;
1060
+ return Object.fromEntries(
1061
+ Object.entries(value as Record<string, unknown>).map(([key, child]) => [
1062
+ text(key),
1063
+ object(child, seen),
1064
+ ]),
1065
+ ) as T;
1066
+ };
1067
+ return { text, object: (value) => object(value) };
1068
+ }
1069
+
1008
1070
  /**
1009
1071
  * Force the env-value cache to rebuild on the next scrub call. Callers should
1010
1072
  * invoke this whenever the swarm_config is reloaded (`/internal/reload-config`
@@ -1031,6 +1093,20 @@ export function registerVolatileSecret(value: string, name: string): void {
1031
1093
  }
1032
1094
  }
1033
1095
 
1096
+ /**
1097
+ * Register the sensitive values of an env map that is not `process.env`
1098
+ * (a runner's per-task env, an adapter's session env). A key picked from a
1099
+ * credential pool, or a row admitted after boot, lives only there, so the
1100
+ * env pass never sees it. Comma-separated pools register each member too,
1101
+ * and padded values register their trimmed form.
1102
+ */
1103
+ export function registerEnvSecrets(env: Record<string, string | undefined>): void {
1104
+ for (const [key, value] of Object.entries(env)) {
1105
+ if (!value || !isSensitiveKey(key)) continue;
1106
+ for (const candidate of credentialCandidates(value)) registerVolatileSecret(candidate, key);
1107
+ }
1108
+ }
1109
+
1034
1110
  /** Remove a short-lived secret and its encoded forms after credential revocation. */
1035
1111
  export function unregisterVolatileSecret(name: string): void {
1036
1112
  for (const [value, registeredName] of volatileSecrets) {
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { chatThreadOfContextKey } from "../tasks/context-key";
2
3
  import { ALL_TOOLS } from "../tools/tool-config";
3
4
 
4
5
  const toolNames = z
@@ -15,9 +16,22 @@ export function parseTaskToolManifest(value: string) {
15
16
  return taskToolManifestSchema.parse(JSON.parse(value));
16
17
  }
17
18
 
19
+ /** Tools that act on the chat thread of the calling task; useless on any other task. */
20
+ export const CHAT_TASK_ONLY_TOOLS: readonly string[] = ["chat-reply"];
21
+
22
+ /** True when the task came from a Chat SDK thread (contextKey family `chat-sdk`). */
23
+ export function isChatSdkTask(task: { contextKey?: string | null }): boolean {
24
+ return chatThreadOfContextKey(task.contextKey) !== null;
25
+ }
26
+
18
27
  export function selectTaskTools(
19
28
  manifest: z.infer<typeof taskToolManifestSchema>,
20
- task: { taskType?: string; scheduleId?: string; slackChannelId?: string },
29
+ task: {
30
+ taskType?: string;
31
+ scheduleId?: string;
32
+ slackChannelId?: string;
33
+ contextKey?: string | null;
34
+ },
21
35
  ): string[] {
22
36
  const fromSchedule =
23
37
  task.scheduleId && Object.hasOwn(manifest.schedules ?? {}, task.scheduleId)
@@ -27,7 +41,10 @@ export function selectTaskTools(
27
41
  task.taskType && Object.hasOwn(manifest.taskTypes ?? {}, task.taskType)
28
42
  ? manifest.taskTypes?.[task.taskType]
29
43
  : undefined;
44
+ const chatTask = isChatSdkTask(task);
30
45
  return [...new Set(fromSchedule ?? fromType ?? [])].filter(
31
- (name) => !name.startsWith("slack-") || Boolean(task.slackChannelId),
46
+ (name) =>
47
+ (!name.startsWith("slack-") || Boolean(task.slackChannelId)) &&
48
+ (chatTask || !CHAT_TASK_ONLY_TOOLS.includes(name)),
32
49
  );
33
50
  }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * How a task's `vcsRepo` names a registered repo (`swarm_repos`). The worker
3
+ * loads that repo's scoped config with it (`fetchRepoConfig`), and the API
4
+ * reads the same repo at claim, so both sides must match the same way.
5
+ */
6
+
7
+ /** `vcsRepo` without surrounding spaces, trailing slashes or `.git`. */
8
+ export function normalizeVcsRepo(vcsRepo: string): string {
9
+ return vcsRepo
10
+ .trim()
11
+ .replace(/\/+$/, "")
12
+ .replace(/\.git$/, "");
13
+ }
14
+
15
+ /** The `swarm_repos.name` candidates are looked up by: the last path segment. */
16
+ export function vcsRepoName(vcsRepo: string): string {
17
+ const requested = normalizeVcsRepo(vcsRepo);
18
+ return requested.split("/").pop() || requested;
19
+ }
20
+
21
+ /** Whether a registered repo URL is exactly the repo `vcsRepo` names. */
22
+ export function vcsRepoUrlMatches(url: string, vcsRepo: string): boolean {
23
+ const requested = normalizeVcsRepo(vcsRepo);
24
+ const normalized = normalizeVcsRepo(url);
25
+ if (normalized === requested) return true;
26
+ // Qualified URLs must retain their host. Only shorthand references use suffix matching.
27
+ if (requested.includes(":")) return false;
28
+ return normalized.endsWith(`/${requested}`) || normalized.endsWith(`:${requested}`);
29
+ }
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import type { ExecutorMeta } from "../../types";
3
+ import { scrubSecrets } from "../../utils/secret-scrubber";
3
4
  import { BaseExecutor, type ExecutorResult } from "./base";
4
5
  import { resolveWorkflowLlmConfig } from "./workflow-llm";
5
6
 
@@ -72,7 +73,7 @@ export async function executeRawLlm(
72
73
  const isRateLimited =
73
74
  httpStatus === 429 ||
74
75
  httpStatus === 529 ||
75
- (err instanceof Error && /rate.?limit|too many requests|529/i.test(err.message));
76
+ (err instanceof Error && /rate.?limit|too many requests|\b529\b/i.test(err.message));
76
77
  if (isRateLimited) {
77
78
  throw err;
78
79
  }
@@ -80,12 +81,16 @@ export async function executeRawLlm(
80
81
  return {
81
82
  status: "success",
82
83
  output: { result: null, model: modelName },
83
- error: `LLM call failed, using fallback port: ${err instanceof Error ? err.message : String(err)}`,
84
+ error: scrubSecrets(
85
+ `LLM call failed, using fallback port: ${err instanceof Error ? err.message : String(err)}`,
86
+ ),
84
87
  };
85
88
  }
86
89
  return {
87
90
  status: "failed",
88
- error: `LLM call failed: ${err instanceof Error ? err.message : String(err)}`,
91
+ // Provider errors can echo the request key back; scrub before any
92
+ // consumer (journal, HTTP route, workflow step) sees the text.
93
+ error: scrubSecrets(`LLM call failed: ${err instanceof Error ? err.message : String(err)}`),
89
94
  };
90
95
  }
91
96
  }
@@ -127,6 +127,7 @@ export class SwarmScriptExecutor extends BaseExecutor<
127
127
  mcpBaseUrl: getMcpBaseUrl(),
128
128
  egressSecrets: credentials.egressSecrets,
129
129
  failedBindings: credentials.failedBindings,
130
+ userConfig: credentials.userConfig,
130
131
  apiConnections: getScriptApiConnectionDescriptors({ agentId: agentId ?? undefined }),
131
132
  mcpConnections: getScriptMcpConnectionDescriptors({ agentId: agentId ?? undefined }),
132
133
  timeoutMs: config.timeoutMs,