@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,154 @@
1
+ import { afterAll, beforeAll, beforeEach, describe, expect, spyOn, test } from "bun:test";
2
+ import { unlink } from "node:fs/promises";
3
+ import { closeDb, createAgent, getDbClient, getKv, initDb } from "../be/db";
4
+ import { installExtension, listExtensionRuns } from "../be/extensions/db";
5
+ import { enableExtension, stopExtensionRuntime } from "../extensions/lifecycle";
6
+ import { chatSdkContextKey } from "../tasks/context-key";
7
+ import type { Agent } from "../types";
8
+ import {
9
+ flushChatBuffers,
10
+ postTelegramUpdate,
11
+ startTelegramHarness,
12
+ type TelegramHarness,
13
+ telegramUpdate,
14
+ } from "./chat-sdk-harness";
15
+ import { loadBundleFixture } from "./fixtures/extensions/load";
16
+
17
+ const TEST_DB_PATH = "./test-extensions-example-route-chat-channel.sqlite";
18
+
19
+ let harness: TelegramHarness;
20
+ let lead: Agent;
21
+ let worker: Agent;
22
+
23
+ async function removeDbFiles(): Promise<void> {
24
+ for (const suffix of ["", "-wal", "-shm"]) {
25
+ await unlink(`${TEST_DB_PATH}${suffix}`).catch(() => {});
26
+ }
27
+ }
28
+
29
+ async function chatTask(chatId: number) {
30
+ return getDbClient().get<{ agentId: string | null }>(
31
+ "SELECT agentId FROM agent_tasks WHERE source = 'chat' AND contextKey = ? ORDER BY createdAt DESC LIMIT 1",
32
+ [chatSdkContextKey(`telegram:${chatId}`)],
33
+ );
34
+ }
35
+
36
+ /** A private-chat message, flushed out of the thread buffer. */
37
+ async function sendDm(chatId: number, text: string): Promise<void> {
38
+ await postTelegramUpdate(telegramUpdate({ id: chatId, type: "private" }, text));
39
+ await flushChatBuffers();
40
+ }
41
+
42
+ async function installRouteFixture(channelId: string, agentId: string) {
43
+ const bundle = await loadBundleFixture("route-chat-channel-to-agent");
44
+ const { extension } = await installExtension({
45
+ ...bundle,
46
+ config: { adapter: "telegram", channelId, agentId },
47
+ });
48
+ return { extension, enabled: await enableExtension(extension.id) };
49
+ }
50
+
51
+ describe("extension Chat SDK channel routing", () => {
52
+ beforeAll(async () => {
53
+ await removeDbFiles();
54
+ initDb(TEST_DB_PATH);
55
+ lead = await createAgent({
56
+ id: crypto.randomUUID(),
57
+ name: "chat-route-lead",
58
+ isLead: true,
59
+ status: "idle",
60
+ });
61
+ worker = await createAgent({
62
+ id: crypto.randomUUID(),
63
+ name: "chat-route-worker",
64
+ isLead: false,
65
+ status: "idle",
66
+ });
67
+ harness = await startTelegramHarness();
68
+ });
69
+
70
+ afterAll(async () => {
71
+ await harness.stop();
72
+ await stopExtensionRuntime();
73
+ closeDb();
74
+ await removeDbFiles();
75
+ });
76
+
77
+ beforeEach(async () => {
78
+ await stopExtensionRuntime();
79
+ await getDbClient().run("DELETE FROM extensions");
80
+ });
81
+
82
+ test("routes the configured channel to its agent and leaves other channels with the lead", async () => {
83
+ const { extension } = await installRouteFixture("telegram:3001", worker.id);
84
+
85
+ await sendDm(3001, "route this to the worker");
86
+ expect(await chatTask(3001)).toEqual({ agentId: worker.id });
87
+ expect(
88
+ (await listExtensionRuns(extension.id)).some(
89
+ (run) => run.event === "pre.chat.route" && run.action === "modify",
90
+ ),
91
+ ).toBe(true);
92
+
93
+ await sendDm(3002, "use the normal route");
94
+ expect(await chatTask(3002)).toEqual({ agentId: lead.id });
95
+ });
96
+
97
+ test("a block creates no task", async () => {
98
+ const bundle = await loadBundleFixture("minimal");
99
+ const { extension } = await installExtension({
100
+ manifest: { ...bundle.manifest, name: "block-chat-route", description: "blocks chat" },
101
+ files: {
102
+ "hooks.ts": `import { block, type SwarmExtension } from "swarm-extension";
103
+ const extension: SwarmExtension = (api) => {
104
+ api.on("pre.chat.route", () => block("chat is muted"));
105
+ };
106
+ export default extension;
107
+ `,
108
+ },
109
+ });
110
+ await enableExtension(extension.id);
111
+
112
+ await sendDm(3003, "this must not become a task");
113
+ expect(await chatTask(3003)).toBeNull();
114
+ const runs = await listExtensionRuns(extension.id);
115
+ expect(runs.some((run) => run.event === "pre.chat.route" && run.action === "block")).toBe(true);
116
+ // Ids only in the run subject: never the message text.
117
+ expect(runs.every((run) => !run.subject?.includes("must not"))).toBe(true);
118
+ });
119
+
120
+ test("falls back to the built-in router when the extension names an unknown agent", async () => {
121
+ await installRouteFixture("telegram:3004", "missing-agent");
122
+ const warn = spyOn(console, "warn").mockImplementation(() => {});
123
+ try {
124
+ await sendDm(3004, "use the fallback");
125
+ expect(await chatTask(3004)).toEqual({ agentId: lead.id });
126
+ expect(warn).toHaveBeenCalledWith(
127
+ "[chat-sdk] Extension selected an unknown agent missing-agent. Using the router.",
128
+ );
129
+ } finally {
130
+ warn.mockRestore();
131
+ }
132
+ });
133
+
134
+ test("post.chat.message carries the inbound sender", async () => {
135
+ const { extension, enabled } = await installRouteFixture("telegram:3005", worker.id);
136
+ await sendDm(3005, "record this message");
137
+
138
+ const deadline = Date.now() + 5_000;
139
+ while (
140
+ !(await listExtensionRuns(extension.id)).some(
141
+ (run) => run.event === "post.chat.message" && run.action === "continue",
142
+ ) &&
143
+ Date.now() < deadline
144
+ ) {
145
+ await Bun.sleep(20);
146
+ }
147
+ expect(
148
+ await getKv(
149
+ `task:agent:${enabled.agentId}`,
150
+ "ext:route-chat-channel-to-agent:chat:telegram:3005",
151
+ ),
152
+ ).toMatchObject({ value: "777001" });
153
+ });
154
+ });
@@ -52,6 +52,21 @@ describe("extension loader", () => {
52
52
  expect(await Bun.file(loaded.sourcePath).exists()).toBe(false);
53
53
  });
