@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
@@ -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
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Per-adapter message length limits, in UTF-16 code units (what the platforms
3
+ * count). Outbound text longer than the limit is split into several messages.
4
+ */
5
+ const CHAT_MESSAGE_LIMITS: Record<string, number> = {
6
+ telegram: 4096,
7
+ discord: 2000,
8
+ // Slack messages use mrkdwn, not markdown. The SDK posts our markdown as
9
+ // `markdown_text` (Slack converts it, 12000 character cap). 4000 is Slack's
10
+ // guidance for one readable message and stays well under that cap.
11
+ slack: 4000,
12
+ teams: 20000,
13
+ };
14
+
15
+ /** Used for an adapter without an entry: the smallest known limit. */
16
+ const FALLBACK_LIMIT = 2000;
17
+
18
+ export function chatMessageLimit(adapter: string): number {
19
+ return CHAT_MESSAGE_LIMITS[adapter] ?? FALLBACK_LIMIT;
20
+ }
21
+
22
+ /**
23
+ * Split `text` into chunks of at most `limit` characters. A chunk ends at the
24
+ * last line break inside the window, else at the last space, else at the
25
+ * limit. Breaks in the first half of the window are ignored, so chunks never
26
+ * get very short.
27
+ */
28
+ export function splitChatMessage(text: string, limit: number): string[] {
29
+ if (limit <= 0) throw new Error("splitChatMessage: limit must be positive");
30
+ const chunks: string[] = [];
31
+ let rest = text;
32
+ while (rest.length > limit) {
33
+ const window = rest.slice(0, limit);
34
+ const minBreak = Math.floor(limit / 2);
35
+ let cut = window.lastIndexOf("\n");
36
+ if (cut < minBreak) cut = window.lastIndexOf(" ");
37
+ if (cut < minBreak) {
38
+ cut = limit;
39
+ // Never split a surrogate pair (emoji and other astral characters).
40
+ const code = rest.charCodeAt(cut - 1);
41
+ if (code >= 0xd800 && code <= 0xdbff) cut -= 1;
42
+ }
43
+ chunks.push(rest.slice(0, cut).trimEnd());
44
+ rest = rest.slice(cut).replace(/^[\n ]+/, "");
45
+ }
46
+ if (rest.length > 0 || chunks.length === 0) chunks.push(rest);
47
+ return chunks.filter((chunk, index) => chunk.length > 0 || index === 0);
48
+ }
@@ -0,0 +1,81 @@
1
+ import type { Logger } from "chat";
2
+ import { scrubObject, scrubSecrets } from "@/utils/secret-scrubber";
3
+
4
+ /** Called for every warn/error line, already scrubbed. Feeds adapter health. */
5
+ export type ChatLogIssueSink = (level: "warn" | "error", message: string) => void;
6
+
7
+ /** Bound on the issue text handed to the sink (adapter health `lastWarning`). */
8
+ const MAX_ISSUE_CHARS = 300;
9
+
10
+ function scrubArg(arg: unknown): unknown {
11
+ if (arg instanceof Error) return scrubSecrets(`${arg.name}: ${arg.message}`);
12
+ return scrubObject(arg);
13
+ }
14
+
15
+ function describeCause(value: unknown): string {
16
+ return value instanceof Error ? `${value.name}: ${value.message}` : String(value);
17
+ }
18
+
19
+ /**
20
+ * The cause of a warn/error line: an `Error` argument, or the `error` / `err`
21
+ * field of an object argument (adapters log `{ error: String(error) }`).
22
+ */
23
+ function causeOf(args: unknown[]): string | undefined {
24
+ for (const arg of args) {
25
+ if (arg instanceof Error) return describeCause(arg);
26
+ if (arg && typeof arg === "object") {
27
+ const record = arg as Record<string, unknown>;
28
+ const value = record.error ?? record.err;
29
+ if (value !== undefined && value !== null) return describeCause(value);
30
+ }
31
+ }
32
+ return undefined;
33
+ }
34
+
35
+ /**
36
+ * SDK `Logger` that scrubs every argument before it reaches the console. The
37
+ * Chat SDK has no egress hook like `installSlackEgressScrub`, so the logger is
38
+ * the scrub point for everything the SDK and its adapters print.
39
+ */
40
+ export function createScrubbingLogger(prefix: string, onIssue?: ChatLogIssueSink): Logger {
41
+ const tag = `[chat-sdk:${prefix}]`;
42
+ const emit = (level: "info" | "warn" | "error", message: string, args: unknown[]): void => {
43
+ const scrubbedMessage = scrubSecrets(message);
44
+ const scrubbedArgs = args.map(scrubArg);
45
+ if (onIssue && (level === "warn" || level === "error")) {
46
+ const cause = causeOf(args);
47
+ const issue = cause ? `${scrubbedMessage}: ${scrubSecrets(cause)}` : scrubbedMessage;
48
+ onIssue(level, issue.slice(0, MAX_ISSUE_CHARS));
49
+ }
50
+ console[level](tag, scrubbedMessage, ...scrubbedArgs);
51
+ };
52
+ return {
53
+ // Debug is dropped, matching the SDK's default ConsoleLogger("info") level.
54
+ debug: () => {},
55
+ info: (message, ...args) => emit("info", message, args),
56
+ warn: (message, ...args) => emit("warn", message, args),
57
+ error: (message, ...args) => emit("error", message, args),
58
+ child: (childPrefix) => createScrubbingLogger(`${prefix}:${childPrefix}`, onIssue),
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Drop `info` lines for which `keep(message)` is false. Warn and error lines
64
+ * always pass. Child loggers keep the filter.
65
+ */
66
+ export function withInfoFilter(logger: Logger, keep: (message: string) => boolean): Logger {
67
+ return {
68
+ debug: (message, ...args) => logger.debug(message, ...args),
69
+ info: (message, ...args) => {
70
+ if (keep(message)) logger.info(message, ...args);
71
+ },
72
+ warn: (message, ...args) => logger.warn(message, ...args),
73
+ error: (message, ...args) => logger.error(message, ...args),
74
+ child: (childPrefix) => withInfoFilter(logger.child(childPrefix), keep),
75
+ };
76
+ }
77
+
78
+ /** Error text for logs and tool results, scrubbed. */
79
+ export function errorMessage(err: unknown): string {
80
+ return scrubSecrets(err instanceof Error ? err.message : String(err));
81
+ }
@@ -0,0 +1,123 @@
1
+ import { scrubSecrets } from "@/utils/secret-scrubber";
2
+ import { workflowEventBus } from "@/workflows/event-bus";
3
+ import { chatMessageLimit, splitChatMessage } from "./limits";
4
+ import { getChatInstance } from "./runtime";
5
+ import { assertTrustedChatTarget } from "./teams-origin";
6
+
7
+ /** The adapter is not configured, failed to start, or is shutting down. */
8
+ export class ChatAdapterUnavailableError extends Error {
9
+ constructor(adapter: string) {
10
+ super(`Chat SDK adapter '${adapter}' is not running`);
11
+ this.name = "ChatAdapterUnavailableError";
12
+ }
13
+ }
14
+
15
+ export interface ChatSentEvent {
16
+ adapter: string;
17
+ channelId: string;
18
+ threadId: string;
19
+ text: string;
20
+ taskId?: string;
21
+ messageId?: string;
22
+ }
23
+
24
+ /**
25
+ * Publish an outbound message on the bus. The post-bridge turns it into the
26
+ * `post.chat.message` extension event with `direction: "outbound"`.
27
+ */
28
+ export function emitChatMessageSent(event: ChatSentEvent): void {
29
+ workflowEventBus.emit("chat.message.sent", { ...event, text: scrubSecrets(event.text) });
30
+ }
31
+
32
+ /** The adapter name is the first segment of an SDK thread or channel id. */
33
+ export function adapterOfChatId(id: string): string {
34
+ const index = id.indexOf(":");
35
+ return index === -1 ? id : id.slice(0, index);
36
+ }
37
+
38
+ export interface ChatPostResult {
39
+ adapter: string;
40
+ threadId: string;
41
+ channelId: string;
42
+ /** One id per chunk, in send order. */
43
+ messageIds: string[];
44
+ }
45
+
46
+ interface PostTarget {
47
+ post(message: { markdown: string }): Promise<{ id: string; threadId?: string }>;
48
+ }
49
+
50
+ async function postChunks(
51
+ adapter: string,
52
+ target: PostTarget,
53
+ text: string,
54
+ ): Promise<{ messageIds: string[]; threadId?: string }> {
55
+ const messageIds: string[] = [];
56
+ let threadId: string | undefined;
57
+ for (const chunk of splitChatMessage(text, chatMessageLimit(adapter))) {
58
+ const sent = await target.post({ markdown: chunk });
59
+ messageIds.push(sent.id);
60
+ threadId ??= sent.threadId;
61
+ }
62
+ return { messageIds, threadId };
63
+ }
64
+
65
+ /**
66
+ * Post markdown to a Chat SDK thread or channel. Scrubs the text, splits it by
67
+ * the adapter limit, and emits one `chat.message.sent` for the whole message
68
+ * once every chunk was accepted. Throws `ChatAdapterUnavailableError` when the
69
+ * adapter is not running, `UntrustedTeamsServiceUrlError` before any send
70
+ * when a Teams id points at a host that is not Bot Framework, and the adapter
71
+ * error when a send fails.
72
+ *
73
+ * Delivery is at-least-once: a chunk failure re-posts earlier chunks on retry,
74
+ * and a crash between the send and `markChatRelayDelivered` re-sends the
75
+ * message.
76
+ */
77
+ export async function postChatMessage(args: {
78
+ adapter: string;
79
+ threadId?: string;
80
+ channelId?: string;
81
+ text: string;
82
+ taskId?: string;
83
+ }): Promise<ChatPostResult> {
84
+ // Ids come from tools and stored context keys. Check the Teams origin
85
+ // before the adapter sends its token.
86
+ assertTrustedChatTarget(args.threadId ?? args.channelId ?? "");
87
+ const chat = getChatInstance(args.adapter);
88
+ if (!chat) throw new ChatAdapterUnavailableError(args.adapter);
89
+ const text = scrubSecrets(args.text);
90
+
91
+ let result: ChatPostResult;
92
+ if (args.threadId) {
93
+ const thread = chat.thread(args.threadId);
94
+ const sent = await postChunks(args.adapter, thread, text);
95
+ result = {
96
+ adapter: args.adapter,
97
+ threadId: thread.id,
98
+ channelId: thread.channelId,
99
+ messageIds: sent.messageIds,
100
+ };
101
+ } else if (args.channelId) {
102
+ const channel = chat.channel(args.channelId);
103
+ const sent = await postChunks(args.adapter, channel, text);
104
+ result = {
105
+ adapter: args.adapter,
106
+ threadId: sent.threadId ?? channel.id,
107
+ channelId: channel.id,
108
+ messageIds: sent.messageIds,
109
+ };
110
+ } else {
111
+ throw new Error("postChatMessage needs a threadId or a channelId");
112
+ }
113
+
114
+ emitChatMessageSent({
115
+ adapter: result.adapter,
116
+ channelId: result.channelId,
117
+ threadId: result.threadId,
118
+ text,
119
+ taskId: args.taskId,
120
+ messageId: result.messageIds.at(-1),
121
+ });
122
+ return result;
123
+ }