@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
@@ -181,6 +181,32 @@ export interface SlackRouteModify {
181
181
  target: { kind: "agent"; agentId: string } | { kind: "lead" } | { kind: "broadcast" };
182
182
  }
183
183
 
184
+ /** A Chat SDK message after its thread buffer flushed, before routing. */
185
+ export interface ChatRouteEvent {
186
+ /** Adapter name, such as `telegram` or `slack`. */
187
+ adapter: string;
188
+ channelId: string;
189
+ /** The Chat SDK thread id, prefixed with the adapter name. */
190
+ threadId: string;
191
+ /** The first buffered sender. */
192
+ userId: string;
193
+ userName?: string;
194
+ /** The buffered messages, joined. */
195
+ text: string;
196
+ botMentioned: boolean;
197
+ isDirectMessage: boolean;
198
+ /** Set when earlier swarm tasks exist in this thread. */
199
+ threadContext?: { adapter: string; threadId: string };
200
+ }
201
+
202
+ export interface ChatRouteModify {
203
+ target:
204
+ | { kind: "agent"; agentId: string }
205
+ | { kind: "lead" }
206
+ | { kind: "broadcast" }
207
+ | { kind: "workflow"; workflowId: string };
208
+ }
209
+
184
210
  export type HeartbeatClassification = "no-session" | "stale-session" | "fresh-stalled";
185
211
  export type HeartbeatAction = "supersede-resume" | "fail" | "record";
186
212
 
@@ -236,6 +262,19 @@ export interface SlackMessageEvent {
236
262
  taskId?: string;
237
263
  }
238
264
 
265
+ /** A Chat SDK message the swarm received or sent. */
266
+ export interface ChatMessageEvent {
267
+ adapter: string;
268
+ channelId: string;
269
+ threadId: string;
270
+ /** The sender, for an inbound message. */
271
+ userId?: string;
272
+ text: string;
273
+ direction: "inbound" | "outbound";
274
+ taskId?: string;
275
+ messageId?: string;
276
+ }
277
+
239
278
  /**
240
279
  * A person answered an approval request. Fires for workflow and standalone
241
280
  * requests. Timeouts and cancellations do not emit it today.
@@ -352,6 +391,11 @@ export interface SwarmEventMap {
352
391
  modify: SlackRouteModify;
353
392
  result: PreResult<SlackRouteModify>;
354
393
  };
394
+ "pre.chat.route": {
395
+ event: ChatRouteEvent;
396
+ modify: ChatRouteModify;
397
+ result: PreResult<ChatRouteModify>;
398
+ };
355
399
  "pre.heartbeat.remediate": {
356
400
  event: HeartbeatRemediateEvent;
357
401
  modify: HeartbeatRemediateModify;
@@ -369,6 +413,7 @@ export interface SwarmEventMap {
369
413
  "post.task.superseded": { event: TaskSupersededEvent; modify: never; result: void };
370
414
  "post.task.progress": { event: TaskProgressEvent; modify: never; result: void };
371
415
  "post.slack.message": { event: SlackMessageEvent; modify: never; result: void };
416
+ "post.chat.message": { event: ChatMessageEvent; modify: never; result: void };
372
417
  "post.approval.resolved": { event: ApprovalResolvedEvent; modify: never; result: void };
373
418
  "post.task.budgetRefused": { event: TaskBudgetRefusedEvent; modify: never; result: void };
374
419
  "post.email.received": { event: EmailReceivedEvent; modify: never; result: void };
@@ -171,6 +171,13 @@ export function runContext(event: keyof SwarmEventMap, payload: unknown): RunCon
171
171
  case "post.kapso.message":
172
172
  subject = `number ${stringField(payload, "phoneNumberId") ?? "?"}: message ${stringField(payload, "messageId") ?? "?"}`;
173
173
  break;
174
+ // Ids only: a chat message text is personal content.
175
+ case "pre.chat.route":
176
+ case "post.chat.message": {
177
+ const taskId = stringField(payload, "taskId");
178
+ subject = `${stringField(payload, "adapter") ?? "?"} thread ${stringField(payload, "threadId") ?? "?"}${taskId ? `: task ${taskId}` : ""}`;
179
+ break;
180
+ }
174
181
  case "post.vcs.event": {
175
182
  const number = field(payload, "number");
176
183
  subject = `${stringField(payload, "provider") ?? "?"} ${stringField(payload, "kind") ?? "?"}.${stringField(payload, "action") ?? "?"}: ${stringField(payload, "repo") ?? "?"}${typeof number === "number" ? `#${number}` : ""}`;
@@ -3,6 +3,7 @@ import { scrubSecrets } from "../utils/secret-scrubber";
3
3
  import { workflowEventBus } from "../workflows/event-bus";
4
4
  import type {
5
5
  ApprovalResolvedEvent,
6
+ ChatMessageEvent,
6
7
  KapsoMessageEvent,
7
8
  TaskBudgetRefusedEvent,
8
9
  VcsEvent,
@@ -258,6 +259,41 @@ const onKapsoMessage = observed("kapso.message.received", async (data) => {
258
259
  );
259
260
  });
260
261
 
262
+ /** `chat.message.received` and `chat.message.sent` carry the same fields; the name sets the direction. */
263
+ function chatMessageBridge(eventName: string, direction: ChatMessageEvent["direction"]) {
264
+ return observed(eventName, async (data) => {
265
+ const source = (data ?? {}) as Record<string, unknown>;
266
+ const adapter = text(source.adapter);
267
+ const channelId = text(source.channelId);
268
+ const threadId = text(source.threadId);
269
+ const messageText = text(source.text);
270
+ if (
271
+ adapter === undefined ||
272
+ channelId === undefined ||
273
+ threadId === undefined ||
274
+ messageText === undefined
275
+ ) {
276
+ return;
277
+ }
278
+ await dispatchPost(
279
+ "post.chat.message",
280
+ compact<ChatMessageEvent>({
281
+ adapter,
282
+ channelId,
283
+ threadId,
284
+ userId: text(source.userId),
285
+ text: messageText,
286
+ direction,
287
+ taskId: text(source.taskId),
288
+ messageId: text(source.messageId),
289
+ }),
290
+ );
291
+ });
292
+ }
293
+
294
+ const onChatMessageReceived = chatMessageBridge("chat.message.received", "inbound");
295
+ const onChatMessageSent = chatMessageBridge("chat.message.sent", "outbound");
296
+
261
297
  // The code-host events are named `<provider>.<kind>.<action>` and the last