54
54
 
55
+ test("loads hooks from a safe nested bundle path", async () => {
56
+ const bundle = await loadBundleFixture("minimal");
57
+ const hooks = bundle.files[bundle.manifest.assets.hooks]!;
58
+ bundle.manifest = {
59
+ ...bundle.manifest,
60
+ assets: { ...bundle.manifest.assets, hooks: "nested/hooks.ts" },
61
+ };
62
+ bundle.files = { "nested/hooks.ts": hooks };
63
+ const installed = await installExtension(bundle);
64
+ const loaded = await loadExtension({ record: installed.extension, ...bundle });
65
+
66
+ expect(await Bun.file(loaded.sourcePath).exists()).toBe(true);
67
+ await loaded.dispose();
68
+ });
69
+
55
70
  test("defensively rejects file traversal before writing bundle files", async () => {
56
71
  const input = await installLoadable("minimal");
57
72
  input.files["../escape.ts"] = "export default () => {};";
@@ -33,6 +33,7 @@ const NEW_EVENTS = [
33
33
  "post.task.budgetRefused",
34
34
  "post.email.received",
35
35
  "post.kapso.message",
36
+ "post.chat.message",
36
37
  "post.vcs.event",
37
38
  ] as const satisfies readonly (keyof SwarmEventMap)[];
38
39
 
@@ -358,6 +359,56 @@ describe("extension post bridge: new events", () => {
358
359
  });
359
360
  });
