@desplega.ai/agent-swarm 1.173.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 (432) hide show
  1. package/README.md +1 -1
  2. package/dist/{acp-adapter-ccm9xrep.js → acp-adapter-sj2g0m84.js} +17 -16
  3. package/dist/{actions-sgeyv08k.js → actions-fegk44gr.js} +39 -34
  4. package/dist/{amp-adapter-gpxp3kc9.js → amp-adapter-ad8ft89x.js} +8 -7
  5. package/dist/{app-km9qnv2f.js → app-kfvgbvzy.js} +18 -14
  6. package/dist/{assistant-5r8tcvvd.js → assistant-z7zdy9ja.js} +40 -35
  7. package/dist/{boot-reembed-swwzdgd7.js → boot-reembed-5c2z0mbe.js} +10 -9
  8. package/dist/{boot-reembed-16rv9843.js → boot-reembed-j8xa181e.js} +10 -9
  9. package/dist/{boot-scrub-sweep-zkr6vkqb.js → boot-scrub-sweep-hnkn3rgt.js} +11 -10
  10. package/dist/{boot-seeds-d8sp49t2.js → boot-seeds-kwjq4d2z.js} +14 -13
  11. package/dist/{claude-adapter-yzzzh8jp.js → claude-adapter-9vmvpvgd.js} +19 -18
  12. package/dist/{claude-managed-adapter-xgxhqrt2.js → claude-managed-adapter-k9qae05j.js} +7 -5
  13. package/dist/{claude-managed-setup-bnmjarjc.js → claude-managed-setup-q54zx5q2.js} +6 -4
  14. package/dist/{claude-sdk-session-4whxere4.js → claude-sdk-session-9jye6cxr.js} +11881 -11472
  15. package/dist/{cli-94b48jhk.js → cli-03gedhng.js} +16 -14
  16. package/dist/{cli-1w0vj3gw.js → cli-1e4b6bzh.js} +7 -7
  17. package/dist/{cli-d603qf86.js → cli-29jc5q7w.js} +10 -8
  18. package/dist/cli-2m1ktfzz.js +17795 -0
  19. package/dist/{cli-x119kdba.js → cli-3dj8k4vy.js} +1 -1
  20. package/dist/{cli-srqq3gc5.js → cli-3fbmmawm.js} +5 -5
  21. package/dist/{cli-gmdajfw5.js → cli-4jxm2qtz.js} +4987 -1708
  22. package/dist/{cli-tadfgd0t.js → cli-5hkm7whv.js} +33 -4
  23. package/dist/{cli-42qhyvwx.js → cli-5t4f29ez.js} +3 -3
  24. package/dist/{cli-7a0sde2x.js → cli-6cagzc96.js} +9 -0
  25. package/dist/cli-6cvfxs8r.js +47 -0
  26. package/dist/{cli-0kxf81jj.js → cli-6jyj59nb.js} +2 -2
  27. package/dist/{cli-w1apjecb.js → cli-6razj6t4.js} +2 -2
  28. package/dist/{cli-anzdk0z4.js → cli-74ahz61x.js} +2 -2
  29. package/dist/{cli-m89t9jdf.js → cli-7x0g4sn1.js} +15416 -16024
  30. package/dist/{cli-yatxqynm.js → cli-9ha6hs2d.js} +1 -290
  31. package/dist/{cli-81r1wjyv.js → cli-9nfx44eh.js} +1 -1
  32. package/dist/cli-9traj3dn.js +638 -0
  33. package/dist/{cli-smcrrmhy.js → cli-agzebbd5.js} +9 -9
  34. package/dist/{cli-na2pxe4f.js → cli-apx87jne.js} +2 -2
  35. package/dist/{cli-rfz2ve55.js → cli-bzszjf2p.js} +2855 -862
  36. package/dist/{cli-c5km5znr.js → cli-c8r3kzzb.js} +1 -1
  37. package/dist/{cli-hsem16hb.js → cli-cgtksawx.js} +7 -4
  38. package/dist/{cli-db3m6zdt.js → cli-d2qr7aj4.js} +1 -1
  39. package/dist/{cli-rfs4vddv.js → cli-d5f9ed5c.js} +5 -5
  40. package/dist/cli-e3g5mbkz.js +776 -0
  41. package/dist/{cli-7jykv7ef.js → cli-e6mpjhed.js} +1 -1
  42. package/dist/{cli-7a93cv8c.js → cli-ee9gtz70.js} +98 -23
  43. package/dist/{cli-3nxdmjrc.js → cli-eejsgvm4.js} +4 -4
  44. package/dist/{cli-fmhj1ehj.js → cli-ef1hg1wv.js} +470 -51
  45. package/dist/cli-f527ce74.js +781 -0
  46. package/dist/{cli-ppy4nnnh.js → cli-f7kr7svz.js} +2 -2
  47. package/dist/cli-hjwmhcg1.js +10622 -0
  48. package/dist/{cli-2bw3w47j.js → cli-hnhtk6cj.js} +1 -1
  49. package/dist/{cli-ywm1azg9.js → cli-hnmhtzvh.js} +6 -4
  50. package/dist/{cli-h7k0xtry.js → cli-hvqbtna4.js} +4 -1256
  51. package/dist/{cli-s8z6x4b4.js → cli-j0pfptdp.js} +218 -102
  52. package/dist/cli-jta2eyzc.js +619 -0
  53. package/dist/{cli-5er5gjbm.js → cli-k382tshb.js} +10 -11
  54. package/dist/{cli-v3zhnxsb.js → cli-k3dp5tpf.js} +2 -2
  55. package/dist/{cli-wd0hz6ah.js → cli-kbrt8gqa.js} +9 -91
  56. package/dist/cli-kceah1eh.js +92 -0
  57. package/dist/{cli-ne7smj1b.js → cli-ke896fcf.js} +10 -10
  58. package/dist/{cli-xc7p7s3g.js → cli-kt6zqg0e.js} +6 -6
  59. package/dist/{cli-wrh08zjd.js → cli-m50t4309.js} +5 -18
  60. package/dist/cli-n2wq4j7m.js +6985 -0
  61. package/dist/{cli-bv2etw2y.js → cli-n3hzg89b.js} +46 -15
  62. package/dist/{cli-tmhsxhb8.js → cli-n5vwg842.js} +23 -6
  63. package/dist/{cli-qm1448f9.js → cli-ne236afy.js} +8 -8
  64. package/dist/{cli-d161g69q.js → cli-p1x85d2t.js} +50 -2
  65. package/dist/{cli-7pz3b7x5.js → cli-przpmdgr.js} +25 -0
  66. package/dist/{cli-0a7mm3zp.js → cli-q0ke1yww.js} +25 -21
  67. package/dist/{cli-e3pmyt39.js → cli-q50942rs.js} +1 -1
  68. package/dist/{cli-r44t9wqc.js → cli-r106725k.js} +2 -2
  69. package/dist/{cli-phcqp1b9.js → cli-r2pnvj8g.js} +2 -2
  70. package/dist/{cli-jpc4r224.js → cli-r62a35ea.js} +19 -3
  71. package/dist/{cli-cd0hv9nh.js → cli-ra0xzggj.js} +2 -2
  72. package/dist/cli-rhs9pz33.js +294 -0
  73. package/dist/{cli-r4fqfpwf.js → cli-s2sp957d.js} +50 -27
  74. package/dist/{cli-1x7da0de.js → cli-s4setq91.js} +8 -8
  75. package/dist/{cli-6thk4jz3.js → cli-se8w6wny.js} +6 -6
  76. package/dist/cli-skxvvpap.js +10140 -0
  77. package/dist/{cli-014vns2y.js → cli-stfs0k5s.js} +208 -525
  78. package/dist/{cli-0p3enk35.js → cli-tqaazemc.js} +9 -1
  79. package/dist/{cli-nwrdx840.js → cli-tr4sxp68.js} +3 -3
  80. package/dist/{cli-r0byd61h.js → cli-tsde1hdr.js} +15 -15
  81. package/dist/{cli-b07hr3bt.js → cli-txqb9s38.js} +2 -2
  82. package/dist/{cli-6r2f7edk.js → cli-v1yb0rqb.js} +6 -6
  83. package/dist/{cli-saqyage0.js → cli-vatyhshy.js} +179 -27
  84. package/dist/{cli-5wpw4rab.js → cli-vdach5a9.js} +5 -5
  85. package/dist/{cli-7avy4v8w.js → cli-w1q7h2r2.js} +5 -3
  86. package/dist/{cli-rcp5aqdw.js → cli-w7m81tb7.js} +1 -1
  87. package/dist/{cli-c3rk1v0x.js → cli-wayw93nt.js} +3 -3
  88. package/dist/cli-wmgrkscj.js +23254 -0
  89. package/dist/{cli-9xf7fdtp.js → cli-z5jjpaxw.js} +29 -6
  90. package/dist/{cli-451ks6aw.js → cli-zkpks3r3.js} +1 -1
  91. package/dist/cli.js +29 -45
  92. package/dist/{codex-adapter-r3dn4fp5.js → codex-adapter-y81sssk0.js} +15 -14
  93. package/dist/{codex-login-841kv5fk.js → codex-login-f2rcfpp9.js} +3 -2
  94. package/dist/{codex-session-runner-fwt793h4.js → codex-session-runner-8vx4j6a9.js} +15 -14
  95. package/dist/{commands-z8fq9qna.js → commands-kcpvpcrr.js} +11 -10
  96. package/dist/{controller-hq5ggpcp.js → controller-ywrmt6yd.js} +5 -4
  97. package/dist/{controllers-connect-3mf2btyj.js → controllers-connect-kwpvj3rf.js} +42 -37
  98. package/dist/{cursor-adapter-q8pgv5qa.js → cursor-adapter-cehv1qns.js} +7 -6
  99. package/dist/{db-yyhqqrbf.js → db-k8ankj20.js} +12 -11
  100. package/dist/{db-qfpq4bt7.js → db-ww5d7jq3.js} +28 -9
  101. package/dist/{devin-adapter-xgtj680j.js → devin-adapter-x1022qb1.js} +3 -2
  102. package/dist/{dispatcher-x0x7qfcq.js → dispatcher-e8gq5m68.js} +38 -33
  103. package/dist/{docker-f503k9p0.js → docker-ff6y1190.js} +3 -2
  104. package/dist/{dsh-adapter-34etvb5m.js → dsh-adapter-3epjm3af.js} +7 -6
  105. package/dist/{e2b-66xra4az.js → e2b-8ygwhg0f.js} +4 -3
  106. package/dist/{e2b-xwhyj8cw.js → e2b-swhnpw3n.js} +4 -3
  107. package/dist/{event-bus-vjtv82gm.js → event-bus-6axnngdj.js} +3 -2
  108. package/dist/{external-channel-h1fjecwe.js → external-channel-h3359cwn.js} +11 -10
  109. package/dist/{google-generative-ai-szdxbpqg.js → google-generative-ai-g8db7x0f.js} +4 -2
  110. package/dist/{google-vertex-qzw3p36d.js → google-vertex-r2692q0r.js} +4 -2
  111. package/dist/{grok-adapter-e7mr287e.js → grok-adapter-0d4x7gkx.js} +18 -17
  112. package/dist/{handlers-znb9n7d4.js → handlers-an1gzv21.js} +41 -36
  113. package/dist/{heartbeat-bm4qkn8d.js → heartbeat-6dakeb61.js} +43 -38
  114. package/dist/{hook-254r4mza.js → hook-6v6cv6d5.js} +11 -23
  115. package/dist/{hook-xaa3q80s.js → hook-sa5xye4y.js} +12 -11
  116. package/dist/{http-3dgrgp8y.js → http-r7f3eky0.js} +1499 -251
  117. package/dist/index-52j1aja4.js +3465 -0
  118. package/dist/{index-fx3jqq1e.js → index-7ep4gzdk.js} +38 -33
  119. package/dist/index-9gw58n4k.js +193 -0
  120. package/dist/index-9j87mcm8.js +56305 -0
  121. package/dist/index-bsmcresk.js +79424 -0
  122. package/dist/index-mh85qhec.js +475 -0
  123. package/dist/{index-g2byveez.js → index-rmpw9a8q.js} +38 -33
  124. package/dist/{index-2ybk7khy.js → index-s2wxgzr0.js} +468 -683
  125. package/dist/{index-bdsqa799.js → index-v1v6n7hg.js} +43 -38
  126. package/dist/index-we27kd31.js +12810 -0
  127. package/dist/{index-jd7sejm2.js → index-zk96aq1k.js} +10 -9
  128. package/dist/{keepalive-6ywhpja1.js → keepalive-be9vwy5h.js} +12 -11
  129. package/dist/{lead-cw45n21s.js → lead-0861cwrf.js} +61 -56
  130. package/dist/{maintenance-ss1a1ge9.js → maintenance-mzzgc8bn.js} +10 -9
  131. package/dist/{node-fkajaq96.js → node-mn1zs7an.js} +2 -1
  132. package/dist/{oauth-refresh-sweep-3c6f8vyy.js → oauth-refresh-sweep-cpdnfwdj.js} +11 -10
  133. package/dist/{onboard-8hzdhc1k.js → onboard-fh8qs4r3.js} +11 -3
  134. package/dist/{opencode-adapter-qbchbcfz.js → opencode-adapter-nm42wwj8.js} +18 -13
  135. package/dist/{otel-impl-e6mnvc4v.js → otel-impl-x6c1c9g4.js} +242 -868
  136. package/dist/{pi-mono-adapter-3c9g9snh.js → pi-mono-adapter-9n2ya240.js} +244 -106
  137. package/dist/{pricing-refresh-gedkjdw0.js → pricing-refresh-qb0rv0ap.js} +12 -11
  138. package/dist/{rbac-roles-dt4vh1yy.js → rbac-roles-1gzdwbh1.js} +11 -10
  139. package/dist/{rbac-roles-eszdg6pm.js → rbac-roles-3mraey52.js} +12 -11
  140. package/dist/{render-v2-907cy6sa.js → render-v2-agpa45zz.js} +18 -14
  141. package/dist/{resume-42stcaz0.js → resume-x0ydy9s9.js} +25 -21
  142. package/dist/{runtime-qfry88v2.js → runtime-sa2vr16g.js} +2 -2
  143. package/dist/{setup-adbc1c10.js → setup-as9ga0d6.js} +2 -2
  144. package/dist/{virtual-modules-rtmt494q.js → virtual-modules-9gkxqrap.js} +6 -5
  145. package/dist/{work-objects-wpj8ck95.js → work-objects-y5g1y7ev.js} +41 -36
  146. package/dist/{worker-bzdra9rf.js → worker-860c3e38.js} +61 -56
  147. package/dist/{x-qfmygqqv.js → x-m477fr0h.js} +3 -2
  148. package/openapi.json +523 -344
  149. package/package.json +10 -11
  150. package/src/be/config-refs.ts +60 -0
  151. package/src/be/db/page-versions.ts +15 -0
  152. package/src/be/db/runtime.ts +35 -8
  153. package/src/be/db/skill-name-resolution.ts +36 -0
  154. package/src/be/db/tasks/read.ts +230 -0
  155. package/src/be/db/tasks/write.ts +7 -0
  156. package/src/be/db.ts +52 -7
  157. package/src/be/migrations/215_swarm_config_export_to_env.sql +34 -0
  158. package/src/be/migrations/216_task_resolved_effort.sql +5 -0
  159. package/src/be/migrations/217_chat_sdk_state.sql +104 -0
  160. package/src/be/model-catalog-store.ts +5 -1
  161. package/src/be/model-tier-resolution.ts +12 -1
  162. package/src/be/model-validation.ts +5 -2
  163. package/src/be/models-catalog.ts +26 -1
  164. package/src/be/modelsdev-cache.json +198 -63
  165. package/src/be/modelsdev-cache.ts +9 -1
  166. package/src/be/script-credential-broker.ts +39 -2
  167. package/src/be/scripts/run-saved.ts +1 -0
  168. package/src/be/scripts/typecheck.ts +10 -8
  169. package/src/be/seed/runner.ts +13 -1
  170. package/src/be/seed/state-db.ts +5 -0
  171. package/src/be/seed/types.ts +6 -0
  172. package/src/be/seed-pricing.ts +19 -2
  173. package/src/be/seed-scripts/catalog/ops-catalog-audit.inline.ts +1 -1
  174. package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +1 -1
  175. package/src/be/seed-skills/bundled-files.generated.json +0 -18
  176. package/src/be/seed-skills/index.ts +104 -3
  177. package/src/be/swarm-config-guard.ts +52 -0
  178. package/src/cli.tsx +0 -16
  179. package/src/commands/onboard/non-interactive.ts +4 -2
  180. package/src/commands/provider-credentials.ts +52 -6
  181. package/src/commands/runner.ts +190 -54
  182. package/src/e2b/env.ts +5 -0
  183. package/src/extensions/contract-types.generated.ts +1 -1
  184. package/src/extensions/contract.ts +45 -0
  185. package/src/extensions/dispatcher.ts +7 -0
  186. package/src/extensions/post-bridge.ts +40 -0
  187. package/src/github/handlers.ts +11 -4
  188. package/src/heartbeat/templates.ts +8 -5
  189. package/src/hooks/hook.ts +0 -26
  190. package/src/http/all-routes.ts +0 -1
  191. package/src/http/config.ts +36 -17
  192. package/src/http/core.ts +4 -0
  193. package/src/http/index.ts +14 -2
  194. package/src/http/integrations.ts +88 -1
  195. package/src/http/mcp.ts +15 -5
  196. package/src/http/page-proxy.ts +2 -4
  197. package/src/http/pages-public.ts +3 -3
  198. package/src/http/pages.ts +3 -13
  199. package/src/http/poll.ts +28 -68
  200. package/src/http/requested-by.ts +54 -0
  201. package/src/http/scripts.ts +1 -0
  202. package/src/http/stats.ts +1 -30
  203. package/src/http/tasks.ts +61 -5
  204. package/src/http/utils.ts +14 -0
  205. package/src/http/webhooks.ts +159 -1
  206. package/src/http/x.ts +1 -0
  207. package/src/integrations/chat-sdk/adapters.ts +129 -0
  208. package/src/integrations/chat-sdk/attachments.ts +232 -0
  209. package/src/integrations/chat-sdk/background.ts +36 -0
  210. package/src/integrations/chat-sdk/commands.ts +194 -0
  211. package/src/integrations/chat-sdk/config.ts +198 -0
  212. package/src/integrations/chat-sdk/discord-api.ts +63 -0
  213. package/src/integrations/chat-sdk/discord-gateway.ts +185 -0
  214. package/src/integrations/chat-sdk/handlers.ts +28 -0
  215. package/src/integrations/chat-sdk/inbound.ts +426 -0
  216. package/src/integrations/chat-sdk/limits.ts +48 -0
  217. package/src/integrations/chat-sdk/logger.ts +81 -0
  218. package/src/integrations/chat-sdk/outbound.ts +123 -0
  219. package/src/integrations/chat-sdk/relay.ts +172 -0
  220. package/src/integrations/chat-sdk/router.ts +169 -0
  221. package/src/integrations/chat-sdk/runtime.ts +351 -0
  222. package/src/integrations/chat-sdk/schema.ts +245 -0
  223. package/src/integrations/chat-sdk/state.ts +243 -0
  224. package/src/integrations/chat-sdk/status.ts +244 -0
  225. package/src/integrations/chat-sdk/steering.ts +58 -0
  226. package/src/integrations/chat-sdk/teams-origin.ts +77 -0
  227. package/src/integrations/chat-sdk/telegram-api.ts +56 -0
  228. package/src/integrations/chat-sdk/templates.ts +16 -0
  229. package/src/integrations/chat-sdk/test-hooks.ts +37 -0
  230. package/src/integrations/chat-sdk/thread-buffer.ts +100 -0
  231. package/src/integrations/chat-sdk/typing.ts +65 -0
  232. package/src/pages/version.ts +4 -5
  233. package/src/prompts/base-prompt.ts +111 -45
  234. package/src/providers/acp-target-catalog.ts +6 -0
  235. package/src/providers/acp-targets.ts +55 -2
  236. package/src/providers/amp-adapter.ts +1 -1
  237. package/src/providers/claude-adapter.ts +1 -1
  238. package/src/providers/claude-flag-settings.ts +17 -5
  239. package/src/providers/claude-managed-adapter.ts +1 -1
  240. package/src/providers/claude-session-events.ts +6 -1
  241. package/src/providers/dsh-adapter.ts +1 -1
  242. package/src/providers/opencode-adapter.ts +243 -26
  243. package/src/providers/opencode-swarm-plugin.ts +31 -0
  244. package/src/providers/pi-mono-adapter.ts +398 -153
  245. package/src/providers/pricing-sources.md +33 -2
  246. package/src/providers/reasoning-effort.ts +27 -0
  247. package/src/providers/types.ts +6 -2
  248. package/src/rbac/legacy-policy.ts +8 -2
  249. package/src/rbac/permissions.ts +21 -0
  250. package/src/scheduler/scheduler.ts +4 -2
  251. package/src/scripts-runtime/loader.ts +19 -7
  252. package/src/scripts-runtime/sdk-allowlist.ts +8 -6
  253. package/src/scripts-runtime/swarm-sdk.ts +0 -11
  254. package/src/scripts-runtime/types/stdlib.d.ts +27 -15
  255. package/src/scripts-runtime/types/swarm-extension.d.ts +63 -1
  256. package/src/scripts-runtime/types/swarm-sdk.d.ts +27 -15
  257. package/src/server.ts +52 -18
  258. package/src/slack/event-dedup.ts +1 -1
  259. package/src/tasks/additive-buffer.ts +4 -2
  260. package/src/tasks/context-key.ts +60 -1
  261. package/src/tasks/model-pin.ts +67 -0
  262. package/src/tasks/worker-follow-up.ts +4 -31
  263. package/src/tests/acp-adapter.test.ts +42 -0
  264. package/src/tests/api-drain-worker-e2e.test.ts +1 -6
  265. package/src/tests/base-prompt.test.ts +58 -54
  266. package/src/tests/chat-sdk-commands.test.ts +191 -0
  267. package/src/tests/chat-sdk-config.test.ts +334 -0
  268. package/src/tests/chat-sdk-discord-api.test.ts +68 -0
  269. package/src/tests/chat-sdk-discord-gateway.test.ts +252 -0
  270. package/src/tests/chat-sdk-discord-webhook.test.ts +126 -0
  271. package/src/tests/chat-sdk-fields-sync.test.ts +50 -0
  272. package/src/tests/chat-sdk-harness.ts +146 -0
  273. package/src/tests/chat-sdk-inbound.test.ts +363 -0
  274. package/src/tests/chat-sdk-relay.test.ts +470 -0
  275. package/src/tests/chat-sdk-reload.test.ts +128 -0
  276. package/src/tests/chat-sdk-shutdown.test.ts +220 -0
  277. package/src/tests/chat-sdk-slack.test.ts +268 -0
  278. package/src/tests/chat-sdk-state.test.ts +163 -0
  279. package/src/tests/chat-sdk-status-route.test.ts +382 -0
  280. package/src/tests/chat-sdk-teams.test.ts +277 -0
  281. package/src/tests/chat-sdk-tools.test.ts +520 -0
  282. package/src/tests/chat-sdk-typing.test.ts +120 -0
  283. package/src/tests/chat-sdk-webhook-telegram.test.ts +200 -0
  284. package/src/tests/claude-adapter-binary.test.ts +9 -4
  285. package/src/tests/claude-flag-settings.test.ts +11 -3
  286. package/src/tests/claude-managed-adapter.test.ts +1 -1
  287. package/src/tests/claude-sdk-lifecycle.test.ts +9 -3
  288. package/src/tests/claude-thinking-tokens.test.ts +57 -0
  289. package/src/tests/claude-worker-parity.test.ts +2 -7
  290. package/src/tests/config-env-export-chat-sdk.test.ts +11 -0
  291. package/src/tests/config-refs.test.ts +66 -0
  292. package/src/tests/context-key.test.ts +19 -0
  293. package/src/tests/cors-allowlist.test.ts +25 -0
  294. package/src/tests/create-page-tool.test.ts +18 -0
  295. package/src/tests/credential-check.test.ts +139 -0
  296. package/src/tests/credentials.test.ts +21 -0
  297. package/src/tests/db-runtime.test.ts +61 -0
  298. package/src/tests/db-task-read-contract.test.ts +20 -0
  299. package/src/tests/db-task-write-contract.test.ts +10 -0
  300. package/src/tests/docs-index-footprint.test.js +3 -3
  301. package/src/tests/dsh-adapter.test.ts +1 -1
  302. package/src/tests/enabled-capabilities.test.ts +20 -0
  303. package/src/tests/entrypoint-api-readiness.test.ts +14 -8
  304. package/src/tests/entrypoint-config-env-export.test.ts +5 -2
  305. package/src/tests/entrypoint-export-to-env.test.ts +78 -0
  306. package/src/tests/explicit-model-validation.test.ts +105 -1
  307. package/src/tests/extensions-example-route-chat-channel.test.ts +154 -0
  308. package/src/tests/extensions-post-bridge.test.ts +51 -0
  309. package/src/tests/fetch-resolved-env.test.ts +128 -0
  310. package/src/tests/fixtures/chat-sdk/telegram-private-text.json +10 -0
  311. package/src/tests/fixtures/extensions/route-chat-channel-to-agent/hooks.ts +27 -0
  312. package/src/tests/fixtures/extensions/route-chat-channel-to-agent/manifest.json +9 -0
  313. package/src/tests/fixtures/openai-compatible-registry-extension.child.ts +146 -0
  314. package/src/tests/fixtures/runner-legacy-resume-prompt.ts +122 -0
  315. package/src/tests/github-handlers.test.ts +35 -0
  316. package/src/tests/harness-provider-resolution.test.ts +12 -0
  317. package/src/tests/heartbeat-checklist.test.ts +3 -2
  318. package/src/tests/hook-runtime-close.test.ts +0 -2
  319. package/src/tests/http-api-integration.test.ts +0 -25
  320. package/src/tests/migration-046-budgets.test.ts +17 -16
  321. package/src/tests/migration-217-chat-sdk-state.test.ts +113 -0
  322. package/src/tests/model-tier-resolution.test.ts +93 -0
  323. package/src/tests/models-catalog.test.ts +42 -1
  324. package/src/tests/openai-compatible-providers.test.ts +134 -0
  325. package/src/tests/openai-compatible-registry-extension.test.ts +42 -0
  326. package/src/tests/opencode-adapter.test.ts +216 -2
  327. package/src/tests/opencode-helmcode-sdk.test.ts +201 -0
  328. package/src/tests/opencode-shell-env.test.ts +145 -0
  329. package/src/tests/openrouter-base-url.test.ts +101 -151
  330. package/src/tests/page-proxy.test.ts +1 -1
  331. package/src/tests/pages-browser-sdk.test.ts +76 -0
  332. package/src/tests/pages-feedback.test.ts +1 -1
  333. package/src/tests/paused-tasks-requested-by.test.ts +102 -0
  334. package/src/tests/pi-custom-provider-fake-server.test.ts +442 -0
  335. package/src/tests/pi-mono-adapter.test.ts +385 -5
  336. package/src/tests/pi-shell-env.test.ts +170 -0
  337. package/src/tests/prompt-template-remaining.test.ts +35 -0
  338. package/src/tests/rbac-engine.test.ts +11 -3
  339. package/src/tests/runner-exit-after-tasks.test.ts +0 -4
  340. package/src/tests/runner-legacy-resume-prompt.test.ts +64 -0
  341. package/src/tests/script-connections.test.ts +2 -0
  342. package/src/tests/scripts-http.test.ts +201 -1
  343. package/src/tests/scripts-only-gating.test.ts +2 -3
  344. package/src/tests/scripts-runtime.test.ts +58 -40
  345. package/src/tests/secret-scrubber.test.ts +86 -0
  346. package/src/tests/seed-skills-bundled-files.test.ts +45 -53
  347. package/src/tests/seed-skills-retire.test.ts +182 -0
  348. package/src/tests/send-task-requester-gate.test.ts +58 -0
  349. package/src/tests/session-cost-recompute-unit.test.ts +46 -0
  350. package/src/tests/skill-get-name-resolution.test.ts +137 -0
  351. package/src/tests/sonnet-5-5-registry.test.ts +9 -1
  352. package/src/tests/swarm-config-export-to-env.test.ts +267 -0
  353. package/src/tests/swarm-config-reserved-keys.test.ts +16 -0
  354. package/src/tests/swarm-diff.test.ts +1 -1
  355. package/src/tests/task-model-effort-rerun.test.ts +164 -0
  356. package/src/tests/task-session-prompts.test.ts +132 -0
  357. package/src/tests/task-tool-manifest.test.ts +14 -0
  358. package/src/tests/task-tools-ctx.test.ts +2 -0
  359. package/src/tests/tool-annotations.test.ts +6 -6
  360. package/src/tests/ui-logs-parser.test.ts +43 -0
  361. package/src/tests/used-by-route.test.ts +6 -0
  362. package/src/tests/vercel-ai-gateway.test.ts +149 -0
  363. package/src/tests/workflow-swarm-script.test.ts +38 -0
  364. package/src/tests/workflow-wait-filter.test.ts +5 -3
  365. package/src/tools/chat-access.ts +41 -0
  366. package/src/tools/chat-open-dm.ts +59 -0
  367. package/src/tools/chat-post.ts +74 -0
  368. package/src/tools/chat-read-thread.ts +100 -0
  369. package/src/tools/chat-reply.ts +75 -0
  370. package/src/tools/create-page.ts +3 -13
  371. package/src/tools/model-catalog/index.ts +2 -1
  372. package/src/tools/register-chat-channel.ts +139 -0
  373. package/src/tools/send-task.ts +24 -0
  374. package/src/tools/skills/skill-get.ts +17 -9
  375. package/src/tools/swarm-config/get-config.ts +2 -1
  376. package/src/tools/swarm-config/list-config.ts +2 -1
  377. package/src/tools/swarm-config/set-config.ts +16 -3
  378. package/src/tools/tool-config.ts +8 -6
  379. package/src/types.ts +12 -2
  380. package/src/utils/config-env-export.ts +74 -0
  381. package/src/utils/credentials.test.ts +92 -0
  382. package/src/utils/credentials.ts +52 -4
  383. package/src/utils/harness-provider.ts +15 -6
  384. package/src/utils/runtime-model-catalog.ts +11 -1
  385. package/src/utils/secret-scrubber.ts +71 -2
  386. package/src/utils/task-tool-manifest.ts +19 -2
  387. package/src/workflows/executors/swarm-script.ts +1 -0
  388. package/templates/skills/agent-browser/SKILL.md +1 -1
  389. package/templates/skills/agent-browser/content.md +1 -1
  390. package/templates/skills/apps/SKILL.md +8 -1
  391. package/templates/skills/apps/content.md +8 -1
  392. package/templates/skills/chat-sdk/config.json +14 -0
  393. package/templates/skills/chat-sdk/content.md +84 -0
  394. package/templates/skills/composio-gmail/content.md +7 -0
  395. package/templates/skills/composio-google-docs/content.md +1 -1
  396. package/templates/skills/email-domain-health/config.json +14 -0
  397. package/templates/skills/email-domain-health/content.md +131 -0
  398. package/templates/skills/kv-storage/content.md +2 -2
  399. package/templates/skills/pages/content.md +8 -10
  400. package/templates/skills/swarm-extensions/content.md +2 -0
  401. package/templates/skills/swarm-scripts/SKILL.md +4 -2
  402. package/templates/skills/swarm-scripts/config.json +1 -1
  403. package/templates/skills/swarm-scripts/content.md +4 -2
  404. package/templates/skills/workflow-iterate/content.md +38 -0
  405. package/dist/artifact-eczsknc3.js +0 -3843
  406. package/dist/cli-bd957dgb.js +0 -153
  407. package/dist/cli-q51yprfq.js +0 -1104
  408. package/dist/cli-t1ze64h3.js +0 -811
  409. package/dist/cli-tcc4wd5d.js +0 -56471
  410. package/dist/index-9ch1n761.js +0 -1102
  411. package/src/artifact-sdk/index.ts +0 -2
  412. package/src/artifact-sdk/localtunnel.d.ts +0 -20
  413. package/src/artifact-sdk/port.ts +0 -12
  414. package/src/artifact-sdk/server.ts +0 -183
  415. package/src/artifact-sdk/tunnel.ts +0 -19
  416. package/src/commands/artifact.ts +0 -249
  417. package/src/http/ecosystem.ts +0 -77
  418. package/src/tests/artifact-commands.test.ts +0 -92
  419. package/src/tests/artifact-sdk.test.ts +0 -793
  420. package/src/tests/register-service-security.test.ts +0 -194
  421. package/src/tools/list-services.ts +0 -107
  422. package/src/tools/register-service.ts +0 -177
  423. package/src/tools/unregister-service.ts +0 -73
  424. package/src/tools/update-service-status.ts +0 -96
  425. package/templates/skills/artifacts/config.json +0 -14
  426. package/templates/skills/artifacts/content.md +0 -344
  427. package/templates/skills/artifacts/files/examples/approval-flow.ts +0 -34
  428. package/templates/skills/artifacts/files/examples/hono-dashboard.ts +0 -31
  429. package/templates/skills/artifacts/files/examples/multi-artifact.ts +0 -20
  430. package/templates/skills/artifacts/files/examples/static-report.sh +0 -17
  431. /package/src/{artifact-sdk → pages}/browser-sdk.ts +0 -0
  432. /package/src/{artifact-sdk → pages}/feedback-overlay.ts +0 -0