262
298
  // segment is open-ended (`github.pull_request.<action>`, `gitlab.pipeline.<status>`),
263
299
  // so one prefix subscription per provider covers them all.
@@ -331,6 +367,8 @@ export function initExtensionPostBridge(): void {
331
367
  workflowEventBus.on("task.budget_refused", onTaskBudgetRefused);
332
368
  workflowEventBus.on("agentmail.message.received", onEmailReceived);
333
369
  workflowEventBus.on("kapso.message.received", onKapsoMessage);
370
+ workflowEventBus.on("chat.message.received", onChatMessageReceived);
371
+ workflowEventBus.on("chat.message.sent", onChatMessageSent);
334
372
  for (const prefix of VCS_PREFIXES) workflowEventBus.onPrefix(prefix, onVcsEvent);
335
373
  }
336
374
 
@@ -348,5 +386,7 @@ export function teardownExtensionPostBridge(): void {
348
386
  workflowEventBus.off("task.budget_refused", onTaskBudgetRefused);
349
387
  workflowEventBus.off("agentmail.message.received", onEmailReceived);
350
388
  workflowEventBus.off("kapso.message.received", onKapsoMessage);
389
+ workflowEventBus.off("chat.message.received", onChatMessageReceived);
390
+ workflowEventBus.off("chat.message.sent", onChatMessageSent);
351
391
  for (const prefix of VCS_PREFIXES) workflowEventBus.offPrefix(prefix, onVcsEvent);
352
392
  }
@@ -877,20 +877,27 @@ export async function handleComment(
877
877
  comment.body.slice(0, 100),
878
878
  );
879
879
 
880
+ const skip = (reason: string) => {
881
+ console.log(
882
+ `[GitHub] Skipped ${eventType}.${action} by ${sender.login} on ${repository.full_name} comment ${comment.id}: ${reason}`,
883
+ );
884
+ return { created: false };
885
+ };
886
+
880
887
  // Only handle created action
881
888
  if (action !== "created") {
882
- return { created: false };
889
+ return skip("action is not created");
883
890
  }
884
891
 
885
892
  // Check for @agent-swarm mention in comment
886
893
  if (!detectMention(comment.body)) {
887
- return { created: false };
894
+ return skip("no bot mention");
888
895
  }
889
896
 
890
897
  // Deduplicate
891
898
  const eventKey = `comment:${repository.full_name}:${comment.id}`;
