@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
@@ -0,0 +1,675 @@
1
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
2
+ import { readFileSync } from "node:fs";
3
+ import { unlink } from "node:fs/promises";
4
+ import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
5
+ import { Readable } from "node:stream";
6
+ import { SQL } from "bun";
7
+ import { closeDb, createAgent, getDb, getDbClient, initDb, upsertSwarmConfig } from "../be/db";
8
+ import {
9
+ getScriptApiConnectionDescriptors,
10
+ getScriptApiTypes,
11
+ getScriptConnectionById,
12
+ setScriptConnectionEnabled,
13
+ upsertScriptConnection,
14
+ } from "../be/script-connections";
15
+ import { buildScriptCredentialBindings } from "../be/script-credential-broker";
16
+ import { typecheckScript } from "../be/scripts/typecheck";
17
+ import {
18
+ runReadOnlyQuery,
19
+ SQL_MAX_BYTES,
20
+ SQL_MAX_ROWS,
21
+ setSqlExecutorForTesting,
22
+ } from "../be/sql-connections";
23
+ import { handleScriptConnectionProxy } from "../http/script-connection-proxy";
24
+ import { handleScriptConnections } from "../http/script-connections";
25
+ import { getPathSegments, parseQueryParams } from "../http/utils";
26
+ import { createApiRegistryClient } from "../scripts-runtime/api-client";
27
+ import { runScript } from "../scripts-runtime/loader";
28
+ import { SwarmConfig } from "../scripts-runtime/swarm-config";
29
+ import { refreshSecretScrubberCache } from "../utils/secret-scrubber";
30
+ import { SKIP_SANDBOX_SPAWN_TESTS } from "./sandbox-spawn-test-helpers";
31
+ import { listenOnFreePort } from "./test-net";
32
+
33
+ const TEST_DB_PATH = "./test-script-connections-sql.sqlite";
34
+ const API_KEY = "example-test-script-connections-sql-key-1234567890";
35
+ const DSN = "postgres://reader:sql-conn-test-password-123@db.internal.test:5432/analytics";
36
+ const REAL_PG_URL = process.env.SQL_CONNECTION_TEST_URL;
37
+ const spawnTest = test.skipIf(SKIP_SANDBOX_SPAWN_TESTS);
38
+
39
+ let leadId: string;
40
+ let workerId: string;
41
+ let executorCalls: Array<{ dsn: string; query: string; params: unknown[] }> = [];
42
+ const savedEnv = { ...process.env };
43
+
44
+ async function removeDbFiles(path: string): Promise<void> {
45
+ for (const suffix of ["", "-wal", "-shm"]) {
46
+ await unlink(path + suffix).catch((error) => {
47
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
48
+ });
49
+ }
50
+ }
51
+
52
+ async function dispatch(
53
+ path: string,
54
+ init: { method?: string; body?: unknown; agentId?: string } = {},
55
+ ): Promise<{ status: number; text: string; json: () => Record<string, unknown> }> {
56
+ const req = Readable.from(
57
+ init.body === undefined ? [] : [Buffer.from(JSON.stringify(init.body))],
58
+ ) as IncomingMessage;
59
+ req.method = init.method ?? "GET";
60
+ req.url = path;
61
+ req.headers = {
62
+ "content-type": "application/json",
63
+ ...(init.agentId ? { "x-agent-id": init.agentId } : {}),
64
+ };
65
+ let status = 200;
66
+ let text = "";
67
+ const res = {
68
+ headersSent: false,
69
+ writableEnded: false,
70
+ setHeader() {},
71
+ writeHead(code: number) {
72
+ status = code;
73
+ this.headersSent = true;
74
+ return this;
75
+ },
76
+ end(chunk?: unknown) {
77
+ if (chunk !== undefined) text += String(chunk);
78
+ this.writableEnded = true;
79
+ return this;
80
+ },
81
+ } as unknown as ServerResponse;
82
+ const pathSegments = getPathSegments(path);
83
+ const queryParams = parseQueryParams(path);
84
+ const handled =
85
+ (await handleScriptConnectionProxy(req, res, pathSegments, queryParams, init.agentId)) ||
86
+ (await handleScriptConnections(req, res, pathSegments, queryParams, init.agentId));
87
+ if (!handled) status = 404;
88
+ return { status, text, json: () => JSON.parse(text) };
89
+ }
90
+
91
+ function registerSql(body: Record<string, unknown>, agentId = leadId) {
92
+ return dispatch("/api/script-connections", {
93
+ method: "POST",
94
+ agentId,
95
+ body: { kind: "sql", slug: "analytics", ...body },
96
+ });
97
+ }
98
+
99
+ function sqlQuery(connectionId: string, agentId: string, body: Record<string, unknown>) {
100
+ return dispatch(`/api/script-connections/${connectionId}/sql-query`, {
101
+ method: "POST",
102
+ agentId,
103
+ body,
104
+ });
105
+ }
106
+
107
+ async function configRows(key: string) {
108
+ return getDbClient().query<{
109
+ scope: string;
110
+ scopeId: string | null;
111
+ value: string;
112
+ encrypted: number;
113
+ }>("SELECT scope, scopeId, value, encrypted FROM swarm_config WHERE key = ?", [key]);
114
+ }
115
+
116
+ beforeAll(async () => {
117
+ await removeDbFiles(TEST_DB_PATH);
118
+ closeDb();
119
+ initDb(TEST_DB_PATH);
120
+ process.env.AGENT_SWARM_API_KEY = API_KEY;
121
+ delete process.env.API_KEY;
122
+ refreshSecretScrubberCache();
123
+ });
124
+
125
+ afterAll(async () => {
126
+ setSqlExecutorForTesting(null);
127
+ closeDb();
128
+ await removeDbFiles(TEST_DB_PATH);
129
+ for (const key of Object.keys(process.env)) {
130
+ if (!(key in savedEnv)) delete process.env[key];
131
+ }
132
+ Object.assign(process.env, savedEnv);
133
+ refreshSecretScrubberCache();
134
+ });
135
+
136
+ beforeEach(async () => {
137
+ const client = getDbClient();
138
+ await client.run("DELETE FROM script_connections");
139
+ await client.run("DELETE FROM script_credential_bindings");
140
+ await client.run("DELETE FROM swarm_config");
141
+ await client.run("DELETE FROM agents");
142
+ leadId = (await createAgent({ name: "sql-lead", isLead: true, status: "idle" })).id;
143
+ workerId = (await createAgent({ name: "sql-worker", isLead: false, status: "idle" })).id;
144
+ executorCalls = [];
145
+ setSqlExecutorForTesting(async (dsn, query, params, take) => {
146
+ executorCalls.push({ dsn, query, params });
147
+ take([{ x: 1 }]);
148
+ });
149
+ });
150
+
151
+ describe("migration 213", () => {
152
+ test("adds 'sql' to the kind CHECK and requires a DSN key for sql rows", async () => {
153
+ const schema = getDb()
154
+ .query<{ sql: string }, []>(
155
+ "SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'script_connections'",
156
+ )
157
+ .get();
158
+ expect(schema?.sql).toContain("'graphql', 'sql'");
159
+ await expect(
160
+ getDbClient().run(
161
+ "INSERT INTO script_connections (id, slug, kind) VALUES (?, 'no-key', 'sql')",
162
+ [crypto.randomUUID()],
163
+ ),
164
+ ).rejects.toThrow(/CHECK constraint failed/);
165
+ });
166
+
167
+ test("rebuild keeps existing rows, auth columns, and managed bindings", async () => {
168
+ const graphql = await upsertScriptConnection({
169
+ slug: "vendor",
170
+ kind: "graphql",
171
+ baseUrl: "https://vendor.example.test/graphql",
172
+ allowedHosts: ["vendor.example.test"],
173
+ auth: { type: "bearer", secret: "vendor-secret-value-123456" },
174
+ });
175
+ expect(graphql.credentialBindingId).not.toBeNull();
176
+ const before = await getDbClient().query<Record<string, unknown>>(
177
+ "SELECT * FROM script_connections ORDER BY id",
178
+ );
179
+
180
+ const db = getDb();
181
+ db.run("PRAGMA foreign_keys = OFF");
182
+ try {
183
+ db.exec(
184
+ readFileSync(
185
+ new URL("../be/migrations/213_script_connections_sql.sql", import.meta.url),
186
+ "utf-8",
187
+ ),
188
+ );
189
+ } finally {
190
+ db.run("PRAGMA foreign_keys = ON");
191
+ }
192
+
193
+ const after = await getDbClient().query<Record<string, unknown>>(
194
+ "SELECT * FROM script_connections ORDER BY id",
195
+ );
196
+ expect(after).toEqual(before);
197
+ expect(after[0]?.auth_type).toBe("bearer");
198
+ expect(after[0]?.auth_config_key).toBe("connection.vendor.secret");
199
+ const bindings = await getDbClient().query<{ id: string }>(
200
+ "SELECT id FROM script_credential_bindings WHERE managed_by_connection_id = ?",
201
+ [graphql.id],
202
+ );
203
+ expect(bindings.map((binding) => binding.id)).toEqual([graphql.credentialBindingId!]);
204
+ });
205
+ });
206
+
207
+ describe("sql connection registration", () => {
208
+ test("inline DSN is stored encrypted under connection.<slug>.secret and never echoed", async () => {
209
+ const response = await registerSql({ dsn: { secret: DSN } });
210
+ expect(response.status).toBe(200);
211
+ expect(response.text).not.toContain("postgres://");
212
+ const connection = (response.json().connection ?? {}) as Record<string, unknown>;
213
+ expect(connection).toMatchObject({
214
+ kind: "sql",
215
+ authType: "none",
216
+ authConfigKey: "connection.analytics.secret",
217
+ });
218
+
219
+ const rows = await configRows("connection.analytics.secret");
220
+ expect(rows).toHaveLength(1);
221
+ expect(rows[0]).toMatchObject({ scope: "global", encrypted: 1 });
222
+ expect(rows[0]?.value).not.toContain("postgres://");
223
+
224
+ const stored = await getScriptConnectionById(String(connection.id));
225
+ expect(stored?.generatedRuntimeJson).not.toContain("postgres://");
226
+ expect(JSON.parse(stored?.generatedRuntimeJson ?? "{}")).toEqual({
227
+ slug: "analytics",
228
+ kind: "sql",
229
+ connectionId: connection.id,
230
+ });
231
+
232
+ const detail = await dispatch(`/api/script-connections/${connection.id}`, { agentId: leadId });
233
+ const list = await dispatch("/api/script-connections", { agentId: leadId });
234
+ expect(detail.status).toBe(200);
235
+ expect(list.status).toBe(200);
236
+ expect(detail.text).not.toContain("postgres://");
237
+ expect(list.text).not.toContain("postgres://");
238
+ });
239
+
240
+ test("registration is lead-only", async () => {
241
+ const response = await registerSql({ dsn: { secret: DSN } }, workerId);
242
+ expect(response.status).toBe(403);
243
+ expect(await configRows("connection.analytics.secret")).toHaveLength(0);
244
+ });
245
+
246
+ test("rejects a non-Postgres DSN without echoing it", async () => {
247
+ for (const secret of ["sqlite:///var/lib/swarm/agent-swarm-db.sqlite", "not a url at all"]) {
248
+ const response = await registerSql({ dsn: { secret } });
249
+ expect(response.status).toBe(400);
250
+ expect(response.text).toContain("postgres://");
251
+ expect(response.text).not.toContain(secret);
252
+ }
253
+ });
254
+
255
+ test("rejects inline auth, both or neither DSN fields, and reserved config keys", async () => {
256
+ const auth = await registerSql({
257
+ dsn: { secret: DSN },
258
+ auth: { type: "bearer", secret: "token-value-1234567890" },
259
+ });
260
+ expect(auth.status).toBe(400);
261
+ expect(auth.text).toContain("`auth` is not supported");
262
+
263
+ const both = await registerSql({ dsn: { secret: DSN, configKey: "ANALYTICS_DSN" } });
264
+ expect(both.status).toBe(400);
265
+ const missing = await registerSql({});
266
+ expect(missing.status).toBe(400);
267
+ expect(missing.text).toContain("dsn.secret");
268
+
269
+ const reserved = await registerSql({ dsn: { configKey: "connection.other.secret" } });
270
+ expect(reserved.status).toBe(400);
271
+ expect(reserved.text).toContain("reserved");
272
+ });
273
+
274
+ test("configKey DSN stores the key as-is", async () => {
275
+ const response = await registerSql({ dsn: { configKey: "ANALYTICS_DSN" } });
276
+ expect(response.status).toBe(200);
277
+ expect(response.json().connection).toMatchObject({ authConfigKey: "ANALYTICS_DSN" });
278
+ expect(await configRows("connection.analytics.secret")).toHaveLength(0);
279
+ });
280
+
281
+ test("edit without dsn keeps the key; a slug rename with a new DSN drops the old secret", async () => {
282
+ const created = (await registerSql({ dsn: { secret: DSN } })).json().connection as {
283
+ id: string;
284
+ };
285
+ const renamed = await registerSql({ id: created.id, slug: "warehouse", displayName: "WH" });
286
+ expect(renamed.status).toBe(200);
287
+ expect(renamed.json().connection).toMatchObject({
288
+ slug: "warehouse",
289
+ authConfigKey: "connection.analytics.secret",
290
+ });
291
+ expect(await configRows("connection.analytics.secret")).toHaveLength(1);
292
+
293
+ const rekeyed = await registerSql({ id: created.id, slug: "warehouse", dsn: { secret: DSN } });
294
+ expect(rekeyed.status).toBe(200);
295
+ expect(rekeyed.json().connection).toMatchObject({
296
+ authConfigKey: "connection.warehouse.secret",
297
+ });
298
+ expect(await configRows("connection.analytics.secret")).toHaveLength(0);
299
+ expect(await configRows("connection.warehouse.secret")).toHaveLength(1);
300
+ });
301
+
302
+ test("descriptors and types expose ctx.api.<slug>.query and typecheck", async () => {
303
+ const created = (await registerSql({ dsn: { secret: DSN } })).json().connection as {
304
+ id: string;
305
+ };
306
+ expect(getScriptApiConnectionDescriptors({ agentId: workerId })).toContainEqual({
307
+ slug: "analytics",
308
+ kind: "sql",
309
+ connectionId: created.id,
310
+ });
311
+ expect(getScriptApiTypes({ agentId: workerId })).toContain("query<T");
312
+
313
+ const typecheck = await typecheckScript(
314
+ `
315
+ import type { ScriptMain } from "swarm-sdk";
316
+ const main: ScriptMain = async (_args, ctx) => {
317
+ const { rows, rowCount, truncated } = await ctx.api.analytics.query("select $1::int as x", [1]);
318
+ return { rows, rowCount, truncated };
319
+ };
320
+ export default main;
321
+ `,
322
+ { agentId: workerId },
323
+ );
324
+ expect(typecheck).toEqual({ ok: true });
325
+ });
326
+ });
327
+
328
+ describe("sql-query route", () => {
329
+ async function createSql(extra: Partial<Parameters<typeof upsertScriptConnection>[0]> = {}) {
330
+ return upsertScriptConnection({
331
+ slug: "analytics",
332
+ kind: "sql",
333
+ sqlDsn: { secret: DSN },
334
+ ...extra,
335
+ });
336
+ }
337
+
338
+ test("a non-lead agent runs a query with the server-resolved DSN", async () => {
339
+ const connection = await createSql();
340
+ const response = await sqlQuery(connection.id, workerId, {
341
+ sql: "select $1::int as x",
342
+ params: [1],
343
+ });
344
+ expect(response.status).toBe(200);
345
+ expect(response.json()).toEqual({ ok: true, rows: [{ x: 1 }], rowCount: 1, truncated: false });
346
+ expect(response.text).not.toContain("postgres://");
347
+ expect(executorCalls).toEqual([{ dsn: DSN, query: "select $1::int as x", params: [1] }]);
348
+ });
349
+
350
+ test("gates: disabled, agent scope, wrong kind, unknown id", async () => {
351
+ const connection = await createSql();
352
+ await setScriptConnectionEnabled(connection.id, false);
353
+ expect((await sqlQuery(connection.id, workerId, { sql: "select 1" })).status).toBe(403);
354
+
355
+ const owned = await createSql({ slug: "owned", scope: "agent", scopeId: leadId });
356
+ expect((await sqlQuery(owned.id, workerId, { sql: "select 1" })).status).toBe(403);
357
+
358
+ const graphql = await upsertScriptConnection({
359
+ slug: "vendor",
360
+ kind: "graphql",
361
+ baseUrl: "https://vendor.example.test/graphql",
362
+ allowedHosts: ["vendor.example.test"],
363
+ });
364
+ expect((await sqlQuery(graphql.id, workerId, { sql: "select 1" })).status).toBe(400);
365
+ const enabledSql = await createSql({ slug: "enabled" });
366
+ const mcpCall = await dispatch(`/api/script-connections/${enabledSql.id}/mcp-call`, {
367
+ method: "POST",
368
+ agentId: workerId,
369
+ body: { tool: "query" },
370
+ });
371
+ expect(mcpCall.status).toBe(400);
372
+ expect((await sqlQuery(crypto.randomUUID(), workerId, { sql: "select 1" })).status).toBe(404);
373
+ expect(executorCalls).toHaveLength(0);
374
+ });
375
+
376
+ test("repo-scoped connections resolve the repo-scope secret", async () => {
377
+ const repoId = crypto.randomUUID();
378
+ const connection = await createSql({ scope: "repo", scopeId: repoId });
379
+ expect(await configRows("connection.analytics.secret")).toEqual([
380
+ expect.objectContaining({ scope: "repo", scopeId: repoId }),
381
+ ]);
382
+ const response = await sqlQuery(connection.id, workerId, { sql: "select 1" });
383
+ expect(response.json()).toMatchObject({ ok: true });
384
+ expect(executorCalls[0]?.dsn).toBe(DSN);
385
+ });
386
+
387
+ test("never falls back to process.env for the DSN key", async () => {
388
+ process.env.ANALYTICS_ENV_DSN = DSN;
389
+ try {
390
+ const connection = await createSql({ sqlDsn: { configKey: "ANALYTICS_ENV_DSN" } });
391
+ const response = await sqlQuery(connection.id, workerId, { sql: "select 1" });
392
+ expect(response.json()).toEqual({
393
+ ok: false,
394
+ error: "SQL connection DSN 'ANALYTICS_ENV_DSN' is not set.",
395
+ });
396
+ expect(executorCalls).toHaveLength(0);
397
+ } finally {
398
+ delete process.env.ANALYTICS_ENV_DSN;
399
+ }
400
+ });
401
+
402
+ test("a stored non-Postgres DSN is refused before any connection", async () => {
403
+ await upsertSwarmConfig({
404
+ scope: "global",
405
+ key: "LOCAL_FILE_DSN",
406
+ value: "sqlite:///tmp/agent-swarm-db.sqlite",
407
+ isSecret: true,
408
+ });
409
+ const connection = await createSql({ sqlDsn: { configKey: "LOCAL_FILE_DSN" } });
410
+ const response = await sqlQuery(connection.id, workerId, { sql: "select 1" });
411
+ expect(response.json()).toMatchObject({ ok: false });
412
+ expect(response.text).not.toContain("sqlite:///");
413
+ expect(executorCalls).toHaveLength(0);
414
+ });
415
+
416
+ test("error text is scrubbed of the DSN and its password", async () => {
417
+ const connection = await createSql();
418
+ setSqlExecutorForTesting(async () => {
419
+ throw new Error(`connect failed for ${DSN} (password sql-conn-test-password-123)`);
420
+ });
421
+ const response = await sqlQuery(connection.id, workerId, { sql: "select 1" });
422
+ const body = response.json();
423
+ expect(body.ok).toBe(false);
424
+ expect(String(body.error)).toContain("[REDACTED");
425
+ expect(response.text).not.toContain("sql-conn-test-password-123");
426
+ expect(response.text).not.toContain("postgres://");
427
+ });
428
+
429
+ test("caps rows, stops fetching, and reports truncation", async () => {
430
+ const connection = await createSql();
431
+ const accepted: boolean[] = [];
432
+ setSqlExecutorForTesting(async (_dsn, _query, _params, take) => {
433
+ for (let b = 0; b < 20; b++) {
434
+ const more = take(Array.from({ length: 100 }, (_, i) => ({ i: b * 100 + i })));
435
+ accepted.push(more);
436
+ if (!more) return;
437
+ }
438
+ });
439
+ const body = (await sqlQuery(connection.id, workerId, { sql: "select 1" })).json();
440
+ expect(body).toMatchObject({ ok: true, rowCount: SQL_MAX_ROWS, truncated: true });
441
+ expect((body.rows as unknown[]).length).toBe(SQL_MAX_ROWS);
442
+ expect(accepted).toEqual([...Array(10).fill(true), false]);
443
+ });
444
+
445
+ test("caps the serialized size and reports truncation", async () => {
446
+ const connection = await createSql();
447
+ const big = "x".repeat(1024 * 1024);
448
+ setSqlExecutorForTesting(async (_dsn, _query, _params, take) => {
449
+ take(Array.from({ length: 10 }, (_, i) => ({ i, big })));
450
+ });
451
+ const response = await sqlQuery(connection.id, workerId, { sql: "select 1" });
452
+ const body = response.json();
453
+ expect(body).toMatchObject({ ok: true, rowCount: 4, truncated: true });
454
+ expect(response.text.length).toBeLessThan(SQL_MAX_BYTES + 1024);
455
+ });
456
+ });
457
+
458
+ describe("ctx.api sql runtime", () => {
459
+ test("query posts to the sql-query route as the script's agent", async () => {
460
+ const calls: Array<{ url: string; agent: string | null; body: unknown }> = [];
461
+ const savedFetch = globalThis.fetch;
462
+ globalThis.fetch = (async (input, init) => {
463
+ calls.push({
464
+ url: String(input),
465
+ agent: new Headers(init?.headers).get("x-agent-id"),
466
+ body: JSON.parse(String(init?.body)),
467
+ });
468
+ return Response.json({ ok: true, rows: [{ x: 1 }], rowCount: 1, truncated: false });
469
+ }) as typeof globalThis.fetch;
470
+ try {
471
+ const swarmConfig = new SwarmConfig({
472
+ system: {
473
+ apiKey: { value: API_KEY, isSecret: true },
474
+ agentId: { value: "agent-runtime", isSecret: false },
475
+ mcpBaseUrl: { value: "http://swarm.test/", isSecret: false },
476
+ },
477
+ user: {},
478
+ });
479
+ const api = createApiRegistryClient(
480
+ [{ slug: "analytics", kind: "sql", connectionId: "conn-1" }],
481
+ { swarmConfig },
482
+ );
483
+ expect(await api.analytics!.query!("select $1::int as x", [1])).toEqual({
484
+ rows: [{ x: 1 }],
485
+ rowCount: 1,
486
+ truncated: false,
487
+ });
488
+ expect(calls).toEqual([
489
+ {
490
+ url: "http://swarm.test/api/script-connections/conn-1/sql-query",
491
+ agent: "agent-runtime",
492
+ body: { sql: "select $1::int as x", params: [1] },
493
+ },
494
+ ]);
495
+ } finally {
496
+ globalThis.fetch = savedFetch;
497
+ }
498
+ });
499
+
500
+ for (const executor of ["native", "quickjs"]) {
501
+ spawnTest(`script round trip through the ${executor} executor`, async () => {
502
+ const connection = await upsertScriptConnection({
503
+ slug: "analytics",
504
+ kind: "sql",
505
+ sqlDsn: { secret: DSN },
506
+ });
507
+ const server = createServer((req, res) => {
508
+ const url = req.url ?? "/";
509
+ const agentId = req.headers["x-agent-id"] as string | undefined;
510
+ void handleScriptConnectionProxy(
511
+ req,
512
+ res,
513
+ getPathSegments(url),
514
+ parseQueryParams(url),
515
+ agentId,
516
+ ).then((handled) => {
517
+ if (!handled) res.writeHead(404).end();
518
+ });
519
+ });
520
+ const port = await listenOnFreePort(server, "127.0.0.1");
521
+ const savedExecutor = process.env.SCRIPT_EXECUTOR;
522
+ process.env.SCRIPT_EXECUTOR = executor;
523
+ try {
524
+ const apiConnections = getScriptApiConnectionDescriptors({ agentId: workerId });
525
+ const egressSecrets = await buildScriptCredentialBindings({ agentId: workerId });
526
+ expect(JSON.stringify({ apiConnections, egressSecrets })).not.toContain("postgres://");
527
+ const output = await runScript({
528
+ agentId: workerId,
529
+ mcpBaseUrl: `http://127.0.0.1:${port}`,
530
+ apiConnections,
531
+ egressSecrets,
532
+ source: `
533
+ export default async (_args, ctx) => ctx.api.analytics.query("select 1 as x");
534
+ `,
535
+ });
536
+ expect(output.error).toBeUndefined();
537
+ expect(output.result).toEqual({ rows: [{ x: 1 }], rowCount: 1, truncated: false });
538
+ expect(executorCalls).toEqual([{ dsn: DSN, query: "select 1 as x", params: [] }]);
539
+ expect(connection.kind).toBe("sql");
540
+ } finally {
541
+ if (savedExecutor === undefined) delete process.env.SCRIPT_EXECUTOR;
542
+ else process.env.SCRIPT_EXECUTOR = savedExecutor;
543
+ server.close();
544
+ }
545
+ });
546
+ }
547
+ });
548
+
549
+ // Proves the read-only guarantee against a real Postgres. CI sets the URL from a postgres service
550
+ // in merge-gate.yml and ci.yml; locally it is opt-in.
551
+ // SQL_CONNECTION_TEST_URL=postgres://postgres:<pw>@127.0.0.1:55432/postgres
552
+ describe.skipIf(!REAL_PG_URL)("read-only against real Postgres", () => {
553
+ const dsn = REAL_PG_URL ?? "";
554
+ const table = "sql_conn_readonly_probe";
555
+ const reader = "sql_conn_reader_probe";
556
+ let admin: SQL;
557
+
558
+ async function rowCount(): Promise<number> {
559
+ const [row] = await admin.unsafe(`select count(*)::int as n from ${table}`);
560
+ return (row as { n: number }).n;
561
+ }
562
+
563
+ async function sqlState(query: string, params: unknown[] = []): Promise<string> {
564
+ setSqlExecutorForTesting(null);
565
+ const err = await runReadOnlyQuery(dsn, query, params).then(
566
+ () => null,
567
+ (e: Error) => e,
568
+ );
569
+ expect(err).not.toBeNull();
570
+ return /SQLSTATE (\w+)/.exec(err?.message ?? "")?.[1] ?? "none";
571
+ }
572
+
573
+ beforeAll(async () => {
574
+ admin = new SQL(dsn);
575
+ await admin.unsafe(`drop table if exists ${table}`);
576
+ await admin.unsafe(`create table ${table} (id serial primary key, v int)`);
577
+ await admin.unsafe(`insert into ${table} (v) values (1), (2)`);
578
+ });
579
+
580
+ afterAll(async () => {
581
+ await admin.unsafe(`drop table if exists ${table}`);
582
+ await admin.unsafe(`drop owned by ${reader}`).catch(() => undefined);
583
+ await admin.unsafe(`drop role if exists ${reader}`);
584
+ await admin.close();
585
+ });
586
+
587
+ beforeEach(() => setSqlExecutorForTesting(null));
588
+
589
+ test("select returns rows", async () => {
590
+ const result = await runReadOnlyQuery(dsn, `select v from ${table} where v = $1`, [2]);
591
+ expect(result).toEqual({ rows: [{ v: 2 }], rowCount: 1, truncated: false });
592
+ expect(await runReadOnlyQuery(dsn, "select 1 as x")).toMatchObject({ rows: [{ x: 1 }] });
593
+ });
594
+
595
+ test("writes are refused by the cursor grammar or the read-only transaction", async () => {
596
+ // 42601: DECLARE ... CURSOR FOR only takes a query. 0A000: no data-modifying WITH.
597
+ // 25006: read_only_sql_transaction, for writes hidden inside a SELECT.
598
+ expect(await sqlState(`insert into ${table} (v) values (3)`)).toBe("42601");
599
+ expect(await sqlState(`insert into ${table} (v) values ($1)`, [3])).toBe("42601");
600
+ expect(await sqlState(`update ${table} set v = 9`)).toBe("42601");
601
+ expect(await sqlState(`delete from ${table}`)).toBe("42601");
602
+ expect(await sqlState("create table sql_conn_should_not_exist (id int)")).toBe("42601");
603
+ expect(await sqlState(`with d as (delete from ${table} returning *) select * from d`)).toBe(
604
+ "0A000",
605
+ );
606
+ expect(await sqlState(`select nextval('${table}_id_seq')`)).toBe("25006");
607
+ expect(await rowCount()).toBe(2);
608
+ });
609
+
610
+ test("the cursor stops at the row cap", async () => {
611
+ expect(
612
+ await runReadOnlyQuery(dsn, "select generate_series(1, $1::int) as i", [5000]),
613
+ ).toMatchObject({
614
+ rowCount: SQL_MAX_ROWS,
615
+ truncated: true,
616
+ });
617
+ expect(
618
+ await runReadOnlyQuery(dsn, `select generate_series(1, ${SQL_MAX_ROWS}) as i`),
619
+ ).toMatchObject({ rowCount: SQL_MAX_ROWS, truncated: false });
620
+ });
621
+
622
+ test("multi-statement calls are rejected, with or without params", async () => {
623
+ // 42601: "cannot insert multiple commands into a prepared statement".
624
+ expect(await sqlState(`select 1; delete from ${table}`)).toBe("42601");
625
+ expect(await sqlState(`select $1::int; delete from ${table}`, [1])).toBe("42601");
626
+ expect(
627
+ await sqlState(
628
+ `commit; set default_transaction_read_only = off; commit; insert into ${table} (v) values (4)`,
629
+ ),
630
+ ).toBe("42601");
631
+ expect(await rowCount()).toBe(2);
632
+ });
633
+
634
+ test("session state never carries into the next call", async () => {
635
+ await runReadOnlyQuery(dsn, "set default_transaction_read_only = off").catch(() => undefined);
636
+ await runReadOnlyQuery(dsn, "select set_config('default_transaction_read_only', 'off', false)");
637
+ expect(await sqlState(`select nextval('${table}_id_seq')`)).toBe("25006");
638
+ await runReadOnlyQuery(dsn, "select set_config('transaction_read_only', 'off', false)").catch(
639
+ () => undefined,
640
+ );
641
+ expect(await sqlState(`select nextval('${table}_id_seq')`)).toBe("25006");
642
+ expect(await rowCount()).toBe(2);
643
+ });
644
+
645
+ // The least-privilege role from the script-connections docs page.
646
+ test("a SELECT-only role cannot reach server files or programs", async () => {
647
+ await admin.unsafe(`drop owned by ${reader}`).catch(() => undefined);
648
+ await admin.unsafe(`drop role if exists ${reader}`);
649
+ await admin.unsafe(
650
+ `create role ${reader} login password 'reader-probe-pw' nosuperuser nocreatedb nocreaterole noreplication nobypassrls`,
651
+ );
652
+ await admin.unsafe(`alter role ${reader} set default_transaction_read_only = on`);
653
+ await admin.unsafe(`grant usage on schema public to ${reader}`);
654
+ await admin.unsafe(`grant select on ${table} to ${reader}`);
655
+ const url = new URL(dsn);
656
+ url.username = reader;
657
+ url.password = "reader-probe-pw";
658
+ const readerDsn = url.toString();
659
+
660
+ expect(await runReadOnlyQuery(readerDsn, `select count(*)::int as n from ${table}`)).toEqual({
661
+ rows: [{ n: 2 }],
662
+ rowCount: 1,
663
+ truncated: false,
664
+ });
665
+ const denied = async (query: string) => {
666
+ const err = await runReadOnlyQuery(readerDsn, query).then(
667
+ () => null,
668
+ (e: Error) => e,
669
+ );
670
+ return /SQLSTATE (\w+)/.exec(err?.message ?? "")?.[1];
671
+ };
672
+ expect(await denied("select pg_read_file('/etc/hostname')")).toBe("42501");
673
+ expect(await denied("select pg_ls_dir('.')")).toBe("42501");
674
+ });
675
+ });
@@ -624,6 +624,10 @@ describe("script connections", () => {
624
624
  markMigrationApplied(database, "206_runtime_pools.sql");
625
625
  // 207 rebuilds session_tokens, which this migration-112-only fixture does not create.
626
626
  markMigrationApplied(database, "207_runtime_session_tokens.sql");
627
+ // 213 rebuilds script_connections with the post-117 columns this fixture skips.
628
+ markMigrationApplied(database, "213_script_connections_sql.sql");
629
+ // 215 alters swarm_config, which this migration-112-only fixture does not create.
630
+ markMigrationApplied(database, "215_swarm_config_export_to_env.sql");
627
631
 
628
632
  const id = crypto.randomUUID();
629
633
  const now = new Date().toISOString();
@@ -757,7 +761,9 @@ describe("script connections", () => {
757
761
  new Date().toISOString(),
758
762
  createHash("sha256").update(sql117).digest("hex"),
759
763
  );
760
- runMigrations(database); // applies every migration except 117
764
+ // 213 rebuilds script_connections with the post-117 columns; skip it with 117.
765
+ markMigrationApplied(database, "213_script_connections_sql.sql");
766
+ runMigrations(database); // applies every migration except 117 and 213
761
767
 
762
768
  const now = new Date().toISOString();
763
769
  // A user-created STANDALONE binding: source='user', arbitrary config_key