@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,126 @@
1
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
+ import { unlink } from "node:fs/promises";
3
+ import { closeDb, initDb } from "../be/db";
4
+ import {
5
+ type DiscordGatewayListener,
6
+ getDiscordGatewayHealth,
7
+ startDiscordGateway,
8
+ } from "../integrations/chat-sdk/discord-gateway";
9
+ import { stopChatRelayWatcher } from "../integrations/chat-sdk/relay";
10
+ import { getChatSdkHealth, startChatSdk, stopChatSdk } from "../integrations/chat-sdk/runtime";
11
+ import { setChatSdkTestHooks } from "../integrations/chat-sdk/test-hooks";
12
+ import { postChatWebhook, snapshotChatSdkEnv } from "./chat-sdk-harness";
13
+ import { getFreePort } from "./test-net";
14
+
15
+ /*
16
+ * Network: Discord `initialize()` only resolves the application id (no API
17
+ * call), and a PING is answered from the signature check alone. The Gateway
18
+ * loop is off through the test hook: it logs in through `discord.js`, which
19
+ * ignores `apiUrl` and would reach the real Discord API. `apiUrl` points at a
20
+ * port nothing listens on, so an unexpected REST call fails here.
21
+ */
22
+ const TEST_DB_PATH = "./test-chat-sdk-discord-webhook.sqlite";
23
+ const restoreEnv = snapshotChatSdkEnv();
24
+ let privateKey: CryptoKey;
25
+
26
+ function toHex(bytes: ArrayBuffer): string {
27
+ return Buffer.from(bytes).toString("hex");
28
+ }
29
+
30
+ async function removeDbFiles(): Promise<void> {
31
+ for (const suffix of ["", "-wal", "-shm"]) {
32
+ await unlink(`${TEST_DB_PATH}${suffix}`).catch(() => {});
33
+ }
34
+ }
35
+
36
+ /** Discord signs `timestamp + body` with the application's Ed25519 key. */
37
+ async function signedHeaders(body: string): Promise<Record<string, string>> {
38
+ const timestamp = String(Math.floor(Date.now() / 1000));
39
+ const signature = await crypto.subtle.sign(
40
+ "Ed25519",
41
+ privateKey,
42
+ new TextEncoder().encode(timestamp + body),
43
+ );
44
+ return {
45
+ "content-type": "application/json",
46
+ "x-signature-ed25519": toHex(signature),
47
+ "x-signature-timestamp": timestamp,
48
+ };
49
+ }
50
+
51
+ beforeAll(async () => {
52
+ await removeDbFiles();
53
+ initDb(TEST_DB_PATH);
54
+ const keyPair = (await crypto.subtle.generateKey({ name: "Ed25519" }, true, [
55
+ "sign",
56
+ "verify",
57
+ ])) as CryptoKeyPair;
58
+ privateKey = keyPair.privateKey;
59
+ const publicKey = toHex(await crypto.subtle.exportKey("raw", keyPair.publicKey));
60
+
61
+ setChatSdkTestHooks({ disableDiscordGateway: true });
62
+ process.env.CHAT_SDK_ENABLED = "true";
63
+ process.env.CHAT_SDK_ADAPTERS = JSON.stringify({
64
+ discord: { botToken: "discord-test-token", publicKey, applicationId: "1234567890" },
65
+ });
66
+ process.env.CHAT_SDK_API_URL_OVERRIDES = JSON.stringify({
67
+ discord: `http://127.0.0.1:${await getFreePort()}/api/v10`,
68
+ });
69
+ await startChatSdk();
70
+ await stopChatRelayWatcher();
71
+ });
72
+
73
+ afterAll(async () => {
74
+ await stopChatSdk();
75
+ setChatSdkTestHooks({});
76
+ restoreEnv();
77
+ closeDb();
78
+ await removeDbFiles();
79
+ });
80
+
81
+ describe("Chat SDK Discord interactions webhook", () => {
82
+ test("the adapter starts without the Gateway loop", () => {
83
+ const health = getChatSdkHealth().discord;
84
+ expect(health?.status).toBe("ok");
85
+ expect(health?.gateway).toBeUndefined();
86
+ });
87
+
88
+ test("an unsigned interaction is rejected with 401", async () => {
89
+ const response = await postChatWebhook("discord", JSON.stringify({ type: 1 }), {
90
+ "content-type": "application/json",
91
+ });
92
+ expect(response.status).toBe(401);
93
+ });
94
+
95
+ test("a body that does not match its signature is rejected with 401", async () => {
96
+ const headers = await signedHeaders(JSON.stringify({ type: 1 }));
97
+ const response = await postChatWebhook("discord", JSON.stringify({ type: 2 }), headers);
98
+ expect(response.status).toBe(401);
99
+ });
100
+
101
+ test("a signed PING is answered with a PONG", async () => {
102
+ const body = JSON.stringify({ type: 1 });
103
+ const response = await postChatWebhook("discord", body, await signedHeaders(body));
104
+ expect(response.status).toBe(200);
105
+ expect(JSON.parse(response.body)).toEqual({ type: 1 });
106
+ });
107
+
108
+ // Last in the file: it stops the runtime. `afterAll` stops it again, a no-op.
109
+ test("health shows the Gateway loop while it runs and drops it on shutdown", async () => {
110
+ // A stub listener: the real one logs in to Discord. Its window ends only on abort.
111
+ const gateway: DiscordGatewayListener = {
112
+ async startGatewayListener(options, _durationMs, signal) {
113
+ options.waitUntil?.(
114
+ new Promise<void>((resolve) => signal?.addEventListener("abort", () => resolve())),
115
+ );
116
+ return new Response("{}", { status: 200 });
117
+ },
118
+ };
119
+ startDiscordGateway(gateway);
120
+ expect(getChatSdkHealth().discord?.gateway?.state).toBe("running");
121
+
122
+ await stopChatSdk();
123
+ expect(getChatSdkHealth().discord).toBeUndefined();
124
+ expect(getDiscordGatewayHealth()).toBeUndefined();
125
+ });
126
+ });
@@ -0,0 +1,50 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { CHAT_SDK_ADAPTER_DEFS, chatSdkSecretKey } from "../../apps/ui/src/lib/chat-sdk-fields";
3
+ import {
4
+ CHAT_SDK_ADAPTER_FIELDS,
5
+ CHAT_SDK_ADAPTER_NAMES,
6
+ SlackAdapterSchema,
7
+ TeamsAdapterSchema,
8
+ TelegramAdapterSchema,
9
+ } from "../integrations/chat-sdk/schema";
10
+
11
+ /*
12
+ * The dashboard keeps a plain field list for the Chat SDK card because it
13
+ * cannot import the API's zod schemas. This test keeps the two equal.
14
+ */
15
+ describe("Chat SDK field list sync (UI vs API schema)", () => {
16
+ test("the UI lists the same adapters in the same order", () => {
17
+ expect(CHAT_SDK_ADAPTER_DEFS.map((def) => def.name)).toEqual([...CHAT_SDK_ADAPTER_NAMES]);
18
+ });
19
+
20
+ test("every adapter has the same field names", () => {
21
+ for (const def of CHAT_SDK_ADAPTER_DEFS) {
22
+ expect(def.fields.map((field) => field.key).sort()).toEqual(
23
+ [...CHAT_SDK_ADAPTER_FIELDS[def.name]].sort(),
24
+ );
25
+ }
26
+ });
27
+
28
+ test("select options match the schema enums", () => {
29
+ const enums: Record<string, readonly string[]> = {
30
+ "telegram.mode": TelegramAdapterSchema.shape.mode.unwrap().options,
31
+ "slack.mode": SlackAdapterSchema.shape.mode.unwrap().options,
32
+ "teams.appType": TeamsAdapterSchema.shape.appType.unwrap().options,
33
+ };
34
+ const selects: Array<readonly [string, string[] | undefined]> = CHAT_SDK_ADAPTER_DEFS.flatMap(
35
+ (def) =>
36
+ def.fields
37
+ .filter((field) => field.kind === "select")
38
+ .map((field) => [`${def.name}.${field.key}`, field.options] as const),
39
+ );
40
+ expect(selects.map(([path]) => path).sort()).toEqual(Object.keys(enums).sort());
41
+ for (const [path, options] of selects) {
42
+ expect(options).toEqual([...(enums[path] ?? [])]);
43
+ }
44
+ });
45
+
46
+ test("secret rows are named CHAT_SDK_<ADAPTER>_<FIELD>", () => {
47
+ expect(chatSdkSecretKey("telegram", "botToken")).toBe("CHAT_SDK_TELEGRAM_BOT_TOKEN");
48
+ expect(chatSdkSecretKey("teams", "appPassword")).toBe("CHAT_SDK_TEAMS_APP_PASSWORD");
49
+ });
50
+ });
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Shared harness for Chat SDK tests that drive platform webhooks through the
3
+ * real route. Telegram runs against the fake Bot API
4
+ * (`scripts/dev/telegram-bot-api-mock.ts`); `postChatWebhook` and
5
+ * `snapshotChatSdkEnv` serve the Slack and Teams tests too.
6
+ */
7
+ import { expect } from "bun:test";
8
+ import type { IncomingMessage, ServerResponse } from "node:http";
9
+ import {
10
+ startTelegramBotApiMock,
11
+ type TelegramBotApiMock,
12
+ } from "../../scripts/dev/telegram-bot-api-mock";
13
+ import { handleWebhooks } from "../http/webhooks";
14
+ import { drainBackground } from "../integrations/chat-sdk/background";
15
+ import { startChatSdk, stopChatSdk } from "../integrations/chat-sdk/runtime";
16
+ import { flushAllChatBuffers } from "../integrations/chat-sdk/thread-buffer";
17
+
18
+ export const TELEGRAM_SECRET = "chat-sdk-test-secret";
19
+ const ENV_KEYS = [
20
+ "CHAT_SDK_ENABLED",
21
+ "CHAT_SDK_ADAPTERS",
22
+ "CHAT_SDK_API_URL_OVERRIDES",
23
+ "CHAT_SDK_BUFFER_MS",
24
+ "SLACK_DISABLE",
25
+ ];
26
+
27
+ /** Save the Chat SDK env keys; the returned function restores them. */
28
+ export function snapshotChatSdkEnv(): () => void {
29
+ const saved = new Map(ENV_KEYS.map((key) => [key, process.env[key]]));
30
+ return () => {
31
+ for (const [key, value] of saved) {
32
+ if (value === undefined) delete process.env[key];
33
+ else process.env[key] = value;
34
+ }
35
+ };
36
+ }
37
+
38
+ let nextUpdateId = 950_000;
39
+ let nextMessageId = 5_000;
40
+
41
+ export interface TelegramHarness {
42
+ mock: TelegramBotApiMock;
43
+ stop: () => Promise<void>;
44
+ }
45
+
46
+ /** Start the mock Bot API and a webhook-mode Telegram adapter pointed at it. */
47
+ export async function startTelegramHarness(bufferMs = 60_000): Promise<TelegramHarness> {
48
+ const restoreEnv = snapshotChatSdkEnv();
49
+ const mock = startTelegramBotApiMock();
50
+ process.env.CHAT_SDK_ENABLED = "true";
51
+ process.env.CHAT_SDK_ADAPTERS = JSON.stringify({
52
+ telegram: { botToken: "t", secretToken: TELEGRAM_SECRET, mode: "webhook" },
53
+ });
54
+ process.env.CHAT_SDK_API_URL_OVERRIDES = JSON.stringify({ telegram: mock.url });
55
+ process.env.CHAT_SDK_BUFFER_MS = String(bufferMs);
56
+ await startChatSdk();
57
+ return {
58
+ mock,
59
+ stop: async () => {
60
+ await stopChatSdk();
61
+ mock.stop();
62
+ restoreEnv();
63
+ },
64
+ };
65
+ }
66
+
67
+ export interface TelegramChat {
68
+ id: number;
69
+ type: "private" | "group";
70
+ }
71
+
72
+ /** A Telegram `message` update from a human in `chat`. */
73
+ export function telegramUpdate(
74
+ chat: TelegramChat,
75
+ text: string,
76
+ extra: Record<string, unknown> = {},
77
+ ): Record<string, unknown> {
78
+ const from = { id: 777_001, is_bot: false, first_name: "Tester", username: "tester" };
79
+ return {
80
+ update_id: ++nextUpdateId,
81
+ message: {
82
+ message_id: ++nextMessageId,
83
+ date: Math.floor(Date.now() / 1000),
84
+ chat:
85
+ chat.type === "private"
86
+ ? { id: chat.id, type: "private", first_name: "Tester" }
87
+ : { id: chat.id, type: "group", title: "Swarm group" },
88
+ from,
89
+ ...(text ? { text } : {}),
90
+ ...extra,
91
+ },
92
+ };
93
+ }
94
+
95
+ export interface ChatWebhookResponse {
96
+ status: number;
97
+ body: string;
98
+ }
99
+
100
+ /** POST a raw body to `/api/integrations/chat/{adapter}/webhook` through `handleWebhooks`. */
101
+ export async function postChatWebhook(
102
+ adapter: string,
103
+ body: string,
104
+ headers: Record<string, string>,
105
+ ): Promise<ChatWebhookResponse> {
106
+ const path = `/api/integrations/chat/${adapter}/webhook`;
107
+ const req = {
108
+ method: "POST",
109
+ url: path,
110
+ headers: Object.fromEntries(Object.entries(headers).map(([k, v]) => [k.toLowerCase(), v])),
111
+ rawHeaders: Object.entries(headers).flat(),
112
+ async *[Symbol.asyncIterator]() {
113
+ yield Buffer.from(body);
114
+ },
115
+ } as unknown as IncomingMessage;
116
+ const captured = { status: 0, body: "" };
117
+ const res = {
118
+ headersSent: false,
119
+ writeHead(status: number) {
120
+ captured.status = status;
121
+ return this;
122
+ },
123
+ end(chunk?: string | Buffer) {
124
+ if (chunk) captured.body = chunk.toString();
125
+ return this;
126
+ },
127
+ } as unknown as ServerResponse;
128
+ expect(await handleWebhooks(req, res, path.split("/").filter(Boolean))).toBe(true);
129
+ return captured;
130
+ }
131
+
132
+ /** POST one update to the Telegram webhook and wait for the handler work. */
133
+ export async function postTelegramUpdate(update: unknown): Promise<void> {
134
+ const response = await postChatWebhook("telegram", JSON.stringify(update), {
135
+ "content-type": "application/json",
136
+ "x-telegram-bot-api-secret-token": TELEGRAM_SECRET,
137
+ });
138
+ expect(response.status).toBe(200);
139
+ expect(await drainBackground(5_000)).toBe(true);
140
+ }
141
+
142
+ /** Flush every waiting thread (no timer wait) and wait for the resulting work. */
143
+ export async function flushChatBuffers(): Promise<void> {
144
+ await flushAllChatBuffers();
145
+ expect(await drainBackground(5_000)).toBe(true);
146
+ }
@@ -0,0 +1,363 @@
1
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
2
+ import { mkdtemp, rm, unlink } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import {
6
+ closeDb,
7
+ createAgent,
8
+ getDbClient,
9
+ getTaskAttachments,
10
+ getTaskById,
11
+ initDb,
12
+ upsertKv,
13
+ } from "../be/db";
14
+ import { resetFileStorageProviderForTests } from "../fs/registry";
15
+ import { drainBackground } from "../integrations/chat-sdk/background";
16
+ import { CHAT_CHANNELS_NAMESPACE, routeChatMessage } from "../integrations/chat-sdk/router";
17
+ import { getChatInstance } from "../integrations/chat-sdk/runtime";
18
+ import { isChatThreadBuffered } from "../integrations/chat-sdk/thread-buffer";
19
+ import { chatSdkContextKey } from "../tasks/context-key";
20
+ import type { Agent } from "../types";
21
+ import {
22
+ flushChatBuffers,
23
+ postChatWebhook,
24
+ postTelegramUpdate,
25
+ startTelegramHarness,
26
+ TELEGRAM_SECRET,
27
+ type TelegramChat,
28
+ type TelegramHarness,
29
+ telegramUpdate,
30
+ } from "./chat-sdk-harness";
31
+
32
+ const TEST_DB_PATH = "./test-chat-sdk-inbound.sqlite";
33
+ const ENV_KEYS = ["AGENT_FS_LOCAL_DIR", "AGENT_FS_API_URL", "STEERING_ENABLED"];
34
+ const savedEnv = new Map(ENV_KEYS.map((key) => [key, process.env[key]]));
35
+
36
+ let harness: TelegramHarness;
37
+ let lead: Agent;
38
+ let worker: Agent;
39
+ let fsDir: string;
40
+ /** `ctx.skipped` length seen by every DM / subscribed delivery (always 0 under `concurrent`). */
41
+ const skippedCounts: number[] = [];
42
+
43
+ type TaskRow = {
44
+ id: string;
45
+ agentId: string | null;
46
+ task: string;
47
+ status: string;
48
+ routingReason: string | null;
49
+ };
50
+
51
+ async function chatTasks(chatId: number): Promise<TaskRow[]> {
52
+ return getDbClient().query<TaskRow>(
53
+ `SELECT id, agentId, task, status, routing_reason AS routingReason FROM agent_tasks
54
+ WHERE source = 'chat' AND contextKey = ? ORDER BY createdAt, rowid`,
55
+ [chatSdkContextKey(`telegram:${chatId}`)],
56
+ );
57
+ }
58
+
59
+ async function removeDbFiles(): Promise<void> {
60
+ for (const suffix of ["", "-wal", "-shm"]) {
61
+ await unlink(`${TEST_DB_PATH}${suffix}`).catch(() => {});
62
+ }
63
+ }
64
+
65
+ beforeAll(async () => {
66
+ await removeDbFiles();
67
+ fsDir = await mkdtemp(join(tmpdir(), "chat-sdk-inbound-fs-"));
68
+ process.env.AGENT_FS_LOCAL_DIR = fsDir;
69
+ delete process.env.AGENT_FS_API_URL;
70
+ process.env.STEERING_ENABLED = "true";
71
+ resetFileStorageProviderForTests();
72
+ initDb(TEST_DB_PATH);
73
+ lead = await createAgent({
74
+ id: crypto.randomUUID(),
75
+ name: "ChatInboundLead",
76
+ isLead: true,
77
+ status: "idle",
78
+ });
79
+ worker = await createAgent({
80
+ id: crypto.randomUUID(),
81
+ name: "ChatInboundWorker",
82
+ isLead: false,
83
+ status: "idle",
84
+ });
85
+ harness = await startTelegramHarness();
86
+ const chat = getChatInstance("telegram");
87
+ expect(chat).toBeDefined();
88
+ chat!.onDirectMessage((_thread, _message, _channel, ctx) => {
89
+ skippedCounts.push(ctx?.skipped?.length ?? 0);
90
+ });
91
+ chat!.onSubscribedMessage((_thread, _message, ctx) => {
92
+ skippedCounts.push(ctx?.skipped?.length ?? 0);
93
+ });
94
+ });
95
+
96
+ afterAll(async () => {
97
+ await harness.stop();
98
+ closeDb();
99
+ await removeDbFiles();
100
+ await rm(fsDir, { recursive: true, force: true });
101
+ for (const [key, value] of savedEnv) {
102
+ if (value === undefined) delete process.env[key];
103
+ else process.env[key] = value;
104
+ }
105
+ resetFileStorageProviderForTests();
106
+ });
107
+
108
+ beforeEach(() => {
109
+ process.env.CHAT_SDK_BUFFER_MS = "60000";
110
+ });
111
+
112
+ const dm = (id: number): TelegramChat => ({ id, type: "private" });
113
+ const group = (id: number): TelegramChat => ({ id, type: "group" });
114
+
115
+ describe("Chat SDK inbound pipeline", () => {
116
+ test("two messages inside the buffer window become one task", async () => {
117
+ await postTelegramUpdate(telegramUpdate(dm(2001), "first part of the request"));
118
+ await postTelegramUpdate(telegramUpdate(dm(2001), "second part of the request"));
119
+ expect(await chatTasks(2001)).toHaveLength(0);
120
+
121
+ await flushChatBuffers();
122
+ const tasks = await chatTasks(2001);
123
+ expect(tasks).toHaveLength(1);
124
+ expect(tasks[0]!.task).toContain("[Thread follow-up: 2 message(s) buffered]");
125
+ expect(tasks[0]!.task).toContain("first part of the request");
126
+ expect(tasks[0]!.task).toContain("second part of the request");
127
+ expect(tasks[0]!.agentId).toBe(lead.id);
128
+ // `concurrent` never batches deliveries, so the SDK skips nothing.
129
+ expect(skippedCounts.length).toBeGreaterThanOrEqual(2);
130
+ expect(skippedCounts.every((count) => count === 0)).toBe(true);
131
+ });
132
+
133
+ test("!now flushes the thread at once", async () => {
134
+ await postTelegramUpdate(telegramUpdate(dm(2002), "!now ship the hotfix"));
135
+ const tasks = await chatTasks(2002);
136
+ expect(tasks).toHaveLength(1);
137
+ expect(tasks[0]!.task).toBe("ship the hotfix");
138
+ });
139
+
140
+ test("swarm#<uuid> routes to that agent", async () => {
141
+ await postTelegramUpdate(telegramUpdate(dm(2003), `swarm#${worker.id} build the parser`));
142
+ await flushChatBuffers();
143
+ const tasks = await chatTasks(2003);
144
+ expect(tasks).toHaveLength(1);
145
+ expect(tasks[0]!.agentId).toBe(worker.id);
146
+ expect(tasks[0]!.task).toBe("build the parser");
147
+ });
148
+
149
+ test("a KV channel mapping routes to the pinned agent", async () => {
150
+ await upsertKv({
151
+ namespace: CHAT_CHANNELS_NAMESPACE,
152
+ key: "telegram:2004",
153
+ value: { agentId: worker.id },
154
+ valueType: "json",
155
+ });
156
+ await postTelegramUpdate(telegramUpdate(dm(2004), "summarize the logs"));
157
+ await flushChatBuffers();
158
+ const tasks = await chatTasks(2004);
159
+ expect(tasks).toHaveLength(1);
160
+ expect(tasks[0]!.agentId).toBe(worker.id);
161
+ expect(tasks[0]!.routingReason).toBe("human_pinned");
162
+ });
163
+
164
+ test("a workflow channel mapping fires only for a mention or DM, not a subscribed reply", async () => {
165
+ await upsertKv({
166
+ namespace: CHAT_CHANNELS_NAMESPACE,
167
+ key: "telegram:-2008",
168
+ value: { workflowId: "wf-mapped" },
169
+ valueType: "json",
170
+ });
171
+ const ctx = {
172
+ adapter: "telegram" as const,
173
+ channelId: "telegram:-2008",
174
+ threadId: "telegram:-2008",
175
+ };
176
+ expect(
177
+ await routeChatMessage("run it", { ...ctx, botMentioned: true, isDirectMessage: false }),
178
+ ).toEqual({ kind: "workflow", workflowId: "wf-mapped" });
179
+ expect(
180
+ await routeChatMessage("run it", { ...ctx, botMentioned: false, isDirectMessage: true }),
181
+ ).toEqual({ kind: "workflow", workflowId: "wf-mapped" });
182
+ // A plain reply in the subscribed thread after the first run: no owner, no mention.
183
+ expect(
184
+ await routeChatMessage("thanks", { ...ctx, botMentioned: false, isDirectMessage: false }),
185
+ ).toBeNull();
186
+ });
187
+
188
+ async function steeringRows(taskId: string) {
189
+ return getDbClient().query<{ body: string; source: string }>(
190
+ "SELECT body, source FROM task_steering_messages WHERE task_id = ?",
191
+ [taskId],
192
+ );
193
+ }
194
+
195
+ test("a group reply to an in-progress lead task steers it instead of creating a task", async () => {
196
+ await postTelegramUpdate(telegramUpdate(group(-2005), "@swarm_test_bot draft the report"));
197
+ await flushChatBuffers();
198
+ const [first] = await chatTasks(-2005);
199
+ expect(first?.agentId).toBe(lead.id);
200
+ await getDbClient().run("UPDATE agent_tasks SET status = 'in_progress' WHERE id = ?", [
201
+ first!.id,
202
+ ]);
203
+
204
+ const acksBefore = harness.mock.calls.filter((call) =>
205
+ /^send(Rich)?Message$/.test(call.method),
206
+ ).length;
207
+ await postTelegramUpdate(telegramUpdate(group(-2005), "also add the charts"));
208
+ await flushChatBuffers();
209
+
210
+ expect(await chatTasks(-2005)).toHaveLength(1);
211
+ expect(await steeringRows(first!.id)).toEqual([
212
+ { body: "also add the charts", source: "chat" },
213
+ ]);
214
+ expect(
215
+ harness.mock.calls.filter((call) => /^send(Rich)?Message$/.test(call.method)).length,
216
+ ).toBe(acksBefore + 1);
217
+ });
218
+
219
+ test("a Telegram private-chat DM steers too (DMs never reach onSubscribedMessage)", async () => {
220
+ await postTelegramUpdate(telegramUpdate(dm(2006), "start the migration"));
221
+ await flushChatBuffers();
222
+ const [first] = await chatTasks(2006);
223
+ await getDbClient().run("UPDATE agent_tasks SET status = 'in_progress' WHERE id = ?", [
224
+ first!.id,
225
+ ]);
226
+
227
+ await postTelegramUpdate(telegramUpdate(dm(2006), "skip the staging step"));
228
+ await flushChatBuffers();
229
+
230
+ expect(await chatTasks(2006)).toHaveLength(1);
231
+ expect(await steeringRows(first!.id)).toEqual([
232
+ { body: "skip the staging step", source: "chat" },
233
+ ]);
234
+ });
235
+
236
+ test("a reply while the task is still pending becomes a follow-up that depends on it", async () => {
237
+ await postTelegramUpdate(telegramUpdate(dm(2007), "plan the release"));
238
+ await flushChatBuffers();
239
+ const [first] = await chatTasks(2007);
240
+ expect(first?.status).toBe("pending");
241
+
242
+ await postTelegramUpdate(telegramUpdate(dm(2007), "include the changelog"));
243
+ await flushChatBuffers();
244
+
245
+ const tasks = await chatTasks(2007);
246
+ expect(tasks).toHaveLength(2);
247
+ const followUp = await getTaskById(tasks[1]!.id);
248
+ expect(followUp?.dependsOn).toEqual([first!.id]);
249
+ expect(followUp?.parentTaskId).toBe(first!.id);
250
+ expect(followUp?.task).toContain("include the changelog");
251
+ });
252
+
253
+ test("a document attachment becomes a task attachment and a [File: ...] line", async () => {
254
+ const bytes = new TextEncoder().encode("meeting notes\n");
255
+ harness.mock.addFile("documents/notes.txt", bytes);
256
+ await postTelegramUpdate(
257
+ telegramUpdate(dm(2008), "", {
258
+ caption: "see the attached notes",
259
+ document: {
260
+ file_id: "documents/notes.txt",
261
+ file_unique_id: "notes-unique",
262
+ file_name: "notes.txt",
263
+ mime_type: "text/plain",
264
+ file_size: bytes.byteLength,
265
+ },
266
+ }),
267
+ );
268
+ await flushChatBuffers();
269
+
270
+ const tasks = await chatTasks(2008);
271
+ expect(tasks).toHaveLength(1);
272
+ expect(tasks[0]!.status).toBe("pending");
273
+ expect(tasks[0]!.task).toContain("see the attached notes");
274
+ expect(tasks[0]!.task).toMatch(/\[File: notes\.txt \(text\/plain, 14 B\) id=2008_\d+-1\]/);
275
+ const attachments = await getTaskAttachments(tasks[0]!.id);
276
+ expect(attachments).toHaveLength(1);
277
+ expect(attachments[0]).toMatchObject({ name: "notes.txt", sizeBytes: bytes.byteLength });
278
+ });
279
+
280
+ test("overlapping timer flushes of one thread create their tasks in order", async () => {
281
+ // Hold the first message's file download so its flush is still running
282
+ // when the second message's timer flush starts.
283
+ const bytes = new TextEncoder().encode("held file\n");
284
+ harness.mock.addFile("documents/held.txt", bytes);
285
+ let releaseDownload!: () => void;
286
+ const downloadHeld = new Promise<void>((resolve) => {
287
+ releaseDownload = resolve;
288
+ });
289
+ let downloadStarted!: () => void;
290
+ const started = new Promise<void>((resolve) => {
291
+ downloadStarted = resolve;
292
+ });
293
+ const realFetch = globalThis.fetch;
294
+ globalThis.fetch = (async (input: Parameters<typeof fetch>[0], init?: RequestInit) => {
295
+ const url = input instanceof Request ? input.url : String(input);
296
+ if (url.includes("/file/bot") && url.endsWith("documents/held.txt")) {
297
+ downloadStarted();
298
+ await downloadHeld;
299
+ }
300
+ return realFetch(input, init);
301
+ }) as typeof fetch;
302
+ process.env.CHAT_SDK_BUFFER_MS = "50";
303
+ const threadId = "telegram:2010";
304
+ try {
305
+ await postTelegramUpdate(
306
+ telegramUpdate(dm(2010), "", {
307
+ caption: "first: read the held file",
308
+ document: {
309
+ file_id: "documents/held.txt",
310
+ file_unique_id: "held-unique",
311
+ file_name: "held.txt",
312
+ mime_type: "text/plain",
313
+ file_size: bytes.byteLength,
314
+ },
315
+ }),
316
+ );
317
+ await started;
318
+ // A longer window for the second message, so the wait below sees it buffered.
319
+ process.env.CHAT_SDK_BUFFER_MS = "300";
320
+
321
+ // Not `postTelegramUpdate`: its background drain would wait on the held download.
322
+ const response = await postChatWebhook(
323
+ "telegram",
324
+ JSON.stringify(telegramUpdate(dm(2010), "second: then summarize it")),
325
+ {
326
+ "content-type": "application/json",
327
+ "x-telegram-bot-api-secret-token": TELEGRAM_SECRET,
328
+ },
329
+ );
330
+ expect(response.status).toBe(200);
331
+ const waitFor = async (condition: () => boolean) => {
332
+ const deadline = Date.now() + 3_000;
333
+ while (!condition() && Date.now() < deadline) await Bun.sleep(5);
334
+ expect(condition()).toBe(true);
335
+ };
336
+ await waitFor(() => isChatThreadBuffered(threadId));
337
+ await waitFor(() => !isChatThreadBuffered(threadId));
338
+ // The second flush has fired. Without the per-thread chain it would
339
+ // create its task now, before the first one.
340
+ await Bun.sleep(200);
341
+ expect(await chatTasks(2010)).toHaveLength(0);
342
+
343
+ releaseDownload();
344
+ expect(await drainBackground(5_000)).toBe(true);
345
+ } finally {
346
+ releaseDownload();
347
+ globalThis.fetch = realFetch;
348
+ }
349
+
350
+ const tasks = await chatTasks(2010);
351
+ expect(tasks).toHaveLength(2);
352
+ expect(tasks[0]!.task).toContain("first: read the held file");
353
+ expect(tasks[1]!.task).toContain("second: then summarize it");
354
+ const second = await getTaskById(tasks[1]!.id);
355
+ expect(second?.dependsOn).toEqual([tasks[0]!.id]);
356
+ });
357
+
358
+ test("a message in an unmapped group that does not mention the bot is ignored", async () => {
359
+ await postTelegramUpdate(telegramUpdate(group(-2009), "just chatting"));
360
+ await flushChatBuffers();
361
+ expect(await chatTasks(-2009)).toHaveLength(0);
362
+ });
363
+ });