@desplega.ai/agent-swarm 1.172.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 (525) hide show
  1. package/README.md +1 -1
  2. package/dist/{acp-adapter-fpecx1g1.js → acp-adapter-sj2g0m84.js} +18 -17
  3. package/dist/{actions-pw87a1a8.js → actions-fegk44gr.js} +65 -35
  4. package/dist/{amp-adapter-8jem2bc0.js → amp-adapter-ad8ft89x.js} +8 -7
  5. package/dist/{anthropic-messages-8ee2f22q.js → anthropic-messages-r6czkjv1.js} +7 -7
  6. package/dist/{app-wmf0m843.js → app-kfvgbvzy.js} +19 -13
  7. package/dist/{assistant-emp1e108.js → assistant-z7zdy9ja.js} +65 -38
  8. package/dist/{boot-reembed-1de4f4aj.js → boot-reembed-5c2z0mbe.js} +11 -9
  9. package/dist/{boot-reembed-4ymn9atn.js → boot-reembed-j8xa181e.js} +11 -9
  10. package/dist/{boot-scrub-sweep-adhk1s8c.js → boot-scrub-sweep-hnkn3rgt.js} +12 -10
  11. package/dist/{boot-seeds-8q637aew.js → boot-seeds-kwjq4d2z.js} +15 -13
  12. package/dist/{claude-adapter-t26cv8a4.js → claude-adapter-9vmvpvgd.js} +20 -19
  13. package/dist/{claude-managed-adapter-xgxhqrt2.js → claude-managed-adapter-k9qae05j.js} +7 -5
  14. package/dist/{claude-managed-setup-bnmjarjc.js → claude-managed-setup-q54zx5q2.js} +6 -4
  15. package/dist/{claude-sdk-session-gsfsh662.js → claude-sdk-session-9jye6cxr.js} +12698 -11995
  16. package/dist/{cli-k46r9hmk.js → cli-03gedhng.js} +16 -14
  17. package/dist/{cli-kb4ehzx5.js → cli-1e4b6bzh.js} +7 -7
  18. package/dist/{cli-d603qf86.js → cli-29jc5q7w.js} +10 -8
  19. package/dist/cli-2m1ktfzz.js +17795 -0
  20. package/dist/{cli-8vs9trd4.js → cli-3dj8k4vy.js} +1 -1
  21. package/dist/{cli-zxm9p3x4.js → cli-3fbmmawm.js} +5 -5
  22. package/dist/{cli-gmdajfw5.js → cli-4jxm2qtz.js} +4987 -1708
  23. package/dist/{cli-tadfgd0t.js → cli-5hkm7whv.js} +33 -4
  24. package/dist/{cli-6jb2rf77.js → cli-5t4f29ez.js} +3 -3
  25. package/dist/{cli-7a0sde2x.js → cli-6cagzc96.js} +9 -0
  26. package/dist/cli-6cvfxs8r.js +47 -0
  27. package/dist/{cli-pj6x9qce.js → cli-6jyj59nb.js} +2 -2
  28. package/dist/{cli-388egyf6.js → cli-6razj6t4.js} +2 -2
  29. package/dist/{cli-qsztpfry.js → cli-74ahz61x.js} +2 -2
  30. package/dist/{cli-4nz8p1p5.js → cli-7x0g4sn1.js} +15474 -16082
  31. package/dist/{cli-yatxqynm.js → cli-9ha6hs2d.js} +1 -290
  32. package/dist/{cli-81r1wjyv.js → cli-9nfx44eh.js} +1 -1
  33. package/dist/cli-9traj3dn.js +638 -0
  34. package/dist/{cli-smcrrmhy.js → cli-agzebbd5.js} +9 -9
  35. package/dist/{cli-bf7485pm.js → cli-apx87jne.js} +2 -2
  36. package/dist/{cli-ztrv9y21.js → cli-bzszjf2p.js} +3689 -1081
  37. package/dist/{cli-c5km5znr.js → cli-c8r3kzzb.js} +1 -1
  38. package/dist/{cli-hsem16hb.js → cli-cgtksawx.js} +7 -4
  39. package/dist/{cli-db3m6zdt.js → cli-d2qr7aj4.js} +1 -1
  40. package/dist/{cli-1hgat6pa.js → cli-d5f9ed5c.js} +5 -5
  41. package/dist/cli-e3g5mbkz.js +776 -0
  42. package/dist/{cli-7jykv7ef.js → cli-e6mpjhed.js} +1 -1
  43. package/dist/{cli-j6kpj9az.js → cli-ee9gtz70.js} +1553 -68
  44. package/dist/{cli-e0a9sk00.js → cli-eejsgvm4.js} +4 -4
  45. package/dist/{cli-tgpfnm6r.js → cli-ef1hg1wv.js} +518 -59
  46. package/dist/cli-f527ce74.js +781 -0
  47. package/dist/{cli-ppy4nnnh.js → cli-f7kr7svz.js} +2 -2
  48. package/dist/cli-hjwmhcg1.js +10622 -0
  49. package/dist/{cli-2bw3w47j.js → cli-hnhtk6cj.js} +1 -1
  50. package/dist/{cli-ywm1azg9.js → cli-hnmhtzvh.js} +6 -4
  51. package/dist/{cli-4fnb6884.js → cli-hvqbtna4.js} +7 -1259
  52. package/dist/{cli-pstq9888.js → cli-j0pfptdp.js} +220 -103
  53. package/dist/cli-jta2eyzc.js +619 -0
  54. package/dist/{cli-esnpehf5.js → cli-k382tshb.js} +12 -13
  55. package/dist/cli-k3dp5tpf.js +263 -0
  56. package/dist/{cli-wd0hz6ah.js → cli-kbrt8gqa.js} +9 -91
  57. package/dist/cli-kceah1eh.js +92 -0
  58. package/dist/{cli-prrt9fa0.js → cli-ke896fcf.js} +10 -10
  59. package/dist/{cli-nv4zd0b9.js → cli-kt6zqg0e.js} +6 -6
  60. package/dist/{cli-g6t9texm.js → cli-m50t4309.js} +5 -18
  61. package/dist/cli-n2wq4j7m.js +6985 -0
  62. package/dist/{cli-c2pq2rjb.js → cli-n3hzg89b.js} +46 -15
  63. package/dist/{cli-tmhsxhb8.js → cli-n5vwg842.js} +23 -6
  64. package/dist/{cli-5rv1j9ga.js → cli-ne236afy.js} +53 -7
  65. package/dist/cli-p0sg24zp.js +16 -0
  66. package/dist/{cli-d161g69q.js → cli-p1x85d2t.js} +50 -2
  67. package/dist/{cli-7pz3b7x5.js → cli-przpmdgr.js} +25 -0
  68. package/dist/{cli-g4f5pj26.js → cli-q0ke1yww.js} +111 -25
  69. package/dist/{cli-e3pmyt39.js → cli-q50942rs.js} +1 -1
  70. package/dist/{cli-r44t9wqc.js → cli-r106725k.js} +2 -2
  71. package/dist/{cli-gj81e47t.js → cli-r2pnvj8g.js} +2 -2
  72. package/dist/{cli-r8smj8b8.js → cli-r62a35ea.js} +19 -3
  73. package/dist/{cli-cd0hv9nh.js → cli-ra0xzggj.js} +2 -2
  74. package/dist/cli-rhs9pz33.js +294 -0
  75. package/dist/{cli-nyhb1r6x.js → cli-s2sp957d.js} +52 -29
  76. package/dist/{cli-1x7da0de.js → cli-s4setq91.js} +8 -8
  77. package/dist/{cli-xczpdp9e.js → cli-se8w6wny.js} +6 -6
  78. package/dist/cli-skxvvpap.js +10140 -0
  79. package/dist/{cli-014vns2y.js → cli-stfs0k5s.js} +208 -525
  80. package/dist/{cli-0p3enk35.js → cli-tqaazemc.js} +9 -1
  81. package/dist/{cli-cf69s3ew.js → cli-tr4sxp68.js} +3 -3
  82. package/dist/{cli-8hzfb6xz.js → cli-tsde1hdr.js} +15 -15
  83. package/dist/{cli-cceskcvt.js → cli-txqb9s38.js} +8 -8
  84. package/dist/{cli-aspcaef3.js → cli-v1yb0rqb.js} +6 -6
  85. package/dist/{cli-j1pmc07z.js → cli-vatyhshy.js} +268 -40
  86. package/dist/{cli-04emfe2q.js → cli-vdach5a9.js} +5 -5
  87. package/dist/{cli-7avy4v8w.js → cli-w1q7h2r2.js} +5 -3
  88. package/dist/{cli-rcp5aqdw.js → cli-w7m81tb7.js} +1 -1
  89. package/dist/{cli-hwwvy36w.js → cli-wayw93nt.js} +3 -3
  90. package/dist/cli-wmgrkscj.js +23254 -0
  91. package/dist/{cli-gp1rtyvk.js → cli-z5jjpaxw.js} +29 -6
  92. package/dist/{cli-451ks6aw.js → cli-zkpks3r3.js} +1 -1
  93. package/dist/cli.js +32 -48
  94. package/dist/{codex-adapter-m4tgdysy.js → codex-adapter-y81sssk0.js} +16 -15
  95. package/dist/{codex-login-841kv5fk.js → codex-login-f2rcfpp9.js} +3 -2
  96. package/dist/{codex-session-runner-ymwzm6ea.js → codex-session-runner-8vx4j6a9.js} +16 -15
  97. package/dist/{commands-n0hshcvs.js → commands-kcpvpcrr.js} +21 -10
  98. package/dist/{controller-hq5ggpcp.js → controller-ywrmt6yd.js} +5 -4
  99. package/dist/{controllers-connect-y07cqcvy.js → controllers-connect-kwpvj3rf.js} +45 -38
  100. package/dist/{cursor-adapter-q8pgv5qa.js → cursor-adapter-cehv1qns.js} +7 -6
  101. package/dist/{db-hkqvrdcb.js → db-k8ankj20.js} +13 -11
  102. package/dist/{db-4w7tmt1g.js → db-ww5d7jq3.js} +29 -9
  103. package/dist/{devin-adapter-xgtj680j.js → devin-adapter-x1022qb1.js} +3 -2
  104. package/dist/{dispatcher-5sd69y14.js → dispatcher-e8gq5m68.js} +41 -34
  105. package/dist/{docker-f503k9p0.js → docker-ff6y1190.js} +3 -2
  106. package/dist/{dsh-adapter-2h8m4cab.js → dsh-adapter-3epjm3af.js} +7 -6
  107. package/dist/{e2b-66xra4az.js → e2b-8ygwhg0f.js} +4 -3
  108. package/dist/{e2b-xwhyj8cw.js → e2b-swhnpw3n.js} +4 -3
  109. package/dist/{event-bus-vjtv82gm.js → event-bus-6axnngdj.js} +3 -2
  110. package/dist/external-channel-h3359cwn.js +52 -0
  111. package/dist/{google-generative-ai-7pavrbv7.js → google-generative-ai-g8db7x0f.js} +5 -3
  112. package/dist/{google-vertex-3f1jk41f.js → google-vertex-r2692q0r.js} +5 -3
  113. package/dist/{grok-adapter-8m9yqzbq.js → grok-adapter-0d4x7gkx.js} +19 -18
  114. package/dist/{handlers-s20xr10r.js → handlers-an1gzv21.js} +44 -37
  115. package/dist/{heartbeat-3k31q4g2.js → heartbeat-6dakeb61.js} +46 -39
  116. package/dist/{hook-gdn6a2fn.js → hook-6v6cv6d5.js} +12 -24
  117. package/dist/{hook-ygwerrdh.js → hook-sa5xye4y.js} +13 -12
  118. package/dist/{http-jdteeg81.js → http-r7f3eky0.js} +1598 -279
  119. package/dist/index-52j1aja4.js +3465 -0
  120. package/dist/{index-e0snym4t.js → index-7ep4gzdk.js} +41 -34
  121. package/dist/index-9gw58n4k.js +193 -0
  122. package/dist/index-9j87mcm8.js +56305 -0
  123. package/dist/index-bsmcresk.js +79424 -0
  124. package/dist/index-mh85qhec.js +475 -0
  125. package/dist/{index-vm3bm9t7.js → index-rmpw9a8q.js} +41 -34
  126. package/dist/{index-6ksewd5y.js → index-s2wxgzr0.js} +1015 -732
  127. package/dist/{index-tsszt7g9.js → index-v1v6n7hg.js} +46 -39
  128. package/dist/index-we27kd31.js +12810 -0
  129. package/dist/{index-ffqzqgwb.js → index-zk96aq1k.js} +11 -9
  130. package/dist/{keepalive-jekc896t.js → keepalive-be9vwy5h.js} +13 -11
  131. package/dist/{lead-4k9s9szx.js → lead-0861cwrf.js} +64 -57
  132. package/dist/{maintenance-shazch4n.js → maintenance-mzzgc8bn.js} +11 -9
  133. package/dist/node-c9we95xf.js +1 -1
  134. package/dist/{node-fkajaq96.js → node-mn1zs7an.js} +2 -1
  135. package/dist/{oauth-refresh-sweep-5gb36kn8.js → oauth-refresh-sweep-cpdnfwdj.js} +12 -10
  136. package/dist/{onboard-ydrbnvww.js → onboard-fh8qs4r3.js} +11 -3
  137. package/dist/{opencode-adapter-5zh0he20.js → opencode-adapter-nm42wwj8.js} +19 -14
  138. package/dist/{otel-impl-6pf5cv86.js → otel-impl-x6c1c9g4.js} +248 -869
  139. package/dist/{pi-mono-adapter-ef9wwx9m.js → pi-mono-adapter-9n2ya240.js} +245 -107
  140. package/dist/{pricing-refresh-s52nsa5q.js → pricing-refresh-qb0rv0ap.js} +13 -11
  141. package/dist/{rbac-roles-aq3msh4e.js → rbac-roles-1gzdwbh1.js} +12 -10
  142. package/dist/{rbac-roles-q5gb5e8v.js → rbac-roles-3mraey52.js} +13 -11
  143. package/dist/{render-v2-pd1rg54q.js → render-v2-agpa45zz.js} +19 -13
  144. package/dist/{resume-gw4a2c6v.js → resume-x0ydy9s9.js} +27 -21
  145. package/dist/{runtime-qfry88v2.js → runtime-sa2vr16g.js} +2 -2
  146. package/dist/{setup-jda1ksdh.js → setup-as9ga0d6.js} +2 -2
  147. package/dist/{virtual-modules-b3ga2m65.js → virtual-modules-9gkxqrap.js} +7 -6
  148. package/dist/{work-objects-tb7ycye8.js → work-objects-y5g1y7ev.js} +49 -37
  149. package/dist/{worker-njjccz5d.js → worker-860c3e38.js} +64 -57
  150. package/dist/{x-qfmygqqv.js → x-m477fr0h.js} +3 -2
  151. package/openapi.json +951 -356
  152. package/package.json +12 -13
  153. package/src/apps/catalog.generated.json +1309 -53
  154. package/src/apps/page-validator.ts +93 -31
  155. package/src/azure-devops/api.ts +94 -18
  156. package/src/azure-devops/auth.ts +32 -0
  157. package/src/azure-devops/boards-sync.ts +708 -0
  158. package/src/azure-devops/handlers.ts +52 -8
  159. package/src/azure-devops/html.ts +26 -0
  160. package/src/azure-devops/index.ts +15 -0
  161. package/src/azure-devops/templates.ts +86 -0
  162. package/src/azure-devops/types.ts +65 -1
  163. package/src/be/config-refs.ts +60 -0
  164. package/src/be/db/page-versions.ts +15 -0
  165. package/src/be/db/runtime.ts +35 -8
  166. package/src/be/db/skill-name-resolution.ts +36 -0
  167. package/src/be/db/tasks/read.ts +230 -0
  168. package/src/be/db/tasks/write.ts +7 -0
  169. package/src/be/db.ts +74 -13
  170. package/src/be/migrations/213_script_connections_sql.sql +79 -0
  171. package/src/be/migrations/215_swarm_config_export_to_env.sql +34 -0
  172. package/src/be/migrations/216_task_resolved_effort.sql +5 -0
  173. package/src/be/migrations/217_chat_sdk_state.sql +104 -0
  174. package/src/be/model-catalog-store.ts +5 -1
  175. package/src/be/model-tier-resolution.ts +12 -1
  176. package/src/be/model-validation.ts +5 -2
  177. package/src/be/models-catalog.ts +26 -1
  178. package/src/be/modelsdev-cache.json +198 -63
  179. package/src/be/modelsdev-cache.ts +9 -1
  180. package/src/be/script-connections.ts +47 -8
  181. package/src/be/script-credential-broker.ts +39 -2
  182. package/src/be/scripts/run-saved.ts +1 -0
  183. package/src/be/scripts/typecheck.ts +10 -8
  184. package/src/be/seed/runner.ts +13 -1
  185. package/src/be/seed/state-db.ts +5 -0
  186. package/src/be/seed/types.ts +6 -0
  187. package/src/be/seed-pricing.ts +19 -2
  188. package/src/be/seed-scripts/catalog/ops-catalog-audit.inline.ts +1 -1
  189. package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +1 -1
  190. package/src/be/seed-skills/bundled-files.generated.json +10 -18
  191. package/src/be/seed-skills/index.ts +107 -3
  192. package/src/be/sql-connections.ts +118 -0
  193. package/src/be/swarm-config-guard.ts +53 -0
  194. package/src/cli.tsx +0 -16
  195. package/src/commands/onboard/non-interactive.ts +4 -2
  196. package/src/commands/profile-sync.ts +5 -3
  197. package/src/commands/provider-credentials.ts +52 -6
  198. package/src/commands/runner.ts +196 -56
  199. package/src/e2b/env.ts +5 -0
  200. package/src/extensions/contract-types.generated.ts +1 -1
  201. package/src/extensions/contract.ts +45 -0
  202. package/src/extensions/dispatcher.ts +7 -0
  203. package/src/extensions/post-bridge.ts +40 -0
  204. package/src/github/handlers.ts +11 -4
  205. package/src/heartbeat/templates.ts +8 -5
  206. package/src/hooks/hook.ts +0 -26
  207. package/src/http/all-routes.ts +0 -1
  208. package/src/http/config.ts +36 -17
  209. package/src/http/core.ts +4 -0
  210. package/src/http/index.ts +14 -2
  211. package/src/http/integrations.ts +88 -1
  212. package/src/http/mcp-oauth.ts +6 -13
  213. package/src/http/mcp.ts +15 -5
  214. package/src/http/page-proxy.ts +2 -4
  215. package/src/http/pages-public.ts +6 -5
  216. package/src/http/pages.ts +5 -14
  217. package/src/http/poll.ts +28 -68
  218. package/src/http/requested-by.ts +54 -0
  219. package/src/http/route-def.ts +7 -6
  220. package/src/http/script-connection-proxy.ts +51 -4
  221. package/src/http/script-connections.ts +5 -2
  222. package/src/http/scripts.ts +1 -0
  223. package/src/http/session-cost-recompute.ts +12 -2
  224. package/src/http/session-data.ts +4 -0
  225. package/src/http/stats.ts +1 -30
  226. package/src/http/tasks.ts +61 -5
  227. package/src/http/utils.ts +14 -0
  228. package/src/http/webhooks.ts +183 -2
  229. package/src/http/x.ts +1 -0
  230. package/src/integrations/chat-sdk/adapters.ts +129 -0
  231. package/src/integrations/chat-sdk/attachments.ts +232 -0
  232. package/src/integrations/chat-sdk/background.ts +36 -0
  233. package/src/integrations/chat-sdk/commands.ts +194 -0
  234. package/src/integrations/chat-sdk/config.ts +198 -0
  235. package/src/integrations/chat-sdk/discord-api.ts +63 -0
  236. package/src/integrations/chat-sdk/discord-gateway.ts +185 -0
  237. package/src/integrations/chat-sdk/handlers.ts +28 -0
  238. package/src/integrations/chat-sdk/inbound.ts +426 -0
  239. package/src/integrations/chat-sdk/limits.ts +48 -0
  240. package/src/integrations/chat-sdk/logger.ts +81 -0
  241. package/src/integrations/chat-sdk/outbound.ts +123 -0
  242. package/src/integrations/chat-sdk/relay.ts +172 -0
  243. package/src/integrations/chat-sdk/router.ts +169 -0
  244. package/src/integrations/chat-sdk/runtime.ts +351 -0
  245. package/src/integrations/chat-sdk/schema.ts +245 -0
  246. package/src/integrations/chat-sdk/state.ts +243 -0
  247. package/src/integrations/chat-sdk/status.ts +244 -0
  248. package/src/integrations/chat-sdk/steering.ts +58 -0
  249. package/src/integrations/chat-sdk/teams-origin.ts +77 -0
  250. package/src/integrations/chat-sdk/telegram-api.ts +56 -0
  251. package/src/integrations/chat-sdk/templates.ts +16 -0
  252. package/src/integrations/chat-sdk/test-hooks.ts +37 -0
  253. package/src/integrations/chat-sdk/thread-buffer.ts +100 -0
  254. package/src/integrations/chat-sdk/typing.ts +65 -0
  255. package/src/jira/adf.ts +3 -1
  256. package/src/kv-reserved-namespaces.ts +2 -0
  257. package/src/oauth/mcp-wrapper.ts +10 -6
  258. package/src/otel-impl.ts +2 -1
  259. package/src/pages/version.ts +4 -5
  260. package/src/prompts/base-prompt.ts +111 -45
  261. package/src/providers/acp-target-catalog.ts +6 -0
  262. package/src/providers/acp-targets.ts +56 -3
  263. package/src/providers/amp-adapter.ts +1 -1
  264. package/src/providers/claude-adapter.ts +27 -2
  265. package/src/providers/claude-flag-settings.ts +133 -0
  266. package/src/providers/claude-managed-adapter.ts +1 -1
  267. package/src/providers/claude-sdk-session.ts +7 -0
  268. package/src/providers/claude-session-events.ts +6 -1
  269. package/src/providers/dsh-adapter.ts +1 -1
  270. package/src/providers/opencode-adapter.ts +268 -37
  271. package/src/providers/opencode-billing.ts +86 -0
  272. package/src/providers/opencode-swarm-plugin.ts +51 -0
  273. package/src/providers/pi-mono-adapter.ts +398 -153
  274. package/src/providers/pricing-sources.md +37 -3
  275. package/src/providers/reasoning-effort.ts +27 -0
  276. package/src/providers/types.ts +11 -2
  277. package/src/rbac/legacy-policy.ts +8 -2
  278. package/src/rbac/permissions.ts +21 -0
  279. package/src/scheduler/scheduler.ts +4 -2
  280. package/src/scripts-runtime/api-client.ts +15 -1
  281. package/src/scripts-runtime/api-types.ts +9 -1
  282. package/src/scripts-runtime/ctx.ts +1 -1
  283. package/src/scripts-runtime/loader.ts +19 -7
  284. package/src/scripts-runtime/mcp-client.ts +30 -19
  285. package/src/scripts-runtime/sdk-allowlist.ts +8 -6
  286. package/src/scripts-runtime/swarm-sdk.ts +0 -11
  287. package/src/scripts-runtime/types/stdlib.d.ts +27 -15
  288. package/src/scripts-runtime/types/swarm-extension.d.ts +63 -1
  289. package/src/scripts-runtime/types/swarm-sdk.d.ts +27 -15
  290. package/src/server.ts +52 -18
  291. package/src/slack/actions.ts +29 -1
  292. package/src/slack/app.ts +14 -2
  293. package/src/slack/assistant.ts +36 -2
  294. package/src/slack/channel-join.ts +3 -16
  295. package/src/slack/commands.ts +9 -1
  296. package/src/slack/event-dedup.ts +1 -1
  297. package/src/slack/external-channel-egress.ts +113 -0
  298. package/src/slack/external-channel.ts +309 -0
  299. package/src/slack/handlers.ts +61 -0
  300. package/src/slack/render-v2.ts +40 -47
  301. package/src/slack/work-objects.ts +5 -0
  302. package/src/tasks/additive-buffer.ts +4 -2
  303. package/src/tasks/context-key.ts +94 -2
  304. package/src/tasks/model-pin.ts +67 -0
  305. package/src/tasks/worker-follow-up.ts +4 -31
  306. package/src/telemetry.ts +2 -1
  307. package/src/tests/acp-adapter.test.ts +42 -0
  308. package/src/tests/api-drain-worker-e2e.test.ts +1 -6
  309. package/src/tests/apps-catalog-feedback.test.ts +310 -0
  310. package/src/tests/apps-catalog-inputs.test.ts +70 -0
  311. package/src/tests/apps-catalog-table.test.ts +113 -0
  312. package/src/tests/azure-devops-boards-sync.test.ts +584 -0
  313. package/src/tests/azure-devops-html.test.ts +13 -0
  314. package/src/tests/azure-devops-webhook-malformed.test.ts +4 -0
  315. package/src/tests/base-prompt.test.ts +58 -54
  316. package/src/tests/chat-sdk-commands.test.ts +191 -0
  317. package/src/tests/chat-sdk-config.test.ts +334 -0
  318. package/src/tests/chat-sdk-discord-api.test.ts +68 -0
  319. package/src/tests/chat-sdk-discord-gateway.test.ts +252 -0
  320. package/src/tests/chat-sdk-discord-webhook.test.ts +126 -0
  321. package/src/tests/chat-sdk-fields-sync.test.ts +50 -0
  322. package/src/tests/chat-sdk-harness.ts +146 -0
  323. package/src/tests/chat-sdk-inbound.test.ts +363 -0
  324. package/src/tests/chat-sdk-relay.test.ts +470 -0
  325. package/src/tests/chat-sdk-reload.test.ts +128 -0
  326. package/src/tests/chat-sdk-shutdown.test.ts +220 -0
  327. package/src/tests/chat-sdk-slack.test.ts +268 -0
  328. package/src/tests/chat-sdk-state.test.ts +163 -0
  329. package/src/tests/chat-sdk-status-route.test.ts +382 -0
  330. package/src/tests/chat-sdk-teams.test.ts +277 -0
  331. package/src/tests/chat-sdk-tools.test.ts +520 -0
  332. package/src/tests/chat-sdk-typing.test.ts +120 -0
  333. package/src/tests/chat-sdk-webhook-telegram.test.ts +200 -0
  334. package/src/tests/claude-adapter-binary.test.ts +144 -0
  335. package/src/tests/claude-flag-settings.test.ts +114 -0
  336. package/src/tests/claude-managed-adapter.test.ts +1 -1
  337. package/src/tests/claude-sdk-lifecycle.test.ts +148 -1
  338. package/src/tests/claude-sdk-process.fixture.ts +7 -0
  339. package/src/tests/claude-thinking-tokens.test.ts +57 -0
  340. package/src/tests/claude-worker-parity.test.ts +2 -7
  341. package/src/tests/config-env-export-chat-sdk.test.ts +11 -0
  342. package/src/tests/config-refs.test.ts +66 -0
  343. package/src/tests/context-key.test.ts +51 -0
  344. package/src/tests/cors-allowlist.test.ts +25 -0
  345. package/src/tests/create-page-tool.test.ts +18 -0
  346. package/src/tests/credential-check.test.ts +139 -0
  347. package/src/tests/credentials.test.ts +21 -0
  348. package/src/tests/db-runtime.test.ts +61 -0
  349. package/src/tests/db-task-read-contract.test.ts +20 -0
  350. package/src/tests/db-task-write-contract.test.ts +10 -0
  351. package/src/tests/deferred-task-wake.test.ts +52 -4
  352. package/src/tests/docs-index-footprint.test.js +3 -3
  353. package/src/tests/dsh-adapter.test.ts +1 -1
  354. package/src/tests/enabled-capabilities.test.ts +20 -0
  355. package/src/tests/entrypoint-api-readiness.test.ts +14 -8
  356. package/src/tests/entrypoint-config-env-export.test.ts +5 -2
  357. package/src/tests/entrypoint-export-to-env.test.ts +78 -0
  358. package/src/tests/explicit-model-validation.test.ts +105 -1
  359. package/src/tests/extensions-example-route-chat-channel.test.ts +154 -0
  360. package/src/tests/extensions-post-bridge.test.ts +51 -0
  361. package/src/tests/fetch-resolved-env.test.ts +128 -0
  362. package/src/tests/fixtures/azure-boards/workitem.commented.json +89 -0
  363. package/src/tests/fixtures/azure-boards/workitem.created.json +81 -0
  364. package/src/tests/fixtures/azure-boards/workitem.deleted.json +70 -0
  365. package/src/tests/fixtures/azure-boards/workitem.restored.json +82 -0
  366. package/src/tests/fixtures/azure-boards/workitem.updated.json +130 -0
  367. package/src/tests/fixtures/catalog-feedback-definition.json.txt +860 -0
  368. package/src/tests/fixtures/catalog-inputs-definition.json.txt +910 -0
  369. package/src/tests/fixtures/catalog-table-definition.json.txt +468 -0
  370. package/src/tests/fixtures/chat-sdk/telegram-private-text.json +10 -0
  371. package/src/tests/fixtures/extensions/route-chat-channel-to-agent/hooks.ts +27 -0
  372. package/src/tests/fixtures/extensions/route-chat-channel-to-agent/manifest.json +9 -0
  373. package/src/tests/fixtures/openai-compatible-registry-extension.child.ts +146 -0
  374. package/src/tests/fixtures/runner-identity-refresh.ts +3 -0
  375. package/src/tests/fixtures/runner-legacy-resume-prompt.ts +122 -0
  376. package/src/tests/github-handlers.test.ts +35 -0
  377. package/src/tests/harness-provider-resolution.test.ts +12 -0
  378. package/src/tests/heartbeat-checklist.test.ts +3 -2
  379. package/src/tests/hook-runtime-close.test.ts +0 -2
  380. package/src/tests/http-api-integration.test.ts +0 -25
  381. package/src/tests/migration-046-budgets.test.ts +17 -16
  382. package/src/tests/migration-217-chat-sdk-state.test.ts +113 -0
  383. package/src/tests/model-tier-resolution.test.ts +93 -0
  384. package/src/tests/models-catalog.test.ts +42 -1
  385. package/src/tests/node-env-runtime.test.ts +98 -0
  386. package/src/tests/openai-compatible-providers.test.ts +134 -0
  387. package/src/tests/openai-compatible-registry-extension.test.ts +42 -0
  388. package/src/tests/opencode-adapter.test.ts +348 -3
  389. package/src/tests/opencode-billing.test.ts +28 -0
  390. package/src/tests/opencode-helmcode-sdk.test.ts +201 -0
  391. package/src/tests/opencode-shell-env.test.ts +145 -0
  392. package/src/tests/openrouter-base-url.test.ts +101 -151
  393. package/src/tests/page-proxy.test.ts +1 -1
  394. package/src/tests/pages-browser-sdk.test.ts +76 -0
  395. package/src/tests/pages-feedback.test.ts +1 -1
  396. package/src/tests/paused-tasks-requested-by.test.ts +102 -0
  397. package/src/tests/pi-custom-provider-fake-server.test.ts +442 -0
  398. package/src/tests/pi-mono-adapter.test.ts +385 -5
  399. package/src/tests/pi-shell-env.test.ts +170 -0
  400. package/src/tests/prompt-template-remaining.test.ts +35 -0
  401. package/src/tests/rbac-engine.test.ts +11 -3
  402. package/src/tests/runner-exit-after-tasks.test.ts +0 -4
  403. package/src/tests/runner-identity-refresh.test.ts +3 -0
  404. package/src/tests/runner-legacy-resume-prompt.test.ts +64 -0
  405. package/src/tests/script-connections-sql.test.ts +675 -0
  406. package/src/tests/script-connections.test.ts +7 -1
  407. package/src/tests/scripts-http.test.ts +201 -1
  408. package/src/tests/scripts-only-gating.test.ts +2 -3
  409. package/src/tests/scripts-runtime.test.ts +58 -40
  410. package/src/tests/secret-scrubber.test.ts +86 -0
  411. package/src/tests/seed-skills-bundled-files.test.ts +45 -53
  412. package/src/tests/seed-skills-retire.test.ts +182 -0
  413. package/src/tests/send-task-requester-gate.test.ts +58 -0
  414. package/src/tests/session-cost-recompute-unit.test.ts +94 -0
  415. package/src/tests/skill-get-name-resolution.test.ts +137 -0
  416. package/src/tests/slack-channel-join.test.ts +4 -1
  417. package/src/tests/slack-external-channel-config.test.ts +65 -0
  418. package/src/tests/slack-external-channel-egress.test.ts +237 -0
  419. package/src/tests/slack-external-channel-guard.test.ts +280 -0
  420. package/src/tests/slack-external-channel-tools.test.ts +173 -0
  421. package/src/tests/slack-external-channel.test.ts +370 -0
  422. package/src/tests/slack-render-v2.test.ts +86 -10
  423. package/src/tests/sonnet-5-5-registry.test.ts +9 -1
  424. package/src/tests/spec-base-url.test.ts +16 -1
  425. package/src/tests/swarm-config-export-to-env.test.ts +267 -0
  426. package/src/tests/swarm-config-reserved-keys.test.ts +16 -0
  427. package/src/tests/swarm-diff.test.ts +1 -1
  428. package/src/tests/task-model-effort-rerun.test.ts +164 -0
  429. package/src/tests/task-session-prompts.test.ts +132 -0
  430. package/src/tests/task-tool-manifest.test.ts +14 -0
  431. package/src/tests/task-tools-ctx.test.ts +2 -0
  432. package/src/tests/tool-annotations.test.ts +6 -6
  433. package/src/tests/ui-logs-parser.test.ts +43 -0
  434. package/src/tests/used-by-route.test.ts +6 -0
  435. package/src/tests/vendored-openapi.test.ts +16 -1
  436. package/src/tests/vercel-ai-gateway.test.ts +149 -0
  437. package/src/tests/workflow-swarm-script.test.ts +38 -0
  438. package/src/tests/workflow-wait-filter.test.ts +5 -3
  439. package/src/tools/chat-access.ts +41 -0
  440. package/src/tools/chat-open-dm.ts +59 -0
  441. package/src/tools/chat-post.ts +74 -0
  442. package/src/tools/chat-read-thread.ts +100 -0
  443. package/src/tools/chat-reply.ts +75 -0
  444. package/src/tools/create-page.ts +3 -13
  445. package/src/tools/defer-task.ts +17 -0
  446. package/src/tools/model-catalog/index.ts +2 -1
  447. package/src/tools/register-chat-channel.ts +139 -0
  448. package/src/tools/send-task.ts +24 -0
  449. package/src/tools/skills/skill-get.ts +17 -9
  450. package/src/tools/slack-post.ts +6 -0
  451. package/src/tools/slack-read.ts +8 -0
  452. package/src/tools/slack-reply.ts +6 -0
  453. package/src/tools/slack-start-thread.ts +6 -0
  454. package/src/tools/slack-update.ts +6 -0
  455. package/src/tools/slack-upload-file.ts +6 -0
  456. package/src/tools/swarm-config/get-config.ts +2 -1
  457. package/src/tools/swarm-config/list-config.ts +2 -1
  458. package/src/tools/swarm-config/set-config.ts +16 -3
  459. package/src/tools/tool-config.ts +8 -6
  460. package/src/types.ts +12 -2
  461. package/src/utils/config-env-export.ts +74 -0
  462. package/src/utils/credentials.test.ts +92 -0
  463. package/src/utils/credentials.ts +52 -4
  464. package/src/utils/harness-provider.ts +15 -6
  465. package/src/utils/node-env.ts +24 -0
  466. package/src/utils/runtime-model-catalog.ts +11 -1
  467. package/src/utils/secret-scrubber.ts +71 -2
  468. package/src/utils/task-tool-manifest.ts +19 -2
  469. package/src/workflows/executors/swarm-script.ts +1 -0
  470. package/templates/skills/agent-browser/SKILL.md +1 -1
  471. package/templates/skills/agent-browser/content.md +1 -1
  472. package/templates/skills/apps/SKILL.md +749 -0
  473. package/templates/skills/apps/content.md +60 -22
  474. package/templates/skills/chat-sdk/config.json +14 -0
  475. package/templates/skills/chat-sdk/content.md +84 -0
  476. package/templates/skills/composio-gmail/content.md +7 -0
  477. package/templates/skills/composio-google-docs/content.md +1 -1
  478. package/templates/skills/email-domain-health/config.json +14 -0
  479. package/templates/skills/email-domain-health/content.md +131 -0
  480. package/templates/skills/kv-storage/content.md +2 -2
  481. package/templates/skills/pages/content.md +8 -10
  482. package/templates/skills/qa-loom/config.json +14 -0
  483. package/templates/skills/qa-loom/content.md +187 -0
  484. package/templates/skills/qa-loom/files/build-loom.mjs +221 -0
  485. package/templates/skills/qa-loom/files/run-qa.sh +57 -0
  486. package/templates/skills/slack-interaction/content.md +4 -0
  487. package/templates/skills/swarm-extensions/content.md +2 -0
  488. package/templates/skills/swarm-scripts/SKILL.md +4 -2
  489. package/templates/skills/swarm-scripts/config.json +1 -1
  490. package/templates/skills/swarm-scripts/content.md +4 -2
  491. package/templates/skills/workflow-iterate/content.md +38 -0
  492. package/dist/artifact-eczsknc3.js +0 -3843
  493. package/dist/cli-9xsmdpdt.js +0 -56377
  494. package/dist/cli-bd957dgb.js +0 -153
  495. package/dist/cli-q51yprfq.js +0 -1104
  496. package/dist/cli-t1ze64h3.js +0 -811
  497. package/dist/index-9ch1n761.js +0 -1102
  498. package/src/artifact-sdk/index.ts +0 -2
  499. package/src/artifact-sdk/localtunnel.d.ts +0 -20
  500. package/src/artifact-sdk/port.ts +0 -12
  501. package/src/artifact-sdk/server.ts +0 -183
  502. package/src/artifact-sdk/tunnel.ts +0 -19
  503. package/src/commands/artifact.ts +0 -249
  504. package/src/http/ecosystem.ts +0 -77
  505. package/src/tests/artifact-commands.test.ts +0 -92
  506. package/src/tests/artifact-sdk.test.ts +0 -793
  507. package/src/tests/register-service-security.test.ts +0 -194
  508. package/src/tools/list-services.ts +0 -107
  509. package/src/tools/register-service.ts +0 -177
  510. package/src/tools/unregister-service.ts +0 -73
  511. package/src/tools/update-service-status.ts +0 -96
  512. package/templates/skills/artifacts/config.json +0 -14
  513. package/templates/skills/artifacts/content.md +0 -344
  514. package/templates/skills/artifacts/files/examples/approval-flow.ts +0 -34
  515. package/templates/skills/artifacts/files/examples/hono-dashboard.ts +0 -31
  516. package/templates/skills/artifacts/files/examples/multi-artifact.ts +0 -20
  517. package/templates/skills/artifacts/files/examples/static-report.sh +0 -17
  518. package/dist/{azure-openai-responses-ap1gwxgr.js → azure-openai-responses-ygfreq4t.js} +5 -5
  519. package/dist/{cli-5rqrj7q0.js → cli-pw7ebjdt.js} +0 -0
  520. package/dist/{openai-codex-responses-33qhkkdy.js → openai-codex-responses-3bk0grnr.js} +4 -4
  521. package/dist/{openai-completions-s88xm3ns.js → openai-completions-5d4jpakc.js} +9 -9
  522. package/dist/{openai-responses-7xd2f2e2.js → openai-responses-krb51f59.js} +5 -5
  523. package/dist/{pi-messages-xszk613n.js → pi-messages-x9gzynm1.js} +3 -3
  524. /package/src/{artifact-sdk → pages}/browser-sdk.ts +0 -0
  525. /package/src/{artifact-sdk → pages}/feedback-overlay.ts +0 -0