@@ -66,6 +66,14 @@ import {
66
66
  verifyGitLabWebhook,
67
67
  } from "../gitlab";
68
68
  import { gitlabWorkflowActionName } from "../gitlab/workflow-action-name";
69
+ import { trackBackground } from "../integrations/chat-sdk/background";
70
+ import { isChatSdkEnabled } from "../integrations/chat-sdk/config";
71
+ import {
72
+ getChatInstance,
73
+ isChatAdapterConfigured,
74
+ isChatSdkStopping,
75
+ } from "../integrations/chat-sdk/runtime";
76
+ import { isTrustedTeamsServiceUrl } from "../integrations/chat-sdk/teams-origin";
69
77
  import {
70
78
  type KapsoMessageActionResult,
71
79
  markKapsoMessageRead,
@@ -76,11 +84,14 @@ import { getKapsoConfig } from "../integrations/kapso/config";
76
84
  import type { KapsoWebhookPayload } from "../integrations/kapso/inbound";
77
85
  import { resolveKapsoRequestedByUserId, routeKapsoInbound } from "../integrations/kapso/inbound";
78
86
  import { TaskCreationBlockedError } from "../tasks/errors";
87
+ import { getPublicMcpBaseUrl } from "../utils/constants";
88
+ import { scrubSecrets } from "../utils/secret-scrubber";
79
89
  import { canonicalAzureDevOpsRepoUrl } from "../vcs/azure-devops";
80
90
  import { getExecutorRegistry } from "../workflows";
81
91
  import { workflowEventBus } from "../workflows/event-bus";
82
92
  import { handleWebhookTrigger, verifyHmacSignature, WebhookError } from "../workflows/triggers";
83
93
  import { route } from "./route-def";
94
+ import { BODY_TOO_LARGE, enforceContentLengthCap, jsonError } from "./utils";
84
95
 
85
96
  // ─── Route Definitions (documentation only — webhooks handle their own body parsing) ─
86
97
 
@@ -203,8 +214,75 @@ const kapsoWebhook = route({
203
214
  },
204
215
  });
