@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,370 @@
1
+ import {
2
+ afterAll,
3
+ afterEach,
4
+ beforeAll,
5
+ beforeEach,
6
+ describe,
7
+ expect,
8
+ mock,
9
+ spyOn,
10
+ test,
11
+ } from "bun:test";
12
+ import { unlinkSync } from "node:fs";
13
+ import type { WebClient } from "@slack/web-api";
14
+ import { closeDb, getDbClient, getKv, initDb, upsertKv } from "../be/db";
15
+ import {
16
+ EXTERNAL_ALERT_KV_NAMESPACE,
17
+ getChannelBlockReason,
18
+ getChannelExternality,
19
+ invalidateChannelExternality,
20
+ leaveExternalChannel,
21
+ resetExternalChannelStateForTesting,
22
+ scanExternalChannelsOnBoot,
23
+ shouldBlockChannel,
24
+ } from "../slack/external-channel";
25
+
26
+ const originalFlag = process.env.SLACK_BLOCK_EXTERNAL_CHANNELS;
27
+ const originalAlerts = process.env.SLACK_ALERTS_CHANNEL;
28
+ const TEST_DB_PATH = "./test-slack-external-channel.sqlite";
29
+
30
+ function removeDbFiles(): void {
31
+ for (const suffix of ["", "-wal", "-shm"]) {
32
+ try {
33
+ unlinkSync(`${TEST_DB_PATH}${suffix}`);
34
+ } catch {
35
+ // ignore
36
+ }
37
+ }
38
+ }
39
+
40
+ beforeAll(() => {
41
+ removeDbFiles();
42
+ initDb(TEST_DB_PATH);
43
+ });
44
+
45
+ afterAll(() => {
46
+ closeDb();
47
+ removeDbFiles();
48
+ });
49
+
50
+ function makePlatformError(code: string, extra: Record<string, unknown> = {}): Error {
51
+ const err = new Error(`An API error occurred: ${code}`);
52
+ (err as unknown as { data: Record<string, unknown> }).data = { ok: false, error: code, ...extra };
53
+ return err;
54
+ }
55
+
56
+ function makeClient(opts: {
57
+ channel?: Record<string, unknown>;
58
+ info?: () => Promise<unknown>;
59
+ leave?: () => Promise<unknown>;
60
+ channels?: Array<Record<string, unknown>>;
61
+ }) {
62
+ const info = mock(opts.info ?? (() => Promise.resolve({ channel: opts.channel ?? {} })));
63
+ const leave = mock(opts.leave ?? (() => Promise.resolve({ ok: true })));
64
+ const postMessage = mock(() => Promise.resolve({ ok: true }));
65
+ const usersConversations = mock(() =>
66
+ Promise.resolve({ channels: opts.channels ?? [], response_metadata: { next_cursor: "" } }),
67
+ );
68
+ const client = {
69
+ conversations: { info, leave },
70
+ chat: { postMessage },
71
+ users: { conversations: usersConversations },
72
+ } as unknown as WebClient;
73
+ return { client, info, leave, postMessage, usersConversations };
74
+ }
75
+
76
+ function postedText(postMessage: ReturnType<typeof mock>): string {
77
+ return (postMessage.mock.calls[0]?.[0] as { text: string }).text;
78
+ }
79
+
80
+ beforeEach(async () => {
81
+ resetExternalChannelStateForTesting();
82
+ await getDbClient().run("DELETE FROM kv_entries WHERE namespace = ?", [
83
+ EXTERNAL_ALERT_KV_NAMESPACE,
84
+ ]);
85
+ process.env.SLACK_BLOCK_EXTERNAL_CHANNELS = "true";
86
+ process.env.SLACK_ALERTS_CHANNEL = "C_ALERTS";
87
+ });
88
+
89
+ afterEach(() => {
90
+ if (originalFlag === undefined) delete process.env.SLACK_BLOCK_EXTERNAL_CHANNELS;
91
+ else process.env.SLACK_BLOCK_EXTERNAL_CHANNELS = originalFlag;
92
+ if (originalAlerts === undefined) delete process.env.SLACK_ALERTS_CHANNEL;
93
+ else process.env.SLACK_ALERTS_CHANNEL = originalAlerts;
94
+ });
95
+
96
+ describe("getChannelExternality", () => {
97
+ test("is_ext_shared is external", async () => {
98
+ const { client } = makeClient({ channel: { is_ext_shared: true } });
99
+ expect(await getChannelExternality(client, "C1")).toBe("external");
100
+ });
101
+
102
+ test("is_ext_shared with a foreign conversation_host_id is external", async () => {
103
+ const { client } = makeClient({
104
+ channel: { is_ext_shared: true, conversation_host_id: "T0FOREIGN1" },
105
+ });
106
+ expect(await getChannelExternality(client, "C1")).toBe("external");
107
+ });
108
+
109
+ test("is_pending_ext_shared is external", async () => {
110
+ const { client } = makeClient({ channel: { is_pending_ext_shared: true } });
111
+ expect(await getChannelExternality(client, "C1")).toBe("external");
112
+ });
113
+
114
+ test("is_org_shared with both ext flags false is internal", async () => {
115
+ const { client } = makeClient({
116
+ channel: {
117
+ is_shared: true,
118
+ is_org_shared: true,
119
+ is_ext_shared: false,
120
+ is_pending_ext_shared: false,
121
+ },
122
+ });
123
+ expect(await getChannelExternality(client, "C1")).toBe("internal");
124
+ });
125
+
126
+ test("conversations.info error is unknown and not cached", async () => {
127
+ const { client, info } = makeClient({ info: () => Promise.reject(new Error("boom")) });
128
+ expect(await getChannelExternality(client, "C1")).toBe("unknown");
129
+ expect(await getChannelExternality(client, "C1")).toBe("unknown");
130
+ expect(info).toHaveBeenCalledTimes(2);
131
+ });
132
+
133
+ test("2 lookups inside the TTL make 1 API call; invalidate forces a fresh call", async () => {
134
+ const { client, info } = makeClient({ channel: { is_ext_shared: false } });
135
+ await getChannelExternality(client, "C1");
136
+ await getChannelExternality(client, "C1");
137
+ expect(info).toHaveBeenCalledTimes(1);
138
+ invalidateChannelExternality("C1");
139
+ await getChannelExternality(client, "C1");
140
+ expect(info).toHaveBeenCalledTimes(2);
141
+ });
142
+ });
143
+
144
+ describe("shouldBlockChannel", () => {
145
+ test("flag off never blocks and makes no API call", async () => {
146
+ process.env.SLACK_BLOCK_EXTERNAL_CHANNELS = "false";
147
+ const { client, info } = makeClient({ channel: { is_ext_shared: true } });
148
+ expect(await shouldBlockChannel(client, "C1", true)).toBe(false);
149
+ expect(info).not.toHaveBeenCalled();
150
+ });
151
+
152
+ test("envelope flag blocks without an API call", async () => {
153
+ const { client, info } = makeClient({});
154
+ expect(await shouldBlockChannel(client, "C1", true)).toBe(true);
155
+ expect(info).not.toHaveBeenCalled();
156
+ });
157
+
158
+ test("unknown blocks as unverified (fail closed)", async () => {
159
+ const { client } = makeClient({ info: () => Promise.reject(new Error("boom")) });
160
+ expect(await shouldBlockChannel(client, "C1")).toBe(true);
161
+ expect(await getChannelBlockReason(client, "C1")).toBe("unverified");
162
+ });
163
+
164
+ test("a verified internal channel is not blocked", async () => {
165
+ const { client } = makeClient({ channel: { is_ext_shared: false } });
166
+ expect(await getChannelBlockReason(client, "C1")).toBeNull();
167
+ });
168
+
169
+ test("envelope evidence replaces a cached internal value", async () => {
170
+ let channel: Record<string, unknown> = { is_ext_shared: false };
171
+ const { client, info } = makeClient({ info: () => Promise.resolve({ channel }) });
172
+ expect(await getChannelExternality(client, "C1")).toBe("internal");
173
+ channel = { is_ext_shared: true };
174
+ expect(await shouldBlockChannel(client, "C1", true)).toBe(true);
175
+ // Reviewer repro: the next outbound check without an envelope must block too.
176
+ expect(await shouldBlockChannel(client, "C1")).toBe(true);
177
+ expect(info).toHaveBeenCalledTimes(1);
178
+ });
179
+
180
+ test("an in-flight internal lookup does not overwrite envelope evidence", async () => {
181
+ let resolveInfo: (value: unknown) => void = () => {};
182
+ const { client } = makeClient({
183
+ info: () =>
184
+ new Promise((resolve) => {
185
+ resolveInfo = resolve;
186
+ }),
187
+ });
188
+ const inFlight = getChannelExternality(client, "C1");
189
+ expect(await shouldBlockChannel(client, "C1", true)).toBe(true);
190
+ resolveInfo({ channel: { is_ext_shared: false } });
191
+ expect(await inFlight).toBe("external");
192
+ expect(await shouldBlockChannel(client, "C1")).toBe(true);
193
+ });
194
+
195
+ test("an in-flight lookup after invalidation is not cached", async () => {
196
+ let resolveInfo: (value: unknown) => void = () => {};
197
+ const { client, info } = makeClient({
198
+ info: () =>
199
+ new Promise((resolve) => {
200
+ resolveInfo = resolve;
201
+ }),
202
+ });
203
+ const inFlight = getChannelExternality(client, "C1");
204
+ invalidateChannelExternality("C1");
205
+ resolveInfo({ channel: { is_ext_shared: false } });
206
+ expect(await inFlight).toBe("unknown");
207
+ const next = getChannelExternality(client, "C1");
208
+ resolveInfo({ channel: { is_ext_shared: false } });
209
+ expect(await next).toBe("internal");
210
+ expect(info).toHaveBeenCalledTimes(2);
211
+ });
212
+ });
213
+
214
+ describe("leaveExternalChannel", () => {
215
+ test("success: 1 leave and 1 alert with Leave: left", async () => {
216
+ const { client, leave, postMessage } = makeClient({});
217
+ await leaveExternalChannel(client, "C1", "joined");
218
+ expect(leave).toHaveBeenCalledTimes(1);
219
+ expect(postMessage).toHaveBeenCalledTimes(1);
220
+ expect((postMessage.mock.calls[0]?.[0] as { channel: string }).channel).toBe("C_ALERTS");
221
+ expect(postedText(postMessage)).toContain("Trigger: joined");
222
+ expect(postedText(postMessage)).toContain("Leave: left");
223
+ });
224
+
225
+ test("missing_scope: failed line and scope hint", async () => {
226
+ const { client, postMessage } = makeClient({
227
+ leave: () =>
228
+ Promise.reject(makePlatformError("missing_scope", { needed: "channels:manage" })),
229
+ });
230
+ await leaveExternalChannel(client, "C1", "joined");
231
+ expect(postedText(postMessage)).toContain("Leave: failed (missing_scope)");
232
+ expect(postedText(postMessage)).toContain("Slack requires the `channels:manage` scope");
233
+ });
234
+
235
+ test.each([
236
+ "cant_leave_general",
237
+ "last_member",
238
+ "method_not_supported_for_channel_type",
239
+ ])("%s maps to failed (<code>)", async (code) => {
240
+ const { client, postMessage } = makeClient({
241
+ leave: () => Promise.reject(makePlatformError(code)),
242
+ });
243
+ await leaveExternalChannel(client, "C1", "shared");
244
+ expect(postedText(postMessage)).toContain(`Leave: failed (${code})`);
245
+ });
246
+
247
+ test("not_in_channel response counts as left", async () => {
248
+ const { client, postMessage } = makeClient({
249
+ leave: () => Promise.reject(makePlatformError("not_in_channel", { not_in_channel: true })),
250
+ });
251
+ await leaveExternalChannel(client, "C1", "joined");
252
+ expect(postedText(postMessage)).toContain("Leave: left");
253
+ });
254
+
255
+ test("second call within 1 hour posts no second alert", async () => {
256
+ const { client, leave, postMessage } = makeClient({});
257
+ await leaveExternalChannel(client, "C1", "joined");
258
+ await leaveExternalChannel(client, "C1", "joined");
259
+ expect(leave).toHaveBeenCalledTimes(2);
260
+ expect(postMessage).toHaveBeenCalledTimes(1);
261
+ });
262
+
263
+ test("alert dedupe survives a restart", async () => {
264
+ const first = makeClient({});
265
+ await leaveExternalChannel(first.client, "C1", "joined");
266
+ expect(first.postMessage).toHaveBeenCalledTimes(1);
267
+
268
+ // Restart: all process memory is gone; only kv_entries survives. (The test
269
+ // DB restores from an in-memory template, so it cannot be reopened here.)
270
+ resetExternalChannelStateForTesting();
271
+
272
+ const second = makeClient({});
273
+ await leaveExternalChannel(second.client, "C1", "joined");
274
+ expect(second.postMessage).not.toHaveBeenCalled();
275
+ const entry = await getKv(EXTERNAL_ALERT_KV_NAMESPACE, "C1:joined");
276
+ expect(entry?.value).toBe("sent");
277
+ expect(entry?.expiresAt).toBeGreaterThan(Date.now() + 59 * 60 * 1000);
278
+ });
279
+
280
+ test("a failed alert post releases the key so the next attempt sends", async () => {
281
+ const error = spyOn(console, "error").mockImplementation(() => {});
282
+ try {
283
+ const { client, postMessage } = makeClient({});
284
+ postMessage.mockImplementationOnce(() => Promise.reject(new Error("slack down")));
285
+ await leaveExternalChannel(client, "C1", "joined");
286
+ expect(await getKv(EXTERNAL_ALERT_KV_NAMESPACE, "C1:joined")).toBeNull();
287
+ await leaveExternalChannel(client, "C1", "joined");
288
+ expect(postMessage).toHaveBeenCalledTimes(2);
289
+ await leaveExternalChannel(client, "C1", "joined");
290
+ expect(postMessage).toHaveBeenCalledTimes(2);
291
+ } finally {
292
+ error.mockRestore();
293
+ }
294
+ });
295
+
296
+ test("a stale sending reservation (crash mid-post) does not suppress the alert", async () => {
297
+ await upsertKv({
298
+ namespace: EXTERNAL_ALERT_KV_NAMESPACE,
299
+ key: "C1:joined",
300
+ value: "sending",
301
+ valueType: "string",
302
+ expiresAt: Date.now() - 1,
303
+ });
304
+ const { client, postMessage } = makeClient({});
305
+ await leaveExternalChannel(client, "C1", "joined");
306
+ expect(postMessage).toHaveBeenCalledTimes(1);
307
+ });
308
+
309
+ test("an in-flight sending reservation suppresses a concurrent duplicate", async () => {
310
+ const { client, postMessage } = makeClient({});
311
+ await Promise.all([
312
+ leaveExternalChannel(client, "C1", "joined"),
313
+ leaveExternalChannel(client, "C1", "joined"),
314
+ ]);
315
+ expect(postMessage).toHaveBeenCalledTimes(1);
316
+ });
317
+
318
+ test("unset SLACK_ALERTS_CHANNEL: 0 posts and 1 warning", async () => {
319
+ delete process.env.SLACK_ALERTS_CHANNEL;
320
+ const warn = spyOn(console, "warn").mockImplementation(() => {});
321
+ try {
322
+ const { client, postMessage } = makeClient({});
323
+ await leaveExternalChannel(client, "C1", "joined");
324
+ expect(postMessage).not.toHaveBeenCalled();
325
+ expect(warn).toHaveBeenCalledTimes(1);
326
+ } finally {
327
+ warn.mockRestore();
328
+ }
329
+ });
330
+ });
331
+
332
+ describe("scanExternalChannelsOnBoot", () => {
333
+ test("boot scan sweeps expired alert keys", async () => {
334
+ await upsertKv({
335
+ namespace: EXTERNAL_ALERT_KV_NAMESPACE,
336
+ key: "C_OLD:boot-scan",
337
+ value: "sent",
338
+ valueType: "string",
339
+ expiresAt: Date.now() - 1,
340
+ });
341
+ const { client } = makeClient({});
342
+ await scanExternalChannelsOnBoot(client);
343
+ const rows = await getDbClient().query<{ key: string }>(
344
+ "SELECT key FROM kv_entries WHERE namespace = ?",
345
+ [EXTERNAL_ALERT_KV_NAMESPACE],
346
+ );
347
+ expect(rows).toEqual([]);
348
+ });
349
+
350
+ test("1 external + 1 internal: 1 report-only alert, 0 leaves", async () => {
351
+ const { client, leave, postMessage } = makeClient({
352
+ channels: [
353
+ { id: "C_EXT", is_ext_shared: true },
354
+ { id: "C_INT", is_ext_shared: false, is_pending_ext_shared: false },
355
+ ],
356
+ });
357
+ await scanExternalChannelsOnBoot(client);
358
+ expect(leave).not.toHaveBeenCalled();
359
+ expect(postMessage).toHaveBeenCalledTimes(1);
360
+ expect(postedText(postMessage)).toContain("C_EXT");
361
+ expect(postedText(postMessage)).toContain("Leave: not attempted (report only)");
362
+ });
363
+
364
+ test("flag off: no Slack calls", async () => {
365
+ process.env.SLACK_BLOCK_EXTERNAL_CHANNELS = "false";
366
+ const { client, usersConversations } = makeClient({});
367
+ await scanExternalChannelsOnBoot(client);
368
+ expect(usersConversations).not.toHaveBeenCalled();
369
+ });
370
+ });
@@ -556,6 +556,40 @@ describe("Slack renderer v2", () => {
556
556
  });
