@desplega.ai/agent-swarm 1.172.0 → 2.0.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 (525) hide show
  1. package/README.md +1 -1
  2. package/dist/{acp-adapter-fpecx1g1.js → acp-adapter-sj2g0m84.js} +18 -17
  3. package/dist/{actions-pw87a1a8.js → actions-fegk44gr.js} +65 -35
  4. package/dist/{amp-adapter-8jem2bc0.js → amp-adapter-ad8ft89x.js} +8 -7
  5. package/dist/{anthropic-messages-8ee2f22q.js → anthropic-messages-r6czkjv1.js} +7 -7
  6. package/dist/{app-wmf0m843.js → app-kfvgbvzy.js} +19 -13
  7. package/dist/{assistant-emp1e108.js → assistant-z7zdy9ja.js} +65 -38
  8. package/dist/{boot-reembed-1de4f4aj.js → boot-reembed-5c2z0mbe.js} +11 -9
  9. package/dist/{boot-reembed-4ymn9atn.js → boot-reembed-j8xa181e.js} +11 -9
  10. package/dist/{boot-scrub-sweep-adhk1s8c.js → boot-scrub-sweep-hnkn3rgt.js} +12 -10
  11. package/dist/{boot-seeds-8q637aew.js → boot-seeds-kwjq4d2z.js} +15 -13
  12. package/dist/{claude-adapter-t26cv8a4.js → claude-adapter-9vmvpvgd.js} +20 -19
  13. package/dist/{claude-managed-adapter-xgxhqrt2.js → claude-managed-adapter-k9qae05j.js} +7 -5
  14. package/dist/{claude-managed-setup-bnmjarjc.js → claude-managed-setup-q54zx5q2.js} +6 -4
  15. package/dist/{claude-sdk-session-gsfsh662.js → claude-sdk-session-9jye6cxr.js} +12698 -11995
  16. package/dist/{cli-k46r9hmk.js → cli-03gedhng.js} +16 -14
  17. package/dist/{cli-kb4ehzx5.js → cli-1e4b6bzh.js} +7 -7
  18. package/dist/{cli-d603qf86.js → cli-29jc5q7w.js} +10 -8
  19. package/dist/cli-2m1ktfzz.js +17795 -0
  20. package/dist/{cli-8vs9trd4.js → cli-3dj8k4vy.js} +1 -1
  21. package/dist/{cli-zxm9p3x4.js → cli-3fbmmawm.js} +5 -5
  22. package/dist/{cli-gmdajfw5.js → cli-4jxm2qtz.js} +4987 -1708
  23. package/dist/{cli-tadfgd0t.js → cli-5hkm7whv.js} +33 -4
  24. package/dist/{cli-6jb2rf77.js → cli-5t4f29ez.js} +3 -3
  25. package/dist/{cli-7a0sde2x.js → cli-6cagzc96.js} +9 -0
  26. package/dist/cli-6cvfxs8r.js +47 -0
  27. package/dist/{cli-pj6x9qce.js → cli-6jyj59nb.js} +2 -2
  28. package/dist/{cli-388egyf6.js → cli-6razj6t4.js} +2 -2
  29. package/dist/{cli-qsztpfry.js → cli-74ahz61x.js} +2 -2
  30. package/dist/{cli-4nz8p1p5.js → cli-7x0g4sn1.js} +15474 -16082
  31. package/dist/{cli-yatxqynm.js → cli-9ha6hs2d.js} +1 -290
  32. package/dist/{cli-81r1wjyv.js → cli-9nfx44eh.js} +1 -1
  33. package/dist/cli-9traj3dn.js +638 -0
  34. package/dist/{cli-smcrrmhy.js → cli-agzebbd5.js} +9 -9
  35. package/dist/{cli-bf7485pm.js → cli-apx87jne.js} +2 -2
  36. package/dist/{cli-ztrv9y21.js → cli-bzszjf2p.js} +3689 -1081
  37. package/dist/{cli-c5km5znr.js → cli-c8r3kzzb.js} +1 -1
  38. package/dist/{cli-hsem16hb.js → cli-cgtksawx.js} +7 -4
  39. package/dist/{cli-db3m6zdt.js → cli-d2qr7aj4.js} +1 -1
  40. package/dist/{cli-1hgat6pa.js → cli-d5f9ed5c.js} +5 -5
  41. package/dist/cli-e3g5mbkz.js +776 -0
  42. package/dist/{cli-7jykv7ef.js → cli-e6mpjhed.js} +1 -1
  43. package/dist/{cli-j6kpj9az.js → cli-ee9gtz70.js} +1553 -68
  44. package/dist/{cli-e0a9sk00.js → cli-eejsgvm4.js} +4 -4
  45. package/dist/{cli-tgpfnm6r.js → cli-ef1hg1wv.js} +518 -59
  46. package/dist/cli-f527ce74.js +781 -0
  47. package/dist/{cli-ppy4nnnh.js → cli-f7kr7svz.js} +2 -2
  48. package/dist/cli-hjwmhcg1.js +10622 -0
  49. package/dist/{cli-2bw3w47j.js → cli-hnhtk6cj.js} +1 -1
  50. package/dist/{cli-ywm1azg9.js → cli-hnmhtzvh.js} +6 -4
  51. package/dist/{cli-4fnb6884.js → cli-hvqbtna4.js} +7 -1259
  52. package/dist/{cli-pstq9888.js → cli-j0pfptdp.js} +220 -103
  53. package/dist/cli-jta2eyzc.js +619 -0
  54. package/dist/{cli-esnpehf5.js → cli-k382tshb.js} +12 -13
  55. package/dist/cli-k3dp5tpf.js +263 -0
  56. package/dist/{cli-wd0hz6ah.js → cli-kbrt8gqa.js} +9 -91
  57. package/dist/cli-kceah1eh.js +92 -0
  58. package/dist/{cli-prrt9fa0.js → cli-ke896fcf.js} +10 -10
  59. package/dist/{cli-nv4zd0b9.js → cli-kt6zqg0e.js} +6 -6
  60. package/dist/{cli-g6t9texm.js → cli-m50t4309.js} +5 -18
  61. package/dist/cli-n2wq4j7m.js +6985 -0
  62. package/dist/{cli-c2pq2rjb.js → cli-n3hzg89b.js} +46 -15
  63. package/dist/{cli-tmhsxhb8.js → cli-n5vwg842.js} +23 -6
  64. package/dist/{cli-5rv1j9ga.js → cli-ne236afy.js} +53 -7
  65. package/dist/cli-p0sg24zp.js +16 -0
  66. package/dist/{cli-d161g69q.js → cli-p1x85d2t.js} +50 -2
  67. package/dist/{cli-7pz3b7x5.js → cli-przpmdgr.js} +25 -0
  68. package/dist/{cli-g4f5pj26.js → cli-q0ke1yww.js} +111 -25
  69. package/dist/{cli-e3pmyt39.js → cli-q50942rs.js} +1 -1
  70. package/dist/{cli-r44t9wqc.js → cli-r106725k.js} +2 -2
  71. package/dist/{cli-gj81e47t.js → cli-r2pnvj8g.js} +2 -2
  72. package/dist/{cli-r8smj8b8.js → cli-r62a35ea.js} +19 -3
  73. package/dist/{cli-cd0hv9nh.js → cli-ra0xzggj.js} +2 -2
  74. package/dist/cli-rhs9pz33.js +294 -0
  75. package/dist/{cli-nyhb1r6x.js → cli-s2sp957d.js} +52 -29
  76. package/dist/{cli-1x7da0de.js → cli-s4setq91.js} +8 -8
  77. package/dist/{cli-xczpdp9e.js → cli-se8w6wny.js} +6 -6
  78. package/dist/cli-skxvvpap.js +10140 -0
  79. package/dist/{cli-014vns2y.js → cli-stfs0k5s.js} +208 -525
  80. package/dist/{cli-0p3enk35.js → cli-tqaazemc.js} +9 -1
  81. package/dist/{cli-cf69s3ew.js → cli-tr4sxp68.js} +3 -3
  82. package/dist/{cli-8hzfb6xz.js → cli-tsde1hdr.js} +15 -15
  83. package/dist/{cli-cceskcvt.js → cli-txqb9s38.js} +8 -8
  84. package/dist/{cli-aspcaef3.js → cli-v1yb0rqb.js} +6 -6
  85. package/dist/{cli-j1pmc07z.js → cli-vatyhshy.js} +268 -40
  86. package/dist/{cli-04emfe2q.js → cli-vdach5a9.js} +5 -5
  87. package/dist/{cli-7avy4v8w.js → cli-w1q7h2r2.js} +5 -3
  88. package/dist/{cli-rcp5aqdw.js → cli-w7m81tb7.js} +1 -1
  89. package/dist/{cli-hwwvy36w.js → cli-wayw93nt.js} +3 -3
  90. package/dist/cli-wmgrkscj.js +23254 -0
  91. package/dist/{cli-gp1rtyvk.js → cli-z5jjpaxw.js} +29 -6
  92. package/dist/{cli-451ks6aw.js → cli-zkpks3r3.js} +1 -1
  93. package/dist/cli.js +32 -48
  94. package/dist/{codex-adapter-m4tgdysy.js → codex-adapter-y81sssk0.js} +16 -15
  95. package/dist/{codex-login-841kv5fk.js → codex-login-f2rcfpp9.js} +3 -2
  96. package/dist/{codex-session-runner-ymwzm6ea.js → codex-session-runner-8vx4j6a9.js} +16 -15
  97. package/dist/{commands-n0hshcvs.js → commands-kcpvpcrr.js} +21 -10
  98. package/dist/{controller-hq5ggpcp.js → controller-ywrmt6yd.js} +5 -4
  99. package/dist/{controllers-connect-y07cqcvy.js → controllers-connect-kwpvj3rf.js} +45 -38
  100. package/dist/{cursor-adapter-q8pgv5qa.js → cursor-adapter-cehv1qns.js} +7 -6
  101. package/dist/{db-hkqvrdcb.js → db-k8ankj20.js} +13 -11
  102. package/dist/{db-4w7tmt1g.js → db-ww5d7jq3.js} +29 -9
  103. package/dist/{devin-adapter-xgtj680j.js → devin-adapter-x1022qb1.js} +3 -2
  104. package/dist/{dispatcher-5sd69y14.js → dispatcher-e8gq5m68.js} +41 -34
  105. package/dist/{docker-f503k9p0.js → docker-ff6y1190.js} +3 -2
  106. package/dist/{dsh-adapter-2h8m4cab.js → dsh-adapter-3epjm3af.js} +7 -6
  107. package/dist/{e2b-66xra4az.js → e2b-8ygwhg0f.js} +4 -3
  108. package/dist/{e2b-xwhyj8cw.js → e2b-swhnpw3n.js} +4 -3
  109. package/dist/{event-bus-vjtv82gm.js → event-bus-6axnngdj.js} +3 -2
  110. package/dist/external-channel-h3359cwn.js +52 -0
  111. package/dist/{google-generative-ai-7pavrbv7.js → google-generative-ai-g8db7x0f.js} +5 -3
  112. package/dist/{google-vertex-3f1jk41f.js → google-vertex-r2692q0r.js} +5 -3
  113. package/dist/{grok-adapter-8m9yqzbq.js → grok-adapter-0d4x7gkx.js} +19 -18
  114. package/dist/{handlers-s20xr10r.js → handlers-an1gzv21.js} +44 -37
  115. package/dist/{heartbeat-3k31q4g2.js → heartbeat-6dakeb61.js} +46 -39
  116. package/dist/{hook-gdn6a2fn.js → hook-6v6cv6d5.js} +12 -24
  117. package/dist/{hook-ygwerrdh.js → hook-sa5xye4y.js} +13 -12
  118. package/dist/{http-jdteeg81.js → http-r7f3eky0.js} +1598 -279
  119. package/dist/index-52j1aja4.js +3465 -0
  120. package/dist/{index-e0snym4t.js → index-7ep4gzdk.js} +41 -34
  121. package/dist/index-9gw58n4k.js +193 -0
  122. package/dist/index-9j87mcm8.js +56305 -0
  123. package/dist/index-bsmcresk.js +79424 -0
  124. package/dist/index-mh85qhec.js +475 -0
  125. package/dist/{index-vm3bm9t7.js → index-rmpw9a8q.js} +41 -34
  126. package/dist/{index-6ksewd5y.js → index-s2wxgzr0.js} +1015 -732
  127. package/dist/{index-tsszt7g9.js → index-v1v6n7hg.js} +46 -39
  128. package/dist/index-we27kd31.js +12810 -0
  129. package/dist/{index-ffqzqgwb.js → index-zk96aq1k.js} +11 -9
  130. package/dist/{keepalive-jekc896t.js → keepalive-be9vwy5h.js} +13 -11
  131. package/dist/{lead-4k9s9szx.js → lead-0861cwrf.js} +64 -57
  132. package/dist/{maintenance-shazch4n.js → maintenance-mzzgc8bn.js} +11 -9
  133. package/dist/node-c9we95xf.js +1 -1
  134. package/dist/{node-fkajaq96.js → node-mn1zs7an.js} +2 -1
  135. package/dist/{oauth-refresh-sweep-5gb36kn8.js → oauth-refresh-sweep-cpdnfwdj.js} +12 -10
  136. package/dist/{onboard-ydrbnvww.js → onboard-fh8qs4r3.js} +11 -3
  137. package/dist/{opencode-adapter-5zh0he20.js → opencode-adapter-nm42wwj8.js} +19 -14
  138. package/dist/{otel-impl-6pf5cv86.js → otel-impl-x6c1c9g4.js} +248 -869
  139. package/dist/{pi-mono-adapter-ef9wwx9m.js → pi-mono-adapter-9n2ya240.js} +245 -107
  140. package/dist/{pricing-refresh-s52nsa5q.js → pricing-refresh-qb0rv0ap.js} +13 -11
  141. package/dist/{rbac-roles-aq3msh4e.js → rbac-roles-1gzdwbh1.js} +12 -10
  142. package/dist/{rbac-roles-q5gb5e8v.js → rbac-roles-3mraey52.js} +13 -11
  143. package/dist/{render-v2-pd1rg54q.js → render-v2-agpa45zz.js} +19 -13
  144. package/dist/{resume-gw4a2c6v.js → resume-x0ydy9s9.js} +27 -21
  145. package/dist/{runtime-qfry88v2.js → runtime-sa2vr16g.js} +2 -2
  146. package/dist/{setup-jda1ksdh.js → setup-as9ga0d6.js} +2 -2
  147. package/dist/{virtual-modules-b3ga2m65.js → virtual-modules-9gkxqrap.js} +7 -6
  148. package/dist/{work-objects-tb7ycye8.js → work-objects-y5g1y7ev.js} +49 -37
  149. package/dist/{worker-njjccz5d.js → worker-860c3e38.js} +64 -57
  150. package/dist/{x-qfmygqqv.js → x-m477fr0h.js} +3 -2
  151. package/openapi.json +951 -356
  152. package/package.json +12 -13
  153. package/src/apps/catalog.generated.json +1309 -53
  154. package/src/apps/page-validator.ts +93 -31
  155. package/src/azure-devops/api.ts +94 -18
  156. package/src/azure-devops/auth.ts +32 -0
  157. package/src/azure-devops/boards-sync.ts +708 -0
  158. package/src/azure-devops/handlers.ts +52 -8
  159. package/src/azure-devops/html.ts +26 -0
  160. package/src/azure-devops/index.ts +15 -0
  161. package/src/azure-devops/templates.ts +86 -0
  162. package/src/azure-devops/types.ts +65 -1
  163. package/src/be/config-refs.ts +60 -0
  164. package/src/be/db/page-versions.ts +15 -0
  165. package/src/be/db/runtime.ts +35 -8
  166. package/src/be/db/skill-name-resolution.ts +36 -0
  167. package/src/be/db/tasks/read.ts +230 -0
  168. package/src/be/db/tasks/write.ts +7 -0
  169. package/src/be/db.ts +74 -13
  170. package/src/be/migrations/213_script_connections_sql.sql +79 -0
  171. package/src/be/migrations/215_swarm_config_export_to_env.sql +34 -0
  172. package/src/be/migrations/216_task_resolved_effort.sql +5 -0
  173. package/src/be/migrations/217_chat_sdk_state.sql +104 -0
  174. package/src/be/model-catalog-store.ts +5 -1
  175. package/src/be/model-tier-resolution.ts +12 -1
  176. package/src/be/model-validation.ts +5 -2
  177. package/src/be/models-catalog.ts +26 -1
  178. package/src/be/modelsdev-cache.json +198 -63
  179. package/src/be/modelsdev-cache.ts +9 -1
  180. package/src/be/script-connections.ts +47 -8
  181. package/src/be/script-credential-broker.ts +39 -2
  182. package/src/be/scripts/run-saved.ts +1 -0
  183. package/src/be/scripts/typecheck.ts +10 -8
  184. package/src/be/seed/runner.ts +13 -1
  185. package/src/be/seed/state-db.ts +5 -0
  186. package/src/be/seed/types.ts +6 -0
  187. package/src/be/seed-pricing.ts +19 -2
  188. package/src/be/seed-scripts/catalog/ops-catalog-audit.inline.ts +1 -1
  189. package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +1 -1
  190. package/src/be/seed-skills/bundled-files.generated.json +10 -18
  191. package/src/be/seed-skills/index.ts +107 -3
  192. package/src/be/sql-connections.ts +118 -0
  193. package/src/be/swarm-config-guard.ts +53 -0
  194. package/src/cli.tsx +0 -16
  195. package/src/commands/onboard/non-interactive.ts +4 -2
  196. package/src/commands/profile-sync.ts +5 -3
  197. package/src/commands/provider-credentials.ts +52 -6
  198. package/src/commands/runner.ts +196 -56
  199. package/src/e2b/env.ts +5 -0
  200. package/src/extensions/contract-types.generated.ts +1 -1
  201. package/src/extensions/contract.ts +45 -0
  202. package/src/extensions/dispatcher.ts +7 -0
  203. package/src/extensions/post-bridge.ts +40 -0
  204. package/src/github/handlers.ts +11 -4
  205. package/src/heartbeat/templates.ts +8 -5
  206. package/src/hooks/hook.ts +0 -26
  207. package/src/http/all-routes.ts +0 -1
  208. package/src/http/config.ts +36 -17
  209. package/src/http/core.ts +4 -0
  210. package/src/http/index.ts +14 -2
  211. package/src/http/integrations.ts +88 -1
  212. package/src/http/mcp-oauth.ts +6 -13
  213. package/src/http/mcp.ts +15 -5
  214. package/src/http/page-proxy.ts +2 -4
  215. package/src/http/pages-public.ts +6 -5
  216. package/src/http/pages.ts +5 -14
  217. package/src/http/poll.ts +28 -68
  218. package/src/http/requested-by.ts +54 -0
  219. package/src/http/route-def.ts +7 -6
  220. package/src/http/script-connection-proxy.ts +51 -4
  221. package/src/http/script-connections.ts +5 -2
  222. package/src/http/scripts.ts +1 -0
  223. package/src/http/session-cost-recompute.ts +12 -2
  224. package/src/http/session-data.ts +4 -0
  225. package/src/http/stats.ts +1 -30
  226. package/src/http/tasks.ts +61 -5
  227. package/src/http/utils.ts +14 -0
  228. package/src/http/webhooks.ts +183 -2
  229. package/src/http/x.ts +1 -0
  230. package/src/integrations/chat-sdk/adapters.ts +129 -0
  231. package/src/integrations/chat-sdk/attachments.ts +232 -0
  232. package/src/integrations/chat-sdk/background.ts +36 -0
  233. package/src/integrations/chat-sdk/commands.ts +194 -0
  234. package/src/integrations/chat-sdk/config.ts +198 -0
  235. package/src/integrations/chat-sdk/discord-api.ts +63 -0
  236. package/src/integrations/chat-sdk/discord-gateway.ts +185 -0
  237. package/src/integrations/chat-sdk/handlers.ts +28 -0
  238. package/src/integrations/chat-sdk/inbound.ts +426 -0
  239. package/src/integrations/chat-sdk/limits.ts +48 -0
  240. package/src/integrations/chat-sdk/logger.ts +81 -0
  241. package/src/integrations/chat-sdk/outbound.ts +123 -0
  242. package/src/integrations/chat-sdk/relay.ts +172 -0
  243. package/src/integrations/chat-sdk/router.ts +169 -0
  244. package/src/integrations/chat-sdk/runtime.ts +351 -0
  245. package/src/integrations/chat-sdk/schema.ts +245 -0
  246. package/src/integrations/chat-sdk/state.ts +243 -0
  247. package/src/integrations/chat-sdk/status.ts +244 -0
  248. package/src/integrations/chat-sdk/steering.ts +58 -0
  249. package/src/integrations/chat-sdk/teams-origin.ts +77 -0
  250. package/src/integrations/chat-sdk/telegram-api.ts +56 -0
  251. package/src/integrations/chat-sdk/templates.ts +16 -0
  252. package/src/integrations/chat-sdk/test-hooks.ts +37 -0
  253. package/src/integrations/chat-sdk/thread-buffer.ts +100 -0
  254. package/src/integrations/chat-sdk/typing.ts +65 -0
  255. package/src/jira/adf.ts +3 -1
  256. package/src/kv-reserved-namespaces.ts +2 -0
  257. package/src/oauth/mcp-wrapper.ts +10 -6
  258. package/src/otel-impl.ts +2 -1
  259. package/src/pages/version.ts +4 -5
  260. package/src/prompts/base-prompt.ts +111 -45
  261. package/src/providers/acp-target-catalog.ts +6 -0
  262. package/src/providers/acp-targets.ts +56 -3
  263. package/src/providers/amp-adapter.ts +1 -1
  264. package/src/providers/claude-adapter.ts +27 -2
  265. package/src/providers/claude-flag-settings.ts +133 -0
  266. package/src/providers/claude-managed-adapter.ts +1 -1
  267. package/src/providers/claude-sdk-session.ts +7 -0
  268. package/src/providers/claude-session-events.ts +6 -1
  269. package/src/providers/dsh-adapter.ts +1 -1
  270. package/src/providers/opencode-adapter.ts +268 -37
  271. package/src/providers/opencode-billing.ts +86 -0
  272. package/src/providers/opencode-swarm-plugin.ts +51 -0
  273. package/src/providers/pi-mono-adapter.ts +398 -153
  274. package/src/providers/pricing-sources.md +37 -3
  275. package/src/providers/reasoning-effort.ts +27 -0
  276. package/src/providers/types.ts +11 -2
  277. package/src/rbac/legacy-policy.ts +8 -2
  278. package/src/rbac/permissions.ts +21 -0
  279. package/src/scheduler/scheduler.ts +4 -2
  280. package/src/scripts-runtime/api-client.ts +15 -1
  281. package/src/scripts-runtime/api-types.ts +9 -1
  282. package/src/scripts-runtime/ctx.ts +1 -1
  283. package/src/scripts-runtime/loader.ts +19 -7
  284. package/src/scripts-runtime/mcp-client.ts +30 -19
  285. package/src/scripts-runtime/sdk-allowlist.ts +8 -6
  286. package/src/scripts-runtime/swarm-sdk.ts +0 -11
  287. package/src/scripts-runtime/types/stdlib.d.ts +27 -15
  288. package/src/scripts-runtime/types/swarm-extension.d.ts +63 -1
  289. package/src/scripts-runtime/types/swarm-sdk.d.ts +27 -15
  290. package/src/server.ts +52 -18
  291. package/src/slack/actions.ts +29 -1
  292. package/src/slack/app.ts +14 -2
  293. package/src/slack/assistant.ts +36 -2
  294. package/src/slack/channel-join.ts +3 -16
  295. package/src/slack/commands.ts +9 -1
  296. package/src/slack/event-dedup.ts +1 -1
  297. package/src/slack/external-channel-egress.ts +113 -0
  298. package/src/slack/external-channel.ts +309 -0
  299. package/src/slack/handlers.ts +61 -0
  300. package/src/slack/render-v2.ts +40 -47
  301. package/src/slack/work-objects.ts +5 -0
  302. package/src/tasks/additive-buffer.ts +4 -2
  303. package/src/tasks/context-key.ts +94 -2
  304. package/src/tasks/model-pin.ts +67 -0
  305. package/src/tasks/worker-follow-up.ts +4 -31
  306. package/src/telemetry.ts +2 -1
  307. package/src/tests/acp-adapter.test.ts +42 -0
  308. package/src/tests/api-drain-worker-e2e.test.ts +1 -6
  309. package/src/tests/apps-catalog-feedback.test.ts +310 -0
  310. package/src/tests/apps-catalog-inputs.test.ts +70 -0
  311. package/src/tests/apps-catalog-table.test.ts +113 -0
  312. package/src/tests/azure-devops-boards-sync.test.ts +584 -0
  313. package/src/tests/azure-devops-html.test.ts +13 -0
  314. package/src/tests/azure-devops-webhook-malformed.test.ts +4 -0
  315. package/src/tests/base-prompt.test.ts +58 -54
  316. package/src/tests/chat-sdk-commands.test.ts +191 -0
  317. package/src/tests/chat-sdk-config.test.ts +334 -0
  318. package/src/tests/chat-sdk-discord-api.test.ts +68 -0
  319. package/src/tests/chat-sdk-discord-gateway.test.ts +252 -0
  320. package/src/tests/chat-sdk-discord-webhook.test.ts +126 -0
  321. package/src/tests/chat-sdk-fields-sync.test.ts +50 -0
  322. package/src/tests/chat-sdk-harness.ts +146 -0
  323. package/src/tests/chat-sdk-inbound.test.ts +363 -0
  324. package/src/tests/chat-sdk-relay.test.ts +470 -0
  325. package/src/tests/chat-sdk-reload.test.ts +128 -0
  326. package/src/tests/chat-sdk-shutdown.test.ts +220 -0
  327. package/src/tests/chat-sdk-slack.test.ts +268 -0
  328. package/src/tests/chat-sdk-state.test.ts +163 -0
  329. package/src/tests/chat-sdk-status-route.test.ts +382 -0
  330. package/src/tests/chat-sdk-teams.test.ts +277 -0
  331. package/src/tests/chat-sdk-tools.test.ts +520 -0
  332. package/src/tests/chat-sdk-typing.test.ts +120 -0
  333. package/src/tests/chat-sdk-webhook-telegram.test.ts +200 -0
  334. package/src/tests/claude-adapter-binary.test.ts +144 -0
  335. package/src/tests/claude-flag-settings.test.ts +114 -0
  336. package/src/tests/claude-managed-adapter.test.ts +1 -1
  337. package/src/tests/claude-sdk-lifecycle.test.ts +148 -1
  338. package/src/tests/claude-sdk-process.fixture.ts +7 -0
  339. package/src/tests/claude-thinking-tokens.test.ts +57 -0
  340. package/src/tests/claude-worker-parity.test.ts +2 -7
  341. package/src/tests/config-env-export-chat-sdk.test.ts +11 -0
  342. package/src/tests/config-refs.test.ts +66 -0
  343. package/src/tests/context-key.test.ts +51 -0
  344. package/src/tests/cors-allowlist.test.ts +25 -0
  345. package/src/tests/create-page-tool.test.ts +18 -0
  346. package/src/tests/credential-check.test.ts +139 -0
  347. package/src/tests/credentials.test.ts +21 -0
  348. package/src/tests/db-runtime.test.ts +61 -0
  349. package/src/tests/db-task-read-contract.test.ts +20 -0
  350. package/src/tests/db-task-write-contract.test.ts +10 -0
  351. package/src/tests/deferred-task-wake.test.ts +52 -4
  352. package/src/tests/docs-index-footprint.test.js +3 -3
  353. package/src/tests/dsh-adapter.test.ts +1 -1
  354. package/src/tests/enabled-capabilities.test.ts +20 -0
  355. package/src/tests/entrypoint-api-readiness.test.ts +14 -8
  356. package/src/tests/entrypoint-config-env-export.test.ts +5 -2
  357. package/src/tests/entrypoint-export-to-env.test.ts +78 -0
  358. package/src/tests/explicit-model-validation.test.ts +105 -1
  359. package/src/tests/extensions-example-route-chat-channel.test.ts +154 -0
  360. package/src/tests/extensions-post-bridge.test.ts +51 -0
  361. package/src/tests/fetch-resolved-env.test.ts +128 -0
  362. package/src/tests/fixtures/azure-boards/workitem.commented.json +89 -0
  363. package/src/tests/fixtures/azure-boards/workitem.created.json +81 -0
  364. package/src/tests/fixtures/azure-boards/workitem.deleted.json +70 -0
  365. package/src/tests/fixtures/azure-boards/workitem.restored.json +82 -0
  366. package/src/tests/fixtures/azure-boards/workitem.updated.json +130 -0
  367. package/src/tests/fixtures/catalog-feedback-definition.json.txt +860 -0
  368. package/src/tests/fixtures/catalog-inputs-definition.json.txt +910 -0
  369. package/src/tests/fixtures/catalog-table-definition.json.txt +468 -0
  370. package/src/tests/fixtures/chat-sdk/telegram-private-text.json +10 -0
  371. package/src/tests/fixtures/extensions/route-chat-channel-to-agent/hooks.ts +27 -0
  372. package/src/tests/fixtures/extensions/route-chat-channel-to-agent/manifest.json +9 -0
  373. package/src/tests/fixtures/openai-compatible-registry-extension.child.ts +146 -0
  374. package/src/tests/fixtures/runner-identity-refresh.ts +3 -0
  375. package/src/tests/fixtures/runner-legacy-resume-prompt.ts +122 -0
  376. package/src/tests/github-handlers.test.ts +35 -0
  377. package/src/tests/harness-provider-resolution.test.ts +12 -0
  378. package/src/tests/heartbeat-checklist.test.ts +3 -2
  379. package/src/tests/hook-runtime-close.test.ts +0 -2
  380. package/src/tests/http-api-integration.test.ts +0 -25
  381. package/src/tests/migration-046-budgets.test.ts +17 -16
  382. package/src/tests/migration-217-chat-sdk-state.test.ts +113 -0
  383. package/src/tests/model-tier-resolution.test.ts +93 -0
  384. package/src/tests/models-catalog.test.ts +42 -1
  385. package/src/tests/node-env-runtime.test.ts +98 -0
  386. package/src/tests/openai-compatible-providers.test.ts +134 -0
  387. package/src/tests/openai-compatible-registry-extension.test.ts +42 -0
  388. package/src/tests/opencode-adapter.test.ts +348 -3
  389. package/src/tests/opencode-billing.test.ts +28 -0
  390. package/src/tests/opencode-helmcode-sdk.test.ts +201 -0
  391. package/src/tests/opencode-shell-env.test.ts +145 -0
  392. package/src/tests/openrouter-base-url.test.ts +101 -151
  393. package/src/tests/page-proxy.test.ts +1 -1
  394. package/src/tests/pages-browser-sdk.test.ts +76 -0
  395. package/src/tests/pages-feedback.test.ts +1 -1
  396. package/src/tests/paused-tasks-requested-by.test.ts +102 -0
  397. package/src/tests/pi-custom-provider-fake-server.test.ts +442 -0
  398. package/src/tests/pi-mono-adapter.test.ts +385 -5
  399. package/src/tests/pi-shell-env.test.ts +170 -0
  400. package/src/tests/prompt-template-remaining.test.ts +35 -0
  401. package/src/tests/rbac-engine.test.ts +11 -3
  402. package/src/tests/runner-exit-after-tasks.test.ts +0 -4
  403. package/src/tests/runner-identity-refresh.test.ts +3 -0
  404. package/src/tests/runner-legacy-resume-prompt.test.ts +64 -0
  405. package/src/tests/script-connections-sql.test.ts +675 -0
  406. package/src/tests/script-connections.test.ts +7 -1
  407. package/src/tests/scripts-http.test.ts +201 -1
  408. package/src/tests/scripts-only-gating.test.ts +2 -3
  409. package/src/tests/scripts-runtime.test.ts +58 -40
  410. package/src/tests/secret-scrubber.test.ts +86 -0
  411. package/src/tests/seed-skills-bundled-files.test.ts +45 -53
  412. package/src/tests/seed-skills-retire.test.ts +182 -0
  413. package/src/tests/send-task-requester-gate.test.ts +58 -0
  414. package/src/tests/session-cost-recompute-unit.test.ts +94 -0
  415. package/src/tests/skill-get-name-resolution.test.ts +137 -0
  416. package/src/tests/slack-channel-join.test.ts +4 -1
  417. package/src/tests/slack-external-channel-config.test.ts +65 -0
  418. package/src/tests/slack-external-channel-egress.test.ts +237 -0
  419. package/src/tests/slack-external-channel-guard.test.ts +280 -0
  420. package/src/tests/slack-external-channel-tools.test.ts +173 -0
  421. package/src/tests/slack-external-channel.test.ts +370 -0
  422. package/src/tests/slack-render-v2.test.ts +86 -10
  423. package/src/tests/sonnet-5-5-registry.test.ts +9 -1
  424. package/src/tests/spec-base-url.test.ts +16 -1
  425. package/src/tests/swarm-config-export-to-env.test.ts +267 -0
  426. package/src/tests/swarm-config-reserved-keys.test.ts +16 -0
  427. package/src/tests/swarm-diff.test.ts +1 -1
  428. package/src/tests/task-model-effort-rerun.test.ts +164 -0
  429. package/src/tests/task-session-prompts.test.ts +132 -0
  430. package/src/tests/task-tool-manifest.test.ts +14 -0
  431. package/src/tests/task-tools-ctx.test.ts +2 -0
  432. package/src/tests/tool-annotations.test.ts +6 -6
  433. package/src/tests/ui-logs-parser.test.ts +43 -0
  434. package/src/tests/used-by-route.test.ts +6 -0
  435. package/src/tests/vendored-openapi.test.ts +16 -1
  436. package/src/tests/vercel-ai-gateway.test.ts +149 -0
  437. package/src/tests/workflow-swarm-script.test.ts +38 -0
  438. package/src/tests/workflow-wait-filter.test.ts +5 -3
  439. package/src/tools/chat-access.ts +41 -0
  440. package/src/tools/chat-open-dm.ts +59 -0
  441. package/src/tools/chat-post.ts +74 -0
  442. package/src/tools/chat-read-thread.ts +100 -0
  443. package/src/tools/chat-reply.ts +75 -0
  444. package/src/tools/create-page.ts +3 -13
  445. package/src/tools/defer-task.ts +17 -0
  446. package/src/tools/model-catalog/index.ts +2 -1
  447. package/src/tools/register-chat-channel.ts +139 -0
  448. package/src/tools/send-task.ts +24 -0
  449. package/src/tools/skills/skill-get.ts +17 -9
  450. package/src/tools/slack-post.ts +6 -0
  451. package/src/tools/slack-read.ts +8 -0
  452. package/src/tools/slack-reply.ts +6 -0
  453. package/src/tools/slack-start-thread.ts +6 -0
  454. package/src/tools/slack-update.ts +6 -0
  455. package/src/tools/slack-upload-file.ts +6 -0
  456. package/src/tools/swarm-config/get-config.ts +2 -1
  457. package/src/tools/swarm-config/list-config.ts +2 -1
  458. package/src/tools/swarm-config/set-config.ts +16 -3
  459. package/src/tools/tool-config.ts +8 -6
  460. package/src/types.ts +12 -2
  461. package/src/utils/config-env-export.ts +74 -0
  462. package/src/utils/credentials.test.ts +92 -0
  463. package/src/utils/credentials.ts +52 -4
  464. package/src/utils/harness-provider.ts +15 -6
  465. package/src/utils/node-env.ts +24 -0
  466. package/src/utils/runtime-model-catalog.ts +11 -1
  467. package/src/utils/secret-scrubber.ts +71 -2
  468. package/src/utils/task-tool-manifest.ts +19 -2
  469. package/src/workflows/executors/swarm-script.ts +1 -0
  470. package/templates/skills/agent-browser/SKILL.md +1 -1
  471. package/templates/skills/agent-browser/content.md +1 -1
  472. package/templates/skills/apps/SKILL.md +749 -0
  473. package/templates/skills/apps/content.md +60 -22
  474. package/templates/skills/chat-sdk/config.json +14 -0
  475. package/templates/skills/chat-sdk/content.md +84 -0
  476. package/templates/skills/composio-gmail/content.md +7 -0
  477. package/templates/skills/composio-google-docs/content.md +1 -1
  478. package/templates/skills/email-domain-health/config.json +14 -0
  479. package/templates/skills/email-domain-health/content.md +131 -0
  480. package/templates/skills/kv-storage/content.md +2 -2
  481. package/templates/skills/pages/content.md +8 -10
  482. package/templates/skills/qa-loom/config.json +14 -0
  483. package/templates/skills/qa-loom/content.md +187 -0
  484. package/templates/skills/qa-loom/files/build-loom.mjs +221 -0
  485. package/templates/skills/qa-loom/files/run-qa.sh +57 -0
  486. package/templates/skills/slack-interaction/content.md +4 -0
  487. package/templates/skills/swarm-extensions/content.md +2 -0
  488. package/templates/skills/swarm-scripts/SKILL.md +4 -2
  489. package/templates/skills/swarm-scripts/config.json +1 -1
  490. package/templates/skills/swarm-scripts/content.md +4 -2
  491. package/templates/skills/workflow-iterate/content.md +38 -0
  492. package/dist/artifact-eczsknc3.js +0 -3843
  493. package/dist/cli-9xsmdpdt.js +0 -56377
  494. package/dist/cli-bd957dgb.js +0 -153
  495. package/dist/cli-q51yprfq.js +0 -1104
  496. package/dist/cli-t1ze64h3.js +0 -811
  497. package/dist/index-9ch1n761.js +0 -1102
  498. package/src/artifact-sdk/index.ts +0 -2
  499. package/src/artifact-sdk/localtunnel.d.ts +0 -20
  500. package/src/artifact-sdk/port.ts +0 -12
  501. package/src/artifact-sdk/server.ts +0 -183
  502. package/src/artifact-sdk/tunnel.ts +0 -19
  503. package/src/commands/artifact.ts +0 -249
  504. package/src/http/ecosystem.ts +0 -77
  505. package/src/tests/artifact-commands.test.ts +0 -92
  506. package/src/tests/artifact-sdk.test.ts +0 -793
  507. package/src/tests/register-service-security.test.ts +0 -194
  508. package/src/tools/list-services.ts +0 -107
  509. package/src/tools/register-service.ts +0 -177
  510. package/src/tools/unregister-service.ts +0 -73
  511. package/src/tools/update-service-status.ts +0 -96
  512. package/templates/skills/artifacts/config.json +0 -14
  513. package/templates/skills/artifacts/content.md +0 -344
  514. package/templates/skills/artifacts/files/examples/approval-flow.ts +0 -34
  515. package/templates/skills/artifacts/files/examples/hono-dashboard.ts +0 -31
  516. package/templates/skills/artifacts/files/examples/multi-artifact.ts +0 -20
  517. package/templates/skills/artifacts/files/examples/static-report.sh +0 -17
  518. package/dist/{azure-openai-responses-ap1gwxgr.js → azure-openai-responses-ygfreq4t.js} +5 -5
  519. package/dist/{cli-5rqrj7q0.js → cli-pw7ebjdt.js} +0 -0
  520. package/dist/{openai-codex-responses-33qhkkdy.js → openai-codex-responses-3bk0grnr.js} +4 -4
  521. package/dist/{openai-completions-s88xm3ns.js → openai-completions-5d4jpakc.js} +9 -9
  522. package/dist/{openai-responses-7xd2f2e2.js → openai-responses-krb51f59.js} +5 -5
  523. package/dist/{pi-messages-xszk613n.js → pi-messages-x9gzynm1.js} +3 -3
  524. /package/src/{artifact-sdk → pages}/browser-sdk.ts +0 -0
  525. /package/src/{artifact-sdk → pages}/feedback-overlay.ts +0 -0