205
216
 
217
+ const chatSdkWebhook = route({
218
+ method: "post",
219
+ path: "/api/integrations/chat/{adapter}/webhook",
220
+ pattern: ["api", "integrations", "chat", null, "webhook"],
221
+ summary: "Handle Chat SDK platform webhooks (Telegram, Slack, Discord, Teams)",
222
+ tags: ["Webhooks"],
223
+ params: z.object({ adapter: z.string().describe("Chat SDK adapter name, e.g. telegram") }),
224
+ auth: { apiKey: false },
225
+ rbac: {
226
+ ungated:
227
+ "provider webhook: the Chat SDK adapter verifies the platform signature (Telegram secret token, Slack signing secret, Discord Ed25519, Teams JWT)",
228
+ },
229
+ responses: {
230
+ 200: { description: "Adapter response", unstructured: "adapter response proxied as-is" },
231
+ 401: { description: "Invalid platform signature or secret token" },
232
+ 403: { description: "Teams activity whose serviceUrl is not a Bot Framework host" },
233
+ 404: { description: "Chat SDK disabled or adapter not configured" },
234
+ 413: { description: "Body exceeds 1 MiB" },
235
+ 503: { description: "Adapter configured but failed to start" },
236
+ },
237
+ });
238
+
239
+ /**
240
+ * Upper bound for a Chat SDK webhook body. Platform payloads are a few KB and
241
+ * files travel by URL, so anything larger is not a platform delivery.
242
+ */
243
+ const CHAT_SDK_WEBHOOK_MAX_BODY_BYTES = 1024 * 1024;
244
+
206
245
  // ─── Handler ─────────────────────────────────────────────────────────────────
