@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,351 @@
1
+ import { createHash } from "node:crypto";
2
+ import type { Chat } from "chat";
3
+ import { isEnvFlagEnabled } from "@/utils/env-flag";
4
+ import { registerVolatileSecret, scrubSecrets } from "@/utils/secret-scrubber";
5
+ import { loadAdapter, watchSocketConnection } from "./adapters";
6
+ import { drainBackground } from "./background";
7
+ import { registerTelegramCommands } from "./commands";
8
+ import {
9
+ assertNoSlackClash,
10
+ CHAT_SDK_ADAPTER_NAMES,
11
+ type ChatSdkAdapterName,
12
+ type ChatSdkConfig,
13
+ getChatSdkConfig,
14
+ type ResolvedAdapterConfig,
15
+ } from "./config";
16
+ import {
17
+ type DiscordGatewayHealth,
18
+ getDiscordGatewayHealth,
19
+ isDiscordGatewayListener,
20
+ resetDiscordGatewayHealth,
21
+ startDiscordGateway,
22
+ stopDiscordGateway,
23
+ } from "./discord-gateway";
24
+ import { registerChatHandlers } from "./handlers";
25
+ import { createScrubbingLogger, errorMessage } from "./logger";
26
+ import { startChatRelayWatcher, stopChatRelayWatcher } from "./relay";
27
+ import { SqliteChatState } from "./state";
28
+ import { getChatSdkTestHooks } from "./test-hooks";
29
+ import { flushAllChatBuffers } from "./thread-buffer";
30
+ import { stopAllTypingKeepalives } from "./typing";
31
+
32
+ export interface ChatAdapterHealth {
33
+ status: "ok" | "error";
34
+ error?: string;
35
+ /** Last warn/error line the adapter logged, scrubbed. */
36
+ lastWarning?: string;
37
+ since: string;
38
+ /** Discord only: the supervised Gateway listener loop. */
39
+ gateway?: DiscordGatewayHealth;
40
+ }
41
+
42
+ /**
43
+ * One `Chat` per adapter. `Chat.initialize()` runs every adapter under one
44
+ * `Promise.all` and caches a failure forever, so a shared instance would let one
45
+ * broken adapter take every other adapter down. Health lives here, outside
46
+ * `Chat`, for the same reason.
47
+ */
48
+ const instances = new Map<ChatSdkAdapterName, Chat>();
49
+ const health = new Map<ChatSdkAdapterName, ChatAdapterHealth>();
50
+ let stopping = false;
51
+ /** Fingerprint of the config the last `startChatSdk` applied. */
52
+ let appliedFingerprint: string | undefined;
53
+
54
+ /** True while shutdown drains; the webhook path refuses new work. */
55
+ export function isChatSdkStopping(): boolean {
56
+ return stopping;
57
+ }
58
+
59
+ const DEFAULT_CHAT_USER_NAME = "swarm";
60
+
61
+ /** Total wait for every step of `stopChatSdk`, inside the 15 s SIGTERM budget. */
62
+ const SHUTDOWN_BUDGET_MS = 10_000;
63
+ /** A config reload waits this long for the adapters to come back before it answers. */
64
+ const RELOAD_START_BUDGET_MS = 15_000;
65
+
66
+ /** Fields whose string value is a credential, registered with the scrubber. */
67
+ const SECRET_FIELDS = ["botToken", "secretToken", "signingSecret", "appToken", "appPassword"];
68
+
69
+ function setHealth(
70
+ name: ChatSdkAdapterName,
71
+ status: "ok" | "error",
72
+ error: string | undefined,
73
+ lastWarning: string | undefined,
74
+ ): void {
75
+ health.set(name, { status, error, lastWarning, since: new Date().toISOString() });
76
+ }
77
+
78
+ /**
79
+ * Raw values in `CHAT_SDK_ADAPTERS` (env-only deployments) are not named by a
80
+ * sensitive env key, so the scrubber would not know them. `$config`-resolved
81
+ * secret rows are already registered by `loadGlobalConfigsIntoEnv`.
82
+ */
83
+ async function registerAdapterSecrets(
84
+ name: ChatSdkAdapterName,
85
+ config: ResolvedAdapterConfig,
86
+ ): Promise<void> {
87
+ for (const field of SECRET_FIELDS) {
88
+ const raw = config[field];
89
+ const value = typeof raw === "function" ? await (raw as () => Promise<string>)() : raw;
90
+ if (typeof value === "string") {
91
+ registerVolatileSecret(value, `chat-sdk:${name}:${field}`);
92
+ }
93
+ }
94
+ }
95
+
96
+ async function startAdapter(
97
+ name: ChatSdkAdapterName,
98
+ config: ResolvedAdapterConfig,
99
+ state: SqliteChatState,
100
+ ChatClass: typeof Chat,
101
+ ): Promise<void> {
102
+ let lastWarning: string | undefined;
103
+ // No level prefix on the stored line: the message already says what failed,
104
+ // and the status payload carries the tone separately.
105
+ const logger = createScrubbingLogger(name, (_level, message) => {
106
+ lastWarning = message;
107
+ const current = health.get(name);
108
+ if (current) health.set(name, { ...current, lastWarning });
109
+ });
110
+ try {
111
+ await registerAdapterSecrets(name, config);
112
+ const adapter = await loadAdapter(name, config, logger);
113
+ const chat = new ChatClass({
114
+ adapters: { [name]: adapter },
115
+ state,
116
+ // Chat requires a userName. The adapter only gets one when the operator
117
+ // set it, so Telegram can still take the real bot name from getMe.
118
+ userName: typeof config.userName === "string" ? config.userName : DEFAULT_CHAT_USER_NAME,
119
+ logger,
120
+ // The swarm buffers bursts itself (Phase 2). The SDK "queue" strategy
121
+ // would keep only the latest queued message and skip the rest.
122
+ concurrency: "concurrent",
123
+ });
124
+ registerChatHandlers(chat);
125
+ instances.set(name, chat);
126
+ await chat.initialize();
127
+ // Shutdown began while this adapter initialized: do not mark it ready or
128
+ // start the Gateway after `stopChatSdk` already stopped it.
129
+ if (stopping) {
130
+ console.log(`[chat-sdk] ${name} initialized during shutdown, not started`);
131
+ return;
132
+ }
133
+ watchSocketConnection(adapter, logger);
134
+ if (name === "telegram") {
135
+ // The `/` menu in Telegram clients. The commands work without it.
136
+ try {
137
+ await registerTelegramCommands(config);
138
+ } catch (error) {
139
+ logger.warn(`Could not register the Telegram commands: ${errorMessage(error)}`);
140
+ }
141
+ }
142
+ setHealth(name, "ok", undefined, lastWarning);
143
+ console.log(`[chat-sdk] ${name} ready`);
144
+ // Discord messages arrive only over the Gateway, never the webhook.
145
+ if (
146
+ name === "discord" &&
147
+ isDiscordGatewayListener(adapter) &&
148
+ !getChatSdkTestHooks().disableDiscordGateway
149
+ ) {
150
+ startDiscordGateway(adapter);
151
+ }
152
+ } catch (error) {
153
+ setHealth(name, "error", errorMessage(error), lastWarning);
154
+ console.error(`[chat-sdk] ${name} failed to start: ${errorMessage(error)}`);
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Boot the Chat SDK channel. Never throws for config or adapter problems: they
160
+ * are logged and recorded in health, and the API keeps booting.
161
+ */
162
+ export async function startChatSdk(): Promise<void> {
163
+ stopping = false;
164
+ const config = await getChatSdkConfig();
165
+ appliedFingerprint = await configFingerprint(config);
166
+ if (!config.enabled) {
167
+ console.log("[chat-sdk] Disabled (CHAT_SDK_ENABLED is not true)");
168
+ return;
169
+ }
170
+ if (config.error) {
171
+ console.error(`[chat-sdk] Not started: ${scrubSecrets(config.error)}`);
172
+ return;
173
+ }
174
+ try {
175
+ assertNoSlackClash(config);
176
+ } catch (error) {
177
+ console.error(`[chat-sdk] Not started: ${errorMessage(error)}`);
178
+ return;
179
+ }
180
+ for (const [name, reason] of Object.entries(config.incomplete ?? {})) {
181
+ console.warn(`[chat-sdk] ${name} not started, incomplete: ${scrubSecrets(reason)}`);
182
+ }
183
+ const { Chat: ChatClass } = await import("chat");
184
+ const state = new SqliteChatState();
185
+ // In parallel: one slow adapter (Slack's WebClient retries auth.test for
186
+ // up to ~30 min on errors) must not delay the others.
187
+ // Before the adapters: rows for an adapter still initializing are skipped
188
+ // without counting an attempt, and pending rows from before a restart drain
189
+ // as soon as their adapter is up.
190
+ startChatRelayWatcher();
191
+ await Promise.all(
192
+ CHAT_SDK_ADAPTER_NAMES.map(async (name) => {
193
+ const adapterConfig = config.adapters[name];
194
+ if (adapterConfig) await startAdapter(name, adapterConfig, state, ChatClass);
195
+ }),
196
+ );
197
+ }
198
+
199
+ /** Only adapters whose `initialize()` succeeded serve webhooks. */
200
+ export function getChatInstance(name: string): Chat | undefined {
201
+ const adapterName = name as ChatSdkAdapterName;
202
+ return health.get(adapterName)?.status === "ok" ? instances.get(adapterName) : undefined;
203
+ }
204
+
205
+ /** True when the adapter is configured (started, even if it failed). */
206
+ export function isChatAdapterConfigured(name: string): boolean {
207
+ return health.has(name as ChatSdkAdapterName);
208
+ }
209
+
210
+ export function getChatSdkHealth(): Partial<Record<ChatSdkAdapterName, ChatAdapterHealth>> {
211
+ const out: Partial<Record<ChatSdkAdapterName, ChatAdapterHealth>> = Object.fromEntries(health);
212
+ const gateway = getDiscordGatewayHealth();
213
+ if (out.discord && gateway) out.discord = { ...out.discord, gateway };
214
+ return out;
215
+ }
216
+
217
+ /**
218
+ * Wait for `work`, at most `ms`. Never rejects: a rejection is logged, and a
219
+ * timeout logs the abandoned step. Returns true when `work` settled in time.
220
+ */
221
+ async function withTimeout(work: Promise<unknown>, ms: number, step: string): Promise<boolean> {
222
+ let timer: ReturnType<typeof setTimeout> | undefined;
223
+ const timeout = new Promise<false>((resolve) => {
224
+ timer = setTimeout(() => resolve(false), ms);
225
+ });
226
+ const settled = work.then(
227
+ () => true as const,
228
+ (error: unknown) => {
229
+ console.error(`[chat-sdk] ${step} failed: ${errorMessage(error)}`);
230
+ return true as const;
231
+ },
232
+ );
233
+ try {
234
+ const done = await Promise.race([settled, timeout]);
235
+ if (!done) console.warn(`[chat-sdk] Shutdown budget spent, abandoned: ${step}`);
236
+ return done;
237
+ } finally {
238
+ clearTimeout(timer);
239
+ }
240
+ }
241
+
242
+ /**
243
+ * Stop the channel inside one deadline (`budgetMs`, injectable for tests). Every
244
+ * wait below uses the time that is left, so a stuck platform call cannot hold
245
+ * the API shutdown.
246
+ */
247
+ export async function stopChatSdk(budgetMs: number = SHUTDOWN_BUDGET_MS): Promise<void> {
248
+ // Ingress first: webhooks answer 503 and refuse new work from here.
249
+ stopping = true;
250
+ stopAllTypingKeepalives();
251
+ const deadline = Date.now() + budgetMs;
252
+ const remaining = (): number => Math.max(0, deadline - Date.now());
253
+ // No new relay tick or claim from here. The wait for an in-flight send comes
254
+ // after the inbound flush, so a stuck send cannot use the flush's budget.
255
+ const relayStopped = stopChatRelayWatcher();
256
+ // Discord has no `disconnect()`, so `chat.shutdown()` would leave the
257
+ // Gateway open. Abort it before the drain: the open window is tracked
258
+ // background work, so the drain bounds the wait for it to close.
259
+ stopDiscordGateway();
260
+ // Let in-flight handlers finish buffering, then turn waiting threads into
261
+ // tasks while adapters and the DB are still up. Buffered messages exist only
262
+ // in memory: threads not flushed in time are lost (logged).
263
+ let drained = await drainBackground(remaining());
264
+ drained =
265
+ (await withTimeout(
266
+ flushAllChatBuffers(),
267
+ remaining(),
268
+ "inbound buffer flush (threads not yet turned into tasks are lost)",
269
+ )) && drained;
270
+ drained = (await drainBackground(remaining())) && drained;
271
+ // The relay posts through the adapters, so it must end before they shut
272
+ // down. An abandoned send keeps its claimed row: it retries after restart.
273
+ await withTimeout(
274
+ relayStopped,
275
+ remaining(),
276
+ "in-flight relay send (its obligation stays pending and retries after restart)",
277
+ );
278
+ await withTimeout(
279
+ Promise.all(
280
+ [...instances].map(async ([name, chat]) => {
281
+ try {
282
+ await chat.shutdown();
283
+ } catch (error) {
284
+ console.error(`[chat-sdk] ${name} shutdown failed: ${errorMessage(error)}`);
285
+ }
286
+ }),
287
+ ),
288
+ remaining(),
289
+ "adapter shutdown",
290
+ );
291
+ instances.clear();
292
+ health.clear();
293
+ resetDiscordGatewayHealth();
294
+ if (!drained) {
295
+ console.warn(
296
+ `[chat-sdk] Background handlers still running after the ${budgetMs / 1000}s shutdown budget`,
297
+ );
298
+ }
299
+ }
300
+
301
+ /**
302
+ * Hash of the resolved config plus `SLACK_DISABLE` (the Slack clash input).
303
+ * Resolver fields are functions, so their values are read first: a rotated
304
+ * `$config` secret changes the hash. The hashed JSON holds secrets: never log it.
305
+ */
306
+ async function configFingerprint(config: ChatSdkConfig): Promise<string> {
307
+ const adapters: Record<string, Record<string, unknown>> = {};
308
+ for (const [name, adapterConfig] of Object.entries(config.adapters)) {
309
+ const fields: Record<string, unknown> = {};
310
+ for (const [key, value] of Object.entries(adapterConfig ?? {})) {
311
+ fields[key] = typeof value === "function" ? await (value as () => Promise<unknown>)() : value;
312
+ }
313
+ adapters[name] = fields;
314
+ }
315
+ const slackDisabled = isEnvFlagEnabled("SLACK_DISABLE", false);
316
+ // Incomplete adapters are not part of the running state, so editing one
317
+ // does not restart the adapters that run.
318
+ const { incomplete: _incomplete, disabled: _disabled, ...rest } = config;
319
+ return createHash("sha256")
320
+ .update(JSON.stringify({ ...rest, adapters, slackDisabled }))
321
+ .digest("hex");
322
+ }
323
+
324
+ /**
325
+ * Config reload hook: restart the adapters when the config differs from the
326
+ * one the last `startChatSdk` applied, so a dashboard save applies without an
327
+ * API restart. Compared against the applied config, not the pre-reload one:
328
+ * `$config` secrets resolve from the DB, which already holds the new row. An
329
+ * equal config leaves Telegram polling and the Discord Gateway alone. Never
330
+ * throws. Returns true when the restart left at least one adapter configured.
331
+ */
332
+ export async function restartChatSdkIfConfigChanged(): Promise<boolean> {
333
+ try {
334
+ if ((await configFingerprint(await getChatSdkConfig())) === appliedFingerprint) return false;
335
+ console.log("[chat-sdk] Configuration changed, restarting adapters");
336
+ await stopChatSdk();
337
+ // Bounded: a Slack adapter can retry auth.test for a long time, and the
338
+ // reload must not hold every later config save behind it. The start
339
+ // continues in the background after the budget.
340
+ const started = await withTimeout(startChatSdk(), RELOAD_START_BUDGET_MS, "start after reload");
341
+ console.log(
342
+ started
343
+ ? `[chat-sdk] Restart done, adapters configured: ${health.size}`
344
+ : "[chat-sdk] Restart still in progress after the reload budget; check the card status",
345
+ );
346
+ return health.size > 0;
347
+ } catch (error) {
348
+ console.error(`[chat-sdk] Restart after configuration change failed: ${errorMessage(error)}`);
349
+ return false;
350
+ }
351
+ }
@@ -0,0 +1,245 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * `CHAT_SDK_ADAPTERS` schema. Kept free of DB imports because
5
+ * `src/be/swarm-config-guard.ts` (imported by `src/be/db.ts`) validates writes
6
+ * with it; `config.ts` adds the DB-backed resolution on top.
7
+ */
8
+
9
+ export const CHAT_SDK_ADAPTER_NAMES = ["telegram", "slack", "discord", "teams"] as const;
10
+ export type ChatSdkAdapterName = (typeof CHAT_SDK_ADAPTER_NAMES)[number];
11
+
12
+ export const ConfigRefSchema = z.strictObject({ $config: z.string().min(1) });
13
+
14
+ /** A literal string or a `{ "$config": "KEY" }` reference to a swarm_config key. */
15
+ const Str = z.union([z.string().trim().min(1), ConfigRefSchema]);
16
+
17
+ /**
18
+ * Keys an operator must never set. A base URL plus a `$config` ref would send
19
+ * the referenced secret to an arbitrary host. A verifier or an "unverified"
20
+ * switch bypasses the platform signature check on the public webhook.
21
+ * Mock base URLs come only from the env-only `CHAT_SDK_API_URL_OVERRIDES`.
22
+ */
23
+ const FORBIDDEN_ADAPTER_KEYS = [
24
+ "apiUrl",
25
+ "apiBaseUrl",
26
+ "webhookVerifier",
27
+ "allowUnverifiedWebhooks",
28
+ ] as const;
29
+
30
+ type AdapterShape = z.ZodRawShape;
31
+
32
+ function forbiddenKeyIssues(value: Record<string, unknown>, ctx: z.RefinementCtx): void {
33
+ for (const key of FORBIDDEN_ADAPTER_KEYS) {
34
+ if (key in value) {
35
+ ctx.addIssue({
36
+ code: "custom",
37
+ path: [key],
38
+ message: `${key} is not allowed in CHAT_SDK_ADAPTERS`,
39
+ });
40
+ }
41
+ }
42
+ }
43
+
44
+ /** Fields every adapter has next to its own shape. `enabled: false` keeps the entry but does not start it. */
45
+ const COMMON_SHAPE = {
46
+ userName: z.string().min(1).optional(),
47
+ enabled: z.boolean().optional(),
48
+ };
49
+
50
+ function adapterSchema<S extends AdapterShape>(shape: S) {
51
+ return z.looseObject({ ...shape, ...COMMON_SHAPE }).superRefine(forbiddenKeyIssues);
52
+ }
53
+
54
+ /**
55
+ * The same adapter, with every field optional: the shape a dashboard save may
56
+ * store before the operator has every value (`.partial()` refuses schemas with
57
+ * refinements, so the optional shape is built by hand). Forbidden keys and
58
+ * value formats still apply.
59
+ */
60
+ function draftShape<S extends AdapterShape>(shape: S): AdapterShape {
61
+ return Object.fromEntries(
62
+ Object.entries(shape).map(([key, field]) => [key, (field as z.ZodType).optional()]),
63
+ );
64
+ }
65
+
66
+ const TELEGRAM_SHAPE = {
67
+ botToken: Str,
68
+ secretToken: Str.optional(),
69
+ // Never the SDK's "auto": auto starts long polling against the real Bot API
70
+ // when no webhook is registered.
71
+ mode: z.enum(["webhook", "polling"]).default("webhook"),
72
+ allowedUserIds: z.array(z.union([z.string(), z.number()])).optional(),
73
+ };
74
+
75
+ const SLACK_SHAPE = {
76
+ botToken: Str,
77
+ signingSecret: Str.optional(),
78
+ appToken: Str.optional(),
79
+ mode: z.enum(["webhook", "socket"]).default("webhook"),
80
+ };
81
+
82
+ export const TelegramAdapterSchema = adapterSchema(TELEGRAM_SHAPE);
83
+
84
+ export const SlackAdapterSchema = adapterSchema(SLACK_SHAPE).superRefine((value, ctx) => {
85
+ // Required here, not left to the adapter: the adapter falls back to
86
+ // SLACK_SIGNING_SECRET / SLACK_APP_TOKEN, which belong to the built-in Slack app.
87
+ if (value.mode === "webhook" && value.signingSecret === undefined) {
88
+ ctx.addIssue({
89
+ code: "custom",
90
+ path: ["signingSecret"],
91
+ message: "signingSecret is required in webhook mode",
92
+ });
93
+ }
94
+ if (value.mode === "socket" && value.appToken === undefined) {
95
+ ctx.addIssue({
96
+ code: "custom",
97
+ path: ["appToken"],
98
+ message: "appToken is required in socket mode",
99
+ });
100
+ }
101
+ });
102
+
103
+ const DISCORD_PUBLIC_KEY_PATTERN = /^[0-9a-fA-F]{64}$/;
104
+
105
+ const DISCORD_SHAPE = {
106
+ botToken: Str,
107
+ publicKey: Str,
108
+ applicationId: Str,
109
+ respondToChannelIds: z.array(z.string()).optional(),
110
+ respondToGlobalMentions: z.boolean().optional(),
111
+ mentionRoleIds: z.array(z.string()).optional(),
112
+ };
113
+
114
+ // A literal key must be the hex Ed25519 public key from the Developer Portal.
115
+ // A `$config` value resolves at startup, where the adapter logs a bad format.
116
+ function discordPublicKeyIssue(value: Record<string, unknown>, ctx: z.RefinementCtx): void {
117
+ if (typeof value.publicKey === "string" && !DISCORD_PUBLIC_KEY_PATTERN.test(value.publicKey)) {
118
+ ctx.addIssue({
119
+ code: "custom",
120
+ path: ["publicKey"],
121
+ message: "publicKey must be 64 hex characters (the application's Ed25519 public key)",
122
+ });
123
+ }
124
+ }
125
+
126
+ export const DiscordAdapterSchema = adapterSchema(DISCORD_SHAPE).superRefine(discordPublicKeyIssue);
127
+
128
+ const TEAMS_SHAPE = {
129
+ appId: Str,
130
+ appPassword: Str,
131
+ appTenantId: Str.optional(),
132
+ appType: z.enum(["MultiTenant", "SingleTenant"]).default("MultiTenant"),
133
+ };
134
+
135
+ export const TeamsAdapterSchema = adapterSchema(TEAMS_SHAPE).superRefine((value, ctx) => {
136
+ // The adapter falls back to TEAMS_APP_TENANT_ID when a single-tenant app has no tenant.
137
+ if (value.appType === "SingleTenant" && value.appTenantId === undefined) {
138
+ ctx.addIssue({
139
+ code: "custom",
140
+ path: ["appTenantId"],
141
+ message: "appTenantId is required for a SingleTenant app",
142
+ });
143
+ }
144
+ });
145
+
146
+ // Strict: a typo'd adapter name must fail loudly, not silently disable it.
147
+ export const ChatSdkAdaptersSchema = z
148
+ .strictObject({
149
+ telegram: TelegramAdapterSchema,
150
+ slack: SlackAdapterSchema,
151
+ discord: DiscordAdapterSchema,
152
+ teams: TeamsAdapterSchema,
153
+ })
154
+ .partial();
155
+
156
+ export type ChatSdkAdapters = z.infer<typeof ChatSdkAdaptersSchema>;
157
+
158
+ /** The full schema of one adapter entry. An entry that fails it is "incomplete". */
159
+ export const CHAT_SDK_ADAPTER_SCHEMAS: Record<ChatSdkAdapterName, z.ZodType> = {
160
+ telegram: TelegramAdapterSchema,
161
+ slack: SlackAdapterSchema,
162
+ discord: DiscordAdapterSchema,
163
+ teams: TeamsAdapterSchema,
164
+ };
165
+
166
+ /**
167
+ * What a `CHAT_SDK_ADAPTERS` write must satisfy: known adapter names, object
168
+ * entries, no forbidden keys, well-formed values. Required fields may be
169
+ * missing (a partial dashboard save); such an adapter does not start and the
170
+ * status reports it as incomplete, while the complete adapters run.
171
+ */
172
+ export const ChatSdkAdaptersDraftSchema = z
173
+ .strictObject({
174
+ telegram: adapterSchema(draftShape(TELEGRAM_SHAPE)),
175
+ slack: adapterSchema(draftShape(SLACK_SHAPE)),
176
+ discord: adapterSchema(draftShape(DISCORD_SHAPE)).superRefine(discordPublicKeyIssue),
177
+ teams: adapterSchema(draftShape(TEAMS_SHAPE)),
178
+ })
179
+ .partial();
180
+
181
+ function valueAt(input: unknown, path: PropertyKey[]): unknown {
182
+ let current: unknown = input;
183
+ for (const key of path) {
184
+ if (current === null || typeof current !== "object") return undefined;
185
+ current = (current as Record<PropertyKey, unknown>)[key];
186
+ }
187
+ return current;
188
+ }
189
+
190
+ /**
191
+ * `<prefix>.<path>: <message>` for the first issue of a failed parse. With
192
+ * `input`, a missing value reads "required" instead of zod's union text
193
+ * ("Invalid input"), which is what a partial dashboard save produces.
194
+ */
195
+ export function firstIssueText(
196
+ error: z.ZodError,
197
+ prefix = "CHAT_SDK_ADAPTERS",
198
+ input?: unknown,
199
+ ): string {
200
+ const issue = error.issues[0];
201
+ if (!issue) return `${prefix} is invalid`;
202
+ const path = issue.path.length > 0 ? issue.path.join(".") : "(root)";
203
+ const missing =
204
+ input !== undefined && issue.path.length > 0 && valueAt(input, issue.path) === undefined;
205
+ return `${prefix}.${path}: ${missing ? "required" : issue.message}`;
206
+ }
207
+
208
+ /**
209
+ * Field names per adapter, derived from the schemas. The dashboard keeps its
210
+ * own field list (`apps/ui/src/lib/chat-sdk-fields.ts`, it cannot import zod
211
+ * schemas from the API); `src/tests/chat-sdk-fields-sync.test.ts` keeps the two equal.
212
+ */
213
+ export const CHAT_SDK_ADAPTER_FIELDS: Record<ChatSdkAdapterName, readonly string[]> = {
214
+ telegram: formFields(TelegramAdapterSchema.shape),
215
+ slack: formFields(SlackAdapterSchema.shape),
216
+ discord: formFields(DiscordAdapterSchema.shape),
217
+ teams: formFields(TeamsAdapterSchema.shape),
218
+ };
219
+
220
+ /** `enabled` is the panel switch on the card, not a form field. */
221
+ function formFields(shape: AdapterShape): string[] {
222
+ return Object.keys(shape).filter((key) => key !== "enabled");
223
+ }
224
+
225
+ /** True unless the entry carries `enabled: false`. */
226
+ export function isAdapterEntryEnabled(entry: Record<string, unknown> | undefined): boolean {
227
+ return entry !== undefined && entry.enabled !== false;
228
+ }
229
+
230
+ /**
231
+ * `VALIDATED_KEYS` validator for `CHAT_SDK_ADAPTERS`: the draft shape only
232
+ * (missing required fields are allowed, see `ChatSdkAdaptersDraftSchema`), no
233
+ * reference resolution. Returns the first issue as a string, or null.
234
+ */
235
+ export function validateChatSdkAdaptersValue(value: unknown): string | null {
236
+ if (typeof value !== "string") return "CHAT_SDK_ADAPTERS must be a JSON string";
237
+ let parsed: unknown;
238
+ try {
239
+ parsed = JSON.parse(value);
240
+ } catch (error) {
241
+ return `CHAT_SDK_ADAPTERS is not valid JSON: ${error instanceof Error ? error.message : String(error)}`;
242
+ }
243
+ const result = ChatSdkAdaptersDraftSchema.safeParse(parsed);
244
+ return result.success ? null : firstIssueText(result.error);
245
+ }