@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
@@ -95,6 +95,7 @@ type:McpServerFilters
95
95
  type:McpServerInsert
96
96
  type:MentionPreview
97
97
  type:PageListOptions
98
+ type:PendingChatRelay
98
99
  type:PlanSource
99
100
  type:RecordBudgetRefusalNotificationInput
100
101
  type:ScheduledTaskFilters
@@ -260,6 +261,7 @@ value:getAgentMcpServers
260
261
  value:getAgentSkills
261
262
  value:getAgentStatusCounts
262
263
  value:getAgentWithTasks
264
+ value:getAgentWorkingOnContextKey
263
265
  value:getAgentWorkingOnThread
264
266
  value:getAllAgentMailInboxMappings
265
267
  value:getAllAgents
@@ -321,8 +323,10 @@ value:getKv
321
323
  value:getLastReadAt
322
324
  value:getLastRunStart
323
325
  value:getLastSuccessfulRun
326
+ value:getLatestActiveTaskByContextKey
324
327
  value:getLatestActiveTaskInThread
325
328
  value:getLatestContextVersion
329
+ value:getLatestLeadTaskByContextKey
326
330
  value:getLatestLeadTaskInThread
327
331
  value:getLatestPageBySlug
328
332
  value:getLatestScriptRunStepTaskByContextKey
@@ -352,6 +356,7 @@ value:getPageVersion
352
356
  value:getPageVersions
353
357
  value:getPausedTasksForAgent
354
358
  value:getPendingApprovalVoteState
359
+ value:getPendingChatRelayTasks
355
360
  value:getPendingEventWaitNames
356
361
  value:getPendingSlackRelayTasks
357
362
  value:getPendingSteeringForAgent
@@ -449,6 +454,7 @@ value:getWorkflowRunStepsByRunId
449
454
  value:getWorkflowVersion
450
455
  value:getWorkflowVersions
451
456
  value:getWorkflowsByScheduleId
457
+ value:giveUpStaleChatRelays
452
458
  value:hasBudgetRefusalNotificationToday
453
459
  value:hasCapacity
454
460
  value:hasFirstCompletedTask
@@ -500,6 +506,9 @@ value:listUserFavorites
500
506
  value:listWorkflowRuns
501
507
  value:listWorkflowRunsPage
502
508
  value:listWorkflows
509
+ value:markChatRelayAttempted
510
+ value:markChatRelayDelivered
511
+ value:markChatRelayGivenUp
503
512
  value:markFinalizedSlackRelaysDelivered
504
513
  value:markInboxMessageDelegated
505
514
  value:markInboxMessageRead
@@ -528,6 +537,7 @@ value:promoteDraftTask
528
537
  value:reassociateSessionLogs
529
538
  value:recordApprovalVotes
530
539
  value:recordBudgetRefusalNotification
540
+ value:recordChatRelayError
531
541
  value:recordInlineScriptRun
532
542
  value:recordKeyAuthFailure
533
543
  value:recordKeyRateLimitWindows
@@ -76,9 +76,9 @@ afterAll(async () => {
76
76
  await rm(dir, { recursive: true, force: true });
77
77
  });
78
78
 