@@ -1154,6 +1154,49 @@ describe("ui logs parser", () => {
1154
1154
  ]);
1155
1155
  });
1156
1156
 
1157
+ test("keeps a Claude sub-agent's parent_tool_use_id on its messages", () => {
1158
+ const messages = parseSessionLogs([
1159
+ log("spawn", "claude", 1, {
1160
+ type: "assistant",
1161
+ message: {
1162
+ role: "assistant",
1163
+ content: [{ type: "tool_use", id: "toolu_agent_1", name: "Agent", input: {} }],
1164
+ },
1165
+ }),
1166
+ log("child-call", "claude", 2, {
1167
+ type: "assistant",
1168
+ parent_tool_use_id: "toolu_agent_1",
1169
+ message: {
1170
+ role: "assistant",
1171
+ content: [
1172
+ { type: "thinking", thinking: "Look at the fixtures." },
1173
+ { type: "tool_use", id: "toolu_read_1", name: "Read", input: { file_path: "a.ts" } },
1174
+ ],
1175
+ },
1176
+ }),
1177
+ log("child-result", "claude", 3, {
1178
+ type: "user",
1179
+ parent_tool_use_id: "toolu_agent_1",
1180
+ message: {
1181
+ role: "user",
1182
+ content: [{ type: "tool_result", tool_use_id: "toolu_read_1", content: "ok" }],
1183
+ },
1184
+ }),
1185
+ log("main-text", "claude", 4, {
1186
+ type: "assistant",
1187
+ parent_tool_use_id: null,
1188
+ message: { role: "assistant", content: [{ type: "text", text: "Done." }] },
1189
+ }),
1190
+ ]);
1191
+
1192
+ expect(messages.map((m) => [m.id, m.parentToolUseId])).toEqual([
1193
+ ["spawn", undefined],
1194
+ ["child-call", "toolu_agent_1"],
1195
+ ["child-result", "toolu_agent_1"],
1196
+ ["main-text", undefined],
1197
+ ]);
1198
+ });
1199
+
1157
1200
  test("extracts a completed Claude foreground Agent run from its paired result", () => {
1158
1201
  const runs = extractSubagentRuns([
1159
1202
  log("spawn", "claude", 1, {
@@ -61,10 +61,14 @@ const configValue = async (key: string) =>
61
61
 
62
62
  describe("POST /api/used-by", () => {
63
63
  const originalFetch = globalThis.fetch;
64
+ const savedEnvWebsite = process.env.SWARM_ORG_WEBSITE;
65
+ const savedEnvName = process.env.SWARM_ORG_NAME;
64
66
  let proxied: Array<{ url: string; body: Record<string, unknown> }>;
65
67
  let proxyStatus: number;
66
68
 
67
69
  beforeAll(async () => {
70
+ delete process.env.SWARM_ORG_WEBSITE;
71
+ delete process.env.SWARM_ORG_NAME;
68
72
  await removeDbFiles();
69
73
  initDb(TEST_DB_PATH);
70
74
  await createAgent({ id: WORKER_ID, name: "Worker", isLead: false, status: "idle" });
@@ -75,6 +79,8 @@ describe("POST /api/used-by", () => {
75
79
  });
76
80
 
77
81
  afterAll(async () => {
82
+ if (savedEnvWebsite !== undefined) process.env.SWARM_ORG_WEBSITE = savedEnvWebsite;
83
+ if (savedEnvName !== undefined) process.env.SWARM_ORG_NAME = savedEnvName;
78
84
  globalThis.fetch = originalFetch;
79
85
  closeDb();
80
86
  await removeDbFiles();
@@ -255,13 +255,28 @@ describe("vendored OpenAPI", () => {
255
255
  new Date().toISOString(),
256
256
  new Bun.CryptoHasher("sha256").update(migration117).digest("hex"),
257
257
  );
258
+ // 213 rebuilds script_connections with post-117 columns, so it must wait for 117 too.
259
+ const migration213 = readFileSync(
260
+ "src/be/migrations/213_script_connections_sql.sql",
261
+ "utf-8",
262
+ );
263
+ database
264
+ .prepare(
265
+ "INSERT INTO _migrations (version, name, applied_at, checksum) VALUES (?, ?, ?, ?)",
266
+ )
267
+ .run(
268
+ 213,
269
+ "213_script_connections_sql",
270
+ new Date().toISOString(),
271
+ new Bun.CryptoHasher("sha256").update(migration213).digest("hex"),
272
+ );
258
273
  runMigrations(database);
259
274
  database
260
275
  .query(
261
276
  "INSERT INTO script_connections (id, slug, kind, openapi_spec_json) VALUES (?, ?, ?, ?)",
262
277
  )
263
278
  .run("legacy-row", "legacy", "openapi", "{}");
264
- database.run("DELETE FROM _migrations WHERE version = 117");
279
+ database.run("DELETE FROM _migrations WHERE version IN (117, 213)");
265
280
  runMigrations(database);
266
281
  expect(
267
282
  database.query<{ id: string }, []>("SELECT id FROM script_connections").get()?.id,
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Vercel AI Gateway (`vercel/<vendor>/<model>`) on pi and opencode: the full
3
+ * chat catalog, and the one endpoint no env var or hand-written block can move.
4
+ */
5
+ import { afterEach, describe, expect, test } from "bun:test";
6
+ import { mkdtemp, rm } from "node:fs/promises";
7
+ import { tmpdir } from "node:os";
8
+ import { join } from "node:path";
9
+ import {
10
+ isAgentChatModel,
11
+ OPENAI_COMPATIBLE_PROVIDERS,
12
+ openAICompatibleBaseUrlVars,
13
+ providerBaseUrl,
14
+ providerForModel,
15
+ withRegistryCatalogSections,
16
+ } from "@desplega/model-catalog";
17
+ import { buildCatalogEntries } from "../be/model-catalog-store";
18
+ import { isKnownCatalogModel } from "../be/model-validation";
19
+ import { buildModelsCatalog } from "../be/models-catalog";
20
+ import { loadModelsDevCache, type ModelsDevCache } from "../be/modelsdev-cache";
21
+ import snapshot from "../be/modelsdev-cache.json";
22
+ import { applyProviderKeySplice, type OpencodeConfig } from "../providers/opencode-adapter";
23
+ import { customProviderModelsBlock, writeSessionModelsJson } from "../providers/pi-mono-adapter";
24
+
25
+ const VERCEL = OPENAI_COMPATIBLE_PROVIDERS.vercel;
26
+ const ENDPOINT = "https://ai-gateway.vercel.sh/v1";
27
+ const WRONG = "https://ai-gateway.vercel.sh";
28
+ const raw = snapshot as unknown as ModelsDevCache;
29
+ const rawVercel = (raw.vercel?.models ?? {}) as Record<
30
+ string,
31
+ Parameters<typeof isAgentChatModel>[0]
32
+ >;
33
+ const chatIds = Object.keys(rawVercel).filter((id) => isAgentChatModel(rawVercel[id] ?? {}));
34
+
35
+ describe("Vercel AI Gateway catalog", () => {
36
+ test("the snapshot carries non-chat rows, so the filter is exercised", () => {
37
+ expect(rawVercel["voyage/voyage-code-3"]).toBeDefined();
38
+ expect(chatIds.length).toBeGreaterThan(200);
39
+ expect(chatIds.length).toBeLessThan(Object.keys(rawVercel).length);
40
+ });
41
+
42
+ test("pi lists every AI Gateway chat model, and no embedding or video model", () => {
43
+ const block = customProviderModelsBlock(VERCEL);
44
+ const ids = block.models.map((m) => m.id as string);
45
+ expect(ids.sort()).toEqual([...chatIds].sort());
46
+ expect(ids).toContain("zai/glm-5.3");
47
+ expect(ids).toContain("moonshotai/kimi-k3");
48
+ expect(ids).toContain("anthropic/claude-sonnet-4.6");
49
+ expect(ids).not.toContain("voyage/voyage-code-3");
50
+ expect(ids).not.toContain("klingai/kling-v3.0-t2v");
51
+ const glm = block.models.find((m) => m.id === "zai/glm-5.3");
52
+ expect(glm).toMatchObject({ contextWindow: 1000000, maxTokens: 1000000, reasoning: true });
53
+ expect(block).toMatchObject({
54
+ baseUrl: ENDPOINT,
55
+ api: "openai-completions",
56
+ apiKey: "$AI_GATEWAY_API_KEY",
57
+ });
58
+ });
59
+
60
+ test("picker, live and validation catalogs drop non-chat Vercel rows", () => {
61
+ const merged = withRegistryCatalogSections(raw);
62
+ expect(Object.keys(merged.vercel?.models ?? {}).sort()).toEqual([...chatIds].sort());
63
+ // OpenRouter is not a registry provider and keeps its full section.
64
+ expect(Object.keys(merged.openrouter?.models ?? {}).length).toBe(
65
+ Object.keys(raw.openrouter?.models ?? {}).length,
66
+ );
67
+
68
+ const live = buildCatalogEntries(raw, 0).filter((e) => e.provider === "vercel");
69
+ expect(live.map((e) => e.modelId).sort()).toEqual([...chatIds].sort());
70
+
71
+ const cache = loadModelsDevCache();
72
+ if (!cache) throw new Error("snapshot missing");
73
+ const catalog = buildModelsCatalog(cache) as Parameters<typeof isKnownCatalogModel>[1];
74
+ expect(isKnownCatalogModel("vercel/zai/glm-5.3", catalog)).toBe(true);
75
+ expect(isKnownCatalogModel("vercel/anthropic/claude-sonnet-4.6", catalog)).toBe(true);
76
+ expect(isKnownCatalogModel("vercel/voyage/voyage-code-3", catalog)).toBe(false);
77
+ expect(providerForModel("vercel/moonshotai/kimi-k3")?.id).toBe("vercel");
78
+ });
79
+ });
80
+
81
+ describe("Vercel AI Gateway endpoint is fixed", () => {
82
+ const dirs: string[] = [];
83
+ afterEach(async () => {
84
+ await Promise.all(dirs.splice(0).map((d) => rm(d, { recursive: true, force: true })));
85
+ });
86
+
87
+ test("VERCEL_BASE_URL is never read or forwarded", () => {
88
+ expect(VERCEL.baseUrl).toBe(ENDPOINT);
89
+ expect(providerBaseUrl(VERCEL, { VERCEL_BASE_URL: WRONG })).toBe(ENDPOINT);
90
+ expect(openAICompatibleBaseUrlVars()).not.toContain("VERCEL_BASE_URL");
91
+ expect(openAICompatibleBaseUrlVars()).toContain("NAN_BASE_URL");
92
+ });
93
+
94
+ test("opencode gets the key and no baseURL, even over a configured one", () => {
95
+ const key = crypto.randomUUID();
96
+ const config: OpencodeConfig = {
97
+ model: "vercel/zai/glm-5.3",
98
+ provider: { vercel: { options: { baseURL: WRONG } } },
99
+ };
100
+ const picked = applyProviderKeySplice(config, config.model, {
101
+ AI_GATEWAY_API_KEY: key,
102
+ VERCEL_BASE_URL: WRONG,
103
+ });
104
+ expect(picked?.key).toBe(key);
105
+ expect(config.provider?.vercel).toEqual({ env: [], options: { apiKey: key } });
106
+ });
107
+
108
+ test("pi gets the Chat Completions endpoint over VERCEL_BASE_URL and a hand-written block", async () => {
109
+ const agentDir = await mkdtemp(join(tmpdir(), "pi-vercel-"));
110
+ dirs.push(agentDir);
111
+ await Bun.write(
112
+ join(agentDir, "models.json"),
113
+ JSON.stringify({
114
+ providers: { vercel: { baseUrl: WRONG, api: "anthropic-messages", headers: { a: "b" } } },
115
+ }),
116
+ );
117
+ const env = { AI_GATEWAY_API_KEY: "k", VERCEL_BASE_URL: WRONG };
118
+ const session = await writeSessionModelsJson(agentDir, env, "vercel/vendor/brand-new-model");
119
+ try {
120
+ expect(session.modelsPath).toBeDefined();
121
+ const written = await Bun.file(session.modelsPath as string).json();
122
+ const block = written.providers.vercel;
123
+ expect(block.baseUrl).toBe(ENDPOINT);
124
+ expect(block.api).toBe("openai-completions");
125
+ expect(block.headers).toEqual({ a: "b" });
126
+ const ids = (block.models as { id: string }[]).map((m) => m.id);
127
+ expect(ids).toContain("zai/glm-5.3");
128
+ // An id the catalog does not know yet still resolves (bare entry).
129
+ expect(ids).toContain("vendor/brand-new-model");
130
+ } finally {
131
+ await session.dispose();
132
+ }
133
+ });
134
+
135
+ test("pi without a shared block also lands on the endpoint", async () => {
136
+ const agentDir = await mkdtemp(join(tmpdir(), "pi-vercel-"));
137
+ dirs.push(agentDir);
138
+ const session = await writeSessionModelsJson(agentDir, {
139
+ AI_GATEWAY_API_KEY: "k",
140
+ VERCEL_BASE_URL: WRONG,
141
+ });
142
+ try {
143
+ const written = await Bun.file(session.modelsPath as string).json();
144
+ expect(written.providers.vercel.baseUrl).toBe(ENDPOINT);
145
+ } finally {
146
+ await session.dispose();
147
+ }
148
+ });
149
+ });
@@ -9,6 +9,7 @@ import {
9
9
  getWorkflowRun,
10
10
  getWorkflowRunStepsByRunId,
11
11
  initDb,
12
+ upsertSwarmConfig,
12
13
  } from "../be/db";
13
14
  import { upsertScriptConnection } from "../be/script-connections";
14
15
  import { getScript, upsertScriptByName } from "../be/scripts/db";
@@ -31,6 +32,7 @@ import {
31
32
  } from "../workflows/executors/swarm-script";
32
33
  import { interpolate } from "../workflows/template";
33
34
  import { SKIP_SANDBOX_SPAWN_TESTS } from "./sandbox-spawn-test-helpers";
35
+ import { CHILD_PROCESS_TEST_BUDGET_MS } from "./test-proc";
34
36
 
35
37
  const skip = test.skipIf(SKIP_SANDBOX_SPAWN_TESTS);
36
38
 
@@ -370,6 +372,42 @@ describe("SwarmScriptExecutor", () => {
370
372
  expect(result.output?.result).toEqual({ apiKeys: ["workflowVendor"] });
371
373
  });
372
374
 
375
+ skip(
376
+ "swarm-script node reads swarm config through ctx.swarm.config.get",
377
+ async () => {
378
+ await upsertSwarmConfig({ scope: "global", key: "WORKFLOW_CFG_PLAIN", value: "node-value" });
379
+ await saveScript(
380
+ "ctx-config-get",
381
+ `export default async (_args, ctx) => {
382
+ const value = ctx.swarm.config.get("WORKFLOW_CFG_PLAIN");
383
+ return {
384
+ value: value === undefined ? null : ctx.stdlib.Redacted.value(value),
385
+ missing: ctx.swarm.config.get("WORKFLOW_CFG_NOT_SET") ?? "fallback",
386
+ };
387
+ };`,
388
+ );
389
+
390
+ const executor = new SwarmScriptExecutor(deps);
391
+ const wf = await makeWorkflow({ nodes: [] });
392
+ const result = await executor.run({
393
+ config: { scriptName: "ctx-config-get" },
394
+ context: {},
395
+ meta: {
396
+ runId: crypto.randomUUID(),
397
+ stepId: crypto.randomUUID(),
398
+ nodeId: "script",
399
+ workflowId: wf.id,
400
+ dryRun: false,
401
+ },
402
+ });
403
+ await getDbClient().run("DELETE FROM swarm_config WHERE key = 'WORKFLOW_CFG_PLAIN'");
404
+
405
+ expect(result.status).toBe("success");
406
+ expect(result.output?.result).toEqual({ value: "node-value", missing: "fallback" });
407
+ },
408
+ CHILD_PROCESS_TEST_BUDGET_MS,
409
+ );
410
+
373
411
  skip("pinHash correctly resolves to a historic script_versions row", async () => {
374
412
  const first = await saveScript("versioned", `export default async () => ({ version: "old" });`);
375
413
  await saveScript("versioned", `export default async () => ({ version: "new" });`);
@@ -154,12 +154,14 @@ describe("matchesFilter — sandbox penetration (must all return false, never th
154
154
  });
155
155
 
156
156
  test("DoS: pathological regex terminates within timeout", async () => {
157
- const filter = "(p) => /^(a+)+$/.test('a'.repeat(30) + 'X')";
157
+ // Still exponential backtracking, but 24 characters finish in ~0.1 s. The
158
+ // 50 ms timer cannot pre-empt a synchronous regex, so the elapsed time is
159
+ // the engine's own: 30 characters took 2058 ms on a loaded CI runner and
160
+ // failed the bound below without the matcher doing anything wrong.
161
+ const filter = "(p) => /^(a+)+$/.test('a'.repeat(24) + 'X')";
158
162
  const t0 = Date.now();
159
163
  const result = await matchesFilter({}, filter);
160
164
  const elapsed = Date.now() - t0;
161
- // The regex would eventually return false on its own, but if it stalls
162
- // the timeout catches it.
163
165
  expect(typeof result).toBe("boolean");
164
166
  expect(elapsed).toBeLessThan(2000);
165
167
  });
@@ -0,0 +1,41 @@
1
+ import { getAgentById, getAgentCurrentTask, getTaskById } from "@/be/db";
2
+ import { can } from "@/rbac";
3
+ import { chatThreadOfContextKey } from "@/tasks/context-key";
4
+ import type { RequestInfo } from "@/tools/utils";
5
+
6
+ /** Chat SDK verbs; the legacy policy grants them to the lead only. */
7
+ export type ChatVerb = "integration.chat.post" | "integration.chat.read" | "integration.chat.dm";
8
+
9
+ /**
10
+ * The Chat SDK thread of the task the caller works on: the task named by the
11
+ * X-Source-Task-ID header, else the agent's current task. Null when the caller
12
+ * neither owns nor created that task, or when the task did not come from a
13
+ * chat thread.
14
+ */
15
+ export async function callerChatThread(
16
+ requestInfo: RequestInfo,
17
+ ): Promise<{ adapter: string; threadId: string } | null> {
18
+ if (!requestInfo.agentId) return null;
19
+ const task = requestInfo.sourceTaskId
20
+ ? await getTaskById(requestInfo.sourceTaskId)
21
+ : await getAgentCurrentTask(requestInfo.agentId);
22
+ if (!task) return null;
23
+ // Creator is safe: a non-lead agent can only create a chat-bound task under a parent it owns or created.
24
+ if (task.agentId !== requestInfo.agentId && task.creatorAgentId !== requestInfo.agentId) {
25
+ return null;
26
+ }
27
+ return chatThreadOfContextKey(task.contextKey);
28
+ }
29
+
30
+ /** RBAC decision for a chat verb, with the caller's lead flag read from the database. */
31
+ export async function canUseChatVerb(requestInfo: RequestInfo, verb: ChatVerb): Promise<boolean> {
32
+ if (!requestInfo.agentId) return false;
33
+ const agent = await getAgentById(requestInfo.agentId);
34
+ if (!agent) return false;
35
+ return can({
36
+ principal: { kind: "agent", agentId: agent.id, isLead: agent.isLead },
37
+ verb,
38
+ resource: { kind: "none" },
39
+ source: "mcp",
40
+ }).allow;
41
+ }
@@ -0,0 +1,59 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import * as z from "zod";
3
+ import { CHAT_SDK_ADAPTER_NAMES } from "@/integrations/chat-sdk/config";
4
+ import { errorMessage } from "@/integrations/chat-sdk/logger";
5
+ import { getChatInstance } from "@/integrations/chat-sdk/runtime";
6
+ import { assertTrustedTeamsServiceUrl } from "@/integrations/chat-sdk/teams-origin";
7
+ import { canUseChatVerb } from "@/tools/chat-access";
8
+ import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
9
+
10
+ export const registerChatOpenDmTool = (server: McpServer) => {
11
+ createToolRegistrar(server)(
12
+ "chat-open-dm",
13
+ {
14
+ title: "Open a direct message on a chat platform",
15
+ description:
16
+ "Lead only. Open (or find) the direct-message thread with a user on a Chat SDK adapter and return its threadId. Post to it with chat-post. The userId is the platform user id from a chat message (Telegram numeric id, Slack U..., Discord snowflake, Teams 29:...). Telegram bots can message only users who started a chat with the bot.",
17
+ annotations: { openWorldHint: true },
18
+ inputSchema: z.object({
19
+ adapter: z.enum(CHAT_SDK_ADAPTER_NAMES).describe("Chat SDK adapter name."),
20
+ userId: z.string().min(1).describe("Platform user id."),
21
+ }),
22
+ outputSchema: swarmToolOutputSchema({
23
+ yourAgentId: z.string().optional(),
24
+ adapter: z.string().optional(),
25
+ threadId: z.string().optional(),
26
+ }),
27
+ },
28
+ async ({ adapter, userId }, requestInfo) => {
29
+ const data = { yourAgentId: requestInfo.agentId };
30
+ if (!(await canUseChatVerb(requestInfo, "integration.chat.dm"))) {
31
+ return toolErr("Permission denied. Only the lead can open a direct message.", { data });
32
+ }
33
+ const chat = getChatInstance(adapter);
34
+ if (!chat) return toolErr(`Chat SDK adapter '${adapter}' is not running.`, { data });
35
+ // Call the adapter directly: Chat.openDM guesses the adapter from the
36
+ // user id format, and numeric ids are ambiguous.
37
+ const platform = chat.getAdapter(adapter);
38
+ if (!platform?.openDM) {
39
+ return toolErr(`The ${adapter} adapter does not support direct messages.`, { data });
40
+ }
41
+ try {
42
+ if (adapter === "teams") {
43
+ // Teams opens the DM at the service URL cached from the user's last
44
+ // activity (adapter state key). The webhook route refuses untrusted
45
+ // ones; check again before the bot token goes out.
46
+ const cached = await chat.getState().get<string>(`teams:serviceUrl:${userId}`);
47
+ if (cached) assertTrustedTeamsServiceUrl(cached);
48
+ }
49
+ const threadId = await platform.openDM(userId);
50
+ return toolOk(`Direct-message thread with ${userId} on ${adapter}: ${threadId}.`, {
51
+ data: { ...data, adapter, threadId },
52
+ });
53
+ } catch (error) {
54
+ const message = errorMessage(error);
55
+ return toolErr(`Could not open a direct message: ${message}`, { data });
56
+ }
57
+ },
58
+ );
59
+ };
@@ -0,0 +1,74 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import * as z from "zod";
3
+ import { CHAT_SDK_ADAPTER_NAMES } from "@/integrations/chat-sdk/config";
4
+ import { errorMessage } from "@/integrations/chat-sdk/logger";
5
+ import { adapterOfChatId, postChatMessage } from "@/integrations/chat-sdk/outbound";
6
+ import { canUseChatVerb } from "@/tools/chat-access";
7
+ import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
8
+
9
+ export const registerChatPostTool = (server: McpServer) => {
10
+ createToolRegistrar(server)(
11
+ "chat-post",
12
+ {
13
+ title: "Post to a chat thread or channel",
14
+ description:
15
+ "Lead only. Post a message to any Chat SDK thread or channel (Telegram, Slack, Discord, Teams), outside the thread of your current task. Give threadId to reply in a thread, or channelId to start a new message in a channel. Ids carry the adapter prefix, such as telegram:12345. To answer the user of your current chat task, use chat-reply instead.",
16
+ annotations: { openWorldHint: true },
17
+ inputSchema: z.object({
18
+ adapter: z.enum(CHAT_SDK_ADAPTER_NAMES).describe("Chat SDK adapter name."),
19
+ threadId: z
20
+ .string()
21
+ .min(1)
22
+ .optional()
23
+ .describe("Full SDK thread id, such as telegram:12345 or slack:C123:1700000000.000100."),
24
+ channelId: z
25
+ .string()
26
+ .min(1)
27
+ .optional()
28
+ .describe("Full SDK channel id, such as slack:C123. Ignored when threadId is set."),
29
+ text: z.string().min(1).max(20_000).describe("Markdown text to post."),
30
+ }),
31
+ outputSchema: swarmToolOutputSchema({
32
+ yourAgentId: z.string().optional(),
33
+ messageId: z.string().optional(),
34
+ adapter: z.string().optional(),
35
+ threadId: z.string().optional(),
36
+ channelId: z.string().optional(),
37
+ }),
38
+ },
39
+ async ({ adapter, threadId, channelId, text }, requestInfo) => {
40
+ const data = { yourAgentId: requestInfo.agentId };
41
+ const target = threadId ?? channelId;
42
+ if (!target) return toolErr("Give a threadId or a channelId.", { data });
43
+ if (adapterOfChatId(target) !== adapter) {
44
+ return toolErr(`The id ${target} does not belong to the ${adapter} adapter.`, { data });
45
+ }
46
+ if (!(await canUseChatVerb(requestInfo, "integration.chat.post"))) {
47
+ return toolErr(
48
+ "Permission denied. Only the lead can post outside the thread of its task. Use chat-reply for the thread of your current task.",
49
+ { data },
50
+ );
51
+ }
52
+ try {
53
+ const sent = await postChatMessage({
54
+ adapter,
55
+ ...(threadId ? { threadId } : { channelId }),
56
+ text,
57
+ taskId: requestInfo.sourceTaskId,
58
+ });
59
+ return toolOk(`Posted to ${adapter} ${threadId ? "thread" : "channel"} ${target}.`, {
60
+ data: {
61
+ ...data,
62
+ messageId: sent.messageIds.at(-1),
63
+ adapter: sent.adapter,
64
+ threadId: sent.threadId,
65
+ channelId: sent.channelId,
66
+ },
67
+ });
68
+ } catch (error) {
69
+ const message = errorMessage(error);
70
+ return toolErr(`Could not post: ${message}`, { data });
71
+ }
72
+ },
73
+ );
74
+ };
@@ -0,0 +1,100 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import * as z from "zod";
3
+ import { errorMessage } from "@/integrations/chat-sdk/logger";
4
+ import { adapterOfChatId } from "@/integrations/chat-sdk/outbound";
5
+ import { getChatInstance } from "@/integrations/chat-sdk/runtime";
6
+ import { assertTrustedChatTarget } from "@/integrations/chat-sdk/teams-origin";
7
+ import { callerChatThread, canUseChatVerb } from "@/tools/chat-access";
8
+ import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
9
+ import { scrubSecrets } from "@/utils/secret-scrubber";
10
+
11
+ const DEFAULT_LIMIT = 30;
12
+
13
+ export const registerChatReadThreadTool = (server: McpServer) => {
14
+ createToolRegistrar(server)(
15
+ "chat-read-thread",
16
+ {
17
+ title: "Read recent messages of a chat thread",
18
+ description:
19
+ "Read the most recent messages of a Chat SDK thread, oldest first. Every agent can read the thread of its current task. Other threads are lead only. Limits per platform: Telegram has no history API, so you get the history the SDK stored in the swarm database, capped at 100 messages and 7 days. Teams needs the Chat.Read.* Graph permission. Slack and Discord return platform history.",
20
+ annotations: { readOnlyHint: true, openWorldHint: true },
21
+ inputSchema: z.object({
22
+ threadId: z
23
+ .string()
24
+ .min(1)
25
+ .describe("Full SDK thread id with the adapter prefix, such as telegram:12345."),
26
+ limit: z
27
+ .number()
28
+ .int()
29
+ .min(1)
30
+ .max(100)
31
+ .optional()
32
+ .describe(`Number of messages to return (default ${DEFAULT_LIMIT}).`),
33
+ }),
34
+ outputSchema: swarmToolOutputSchema({
35
+ yourAgentId: z.string().optional(),
36
+ threadId: z.string().optional(),
37
+ messages: z
38
+ .array(
39
+ z.looseObject({
40
+ id: z.string().optional(),
41
+ userId: z.string().optional(),
42
+ userName: z.string().optional(),
43
+ isBot: z.boolean().optional(),
44
+ sentAt: z.string().optional(),
45
+ text: z.string().optional(),
46
+ }),
47
+ )
48
+ .optional(),
49
+ }),
50
+ },
51
+ async ({ threadId, limit }, requestInfo) => {
52
+ const data = { yourAgentId: requestInfo.agentId };
53
+ // A worker reads only the thread its task came from; any other thread
54
+ // needs the lead verb, as slack-read does for direct channel reads.
55
+ const own = await callerChatThread(requestInfo);
56
+ if (own?.threadId !== threadId) {
57
+ if (!(await canUseChatVerb(requestInfo, "integration.chat.read"))) {
58
+ return toolErr(
59
+ own
60
+ ? `Permission denied. You can read only the thread of your current task, ${own.threadId}. Other threads need lead privileges.`
61
+ : "Permission denied. Reading a chat thread that is not the thread of your current task needs lead privileges.",
62
+ { data },
63
+ );
64
+ }
65
+ }
66
+ const adapter = adapterOfChatId(threadId);
67
+ const chat = getChatInstance(adapter);
68
+ if (!chat) return toolErr(`Chat SDK adapter '${adapter}' is not running.`, { data });
69
+ try {
70
+ // Teams reads history with the bot token from the service URL in the id.
71
+ assertTrustedChatTarget(threadId);
72
+ const page = await chat.history.thread.list(threadId, {
73
+ limit: limit ?? DEFAULT_LIMIT,
74
+ direction: "backward",
75
+ });
76
+ const messages = page.messages.map((message) => ({
77
+ id: message.id,
78
+ userId: message.author.userId,
79
+ userName: message.author.userName || message.author.fullName || undefined,
80
+ isBot: message.author.isBot === true,
81
+ sentAt: message.metadata?.dateSent?.toISOString(),
82
+ text: scrubSecrets(message.text ?? ""),
83
+ }));
84
+ const rendered = messages
85
+ .map(
86
+ (m) =>
87
+ `[${m.sentAt ?? "?"}] ${m.userName ?? m.userId}${m.isBot ? " (bot)" : ""}: ${m.text}`,
88
+ )
89
+ .join("\n");
90
+ return toolOk(`${messages.length} message(s) from ${threadId}.`, {
91
+ details: rendered || undefined,
92
+ data: { ...data, threadId, messages },
93
+ });
94
+ } catch (error) {
95
+ const message = errorMessage(error);
96
+ return toolErr(`Could not read the thread: ${message}`, { data });
97
+ }
98
+ },
99
+ );
100
+ };