892
899
  if (isDuplicate(eventKey)) {
893
- return { created: false };
900
+ return skip("duplicate delivery");
894
901
  }
895
902
 
896
903
  // Find lead agent (may be null - task will be unassigned)
@@ -932,7 +939,7 @@ export async function handleComment(
932
939
  );
933
940
 
934
941
  if (result.skipped) {
935
- return { created: false };
942
+ return skip("github.comment.mentioned template is skipped");
936
943
  }
937
944
 
938
945
  // Create task (assigned to lead if available, otherwise unassigned)
@@ -11,15 +11,15 @@ import { registerTemplate } from "../prompts/registry";
11
11
  // Heartbeat checklist
12
12
  // ============================================================================
13
13
 
14
+ // Standing orders and instructions come before the live status: they are
15
+ // usually identical from one tick to the next, so they can form a cacheable
16
+ // prefix of the task text, and the status that changes every tick goes last.
14
17
  registerTemplate({
15
18
  eventType: "heartbeat.checklist",
16
19
  header: "",
17
20
  defaultBody: `Task Type: Heartbeat Checklist
18
21
  Goal: Review system status and your standing orders, take action if needed.
19
22
 
20
- ## Current System Status [auto-generated]
21
- {{system_status}}
22
-
23
23
  ## Your Standing Orders (snapshot from HEARTBEAT.md)
24
24
  {{heartbeat_content}}
25
25
 
@@ -29,7 +29,7 @@ Goal: Review system status and your standing orders, take action if needed.
29
29
  1. **Read your HEARTBEAT.md** — run \`read /workspace/HEARTBEAT.md\` to get the latest standing orders (the snapshot above may be slightly stale).
30
30
  2. **Prune tracked HEARTBEAT items FIRST.** Active Blockers + Watch Items + Open Discussion combined must stay at ≤10 items; 20 is the absolute max only when genuinely super busy. This cap does not apply to evergreen Standing Orders / Governance / Playbook-index reference sections. Before adding anything, re-check every tracked item against its lift trigger and remove anything resolved, stale, or past its trigger date. Lift incident detail to memory; never keep it inline in HEARTBEAT.md. Every new tracked item must include an explicit lift trigger + date; if it has no removal condition, do not add it. If the tracked list is already at/over cap, prune before (or instead of) adding — the cap is binding.
31
31
  3. **Run seeded heartbeat data-gathering.** Use \`script-run\` with the global scripts \`schedule-health\` and \`task-failure-audit\`, both with args \`{ "days": 1, "publishPage": false }\`. They report failing schedules and failure clusters. Check any other standing order in HEARTBEAT.md yourself. Scripts can read Slack too (\`ctx.swarm.slack_read\` or the seeded \`slack-thread-flatten\` script), so a recurring Slack check can be scripted.
32
- 4. Review the system status above plus the script results for anything that needs attention (stalled tasks, idle workers with available work, anomalies).
32
+ 4. Review the system status below plus the script results for anything that needs attention (stalled tasks, idle workers with available work, anomalies).
33
33
  5. **CRITICAL — Reboot failure triage:** Failures with reason "worker session not found" or "worker session heartbeat is stale" indicate tasks that were INTERRUPTED by a server restart. These are NOT "expected auto-cleanup" — they represent work that was lost mid-execution. For each such failure:
34
34
  - Check what the task was (via \`get-task-details\` with the task ID from the failure)
35
35
  - If a retry task was auto-created (tagged \`reboot-retry\`), verify it is progressing
@@ -39,7 +39,10 @@ Goal: Review system status and your standing orders, take action if needed.
39
39
  7. If something needs attention — take action now using your available tools (create tasks, post to Slack, cancel stuck tasks, etc.).
40
40
  8. If everything looks healthy and no standing orders are actionable — complete this task with a brief "All clear" summary. You may NOT say "All clear" if reboot-related failures exist that haven't been triaged.
41
41
  9. Do NOT create another heartbeat-checklist task — the system handles scheduling.
42
- 10. **Update HEARTBEAT.md only after pruning.** Keep it current, but keep tracked items capped: remove resolved items, add only dated lift-triggered items, and lift detail to memory instead of growing the file.`,
42
+ 10. **Update HEARTBEAT.md only after pruning.** Keep it current, but keep tracked items capped: remove resolved items, add only dated lift-triggered items, and lift detail to memory instead of growing the file.
43
+
44
+ ## Current System Status [auto-generated]
45
+ {{system_status}}`,
43
46
  variables: [
44
47
  {
45
48
  name: "system_status",
package/src/hooks/hook.ts CHANGED
@@ -1336,32 +1336,6 @@ export async function handleHook(): Promise<void> {
1336
1336
  break;
1337
1337
 
1338
1338
  case "Stop":
1339
- // Clean up any artifact tunnels managed by PM2
1340
- try {
1341
- const pm2ListOutput = await Bun.$`pm2 jlist 2>/dev/null || echo "[]"`.text();
1342
- const pm2Processes = JSON.parse(pm2ListOutput.trim());
1343
- const artifactProcesses = (pm2Processes as { name?: string }[]).filter((p) =>
1344
- p.name?.startsWith("artifact-"),
1345
- );
1346
-
1347
- for (const proc of artifactProcesses) {
1348
- try {
1349
- await Bun.$`pm2 delete ${proc.name!}`.quiet();
1350
- } catch {
1351
- // Process might already be stopped
1352
- }
1353
- }
1354
- } catch {
1355
- // Non-fatal: PM2 might not be available
1356
- }
1357
-
1358
- // Save PM2 processes before shutdown (for container restart persistence)
1359
- try {
1360
- await Bun.$`pm2 save`.quiet();
1361
- } catch {
1362
- // PM2 not available or no processes - silently ignore
1363
- }
1364
-
1365
1339
  // Sync CLAUDE.md (then restore its backup), identity files, and setup script back to database
1366
1340
  if (agentInfo?.id) {
1367
1341
  try {
@@ -21,7 +21,6 @@ import "./config";
21
21
  import "./context";
22
22
  import "./core";
23
23
  import "./db-query";
24
- import "./ecosystem";
25
24
 
26
25
  import "./api-keys";
27
26
  import "./events";
@@ -18,10 +18,12 @@ import { fillOrgLogoFromWebsite } from "../be/org-profile";
18
18
  import { getUserGrant } from "../be/rbac-roles";
19
19
  import {
20
20
  internalConfigKeyError,
21
+ isApiOnlyConfigKey,
21
22
  isInternalConfigKey,
22
23
  isReservedConfigKey,
23
24
  reservedKeyError,
24
25
  validateConfigValue,
26
+ validateExportToEnv,
25
27
  } from "../be/swarm-config-guard";
26
28
  import { can, isRbacEnabled, type PermissionVerb } from "../rbac";
27
29
  import { SwarmConfigSchema } from "../types";
@@ -35,16 +37,9 @@ const MAX_ENV_PRESENCE_KEYS = 200;
35
37
  const SECRETS_FORCE_MASK_NOTE =
36
38
  " (secret values masked: this token cannot read secrets in this scope)";
37
39
 
38
- // Config keys the API server owns for its own use and must NEVER hand out over
39
- // HTTP — workers/entrypoints read config to build their env, and the agent-fs
40
- // bootstrap admin key would let a compromised worker administer the shared org.
41
- // The API materializes these into its own process.env at boot via
42
- // getInjectableGlobalConfigs, so no HTTP consumer legitimately needs them.
43
- const API_ONLY_CONFIG_KEYS = new Set(["API_AGENT_FS_API_KEY", "SLACK_SIGNING_SECRET"]);
44
-
45
40
  function stripApiOnlyKeys<T extends { key: string }>(configs: T[]): T[] {
46
41
  return configs.filter(
47
- (config) => !API_ONLY_CONFIG_KEYS.has(config.key) && !isInternalConfigKey(config.key),
42
+ (config) => !isApiOnlyConfigKey(config.key) && !isInternalConfigKey(config.key),
48
43
  );
49
44
  }
50
45
 
@@ -116,6 +111,28 @@ export async function ensureConfigAdmin(
116
111
  req: IncomingMessage,
117
112
  res: ServerResponse,
118
113
  verb: Extract<PermissionVerb, "config.write.any" | "config.delete.any">,
114
+ ): Promise<boolean> {
115
+ return ensureHttpPermission(
116
+ req,
117
+ res,
118
+ verb,
119
+ verb === "config.write.any"
120
+ ? "Writing swarm config requires the lead agent"
121
+ : "Deleting swarm config requires the lead agent",
122
+ );
123
+ }
124
+
125
+ /**
126
+ * The general form of `ensureConfigAdmin`: operators and users pass, guests
127
+ * get a plain 403, and an agent principal (session identity first, then the
128
+ * legacy X-Agent-ID header) must hold `verb`. On denial it writes a 403 with
129
+ * `forbiddenMessage` and returns false.
130
+ */
131
+ export async function ensureHttpPermission(
132
+ req: IncomingMessage,
133
+ res: ServerResponse,
134
+ verb: PermissionVerb,
135
+ forbiddenMessage: string,
119
136
  ): Promise<boolean> {
120
137
  const auth = getRequestAuth(req);
121
138
  if (auth?.kind === "operator" || auth?.kind === "user") return true;
@@ -132,13 +149,7 @@ export async function ensureConfigAdmin(
132
149
  source: "http",
133
150
  });
134
151
  if (!decision.allow) {
135
- jsonError(
136
- res,
137
- verb === "config.write.any"
138
- ? "Writing swarm config requires the lead agent"
139
- : "Deleting swarm config requires the lead agent",
140
- 403,
141
- );
152
+ jsonError(res, forbiddenMessage, 403);
142
153
  return false;
143
154
  }
144
155
  return true;
@@ -276,6 +287,12 @@ const upsertConfig = route({
276
287
  isSecret: z.boolean().optional(),
277
288
  envPath: z.string().nullish(),
278
289
  description: z.string().nullish(),
290
+ exportToEnv: z
291
+ .boolean()
292
+ .optional()
293
+ .describe(
294
+ "Export this row into agent process env (default true on create, false for a few server-side secrets; omitted on update keeps the stored flag). Rejected as true for keys only the API server reads.",
295
+ ),
279
296
  }),
280
297
  responses: {
281
298
  200: { description: "Config entry upserted", schema: SwarmConfigSchema },
@@ -442,7 +459,7 @@ export async function handleConfig(
442
459
  const parsed = await upsertConfig.parse(req, res, pathSegments, queryParams);
443
460
  if (!parsed) return true;
444
461
  if (!(await ensureConfigAdmin(req, res, "config.write.any"))) return true;
445
- const { scope, scopeId, key, value, isSecret, envPath, description } = parsed.body;
462
+ const { scope, scopeId, key, value, isSecret, envPath, description, exportToEnv } = parsed.body;
446
463
 
447
464
  if (scope === "global" && scopeId) {
448
465
  jsonError(res, "Global scope must not have scopeId", 400);
@@ -464,7 +481,8 @@ export async function handleConfig(
464
481
  return true;
465
482
  }
466
483
 
467
- const validationError = validateConfigValue(key, value);
484
+ const validationError =
485
+ validateConfigValue(key, value) ?? validateExportToEnv(key, exportToEnv);
468
486
  if (validationError) {
469
487
  jsonError(res, validationError, 400);
470
488
  return true;
@@ -480,6 +498,7 @@ export async function handleConfig(
480
498
  isSecret: isSecret || false,
481
499
  envPath: envPath || null,
482
500
  description: description || null,
501
+ exportToEnv,
483
502
  });
484
503
  // Auto-reload integrations when a global-scoped config changes so callers
485
504
  // (CLI, automation, dashboard) don't have to remember to hit /reload.
package/src/http/core.ts CHANGED
@@ -29,6 +29,7 @@ import { getUserGrant } from "../be/rbac-roles";
29
29
  import { syncTelemetryEnabled } from "../be/telemetry-enable";
30
30
  import { initGitHub, resetGitHub } from "../github";
31
31
  import { initGitLab, isGitLabEnabled, resetGitLab } from "../gitlab";
32
+ import { restartChatSdkIfConfigChanged } from "../integrations/chat-sdk/runtime";
32
33
  import { initJira, resetJira } from "../jira";
33
34
  import { initLinear, resetLinear } from "../linear";
34
35
  import { can, decideAdmission, isRbacEnabled } from "../rbac";
@@ -243,6 +244,9 @@ async function reloadGlobalConfigsAndIntegrationsInner(): Promise<ReloadConfigRe
243
244
 
244
245
  if (await startSlackApp()) integrations.push("slack");
245
246
 
247
+ // After Slack: the Chat SDK Slack clash check reads SLACK_DISABLE.
248
+ if (await restartChatSdkIfConfigChanged()) integrations.push("chat-sdk");
249
+
246
250
  return {
247
251
  configsLoaded: updated.length,
248
252
  keysUpdated: updated,
package/src/http/index.ts CHANGED
@@ -41,6 +41,7 @@ import {
41
41
  import { initGitHub } from "../github";
42
42
  import { initGitLab } from "../gitlab";
43
43
  import { stopHeartbeat } from "../heartbeat";
44
+ import { startChatSdk, stopChatSdk } from "../integrations/chat-sdk/runtime";
44
45
  import { initJira } from "../jira";
45
46
  import { initLinear } from "../linear";
46
47
  import {
@@ -81,7 +82,6 @@ import { handleConfig } from "./config";
81
82
  import { handleContext } from "./context";
82
83
  import { handleCore, loadGlobalConfigsIntoEnv } from "./core";
83
84
  import { handleDbQuery } from "./db-query";
84
- import { handleEcosystem } from "./ecosystem";
85
85
  import { handleEvents } from "./events";
86
86
  import { handleExtensions } from "./extensions";
87
87
  import { handleFavorites } from "./favorites";
@@ -345,7 +345,6 @@ const httpServer = createHttpServer(async (req, res) => {
345
345
  () => handleAgentRegister(req, res, pathSegments, myAgentId),
346
346
  () => handlePoll(req, res, pathSegments, queryParams, myAgentId),
347
347
  () => handleSessionData(req, res, pathSegments, queryParams, myAgentId),
348
- () => handleEcosystem(req, res, pathSegments, myAgentId),
349
348
  () => handleTrackers(req, res, pathSegments),
350
349
  () => handleWebhooks(req, res, pathSegments),
351
350
  () => handleAgentsRest(req, res, pathSegments, queryParams, myAgentId),
@@ -503,6 +502,9 @@ async function shutdown() {
503
502
  // Stop Slack bot
504
503
  await stopSlackApp();
505
504
 
505
+ // Stop Chat SDK adapters and drain their background handlers before the DB closes
506
+ await stopChatSdk();
507
+
506
508
  // Stop OAuth keepalive
507
509
  if (process.env.OAUTH_KEEPALIVE_DISABLE !== "true") {
508
510
  const { stopOAuthKeepalive } = await import("../oauth/keepalive");
@@ -762,6 +764,16 @@ httpServer
762
764
  console.error("[Slack] Failed to start, continuing boot without Slack:", err);
763
765
  }
764
766
 
767
+ // Chat SDK channel: never the boot path, so not awaited. Adapter init
768
+ // makes network calls (Slack's auth.test retries for up to ~30 min), and
769
+ // the scheduler, heartbeat and supervisor below must start regardless.
770
+ void startChatSdk().catch((err: unknown) => {
771
+ console.error(
772
+ "[chat-sdk] Failed to start, continuing without Chat SDK:",
773
+ scrubSecrets(err instanceof Error ? err.message : String(err)),
774
+ );
775
+ });
776
+
765
777
  // Independent of workers, scheduler targets, and heartbeat agent tasks.
766
778
  startQueueStallAlarm();
767
779
 
@@ -1,9 +1,17 @@
1
1
  import type { IncomingMessage, ServerResponse } from "node:http";
2
2
  import Anthropic from "@anthropic-ai/sdk";
3
3
  import { z } from "zod";
4
+ import {
5
+ ChatSdkActionError,
6
+ ChatSdkStatusSchema,
7
+ getChatSdkStatus,
8
+ registerTelegramWebhook,
9
+ } from "../integrations/chat-sdk/status";
4
10
  import { buildSlackManifest } from "../slack/manifest";
11
+ import { ensureHttpPermission } from "./config";
5
12
  import { resolveConfigValue, resolveMcpBaseUrl } from "./config-values";
6
13
  import { route } from "./route-def";
14
+ import { jsonError } from "./utils";
7
15
 
8
16
  // ─── Types ───────────────────────────────────────────────────────────────────
9
17
 
@@ -48,6 +56,20 @@ const McpUserConfigSchema = z.object({
48
56
 
49
57
  const SlackManifestSchema = z.record(z.string(), z.unknown());
50
58
 
59
+ const TelegramRegisterWebhookSchema = z.object({
60
+ webhookUrl: z.string(),
61
+ webhookInfo: z.looseObject({
62
+ url: z.string(),
63
+ has_custom_certificate: z.boolean().optional(),
64
+ pending_update_count: z.number().optional(),
65
+ last_error_date: z.number().optional(),
66
+ last_error_message: z.string().optional(),
67
+ max_connections: z.number().optional(),
68
+ ip_address: z.string().optional(),
69
+ allowed_updates: z.array(z.string()).optional(),
70
+ }),
71
+ });
72
+
51
73
  // ─── Route Definition ────────────────────────────────────────────────────────
52
74
 
53
75
  const claudeManagedTestRoute = route({
@@ -94,7 +116,38 @@ const slackManifestRoute = route({
94
116
  },
95
117
  });
96
118
 
97
- // ─── Helpers ─────────────────────────────────────────────────────────────────
119
+ const chatSdkStatusRoute = route({
120
+ method: "get",
121
+ path: "/api/integrations/chat/status",
122
+ pattern: ["api", "integrations", "chat", "status"],
123
+ summary:
124
+ "Chat SDK channel status: enabled flag, Slack clash, per-adapter health and webhook URL.",
125
+ tags: ["Integrations"],
126
+ responses: {
127
+ 200: {
128
+ description:
129
+ "Per-adapter status. `health` is `disabled` for an adapter that is not running. Never contains secret values.",
130
+ schema: ChatSdkStatusSchema,
131
+ },
132
+ },
133
+ });
134
+
135
+ const chatSdkTelegramRegisterWebhookRoute = route({
136
+ method: "post",
137
+ path: "/api/integrations/chat/telegram/register-webhook",
138
+ pattern: ["api", "integrations", "chat", "telegram", "register-webhook"],
139
+ summary:
140
+ "Register this swarm's public Telegram webhook URL and secret token with the Bot API (setWebhook), then return getWebhookInfo.",
141
+ tags: ["Integrations"],
142
+ body: z.object({}).optional(),
143
+ responses: {
144
+ 200: { description: "Webhook registered", schema: TelegramRegisterWebhookSchema },
145
+ 400: { description: "Telegram is not configured for webhook mode" },
146
+ 403: { description: "Only the lead agent or a dashboard user can register the webhook" },
147
+ 502: { description: "The Telegram Bot API rejected the call" },
148
+ },
149
+ rbac: { permission: "integration.chat.manage" },
150
+ });
98
151
 
99
152
  // ─── Public handler factory ──────────────────────────────────────────────────
100
153
 
@@ -122,6 +175,40 @@ export function createIntegrationsHandler(deps: TestConnectionDeps = {}) {
122
175
  return true;
123
176
  }
124
177
 
178
+ if (chatSdkStatusRoute.match(req.method, pathSegments)) {
179
+ chatSdkStatusRoute.respond(res, 200, await getChatSdkStatus());
180
+ return true;
181
+ }
182
+
183
+ if (chatSdkTelegramRegisterWebhookRoute.match(req.method, pathSegments)) {
184
+ const parsed = await chatSdkTelegramRegisterWebhookRoute.parse(
185
+ req,
186
+ res,
187
+ pathSegments,
188
+ queryParams,
189
+ );
190
+ if (!parsed) return true;
191
+ // Operators and dashboard users pass; agents need the verb (lead-only
192
+ // under the legacy policy).
193
+ if (
194
+ !(await ensureHttpPermission(
195
+ req,
196
+ res,
197
+ "integration.chat.manage",
198
+ "Registering the Telegram webhook requires the lead agent",
199
+ ))
200
+ ) {
201
+ return true;
202
+ }
203
+ try {
204
+ chatSdkTelegramRegisterWebhookRoute.respond(res, 200, await registerTelegramWebhook());
205
+ } catch (error) {
206
+ if (!(error instanceof ChatSdkActionError)) throw error;
207
+ jsonError(res, error.message, error.status);
208
+ }
209
+ return true;
210
+ }
211
+
125
212
  if (mcpUserConfigRoute.match(req.method, pathSegments)) {
126
213
  const mcpBaseUrl = await resolveMcpBaseUrl();
127
214
  mcpUserConfigRoute.respond(res, 200, { mcpBaseUrl, mcpUserUrl: `${mcpBaseUrl}/mcp-user` });
package/src/http/mcp.ts CHANGED
@@ -7,7 +7,12 @@ import { createServer } from "@/server";
7
7
  import { parseEnvFlag } from "@/utils/env-flag";
8
8
  import { getRequestAuth } from "@/utils/request-auth-context";
9
9
  import { resolveScriptsOnlyMode } from "@/utils/scripts-only-mode";
10
- import { parseTaskToolManifest, selectTaskTools } from "@/utils/task-tool-manifest";
10
+ import {
11
+ CHAT_TASK_ONLY_TOOLS,
12
+ isChatSdkTask,
13
+ parseTaskToolManifest,
14
+ selectTaskTools,
15
+ } from "@/utils/task-tool-manifest";
11
16
 
12
17
  export type McpTransportActivity = Record<string, number>;
13
18
  export type McpSessionAgents = Record<string, string>;
@@ -218,18 +223,22 @@ export async function handleMcp(
218
223
  true,
219
224
  );
220
225
  let preloadedTools: string[] = [];
226
+ const sourceTaskId = headerValue(req.headers["x-source-task-id"]);
227
+ const sourceTask = sourceTaskId ? await getTaskById(sourceTaskId) : undefined;
228
+ // A session bound to a non-chat task never sees chat-reply. Without the
229
+ // header the tool stays, and resolves the agent's current task itself.
230
+ const excludedTools =
231
+ sourceTask && !isChatSdkTask(sourceTask) ? [...CHAT_TASK_ONLY_TOOLS] : [];
221
232
  if (preloadEnabled) {
222
- const taskId = headerValue(req.headers["x-source-task-id"]);
223
- const task = taskId ? await getTaskById(taskId) : undefined;
224
233
  // Never select a manifest using another agent's task. Session-token
225
234
  // identity is also checked above, before any MCP session is created.
226
- if (task?.agentId === agentId) {
235
+ if (sourceTask?.agentId === agentId) {
227
236
  const manifestValue =
228
237
  configs.find((config) => config.key === "TASK_TOOL_MANIFESTS")?.value ??
229
238
  process.env.TASK_TOOL_MANIFESTS ??
230
239
  "{}";
231
240
  try {
232
- preloadedTools = selectTaskTools(parseTaskToolManifest(manifestValue), task);
241
+ preloadedTools = selectTaskTools(parseTaskToolManifest(manifestValue), sourceTask);
233
242
  } catch {
234
243
  // An invalid deployment value must not prevent tool discovery.
235
244
  // Avoid logging its contents, which may contain misfiled secrets.
@@ -239,6 +248,7 @@ export async function handleMcp(
239
248
  }
240
249
  const server = await createServer({
241
250
  preloadedTools,
251
+ excludedTools,
242
252
  // Agent row, then global row, then deployment env. A blank agent row
243
253
  // resolves to "unset" for that agent.
244
254
  enabledTools:
@@ -6,10 +6,8 @@ import { route } from "./route-def";
6
6
  import { jsonError } from "./utils";
7
7
 
8
8
  /**
9
- * `/@swarm/api/*` proxy. Cookie-gated equivalent of the artifact-sdk proxy
10
- * (src/artifact-sdk/server.ts:42-69), but lives on the MAIN API server and
11
- * authenticates via the page-session cookie instead of basic-auth + a per-
12
- * artifact tunnel.
9
+ * `/@swarm/api/*` proxy. Lives on the MAIN API server and authenticates via
10
+ * the page-session cookie.
13
11
  *
14
12
  * Flow:
15
13
  * 1. Browser hits `/@swarm/api/<rest>` from inside an iframe of `/p/:id`.
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * Scope of THIS module (step-3):
9
9
  * - `auth_mode === 'public'`: ungated. HTML responses inline-inject the
10
- * `BROWSER_SDK_JS` constant from `src/artifact-sdk/browser-sdk.ts` (reused
10
+ * `BROWSER_SDK_JS` constant from `src/pages/browser-sdk.ts` (reused
11
11
  * verbatim — no token-injection hook on the client). SVG responses serve
12
12
  * raw bytes with a restrictive CSP and no injected scripts. JSON responses
13
13
  * 302-redirect to the SPA `/pages/:id` route (the JSON renderer lives
@@ -23,9 +23,9 @@
23
23
  */
24
24
  import type { IncomingMessage, ServerResponse } from "node:http";
25
25
  import { z } from "zod";
26
- import { BROWSER_SDK_JS, SWARM_UI_JS } from "../artifact-sdk/browser-sdk";
27
- import { feedbackOverlayScripts, isPageFeedbackRequested } from "../artifact-sdk/feedback-overlay";
28
26
  import { getPage, incrementPageViewCount } from "../be/db";
27
+ import { BROWSER_SDK_JS, SWARM_UI_JS } from "../pages/browser-sdk";
28
+ import { feedbackOverlayScripts, isPageFeedbackRequested } from "../pages/feedback-overlay";
29
29
  import { type Page, PageSchema } from "../types";
30
30
  import { getAppUrl, getConfiguredAppUrls } from "../utils/constants";
31
31
  import { getNodeEnv } from "../utils/node-env";