79
- async function activeTask() {
79
+ async function activeTask(owner = agentId) {
80
80
  const task = await createTaskExtended("work in progress", {
81
- agentId,
81
+ agentId: owner,
82
82
  modelTier: "smart",
83
83
  priority: 70,
84
84
  });
@@ -90,6 +90,7 @@ async function defer(
90
90
  watchedId: string,
91
91
  event: TaskWakeEvent = "settled",
92
92
  extra: Record<string, unknown> = {},
93
+ caller = agentId,
93
94
  ) {
94
95
  return tool.handler(
95
96
  {
@@ -101,7 +102,7 @@ async function defer(
101
102
  checks: ["verify output"],
102
103
  ...extra,
103
104
  },
104
- { sessionId: crypto.randomUUID(), requestInfo: { headers: { "x-agent-id": agentId } } },
105
+ { sessionId: crypto.randomUUID(), requestInfo: { headers: { "x-agent-id": caller } } },
105
106
  );
106
107
  }
107
108
  async function fixture(event: TaskWakeEvent = "settled") {
@@ -364,10 +365,57 @@ describe("defer-task wakeOn", () => {
364
365
  expect(await children(schedule.id)).toHaveLength(1);
365
366
  });
366
367
 
368
+ test("duplicate watcher admission is atomic, agent-scoped, and permits the fired chain to re-defer", async () => {
369
+ const producer = await activeTask();
370
+ const sibling = await activeTask();
371
+ const first = await activeTask();
372
+ const second = await activeTask();
373
+ const results = await Promise.all([
374
+ defer(first.id, producer.id, "settled", {
375
+ wakeOn: { event: "settled", taskIds: [producer.id, sibling.id] },
376
+ }),
377
+ defer(second.id, producer.id),
378
+ ]);
379
+ const accepted = results.find((result) => result.structuredContent.success)!;
380
+ const rejected = results.find((result) => !result.structuredContent.success)!;
381
+ expect(accepted).toBeDefined();
382
+ expect(rejected).toBeDefined();
383
+ expect(rejected.structuredContent.message).toContain(accepted.structuredContent.scheduleId!);
384
+ const rejectedTask = results[0] === rejected ? first : second;
385
+ expect((await getTaskById(rejectedTask.id))?.status).toBe("in_progress");
386
+ expect(
387
+ await getDbClient().query("SELECT id FROM scheduled_tasks WHERE parentTaskId = ?", [
388
+ rejectedTask.id,
389
+ ]),
390
+ ).toHaveLength(0);
391
+
392
+ const other = await createAgent({ name: "Independent watcher", isLead: false, status: "idle" });
393
+ const otherWatcher = await activeTask(other.id);
394
+ expect(
395
+ (await defer(otherWatcher.id, producer.id, "settled", {}, other.id)).structuredContent
396
+ .success,
397
+ ).toBe(true);
398
+ const dispatched = await dispatchDeferredTaskWait(
399
+ accepted.structuredContent.scheduleId!,
400
+ "ceiling",
401
+ );
402
+ expect(dispatched?.task).toBeDefined();
403
+ await startTask(dispatched!.task!.id);
404
+ expect((await defer(dispatched!.task!.id, producer.id)).structuredContent.success).toBe(true);
405
+ });
406
+
367
407
  test("two waiters follow a time-based deferral and fire once on the resume task's completion", async () => {
368
408
  await initDeferredTaskWaits();
369
409
  const { producer, schedule } = await fixture();
370
- const second = await defer((await activeTask()).id, producer.id, "task.completed");
410
+ const secondAgent = await createAgent({ name: "Second waiter", isLead: false, status: "idle" });
411
+ const second = await defer(
412
+ (await activeTask(secondAgent.id)).id,
413
+ producer.id,
414
+ "task.completed",
415
+ {},
416
+ secondAgent.id,
417
+ );
418
+ expect(second.structuredContent.success).toBe(true);
371
419
  const secondSchedule = (await getScheduledTaskById(second.structuredContent.scheduleId!))!;
372
420
  const deferred = await defer(producer.id, producer.id, "settled", { wakeOn: undefined });
373
421
  expect(deferred.structuredContent.success).toBe(true);
@@ -78,7 +78,7 @@ describe("docs API index footprint", () => {
78
78
  expect(tasks).toContain('"path":"/api/task-templates","method":"get"');
79
79
  });
80
80
 
81
- test("OpenAPI and generated pages keep all 423 operations", async () => {
81
+ test("OpenAPI and generated pages keep all 425 operations", async () => {
82
82
  const spec = await Bun.file(resolve(repoRoot, "openapi.json")).json();
83
83
  const operationKeys = [];
84
84
  const tags = new Set();
@@ -103,8 +103,8 @@ describe("docs API index footprint", () => {
103
103
  expect(spec.paths["/api/agents/{id}/runtime"]?.get).toBeDefined();
104
104
 
105
105
  const generatedKeys = await generatedOperationKeys();
106
- expect(operationKeys).toHaveLength(423);
107
- expect(generatedKeys).toHaveLength(423);
106
+ expect(operationKeys).toHaveLength(425);
107
+ expect(generatedKeys).toHaveLength(425);
108
108
  expect(new Set(generatedKeys)).toEqual(new Set(operationKeys));
109
109
  });
110
110
 
@@ -376,7 +376,7 @@ describe("dsh harness", () => {
376
376
  await rename(join(config.cwd, "dsh"), join(config.cwd, "npx"));
377
377
  config.env = { ...config.env, DSH_BINARY: "", PATH: config.cwd };
378
378
  await expect(new DshAdapter().createSession(config)).rejects.toThrow(
379
- "dsh CLI not found. Install @deepseek-ai/dsh@0.2.1-alpha.2",
379
+ "dsh CLI not found. Install @deepseek-ai/dsh@0.2.1-alpha.4",
380
380
  );
381
381
  expect(await Bun.file(join(config.cwd, "invocation.json")).exists()).toBe(false);
382
382
  });
@@ -0,0 +1,20 @@
1
+ import { afterEach, expect, test } from "bun:test";
2
+ import { ALL_CAPABILITIES, getEnabledCapabilities, hasCapability } from "../server";
3
+
4
+ const originalCapabilities = process.env.CAPABILITIES;
5
+
6
+ afterEach(() => {
7
+ if (originalCapabilities === undefined) delete process.env.CAPABILITIES;
8
+ else process.env.CAPABILITIES = originalCapabilities;
9
+ });
10
+
11
+ // `services` was a real capability until the PM2 service registry was removed.
12
+ // A deployment that still lists it must keep registering agents: the register
13
+ // response validates enabledCapabilities against ALL_CAPABILITIES.
14
+ test("enabledCapabilities drops retired and free-form names", () => {
15
+ process.env.CAPABILITIES = "core,services,artifacts,pages";
16
+
17
+ expect(ALL_CAPABILITIES as readonly string[]).not.toContain("services");
18
+ expect(getEnabledCapabilities()).toEqual(["core", "pages"]);
19
+ expect(hasCapability("pages")).toBe(true);
20
+ });
@@ -221,28 +221,34 @@ describe("wait_for_api_ready: immediate success", () => {
221
221
 
222
222
  describe("wait_for_api_ready: transient failure then success", () => {
223
223
  test("retries roughly once per second until the server recovers", async () => {
224
- let requestCount = 0;
224
+ // A per-test path prefix: an ephemeral port can be reused from another test
225
+ // process whose poller still hits /health, and those stray requests used
226
+ // to count as retries (3 requests in 1033 ms on CI).
227
+ const prefix = `/${crypto.randomUUID()}`;
228
+ const requestTimes: number[] = [];
225
229
  server = Bun.serve({
226
230
  port: 0,
227
231
  hostname: "127.0.0.1",
228
232
  fetch(req) {
229
233
  const url = new URL(req.url);
230
- if (url.pathname !== "/health") return new Response("not found", { status: 404 });
231
- requestCount += 1;
232
- if (requestCount < 3) return new Response("unavailable", { status: 503 });
234
+ if (url.pathname !== `${prefix}/health`) return new Response("not found", { status: 404 });
235
+ requestTimes.push(performance.now());
236
+ if (requestTimes.length < 3) return new Response("unavailable", { status: 503 });
233
237
  return new Response("ok", { status: 200 });
234
238
  },
235
239
  });
236
240
 
237
- const result = await runWaitForApiReady(server.url.toString(), {
241
+ const result = await runWaitForApiReady(`${server.url.origin}${prefix}/`, {
238
242
  WORKER_API_READY_TIMEOUT_SECONDS: "10",
239
243
  });
240
244
 
241
245
  expect(result.exitCode).toBe(0);
242
- expect(requestCount).toBeGreaterThanOrEqual(3);
246
+ expect(requestTimes).toHaveLength(3);
243
247
  expect(result.stdout).toContain("API ready at");
244
- // Two ~1s retries before success — bounded, not instant, not stalled.
245
- expect(result.durationMs).toBeGreaterThanOrEqual(1500);
248
+ // Each retry waits out `sleep 1` after the previous response, so the
249
+ // server-side gap cannot shrink under load: paced, not a hot loop.
250
+ expect(requestTimes[1]! - requestTimes[0]!).toBeGreaterThanOrEqual(900);
251
+ expect(requestTimes[2]! - requestTimes[1]!).toBeGreaterThanOrEqual(900);
246
252
  expect(result.durationMs).toBeLessThan(15000);
247
253
  }, 20000);
248
254
  });
@@ -93,7 +93,10 @@ function extractExportFilter(lineMarker: string): string {
93
93
  const line = script.split("\n").find((l) => l.includes(lineMarker) && l.includes("jq -r '"));
94
94
  if (!line) throw new Error(`Could not locate the jq line containing ${lineMarker}`);
95
95
  const start = line.indexOf("jq -r '") + "jq -r '".length;
96
- const end = line.indexOf("' /tmp/swarm_config.json", start);
96
+ // Inline programs read /tmp/swarm_config.json; the swarm_config_env_lines
97
+ // function reads its "$1" argument.
98
+ const ends = ["' /tmp/swarm_config.json", '\' "$1"'].map((tail) => line.indexOf(tail, start));
99
+ const end = ends.find((index) => index !== -1) ?? -1;
97
100
  if (end === -1) throw new Error(`Could not locate the end of the jq program for ${lineMarker}`);
98
101
  return line.slice(start, end);
99
102
  }
@@ -127,7 +130,7 @@ describe("entrypoint config env export: real jq filter", () => {
127
130
  test(
128
131
  "never exports codex_oauth or codex_oauth_<n>, nor the runner-resolved keys",
129
132
  async () => {
130
- const lines = await runJq(extractExportFilter("> /tmp/swarm_config.env"), configs);
133
+ const lines = await runJq(extractExportFilter("(.value | @sh)"), configs);
131
134
  const keys = lines.map((l) => l.slice(0, l.indexOf("=")));
132
135
  expect(keys).toEqual(["NORMAL", "codex_oauth_extra"]);
133
136
  expect(lines.join("\n")).not.toContain("rt.secret");
@@ -0,0 +1,78 @@
1
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { CHILD_PROCESS_TEST_BUDGET_MS, expectChildOk, runChild } from "./test-proc";
6
+
7
+ /**
8
+ * Runs the real swarm_config_env block from docker-entrypoint.sh (extracted
9
+ * between its BEGIN/END markers) against a /api/config/resolved fixture, the
10
+ * same way the entrypoint sources it, and reads the resulting env.
11
+ */
12
+
13
+ const entrypoint = readFileSync("docker-entrypoint.sh", "utf8");
14
+ const block = entrypoint.slice(
15
+ entrypoint.indexOf("# BEGIN swarm_config_env"),
16
+ entrypoint.indexOf("# END swarm_config_env"),
17
+ );
18
+
19
+ let dir = "";
20
+
21
+ beforeAll(() => {
22
+ dir = mkdtempSync(join(tmpdir(), "entrypoint-export-"));
23
+ });
24
+ afterAll(() => {
25
+ rmSync(dir, { recursive: true, force: true });
26
+ });
27
+
28
+ async function exportedEnv(configs: unknown[], containerEnv: Record<string, string> = {}) {
29
+ const fixture = join(dir, `${crypto.randomUUID()}.json`);
30
+ writeFileSync(fixture, JSON.stringify({ configs }));
31
+ const script = `${block}
32
+ swarm_config_env_lines "$1" > "$1.env"
33
+ set -a; . "$1.env"; set +a
34
+ for withheld_key in $(swarm_config_withheld_keys "$1"); do unset "$withheld_key"; done
35
+ env
36
+ `;
37
+ const result = await runChild(["bash", "-c", script, "bash", fixture], {
38
+ env: { PATH: process.env.PATH ?? "/usr/bin:/bin", ...containerEnv },
39
+ timeoutMs: CHILD_PROCESS_TEST_BUDGET_MS,
40
+ });
41
+ expectChildOk(result, "entrypoint export block");
42
+ return Object.fromEntries(
43
+ result.stdout
44
+ .split("\n")
45
+ .filter((line) => line.includes("="))
46
+ .map((line) => [line.slice(0, line.indexOf("=")), line.slice(line.indexOf("=") + 1)]),
47
+ );
48
+ }
49
+
50
+ describe("docker-entrypoint.sh swarm_config_env", () => {
51
+ test("markers are present", () => {
52
+ expect(block).toContain("swarm_config_env_lines()");
53
+ expect(block).toContain("swarm_config_withheld_keys()");
54
+ });
55
+
56
+ test("exports exportToEnv rows and drops withheld ones, including container copies", async () => {
57
+ const env = await exportedEnv(
58
+ [
59
+ { key: "GITHUB_TOKEN", value: "row-github", isSecret: true, exportToEnv: true },
60
+ { key: "VERCEL_TOKEN", value: "row-vercel", isSecret: true, exportToEnv: false },
61
+ { key: "QUOTED", value: "it's $HOME", isSecret: false, exportToEnv: true },
62
+ // A pre-215 API sends no flag: the row keeps exporting.
63
+ { key: "LEGACY", value: "row-legacy", isSecret: true },
64
+ { key: "CF-Access-Client-Id", value: "x", isSecret: true, exportToEnv: true },
65
+ { key: "codex_oauth_1", value: "x", isSecret: true, exportToEnv: true },
66
+ { key: "HARNESS_PROVIDER", value: "pi", isSecret: false, exportToEnv: true },
67
+ ],
68
+ { VERCEL_TOKEN: "container-vercel" },
69
+ );
70
+ expect(env.GITHUB_TOKEN).toBe("row-github");
71
+ expect(env.QUOTED).toBe("it's $HOME");
72
+ expect(env.LEGACY).toBe("row-legacy");
73
+ expect(env.VERCEL_TOKEN).toBeUndefined();
74
+ expect(env.codex_oauth_1).toBeUndefined();
75
+ expect(env.HARNESS_PROVIDER).toBeUndefined();
76
+ expect(Object.values(env)).not.toContain("row-vercel");
77
+ });
78
+ });
@@ -542,7 +542,12 @@ describe("harness compatibility at every create entry point", () => {
542
542
  });
543
543
 
544
544
  test("send-task: the parent auto-route target is judged", async () => {
545
- const parent = await createTaskExtended("codex parent", { agentId: codexId, source: "mcp" });
545
+ // The worker created the parent, so it may continue it (task.parent.attach).
546
+ const parent = await createTaskExtended("codex parent", {
547
+ agentId: codexId,
548
+ creatorAgentId: workerId,
549
+ source: "mcp",
550
+ });
546
551
  const refused = await sendTaskHandler(ownerCtx({ agentId: workerId }), {
547
552
  task: "child of a codex task",
548
553
  parentTaskId: parent.id,
@@ -662,3 +667,102 @@ describe("harness compatibility at every create entry point", () => {
662
667
  expect(String(res.body.error)).toContain("does not run on the codex harness");
663
668
  });
664
669
  });
670
+
671
+ describe("nan and helmcode catalog models", () => {
672
+ let piId = "";
673
+ let opencodeId = "";
674
+
675
+ beforeAll(async () => {
676
+ piId = (
677
+ await createAgent({
678
+ name: "validation-pi",
679
+ isLead: false,
680
+ status: "idle",
681
+ harnessProvider: "pi",
682
+ })
683
+ ).id;
684
+ opencodeId = (
685
+ await createAgent({
686
+ name: "validation-opencode",
687
+ isLead: false,
688
+ status: "idle",
689
+ harnessProvider: "opencode",
690
+ })
691
+ ).id;
692
+ });
693
+
694
+ const ids = [
695
+ "nan/glm5.3-flash",
696
+ "nan/glm5.3",
697
+ "nan/mimo-v2.6-flash",
698
+ "helmcode/glm5.3",
699
+ "helmcode/qwen3.6",
700
+ "helmcode/deepseek-v4-flash",
701
+ ];
702
+
703
+ test("pi and opencode accept the ids without allowCustomModel", async () => {
704
+ for (const harnessProvider of ["pi", "opencode"]) {
705
+ for (const model of ids) {
706
+ expect(await explicitModelError({ model, harnessProvider })).toBeNull();
707
+ }
708
+ }
709
+ });
710
+
711
+ test("claude and codex reject them with the harness mismatch, even with allowCustomModel", async () => {
712
+ for (const harnessProvider of ["claude", "codex"]) {
713
+ for (const allowCustomModel of [false, true]) {
714
+ for (const model of ["nan/glm5.3-flash", "helmcode/glm5.3"]) {
715
+ expect(await explicitModelError({ model, harnessProvider, allowCustomModel })).toContain(
716
+ `does not run on the ${harnessProvider} harness`,
717
+ );
718
+ }
719
+ }
720
+ }
721
+ });
722
+
723
+ test("a misspelt helmcode id is unknown", async () => {
724
+ expect(
725
+ await explicitModelError({ model: "helmcode/glm-5.3", harnessProvider: "pi" }),
726
+ ).toContain("Unknown model");
727
+ });
728
+
729
+ test("send-task: pi and opencode agents take the ids, a claude agent gets the mismatch", async () => {
730
+ const sender = ownerCtx({ agentId: leadId });
731
+ const args = {
732
+ task: "nan route",
733
+ offerMode: false,
734
+ allowDuplicate: false,
735
+ routingReason: "human_pinned" as const,
736
+ routingNote: "pinned to check the nan/helmcode catalog ids",
737
+ };
738
+ for (const agentId of [piId, opencodeId]) {
739
+ for (const model of ["nan/mimo-v2.6-flash", "helmcode/glm5.3"]) {
740
+ const accepted = await sendTaskHandler(sender, {
741
+ ...args,
742
+ task: `nan route ${model} to ${agentId}`,
743
+ agentId,
744
+ model,
745
+ });
746
+ expect({ ok: accepted.ok, message: accepted.message }).toEqual({
747
+ ok: true,
748
+ message: expect.any(String),
749
+ });
750
+ }
751
+ }
752
+ const refused = await sendTaskHandler(sender, {
753
+ ...args,
754
+ agentId: workerId,
755
+ model: "helmcode/glm5.3",
756
+ });
757
+ expect(refused.ok).toBe(false);
758
+ expect(refused.message).toContain("does not run on the claude harness");
759
+ });
760
+
761
+ test("PATCH /api/agents/:id/runtime stores a helmcode model on a pi agent", async () => {
762
+ const res = await api("PATCH", `/api/agents/${piId}/runtime`, {
763
+ harness_provider: "pi",
764
+ model: "helmcode/glm5.3",
765
+ });
766
+ expect(res.status).toBe(200);
767
+ });
768
+ });
@@ -0,0 +1,154 @@
1
+ import { afterAll, beforeAll, beforeEach, describe, expect, spyOn, test } from "bun:test";
2
+ import { unlink } from "node:fs/promises";
3
+ import { closeDb, createAgent, getDbClient, getKv, initDb } from "../be/db";
4
+ import { installExtension, listExtensionRuns } from "../be/extensions/db";
5
+ import { enableExtension, stopExtensionRuntime } from "../extensions/lifecycle";
6
+ import { chatSdkContextKey } from "../tasks/context-key";
7
+ import type { Agent } from "../types";
8
+ import {
9
+ flushChatBuffers,
10
+ postTelegramUpdate,
11
+ startTelegramHarness,
12
+ type TelegramHarness,
13
+ telegramUpdate,
14
+ } from "./chat-sdk-harness";
15
+ import { loadBundleFixture } from "./fixtures/extensions/load";
16
+
17
+ const TEST_DB_PATH = "./test-extensions-example-route-chat-channel.sqlite";
18
+
19
+ let harness: TelegramHarness;
20
+ let lead: Agent;
21
+ let worker: Agent;
22
+
23
+ async function removeDbFiles(): Promise<void> {
24
+ for (const suffix of ["", "-wal", "-shm"]) {
25
+ await unlink(`${TEST_DB_PATH}${suffix}`).catch(() => {});
26
+ }
27
+ }
28
+
29
+ async function chatTask(chatId: number) {
30
+ return getDbClient().get<{ agentId: string | null }>(
31
+ "SELECT agentId FROM agent_tasks WHERE source = 'chat' AND contextKey = ? ORDER BY createdAt DESC LIMIT 1",
32
+ [chatSdkContextKey(`telegram:${chatId}`)],
33
+ );
34
+ }
35
+
36
+ /** A private-chat message, flushed out of the thread buffer. */
37
+ async function sendDm(chatId: number, text: string): Promise<void> {
38
+ await postTelegramUpdate(telegramUpdate({ id: chatId, type: "private" }, text));
39
+ await flushChatBuffers();
40
+ }
41
+
42
+ async function installRouteFixture(channelId: string, agentId: string) {
43
+ const bundle = await loadBundleFixture("route-chat-channel-to-agent");
44
+ const { extension } = await installExtension({
45
+ ...bundle,
46
+ config: { adapter: "telegram", channelId, agentId },
47
+ });
48
+ return { extension, enabled: await enableExtension(extension.id) };
49
+ }
50
+
51
+ describe("extension Chat SDK channel routing", () => {
52
+ beforeAll(async () => {
53
+ await removeDbFiles();
54
+ initDb(TEST_DB_PATH);
55
+ lead = await createAgent({
56
+ id: crypto.randomUUID(),
57
+ name: "chat-route-lead",
58
+ isLead: true,
59
+ status: "idle",
60
+ });
61
+ worker = await createAgent({
62
+ id: crypto.randomUUID(),
63
+ name: "chat-route-worker",
64
+ isLead: false,
65
+ status: "idle",
66
+ });
67
+ harness = await startTelegramHarness();
68
+ });
69
+
70
+ afterAll(async () => {
71
+ await harness.stop();
72
+ await stopExtensionRuntime();
73
+ closeDb();
74
+ await removeDbFiles();
75
+ });
76
+
77
+ beforeEach(async () => {
78
+ await stopExtensionRuntime();
79
+ await getDbClient().run("DELETE FROM extensions");
80
+ });
81
+
82
+ test("routes the configured channel to its agent and leaves other channels with the lead", async () => {
83
+ const { extension } = await installRouteFixture("telegram:3001", worker.id);
84
+
85
+ await sendDm(3001, "route this to the worker");
86
+ expect(await chatTask(3001)).toEqual({ agentId: worker.id });
87
+ expect(
88
+ (await listExtensionRuns(extension.id)).some(
89
+ (run) => run.event === "pre.chat.route" && run.action === "modify",
90
+ ),
91
+ ).toBe(true);
92
+
93
+ await sendDm(3002, "use the normal route");
94
+ expect(await chatTask(3002)).toEqual({ agentId: lead.id });
95
+ });
96
+
97
+ test("a block creates no task", async () => {
98
+ const bundle = await loadBundleFixture("minimal");
99
+ const { extension } = await installExtension({
100
+ manifest: { ...bundle.manifest, name: "block-chat-route", description: "blocks chat" },
101
+ files: {
102
+ "hooks.ts": `import { block, type SwarmExtension } from "swarm-extension";
103
+ const extension: SwarmExtension = (api) => {
104
+ api.on("pre.chat.route", () => block("chat is muted"));
105
+ };
106
+ export default extension;
107
+ `,
108
+ },
109
+ });
110
+ await enableExtension(extension.id);
111
+
112
+ await sendDm(3003, "this must not become a task");
113
+ expect(await chatTask(3003)).toBeNull();
114
+ const runs = await listExtensionRuns(extension.id);
115
+ expect(runs.some((run) => run.event === "pre.chat.route" && run.action === "block")).toBe(true);
116
+ // Ids only in the run subject: never the message text.
117
+ expect(runs.every((run) => !run.subject?.includes("must not"))).toBe(true);
118
+ });
119
+
120
+ test("falls back to the built-in router when the extension names an unknown agent", async () => {
121
+ await installRouteFixture("telegram:3004", "missing-agent");
122
+ const warn = spyOn(console, "warn").mockImplementation(() => {});
123
+ try {
124
+ await sendDm(3004, "use the fallback");
125
+ expect(await chatTask(3004)).toEqual({ agentId: lead.id });
126
+ expect(warn).toHaveBeenCalledWith(
127
+ "[chat-sdk] Extension selected an unknown agent missing-agent. Using the router.",
128
+ );
129
+ } finally {
130
+ warn.mockRestore();
131
+ }
132
+ });
133
+
134
+ test("post.chat.message carries the inbound sender", async () => {
135
+ const { extension, enabled } = await installRouteFixture("telegram:3005", worker.id);
136
+ await sendDm(3005, "record this message");
137
+
138
+ const deadline = Date.now() + 5_000;
139
+ while (
140
+ !(await listExtensionRuns(extension.id)).some(
141
+ (run) => run.event === "post.chat.message" && run.action === "continue",
142
+ ) &&
143
+ Date.now() < deadline
144
+ ) {
145
+ await Bun.sleep(20);
146
+ }
147
+ expect(
148
+ await getKv(
149
+ `task:agent:${enabled.agentId}`,
150
+ "ext:route-chat-channel-to-agent:chat:telegram:3005",
151
+ ),
152
+ ).toMatchObject({ value: "777001" });
153
+ });
154
+ });
@@ -33,6 +33,7 @@ const NEW_EVENTS = [
33
33
  "post.task.budgetRefused",
34
34
  "post.email.received",
35
35
  "post.kapso.message",
36
+ "post.chat.message",
36
37
  "post.vcs.event",
37
38
  ] as const satisfies readonly (keyof SwarmEventMap)[];
38
39
 
@@ -358,6 +359,56 @@ describe("extension post bridge: new events", () => {
358
359
  });
359
360
  });
360
361
 
362
+ describe("post.chat.message", () => {
363
+ test("chat.message.received dispatches an inbound event", async () => {
364
+ await registerCapture("capture");
365
+ workflowEventBus.emit("chat.message.received", {
366
+ adapter: "telegram",
367
+ channelId: "telegram:555001",
368
+ threadId: "telegram:555001",
369
+ userId: "555001",
370
+ text: "hello swarm",
371
+ messageId: "555001:11",
372
+ });
373
+ await waitForEvents(1);
374
+ expect(received).toEqual([
375
+ {
376
+ extension: "capture",
377
+ event: "post.chat.message",
378
+ payload: {
379
+ adapter: "telegram",
380
+ channelId: "telegram:555001",
381
+ threadId: "telegram:555001",
382
+ userId: "555001",
383
+ text: "hello swarm",
384
+ direction: "inbound",
385
+ messageId: "555001:11",
386
+ },
387
+ },
388
+ ]);
389
+ });
390
+
391
+ test("chat.message.sent dispatches an outbound event", async () => {
392
+ await registerCapture("capture");
393
+ workflowEventBus.emit("chat.message.sent", {
394
+ adapter: "telegram",
395
+ channelId: "telegram:555001",
396
+ threadId: "telegram:555001",
397
+ text: "done",
398
+ taskId: "task-1",
399
+ });
400
+ await waitForEvents(1);
401
+ expect(received[0]?.payload).toEqual({
402
+ adapter: "telegram",
403
+ channelId: "telegram:555001",
404
+ threadId: "telegram:555001",
405
+ text: "done",
406
+ direction: "outbound",
407
+ taskId: "task-1",
408
+ });
409
+ });
410
+ });
411
+
361
412
  describe("post.kapso.message", () => {
362
413
  test("a bus emit dispatches the typed payload", async () => {
363
414
  await registerCapture("capture");