@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
@@ -1154,6 +1154,49 @@ describe("ui logs parser", () => {
1154
1154
  ]);
1155
1155
  });
1156
1156
 
1157
+ test("keeps a Claude sub-agent's parent_tool_use_id on its messages", () => {
1158
+ const messages = parseSessionLogs([
1159
+ log("spawn", "claude", 1, {
1160
+ type: "assistant",
1161
+ message: {
1162
+ role: "assistant",
1163
+ content: [{ type: "tool_use", id: "toolu_agent_1", name: "Agent", input: {} }],
1164
+ },
1165
+ }),
1166
+ log("child-call", "claude", 2, {
1167
+ type: "assistant",
1168
+ parent_tool_use_id: "toolu_agent_1",
1169
+ message: {
1170
+ role: "assistant",
1171
+ content: [
1172
+ { type: "thinking", thinking: "Look at the fixtures." },
1173
+ { type: "tool_use", id: "toolu_read_1", name: "Read", input: { file_path: "a.ts" } },
1174
+ ],
1175
+ },
1176
+ }),
1177
+ log("child-result", "claude", 3, {
1178
+ type: "user",
1179
+ parent_tool_use_id: "toolu_agent_1",
1180
+ message: {
1181
+ role: "user",
1182
+ content: [{ type: "tool_result", tool_use_id: "toolu_read_1", content: "ok" }],
1183
+ },
1184
+ }),
1185
+ log("main-text", "claude", 4, {
1186
+ type: "assistant",
1187
+ parent_tool_use_id: null,
1188
+ message: { role: "assistant", content: [{ type: "text", text: "Done." }] },
1189
+ }),
1190
+ ]);
1191
+
1192
+ expect(messages.map((m) => [m.id, m.parentToolUseId])).toEqual([
1193
+ ["spawn", undefined],
1194
+ ["child-call", "toolu_agent_1"],
1195
+ ["child-result", "toolu_agent_1"],
1196
+ ["main-text", undefined],
1197
+ ]);
1198
+ });
1199
+
1157
1200
  test("extracts a completed Claude foreground Agent run from its paired result", () => {
1158
1201
  const runs = extractSubagentRuns([
1159
1202
  log("spawn", "claude", 1, {
@@ -61,10 +61,14 @@ const configValue = async (key: string) =>
61
61
 
62
62
  describe("POST /api/used-by", () => {
63
63
  const originalFetch = globalThis.fetch;
64
+ const savedEnvWebsite = process.env.SWARM_ORG_WEBSITE;
65
+ const savedEnvName = process.env.SWARM_ORG_NAME;
64
66
  let proxied: Array<{ url: string; body: Record<string, unknown> }>;
65
67
  let proxyStatus: number;
66
68
 
67
69
  beforeAll(async () => {
70
+ delete process.env.SWARM_ORG_WEBSITE;
71
+ delete process.env.SWARM_ORG_NAME;
68
72
  await removeDbFiles();
69
73
  initDb(TEST_DB_PATH);
70
74
  await createAgent({ id: WORKER_ID, name: "Worker", isLead: false, status: "idle" });
@@ -75,6 +79,8 @@ describe("POST /api/used-by", () => {
75
79
  });
76
80
 
77
81
  afterAll(async () => {
82
+ if (savedEnvWebsite !== undefined) process.env.SWARM_ORG_WEBSITE = savedEnvWebsite;
83
+ if (savedEnvName !== undefined) process.env.SWARM_ORG_NAME = savedEnvName;
78
84
  globalThis.fetch = originalFetch;
79
85
  closeDb();
80
86
  await removeDbFiles();
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Vercel AI Gateway (`vercel/<vendor>/<model>`) on pi and opencode: the full
3
+ * chat catalog, and the one endpoint no env var or hand-written block can move.
4
+ */
5
+ import { afterEach, describe, expect, test } from "bun:test";
6
+ import { mkdtemp, rm } from "node:fs/promises";
7
+ import { tmpdir } from "node:os";
8
+ import { join } from "node:path";
9
+ import {
10
+ isAgentChatModel,
11
+ OPENAI_COMPATIBLE_PROVIDERS,
12
+ openAICompatibleBaseUrlVars,
13
+ providerBaseUrl,
14
+ providerForModel,
15
+ withRegistryCatalogSections,
16
+ } from "@desplega/model-catalog";
17
+ import { buildCatalogEntries } from "../be/model-catalog-store";
18
+ import { isKnownCatalogModel } from "../be/model-validation";
19
+ import { buildModelsCatalog } from "../be/models-catalog";
20
+ import { loadModelsDevCache, type ModelsDevCache } from "../be/modelsdev-cache";
21
+ import snapshot from "../be/modelsdev-cache.json";
22
+ import { applyProviderKeySplice, type OpencodeConfig } from "../providers/opencode-adapter";
23
+ import { customProviderModelsBlock, writeSessionModelsJson } from "../providers/pi-mono-adapter";
24
+
25
+ const VERCEL = OPENAI_COMPATIBLE_PROVIDERS.vercel;
26
+ const ENDPOINT = "https://ai-gateway.vercel.sh/v1";
27
+ const WRONG = "https://ai-gateway.vercel.sh";
28
+ const raw = snapshot as unknown as ModelsDevCache;
29
+ const rawVercel = (raw.vercel?.models ?? {}) as Record<
30
+ string,
31
+ Parameters<typeof isAgentChatModel>[0]
32
+ >;
33
+ const chatIds = Object.keys(rawVercel).filter((id) => isAgentChatModel(rawVercel[id] ?? {}));
34
+
35
+ describe("Vercel AI Gateway catalog", () => {
36
+ test("the snapshot carries non-chat rows, so the filter is exercised", () => {
37
+ expect(rawVercel["voyage/voyage-code-3"]).toBeDefined();
38
+ expect(chatIds.length).toBeGreaterThan(200);
39
+ expect(chatIds.length).toBeLessThan(Object.keys(rawVercel).length);
40
+ });
41
+
42
+ test("pi lists every AI Gateway chat model, and no embedding or video model", () => {
43
+ const block = customProviderModelsBlock(VERCEL);
44
+ const ids = block.models.map((m) => m.id as string);
45
+ expect(ids.sort()).toEqual([...chatIds].sort());
46
+ expect(ids).toContain("zai/glm-5.3");
47
+ expect(ids).toContain("moonshotai/kimi-k3");
48
+ expect(ids).toContain("anthropic/claude-sonnet-4.6");
49
+ expect(ids).not.toContain("voyage/voyage-code-3");
50
+ expect(ids).not.toContain("klingai/kling-v3.0-t2v");
51
+ const glm = block.models.find((m) => m.id === "zai/glm-5.3");
52
+ expect(glm).toMatchObject({ contextWindow: 1000000, maxTokens: 1000000, reasoning: true });
53
+ expect(block).toMatchObject({
54
+ baseUrl: ENDPOINT,
55
+ api: "openai-completions",
56
+ apiKey: "$AI_GATEWAY_API_KEY",
57
+ });
58
+ });
59
+
60
+ test("picker, live and validation catalogs drop non-chat Vercel rows", () => {
61
+ const merged = withRegistryCatalogSections(raw);
62
+ expect(Object.keys(merged.vercel?.models ?? {}).sort()).toEqual([...chatIds].sort());
63
+ // OpenRouter is not a registry provider and keeps its full section.
64
+ expect(Object.keys(merged.openrouter?.models ?? {}).length).toBe(
65
+ Object.keys(raw.openrouter?.models ?? {}).length,
66
+ );
67
+
68
+ const live = buildCatalogEntries(raw, 0).filter((e) => e.provider === "vercel");
69
+ expect(live.map((e) => e.modelId).sort()).toEqual([...chatIds].sort());
70
+
71
+ const cache = loadModelsDevCache();
72
+ if (!cache) throw new Error("snapshot missing");
73
+ const catalog = buildModelsCatalog(cache) as Parameters<typeof isKnownCatalogModel>[1];
74
+ expect(isKnownCatalogModel("vercel/zai/glm-5.3", catalog)).toBe(true);
75
+ expect(isKnownCatalogModel("vercel/anthropic/claude-sonnet-4.6", catalog)).toBe(true);
76
+ expect(isKnownCatalogModel("vercel/voyage/voyage-code-3", catalog)).toBe(false);
77
+ expect(providerForModel("vercel/moonshotai/kimi-k3")?.id).toBe("vercel");
78
+ });
79
+ });
80
+
81
+ describe("Vercel AI Gateway endpoint is fixed", () => {
82
+ const dirs: string[] = [];
83
+ afterEach(async () => {
84
+ await Promise.all(dirs.splice(0).map((d) => rm(d, { recursive: true, force: true })));
85
+ });
86
+
87
+ test("VERCEL_BASE_URL is never read or forwarded", () => {
88
+ expect(VERCEL.baseUrl).toBe(ENDPOINT);
89
+ expect(providerBaseUrl(VERCEL, { VERCEL_BASE_URL: WRONG })).toBe(ENDPOINT);
90
+ expect(openAICompatibleBaseUrlVars()).not.toContain("VERCEL_BASE_URL");
91
+ expect(openAICompatibleBaseUrlVars()).toContain("NAN_BASE_URL");
92
+ });
93
+
94
+ test("opencode gets the key and no baseURL, even over a configured one", () => {
95
+ const key = crypto.randomUUID();
96
+ const config: OpencodeConfig = {
97
+ model: "vercel/zai/glm-5.3",
98
+ provider: { vercel: { options: { baseURL: WRONG } } },
99
+ };
100
+ const picked = applyProviderKeySplice(config, config.model, {
101
+ AI_GATEWAY_API_KEY: key,
102
+ VERCEL_BASE_URL: WRONG,
103
+ });
104
+ expect(picked?.key).toBe(key);
105
+ expect(config.provider?.vercel).toEqual({ env: [], options: { apiKey: key } });
106
+ });
107
+
108
+ test("pi gets the Chat Completions endpoint over VERCEL_BASE_URL and a hand-written block", async () => {
109
+ const agentDir = await mkdtemp(join(tmpdir(), "pi-vercel-"));
110
+ dirs.push(agentDir);
111
+ await Bun.write(
112
+ join(agentDir, "models.json"),
113
+ JSON.stringify({
114
+ providers: { vercel: { baseUrl: WRONG, api: "anthropic-messages", headers: { a: "b" } } },
115
+ }),
116
+ );
117
+ const env = { AI_GATEWAY_API_KEY: "k", VERCEL_BASE_URL: WRONG };
118
+ const session = await writeSessionModelsJson(agentDir, env, "vercel/vendor/brand-new-model");
119
+ try {
120
+ expect(session.modelsPath).toBeDefined();
121
+ const written = await Bun.file(session.modelsPath as string).json();
122
+ const block = written.providers.vercel;
123
+ expect(block.baseUrl).toBe(ENDPOINT);
124
+ expect(block.api).toBe("openai-completions");
125
+ expect(block.headers).toEqual({ a: "b" });
126
+ const ids = (block.models as { id: string }[]).map((m) => m.id);
127
+ expect(ids).toContain("zai/glm-5.3");
128
+ // An id the catalog does not know yet still resolves (bare entry).
129
+ expect(ids).toContain("vendor/brand-new-model");
130
+ } finally {
131
+ await session.dispose();
132
+ }
133
+ });
134
+
135
+ test("pi without a shared block also lands on the endpoint", async () => {
136
+ const agentDir = await mkdtemp(join(tmpdir(), "pi-vercel-"));
137
+ dirs.push(agentDir);
138
+ const session = await writeSessionModelsJson(agentDir, {
139
+ AI_GATEWAY_API_KEY: "k",
140
+ VERCEL_BASE_URL: WRONG,
141
+ });
142
+ try {
143
+ const written = await Bun.file(session.modelsPath as string).json();
144
+ expect(written.providers.vercel.baseUrl).toBe(ENDPOINT);
145
+ } finally {
146
+ await session.dispose();
147
+ }
148
+ });
149
+ });
@@ -9,6 +9,7 @@ import {
9
9
  getWorkflowRun,
10
10
  getWorkflowRunStepsByRunId,
11
11
  initDb,
12
+ upsertSwarmConfig,
12
13
  } from "../be/db";
13
14
  import { upsertScriptConnection } from "../be/script-connections";
14
15
  import { getScript, upsertScriptByName } from "../be/scripts/db";
@@ -31,6 +32,7 @@ import {
31
32
  } from "../workflows/executors/swarm-script";
32
33
  import { interpolate } from "../workflows/template";
33
34
  import { SKIP_SANDBOX_SPAWN_TESTS } from "./sandbox-spawn-test-helpers";
35
+ import { CHILD_PROCESS_TEST_BUDGET_MS } from "./test-proc";
34
36
 
35
37
  const skip = test.skipIf(SKIP_SANDBOX_SPAWN_TESTS);
36
38
 
@@ -370,6 +372,42 @@ describe("SwarmScriptExecutor", () => {
370
372
  expect(result.output?.result).toEqual({ apiKeys: ["workflowVendor"] });
371
373
  });
372
374
 
375
+ skip(
376
+ "swarm-script node reads swarm config through ctx.swarm.config.get",
377
+ async () => {
378
+ await upsertSwarmConfig({ scope: "global", key: "WORKFLOW_CFG_PLAIN", value: "node-value" });
379
+ await saveScript(
380
+ "ctx-config-get",
381
+ `export default async (_args, ctx) => {
382
+ const value = ctx.swarm.config.get("WORKFLOW_CFG_PLAIN");
383
+ return {
384
+ value: value === undefined ? null : ctx.stdlib.Redacted.value(value),
385
+ missing: ctx.swarm.config.get("WORKFLOW_CFG_NOT_SET") ?? "fallback",
386
+ };
387
+ };`,
388
+ );
389
+
390
+ const executor = new SwarmScriptExecutor(deps);
391
+ const wf = await makeWorkflow({ nodes: [] });
392
+ const result = await executor.run({
393
+ config: { scriptName: "ctx-config-get" },
394
+ context: {},
395
+ meta: {
396
+ runId: crypto.randomUUID(),
397
+ stepId: crypto.randomUUID(),
398
+ nodeId: "script",
399
+ workflowId: wf.id,
400
+ dryRun: false,
401
+ },
402
+ });
403
+ await getDbClient().run("DELETE FROM swarm_config WHERE key = 'WORKFLOW_CFG_PLAIN'");
404
+
405
+ expect(result.status).toBe("success");
406
+ expect(result.output?.result).toEqual({ value: "node-value", missing: "fallback" });
407
+ },
408
+ CHILD_PROCESS_TEST_BUDGET_MS,
409
+ );
410
+
373
411
  skip("pinHash correctly resolves to a historic script_versions row", async () => {
374
412
  const first = await saveScript("versioned", `export default async () => ({ version: "old" });`);
375
413
  await saveScript("versioned", `export default async () => ({ version: "new" });`);
@@ -154,12 +154,14 @@ describe("matchesFilter — sandbox penetration (must all return false, never th
154
154
  });
155
155
 
156
156
  test("DoS: pathological regex terminates within timeout", async () => {
157
- const filter = "(p) => /^(a+)+$/.test('a'.repeat(30) + 'X')";
157
+ // Still exponential backtracking, but 24 characters finish in ~0.1 s. The
158
+ // 50 ms timer cannot pre-empt a synchronous regex, so the elapsed time is
159
+ // the engine's own: 30 characters took 2058 ms on a loaded CI runner and
160
+ // failed the bound below without the matcher doing anything wrong.
161
+ const filter = "(p) => /^(a+)+$/.test('a'.repeat(24) + 'X')";
158
162
  const t0 = Date.now();
159
163
  const result = await matchesFilter({}, filter);
160
164
  const elapsed = Date.now() - t0;
161
- // The regex would eventually return false on its own, but if it stalls
162
- // the timeout catches it.
163
165
  expect(typeof result).toBe("boolean");
164
166
  expect(elapsed).toBeLessThan(2000);
165
167
  });
@@ -0,0 +1,41 @@
1
+ import { getAgentById, getAgentCurrentTask, getTaskById } from "@/be/db";
2
+ import { can } from "@/rbac";
3
+ import { chatThreadOfContextKey } from "@/tasks/context-key";
4
+ import type { RequestInfo } from "@/tools/utils";
5
+
6
+ /** Chat SDK verbs; the legacy policy grants them to the lead only. */
7
+ export type ChatVerb = "integration.chat.post" | "integration.chat.read" | "integration.chat.dm";
8
+
9
+ /**
10
+ * The Chat SDK thread of the task the caller works on: the task named by the
11
+ * X-Source-Task-ID header, else the agent's current task. Null when the caller
12
+ * neither owns nor created that task, or when the task did not come from a
13
+ * chat thread.
14
+ */
15
+ export async function callerChatThread(
16
+ requestInfo: RequestInfo,
17
+ ): Promise<{ adapter: string; threadId: string } | null> {
18
+ if (!requestInfo.agentId) return null;
19
+ const task = requestInfo.sourceTaskId
20
+ ? await getTaskById(requestInfo.sourceTaskId)
21
+ : await getAgentCurrentTask(requestInfo.agentId);
22
+ if (!task) return null;
23
+ // Creator is safe: a non-lead agent can only create a chat-bound task under a parent it owns or created.
24
+ if (task.agentId !== requestInfo.agentId && task.creatorAgentId !== requestInfo.agentId) {
25
+ return null;
26
+ }
27
+ return chatThreadOfContextKey(task.contextKey);
28
+ }
29
+
30
+ /** RBAC decision for a chat verb, with the caller's lead flag read from the database. */
31
+ export async function canUseChatVerb(requestInfo: RequestInfo, verb: ChatVerb): Promise<boolean> {
32
+ if (!requestInfo.agentId) return false;
33
+ const agent = await getAgentById(requestInfo.agentId);
34
+ if (!agent) return false;
35
+ return can({
36
+ principal: { kind: "agent", agentId: agent.id, isLead: agent.isLead },
37
+ verb,
38
+ resource: { kind: "none" },
39
+ source: "mcp",
40
+ }).allow;
41
+ }
@@ -0,0 +1,59 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import * as z from "zod";
3
+ import { CHAT_SDK_ADAPTER_NAMES } from "@/integrations/chat-sdk/config";
4
+ import { errorMessage } from "@/integrations/chat-sdk/logger";
5
+ import { getChatInstance } from "@/integrations/chat-sdk/runtime";
6
+ import { assertTrustedTeamsServiceUrl } from "@/integrations/chat-sdk/teams-origin";
7
+ import { canUseChatVerb } from "@/tools/chat-access";
8
+ import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
9
+
10
+ export const registerChatOpenDmTool = (server: McpServer) => {
11
+ createToolRegistrar(server)(
12
+ "chat-open-dm",
13
+ {
14
+ title: "Open a direct message on a chat platform",
15
+ description:
16
+ "Lead only. Open (or find) the direct-message thread with a user on a Chat SDK adapter and return its threadId. Post to it with chat-post. The userId is the platform user id from a chat message (Telegram numeric id, Slack U..., Discord snowflake, Teams 29:...). Telegram bots can message only users who started a chat with the bot.",
17
+ annotations: { openWorldHint: true },
18
+ inputSchema: z.object({
19
+ adapter: z.enum(CHAT_SDK_ADAPTER_NAMES).describe("Chat SDK adapter name."),
20
+ userId: z.string().min(1).describe("Platform user id."),
21
+ }),
22
+ outputSchema: swarmToolOutputSchema({
23
+ yourAgentId: z.string().optional(),
24
+ adapter: z.string().optional(),
25
+ threadId: z.string().optional(),
26
+ }),
27
+ },
28
+ async ({ adapter, userId }, requestInfo) => {
29
+ const data = { yourAgentId: requestInfo.agentId };
30
+ if (!(await canUseChatVerb(requestInfo, "integration.chat.dm"))) {
31
+ return toolErr("Permission denied. Only the lead can open a direct message.", { data });
32
+ }
33
+ const chat = getChatInstance(adapter);
34
+ if (!chat) return toolErr(`Chat SDK adapter '${adapter}' is not running.`, { data });
35
+ // Call the adapter directly: Chat.openDM guesses the adapter from the
36
+ // user id format, and numeric ids are ambiguous.
37
+ const platform = chat.getAdapter(adapter);
38
+ if (!platform?.openDM) {
39
+ return toolErr(`The ${adapter} adapter does not support direct messages.`, { data });
40
+ }
41
+ try {
42
+ if (adapter === "teams") {
43
+ // Teams opens the DM at the service URL cached from the user's last
44
+ // activity (adapter state key). The webhook route refuses untrusted
45
+ // ones; check again before the bot token goes out.
46
+ const cached = await chat.getState().get<string>(`teams:serviceUrl:${userId}`);
47
+ if (cached) assertTrustedTeamsServiceUrl(cached);
48
+ }
49
+ const threadId = await platform.openDM(userId);
50
+ return toolOk(`Direct-message thread with ${userId} on ${adapter}: ${threadId}.`, {
51
+ data: { ...data, adapter, threadId },
52
+ });
53
+ } catch (error) {
54
+ const message = errorMessage(error);
55
+ return toolErr(`Could not open a direct message: ${message}`, { data });
56
+ }
57
+ },
58
+ );
59
+ };
@@ -0,0 +1,74 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import * as z from "zod";
3
+ import { CHAT_SDK_ADAPTER_NAMES } from "@/integrations/chat-sdk/config";
4
+ import { errorMessage } from "@/integrations/chat-sdk/logger";
5
+ import { adapterOfChatId, postChatMessage } from "@/integrations/chat-sdk/outbound";
6
+ import { canUseChatVerb } from "@/tools/chat-access";
7
+ import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
8
+
9
+ export const registerChatPostTool = (server: McpServer) => {
10
+ createToolRegistrar(server)(
11
+ "chat-post",
12
+ {
13
+ title: "Post to a chat thread or channel",
14
+ description:
15
+ "Lead only. Post a message to any Chat SDK thread or channel (Telegram, Slack, Discord, Teams), outside the thread of your current task. Give threadId to reply in a thread, or channelId to start a new message in a channel. Ids carry the adapter prefix, such as telegram:12345. To answer the user of your current chat task, use chat-reply instead.",
16
+ annotations: { openWorldHint: true },
17
+ inputSchema: z.object({
18
+ adapter: z.enum(CHAT_SDK_ADAPTER_NAMES).describe("Chat SDK adapter name."),
19
+ threadId: z
20
+ .string()
21
+ .min(1)
22
+ .optional()
23
+ .describe("Full SDK thread id, such as telegram:12345 or slack:C123:1700000000.000100."),
24
+ channelId: z
25
+ .string()
26
+ .min(1)
27
+ .optional()
28
+ .describe("Full SDK channel id, such as slack:C123. Ignored when threadId is set."),
29
+ text: z.string().min(1).max(20_000).describe("Markdown text to post."),
30
+ }),
31
+ outputSchema: swarmToolOutputSchema({
32
+ yourAgentId: z.string().optional(),
33
+ messageId: z.string().optional(),
34
+ adapter: z.string().optional(),
35
+ threadId: z.string().optional(),
36
+ channelId: z.string().optional(),
37
+ }),
38
+ },
39
+ async ({ adapter, threadId, channelId, text }, requestInfo) => {
40
+ const data = { yourAgentId: requestInfo.agentId };
41
+ const target = threadId ?? channelId;
42
+ if (!target) return toolErr("Give a threadId or a channelId.", { data });
43
+ if (adapterOfChatId(target) !== adapter) {
44
+ return toolErr(`The id ${target} does not belong to the ${adapter} adapter.`, { data });
45
+ }
46
+ if (!(await canUseChatVerb(requestInfo, "integration.chat.post"))) {
47
+ return toolErr(
48
+ "Permission denied. Only the lead can post outside the thread of its task. Use chat-reply for the thread of your current task.",
49
+ { data },
50
+ );
51
+ }
52
+ try {
53
+ const sent = await postChatMessage({
54
+ adapter,
55
+ ...(threadId ? { threadId } : { channelId }),
56
+ text,
57
+ taskId: requestInfo.sourceTaskId,
58
+ });
59
+ return toolOk(`Posted to ${adapter} ${threadId ? "thread" : "channel"} ${target}.`, {
60
+ data: {
61
+ ...data,
62
+ messageId: sent.messageIds.at(-1),
63
+ adapter: sent.adapter,
64
+ threadId: sent.threadId,
65
+ channelId: sent.channelId,
66
+ },
67
+ });
68
+ } catch (error) {
69
+ const message = errorMessage(error);
70
+ return toolErr(`Could not post: ${message}`, { data });
71
+ }
72
+ },
73
+ );
74
+ };
@@ -0,0 +1,100 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import * as z from "zod";
3
+ import { errorMessage } from "@/integrations/chat-sdk/logger";
4
+ import { adapterOfChatId } from "@/integrations/chat-sdk/outbound";
5
+ import { getChatInstance } from "@/integrations/chat-sdk/runtime";
6
+ import { assertTrustedChatTarget } from "@/integrations/chat-sdk/teams-origin";
7
+ import { callerChatThread, canUseChatVerb } from "@/tools/chat-access";
8
+ import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
9
+ import { scrubSecrets } from "@/utils/secret-scrubber";
10
+
11
+ const DEFAULT_LIMIT = 30;
12
+
13
+ export const registerChatReadThreadTool = (server: McpServer) => {
14
+ createToolRegistrar(server)(
15
+ "chat-read-thread",
16
+ {
17
+ title: "Read recent messages of a chat thread",
18
+ description:
19
+ "Read the most recent messages of a Chat SDK thread, oldest first. Every agent can read the thread of its current task. Other threads are lead only. Limits per platform: Telegram has no history API, so you get the history the SDK stored in the swarm database, capped at 100 messages and 7 days. Teams needs the Chat.Read.* Graph permission. Slack and Discord return platform history.",
20
+ annotations: { readOnlyHint: true, openWorldHint: true },
21
+ inputSchema: z.object({
22
+ threadId: z
23
+ .string()
24
+ .min(1)
25
+ .describe("Full SDK thread id with the adapter prefix, such as telegram:12345."),
26
+ limit: z
27
+ .number()
28
+ .int()
29
+ .min(1)
30
+ .max(100)
31
+ .optional()
32
+ .describe(`Number of messages to return (default ${DEFAULT_LIMIT}).`),
33
+ }),
34
+ outputSchema: swarmToolOutputSchema({
35
+ yourAgentId: z.string().optional(),
36
+ threadId: z.string().optional(),
37
+ messages: z
38
+ .array(
39
+ z.looseObject({
40
+ id: z.string().optional(),
41
+ userId: z.string().optional(),
42
+ userName: z.string().optional(),
43
+ isBot: z.boolean().optional(),
44
+ sentAt: z.string().optional(),
45
+ text: z.string().optional(),
46
+ }),
47
+ )
48
+ .optional(),
49
+ }),
50
+ },
51
+ async ({ threadId, limit }, requestInfo) => {
52
+ const data = { yourAgentId: requestInfo.agentId };
53
+ // A worker reads only the thread its task came from; any other thread
54
+ // needs the lead verb, as slack-read does for direct channel reads.
55
+ const own = await callerChatThread(requestInfo);
56
+ if (own?.threadId !== threadId) {
57
+ if (!(await canUseChatVerb(requestInfo, "integration.chat.read"))) {
58
+ return toolErr(
59
+ own
60
+ ? `Permission denied. You can read only the thread of your current task, ${own.threadId}. Other threads need lead privileges.`
61
+ : "Permission denied. Reading a chat thread that is not the thread of your current task needs lead privileges.",
62
+ { data },
63
+ );
64
+ }
65
+ }
66
+ const adapter = adapterOfChatId(threadId);
67
+ const chat = getChatInstance(adapter);
68
+ if (!chat) return toolErr(`Chat SDK adapter '${adapter}' is not running.`, { data });
69
+ try {
70
+ // Teams reads history with the bot token from the service URL in the id.
71
+ assertTrustedChatTarget(threadId);
72
+ const page = await chat.history.thread.list(threadId, {
73
+ limit: limit ?? DEFAULT_LIMIT,
74
+ direction: "backward",
75
+ });
76
+ const messages = page.messages.map((message) => ({
77
+ id: message.id,
78
+ userId: message.author.userId,
79
+ userName: message.author.userName || message.author.fullName || undefined,
80
+ isBot: message.author.isBot === true,
81
+ sentAt: message.metadata?.dateSent?.toISOString(),
82
+ text: scrubSecrets(message.text ?? ""),
83
+ }));
84
+ const rendered = messages
85
+ .map(
86
+ (m) =>
87
+ `[${m.sentAt ?? "?"}] ${m.userName ?? m.userId}${m.isBot ? " (bot)" : ""}: ${m.text}`,
88
+ )
89
+ .join("\n");
90
+ return toolOk(`${messages.length} message(s) from ${threadId}.`, {
91
+ details: rendered || undefined,
92
+ data: { ...data, threadId, messages },
93
+ });
94
+ } catch (error) {
95
+ const message = errorMessage(error);
96
+ return toolErr(`Could not read the thread: ${message}`, { data });
97
+ }
98
+ },
99
+ );
100
+ };
@@ -0,0 +1,75 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import * as z from "zod";
3
+ import { getAgentCurrentTask, getTaskById } from "@/be/db";
4
+ import { errorMessage } from "@/integrations/chat-sdk/logger";
5
+ import { ChatAdapterUnavailableError, postChatMessage } from "@/integrations/chat-sdk/outbound";
6
+ import { chatThreadOfContextKey } from "@/tasks/context-key";
7
+ import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
8
+
9
+ export const registerChatReplyTool = (server: McpServer) => {
10
+ createToolRegistrar(server)(
11
+ "chat-reply",
12
+ {
13
+ title: "Reply in the chat thread of this task",
14
+ description:
15
+ "Post a message to the Telegram, Slack, Discord or Teams thread your current task came from. Works only on a task whose contextKey starts with task:chat-sdk:. The task's final output is posted to the thread automatically when the task ends, so use this only for a question to the user or a short progress note on long work. Markdown is supported; long text is split into several messages.",
16
+ annotations: { openWorldHint: true },
17
+ inputSchema: z.object({
18
+ text: z.string().min(1).max(20_000).describe("Markdown text to post to the thread."),
19
+ }),
20
+ outputSchema: swarmToolOutputSchema({
21
+ yourAgentId: z.string().optional(),
22
+ messageId: z.string().optional(),
23
+ adapter: z.string().optional(),
24
+ threadId: z.string().optional(),
25
+ }),
26
+ },
27
+ async ({ text }, requestInfo) => {
28
+ if (!requestInfo.agentId) return toolErr("Agent ID not found.");
29
+ const data = { yourAgentId: requestInfo.agentId };
30
+
31
+ // The header names the task this session works on; without it, the
32
+ // agent's current in-progress task.
33
+ const task = requestInfo.sourceTaskId
34
+ ? await getTaskById(requestInfo.sourceTaskId)
35
+ : await getAgentCurrentTask(requestInfo.agentId);
36
+ if (!task) return toolErr("No current task found for chat-reply.", { data });
37
+ // Creator is safe: a non-lead agent can only create a chat-bound task under a parent it owns or created.
38
+ if (task.agentId !== requestInfo.agentId && task.creatorAgentId !== requestInfo.agentId) {
39
+ return toolErr("You don't have context for this task.", { data });
40
+ }
41
+ const thread = chatThreadOfContextKey(task.contextKey);
42
+ if (!thread) {
43
+ return toolErr(
44
+ `Task ${task.id} did not come from a chat thread (no task:chat-sdk contextKey). Use the tool of the channel the task came from.`,
45
+ { data },
46
+ );
47
+ }
48
+
49
+ try {
50
+ const sent = await postChatMessage({
51
+ adapter: thread.adapter,
52
+ threadId: thread.threadId,
53
+ text,
54
+ taskId: task.id,
55
+ });
56
+ return toolOk(`Posted to ${thread.adapter} thread ${sent.threadId}.`, {
57
+ data: {
58
+ ...data,
59
+ messageId: sent.messageIds.at(-1),
60
+ adapter: sent.adapter,
61
+ threadId: sent.threadId,
62
+ },
63
+ });
64
+ } catch (error) {
65
+ const message = errorMessage(error);
66
+ return toolErr(
67
+ error instanceof ChatAdapterUnavailableError
68
+ ? `${message}. The final task output is still posted when the adapter is back.`
69
+ : `Could not post to the chat thread: ${message}`,
70
+ { data },
71
+ );
72
+ }
73
+ },
74
+ );
75
+ };