@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,244 @@
1
+ import { z } from "zod";
2
+ import { getPublicMcpBaseUrl } from "@/utils/constants";
3
+ import { isEnvFlagEnabled } from "@/utils/env-flag";
4
+ import { scrubSecrets } from "@/utils/secret-scrubber";
5
+ import { resolveAdapterApiUrl } from "./adapters";
6
+ import { CHAT_SDK_ADAPTER_NAMES, type ChatSdkAdapterName, getChatSdkConfig } from "./config";
7
+ import { fetchDiscordGuilds } from "./discord-api";
8
+ import { getChatSdkHealth } from "./runtime";
9
+ import { isAdapterEntryEnabled } from "./schema";
10
+ import { callTelegramBotApi, TelegramBotApiError } from "./telegram-api";
11
+
12
+ /**
13
+ * Read-only Chat SDK status for the dashboard card, and the Telegram
14
+ * register-webhook action. Never returns secret values: configured state comes
15
+ * from adapter names and the non-secret `mode` field only.
16
+ */
17
+
18
+ export const ChatSdkStatusSchema = z.object({
19
+ enabled: z.boolean(),
20
+ slackClash: z.boolean(),
21
+ /** Config problem that stops every adapter from starting (bad JSON, missing `$config` row). */
22
+ error: z.string().optional(),
23
+ adapters: z.array(
24
+ z.object({
25
+ name: z.enum(CHAT_SDK_ADAPTER_NAMES),
26
+ configured: z.boolean(),
27
+ /** False when the entry carries `enabled: false`: configured and kept, not started. */
28
+ enabled: z.boolean(),
29
+ /** `incomplete`: present in `CHAT_SDK_ADAPTERS` with a missing field or an unresolvable `$config`; `error` names it. */
30
+ health: z.enum(["ok", "error", "disabled", "incomplete"]),
31
+ error: z.string().optional(),
32
+ webhookUrl: z.string(),
33
+ mode: z.string().optional(),
34
+ gateway: z
35
+ .object({
36
+ state: z.enum(["running", "backoff", "stopped"]),
37
+ since: z.string(),
38
+ restarts: z.number(),
39
+ })
40
+ .optional(),
41
+ /** Discord only, while the Gateway runs: the servers the bot was invited to. */
42
+ guilds: z.object({ count: z.number(), names: z.array(z.string()) }).optional(),
43
+ }),
44
+ ),
45
+ });
46
+
47
+ export type ChatSdkStatus = z.infer<typeof ChatSdkStatusSchema>;
48
+ export type ChatSdkAdapterStatus = ChatSdkStatus["adapters"][number];
49
+
50
+ /** Schema defaults, repeated here because the raw JSON may omit `mode`. */
51
+ const DEFAULT_MODES: Partial<Record<ChatSdkAdapterName, string>> = {
52
+ telegram: "webhook",
53
+ slack: "webhook",
54
+ };
55
+
56
+ const NOT_RUNNING =
57
+ "The adapter is starting, or the last configuration save is still applying. Refresh in a few seconds.";
58
+
59
+ const SLACK_CLASH_CAUSE =
60
+ "the Slack adapter conflicts with the built-in Slack integration (set SLACK_DISABLE=true)";
61
+
62
+ /** Bound on error text in the status payload. Adapter and parser errors can be long. */
63
+ const MAX_STATUS_ERROR_CHARS = 300;
64
+
65
+ function statusError(message: string): string {
66
+ return scrubSecrets(message).slice(0, MAX_STATUS_ERROR_CHARS);
67
+ }
68
+
69
+ /**
70
+ * The raw `CHAT_SDK_ADAPTERS` object, without validation or `$config`
71
+ * resolution. Lets the card show which adapters an operator configured even
72
+ * while the config fails to resolve. Empty on invalid JSON.
73
+ */
74
+ function readRawAdapters(): Partial<Record<ChatSdkAdapterName, Record<string, unknown>>> {
75
+ const raw = process.env.CHAT_SDK_ADAPTERS?.trim();
76
+ if (!raw) return {};
77
+ try {
78
+ const parsed = JSON.parse(raw) as unknown;
79
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return {};
80
+ const out: Partial<Record<ChatSdkAdapterName, Record<string, unknown>>> = {};
81
+ for (const name of CHAT_SDK_ADAPTER_NAMES) {
82
+ const value = (parsed as Record<string, unknown>)[name];
83
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
84
+ out[name] = value as Record<string, unknown>;
85
+ }
86
+ }
87
+ return out;
88
+ } catch {
89
+ return {};
90
+ }
91
+ }
92
+
93
+ export function chatSdkWebhookUrl(name: ChatSdkAdapterName): string {
94
+ return `${getPublicMcpBaseUrl()}/api/integrations/chat/${name}/webhook`;
95
+ }
96
+
97
+ /**
98
+ * The Discord bot's servers, only while its Gateway runs (a bot that is in no
99
+ * server is the usual "it does not answer" cause). The token comes from the
100
+ * resolved config, never from the status payload.
101
+ */
102
+ async function discordGuildsFor(
103
+ config: Awaited<ReturnType<typeof getChatSdkConfig>>,
104
+ health: ReturnType<typeof getChatSdkHealth>["discord"],
105
+ ): Promise<ChatSdkAdapterStatus["guilds"]> {
106
+ if (health?.status !== "ok" || health.gateway?.state !== "running") return undefined;
107
+ const raw = config.adapters.discord?.botToken;
108
+ const token = typeof raw === "function" ? await (raw as () => Promise<unknown>)() : raw;
109
+ if (typeof token !== "string" || token.length === 0) return undefined;
110
+ return fetchDiscordGuilds(token);
111
+ }
112
+
113
+ export async function getChatSdkStatus(): Promise<ChatSdkStatus> {
114
+ const config = await getChatSdkConfig();
115
+ const raw = readRawAdapters();
116
+ const runtimeHealth = getChatSdkHealth();
117
+ const slackClash = raw.slack !== undefined && !isEnvFlagEnabled("SLACK_DISABLE", false);
118
+ // A config error or a Slack clash stops `startChatSdk` before any adapter.
119
+ const blockedCause =
120
+ config.error ?? (config.enabled && slackClash ? SLACK_CLASH_CAUSE : undefined);
121
+ const discordGuilds = await discordGuildsFor(config, runtimeHealth.discord);
122
+ const adapters = CHAT_SDK_ADAPTER_NAMES.map((name): ChatSdkAdapterStatus => {
123
+ const configured = raw[name] !== undefined;
124
+ const enabled = isAdapterEntryEnabled(raw[name]);
125
+ const current = runtimeHealth[name];
126
+ const incomplete = config.incomplete?.[name];
127
+ const rawMode = raw[name]?.mode;
128
+ const mode =
129
+ typeof rawMode === "string" ? rawMode : configured ? DEFAULT_MODES[name] : undefined;
130
+ let health: ChatSdkAdapterStatus["health"] = "disabled";
131
+ let error: string | undefined;
132
+ if (current) {
133
+ health = current.status;
134
+ // A Discord Gateway in backoff keeps health `ok` (the webhook still
135
+ // works); its last warning says why the listener keeps failing.
136
+ const showWarning = current.status === "error" || current.gateway?.state === "backoff";
137
+ error = current.error ?? (showWarning ? current.lastWarning : undefined);
138
+ } else if (incomplete) {
139
+ // Saved with a missing field, or a `$config` row that does not exist.
140
+ health = "incomplete";
141
+ error = incomplete;
142
+ } else if (configured && !enabled) {
143
+ error = "Turned off on the card. The configuration is kept.";
144
+ } else if (config.enabled && configured) {
145
+ error = blockedCause ? `Not started: ${blockedCause}` : NOT_RUNNING;
146
+ }
147
+ return {
148
+ name,
149
+ configured,
150
+ enabled,
151
+ health,
152
+ ...(error ? { error: statusError(error) } : {}),
153
+ webhookUrl: chatSdkWebhookUrl(name),
154
+ ...(mode ? { mode } : {}),
155
+ ...(current?.gateway ? { gateway: current.gateway } : {}),
156
+ ...(name === "discord" && discordGuilds ? { guilds: discordGuilds } : {}),
157
+ };
158
+ });
159
+ return {
160
+ enabled: config.enabled,
161
+ slackClash,
162
+ ...(config.error ? { error: statusError(config.error) } : {}),
163
+ adapters,
164
+ };
165
+ }
166
+
167
+ /** `getWebhookInfo` result as Telegram returns it (subset; unknown keys pass through). */
168
+ export type TelegramWebhookInfo = Record<string, unknown> & { url: string };
169
+
170
+ export class ChatSdkActionError extends Error {
171
+ constructor(
172
+ message: string,
173
+ readonly status: 400 | 502,
174
+ ) {
175
+ super(message);
176
+ this.name = "ChatSdkActionError";
177
+ }
178
+ }
179
+
180
+ async function callBotApi(
181
+ baseUrl: string,
182
+ token: string,
183
+ method: string,
184
+ body: Record<string, unknown>,
185
+ ): Promise<unknown> {
186
+ try {
187
+ return await callTelegramBotApi(baseUrl, token, method, body);
188
+ } catch (error) {
189
+ if (error instanceof TelegramBotApiError) throw new ChatSdkActionError(error.message, 502);
190
+ throw error;
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Point the Telegram bot at this swarm's public webhook URL with the
196
+ * configured secret token, then return `getWebhookInfo`. The Bot API base is
197
+ * the one the adapter uses (`resolveAdapterApiUrl`), so tests and local QA hit
198
+ * the fake Bot API.
199
+ */
200
+ export async function registerTelegramWebhook(): Promise<{
201
+ webhookUrl: string;
202
+ webhookInfo: TelegramWebhookInfo;
203
+ }> {
204
+ const config = await getChatSdkConfig();
205
+ if (!config.enabled) {
206
+ throw new ChatSdkActionError(
207
+ "The Chat SDK channel is disabled (CHAT_SDK_ENABLED is not true). Enable it and restart the API first",
208
+ 400,
209
+ );
210
+ }
211
+ if (config.error) throw new ChatSdkActionError(statusError(config.error), 400);
212
+ const telegram = config.adapters.telegram;
213
+ if (!telegram) {
214
+ throw new ChatSdkActionError("Telegram is not configured in CHAT_SDK_ADAPTERS", 400);
215
+ }
216
+ if (telegram.mode !== "webhook") {
217
+ throw new ChatSdkActionError(
218
+ "Telegram runs in polling mode; a registered webhook would stop polling",
219
+ 400,
220
+ );
221
+ }
222
+ const secretToken = telegram.secretToken;
223
+ if (typeof secretToken !== "string" || secretToken.length === 0) {
224
+ throw new ChatSdkActionError("Telegram webhook mode needs a secretToken", 400);
225
+ }
226
+ const rawToken = telegram.botToken;
227
+ const token =
228
+ typeof rawToken === "function" ? await (rawToken as () => Promise<string>)() : rawToken;
229
+ if (typeof token !== "string" || token.length === 0) {
230
+ throw new ChatSdkActionError("Telegram botToken is empty", 400);
231
+ }
232
+ const baseUrl = resolveAdapterApiUrl("telegram");
233
+ const webhookUrl = chatSdkWebhookUrl("telegram");
234
+ await callBotApi(baseUrl, token, "setWebhook", { url: webhookUrl, secret_token: secretToken });
235
+ const info = await callBotApi(baseUrl, token, "getWebhookInfo", {});
236
+ if (
237
+ info === null ||
238
+ typeof info !== "object" ||
239
+ typeof (info as { url?: unknown }).url !== "string"
240
+ ) {
241
+ throw new ChatSdkActionError("Telegram getWebhookInfo returned an unexpected result", 502);
242
+ }
243
+ return { webhookUrl, webhookInfo: info as TelegramWebhookInfo };
244
+ }
@@ -0,0 +1,58 @@
1
+ import { getLatestLeadTaskByContextKey } from "@/be/db";
2
+ import { requestSteering, SteeringRequestError } from "@/be/steering";
3
+ import type { AgentTask, SteerResult } from "@/types";
4
+ import { scrubSecrets } from "@/utils/secret-scrubber";
5
+ import { isSteeringEnabled } from "@/utils/steering-enabled";
6
+
7
+ export interface ChatThreadSteeringResult {
8
+ task: AgentTask;
9
+ result: SteerResult;
10
+ }
11
+
12
+ /**
13
+ * Steer the lead's in-progress task on this thread with a new message. Same
14
+ * rule as the Slack default (`SLACK_THREAD_STEERING=lead`, queue mode) as
15
+ * constants: `isSteeringEnabled()` is the only switch. Returns null when the
16
+ * caller should buffer the message into a follow-up task instead.
17
+ */
18
+ export async function requestChatThreadSteering(args: {
19
+ contextKey: string;
20
+ message: string;
21
+ }): Promise<ChatThreadSteeringResult | null> {
22
+ if (!isSteeringEnabled()) return null;
23
+
24
+ const task = await getLatestLeadTaskByContextKey(args.contextKey, "chat");
25
+ if (!task || task.status !== "in_progress") return null;
26
+
27
+ try {
28
+ const result = await requestSteering({
29
+ taskId: task.id,
30
+ message: args.message,
31
+ mode: "queue",
32
+ onUnsupported: "degrade",
33
+ source: "chat",
34
+ createdByKind: "user",
35
+ });
36
+ return { task, result };
37
+ } catch (error) {
38
+ const detail = error instanceof Error ? error.message : String(error);
39
+ const what = error instanceof SteeringRequestError ? "was refused" : "failed";
40
+ console.warn(
41
+ scrubSecrets(
42
+ `[chat-sdk] Steering task ${task.id} ${what} (${detail}); creating a follow-up task`,
43
+ ),
44
+ );
45
+ return null;
46
+ }
47
+ }
48
+
49
+ /** Thread acknowledgement for the steering outcome. Constants, not prompts. */
50
+ export function formatChatSteeringAck(result: SteerResult): string {
51
+ if (result.outcome === "promoted") return "_Your message was queued as a follow-up task._";
52
+ if (result.degradedFrom) {
53
+ return "_Interrupt steering is unavailable for this task, so your message was queued._";
54
+ }
55
+ return result.outcome === "steered"
56
+ ? "_Your message was sent to the active task._"
57
+ : "_Your message was queued for the active task._";
58
+ }
@@ -0,0 +1,77 @@
1
+ import { readApiUrlOverrides } from "./config";
2
+
3
+ /**
4
+ * Bot Framework Connector hosts for Teams: public cloud, GCC, GCC High, DoD
5
+ * and the China cloud. The same set as `TRUSTED_CONNECTOR_HOSTS` in
6
+ * `@chat-adapter/teams` (its standalone connector API), minus the loopback
7
+ * Emulator exception. The adapter's own send path does not check the host.
8
+ */
9
+ const TRUSTED_TEAMS_SERVICE_HOSTS = new Set([
10
+ "smba.trafficmanager.net",
11
+ "smba.infra.gcc.teams.microsoft.com",
12
+ "smba.infra.gov.teams.microsoft.us",
13
+ "smba.infra.dod.teams.microsoft.us",
14
+ "msteams.botframework.azure.cn",
15
+ ]);
16
+
17
+ /** A Teams service URL that is not a Bot Framework host. Nothing was sent. */
18
+ export class UntrustedTeamsServiceUrlError extends Error {
19
+ constructor(detail: string) {
20
+ super(`Refusing to send the Teams bot token: ${detail}`);
21
+ this.name = "UntrustedTeamsServiceUrlError";
22
+ }
23
+ }
24
+
25
+ function originOf(value: string): string | undefined {
26
+ try {
27
+ return new URL(value).origin;
28
+ } catch {
29
+ return undefined;
30
+ }
31
+ }
32
+
33
+ /**
34
+ * The Teams adapter sends the bot token to the service URL of the target
35
+ * conversation. Trusted: https on a Bot Framework host, or the origin of the
36
+ * env-only `CHAT_SDK_API_URL_OVERRIDES.teams` (tests and emulators). Every
37
+ * other origin, loopback included, is refused.
38
+ */
39
+ export function isTrustedTeamsServiceUrl(serviceUrl: string): boolean {
40
+ let url: URL;
41
+ try {
42
+ url = new URL(serviceUrl);
43
+ } catch {
44
+ return false;
45
+ }
46
+ if (url.protocol === "https:" && TRUSTED_TEAMS_SERVICE_HOSTS.has(url.hostname.toLowerCase())) {
47
+ return true;
48
+ }
49
+ const override = readApiUrlOverrides().teams;
50
+ return override !== undefined && originOf(override) === url.origin;
51
+ }
52
+
53
+ /**
54
+ * Throw `UntrustedTeamsServiceUrlError` unless the Teams thread or channel id
55
+ * (`teams:<base64url conversation>:<base64url serviceUrl>[:<type>]`) carries a
56
+ * trusted service URL. Call it before every Teams call that takes an id from
57
+ * outside the adapter. Ids of other adapters pass.
58
+ */
59
+ export function assertTrustedChatTarget(id: string): void {
60
+ const parts = id.split(":");
61
+ if (parts[0] !== "teams") return;
62
+ const encoded = parts[2];
63
+ if ((parts.length !== 3 && parts.length !== 4) || !encoded) {
64
+ throw new UntrustedTeamsServiceUrlError("the Teams id is malformed");
65
+ }
66
+ assertTrustedTeamsServiceUrl(Buffer.from(encoded, "base64url").toString("utf-8"));
67
+ }
68
+
69
+ export function assertTrustedTeamsServiceUrl(serviceUrl: string): void {
70
+ if (isTrustedTeamsServiceUrl(serviceUrl)) return;
71
+ const origin = originOf(serviceUrl);
72
+ throw new UntrustedTeamsServiceUrlError(
73
+ origin && origin !== "null"
74
+ ? `${origin} is not a Microsoft Bot Framework service host`
75
+ : "the service URL is not a valid URL",
76
+ );
77
+ }
@@ -0,0 +1,56 @@
1
+ import { scrubSecrets } from "@/utils/secret-scrubber";
2
+ import { errorMessage } from "./logger";
3
+
4
+ const TELEGRAM_TIMEOUT_MS = 10_000;
5
+
6
+ /** A Bot API call that did not return `ok: true`. The message never carries the token. */
7
+ export class TelegramBotApiError extends Error {
8
+ constructor(message: string) {
9
+ super(message);
10
+ this.name = "TelegramBotApiError";
11
+ }
12
+ }
13
+
14
+ /**
15
+ * One Telegram Bot API call. Shared by the register-webhook action and the
16
+ * command registration at adapter start, so both hit the same base URL
17
+ * (`resolveAdapterApiUrl`) and scrub the token out of every error.
18
+ */
19
+ export async function callTelegramBotApi(
20
+ baseUrl: string,
21
+ token: string,
22
+ method: string,
23
+ body: Record<string, unknown>,
24
+ ): Promise<unknown> {
25
+ let response: Response;
26
+ try {
27
+ response = await fetch(`${baseUrl.replace(/\/+$/, "")}/bot${token}/${method}`, {
28
+ method: "POST",
29
+ headers: { "content-type": "application/json" },
30
+ body: JSON.stringify(body),
31
+ signal: AbortSignal.timeout(TELEGRAM_TIMEOUT_MS),
32
+ });
33
+ } catch (error) {
34
+ if (error instanceof Error && error.name === "TimeoutError") {
35
+ throw new TelegramBotApiError(
36
+ `Telegram ${method} failed: no response from the Bot API within ${TELEGRAM_TIMEOUT_MS / 1000} s`,
37
+ );
38
+ }
39
+ // Fetch errors can echo the URL, which carries the bot token.
40
+ throw new TelegramBotApiError(
41
+ `Telegram ${method} failed: ${scrubSecrets(errorMessage(error).replaceAll(token, "[REDACTED]"))}`,
42
+ );
43
+ }
44
+ const payload = (await response.json().catch(() => null)) as {
45
+ ok?: boolean;
46
+ result?: unknown;
47
+ description?: string;
48
+ } | null;
49
+ if (!response.ok || !payload?.ok) {
50
+ const description = payload?.description ?? `HTTP ${response.status}`;
51
+ throw new TelegramBotApiError(
52
+ `Telegram ${method} failed: ${scrubSecrets(description.replaceAll(token, "[REDACTED]"))}`,
53
+ );
54
+ }
55
+ return payload.result;
56
+ }
@@ -0,0 +1,16 @@
1
+ import { registerTemplate } from "@/prompts/registry";
2
+
3
+ registerTemplate({
4
+ eventType: "chat.message.thread_context",
5
+ header: "",
6
+ defaultBody: `<thread_context>
7
+ {{thread_messages}}
8
+ </thread_context>`,
9
+ variables: [
10
+ {
11
+ name: "thread_messages",
12
+ description: "Formatted Chat SDK thread messages (user: text pairs)",
13
+ },
14
+ ],
15
+ category: "system",
16
+ });
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Test-only seams for the Chat SDK adapters. Production code never calls
3
+ * `setChatSdkTestHooks`, so every hook is unset in a running API. The
4
+ * operator schema rejects `webhookVerifier`, and this module is the only
5
+ * other way to set one.
6
+ */
7
+ export interface ChatSdkTestHooks {
8
+ /**
9
+ * Replaces the Bot Framework JWT check of the Teams adapter. Tests cannot
10
+ * mint a Microsoft-signed token, so they inject `() => true`.
11
+ */
12
+ teamsWebhookVerifier?: (request: Request, body: string) => boolean | Promise<boolean>;
13
+ /**
14
+ * Bot token factory for the Teams adapter, so an outbound send needs no
15
+ * MSAL call to login.microsoftonline.com.
16
+ */
17
+ teamsToken?: () => string;
18
+ /**
19
+ * Skips the Discord Gateway loop. The loop logs in through `discord.js`
20
+ * against the real Discord API, which `apiUrl` does not redirect.
21
+ */
22
+ disableDiscordGateway?: boolean;
23
+ /** Typing keepalive interval, shortened so a test sees several renewals. */
24
+ typingIntervalMs?: number;
25
+ /** Typing keepalive hard cap, shortened so a test can reach it. */
26
+ typingMaxMs?: number;
27
+ }
28
+
29
+ let hooks: ChatSdkTestHooks = {};
30
+
31
+ export function setChatSdkTestHooks(next: ChatSdkTestHooks): void {
32
+ hooks = { ...next };
33
+ }
34
+
35
+ export function getChatSdkTestHooks(): Readonly<ChatSdkTestHooks> {
36
+ return hooks;
37
+ }
@@ -0,0 +1,100 @@
1
+ import type { Attachment } from "chat";
2
+ import { type AdditiveBuffer, createAdditiveBuffer } from "@/tasks/additive-buffer";
3
+ import { trackBackground } from "./background";
4
+ import { flushChatThread } from "./inbound";
5
+ import { stopTypingKeepalive } from "./typing";
6
+
7
+ /** One inbound message waiting in its thread's buffer. */
8
+ export interface ChatBufferedMessage {
9
+ messageId: string;
10
+ channelId: string;
11
+ /** Mention-stripped text. */
12
+ text: string;
13
+ userId: string;
14
+ userName?: string;
15
+ botMentioned: boolean;
16
+ isDirectMessage: boolean;
17
+ attachments: Attachment[];
18
+ }
19
+
20
+ const DEFAULT_BUFFER_MS = 10_000;
21
+
22
+ function configuredBufferMs(): number {
23
+ const value = Number(process.env.CHAT_SDK_BUFFER_MS);
24
+ return Number.isFinite(value) && value > 0 ? value : DEFAULT_BUFFER_MS;
25
+ }
26
+
27
+ let buffer: AdditiveBuffer<ChatBufferedMessage> | undefined;
28
+ let bufferMs = 0;
29
+ /** Set while shutdown flushes every waiting thread; those flushes keep `dependsOn`. */
30
+ let draining = false;
31
+ /**
32
+ * Tail of each thread's flush chain. The additive buffer clears a key before
33
+ * it awaits `onFlush`, so a second flush of one thread can start while the
34
+ * first still routes, downloads files or creates its task. Chaining keeps the
35
+ * task order (and `dependsOn`) of one thread; different threads run in parallel.
36
+ */
37
+ const flushChains = new Map<string, Promise<void>>();
38
+
39
+ /** Run `work` after every earlier flush of `threadId`. A failed flush does not block the next one. */
40
+ function runInThreadOrder(threadId: string, work: () => Promise<void>): Promise<void> {
41
+ const previous = flushChains.get(threadId) ?? Promise.resolve();
42
+ const run = previous.then(work);
43
+ const tail = run.catch(() => {});
44
+ flushChains.set(threadId, tail);
45
+ void tail.then(() => {
46
+ if (flushChains.get(threadId) === tail) flushChains.delete(threadId);
47
+ });
48
+ return run;
49
+ }
50
+
51
+ /**
52
+ * Keyed by the raw SDK `thread.id` (`<adapter>:<platform id>`, colons
53
+ * included); the key is never split. A changed `CHAT_SDK_BUFFER_MS` applies
54
+ * once no thread is waiting on the old timer.
55
+ */
56
+ function getBuffer(): AdditiveBuffer<ChatBufferedMessage> {
57
+ const ms = configuredBufferMs();
58
+ if (!buffer || (ms !== bufferMs && buffer.keys().length === 0)) {
59
+ bufferMs = ms;
60
+ buffer = createAdditiveBuffer<ChatBufferedMessage>({
61
+ timeoutMs: ms,
62
+ label: "chat",
63
+ onFlush: async (items, threadId, reason) => {
64
+ // Read now: `draining` can change while this flush waits in the chain.
65
+ const immediate = reason === "manual" && !draining;
66
+ // Tracked so shutdown drains timer-driven flushes before the DB closes.
67
+ const work = runInThreadOrder(threadId, () => flushChatThread(threadId, items, immediate));
68
+ trackBackground(work);
69
+ // A failed flush created no task, so no relay will stop the indicator.
70
+ await work.catch(() => stopTypingKeepalive(threadId));
71
+ },
72
+ });
73
+ }
74
+ return buffer;
75
+ }
76
+
77
+ /** Add a message to its thread's buffer and reset the debounce timer. */
78
+ export function bufferChatMessage(threadId: string, item: ChatBufferedMessage): void {
79
+ getBuffer().enqueue(threadId, item);
80
+ }
81
+
82
+ /** Flush now with no `dependsOn` (the `!now` command). */
83
+ export async function instantFlushChat(threadId: string): Promise<void> {
84
+ await getBuffer().instantFlush(threadId);
85
+ }
86
+
87
+ export function isChatThreadBuffered(threadId: string): boolean {
88
+ return buffer?.isBuffered(threadId) ?? false;
89
+ }
90
+
91
+ /** Shutdown: turn every waiting thread into its task instead of dropping it. */
92
+ export async function flushAllChatBuffers(): Promise<void> {
93
+ if (!buffer) return;
94
+ draining = true;
95
+ try {
96
+ for (const threadId of buffer.keys()) await buffer.instantFlush(threadId);
97
+ } finally {
98
+ draining = false;
99
+ }
100
+ }
@@ -0,0 +1,65 @@
1
+ import { errorMessage } from "./logger";
2
+ import { getChatInstance, isChatSdkStopping } from "./runtime";
3
+ import { assertTrustedChatTarget } from "./teams-origin";
4
+ import { getChatSdkTestHooks } from "./test-hooks";
5
+
6
+ /** Telegram shows one typing action for about 5 s, so renew it before that. */
7
+ const TYPING_INTERVAL_MS = 4_000;
8
+ /** Hard cap: a task that never relays must not show typing forever. */
9
+ const TYPING_MAX_MS = 15 * 60 * 1000;
10
+
11
+ const keepalives = new Map<string, ReturnType<typeof setInterval>>();
12
+
13
+ /**
14
+ * Show the typing indicator in `threadId` until `stopTypingKeepalive`, the
15
+ * hard cap, or the adapter stops. Idempotent per thread id. Adapters without
16
+ * `startTyping` and untrusted Teams service URLs are skipped.
17
+ */
18
+ export function startTypingKeepalive(adapter: string, threadId: string): void {
19
+ if (keepalives.has(threadId)) return;
20
+ // A shutdown flush creates tasks too; no indicator for a reply that comes after a restart.
21
+ if (isChatSdkStopping()) return;
22
+ // Typing sends the Teams bot token to the service URL in the thread id.
23
+ try {
24
+ assertTrustedChatTarget(threadId);
25
+ } catch {
26
+ return;
27
+ }
28
+ if (typeof getChatInstance(adapter)?.getAdapter(adapter)?.startTyping !== "function") return;
29
+
30
+ const hooks = getChatSdkTestHooks();
31
+ const maxMs = hooks.typingMaxMs ?? TYPING_MAX_MS;
32
+ const startedAt = Date.now();
33
+ let warned = false;
34
+ const sendTyping = async (): Promise<void> => {
35
+ const target = getChatInstance(adapter)?.getAdapter(adapter);
36
+ if (Date.now() - startedAt >= maxMs || typeof target?.startTyping !== "function") {
37
+ stopTypingKeepalive(threadId);
38
+ return;
39
+ }
40
+ try {
41
+ await target.startTyping(threadId);
42
+ } catch (error) {
43
+ if (warned) return;
44
+ warned = true;
45
+ console.warn(`[chat-sdk] Typing indicator failed for ${threadId}: ${errorMessage(error)}`);
46
+ }
47
+ };
48
+ const timer = setInterval(() => void sendTyping(), hooks.typingIntervalMs ?? TYPING_INTERVAL_MS);
49
+ timer.unref();
50
+ keepalives.set(threadId, timer);
51
+ void sendTyping();
52
+ }
53
+
54
+ export function stopTypingKeepalive(threadId: string): void {
55
+ const timer = keepalives.get(threadId);
56
+ if (!timer) return;
57
+ clearInterval(timer);
58
+ keepalives.delete(threadId);
59
+ }
60
+
61
+ /** Stop every keepalive. For `stopChatSdk`. */
62
+ export function stopAllTypingKeepalives(): void {
63
+ for (const timer of keepalives.values()) clearInterval(timer);
64
+ keepalives.clear();
65
+ }
@@ -1,4 +1,5 @@
1
- import { createPageVersion, getPage, getPageVersions } from "../be/db";
1
+ import { createPageVersion, getPage } from "../be/db";
2
+ import { getPageEditCounter } from "../be/db/page-versions";
2
3
  import type { PageSnapshot, PageVersion } from "../types";
3
4
 
4
5
  /**
@@ -8,7 +9,7 @@ import type { PageSnapshot, PageVersion } from "../types";
8
9
  * Mirrors `snapshotWorkflow` (src/workflows/version.ts:13-44).
9
10
  *
10
11
  * 1. Load current page state
11
- * 2. Get max version number for this page (page_versions ORDER BY version DESC)
12
+ * 2. Get max version number for this page (MAX(page_versions.version))
12
13
  * 3. Insert page_versions row with version+1 and the pre-update snapshot
13
14
  *
14
15
  * Throws on missing parent. Callers in HTTP handlers wrap this in a try/catch
@@ -24,9 +25,7 @@ export async function snapshotPage(
24
25
  throw new Error(`Page ${pageId} not found — cannot create snapshot`);
25
26
  }
26
27
 
27
- const existingVersions = await getPageVersions(pageId);
28
- const maxVersion = existingVersions.length > 0 ? existingVersions[0]!.version : 0;
29
- const nextVersion = maxVersion + 1;
28
+ const nextVersion = await getPageEditCounter(pageId);
30
29
 
31
30
  const snapshot: PageSnapshot = {
32
31
  title: page.title,