@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,63 @@
1
+ import { scrubSecrets } from "@/utils/secret-scrubber";
2
+ import { resolveAdapterApiUrl } from "./adapters";
3
+ import { errorMessage } from "./logger";
4
+
5
+ const DISCORD_TIMEOUT_MS = 5_000;
6
+ /** The status card polls every 5 s; one Discord call per minute is enough. */
7
+ const GUILDS_CACHE_MS = 60_000;
8
+ /** Bound on the server names the status carries. */
9
+ const MAX_GUILD_NAMES = 10;
10
+
11
+ export interface DiscordGuilds {
12
+ count: number;
13
+ /** Up to `MAX_GUILD_NAMES` server names, for the card. */
14
+ names: string[];
15
+ }
16
+
17
+ let cache: { token: string; at: number; guilds: DiscordGuilds | undefined } | undefined;
18
+
19
+ /**
20
+ * The servers the bot was invited to (`GET /users/@me/guilds`). A connected
21
+ * Gateway with zero servers means the invite step was skipped, which the card
22
+ * can say outright. Undefined when Discord does not answer; the error is
23
+ * logged without the token.
24
+ */
25
+ export async function fetchDiscordGuilds(
26
+ token: string,
27
+ now = Date.now(),
28
+ ): Promise<DiscordGuilds | undefined> {
29
+ if (cache && cache.token === token && now - cache.at < GUILDS_CACHE_MS) return cache.guilds;
30
+ let guilds: DiscordGuilds | undefined;
31
+ try {
32
+ const response = await fetch(`${resolveAdapterApiUrl("discord")}/users/@me/guilds`, {
33
+ headers: { authorization: `Bot ${token}` },
34
+ signal: AbortSignal.timeout(DISCORD_TIMEOUT_MS),
35
+ });
36
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
37
+ const payload = (await response.json()) as unknown;
38
+ if (!Array.isArray(payload)) throw new Error("unexpected response shape");
39
+ const names = payload
40
+ .map((guild: unknown) =>
41
+ guild !== null &&
42
+ typeof guild === "object" &&
43
+ typeof (guild as { name?: unknown }).name === "string"
44
+ ? (guild as { name: string }).name
45
+ : undefined,
46
+ )
47
+ .filter((name): name is string => name !== undefined);
48
+ guilds = { count: payload.length, names: names.slice(0, MAX_GUILD_NAMES) };
49
+ } catch (error) {
50
+ console.warn(
51
+ scrubSecrets(
52
+ `[chat-sdk] discord: could not list the bot's servers: ${errorMessage(error).replaceAll(token, "[REDACTED]")}`,
53
+ ),
54
+ );
55
+ }
56
+ cache = { token, at: now, guilds };
57
+ return guilds;
58
+ }
59
+
60
+ /** Test seam. */
61
+ export function resetDiscordGuildsCache(): void {
62
+ cache = undefined;
63
+ }
@@ -0,0 +1,185 @@
1
+ import type { DiscordAdapter } from "@chat-adapter/discord";
2
+ import type { Adapter } from "chat";
3
+ import { scrubSecrets } from "@/utils/secret-scrubber";
4
+ import { trackBackground } from "./background";
5
+ import { errorMessage } from "./logger";
6
+
7
+ /**
8
+ * Discord sends channel messages and mentions only over the Gateway WebSocket.
9
+ * The adapter's `startGatewayListener` listens for one window and then
10
+ * destroys its client (it is built for serverless re-invocation), so a
11
+ * long-lived API process re-arms it in a loop. The adapter has no
12
+ * `disconnect()`, so `chat.shutdown()` does not stop the Gateway; aborting
13
+ * this loop is the only stop path.
14
+ *
15
+ * The Gateway does not replay `MESSAGE_CREATE`: messages sent during a re-arm
16
+ * gap or a backoff are lost. A long window keeps gaps rare. 6 h uses 4
17
+ * identifies per day of Discord's 1000.
18
+ */
19
+ const DISCORD_GATEWAY_WINDOW_MS = 6 * 60 * 60 * 1000;
20
+
21
+ /** A window that ends sooner than this failed (bad token, network down). */
22
+ const MIN_HEALTHY_WINDOW_MS = 10_000;
23
+ const BACKOFF_MS = 30_000;
24
+ /** Bound on the response body text logged when the listener refuses to start. */
25
+ const MAX_LOGGED_BODY_CHARS = 200;
26
+
27
+ export interface DiscordGatewayHealth {
28
+ state: "running" | "backoff" | "stopped";
29
+ since: string;
30
+ /** Re-arms after the first listener window. */
31
+ restarts: number;
32
+ }
33
+
34
+ /** The part of the Discord adapter the loop uses. */
35
+ export type DiscordGatewayListener = Pick<DiscordAdapter, "startGatewayListener">;
36
+
37
+ export function isDiscordGatewayListener(
38
+ adapter: Adapter,
39
+ ): adapter is Adapter & DiscordGatewayListener {
40
+ return "startGatewayListener" in adapter && typeof adapter.startGatewayListener === "function";
41
+ }
42
+
43
+ /** Test seams. Production uses the defaults. */
44
+ export interface DiscordGatewayOptions {
45
+ windowMs?: number;
46
+ sleep?: (ms: number, signal: AbortSignal) => Promise<void>;
47
+ now?: () => number;
48
+ }
49
+
50
+ /** Resolves after `ms`, or at once when `signal` aborts. */
51
+ function abortableSleep(ms: number, signal: AbortSignal): Promise<void> {
52
+ return new Promise((resolve) => {
53
+ if (signal.aborted) {
54
+ resolve();
55
+ return;
56
+ }
57
+ const done = (): void => {
58
+ clearTimeout(timer);
59
+ signal.removeEventListener("abort", done);
60
+ resolve();
61
+ };
62
+ const timer = setTimeout(done, ms);
63
+ signal.addEventListener("abort", done, { once: true });
64
+ });
65
+ }
66
+
67
+ let current: { controller: AbortController; loop: Promise<void>; now: () => number } | undefined;
68
+ let health: DiscordGatewayHealth | undefined;
69
+
70
+ function setState(state: DiscordGatewayHealth["state"], restarts: number, nowMs: number): void {
71
+ if (health?.state === state) {
72
+ health = { ...health, restarts };
73
+ return;
74
+ }
75
+ health = { state, since: new Date(nowMs).toISOString(), restarts };
76
+ }
77
+
78
+ async function runGatewayLoop(
79
+ adapter: DiscordGatewayListener,
80
+ signal: AbortSignal,
81
+ options: DiscordGatewayOptions,
82
+ ): Promise<void> {
83
+ const windowMs = options.windowMs ?? DISCORD_GATEWAY_WINDOW_MS;
84
+ const sleep = options.sleep ?? abortableSleep;
85
+ const now = options.now ?? Date.now;
86
+ let restarts = 0;
87
+ let first = true;
88
+ while (!signal.aborted) {
89
+ if (!first) restarts += 1;
90
+ first = false;
91
+ setState("running", restarts, now());
92
+ const started = now();
93
+ let inner: Promise<unknown> | undefined;
94
+ // One controller per window. The adapter adds an `abort` listener to the
95
+ // signal it gets and never removes it, so passing the outer signal would
96
+ // leak one listener (and the destroyed client) per re-arm.
97
+ const windowController = new AbortController();
98
+ const abortWindow = (): void => windowController.abort();
99
+ signal.addEventListener("abort", abortWindow, { once: true });
100
+ try {
101
+ const response = await adapter.startGatewayListener(
102
+ {
103
+ waitUntil: (task) => {
104
+ inner = task;
105
+ trackBackground(task);
106
+ },
107
+ },
108
+ windowMs,
109
+ windowController.signal,
110
+ );
111
+ if (!response.ok) {
112
+ const body = await response.text().catch(() => "");
113
+ console.error(
114
+ scrubSecrets(
115
+ `[chat-sdk] discord gateway listener refused to start: HTTP ${response.status}: ${body.slice(0, MAX_LOGGED_BODY_CHARS)}`,
116
+ ),
117
+ );
118
+ }
119
+ // The inner promise resolves when the window ends or on abort. It never
120
+ // rejects: login errors are logged by the adapter and end the window.
121
+ await inner;
122
+ } catch (error) {
123
+ console.error(
124
+ scrubSecrets(`[chat-sdk] discord gateway listener failed: ${errorMessage(error)}`),
125
+ );
126
+ } finally {
127
+ signal.removeEventListener("abort", abortWindow);
128
+ }
129
+ const elapsed = now() - started;
130
+ if (signal.aborted) break;
131
+ if (elapsed < MIN_HEALTHY_WINDOW_MS) {
132
+ setState("backoff", restarts, now());
133
+ console.warn(
134
+ scrubSecrets(
135
+ `[chat-sdk] discord gateway backoff: window ended after ${elapsed}ms, retrying in ${BACKOFF_MS / 1000}s`,
136
+ ),
137
+ );
138
+ await sleep(BACKOFF_MS, signal);
139
+ }
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Start the supervised Gateway loop. Call only after `chat.initialize()`
145
+ * succeeded: the listener refuses to start before that.
146
+ */
147
+ export function startDiscordGateway(
148
+ adapter: DiscordGatewayListener,
149
+ options: DiscordGatewayOptions = {},
150
+ ): void {
151
+ if (current) return;
152
+ const now = options.now ?? Date.now;
153
+ health = { state: "running", since: new Date(now()).toISOString(), restarts: 0 };
154
+ const controller = new AbortController();
155
+ const loop = runGatewayLoop(adapter, controller.signal, options).catch((error: unknown) => {
156
+ console.error(`[chat-sdk] discord gateway loop crashed: ${errorMessage(error)}`);
157
+ });
158
+ current = { controller, loop, now };
159
+ }
160
+
161
+ /**
162
+ * Abort the loop and return at once. The open window is tracked by
163
+ * `trackBackground`, so the shutdown drain bounds the wait for it to close.
164
+ * The `stopped` state and log line follow when the loop exits.
165
+ */
166
+ export function stopDiscordGateway(): void {
167
+ if (!current) return;
168
+ const { controller, loop, now } = current;
169
+ current = undefined;
170
+ controller.abort();
171
+ void loop.then(() => {
172
+ // Skip when a new loop started, or when shutdown already reset health.
173
+ if (!current && health) setState("stopped", health.restarts, now());
174
+ console.log("[chat-sdk] discord gateway stopped");
175
+ });
176
+ }
177
+
178
+ export function getDiscordGatewayHealth(): DiscordGatewayHealth | undefined {
179
+ return health;
180
+ }
181
+
182
+ /** Forget the gateway record. Runtime shutdown clears adapter health the same way. */
183
+ export function resetDiscordGatewayHealth(): void {
184
+ health = undefined;
185
+ }
@@ -0,0 +1,28 @@
1
+ import type { Chat, Message, Thread } from "chat";
2
+ import { trackBackground } from "./background";
3
+ import { registerChatCommands } from "./commands";
4
+ import { type ChatInboundKind, handleChatInbound } from "./inbound";
5
+
6
+ async function onHumanMessage(
7
+ thread: Thread,
8
+ message: Message,
9
+ kind: ChatInboundKind,
10
+ ): Promise<void> {
11
+ // The SDK drops the bot's own messages; other bots never become tasks.
12
+ if (message.author.isBot === true) return;
13
+ // Socket-mode events (Slack) reach the SDK without a request, so it has no
14
+ // `waitUntil` to hand this work to. Track it here so the shutdown drain
15
+ // waits for it before the DB closes. The tracker logs a rejection.
16
+ const work = handleChatInbound(thread, message, kind);
17
+ trackBackground(work);
18
+ await work.catch(() => undefined);
19
+ }
20
+
21
+ export function registerChatHandlers(chat: Chat): void {
22
+ chat.onNewMention((thread, message) => onHumanMessage(thread, message, "mention"));
23
+ chat.onDirectMessage((thread, message) => onHumanMessage(thread, message, "dm"));
24
+ chat.onSubscribedMessage((thread, message) => onHumanMessage(thread, message, "subscribed"));
25
+ // A `/command` message never reaches the handlers above: the adapters route
26
+ // it here. Without a handler Telegram would drop it silently.
27
+ registerChatCommands(chat);
28
+ }
@@ -0,0 +1,426 @@
1
+ import type { Adapter, Message, Thread } from "chat";
2
+ import { z } from "zod";
3
+ import {
4
+ getAgentById,
5
+ getAllAgents,
6
+ getLatestActiveTaskByContextKey,
7
+ getLatestTaskByContextKey,
8
+ getLeadAgent,
9
+ isExtensionAgent,
10
+ } from "@/be/db";
11
+ import { dispatchPre } from "@/extensions/dispatcher";
12
+ import { resolveTemplate } from "@/prompts/resolver";
13
+ import { chatSdkContextKey } from "@/tasks/context-key";
14
+ import { TaskCreationBlockedError } from "@/tasks/errors";
15
+ import type { CreateTaskOptions } from "@/types";
16
+ import { scrubSecrets } from "@/utils/secret-scrubber";
17
+ import { getExecutorRegistry } from "@/workflows";
18
+ import { workflowEventBus } from "@/workflows/event-bus";
19
+ import { handleWebhookTrigger } from "@/workflows/triggers";
20
+ import {
21
+ buildChatAttachmentText,
22
+ createChatTaskWithFiles,
23
+ fetchChatFiles,
24
+ toChatFiles,
25
+ } from "./attachments";
26
+ import { getChatThreadResetAt } from "./commands";
27
+ import { errorMessage } from "./logger";
28
+ import { emitChatMessageSent } from "./outbound";
29
+ import {
30
+ type ChatAgentMatch,
31
+ type ChatRouteTarget,
32
+ routeChatMessage,
33
+ stripRoutingTokens,
34
+ } from "./router";
35
+ import { getChatInstance } from "./runtime";
36
+ import { readPersistedThreadHistory } from "./state";
37
+ import { formatChatSteeringAck, requestChatThreadSteering } from "./steering";
38
+ import {
39
+ bufferChatMessage,
40
+ type ChatBufferedMessage,
41
+ instantFlushChat,
42
+ isChatThreadBuffered,
43
+ } from "./thread-buffer";
44
+ import { startTypingKeepalive, stopTypingKeepalive } from "./typing";
45
+ // Side-effect import: registers the Chat SDK prompt templates.
46
+ import "./templates";
47
+
48
+ export type ChatInboundKind = "mention" | "dm" | "subscribed";
49
+
50
+ /** Runtime check for `pre.chat.route` modifications (mirrors `ChatRouteModify`). */
51
+ export const ChatRouteModifySchema = z
52
+ .object({
53
+ target: z.discriminatedUnion("kind", [
54
+ z.object({ kind: z.literal("agent"), agentId: z.string().min(1) }).strict(),
55
+ z.object({ kind: z.literal("lead") }).strict(),
56
+ z.object({ kind: z.literal("broadcast") }).strict(),
57
+ z.object({ kind: z.literal("workflow"), workflowId: z.string().min(1) }).strict(),
58
+ ]),
59
+ })
60
+ .strict();
61
+
62
+ const THREAD_CONTEXT_LIMIT = 20;
63
+ const AGENT_MESSAGE_TRUNCATE = 500;
64
+
65
+ function escapeRegex(value: string): string {
66
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
67
+ }
68
+
69
+ /** Remove the bot's own `@name` / `<@id>` mention; other mentions stay. */
70
+ function stripBotMention(text: string, adapter: Adapter): string {
71
+ let out = text;
72
+ if (adapter.userName) {
73
+ out = out.replace(new RegExp(`(?<!\\w)@${escapeRegex(adapter.userName)}(?![\\w-])`, "gi"), "");
74
+ }
75
+ if (adapter.botUserId) {
76
+ out = out.replace(new RegExp(`<@${escapeRegex(adapter.botUserId)}>`, "g"), "");
77
+ }
78
+ return out.trim();
79
+ }
80
+
81
+ /** The adapter name is the thread id's first segment; the rest of the id is opaque. */
82
+ function adapterOf(threadId: string): string {
83
+ const i = threadId.indexOf(":");
84
+ return i === -1 ? threadId : threadId.slice(0, i);
85
+ }
86
+
87
+ /**
88
+ * Entry point for every human message the SDK hands to the swarm. Steering is
89
+ * tried whatever the handler kind: the SDK routes a DM to `onDirectMessage`
90
+ * even when its thread is subscribed. Everything else goes through the buffer.
91
+ */
92
+ export async function handleChatInbound(
93
+ thread: Thread,
94
+ message: Message,
95
+ kind: ChatInboundKind,
96
+ ): Promise<void> {
97
+ const adapter = thread.adapter.name;
98
+ workflowEventBus.emit("chat.message.received", {
99
+ adapter,
100
+ channelId: thread.channelId,
101
+ threadId: thread.id,
102
+ userId: message.author.userId,
103
+ text: message.text,
104
+ messageId: message.id,
105
+ });
106
+
107
+ let text = stripBotMention(message.text ?? "", thread.adapter);
108
+ const immediate = /^!now\b/i.test(text);
109
+ if (immediate) text = text.replace(/^!now\s*/i, "").trim();
110
+ const attachments = message.attachments ?? [];
111
+
112
+ // A thread with messages already waiting keeps its order: append, do not steer past them.
113
+ if (!immediate && attachments.length === 0 && text && !isChatThreadBuffered(thread.id)) {
114
+ const steering = await requestChatThreadSteering({
115
+ contextKey: chatSdkContextKey(thread.id),
116
+ message: text,
117
+ });
118
+ if (steering) {
119
+ console.log(
120
+ `[chat-sdk] ${adapter} message steered task ${steering.task.id} (${steering.result.outcome})`,
121
+ );
122
+ try {
123
+ const ack = formatChatSteeringAck(steering.result);
124
+ const sent = await thread.post({ markdown: ack });
125
+ emitChatMessageSent({
126
+ adapter,
127
+ channelId: thread.channelId,
128
+ threadId: thread.id,
129
+ text: ack,
130
+ taskId: steering.task.id,
131
+ messageId: sent.id,
132
+ });
133
+ // The acknowledgement is the reply to this message.
134
+ stopTypingKeepalive(thread.id);
135
+ } catch (error) {
136
+ console.warn(
137
+ `[chat-sdk] Could not post the steering acknowledgement: ${errorMessage(error)}`,
138
+ );
139
+ }
140
+ return;
141
+ }
142
+ }
143
+
144
+ if (text || attachments.length > 0) {
145
+ bufferChatMessage(thread.id, {
146
+ messageId: message.id,
147
+ channelId: thread.channelId,
148
+ text,
149
+ userId: message.author.userId,
150
+ userName: message.author.userName || undefined,
151
+ botMentioned: kind === "mention" || message.isMention === true,
152
+ isDirectMessage: kind === "dm" || thread.isDM,
153
+ attachments,
154
+ });
155
+ // Subscribed-thread messages may be dropped by the router at flush time;
156
+ // they get typing once a task exists (see flushChatThread).
157
+ if (kind !== "subscribed") startTypingKeepalive(adapter, thread.id);
158
+ }
159
+ if (immediate) await instantFlushChat(thread.id);
160
+ }
161
+
162
+ /**
163
+ * Earlier messages of the thread for the task description, minus the flushed
164
+ * ones and anything before the last `/reset`. The adapter's view is merged with
165
+ * the history the SDK persisted: Telegram answers from process memory, which an
166
+ * adapter restart (API restart, config reload) empties.
167
+ */
168
+ async function getChatThreadContext(
169
+ thread: Thread,
170
+ exclude: ReadonlySet<string>,
171
+ since: Date | null,
172
+ ): Promise<string> {
173
+ try {
174
+ await thread.refresh();
175
+ const byId = new Map<string, Message>();
176
+ for (const m of await readPersistedThreadHistory(thread.id)) byId.set(m.id, m);
177
+ for (const m of thread.recentMessages) byId.set(m.id, m);
178
+ return [...byId.values()]
179
+ .sort((a, b) => a.metadata.dateSent.getTime() - b.metadata.dateSent.getTime())
180
+ .filter((m) => !exclude.has(m.id) && m.text.trim() !== "")
181
+ .filter((m) => since === null || m.metadata.dateSent.getTime() > since.getTime())
182
+ .slice(-THREAD_CONTEXT_LIMIT)
183
+ .map((m) => {
184
+ if (m.author.isMe || m.author.isBot === true) {
185
+ const truncated =
186
+ m.text.length > AGENT_MESSAGE_TRUNCATE
187
+ ? `${m.text.slice(0, AGENT_MESSAGE_TRUNCATE)}...`
188
+ : m.text;
189
+ return `[Agent]: ${truncated}`;
190
+ }
191
+ return `${m.author.userName || m.author.fullName || m.author.userId}: ${m.text}`;
192
+ })
193
+ .join("\n");
194
+ } catch (error) {
195
+ console.warn(`[chat-sdk] Could not read thread context: ${errorMessage(error)}`);
196
+ return "";
197
+ }
198
+ }
199
+
200
+ /** Apply a `pre.chat.route` modification, or null to fall back to the built-in router. */
201
+ async function targetFromExtension(
202
+ target: z.infer<typeof ChatRouteModifySchema>["target"],
203
+ ): Promise<ChatRouteTarget | null> {
204
+ switch (target.kind) {
205
+ case "workflow":
206
+ return { kind: "workflow", workflowId: target.workflowId };
207
+ case "broadcast": {
208
+ const matches = (await getAllAgents())
209
+ .filter((agent) => !agent.isLead)
210
+ .map(
211
+ (agent): ChatAgentMatch => ({ agent, reason: "human_pinned", matchedText: "extension" }),
212
+ );
213
+ if (matches.length === 0) {
214
+ console.warn("[chat-sdk] Extension broadcast target matched no agents; message dropped");
215
+ }
216
+ return { kind: "agents", matches };
217
+ }
218
+ case "lead": {
219
+ const lead = await getLeadAgent();
220
+ if (lead) {
221
+ return {
222
+ kind: "agents",
223
+ matches: [{ agent: lead, reason: "human_pinned", matchedText: "extension" }],
224
+ };
225
+ }
226
+ console.warn("[chat-sdk] Extension selected the lead, but no lead exists. Using the router.");
227
+ return null;
228
+ }
229
+ case "agent": {
230
+ const agent = await getAgentById(target.agentId);
231
+ if (agent && !isExtensionAgent(agent)) {
232
+ return {
233
+ kind: "agents",
234
+ matches: [{ agent, reason: "human_pinned", matchedText: "extension" }],
235
+ };
236
+ }
237
+ console.warn(
238
+ scrubSecrets(
239
+ `[chat-sdk] Extension selected ${agent ? "an extension-identity" : "an unknown"} agent ${target.agentId}. Using the router.`,
240
+ ),
241
+ );
242
+ return null;
243
+ }
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Buffer flush: one routed decision for every message the thread collected.
249
+ * Runs outside any transaction, so `pre.chat.route` is dispatched here, after
250
+ * the flush and before any task exists.
251
+ */
252
+ export async function flushChatThread(
253
+ threadId: string,
254
+ items: ChatBufferedMessage[],
255
+ immediate: boolean,
256
+ ): Promise<void> {
257
+ const first = items[0];
258
+ if (!first) return;
259
+ const adapter = adapterOf(threadId);
260
+ const contextKey = chatSdkContextKey(threadId);
261
+ const thread = getChatInstance(adapter)?.thread(threadId);
262
+ const rawText = items
263
+ .map((item) => item.text)
264
+ .filter(Boolean)
265
+ .join("\n---\n");
266
+ const botMentioned = items.some((item) => item.botMentioned);
267
+ const isDirectMessage = items.some((item) => item.isDirectMessage);
268
+
269
+ // After `/reset` the thread's earlier tasks are not context: the new task gets
270
+ // no parent and no thread history older than the reset.
271
+ const resetAt = await getChatThreadResetAt(threadId);
272
+ const previousTask = await getLatestTaskByContextKey(contextKey);
273
+ const mostRecentTask =
274
+ previousTask && resetAt && new Date(previousTask.createdAt).getTime() <= resetAt.getTime()
275
+ ? null
276
+ : previousTask;
277
+ const routeResult = await dispatchPre(
278
+ "pre.chat.route",
279
+ {
280
+ adapter,
281
+ channelId: first.channelId,
282
+ threadId,
283
+ userId: first.userId,
284
+ ...(first.userName ? { userName: first.userName } : {}),
285
+ text: rawText,
286
+ botMentioned,
287
+ isDirectMessage,
288
+ ...(mostRecentTask ? { threadContext: { adapter, threadId } } : {}),
289
+ },
290
+ {
291
+ // A runtime-invalid target is an extension failure, not a routing input.
292
+ validateModify: (data) => {
293
+ const parsed = ChatRouteModifySchema.safeParse(data);
294
+ return parsed.success
295
+ ? { success: true, data: parsed.data }
296
+ : { success: false, error: new Error(parsed.error.message) };
297
+ },
298
+ },
299
+ );
300
+ if (routeResult.action === "block") {
301
+ console.info(
302
+ `[chat-sdk] Extension blocked routing for ${threadId}:`,
303
+ scrubSecrets(routeResult.reason),
304
+ );
305
+ stopTypingKeepalive(threadId);
306
+ return;
307
+ }
308
+
309
+ let target: ChatRouteTarget | null = null;
310
+ if (routeResult.action === "modify") target = await targetFromExtension(routeResult.data.target);
311
+ target ??= await routeChatMessage(rawText, {
312
+ adapter,
313
+ channelId: first.channelId,
314
+ threadId,
315
+ botMentioned,
316
+ isDirectMessage,
317
+ });
318
+ if (!target) {
319
+ console.log(`[chat-sdk] ${threadId}: message not addressed to the swarm, ignored`);
320
+ stopTypingKeepalive(threadId);
321
+ return;
322
+ }
323
+
324
+ if (target.kind === "workflow") {
325
+ // A workflow run has no relay that would stop the indicator.
326
+ stopTypingKeepalive(threadId);
327
+ try {
328
+ // The adapter verified the platform signature, and the workflow id comes
329
+ // from an operator mapping or an extension, never from the message.
330
+ const { runId } = await handleWebhookTrigger(
331
+ target.workflowId,
332
+ {
333
+ adapter,
334
+ channelId: first.channelId,
335
+ threadId,
336
+ contextKey,
337
+ userId: first.userId,
338
+ userName: first.userName,
339
+ text: rawText,
340
+ messageIds: items.map((item) => item.messageId),
341
+ },
342
+ {},
343
+ getExecutorRegistry(),
344
+ { alreadyAuthenticated: true },
345
+ );
346
+ console.log(`[chat-sdk] ${threadId}: started workflow run ${runId}`);
347
+ await subscribe(thread);
348
+ } catch (error) {
349
+ console.error(`[chat-sdk] ${threadId}: workflow dispatch failed: ${errorMessage(error)}`);
350
+ }
351
+ return;
352
+ }
353
+
354
+ const body = stripRoutingTokens(rawText);
355
+ const files = items.flatMap((item) => toChatFiles(adapter, item.messageId, item.attachments));
356
+ await using inbound = await fetchChatFiles(files);
357
+ const fileText = buildChatAttachmentText(inbound.files, inbound.failed);
358
+ const combined = [body, fileText].filter(Boolean).join("\n\n");
359
+ const marked =
360
+ items.length > 1
361
+ ? `[Thread follow-up: ${items.length} message(s) buffered]\n\n${combined}`
362
+ : combined;
363
+ const threadContext =
364
+ mostRecentTask && thread
365
+ ? await getChatThreadContext(thread, new Set(items.map((item) => item.messageId)), resetAt)
366
+ : "";
367
+ const description = threadContext
368
+ ? `${resolveTemplate("chat.message.thread_context", { thread_messages: threadContext }).text}\n\n${marked}`
369
+ : marked;
370
+
371
+ const latestActive = await getLatestActiveTaskByContextKey(contextKey, "chat");
372
+ const dependsOn = !immediate && latestActive ? [latestActive.id] : undefined;
373
+ let created = 0;
374
+ for (const match of target.matches) {
375
+ const options: CreateTaskOptions = {
376
+ agentId: match.agent?.id ?? null,
377
+ routingReason: match.agent ? match.reason : undefined,
378
+ routingSource: match.agent ? "engine_default" : undefined,
379
+ source: "chat",
380
+ taskType: "chat-inbound",
381
+ tags: ["chat-sdk", adapter],
382
+ priority: 70,
383
+ contextKey,
384
+ dependsOn,
385
+ parentTaskId: mostRecentTask?.id,
386
+ // A human message in the thread: never answer it in the previous task's
387
+ // structured-output contract.
388
+ inheritParentOutputSchema: false,
389
+ };
390
+ try {
391
+ const { task, unattached } = await createChatTaskWithFiles(description, options, inbound);
392
+ created++;
393
+ console.log(
394
+ `[chat-sdk] ${threadId}: task ${task.id} for ${match.agent ? `agent ${match.agent.id}` : "no agent (queued)"} (${match.matchedText}, dependsOn: ${dependsOn?.join(", ") ?? "none"}${unattached.length ? `, ${unattached.length} file(s) not attached` : ""})`,
395
+ );
396
+ } catch (error) {
397
+ if (error instanceof TaskCreationBlockedError) {
398
+ console.info(
399
+ `[chat-sdk] ${threadId}: task creation blocked: ${scrubSecrets(error.reason)}`,
400
+ );
401
+ } else {
402
+ console.error(`[chat-sdk] ${threadId}: task creation failed: ${errorMessage(error)}`);
403
+ }
404
+ }
405
+ }
406
+ if (created > 0) {
407
+ await subscribe(thread);
408
+ // Subscribed-thread messages start typing only here, once a task exists.
409
+ startTypingKeepalive(adapter, threadId);
410
+ } else {
411
+ stopTypingKeepalive(threadId);
412
+ }
413
+ }
414
+
415
+ /**
416
+ * Subscribe so plain replies reach `onSubscribedMessage`. Threads stay
417
+ * subscribed after a terminal task: a later reply is a follow-up.
418
+ */
419
+ async function subscribe(thread: Thread | undefined): Promise<void> {
420
+ if (!thread) return;
421
+ try {
422
+ if (!(await thread.isSubscribed())) await thread.subscribe();
423
+ } catch (error) {
424
+ console.warn(`[chat-sdk] Could not subscribe to ${thread.id}: ${errorMessage(error)}`);
425
+ }
426
+ }