207
246
 
247
+ /** False when a Teams activity body carries a `serviceUrl` that is not a Bot Framework host. */
248
+ function hasTrustedTeamsServiceUrl(body: Buffer): boolean {
249
+ let serviceUrl: unknown;
250
+ try {
251
+ serviceUrl = (JSON.parse(body.toString("utf-8")) as { serviceUrl?: unknown } | null)
252
+ ?.serviceUrl;
253
+ } catch {
254
+ // Not JSON: the adapter rejects it.
255
+ return true;
256
+ }
257
+ if (serviceUrl === undefined || serviceUrl === null || serviceUrl === "") return true;
258
+ return typeof serviceUrl === "string" && isTrustedTeamsServiceUrl(serviceUrl);
259
+ }
260
+
261
+ /**
262
+ * Rebuild a Fetch `Request` from the raw Node request. Headers come from
263
+ * `rawHeaders` pairs: building `Headers` from `IncomingMessage.headers`
264
+ * comma-joins repeated values, which breaks signature checks.
265
+ */
266
+ function toFetchRequest(req: IncomingMessage, rawBody: Buffer, fallbackPath: string): Request {
267
+ const headers = new Headers();
268
+ if (Array.isArray(req.rawHeaders) && req.rawHeaders.length > 0) {
269
+ for (let i = 0; i + 1 < req.rawHeaders.length; i += 2) {
270
+ headers.append(req.rawHeaders[i] as string, req.rawHeaders[i + 1] as string);
271
+ }
272
+ } else {
273
+ for (const [name, value] of Object.entries(req.headers)) {
274
+ for (const item of Array.isArray(value) ? value : value === undefined ? [] : [value]) {
275
+ headers.append(name, item);
276
+ }
277
+ }
278
+ }
279
+ return new Request(`${getPublicMcpBaseUrl()}${req.url ?? fallbackPath}`, {
280
+ method: req.method ?? "POST",
281
+ headers,
282
+ body: rawBody.length > 0 ? new Uint8Array(rawBody) : undefined,
283
+ });
284
+ }
285
+
208
286
  function logKapsoAckResult(action: string, result: KapsoMessageActionResult): void {
209
287
  if (result.ok) return;
210
288
  console.warn(
@@ -316,7 +394,8 @@ export async function handleWebhooks(
316
394
  return true;
317
395
  }
318
396
 
319
- console.log(`[GitHub] Received ${eventType} event`);
397
+ const deliveryId = req.headers["x-github-delivery"] as string | undefined;
398
+ console.log(`[GitHub] Received ${eventType} event (delivery ${deliveryId ?? "unknown"})`);
320
399
 
321
400
  let result: WebhookDispatchResult = { created: false };
322
401
 
@@ -818,5 +897,84 @@ export async function handleWebhooks(
818
897
  return true;
819
898
  }
820
899
 
900
+ // Chat SDK webhook: the adapter verifies the platform signature over the raw
901
+ // body, so the request is proxied as-is and the adapter's response returned.
902
+ if (chatSdkWebhook.match(req.method, pathSegments)) {
903
+ const adapterName = pathSegments[3] ?? "";
904
+ if (!isChatSdkEnabled() || !isChatAdapterConfigured(adapterName)) {
905
+ res.writeHead(404, { "Content-Type": "application/json" });
906
+ res.end(JSON.stringify({ error: "Chat SDK adapter not configured" }));
907
+ return true;
908
+ }
909
+ if (isChatSdkStopping()) {
910
+ res.writeHead(503, { "Content-Type": "application/json" });
911
+ res.end(JSON.stringify({ error: "Chat SDK is shutting down" }));
912
+ return true;
913
+ }
914
+ const chat = getChatInstance(adapterName);
915
+ const handler = chat?.webhooks[adapterName];
916
+ if (!handler) {
917
+ res.writeHead(503, { "Content-Type": "application/json" });
918
+ res.end(JSON.stringify({ error: "Chat SDK adapter failed to start" }));
919
+ return true;
920
+ }
921
+
922
+ // The route is unauthenticated and the adapter verifies the signature over
923
+ // the buffered body, so the body is bounded before it is buffered. An
924
+ // oversized stream is answered at once and drained without buffering, the
925
+ // same way `parseBody` keeps the connection usable.
926
+ if (enforceContentLengthCap(req, res, CHAT_SDK_WEBHOOK_MAX_BODY_BYTES) === BODY_TOO_LARGE) {
927
+ return true;
928
+ }
929
+ const chunks: Buffer[] = [];
930
+ let receivedBytes = 0;
931
+ let tooLarge = false;
932
+ for await (const chunk of req) {
933
+ if (tooLarge) continue;
934
+ receivedBytes += (chunk as Buffer).byteLength;
935
+ if (receivedBytes > CHAT_SDK_WEBHOOK_MAX_BODY_BYTES) {
936
+ tooLarge = true;
937
+ chunks.length = 0;
938
+ jsonError(res, `Payload too large (max ${CHAT_SDK_WEBHOOK_MAX_BODY_BYTES} bytes)`, 413);
939
+ continue;
940
+ }
941
+ chunks.push(chunk as Buffer);
942
+ }
943
+ if (tooLarge) return true;
944
+ const rawBody = Buffer.concat(chunks);
945
+ // The Teams adapter replies, reads history and downloads files with the
946
+ // bot token at the activity's serviceUrl, and caches it for DMs. Refuse an
947
+ // untrusted one before the adapter sees the activity.
948
+ if (adapterName === "teams" && !hasTrustedTeamsServiceUrl(rawBody)) {
949
+ console.warn("[chat-sdk] teams webhook refused: serviceUrl is not a Bot Framework host");
950
+ res.writeHead(403, { "Content-Type": "application/json" });
951
+ res.end(JSON.stringify({ error: "Untrusted Teams serviceUrl" }));
952
+ return true;
953
+ }
954
+ const request = toFetchRequest(req, rawBody, `/api/integrations/chat/${adapterName}/webhook`);
955
+
956
+ try {
957
+ const response = await handler(request, {
958
+ // Handler errors are logged by `trackBackground` in `handlers.ts`.
959
+ waitUntil: trackBackground,
960
+ });
961
+ const responseHeaders: Record<string, string> = {};
962
+ response.headers.forEach((value, name) => {
963
+ responseHeaders[name] = value;
964
+ });
965
+ const body = Buffer.from(await response.arrayBuffer());
966
+ res.writeHead(response.status, responseHeaders);
967
+ res.end(body);
968
+ } catch (err) {
969
+ const message = err instanceof Error ? err.message : String(err);
970
+ console.error(`[chat-sdk] ${adapterName} webhook failed: ${scrubSecrets(message)}`);
971
+ if (!res.headersSent) {
972
+ res.writeHead(500, { "Content-Type": "application/json" });
973
+ }
974
+ res.end(JSON.stringify({ error: "Chat SDK webhook failed" }));
975
+ }
976
+ return true;
977
+ }
978
+
821
979
  return false;
822
980
  }
package/src/http/x.ts CHANGED
@@ -255,6 +255,7 @@ export async function handleX(
255
255
  timeoutMs,
256
256
  egressSecrets: credentials.egressSecrets,
257
257
  failedBindings: credentials.failedBindings,
258
+ userConfig: credentials.userConfig,
258
259
  apiConnections: getScriptApiConnectionDescriptors({ agentId: endpoint.agentId }),
259
260
  mcpConnections: getScriptMcpConnectionDescriptors({ agentId: endpoint.agentId }),
260
261
  });