360
361
 
362
+ describe("post.chat.message", () => {
363
+ test("chat.message.received dispatches an inbound event", async () => {
364
+ await registerCapture("capture");
365
+ workflowEventBus.emit("chat.message.received", {
366
+ adapter: "telegram",
367
+ channelId: "telegram:555001",
368
+ threadId: "telegram:555001",
369
+ userId: "555001",
370
+ text: "hello swarm",
371
+ messageId: "555001:11",
372
+ });
373
+ await waitForEvents(1);
374
+ expect(received).toEqual([
375
+ {
376
+ extension: "capture",
377
+ event: "post.chat.message",
378
+ payload: {
379
+ adapter: "telegram",
380
+ channelId: "telegram:555001",
381
+ threadId: "telegram:555001",
382
+ userId: "555001",
383
+ text: "hello swarm",
384
+ direction: "inbound",
385
+ messageId: "555001:11",
386
+ },
387
+ },
388
+ ]);
389
+ });
390
+
391
+ test("chat.message.sent dispatches an outbound event", async () => {
392
+ await registerCapture("capture");
393
+ workflowEventBus.emit("chat.message.sent", {
394
+ adapter: "telegram",
395
+ channelId: "telegram:555001",
396
+ threadId: "telegram:555001",
397
+ text: "done",
398
+ taskId: "task-1",
399
+ });
400
+ await waitForEvents(1);
401
+ expect(received[0]?.payload).toEqual({
402
+ adapter: "telegram",
403
+ channelId: "telegram:555001",
404
+ threadId: "telegram:555001",
405
+ text: "done",
406
+ direction: "outbound",
407
+ taskId: "task-1",
408
+ });
409
+ });
410
+ });
411
+
361
412
  describe("post.kapso.message", () => {
362
413
  test("a bus emit dispatches the typed payload", async () => {
363
414
  await registerCapture("capture");
@@ -120,6 +120,95 @@ describe("fetchResolvedEnv", () => {
120
120
  warning.mockRestore();
121
121
  }
122
122
  });
123
+
124
+ test("exportToEnv=false rows stay out of the harness env, even over a boot copy", async () => {
125
+ const agentId = crypto.randomUUID();
126
+ mockResponsesByAgentId.set(agentId, {
127
+ status: 200,
128
+ body: {
129
+ configs: [
130
+ { key: "VERCEL_TOKEN", value: "row-vercel", isSecret: true, exportToEnv: false },
131
+ { key: "GITHUB_TOKEN", value: "row-github", isSecret: true, exportToEnv: true },
132
+ { key: "PUBLIC_SETTING", value: "visible", isSecret: false, exportToEnv: true },
133
+ { key: "HIDDEN_SETTING", value: "hidden", isSecret: false, exportToEnv: false },
134
+ // A pre-215 API sends no flag: the row keeps exporting.
135
+ { key: "LEGACY_SECRET", value: "row-legacy", isSecret: true },
136
+ // Restricted keys never export, whatever the row says.
137
+ { key: "SLACK_APP_TOKEN", value: "row-xapp", isSecret: true, exportToEnv: true },
138
+ ],
139
+ },
140
+ });
141
+ const log = spyOn(console, "log").mockImplementation(() => {});
142
+ try {
143
+ // The base env stands for the container env or an older entrypoint's
144
+ // boot export; a withheld row must remove those copies too.
145
+ const result = await fetchResolvedEnv(testUrl, "key", agentId, {
146
+ VERCEL_TOKEN: "boot-vercel",
147
+ SLACK_APP_TOKEN: "boot-xapp",
148
+ });
149
+ expect(result.env.VERCEL_TOKEN).toBeUndefined();
150
+ expect(result.env.HIDDEN_SETTING).toBeUndefined();
151
+ expect(result.env.SLACK_APP_TOKEN).toBeUndefined();
152
+ expect(result.env.GITHUB_TOKEN).toBe("row-github");
153
+ expect(result.env.PUBLIC_SETTING).toBe("visible");
154
+ expect(result.env.LEGACY_SECRET).toBe("row-legacy");
155
+ const logged = log.mock.calls.map((call) => String(call[0])).join("\n");
156
+ expect(logged).toContain("HIDDEN_SETTING, SLACK_APP_TOKEN, VERCEL_TOKEN");
157
+ expect(logged).not.toContain("row-vercel");
158
+ expect(logged).not.toContain("boot-vercel");
159
+ } finally {
160
+ log.mockRestore();
161
+ }
162
+ });
163
+
164
+ test("a row revoked after boot leaves process.env, so merged adapter envs lose it", async () => {
165
+ const agentId = crypto.randomUUID();
166
+ const key = "REVOKED_AFTER_BOOT_SECRET";
167
+ mockResponsesByAgentId.set(agentId, {
168
+ status: 200,
169
+ body: { configs: [{ key, value: "row-value", isSecret: true, exportToEnv: false }] },
170
+ });
171
+ // The entrypoint exported the row at boot, before it was switched off.
172
+ process.env[key] = "boot-export";
173
+ const log = spyOn(console, "log").mockImplementation(() => {});
174
+ try {
175
+ const result = await fetchResolvedEnv(testUrl, "key", agentId);
176
+ expect(result.env[key]).toBeUndefined();
177
+ expect(process.env[key]).toBeUndefined();
178
+ // The amp, dsh, grok, cursor and claude adapters spawn with this shape.
179
+ expect({ ...process.env, ...result.env }[key]).toBeUndefined();
180
+ } finally {
181
+ delete process.env[key];
182
+ log.mockRestore();
183
+ }
184
+ });
185
+
186
+ test("a masked row keeps its boot value only when the row is exported", async () => {
187
+ const agentId = crypto.randomUUID();
188
+ mockResponsesByAgentId.set(agentId, {
189
+ status: 200,
190
+ body: {
191
+ configs: [
192
+ { key: "EXPORTED_BOOT", value: MASKED_SECRET_VALUE, isSecret: true, exportToEnv: true },
193
+ { key: "WITHHELD_BOOT", value: MASKED_SECRET_VALUE, isSecret: true, exportToEnv: false },
194
+ ],
195
+ },
196
+ });
197
+ const warning = spyOn(console, "warn").mockImplementation(() => {});
198
+ const log = spyOn(console, "log").mockImplementation(() => {});
199
+ try {
200
+ const result = await fetchResolvedEnv(testUrl, "key", agentId, {
201
+ EXPORTED_BOOT: "boot-exported",
202
+ WITHHELD_BOOT: "boot-withheld",
203
+ });
204
+ expect(result.env.EXPORTED_BOOT).toBe("boot-exported");
205
+ expect(result.env.WITHHELD_BOOT).toBeUndefined();
206
+ } finally {
207
+ warning.mockRestore();
208
+ log.mockRestore();
209
+ }
210
+ });
211
+
123
212
  test("repository scope requires the requested repository identity", async () => {
124
213
  const repoId = crypto.randomUUID();
125
214
  let url = "https://github.com/another-owner/fixture";
@@ -305,6 +394,45 @@ describe("fetchResolvedEnv", () => {
305
394
  ]);
306
395
  });
307
396
 
397
+ test("HELMCODE_API_NAN alias becomes NAN_API_KEY and pools for a nan/* model", async () => {
398
+ const agentId = crypto.randomUUID();
399
+ mockResponsesByAgentId.set(agentId, {
400
+ status: 200,
401
+ body: { configs: [{ key: "HELMCODE_API_NAN", value: "fixture-nan-a,fixture-nan-b" }] },
402
+ });
403
+ const result = await fetchResolvedEnv(testUrl, "key", agentId, {}, "nan/glm5.3-flash", {
404
+ provider: "opencode",
405
+ });
406
+ expect(result.env.NAN_API_KEY).toMatch(/^fixture-nan-[ab]$/);
407
+ expect(result.credentialSelections.map((selection) => selection.keyType)).toEqual([
408
+ "NAN_API_KEY",
409
+ ]);
410
+ });
411
+
412
+ test("an explicit NAN_API_KEY wins over the HELMCODE_API_NAN alias", async () => {
413
+ const agentId = crypto.randomUUID();
414
+ mockResponsesByAgentId.set(agentId, {
415
+ status: 200,
416
+ body: {
417
+ configs: [
418
+ { key: "NAN_API_KEY", value: "fixture-nan-canonical" },
419
+ { key: "HELMCODE_API_NAN", value: "fixture-nan-alias" },
420
+ ],
421
+ },
422
+ });
423
+ const result = await fetchResolvedEnv(testUrl, "key", agentId, {}, "nan/qwen3.6", {
424
+ provider: "pi",
425
+ });
426
+ expect(result.env.NAN_API_KEY).toBe("fixture-nan-canonical");
427
+ });
428
+
429
+ test("the alias also normalizes from the boot env when the API is unreachable", async () => {
430
+ const result = await fetchResolvedEnv("", "key", "agent-1", {
431
+ HELMCODE_API_NAN: "fixture-nan-boot",
432
+ });
433
+ expect(result.env.NAN_API_KEY).toBe("fixture-nan-boot");
434
+ });
435
+
308
436
  describe("model-scoped window filter — GET /api/keys/available?model=<family>", () => {
309
437
  beforeEach(() => {
310
438
  keysAvailableRequests.length = 0;
@@ -0,0 +1,10 @@
1
+ {
2
+ "update_id": 900001,
3
+ "message": {
4
+ "message_id": 11,
5
+ "date": 1760400000,
6
+ "chat": { "id": 555001, "type": "private", "first_name": "Taras", "username": "taras_test" },
7
+ "from": { "id": 555001, "is_bot": false, "first_name": "Taras", "username": "taras_test" },
8
+ "text": "hello swarm, please summarize the open tasks"
9
+ }
10
+ }
@@ -0,0 +1,27 @@
1
+ import { modify, type SwarmExtension } from "swarm-extension";
2
+ import { z } from "zod";
3
+
4
+ export const config = z.object({ adapter: z.string(), channelId: z.string(), agentId: z.string() });
5
+
6
+ const manifest = {
7
+ name: "route-chat-channel-to-agent",
8
+ description: "Routes one Chat SDK channel to a configured agent",
9
+ version: "1.0.0",
10
+ runtime: "api",
11
+ assets: { hooks: "hooks.ts" },
12
+ config,
13
+ } as const;
14
+
15
+ const extension: SwarmExtension<typeof manifest> = (api) => {
16
+ api.on("pre.chat.route", (event, ctx) => {
17
+ if (event.adapter !== ctx.config.adapter || event.channelId !== ctx.config.channelId) return;
18
+ return modify({ target: { kind: "agent", agentId: ctx.config.agentId } });
19
+ });
20
+
21
+ api.on("post.chat.message", async (event, ctx) => {
22
+ if (event.direction !== "inbound" || !event.userId) return;
23
+ await ctx.state.set(`chat:${event.channelId}`, event.userId);
24
+ });
25
+ };
26
+
27
+ export default extension;
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "route-chat-channel-to-agent",
3
+ "description": "Routes one Chat SDK channel to a configured agent",
4
+ "version": "1.0.0",
5
+ "runtime": "api",
6
+ "assets": {
7
+ "hooks": "hooks.ts"
8
+ }
9
+ }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Proof that adding an OpenAI-compatible provider takes one registry entry.
3
+ *
4
+ * A fake `testgw` entry is added to the registry table
5
+ * (`packages/model-catalog/src/openai-compatible-providers.ts`) through
6
+ * `mock.module`, without editing any production file, and must reach
7
+ * credential routing, the opencode config, the pi models.json writer, the
8
+ * dashboard integration cards and the model picker groups.
9
+ *
10
+ * The mock is installed before any consumer below is imported (dynamic
11
+ * imports). Consumers cache derivations of the table at import (the
12
+ * integration cards, the picker constants), and `mock.module` cannot undo
13
+ * those, so this file only runs in its own `bun test` child process,
14
+ * spawned by `src/tests/openai-compatible-registry-extension.test.ts`. Its
15
+ * name keeps it out of the root test glob.
16
+ */
17
+ import { afterAll, describe, expect, mock, test } from "bun:test";
18
+ import { mkdtemp, readFile, rm } from "node:fs/promises";
19
+ import { tmpdir } from "node:os";
20
+ import { join } from "node:path";
21
+
22
+ const REGISTRY_MODULE = "../../../packages/model-catalog/src/openai-compatible-providers.ts";
23
+
24
+ const TESTGW = {
25
+ id: "testgw",
26
+ displayName: "TestGW",
27
+ description: "A fake gateway for the registry extension test.",
28
+ baseUrl: "https://testgw.invalid/v1",
29
+ docsUrl: "https://testgw.invalid/docs",
30
+ envVar: "TESTGW_API_KEY",
31
+ aliasEnvVars: [],
32
+ catalog: "registry",
33
+ npm: "@ai-sdk/openai-compatible",
34
+ models: [
35
+ {
36
+ id: "m1",
37
+ name: "Test Model 1",
38
+ contextWindow: 131072,
39
+ maxOutputTokens: 8192,
40
+ input: ["text"],
41
+ reasoning: true,
42
+ compat: { supportsDeveloperRole: true },
43
+ reasoningEfforts: ["low", "high"],
44
+ },
45
+ ],
46
+ } as const;
47
+
48
+ const real = (await import(REGISTRY_MODULE)) as {
49
+ OPENAI_COMPATIBLE_PROVIDERS: Record<string, unknown>;
50
+ };
51
+ const original = real.OPENAI_COMPATIBLE_PROVIDERS;
52
+ mock.module(REGISTRY_MODULE, () => ({
53
+ OPENAI_COMPATIBLE_PROVIDERS: { ...original, testgw: TESTGW },
54
+ }));
55
+
56
+ // The test preload already loaded `src/utils/credentials.ts`, so only its
57
+ // call-time paths (routing, pooling) see the mock; `CREDENTIAL_POOL_VARS` is
58
+ // fixed at import and covered by the registry spread in its declaration.
59
+ const { getModelAwareCredentialVars, resolveCredentialPools } = await import(
60
+ "../../utils/credentials"
61
+ );
62
+ const { applyProviderKeySplice } = await import("../../providers/opencode-adapter");
63
+ type OpencodeConfig = import("../../providers/opencode-adapter").OpencodeConfig;
64
+ const { writeSessionModelsJson } = await import("../../providers/pi-mono-adapter");
65
+ const { INTEGRATIONS } = await import("../../../apps/ui/src/lib/integrations-catalog");
66
+ const { modelGroupsForHarness } = await import("../../../apps/ui/src/lib/agent-runtime-models");
67
+
68
+ const tmpDirs: string[] = [];
69
+ afterAll(async () => {
70
+ for (const dir of tmpDirs) await rm(dir, { recursive: true, force: true });
71
+ });
72
+
73
+ describe("a new registry entry (testgw) reaches every derived surface", () => {
74
+ test("credential routing picks its key on pi and opencode", () => {
75
+ expect(getModelAwareCredentialVars("pi", "testgw/x")).toEqual(["TESTGW_API_KEY"]);
76
+ expect(getModelAwareCredentialVars("opencode", "testgw/x")).toEqual(["TESTGW_API_KEY"]);
77
+ });
78
+
79
+ test("its key pools alone for a testgw/* model", async () => {
80
+ const env: Record<string, string | undefined> = {
81
+ TESTGW_API_KEY: "example-gw-a,example-gw-b",
82
+ OPENROUTER_API_KEY: "example-or-a,example-or-b",
83
+ };
84
+ const selections = await resolveCredentialPools(env, { provider: "pi", model: "testgw/m1" });
85
+ expect(["example-gw-a", "example-gw-b"]).toContain(env.TESTGW_API_KEY ?? "");
86
+ expect(selections.map((s) => [s.keyType, s.total])).toEqual([["TESTGW_API_KEY", 2]]);
87
+ expect(env.OPENROUTER_API_KEY).toBe("example-or-a,example-or-b");
88
+ });
89
+
90
+ test("opencode gets a custom block with the picked key at the base URL", () => {
91
+ const config: OpencodeConfig = {};
92
+ const picked = applyProviderKeySplice(config, "testgw/m1", { TESTGW_API_KEY: "k1,k2" });
93
+ expect(picked).toEqual({ key: "k1", name: "TESTGW_API_KEY" });
94
+ const block = config.provider?.testgw;
95
+ expect(block?.npm).toBe("@ai-sdk/openai-compatible");
96
+ expect(block?.env).toEqual([]);
97
+ expect(block?.options).toEqual({ baseURL: "https://testgw.invalid/v1", apiKey: "k1" });
98
+ expect(block?.models?.m1).toMatchObject({ name: "Test Model 1", tool_call: true });
99
+
100
+ const overridden: OpencodeConfig = {};
101
+ applyProviderKeySplice(overridden, "testgw/m1", {
102
+ TESTGW_API_KEY: "k1",
103
+ TESTGW_BASE_URL: "http://127.0.0.1:9/v1",
104
+ });
105
+ expect(overridden.provider?.testgw?.options?.baseURL).toBe("http://127.0.0.1:9/v1");
106
+ });
107
+
108
+ test("the pi models.json writer adds its provider block", async () => {
109
+ const dir = await mkdtemp(join(tmpdir(), "testgw-pi-"));
110
+ tmpDirs.push(dir);
111
+ const models = await writeSessionModelsJson(dir, { TESTGW_API_KEY: "k1" }, "testgw/m1");
112
+ const written = JSON.parse(await readFile(models.modelsPath as string, "utf-8"));
113
+ await models.dispose();
114
+ expect(written.providers.testgw).toMatchObject({
115
+ baseUrl: "https://testgw.invalid/v1",
116
+ api: "openai-completions",
117
+ apiKey: "$TESTGW_API_KEY",
118
+ });
119
+ expect(written.providers.testgw.models.map((m: { id: string }) => m.id)).toEqual(["m1"]);
120
+ });
121
+
122
+ test("the dashboard lists an integration card with its key and base URL", () => {
123
+ const card = INTEGRATIONS.find((def) => def.id === "testgw");
124
+ expect(card?.name).toBe("TestGW");
125
+ expect(card?.providerIconKey as string | undefined).toBe("testgw");
126
+ expect(card?.fields.map((f) => [f.key, f.isSecret])).toEqual([
127
+ ["TESTGW_API_KEY", true],
128
+ ["TESTGW_BASE_URL", false],
129
+ ]);
130
+ });
131
+
132
+ test("the picker shows its models, locked without a key and enabled with one", () => {
133
+ for (const harness of ["pi", "opencode"] as const) {
134
+ const locked = modelGroupsForHarness(harness, [], {}).find((g) => g.provider === "TestGW");
135
+ expect(locked?.enabled).toBe(false);
136
+ expect(locked?.requiredKey).toBe("TESTGW_API_KEY");
137
+ expect(locked?.models.map((m) => m.id)).toEqual(["testgw/m1"]);
138
+ expect(locked?.models[0]?.reasoningLevels).toEqual(["low", "high"]);
139
+
140
+ const enabled = modelGroupsForHarness(harness, [], { TESTGW_API_KEY: true }).find(
141
+ (g) => g.provider === "TestGW",
142
+ );
143
+ expect(enabled?.enabled).toBe(true);
144
+ }
145
+ });
146
+ });