@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,334 @@
1
+ import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
2
+ import { unlink } from "node:fs/promises";
3
+ import { SlackMock } from "@desplega.ai/slack-mock";
4
+ import { closeDb, initDb, upsertSwarmConfig } from "../be/db";
5
+ import { validateConfigValue } from "../be/swarm-config-guard";
6
+ import {
7
+ assertNoSlackClash,
8
+ ChatSdkAdaptersSchema,
9
+ getChatSdkConfig,
10
+ readApiUrlOverrides,
11
+ } from "../integrations/chat-sdk/config";
12
+ import { getChatSdkHealth, startChatSdk, stopChatSdk } from "../integrations/chat-sdk/runtime";
13
+ import { snapshotChatSdkEnv } from "./chat-sdk-harness";
14
+
15
+ const TEST_DB_PATH = "./test-chat-sdk-config.sqlite";
16
+ const restoreEnv = snapshotChatSdkEnv();
17
+
18
+ async function removeDbFiles(): Promise<void> {
19
+ for (const suffix of ["", "-wal", "-shm"]) {
20
+ await unlink(`${TEST_DB_PATH}${suffix}`).catch(() => {});
21
+ }
22
+ }
23
+
24
+ /** The brainstorm's example config, one adapter of each kind. */
25
+ const BRAINSTORM_EXAMPLE = {
26
+ slack: {
27
+ botToken: { $config: "SLACK_CHAT_BOT_TOKEN" },
28
+ signingSecret: { $config: "SLACK_CHAT_SIGNING_SECRET" },
29
+ mode: "webhook",
30
+ },
31
+ discord: {
32
+ botToken: { $config: "DISCORD_BOT_TOKEN" },
33
+ publicKey: "abababababababababababababababababababababababababababababababab",
34
+ applicationId: "1234",
35
+ respondToChannelIds: ["c1"],
36
+ respondToGlobalMentions: true,
37
+ },
38
+ telegram: {
39
+ botToken: { $config: "TG_TOKEN" },
40
+ secretToken: "s3cret-token",
41
+ mode: "polling",
42
+ allowedUserIds: ["1", 2],
43
+ },
44
+ teams: {
45
+ appId: "app",
46
+ appPassword: { $config: "TEAMS_PW" },
47
+ appType: "SingleTenant",
48
+ appTenantId: "tenant",
49
+ },
50
+ };
51
+
52
+ function setAdapters(value: unknown): void {
53
+ process.env.CHAT_SDK_ENABLED = "true";
54
+ process.env.CHAT_SDK_ADAPTERS = JSON.stringify(value);
55
+ }
56
+
57
+ beforeAll(async () => {
58
+ await removeDbFiles();
59
+ initDb(TEST_DB_PATH);
60
+ await upsertSwarmConfig({
61
+ scope: "global",
62
+ key: "TG_TOKEN",
63
+ value: "tg-token-from-config",
64
+ isSecret: true,
65
+ });
66
+ await upsertSwarmConfig({ scope: "global", key: "TG_SECRET", value: "tg-secret-from-config" });
67
+ });
68
+
69
+ afterEach(() => {
70
+ restoreEnv();
71
+ delete process.env.CHAT_TEST_ENV_ONLY;
72
+ });
73
+
74
+ afterAll(async () => {
75
+ closeDb();
76
+ await removeDbFiles();
77
+ });
78
+
79
+ describe("ChatSdkAdaptersSchema", () => {
80
+ test("accepts the brainstorm example", () => {
81
+ expect(ChatSdkAdaptersSchema.safeParse(BRAINSTORM_EXAMPLE).success).toBe(true);
82
+ });
83
+
84
+ test("rejects a non-object adapter and an unknown adapter name", () => {
85
+ expect(ChatSdkAdaptersSchema.safeParse({ telegram: "token" }).success).toBe(false);
86
+ expect(ChatSdkAdaptersSchema.safeParse({ whatsapp: {} }).success).toBe(false);
87
+ });
88
+
89
+ test("defaults telegram mode to webhook, never the SDK's auto", () => {
90
+ const parsed = ChatSdkAdaptersSchema.parse({ telegram: { botToken: "x" } });
91
+ expect(parsed.telegram?.mode).toBe("webhook");
92
+ expect(
93
+ ChatSdkAdaptersSchema.safeParse({ telegram: { botToken: "x", mode: "auto" } }).success,
94
+ ).toBe(false);
95
+ });
96
+
97
+ test("slack and teams defaults and mode-dependent required fields", () => {
98
+ const parsed = ChatSdkAdaptersSchema.parse({
99
+ slack: { botToken: "x", signingSecret: "s" },
100
+ teams: { appId: "a", appPassword: "p" },
101
+ });
102
+ expect(parsed.slack?.mode).toBe("webhook");
103
+ expect(parsed.teams?.appType).toBe("MultiTenant");
104
+ const invalid = [
105
+ { slack: { botToken: "x" } },
106
+ { slack: { botToken: "x", mode: "socket" } },
107
+ { teams: { appId: "a", appPassword: "p", appType: "SingleTenant" } },
108
+ ];
109
+ for (const value of invalid) {
110
+ expect(ChatSdkAdaptersSchema.safeParse(value).success).toBe(false);
111
+ }
112
+ });
113
+
114
+ test("passes unknown adapter keys through", () => {
115
+ const parsed = ChatSdkAdaptersSchema.parse({
116
+ telegram: { botToken: "x", mentionOnReply: true },
117
+ });
118
+ expect((parsed.telegram as Record<string, unknown>).mentionOnReply).toBe(true);
119
+ });
120
+ });
121
+
122
+ describe("CHAT_SDK_ADAPTERS validator", () => {
123
+ test("returns an error string for bad JSON", () => {
124
+ expect(validateConfigValue("CHAT_SDK_ADAPTERS", "{not json")).toContain("not valid JSON");
125
+ });
126
+
127
+ test("rejects operator base URLs, verifiers and unverified webhooks", () => {
128
+ for (const key of ["apiUrl", "apiBaseUrl", "webhookVerifier", "allowUnverifiedWebhooks"]) {
129
+ const error = validateConfigValue(
130
+ "CHAT_SDK_ADAPTERS",
131
+ JSON.stringify({ telegram: { botToken: "x", [key]: "http://evil.example" } }),
132
+ );
133
+ expect(error).toContain(`telegram.${key}`);
134
+ }
135
+ });
136
+
137
+ test("Discord: typed mention fields, verifier still rejected", () => {
138
+ const discord = {
139
+ botToken: "x",
140
+ publicKey: "abababababababababababababababababababababababababababababababab",
141
+ applicationId: "1234",
142
+ };
143
+ expect(
144
+ validateConfigValue(
145
+ "CHAT_SDK_ADAPTERS",
146
+ JSON.stringify({ discord: { ...discord, mentionRoleIds: ["r1"] } }),
147
+ ),
148
+ ).toBeNull();
149
+ expect(
150
+ validateConfigValue(
151
+ "CHAT_SDK_ADAPTERS",
152
+ JSON.stringify({ discord: { ...discord, mentionRoleIds: "r1" } }),
153
+ ),
154
+ ).toContain("discord.mentionRoleIds");
155
+ expect(
156
+ validateConfigValue(
157
+ "CHAT_SDK_ADAPTERS",
158
+ JSON.stringify({ discord: { ...discord, webhookVerifier: "x" } }),
159
+ ),
160
+ ).toContain("discord.webhookVerifier");
161
+ const badKey = validateConfigValue(
162
+ "CHAT_SDK_ADAPTERS",
163
+ JSON.stringify({ discord: { ...discord, publicKey: "abcdef" } }),
164
+ );
165
+ expect(badKey).toContain("discord.publicKey");
166
+ expect(badKey).toContain("64 hex characters");
167
+ });
168
+
169
+ test("accepts a partial entry (a dashboard save with fields still missing)", () => {
170
+ expect(
171
+ validateConfigValue("CHAT_SDK_ADAPTERS", JSON.stringify({ discord: { botToken: "x" } })),
172
+ ).toBeNull();
173
+ expect(validateConfigValue("CHAT_SDK_ADAPTERS", JSON.stringify({ slack: {} }))).toBeNull();
174
+ // Formats and forbidden keys still apply to a partial entry.
175
+ expect(
176
+ validateConfigValue("CHAT_SDK_ADAPTERS", JSON.stringify({ discord: { publicKey: "nope" } })),
177
+ ).toContain("64 hex");
178
+ expect(
179
+ validateConfigValue("CHAT_SDK_ADAPTERS", JSON.stringify({ telegram: { mode: "auto" } })),
180
+ ).toContain("telegram.mode");
181
+ });
182
+
183
+ test("accepts a valid value without resolving refs", () => {
184
+ expect(validateConfigValue("CHAT_SDK_ADAPTERS", JSON.stringify(BRAINSTORM_EXAMPLE))).toBeNull();
185
+ });
186
+
187
+ test("CHAT_SDK_ENABLED is a boolean key", () => {
188
+ expect(validateConfigValue("CHAT_SDK_ENABLED", "true")).toBeNull();
189
+ expect(validateConfigValue("CHAT_SDK_ENABLED", "yes please")).not.toBeNull();
190
+ });
191
+ });
192
+
193
+ describe("getChatSdkConfig", () => {
194
+ test("disabled returns no adapters and no error", async () => {
195
+ process.env.CHAT_SDK_ENABLED = "false";
196
+ process.env.CHAT_SDK_ADAPTERS = JSON.stringify({ telegram: { botToken: "x" } });
197
+ expect(await getChatSdkConfig()).toEqual({ enabled: false, adapters: {} });
198
+ });
199
+
200
+ test("resolves refs into functions for botToken and strings for secretToken", async () => {
201
+ setAdapters({
202
+ telegram: { botToken: { $config: "TG_TOKEN" }, secretToken: { $config: "TG_SECRET" } },
203
+ });
204
+ const config = await getChatSdkConfig();
205
+ expect(config.error).toBeUndefined();
206
+ const telegram = config.adapters.telegram!;
207
+ expect(typeof telegram.botToken).toBe("function");
208
+ expect(await (telegram.botToken as () => Promise<string>)()).toBe("tg-token-from-config");
209
+ expect(telegram.secretToken).toBe("tg-secret-from-config");
210
+ expect(telegram.mode).toBe("webhook");
211
+ expect(telegram.userName).toBeUndefined();
212
+ });
213
+
214
+ test("rejects reserved keys as $config targets", async () => {
215
+ for (const key of ["API_KEY", "SECRETS_ENCRYPTION_KEY", "CHAT_SDK_API_URL_OVERRIDES"]) {
216
+ setAdapters({ telegram: { botToken: { $config: key }, secretToken: "s" } });
217
+ const config = await getChatSdkConfig();
218
+ expect(config.adapters).toEqual({});
219
+ expect(config.error).toBeUndefined();
220
+ expect(config.incomplete?.telegram).toContain(`reserved key '${key}'`);
221
+ }
222
+ });
223
+
224
+ test("a ref to an env-only value is reported missing", async () => {
225
+ process.env.CHAT_TEST_ENV_ONLY = "lives-only-in-env";
226
+ setAdapters({ telegram: { botToken: { $config: "CHAT_TEST_ENV_ONLY" }, secretToken: "s" } });
227
+ const config = await getChatSdkConfig();
228
+ expect(config.adapters).toEqual({});
229
+ expect(config.error).toBeUndefined();
230
+ expect(config.incomplete?.telegram).toContain("CHAT_TEST_ENV_ONLY");
231
+ });
232
+
233
+ test("an adapter with a missing required field is incomplete; the others still resolve", async () => {
234
+ setAdapters({
235
+ telegram: { botToken: { $config: "TG_TOKEN" }, secretToken: "s" },
236
+ discord: { botToken: { $config: "TG_TOKEN" } },
237
+ teams: { appId: "a", appPassword: "p", appType: "SingleTenant" },
238
+ });
239
+ const config = await getChatSdkConfig();
240
+ expect(config.error).toBeUndefined();
241
+ expect(Object.keys(config.adapters)).toEqual(["telegram"]);
242
+ expect(config.incomplete?.discord).toBe("CHAT_SDK_ADAPTERS.discord.publicKey: required");
243
+ expect(config.incomplete?.teams).toBe("CHAT_SDK_ADAPTERS.teams.appTenantId: required");
244
+ });
245
+
246
+ test("enabled: false keeps the entry out of the started adapters, listed as disabled", async () => {
247
+ setAdapters({
248
+ telegram: { botToken: { $config: "TG_TOKEN" }, secretToken: "s", enabled: false },
249
+ discord: { botToken: "x", enabled: false },
250
+ });
251
+ const config = await getChatSdkConfig();
252
+ expect(config.adapters).toEqual({});
253
+ expect(config.disabled).toEqual(["telegram", "discord"]);
254
+ // Not validated for completeness while off.
255
+ expect(config.incomplete).toBeUndefined();
256
+ const on = { telegram: { botToken: { $config: "TG_TOKEN" }, secretToken: "s", enabled: true } };
257
+ setAdapters(on);
258
+ const enabled = await getChatSdkConfig();
259
+ expect(Object.keys(enabled.adapters)).toEqual(["telegram"]);
260
+ // The switch is not passed to the adapter factory.
261
+ expect(enabled.adapters.telegram?.enabled).toBeUndefined();
262
+ });
263
+
264
+ test("a forbidden key or an unknown adapter still fails the whole value", async () => {
265
+ setAdapters({ telegram: { botToken: "x" }, discord: { apiUrl: "http://evil.example" } });
266
+ const config = await getChatSdkConfig();
267
+ expect(config.adapters).toEqual({});
268
+ expect(config.error).toContain("discord.apiUrl");
269
+ setAdapters({ telegram: { botToken: "x" }, whatsapp: {} });
270
+ expect((await getChatSdkConfig()).error).toContain("whatsapp");
271
+ });
272
+
273
+ test("reports invalid JSON and schema errors without throwing", async () => {
274
+ process.env.CHAT_SDK_ENABLED = "true";
275
+ process.env.CHAT_SDK_ADAPTERS = "{oops";
276
+ expect((await getChatSdkConfig()).error).toContain("not valid JSON");
277
+ setAdapters({ telegram: { botToken: "x", apiUrl: "http://evil.example" } });
278
+ expect((await getChatSdkConfig()).error).toContain("telegram.apiUrl");
279
+ });
280
+ });
281
+
282
+ describe("assertNoSlackClash", () => {
283
+ test("throws for a slack adapter unless SLACK_DISABLE=true", async () => {
284
+ setAdapters({ slack: { botToken: "xoxb-1", signingSecret: "sig" } });
285
+ delete process.env.SLACK_DISABLE;
286
+ const config = await getChatSdkConfig();
287
+ expect(() => assertNoSlackClash(config)).toThrow("set SLACK_DISABLE=true");
288
+ process.env.SLACK_DISABLE = "true";
289
+ expect(() => assertNoSlackClash(config)).not.toThrow();
290
+ });
291
+
292
+ test("startChatSdk boots a slack entry only with SLACK_DISABLE=true", async () => {
293
+ const slack = await SlackMock.start({ port: 0 });
294
+ try {
295
+ setAdapters({
296
+ slack: {
297
+ mode: "webhook",
298
+ botToken: slack.env.SLACK_BOT_TOKEN,
299
+ signingSecret: "mock-signing-secret",
300
+ },
301
+ });
302
+ process.env.CHAT_SDK_API_URL_OVERRIDES = JSON.stringify({ slack: slack.env.SLACK_API_URL });
303
+
304
+ delete process.env.SLACK_DISABLE;
305
+ await startChatSdk();
306
+ expect(getChatSdkHealth()).toEqual({});
307
+ await stopChatSdk();
308
+
309
+ process.env.SLACK_DISABLE = "true";
310
+ await startChatSdk();
311
+ expect(getChatSdkHealth().slack?.status).toBe("ok");
312
+ } finally {
313
+ await stopChatSdk();
314
+ await slack.stop();
315
+ }
316
+ });
317
+
318
+ test("ignores configs without slack", () => {
319
+ delete process.env.SLACK_DISABLE;
320
+ expect(() => assertNoSlackClash({ enabled: true, adapters: { telegram: {} } })).not.toThrow();
321
+ });
322
+ });
323
+
324
+ describe("readApiUrlOverrides", () => {
325
+ test("reads known adapters from env JSON and ignores garbage", () => {
326
+ process.env.CHAT_SDK_API_URL_OVERRIDES = JSON.stringify({
327
+ telegram: "http://127.0.0.1:1",
328
+ unknown: "http://x",
329
+ });
330
+ expect(readApiUrlOverrides()).toEqual({ telegram: "http://127.0.0.1:1" });
331
+ process.env.CHAT_SDK_API_URL_OVERRIDES = "not json";
332
+ expect(readApiUrlOverrides()).toEqual({});
333
+ });
334
+ });
@@ -0,0 +1,68 @@
1
+ import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
2
+ import { fetchDiscordGuilds, resetDiscordGuildsCache } from "../integrations/chat-sdk/discord-api";
3
+ import { snapshotChatSdkEnv } from "./chat-sdk-harness";
4
+
5
+ const restoreEnv = snapshotChatSdkEnv();
6
+ let server: ReturnType<typeof Bun.serve>;
7
+ let hits: Array<{ auth: string | null; path: string }> = [];
8
+ let respond: () => Response = () => Response.json([]);
9
+
10
+ beforeAll(() => {
11
+ server = Bun.serve({
12
+ port: 0,
13
+ fetch(request) {
14
+ const url = new URL(request.url);
15
+ hits.push({ auth: request.headers.get("authorization"), path: url.pathname });
16
+ return respond();
17
+ },
18
+ });
19
+ process.env.CHAT_SDK_API_URL_OVERRIDES = JSON.stringify({
20
+ discord: `http://127.0.0.1:${server.port}`,
21
+ });
22
+ });
23
+
24
+ afterEach(() => {
25
+ hits = [];
26
+ resetDiscordGuildsCache();
27
+ });
28
+
29
+ afterAll(() => {
30
+ server.stop(true);
31
+ restoreEnv();
32
+ });
33
+
34
+ describe("fetchDiscordGuilds", () => {
35
+ test("lists the bot's servers with the Bot authorization header", async () => {
36
+ respond = () =>
37
+ Response.json([
38
+ { id: "1", name: "sheetsql" },
39
+ { id: "2", name: "test server" },
40
+ ]);
41
+ expect(await fetchDiscordGuilds("tok-1")).toEqual({
42
+ count: 2,
43
+ names: ["sheetsql", "test server"],
44
+ });
45
+ expect(hits).toEqual([{ auth: "Bot tok-1", path: "/users/@me/guilds" }]);
46
+ });
47
+
48
+ test("caches per token for a minute, so the status poll does not hammer Discord", async () => {
49
+ respond = () => Response.json([]);
50
+ const now = 1_000_000;
51
+ expect(await fetchDiscordGuilds("tok-2", now)).toEqual({ count: 0, names: [] });
52
+ expect(await fetchDiscordGuilds("tok-2", now + 30_000)).toEqual({ count: 0, names: [] });
53
+ expect(hits).toHaveLength(1);
54
+ await fetchDiscordGuilds("tok-2", now + 61_000);
55
+ expect(hits).toHaveLength(2);
56
+ // Another token is another bot.
57
+ await fetchDiscordGuilds("tok-3", now + 61_000);
58
+ expect(hits).toHaveLength(3);
59
+ });
60
+
61
+ test("an HTTP error or a bad body yields undefined, without throwing", async () => {
62
+ respond = () => new Response("nope", { status: 401 });
63
+ expect(await fetchDiscordGuilds("tok-4")).toBeUndefined();
64
+ resetDiscordGuildsCache();
65
+ respond = () => Response.json({ not: "an array" });
66
+ expect(await fetchDiscordGuilds("tok-4")).toBeUndefined();
67
+ });
68
+ });
@@ -0,0 +1,252 @@
1
+ import { afterEach, describe, expect, spyOn, test } from "bun:test";
2
+ import type { WebhookOptions } from "chat";
3
+ import { keepDiscordInfoLine } from "../integrations/chat-sdk/adapters";
4
+ import { drainBackground } from "../integrations/chat-sdk/background";
5
+ import {
6
+ type DiscordGatewayListener,
7
+ getDiscordGatewayHealth,
8
+ startDiscordGateway,
9
+ stopDiscordGateway,
10
+ } from "../integrations/chat-sdk/discord-gateway";
11
+ import { createScrubbingLogger, withInfoFilter } from "../integrations/chat-sdk/logger";
12
+
13
+ /**
14
+ * A stand-in for the Discord adapter. Like the real one, it answers at once
15
+ * and hands the listener window to `waitUntil`. A window resolves after its
16
+ * duration or on abort, and never rejects. `innerMs` maps the call index to
17
+ * the window duration.
18
+ */
19
+ function stubAdapter(innerMs: number | ((call: number) => number) = 20) {
20
+ const calls: { durationMs?: number; signal?: AbortSignal }[] = [];
21
+ const adapter: DiscordGatewayListener = {
22
+ async startGatewayListener(options: WebhookOptions, durationMs?: number, signal?: AbortSignal) {
23
+ const ms = typeof innerMs === "function" ? innerMs(calls.length) : innerMs;
24
+ calls.push({ durationMs, signal });
25
+ const inner = new Promise<void>((resolve) => {
26
+ const timer = setTimeout(resolve, ms);
27
+ signal?.addEventListener(
28
+ "abort",
29
+ () => {
30
+ clearTimeout(timer);
31
+ resolve();
32
+ },
33
+ { once: true },
34
+ );
35
+ });
36
+ options.waitUntil?.(inner);
37
+ return new Response(JSON.stringify({ status: "listening" }), { status: 200 });
38
+ },
39
+ };
40
+ return { adapter, calls };
41
+ }
42
+
43
+ /** Resolves on `signal` abort, so an injected sleep never outlives its loop. */
44
+ function onAbort(signal: AbortSignal, resolve: () => void): void {
45
+ signal.addEventListener("abort", () => resolve(), { once: true });
46
+ }
47
+
48
+ /** Resolves when `predicate` holds, polling on the event loop (no fixed sleep). */
49
+ async function waitFor(predicate: () => boolean, timeoutMs = 2_000): Promise<void> {
50
+ const deadline = Date.now() + timeoutMs;
51
+ while (!predicate()) {
52
+ if (Date.now() > deadline) throw new Error("waitFor timed out");
53
+ await new Promise((resolve) => setTimeout(resolve, 1));
54
+ }
55
+ }
56
+
57
+ afterEach(async () => {
58
+ stopDiscordGateway();
59
+ await drainBackground(1_000);
60
+ });
61
+
62
+ describe("Discord gateway loop", () => {
63
+ test("re-arms the listener after each window and counts restarts", async () => {
64
+ const { adapter, calls } = stubAdapter();
65
+ // Each window looks 30 s long, so no backoff runs.
66
+ let clock = 0;
67
+ const now = () => {
68
+ clock += 30_000;
69
+ return clock;
70
+ };
71
+ const sleeps: number[] = [];
72
+ startDiscordGateway(adapter, {
73
+ windowMs: 60_000,
74
+ now,
75
+ sleep: (ms, signal) =>
76
+ new Promise<void>((resolve) => {
77
+ sleeps.push(ms);
78
+ onAbort(signal, resolve);
79
+ resolve();
80
+ }),
81
+ });
82
+
83
+ await waitFor(() => calls.length >= 3);
84
+ expect(calls[0]!.durationMs).toBe(60_000);
85
+ // Each window gets its own signal, not the loop's.
86
+ expect(calls[0]!.signal).not.toBe(calls[1]!.signal);
87
+ expect(sleeps).toEqual([]);
88
+ const health = getDiscordGatewayHealth();
89
+ expect(health?.state).toBe("running");
90
+ expect(health?.restarts).toBeGreaterThanOrEqual(2);
91
+ });
92
+
93
+ test("backs off for 30 s when a window ends in under 10 s", async () => {
94
+ // The first window ends after 20 ms. Later windows only end on abort.
95
+ const { adapter, calls } = stubAdapter((call) => (call === 0 ? 20 : 60_000));
96
+ const sleeps: number[] = [];
97
+ let releaseSleep: (() => void) | undefined;
98
+ startDiscordGateway(adapter, {
99
+ sleep: (ms, signal) => {
100
+ sleeps.push(ms);
101
+ // Hold the loop in backoff until the test releases it or the loop stops.
102
+ return new Promise<void>((resolve) => {
103
+ releaseSleep = resolve;
104
+ onAbort(signal, resolve);
105
+ });
106
+ },
107
+ });
108
+
109
+ await waitFor(() => sleeps.length === 1);
110
+ expect(sleeps).toEqual([30_000]);
111
+ expect(calls).toHaveLength(1);
112
+ expect(getDiscordGatewayHealth()?.state).toBe("backoff");
113
+
114
+ releaseSleep?.();
115
+ await waitFor(() => calls.length === 2);
116
+ expect(getDiscordGatewayHealth()?.restarts).toBe(1);
117
+ expect(getDiscordGatewayHealth()?.state).toBe("running");
118
+ });
119
+
120
+ test("stops on abort within one iteration and reports stopped", async () => {
121
+ // A window far longer than the test: only the abort can end it.
122
+ const { adapter, calls } = stubAdapter(60_000);
123
+ startDiscordGateway(adapter, {
124
+ sleep: (_ms, signal) =>
125
+ new Promise<void>((resolve) => {
126
+ onAbort(signal, resolve);
127
+ resolve();
128
+ }),
129
+ });
130
+ await waitFor(() => calls.length === 1);
131
+ expect(calls[0]!.signal?.aborted).toBe(false);
132
+
133
+ stopDiscordGateway();
134
+ expect(calls[0]!.signal?.aborted).toBe(true);
135
+ // The inner window was tracked for the shutdown drain and is closed now.
136
+ expect(await drainBackground(1_000)).toBe(true);
137
+ await waitFor(() => getDiscordGatewayHealth()?.state === "stopped", 1_000);
138
+ expect(calls).toHaveLength(1);
139
+ });
140
+
141
+ test("a listener that refuses to start backs off instead of spinning", async () => {
142
+ let starts = 0;
143
+ const adapter: DiscordGatewayListener = {
144
+ async startGatewayListener() {
145
+ starts += 1;
146
+ return new Response("Chat instance not initialized", { status: 500 });
147
+ },
148
+ };
149
+ const sleeps: number[] = [];
150
+ startDiscordGateway(adapter, {
151
+ // Yields to the timer queue, as the real 30 s sleep does.
152
+ sleep: (ms, signal) => {
153
+ sleeps.push(ms);
154
+ return new Promise((resolve) => {
155
+ setTimeout(resolve, 1);
156
+ onAbort(signal, resolve);
157
+ });
158
+ },
159
+ });
160
+ await waitFor(() => starts >= 2);
161
+ expect(sleeps.every((ms) => ms === 30_000)).toBe(true);
162
+ expect(sleeps.length).toBeGreaterThanOrEqual(1);
163
+ });
164
+
165
+ test("an abort during the real 30 s backoff sleep ends the loop at once", async () => {
166
+ // Production sleep: windows end after 20 ms, so the loop backs off for 30 s.
167
+ const { adapter, calls } = stubAdapter(20);
168
+ startDiscordGateway(adapter);
169
+ await waitFor(() => getDiscordGatewayHealth()?.state === "backoff");
170
+ await new Promise((resolve) => setTimeout(resolve, 10));
171
+
172
+ const started = Date.now();
173
+ stopDiscordGateway();
174
+ await waitFor(() => getDiscordGatewayHealth()?.state === "stopped", 500);
175
+ expect(Date.now() - started).toBeLessThan(500);
176
+ expect(calls).toHaveLength(1);
177
+ });
178
+
179
+ test("stop returns at once and the drain bounds a window that never closes", async () => {
180
+ let release: (() => void) | undefined;
181
+ let starts = 0;
182
+ const adapter: DiscordGatewayListener = {
183
+ async startGatewayListener(options: WebhookOptions) {
184
+ starts += 1;
185
+ // Ignores the abort signal: only `release` ends this window.
186
+ options.waitUntil?.(
187
+ new Promise<void>((resolve) => {
188
+ release = resolve;
189
+ }),
190
+ );
191
+ return new Response("{}", { status: 200 });
192
+ },
193
+ };
194
+ startDiscordGateway(adapter);
195
+ await waitFor(() => starts === 1);
196
+
197
+ const stopStarted = Date.now();
198
+ stopDiscordGateway();
199
+ expect(Date.now() - stopStarted).toBeLessThan(500);
200
+
201
+ const drainStarted = Date.now();
202
+ expect(await drainBackground(200)).toBe(false);
203
+ const drainMs = Date.now() - drainStarted;
204
+ expect(drainMs).toBeGreaterThanOrEqual(150);
205
+ expect(drainMs).toBeLessThan(1_000);
206
+ expect(getDiscordGatewayHealth()?.state).toBe("running");
207
+
208
+ // Let the loop exit so later tests see an empty background set.
209
+ release?.();
210
+ await waitFor(() => getDiscordGatewayHealth()?.state === "stopped");
211
+ expect(starts).toBe(1);
212
+ });
213
+ });
214
+
215
+ describe("Chat SDK logger", () => {
216
+ test("the Discord info filter drops noisy lines and keeps the others", () => {
217
+ const info = spyOn(console, "info").mockImplementation(() => {});
218
+ try {
219
+ const logger = withInfoFilter(createScrubbingLogger("discord"), keepDiscordInfoLine);
220
+ logger.info("Discord Gateway message received", { content: "private guild text" });
221
+ logger.child("gateway").info("Discord signature verification attempt", { publicKey: "k" });
222
+ logger.info("Discord Gateway connected", { username: "bot" });
223
+ expect(info).toHaveBeenCalledTimes(1);
224
+ expect(info.mock.calls[0]?.[1]).toBe("Discord Gateway connected");
225
+ } finally {
226
+ info.mockRestore();
227
+ }
228
+ });
229
+
230
+ test("warn and error lines pass the filter and carry their cause to the sink", () => {
231
+ const error = spyOn(console, "error").mockImplementation(() => {});
232
+ const warn = spyOn(console, "warn").mockImplementation(() => {});
233
+ try {
234
+ const issues: string[] = [];
235
+ const logger = withInfoFilter(
236
+ createScrubbingLogger("discord", (level, message) => issues.push(`${level}: ${message}`)),
237
+ () => false,
238
+ );
239
+ logger.error("Discord Gateway listener error", { error: "Error [TokenInvalid]: bad" });
240
+ logger.warn("Discord webhook verifier rejected the request", new Error("nope"));
241
+ logger.warn("x".repeat(400));
242
+ expect(issues[0]).toBe("error: Discord Gateway listener error: Error [TokenInvalid]: bad");
243
+ expect(issues[1]).toBe("warn: Discord webhook verifier rejected the request: Error: nope");
244
+ expect(issues[2]).toHaveLength("warn: ".length + 300);
245
+ expect(error).toHaveBeenCalledTimes(1);
246
+ expect(warn).toHaveBeenCalledTimes(2);
247
+ } finally {
248
+ error.mockRestore();
249
+ warn.mockRestore();
250
+ }
251
+ });
252
+ });