@@ -0,0 +1,129 @@
1
+ import type { Adapter, Logger } from "chat";
2
+ import { type ChatSdkAdapterName, type ResolvedAdapterConfig, readApiUrlOverrides } from "./config";
3
+ import { withInfoFilter } from "./logger";
4
+ import { getChatSdkTestHooks } from "./test-hooks";
5
+
6
+ /**
7
+ * Platform API bases passed explicitly so the adapters never fall back to
8
+ * `TELEGRAM_API_BASE_URL` / `SLACK_API_URL` / `DISCORD_API_URL` /
9
+ * `TEAMS_API_URL` from `process.env`. Global swarm_config rows are injected
10
+ * into `process.env`, so an env fallback would let a config row redirect bot
11
+ * tokens to another host. Teams gets "" which blocks only `TEAMS_API_URL`.
12
+ * The other Teams SDK env fallbacks (`SERVICE_URL`, `TENANT_ID`, `CLIENT_ID`,
13
+ * `CLIENT_SECRET`, `MANAGED_IDENTITY_CLIENT_ID`, `CLOUD`) are closed by the
14
+ * injection denylist in `getInjectableGlobalConfigs` (`src/be/db.ts`).
15
+ * Deployment env can still set them on purpose.
16
+ */
17
+ const DEFAULT_API_URLS: Record<ChatSdkAdapterName, string> = {
18
+ telegram: "https://api.telegram.org",
19
+ slack: "https://slack.com/api/",
20
+ discord: "https://discord.com/api/v10",
21
+ teams: "",
22
+ };
23
+
24
+ /**
25
+ * Discord adapter info lines dropped from the logs. They fire per event and
26
+ * carry guild message content (first 100 chars), or the public key and
27
+ * signature prefix of every interaction. Warn and error lines are kept.
28
+ */
29
+ const DISCORD_NOISY_INFO_PREFIXES = [
30
+ "Discord Gateway message received",
31
+ "Discord webhook received",
32
+ "Discord signature verification",
33
+ "Discord interaction parsed",
34
+ "Discord PING received",
35
+ ];
36
+
37
+ export function keepDiscordInfoLine(message: string): boolean {
38
+ return !DISCORD_NOISY_INFO_PREFIXES.some((prefix) => message.startsWith(prefix));
39
+ }
40
+
41
+ /**
42
+ * Platform API base for one adapter: the env-only `CHAT_SDK_API_URL_OVERRIDES`
43
+ * entry, else the platform default. The adapters and the Telegram
44
+ * register-webhook route use the same base.
45
+ */
46
+ export function resolveAdapterApiUrl(name: ChatSdkAdapterName): string {
47
+ return readApiUrlOverrides()[name] ?? DEFAULT_API_URLS[name];
48
+ }
49
+
50
+ /**
51
+ * Construct one adapter. Each branch is a literal-path dynamic import so
52
+ * `bun build --compile` bundles it (a computed specifier is not bundled).
53
+ * `apiUrl` comes only from the env-only `CHAT_SDK_API_URL_OVERRIDES`.
54
+ */
55
+ export async function loadAdapter(
56
+ name: ChatSdkAdapterName,
57
+ config: ResolvedAdapterConfig,
58
+ logger: Logger,
59
+ ): Promise<Adapter> {
60
+ const apiUrl = resolveAdapterApiUrl(name);
61
+ const base = { ...config, apiUrl, logger };
62
+ switch (name) {
63
+ case "telegram": {
64
+ const { createTelegramAdapter } = await import("@chat-adapter/telegram");
65
+ // Explicit `false` blocks the TELEGRAM_ALLOW_UNVERIFIED_WEBHOOKS env fallback.
66
+ return createTelegramAdapter({ ...base, allowUnverifiedWebhooks: false } as Parameters<
67
+ typeof createTelegramAdapter
68
+ >[0]);
69
+ }
70
+ case "slack": {
71
+ const { createSlackAdapter } = await import("@chat-adapter/slack");
72
+ // "" (not undefined) blocks each `?? process.env.SLACK_*` fallback, which
73
+ // would pick up the built-in Slack app's secrets. Socket forwarding and
74
+ // the installation encryption key are multi-workspace features we do not
75
+ // use; "" turns both off.
76
+ return createSlackAdapter({
77
+ ...base,
78
+ signingSecret: config.signingSecret ?? "",
79
+ appToken: config.appToken ?? "",
80
+ socketForwardingSecret: "",
81
+ encryptionKey: "",
82
+ } as Parameters<typeof createSlackAdapter>[0]);
83
+ }
84
+ case "discord": {
85
+ const { createDiscordAdapter } = await import("@chat-adapter/discord");
86
+ // `[]` (not undefined) blocks the DISCORD_RESPOND_TO_CHANNEL_IDS and
87
+ // DISCORD_MENTION_ROLE_IDS env fallbacks.
88
+ return createDiscordAdapter({
89
+ ...base,
90
+ logger: withInfoFilter(logger, keepDiscordInfoLine),
91
+ respondToChannelIds: config.respondToChannelIds ?? [],
92
+ mentionRoleIds: config.mentionRoleIds ?? [],
93
+ } as Parameters<typeof createDiscordAdapter>[0]);
94
+ }
95
+ case "teams": {
96
+ const { createTeamsAdapter } = await import("@chat-adapter/teams");
97
+ // `webhookVerifier` replaces the Bot Framework JWT check and `token` the
98
+ // bot token call. Only tests set them (test-hooks.ts); the operator
99
+ // schema rejects `webhookVerifier`, and `token` is a function.
100
+ const { teamsWebhookVerifier, teamsToken } = getChatSdkTestHooks();
101
+ return createTeamsAdapter({
102
+ ...base,
103
+ ...(teamsWebhookVerifier ? { webhookVerifier: teamsWebhookVerifier } : {}),
104
+ ...(teamsToken ? { token: teamsToken } : {}),
105
+ } as Parameters<typeof createTeamsAdapter>[0]);
106
+ }
107
+ }
108
+ }
109
+
110
+ type SocketClientLike = { on(event: string, listener: () => void): unknown };
111
+
112
+ /**
113
+ * Slack socket mode reconnects on its own and logs through its own logger,
114
+ * not ours. Surface hard failures through the scrubbing logger so adapter
115
+ * health (`lastWarning`) shows them. A failed first connect rejects
116
+ * `initialize()` for auth errors. Network failures go through the WebClient
117
+ * retry policy and can take long before they surface here.
118
+ * `reconnecting` also fires on Slack's routine connection refresh, so it is
119
+ * logged at info only and never reaches health.
120
+ */
121
+ export function watchSocketConnection(adapter: Adapter, logger: Logger): void {
122
+ const client = (adapter as { socketClient?: SocketClientLike }).socketClient;
123
+ if (!client) return;
124
+ client.on("reconnecting", () => logger.info("Slack socket mode reconnecting"));
125
+ client.on("unable_to_socket_mode_start", () =>
126
+ logger.warn("Slack socket mode unable to start, retrying"),
127
+ );
128
+ client.on("error", () => logger.warn("Slack socket mode error"));
129
+ }
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Chat SDK attachments turned into task attachments. A copy of the Slack
3
+ * inbound-files flow (`src/slack/inbound-files.ts`), not an extraction: the
4
+ * Slack integration is left as is.
5
+ *
6
+ * Each attachment is fetched once per buffer flush through the adapter's own
7
+ * `fetchData()` (it carries the platform auth), spooled to a temp file, then
8
+ * stored through the active file provider and recorded as a `task_attachments`
9
+ * row. The task is created in `draft` while its uploads run, with its lease
10
+ * renewed, so no worker can claim it before its attachments exist. Every file
11
+ * also stays in the task text as a `[File: ...]` line. Only a fetch failure is
12
+ * flagged there; upload and record failures are logged.
13
+ */
14
+ import { mkdtemp, rm } from "node:fs/promises";
15
+ import { tmpdir } from "node:os";
16
+ import { join } from "node:path";
17
+ import type { Attachment } from "chat";
18
+ import { getTaskAttachments, promoteDraftTask, refreshDraftTaskLease } from "@/be/db";
19
+ import { MAX_TASK_ATTACHMENT_BYTES, recordTaskAttachmentUpload } from "@/be/task-attachment-store";
20
+ import { providerPath } from "@/fs/provider";
21
+ import { getFileStorageProvider } from "@/fs/registry";
22
+ import { createTaskWithSiblingAwareness } from "@/tasks/sibling-awareness";
23
+ import type { AgentTask, CreateTaskOptions } from "@/types";
24
+ import { errorMessage } from "./logger";
25
+
26
+ /** How often a draft's lease is renewed while its uploads run; the sweep waits 5 minutes. */
27
+ const DRAFT_LEASE_REFRESH_MS = 60_000;
28
+ /** A file a user sent the bot, like one uploaded from the dashboard composer. */
29
+ const SHARED_FILE_INTENT = "user-upload";
30
+
31
+ /**
32
+ * One attachment of a buffered chat message. `id` is `<messageId>-<n>` with
33
+ * every character outside `[A-Za-z0-9_.-]` replaced by `_` (Telegram message
34
+ * ids carry the chat id and a colon); it names the stored blob.
35
+ */
36
+ export interface ChatFile {
37
+ id: string;
38
+ adapter: string;
39
+ messageId: string;
40
+ name: string;
41
+ mimeType?: string;
42
+ size?: number;
43
+ attachment: Attachment;
44
+ }
45
+
46
+ export type ChatFileFailure = { file: ChatFile; reason: string };
47
+
48
+ export type FetchedChatFile = { file: ChatFile; path: string; sizeBytes: number; sha256: string };
49
+
50
+ export type InboundChatFiles = {
51
+ files: ChatFile[];
52
+ fetched: FetchedChatFile[];
53
+ failed: ChatFileFailure[];
54
+ /** Deletes the downloaded copies; hold the batch with `await using`. */
55
+ [Symbol.asyncDispose](): Promise<void>;
56
+ };
57
+
58
+ function formatFileSize(bytes: number): string {
59
+ if (bytes < 1024) return `${bytes} B`;
60
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
61
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
62
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
63
+ }
64
+
65
+ /** Describe the attachments of one message as `ChatFile`s. */
66
+ export function toChatFiles(
67
+ adapter: string,
68
+ messageId: string,
69
+ attachments: Attachment[],
70
+ ): ChatFile[] {
71
+ return attachments.map((attachment, index) => ({
72
+ id: `${messageId}-${index + 1}`.replace(/[^\w.-]/g, "_"),
73
+ adapter,
74
+ messageId,
75
+ name: attachment.name ?? `${attachment.type}-${index + 1}`,
76
+ mimeType: attachment.mimeType,
77
+ size: attachment.size,
78
+ attachment,
79
+ }));
80
+ }
81
+
82
+ /**
83
+ * `[File: <name> (<mime>, <size>) id=<id>]` per file, followed by
84
+ * `(not attached: <reason>)` when the file never reached the task.
85
+ */
86
+ export function buildChatAttachmentText(files: ChatFile[], failed: ChatFileFailure[] = []): string {
87
+ const reasons = new Map(failed.map((f) => [f.file.id, f.reason]));
88
+ return files
89
+ .map((f) => {
90
+ const size = typeof f.size === "number" ? formatFileSize(f.size) : "unknown size";
91
+ const line = `[File: ${f.name} (${f.mimeType ?? "unknown type"}, ${size}) id=${f.id}]`;
92
+ const reason = reasons.get(f.id);
93
+ return reason ? `${line} (not attached: ${reason})` : line;
94
+ })
95
+ .join("\n");
96
+ }
97
+
98
+ async function readAttachment(attachment: Attachment): Promise<Uint8Array> {
99
+ if (attachment.data instanceof Blob) return new Uint8Array(await attachment.data.arrayBuffer());
100
+ if (attachment.data) return new Uint8Array(attachment.data);
101
+ if (!attachment.fetchData) throw new Error("the adapter gave no way to download it");
102
+ const data = await attachment.fetchData();
103
+ return new Uint8Array(data);
104
+ }
105
+
106
+ /**
107
+ * Download every attachment, each to its own temp file. Never throws: a file
108
+ * that cannot be fetched lands in `failed` with a readable reason.
109
+ */
110
+ export async function fetchChatFiles(files: ChatFile[]): Promise<InboundChatFiles> {
111
+ let dir: string | undefined;
112
+ const result: InboundChatFiles = {
113
+ files,
114
+ fetched: [],
115
+ failed: [],
116
+ async [Symbol.asyncDispose]() {
117
+ if (dir) await rm(dir, { recursive: true, force: true });
118
+ },
119
+ };
120
+ if (files.length === 0) return result;
121
+
122
+ const limit = `larger than the ${MAX_TASK_ATTACHMENT_BYTES / (1024 * 1024)} MB limit`;
123
+ dir = await mkdtemp(join(tmpdir(), "chat-files-"));
124
+ for (const [index, file] of files.entries()) {
125
+ if (typeof file.size === "number" && file.size > MAX_TASK_ATTACHMENT_BYTES) {
126
+ result.failed.push({ file, reason: limit });
127
+ continue;
128
+ }
129
+ try {
130
+ const bytes = await readAttachment(file.attachment);
131
+ if (bytes.byteLength > MAX_TASK_ATTACHMENT_BYTES) {
132
+ result.failed.push({ file, reason: limit });
133
+ continue;
134
+ }
135
+ const path = join(dir, String(index));
136
+ await Bun.write(path, bytes);
137
+ const sha256 = new Bun.CryptoHasher("sha256").update(bytes).digest("hex");
138
+ file.size ??= bytes.byteLength;
139
+ result.fetched.push({ file, path, sizeBytes: bytes.byteLength, sha256 });
140
+ } catch (error) {
141
+ const reason = `download failed (${errorMessage(error)})`;
142
+ console.warn(`[chat-sdk] could not fetch file ${file.id}: ${reason}`);
143
+ result.failed.push({ file, reason });
144
+ }
145
+ }
146
+ return result;
147
+ }
148
+
149
+ /**
150
+ * Create a chat-sourced task and attach the fetched files. Without files this
151
+ * is `createTaskWithSiblingAwareness`. With files the task is created in
152
+ * `draft` and promoted once every upload has settled, success or not.
153
+ */
154
+ export async function createChatTaskWithFiles(
155
+ description: string,
156
+ options: CreateTaskOptions,
157
+ inbound: InboundChatFiles,
158
+ ): Promise<{ task: AgentTask; unattached: ChatFileFailure[] }> {
159
+ if (inbound.fetched.length === 0) {
160
+ const task = await createTaskWithSiblingAwareness(description, options, { origin: "webhook" });
161
+ return { task, unattached: inbound.failed };
162
+ }
163
+
164
+ const task = await createTaskWithSiblingAwareness(
165
+ description,
166
+ { ...options, status: "draft" },
167
+ { origin: "webhook" },
168
+ );
169
+ const unattached = [...inbound.failed];
170
+ const lease = setInterval(() => {
171
+ void refreshDraftTaskLease(task.id).catch((error) =>
172
+ console.warn(
173
+ `[chat-sdk] could not renew the draft lease of task ${task.id}: ${errorMessage(error)}`,
174
+ ),
175
+ );
176
+ }, DRAFT_LEASE_REFRESH_MS);
177
+ try {
178
+ unattached.push(...(await attachChatFilesToTask(task.id, inbound.fetched)));
179
+ } finally {
180
+ clearInterval(lease);
181
+ await promoteDraftTask(task.id);
182
+ }
183
+ return { task, unattached };
184
+ }
185
+
186
+ /**
187
+ * Store fetched chat files as attachments of a task. A file whose bytes the
188
+ * task already holds is reused. Returns the files that could not be stored.
189
+ */
190
+ export async function attachChatFilesToTask(
191
+ taskId: string,
192
+ fetched: FetchedChatFile[],
193
+ ): Promise<ChatFileFailure[]> {
194
+ const existing = await getTaskAttachments(taskId);
195
+ const bySha = new Set(existing.flatMap((a) => (a.sha256 ? [a.sha256] : [])));
196
+ const usedNames = new Set(existing.map((a) => a.name));
197
+ const provider = getFileStorageProvider();
198
+ const unattached: ChatFileFailure[] = [];
199
+
200
+ for (const { file, path, sizeBytes, sha256 } of fetched) {
201
+ if (bySha.has(sha256)) continue;
202
+ const name = usedNames.has(file.name) ? `${file.id}-${file.name}` : file.name;
203
+ usedNames.add(name);
204
+ const key = providerPath({ taskId, name: `chat-${file.id}-${file.name}` });
205
+ const scope = { taskId, name, key };
206
+ const contentType = file.mimeType ?? "application/octet-stream";
207
+ try {
208
+ const uploaded = await provider.upload(scope, Bun.file(path), {
209
+ contentType,
210
+ sizeBytes,
211
+ message: `Upload ${name} from ${file.adapter} for task ${taskId}`,
212
+ });
213
+ await recordTaskAttachmentUpload({
214
+ provider,
215
+ scope,
216
+ uploaded,
217
+ sizeBytes,
218
+ sha256,
219
+ contentType,
220
+ agentId: null,
221
+ intent: SHARED_FILE_INTENT,
222
+ description: `Shared on ${file.adapter} (message ${file.messageId})`,
223
+ });
224
+ bySha.add(sha256);
225
+ } catch (error) {
226
+ const reason = `could not be stored (${errorMessage(error)})`;
227
+ console.warn(`[chat-sdk] file ${file.id} for task ${taskId} ${reason}`);
228
+ unattached.push({ file, reason });
229
+ }
230
+ }
231
+ return unattached;
232
+ }
@@ -0,0 +1,36 @@
1
+ import { scrubSecrets } from "@/utils/secret-scrubber";
2
+
3
+ /**
4
+ * In-process `waitUntil` for Chat SDK webhooks. The SDK hands handler work to
5
+ * `waitUntil` and answers the platform immediately; this tracker keeps that
6
+ * work visible so shutdown can drain it before the DB closes.
7
+ */
8
+ const inFlight = new Set<Promise<unknown>>();
9
+
10
+ /** The `waitUntil` implementation: track, and scrub-log any rejection. */
11
+ export function trackBackground(task: Promise<unknown>): void {
12
+ const tracked = task.then(
13
+ () => undefined,
14
+ (error: unknown) => {
15
+ const message = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
16
+ console.error("[chat-sdk] Background handler failed:", scrubSecrets(message));
17
+ },
18
+ );
19
+ inFlight.add(tracked);
20
+ void tracked.finally(() => inFlight.delete(tracked));
21
+ }
22
+
23
+ /** Await in-flight work, bounded by `timeoutMs`. Returns true when fully drained. */
24
+ export async function drainBackground(timeoutMs: number): Promise<boolean> {
25
+ if (inFlight.size === 0) return true;
26
+ let timer: ReturnType<typeof setTimeout> | undefined;
27
+ const timeout = new Promise<false>((resolve) => {
28
+ timer = setTimeout(() => resolve(false), timeoutMs);
29
+ });
30
+ const drained = Promise.all([...inFlight]).then(() => true as const);
31
+ try {
32
+ return await Promise.race([drained, timeout]);
33
+ } finally {
34
+ clearTimeout(timer);
35
+ }
36
+ }