557
557
  }
558
558
 
559
+ for (const delegationEnabled of [false, true]) {
560
+ for (const status of ["completed", "failed"] as const) {
561
+ test(`worker deferred ${status} wake posts no outcome with delegation=${delegationEnabled}`, async () => {
562
+ process.env.SLACK_RENDER_V2_DELEGATION = String(delegationEnabled);
563
+ if (delegationEnabled) await ensureSlackDelegationActivation();
564
+ const worker = await createAgent({ name: "Fast Coder", isLead: false, status: "idle" });
565
+ const { channelId, threadTs } = uniqueSlackAddress("C_WORKER_WAKE");
566
+ const wake = await createTaskExtended("Check PR #2136 CI at the expected head", {
567
+ agentId: worker.id,
568
+ source: "schedule",
569
+ taskType: "deferred",
570
+ slackChannelId: channelId,
571
+ slackThreadTs: threadTs,
572
+ contextKey: slackContextKey({ channelId, threadTs }),
573
+ });
574
+ await startTask(wake.id);
575
+ await ensureSlackThreadTree([wake.id]);
576
+ await processSlackRenderV2();
577
+ if (status === "failed") {
578
+ await failTask(wake.id, "CI is failing at expected head");
579
+ } else {
580
+ await completeTask(wake.id, "CI is green at expected head");
581
+ }
582
+ calls.length = 0;
583
+
584
+ await processSlackRenderV2();
585
+ await processSlackRenderV2();
586
+
587
+ expect(await getSlackOutcomeMessage(wake.id)).toBeNull();
588
+ expect(calls.filter((call) => call.method === "chat.startStream")).toHaveLength(0);
589
+ });
590
+ }
591
+ }
592
+
559
593
  test("does not relay an ordinary scheduled task without Slack context", async () => {
560
594
  const schedule = await createScheduledTask({
561
595
  name: "ordinary-maintenance",
@@ -2474,7 +2508,7 @@ describe("Slack renderer v2", () => {
2474
2508
  expect(calls).toContainEqual(doneReaction);
2475
2509
  });
2476
2510
 
2477
- test("a wake-up that deferred again posts the next ⏳ card, and the old card points at it", async () => {
2511
+ test("re-defers retain one ⏳ card through multiple wakes and the final result", async () => {
2478
2512
  const lead = await createAgent({ name: "Chained Lead", isLead: true, status: "idle" });
2479
2513
  const { channelId, threadTs } = uniqueSlackAddress("C_DEFER_CHAIN");
2480
2514
  const ask = await createTaskExtended("answer when the build is done", {
@@ -2513,18 +2547,60 @@ describe("Slack renderer v2", () => {
2513
2547
  calls.length = 0;
2514
2548
  await processSlackRenderV2();
2515
2549
 
2516
- // The chain continues on a new ⏳ card, which the next wake-up resolves.
2517
- const next = calls.find((call) => call.method === "chat.startStream");
2518
- expect(next?.payload.markdown_text).toBe("⏳ Checking back later");
2519
- const nextCard = await getSlackOutcomeMessage(wake.id);
2520
- expect(nextCard?.permalink).toBeTruthy();
2521
- // The old card hands over to it rather than repeating the new ETA.
2550
+ expect(calls.filter((call) => call.method === "chat.startStream")).toHaveLength(0);
2551
+ expect(await getSlackOutcomeMessage(wake.id)).toBeNull();
2522
2552
  const rewrite = calls.find(
2523
2553
  (call) => call.method === "chat.update" && call.payload.ts === card?.ts,
2524
2554
  );
2525
- expect(rewrite?.payload.text).toBe(
2526
- `↪️ Resumed by ${lead.name} and deferred again — <${nextCard?.permalink}|next update>`,
2527
- );
2555
+ expect(rewrite?.payload.text).toBe("⏳ Checking back later");
2556
+ expect((await getSlackOutcomeMessage(ask.id))?.deferralResolvedAt).toBeUndefined();
2557
+
2558
+ const nextSchedule = await createScheduledTask({
2559
+ ...schedule,
2560
+ name: "deferred-chain-next",
2561
+ parentTaskId: wake.id,
2562
+ });
2563
+ const nextWake = await createStandaloneScheduleTask(nextSchedule);
2564
+ await startTask(nextWake.id);
2565
+ await completeTask(nextWake.id, "Waiting on Builder — or today at 21:00 at the latest", {
2566
+ addTags: ["deferred"],
2567
+ deferredAt: new Date().toISOString(),
2568
+ });
2569
+ calls.length = 0;
2570
+ await processSlackRenderV2();
2571
+ expect(calls.filter((call) => call.method === "chat.startStream")).toHaveLength(0);
2572
+ expect(
2573
+ calls.find((call) => call.method === "chat.update" && call.payload.ts === card?.ts)?.payload
2574
+ .text,
2575
+ ).toBe("⏳ Waiting on Builder");
2576
+
2577
+ calls.length = 0;
2578
+ await processSlackRenderV2();
2579
+ expect(
2580
+ calls.filter((call) => call.method === "chat.update" && call.payload.ts === card?.ts),
2581
+ ).toHaveLength(0);
2582
+
2583
+ const finalSchedule = await createScheduledTask({
2584
+ ...schedule,
2585
+ name: "deferred-chain-final",
2586
+ parentTaskId: nextWake.id,
2587
+ });
2588
+ const finalWake = await createStandaloneScheduleTask(finalSchedule);
2589
+ await startTask(finalWake.id);
2590
+ await completeTask(finalWake.id, "The build passed.");
2591
+ calls.length = 0;
2592
+ await processSlackRenderV2();
2593
+ expect(calls.filter((call) => call.method === "chat.startStream")).toHaveLength(0);
2594
+ expect(
2595
+ calls.find((call) => call.method === "chat.update" && call.payload.ts === card?.ts)?.payload
2596
+ .text,
2597
+ ).toContain("The build passed.");
2598
+ expect((await getSlackOutcomeMessage(ask.id))?.deferralResolvedAt).toBeTruthy();
2599
+ calls.length = 0;
2600
+ await processSlackRenderV2();
2601
+ expect(
2602
+ calls.filter((call) => call.method === "chat.update" && call.payload.ts === card?.ts),
2603
+ ).toHaveLength(0);
2528
2604
  });
2529
2605
 
2530
2606
  test("a wake-up that failed closes the deferral card as failed, not as done", async () => {
@@ -23,10 +23,11 @@ describe("Sonnet 5.5 registry", () => {
23
23
  .filter((row) => row.provider === provider && row.model === model)
24
24
  .map((row) => [row.tokenClass, row.pricePerMillionUsd]),
25
25
  );
26
+ // Sonnet 5.5 cache hits cost 0.05x input, not the usual 0.1x.
26
27
  expect(rates).toEqual({
27
28
  input: 2,
28
29
  output: 10,
29
- cached_input: 0.2,
30
+ cached_input: 0.1,
30
31
  cache_write: 2.5,
31
32
  cache_write_1h: 4,
32
33
  });
@@ -41,4 +42,11 @@ describe("Sonnet 5.5 registry", () => {
41
42
  ).toBe(2);
42
43
  }
43
44
  });
45
+
46
+ test("the shortname fallback seeds the same cache-read rate when the snapshot lacks the model", () => {
47
+ const row = buildModelsDevSeedRows({ anthropic: { models: {} } }).find(
48
+ (r) => r.provider === "claude" && r.model === "sonnet" && r.tokenClass === "cached_input",
49
+ );
50
+ expect(row?.pricePerMillionUsd).toBe(0.1);
51
+ });
44
52
  });
@@ -246,12 +246,27 @@ describe("migration 117 consolidated connections-redesign schema", () => {
246
246
  new Date().toISOString(),
247
247
  new Bun.CryptoHasher("sha256").update(migration117).digest("hex"),
248
248
  );
249
+ // 213 rebuilds script_connections with post-117 columns, so it must wait for 117 too.
250
+ const migration213 = readFileSync(
251
+ "src/be/migrations/213_script_connections_sql.sql",
252
+ "utf-8",
253
+ );
254
+ database
255
+ .prepare(
256
+ "INSERT INTO _migrations (version, name, applied_at, checksum) VALUES (?, ?, ?, ?)",
257
+ )
258
+ .run(
259
+ 213,
260
+ "213_script_connections_sql",
261
+ new Date().toISOString(),
262
+ new Bun.CryptoHasher("sha256").update(migration213).digest("hex"),
263
+ );
249
264
  runMigrations(database);
250
265
  database.run(
251
266
  `INSERT INTO script_connections (id, slug, kind, base_url)
252
267
  VALUES ('existing', 'existing', 'raw', 'https://api.vendor.test')`,
253
268
  );
254
- database.run("DELETE FROM _migrations WHERE version = 117");
269
+ database.run("DELETE FROM _migrations WHERE version IN (117, 213)");
255
270
  runMigrations(database);
256
271
  expect(
257
272
  database