@@ -0,0 +1,520 @@
1
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
+ import { unlink } from "node:fs/promises";
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import {
5
+ closeDb,
6
+ createAgent,
7
+ createTaskExtended,
8
+ getDbClient,
9
+ getKv,
10
+ getTaskById,
11
+ initDb,
12
+ } from "../be/db";
13
+ import { CHAT_CHANNELS_NAMESPACE } from "../integrations/chat-sdk/router";
14
+ import { createServer } from "../server";
15
+ import { chatSdkContextKey } from "../tasks/context-key";
16
+ import { registerChatOpenDmTool } from "../tools/chat-open-dm";
17
+ import { registerChatPostTool } from "../tools/chat-post";
18
+ import { registerChatReadThreadTool } from "../tools/chat-read-thread";
19
+ import { registerChatReplyTool } from "../tools/chat-reply";
20
+ import {
21
+ registerRegisterChatChannelTool,
22
+ registerUnregisterChatChannelTool,
23
+ } from "../tools/register-chat-channel";
24
+ import { registerSendTaskTool } from "../tools/send-task";
25
+ import type { Agent } from "../types";
26
+ import { startTelegramHarness, type TelegramHarness } from "./chat-sdk-harness";
27
+
28
+ const TEST_DB_PATH = "./test-chat-sdk-tools.sqlite";
29
+ const CHAT_TOOLS = [
30
+ "chat-open-dm",
31
+ "chat-post",
32
+ "chat-read-thread",
33
+ "chat-reply",
34
+ "register-chat-channel",
35
+ "unregister-chat-channel",
36
+ ];
37
+ const ENV_KEYS = ["CHAT_SDK_ENABLED", "CAPABILITIES", "SWARM_ENABLED_TOOLS"];
38
+ const savedEnv = new Map(ENV_KEYS.map((key) => [key, process.env[key]]));
39
+
40
+ let lead: Agent;
41
+ let worker: Agent;
42
+ let server: McpServer;
43
+
44
+ type ToolResult = {
45
+ isError?: boolean;
46
+ structuredContent: { success: boolean; message: string; [key: string]: unknown };
47
+ };
48
+
49
+ async function removeDbFiles(): Promise<void> {
50
+ for (const suffix of ["", "-wal", "-shm"]) {
51
+ await unlink(`${TEST_DB_PATH}${suffix}`).catch(() => {});
52
+ }
53
+ }
54
+
55
+ function restoreEnv(): void {
56
+ for (const [key, value] of savedEnv) {
57
+ if (value === undefined) delete process.env[key];
58
+ else process.env[key] = value;
59
+ }
60
+ }
61
+
62
+ async function chatToolNames(opts: Parameters<typeof createServer>[0] = {}): Promise<string[]> {
63
+ const created = (await createServer(opts)) as unknown as {
64
+ _registeredTools: Record<string, unknown>;
65
+ };
66
+ return Object.keys(created._registeredTools)
67
+ .filter((name) => CHAT_TOOLS.includes(name))
68
+ .sort();
69
+ }
70
+
71
+ async function callTool(
72
+ name: string,
73
+ headers: Record<string, string>,
74
+ args: Record<string, unknown>,
75
+ ): Promise<ToolResult> {
76
+ // biome-ignore lint/complexity/noBannedTypes: accessing internal MCP SDK type for test
77
+ const tools = (server as unknown as { _registeredTools: Record<string, { handler: Function }> })
78
+ ._registeredTools;
79
+ const handler = tools[name]?.handler;
80
+ if (!handler) throw new Error(`Tool not registered: ${name}`);
81
+ return (await handler(args, {
82
+ sessionId: "test-session",
83
+ requestInfo: { headers },
84
+ })) as ToolResult;
85
+ }
86
+
87
+ beforeAll(async () => {
88
+ await removeDbFiles();
89
+ for (const key of ENV_KEYS) delete process.env[key];
90
+ initDb(TEST_DB_PATH);
91
+ lead = await createAgent({
92
+ id: crypto.randomUUID(),
93
+ name: "ChatToolsLead",
94
+ isLead: true,
95
+ status: "idle",
96
+ });
97
+ worker = await createAgent({
98
+ id: crypto.randomUUID(),
99
+ name: "ChatToolsWorker",
100
+ isLead: false,
101
+ status: "idle",
102
+ });
103
+ server = new McpServer({ name: "chat-tools-test", version: "1.0.0" });
104
+ registerChatReplyTool(server);
105
+ registerChatPostTool(server);
106
+ registerChatOpenDmTool(server);
107
+ registerChatReadThreadTool(server);
108
+ registerRegisterChatChannelTool(server);
109
+ registerUnregisterChatChannelTool(server);
110
+ registerSendTaskTool(server);
111
+ });
112
+
113
+ afterAll(async () => {
114
+ restoreEnv();
115
+ closeDb();
116
+ await removeDbFiles();
117
+ });
118
+
119
+ describe("chat-sdk tool registration", () => {
120
+ test("the six tools register only when CHAT_SDK_ENABLED is true", async () => {
121
+ try {
122
+ delete process.env.CHAT_SDK_ENABLED;
123
+ expect(await chatToolNames()).toEqual([]);
124
+
125
+ // The CAPABILITIES list alone does not switch the channel on.
126
+ process.env.CAPABILITIES = "core,chat-sdk";
127
+ expect(await chatToolNames()).toEqual([]);
128
+ delete process.env.CAPABILITIES;
129
+
130
+ process.env.CHAT_SDK_ENABLED = "true";
131
+ expect(await chatToolNames()).toEqual(CHAT_TOOLS);
132
+ // A session bound to a non-chat task drops chat-reply.
133
+ expect(await chatToolNames({ excludedTools: ["chat-reply"] })).toEqual(
134
+ CHAT_TOOLS.filter((name) => name !== "chat-reply"),
135
+ );
136
+ delete process.env.CHAT_SDK_ENABLED;
137
+ } finally {
138
+ restoreEnv();
139
+ }
140
+ });
141
+
142
+ test("the full surface (scripts bridge) always has them", async () => {
143
+ delete process.env.CHAT_SDK_ENABLED;
144
+ expect(await chatToolNames({ fullSurface: true })).toEqual(CHAT_TOOLS);
145
+ });
146
+ });
147
+
148
+ describe("chat-reply", () => {
149
+ let harness: TelegramHarness;
150
+
151
+ beforeAll(async () => {
152
+ harness = await startTelegramHarness();
153
+ });
154
+
155
+ afterAll(async () => {
156
+ await harness.stop();
157
+ });
158
+
159
+ test("refuses a task without a chat-sdk contextKey", async () => {
160
+ const task = await createTaskExtended("not a chat task", {
161
+ agentId: worker.id,
162
+ source: "mcp",
163
+ });
164
+ const result = await callTool(
165
+ "chat-reply",
166
+ { "x-agent-id": worker.id, "x-source-task-id": task.id },
167
+ { text: "hello" },
168
+ );
169
+ expect(result.structuredContent.success).toBe(false);
170
+ expect(result.structuredContent.message).toContain("did not come from a chat thread");
171
+ });
172
+
173
+ test("refuses another agent's task", async () => {
174
+ const task = await createTaskExtended("someone else's chat task", {
175
+ agentId: lead.id,
176
+ source: "chat",
177
+ contextKey: chatSdkContextKey("telegram:991001"),
178
+ });
179
+ const result = await callTool(
180
+ "chat-reply",
181
+ { "x-agent-id": worker.id, "x-source-task-id": task.id },
182
+ { text: "hello" },
183
+ );
184
+ expect(result.structuredContent.success).toBe(false);
185
+ expect(
186
+ harness.mock.calls.some(
187
+ (call) => String((call.body as { chat_id?: unknown }).chat_id) === "991001",
188
+ ),
189
+ ).toBe(false);
190
+ });
191
+
192
+ test("posts to the thread of a chat task", async () => {
193
+ const task = await createTaskExtended("a chat task", {
194
+ agentId: worker.id,
195
+ source: "chat",
196
+ contextKey: chatSdkContextKey("telegram:991002"),
197
+ });
198
+ const result = await callTool(
199
+ "chat-reply",
200
+ { "x-agent-id": worker.id, "x-source-task-id": task.id },
201
+ { text: "Which branch should I use?" },
202
+ );
203
+ expect(result.structuredContent.success).toBe(true);
204
+ expect(result.structuredContent).toMatchObject({
205
+ adapter: "telegram",
206
+ threadId: "telegram:991002",
207
+ });
208
+ expect(typeof result.structuredContent.messageId).toBe("string");
209
+ const sends = harness.mock.calls.filter(
210
+ (call) =>
211
+ /^send(Rich)?Message$/.test(call.method) &&
212
+ String((call.body as { chat_id?: unknown }).chat_id) === "991002",
213
+ );
214
+ expect(sends).toHaveLength(1);
215
+ });
216
+ });
217
+
218
+ describe("register-chat-channel", () => {
219
+ test("is refused for a non-lead agent", async () => {
220
+ const result = await callTool(
221
+ "register-chat-channel",
222
+ { "x-agent-id": worker.id },
223
+ { adapter: "telegram", channelId: "telegram:-100555", agentId: worker.id },
224
+ );
225
+ expect(result.structuredContent.success).toBe(false);
226
+ expect(result.structuredContent.message).toContain("Permission denied");
227
+ expect(await getKv(CHAT_CHANNELS_NAMESPACE, "telegram:-100555")).toBeNull();
228
+
229
+ const unregister = await callTool(
230
+ "unregister-chat-channel",
231
+ { "x-agent-id": worker.id },
232
+ { adapter: "telegram", channelId: "telegram:-100555" },
233
+ );
234
+ expect(unregister.structuredContent.message).toContain("Permission denied");
235
+ });
236
+
237
+ test("a lead writes and removes the mapping under chatChannelKey", async () => {
238
+ const both = await callTool(
239
+ "register-chat-channel",
240
+ { "x-agent-id": lead.id },
241
+ { adapter: "telegram", channelId: "-100556", agentId: worker.id, workflowId: "w" },
242
+ );
243
+ expect(both.structuredContent.success).toBe(false);
244
+
245
+ // A bare platform id gets the adapter prefix; a prefixed id is kept.
246
+ const result = await callTool(
247
+ "register-chat-channel",
248
+ { "x-agent-id": lead.id },
249
+ { adapter: "telegram", channelId: "-100556", agentId: worker.id, name: "ops" },
250
+ );
251
+ expect(result.structuredContent.success).toBe(true);
252
+ expect(result.structuredContent.key).toBe("telegram:-100556");
253
+ const stored = await getKv(CHAT_CHANNELS_NAMESPACE, "telegram:-100556");
254
+ expect(stored?.value).toMatchObject({ agentId: worker.id, name: "ops" });
255
+
256
+ const removed = await callTool(
257
+ "unregister-chat-channel",
258
+ { "x-agent-id": lead.id },
259
+ { adapter: "telegram", channelId: "telegram:-100556" },
260
+ );
261
+ expect(removed.structuredContent.success).toBe(true);
262
+ expect(await getKv(CHAT_CHANNELS_NAMESPACE, "telegram:-100556")).toBeNull();
263
+ });
264
+ });
265
+
266
+ describe("chat tool permissions", () => {
267
+ let harness: TelegramHarness;
268
+
269
+ beforeAll(async () => {
270
+ harness = await startTelegramHarness();
271
+ });
272
+
273
+ afterAll(async () => {
274
+ await harness.stop();
275
+ });
276
+
277
+ const sendsTo = (chatId: string) =>
278
+ harness.mock.calls.filter(
279
+ (call) =>
280
+ /^send(Rich)?Message$/.test(call.method) &&
281
+ String((call.body as { chat_id?: unknown }).chat_id) === chatId,
282
+ );
283
+
284
+ test("chat-post is refused for a worker and sends nothing", async () => {
285
+ const result = await callTool(
286
+ "chat-post",
287
+ { "x-agent-id": worker.id },
288
+ { adapter: "telegram", threadId: "telegram:991010", text: "hello" },
289
+ );
290
+ expect(result.structuredContent.success).toBe(false);
291
+ expect(result.structuredContent.message).toContain("Permission denied");
292
+ expect(sendsTo("991010")).toHaveLength(0);
293
+ });
294
+
295
+ test("chat-post lets the lead post to any thread", async () => {
296
+ const result = await callTool(
297
+ "chat-post",
298
+ { "x-agent-id": lead.id },
299
+ { adapter: "telegram", threadId: "telegram:991010", text: "hello" },
300
+ );
301
+ expect(result.structuredContent.success).toBe(true);
302
+ expect(result.structuredContent.threadId).toBe("telegram:991010");
303
+ expect(sendsTo("991010")).toHaveLength(1);
304
+ });
305
+
306
+ test("chat-open-dm is refused for a worker and allowed for the lead", async () => {
307
+ const refused = await callTool(
308
+ "chat-open-dm",
309
+ { "x-agent-id": worker.id },
310
+ { adapter: "telegram", userId: "991011" },
311
+ );
312
+ expect(refused.structuredContent.success).toBe(false);
313
+ expect(refused.structuredContent.message).toContain("Permission denied");
314
+
315
+ const opened = await callTool(
316
+ "chat-open-dm",
317
+ { "x-agent-id": lead.id },
318
+ { adapter: "telegram", userId: "991011" },
319
+ );
320
+ expect(opened.structuredContent.success).toBe(true);
321
+ expect(opened.structuredContent.threadId).toBe("telegram:991011");
322
+ });
323
+
324
+ test("chat-read-thread: a worker reads its task thread, not another one", async () => {
325
+ const task = await createTaskExtended("worker chat task", {
326
+ agentId: worker.id,
327
+ source: "chat",
328
+ contextKey: chatSdkContextKey("telegram:991012"),
329
+ });
330
+ const own = await callTool(
331
+ "chat-read-thread",
332
+ { "x-agent-id": worker.id, "x-source-task-id": task.id },
333
+ { threadId: "telegram:991012" },
334
+ );
335
+ expect(own.structuredContent.success).toBe(true);
336
+ expect(own.structuredContent.threadId).toBe("telegram:991012");
337
+
338
+ const other = await callTool(
339
+ "chat-read-thread",
340
+ { "x-agent-id": worker.id, "x-source-task-id": task.id },
341
+ { threadId: "telegram:991013" },
342
+ );
343
+ expect(other.structuredContent.success).toBe(false);
344
+ expect(other.structuredContent.message).toContain("Permission denied");
345
+ expect(other.structuredContent.message).toContain("telegram:991012");
346
+
347
+ // Without a task binding at all, a worker gets nothing.
348
+ const noTask = await callTool(
349
+ "chat-read-thread",
350
+ { "x-agent-id": worker.id },
351
+ { threadId: "telegram:991013" },
352
+ );
353
+ expect(noTask.structuredContent.success).toBe(false);
354
+ expect(noTask.structuredContent.message).toContain("Permission denied");
355
+ });
356
+
357
+ test("chat-read-thread lets the lead read any thread", async () => {
358
+ const result = await callTool(
359
+ "chat-read-thread",
360
+ { "x-agent-id": lead.id },
361
+ { threadId: "telegram:991013" },
362
+ );
363
+ expect(result.structuredContent.success).toBe(true);
364
+ expect(result.structuredContent.threadId).toBe("telegram:991013");
365
+ });
366
+ });
367
+
368
+ describe("chat thread binding through send-task", () => {
369
+ let harness: TelegramHarness;
370
+
371
+ beforeAll(async () => {
372
+ harness = await startTelegramHarness();
373
+ });
374
+
375
+ afterAll(async () => {
376
+ await harness.stop();
377
+ });
378
+
379
+ const sendsTo = (chatId: string) =>
380
+ harness.mock.calls.filter(
381
+ (call) =>
382
+ /^send(Rich)?Message$/.test(call.method) &&
383
+ String((call.body as { chat_id?: unknown }).chat_id) === chatId,
384
+ );
385
+
386
+ const childrenOf = async (parentId: string) =>
387
+ await getDbClient().query<{ id: string }>("SELECT id FROM agent_tasks WHERE parentTaskId = ?", [
388
+ parentId,
389
+ ]);
390
+
391
+ test("a worker cannot create a child under a foreign chat task, so it gains no thread access", async () => {
392
+ const own = await createTaskExtended("worker chat task A", {
393
+ agentId: worker.id,
394
+ source: "chat",
395
+ contextKey: chatSdkContextKey("telegram:111111"),
396
+ });
397
+ const foreign = await createTaskExtended("lead chat task B", {
398
+ agentId: lead.id,
399
+ source: "chat",
400
+ contextKey: chatSdkContextKey("telegram:222222"),
401
+ });
402
+
403
+ // An explicit foreign parentTaskId, routed to the parent's assignee or offered to the
404
+ // worker itself, and a foreign X-Source-Task-ID as the implicit parent: all refused.
405
+ const attempts: Array<{ headers: Record<string, string>; args: Record<string, unknown> }> = [
406
+ {
407
+ headers: { "x-agent-id": worker.id, "x-source-task-id": own.id },
408
+ args: { task: "child under B", parentTaskId: foreign.id, allowDuplicate: true },
409
+ },
410
+ {
411
+ headers: { "x-agent-id": worker.id, "x-source-task-id": own.id },
412
+ args: {
413
+ task: "child under B offered to me",
414
+ parentTaskId: foreign.id,
415
+ offerMode: true,
416
+ allowDuplicate: true,
417
+ },
418
+ },
419
+ {
420
+ headers: { "x-agent-id": worker.id, "x-source-task-id": foreign.id },
421
+ args: { task: "child under the header task", allowDuplicate: true },
422
+ },
423
+ ];
424
+ for (const { headers, args } of attempts) {
425
+ const result = await callTool("send-task", headers, args);
426
+ expect(result.structuredContent.success, String(args.task)).toBe(false);
427
+ expect(result.structuredContent.message).toContain(
428
+ `You don't have context for parent task ${foreign.id}`,
429
+ );
430
+ }
431
+ expect(await childrenOf(foreign.id)).toHaveLength(0);
432
+
433
+ // Without a manufactured child, the worker's only binding is its own thread.
434
+ const read = await callTool(
435
+ "chat-read-thread",
436
+ { "x-agent-id": worker.id, "x-source-task-id": own.id },
437
+ { threadId: "telegram:222222" },
438
+ );
439
+ expect(read.structuredContent.success).toBe(false);
440
+ expect(read.structuredContent.message).toContain("Permission denied");
441
+ const reply = await callTool(
442
+ "chat-reply",
443
+ { "x-agent-id": worker.id, "x-source-task-id": foreign.id },
444
+ { text: "hijack" },
445
+ );
446
+ expect(reply.structuredContent.success).toBe(false);
447
+ expect(sendsTo("222222")).toHaveLength(0);
448
+ });
449
+
450
+ test("the lead delegates its chat task, and the assigned worker replies in that thread", async () => {
451
+ const delegate = await createAgent({
452
+ id: crypto.randomUUID(),
453
+ name: "ChatToolsDelegate",
454
+ isLead: false,
455
+ status: "idle",
456
+ maxTasks: 5,
457
+ });
458
+ const parent = await createTaskExtended("lead chat task", {
459
+ agentId: lead.id,
460
+ source: "chat",
461
+ contextKey: chatSdkContextKey("telegram:333333"),
462
+ });
463
+
464
+ const sent = await callTool(
465
+ "send-task",
466
+ { "x-agent-id": lead.id, "x-source-task-id": parent.id },
467
+ {
468
+ task: "do the chat work",
469
+ agentId: delegate.id,
470
+ routingReason: "skill",
471
+ routingNote: "Delegate owns this kind of chat work.",
472
+ allowDuplicate: true,
473
+ },
474
+ );
475
+ expect(sent.structuredContent.success).toBe(true);
476
+ const childId = (sent.structuredContent.task as { id: string }).id;
477
+ const child = await getTaskById(childId);
478
+ expect(child?.parentTaskId).toBe(parent.id);
479
+ expect(child?.contextKey).toBe(chatSdkContextKey("telegram:333333"));
480
+
481
+ const reply = await callTool(
482
+ "chat-reply",
483
+ { "x-agent-id": delegate.id, "x-source-task-id": childId },
484
+ { text: "Working on it." },
485
+ );
486
+ expect(reply.structuredContent.success).toBe(true);
487
+ expect(reply.structuredContent.threadId).toBe("telegram:333333");
488
+ expect(sendsTo("333333")).toHaveLength(1);
489
+ });
490
+
491
+ test("a worker sub-delegates under its own chat task", async () => {
492
+ const helper = await createAgent({
493
+ id: crypto.randomUUID(),
494
+ name: "ChatToolsHelper",
495
+ isLead: false,
496
+ status: "idle",
497
+ maxTasks: 5,
498
+ });
499
+ const own = await createTaskExtended("worker chat task to split", {
500
+ agentId: worker.id,
501
+ source: "chat",
502
+ contextKey: chatSdkContextKey("telegram:444444"),
503
+ });
504
+ const sent = await callTool(
505
+ "send-task",
506
+ { "x-agent-id": worker.id, "x-source-task-id": own.id },
507
+ {
508
+ task: "do half of the chat work",
509
+ agentId: helper.id,
510
+ routingReason: "skill",
511
+ routingNote: "Helper takes the second half of the work.",
512
+ allowDuplicate: true,
513
+ },
514
+ );
515
+ expect(sent.structuredContent.success).toBe(true);
516
+ const child = await getTaskById((sent.structuredContent.task as { id: string }).id);
517
+ expect(child?.parentTaskId).toBe(own.id);
518
+ expect(child?.contextKey).toBe(chatSdkContextKey("telegram:444444"));
519
+ });
520
+ });
@@ -0,0 +1,120 @@
1
+ import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
2
+ import { unlink } from "node:fs/promises";
3
+ import { closeDb, completeTask, createAgent, getDbClient, initDb } from "../be/db";
4
+ import { processPendingChatRelays, stopChatRelayWatcher } from "../integrations/chat-sdk/relay";
5
+ import { setChatSdkTestHooks } from "../integrations/chat-sdk/test-hooks";
6
+ import { stopAllTypingKeepalives } from "../integrations/chat-sdk/typing";
7
+ import { chatSdkContextKey } from "../tasks/context-key";
8
+ import {
9
+ flushChatBuffers,
10
+ postTelegramUpdate,
11
+ startTelegramHarness,
12
+ type TelegramHarness,
13
+ telegramUpdate,
14
+ } from "./chat-sdk-harness";
15
+
16
+ const TEST_DB_PATH = "./test-chat-sdk-typing.sqlite";
17
+ const TYPING_INTERVAL_MS = 50;
18
+
19
+ let harness: TelegramHarness;
20
+ let nextChatId = 990_000;
21
+
22
+ async function removeDbFiles(): Promise<void> {
23
+ for (const suffix of ["", "-wal", "-shm"]) {
24
+ await unlink(`${TEST_DB_PATH}${suffix}`).catch(() => {});
25
+ }
26
+ }
27
+
28
+ function typingCalls(chatId: number): number {
29
+ return harness.mock.calls.filter(
30
+ (call) =>
31
+ call.method === "sendChatAction" &&
32
+ String((call.body as Record<string, unknown>).chat_id) === String(chatId),
33
+ ).length;
34
+ }
35
+
36
+ const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
37
+
38
+ beforeAll(async () => {
39
+ await removeDbFiles();
40
+ initDb(TEST_DB_PATH);
41
+ await createAgent({
42
+ id: crypto.randomUUID(),
43
+ name: "ChatTypingLead",
44
+ isLead: true,
45
+ status: "idle",
46
+ });
47
+ harness = await startTelegramHarness();
48
+ // Relay ticks are driven by the tests.
49
+ await stopChatRelayWatcher();
50
+ });
51
+
52
+ afterAll(async () => {
53
+ stopAllTypingKeepalives();
54
+ setChatSdkTestHooks({});
55
+ await harness.stop();
56
+ closeDb();
57
+ await removeDbFiles();
58
+ });
59
+
60
+ afterEach(() => {
61
+ stopAllTypingKeepalives();
62
+ setChatSdkTestHooks({});
63
+ });
64
+
65
+ describe("Chat SDK typing keepalive", () => {
66
+ // Timing-sensitive: counts real-timer renewals over a short window.
67
+ test(
68
+ "a buffered DM keeps sending the typing action",
69
+ async () => {
70
+ setChatSdkTestHooks({ typingIntervalMs: TYPING_INTERVAL_MS });
71
+ const chatId = ++nextChatId;
72
+ await postTelegramUpdate(telegramUpdate({ id: chatId, type: "private" }, "hello"));
73
+ await wait(TYPING_INTERVAL_MS * 6);
74
+ // The adapter sends one action on arrival; the keepalive adds the rest.
75
+ expect(typingCalls(chatId)).toBeGreaterThan(2);
76
+ },
77
+ { retry: 2 },
78
+ );
79
+
80
+ // Timing-sensitive: asserts that no renewal arrives after the relay.
81
+ test(
82
+ "the relay of the task output stops the typing action",
83
+ async () => {
84
+ setChatSdkTestHooks({ typingIntervalMs: TYPING_INTERVAL_MS });
85
+ const chatId = ++nextChatId;
86
+ await postTelegramUpdate(telegramUpdate({ id: chatId, type: "private" }, "do the thing"));
87
+ await flushChatBuffers();
88
+ const task = await getDbClient().get<{ id: string }>(
89
+ "SELECT id FROM agent_tasks WHERE contextKey = ?",
90
+ [chatSdkContextKey(`telegram:${chatId}`)],
91
+ );
92
+ expect(task).toBeTruthy();
93
+ await completeTask(task!.id, "Done.");
94
+ await processPendingChatRelays();
95
+ // Let a renewal that was already in flight land.
96
+ await wait(TYPING_INTERVAL_MS * 2);
97
+ const afterRelay = typingCalls(chatId);
98
+ expect(afterRelay).toBeGreaterThan(1);
99
+ await wait(TYPING_INTERVAL_MS * 6);
100
+ expect(typingCalls(chatId)).toBe(afterRelay);
101
+ },
102
+ { retry: 2 },
103
+ );
104
+
105
+ // Timing-sensitive: waits past a shortened hard cap.
106
+ test(
107
+ "the hard cap stops the typing action",
108
+ async () => {
109
+ setChatSdkTestHooks({ typingIntervalMs: TYPING_INTERVAL_MS, typingMaxMs: 200 });
110
+ const chatId = ++nextChatId;
111
+ await postTelegramUpdate(telegramUpdate({ id: chatId, type: "private" }, "slow request"));
112
+ await wait(400);
113
+ const afterCap = typingCalls(chatId);
114
+ expect(afterCap).toBeGreaterThan(1);
115
+ await wait(TYPING_INTERVAL_MS * 6);
116
+ expect(typingCalls(chatId)).toBe(afterCap);
117
+ },
118
+ { retry: 2 },
119
+ );
120
+ });