@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
@@ -3,7 +3,7 @@ import { unlink } from "node:fs/promises";
3
3
  import type { IncomingMessage, ServerResponse } from "node:http";
4
4
  import { Readable } from "node:stream";
5
5
  import { createApp } from "../apps/store";
6
- import { closeDb, createAgent, getDbClient, initDb } from "../be/db";
6
+ import { closeDb, createAgent, getDbClient, initDb, upsertSwarmConfig } from "../be/db";
7
7
  import { getScript, listScripts } from "../be/scripts/db";
8
8
  import { setScriptEmbeddingProviderForTests } from "../be/scripts/embeddings";
9
9
  import { configSecretName, registerStoredSecret } from "../be/secret-registry";
@@ -16,6 +16,7 @@ import {
16
16
  } from "../utils/secret-scrubber";
17
17
  import { SKIP_SANDBOX_SPAWN_TESTS } from "./sandbox-spawn-test-helpers";
18
18
  import { randomToken } from "./synthetic-secret-helpers";
19
+ import { CHILD_PROCESS_TEST_BUDGET_MS } from "./test-proc";
19
20
 
20
21
  const skip = test.skipIf(SKIP_SANDBOX_SPAWN_TESTS);
21
22
 
@@ -737,3 +738,202 @@ describe("script source with an embedded secret is refused at write", () => {
737
738
  expect(response.status).toBe(200);
738
739
  });
739
740
  });
741
+
742
+ // Regression: ctx.swarm.config.get used to return undefined for every key,
743
+ // because no run entry point filled the payload's user config. These go
744
+ // through the real POST /api/scripts/run route, not an injected payload.
745
+ describe("ctx.swarm.config.get through POST /api/scripts/run", () => {
746
+ const secretValue = `cfg-secret-${randomToken(32)}`;
747
+ // Under the global scrubber's 12-char floor, so only the run's own
748
+ // output boundary can redact it.
749
+ const shortSecretValue = randomToken(10);
750
+ const readerSource = `
751
+ import type { ScriptContext } from "swarm-sdk";
752
+ export default async (_args: unknown, ctx: ScriptContext) => {
753
+ const read = (key: string) => {
754
+ const value = ctx.swarm.config.get(key);
755
+ return value === undefined ? null : ctx.stdlib.Redacted.value(value);
756
+ };
757
+ const secret = ctx.swarm.config.get("SCRIPT_CFG_SECRET");
758
+ console.log("secret:", secret === undefined ? "missing" : ctx.stdlib.Redacted.value(secret));
759
+ return {
760
+ plain: read("SCRIPT_CFG_PLAIN"),
761
+ override: read("SCRIPT_CFG_OVERRIDE"),
762
+ // Compare by shape: the literal value in source would be refused at write.
763
+ secretMatches:
764
+ secret !== undefined &&
765
+ ctx.stdlib.Redacted.value(secret).startsWith("cfg-secret-") &&
766
+ ctx.stdlib.Redacted.value(secret).length === ${secretValue.length},
767
+ secretIsSecret: secret !== undefined && ctx.stdlib.Redacted.isSecret(secret),
768
+ secretStringified: String(secret),
769
+ missingIsUndefined: ctx.swarm.config.get("SCRIPT_CFG_NOT_SET") === undefined,
770
+ fallback: read("SCRIPT_CFG_NOT_SET") ?? "fallback",
771
+ apiOnly: read("SLACK_SIGNING_SECRET"),
772
+ proto: read("__proto__"),
773
+ };
774
+ };`;
775
+
776
+ type Reader = {
777
+ plain: string | null;
778
+ override: string | null;
779
+ secretMatches: boolean;
780
+ secretIsSecret: boolean;
781
+ secretStringified: string;
782
+ missingIsUndefined: boolean;
783
+ fallback: string;
784
+ apiOnly: string | null;
785
+ proto: string | null;
786
+ };
787
+
788
+ beforeAll(async () => {
789
+ await upsertSwarmConfig({ scope: "global", key: "SCRIPT_CFG_PLAIN", value: "plain-global" });
790
+ await upsertSwarmConfig({ scope: "global", key: "SCRIPT_CFG_OVERRIDE", value: "from-global" });
791
+ await upsertSwarmConfig({
792
+ scope: "agent",
793
+ scopeId: workerId,
794
+ key: "SCRIPT_CFG_OVERRIDE",
795
+ value: "from-worker",
796
+ });
797
+ await upsertSwarmConfig({
798
+ scope: "global",
799
+ key: "SCRIPT_CFG_SECRET",
800
+ value: secretValue,
801
+ isSecret: true,
802
+ });
803
+ await upsertSwarmConfig({
804
+ scope: "global",
805
+ key: "SCRIPT_CFG_SHORT_SECRET",
806
+ value: shortSecretValue,
807
+ isSecret: true,
808
+ });
809
+ await upsertSwarmConfig({ scope: "global", key: "__proto__", value: "proto-global" });
810
+ await upsertSwarmConfig({
811
+ scope: "global",
812
+ key: "SLACK_SIGNING_SECRET",
813
+ value: `signing-${randomToken(24)}`,
814
+ isSecret: true,
815
+ });
816
+ });
817
+
818
+ afterAll(async () => {
819
+ await getDbClient().run(
820
+ "DELETE FROM swarm_config WHERE key LIKE 'SCRIPT_CFG_%' OR key IN ('SLACK_SIGNING_SECRET', '__proto__')",
821
+ );
822
+ clearVolatileSecretsForTesting();
823
+ });
824
+
825
+ async function run(body: Record<string, unknown>, agentId: string) {
826
+ const response = await dispatch("/api/scripts/run", {
827
+ method: "POST",
828
+ agentId,
829
+ body: JSON.stringify(body),
830
+ });
831
+ expect(response.status).toBe(200);
832
+ return (await response.json()) as { result: Reader; stdout: string; error?: string };
833
+ }
834
+
835
+ skip(
836
+ "inline run reads existing keys, agent scope over global, and undefined for a missing key",
837
+ async () => {
838
+ const out = await run({ source: readerSource, intent: "config get regression" }, workerId);
839
+ expect(out.error).toBeUndefined();
840
+ expect(out.result).toEqual({
841
+ plain: "plain-global",
842
+ override: "from-worker",
843
+ secretMatches: true,
844
+ secretIsSecret: true,
845
+ secretStringified: "<redacted>",
846
+ missingIsUndefined: true,
847
+ fallback: "fallback",
848
+ apiOnly: null,
849
+ proto: "proto-global",
850
+ });
851
+ // The unwrapped secret is scrubbed from everything the run returns.
852
+ expect(out.stdout).toContain("secret:");
853
+ expect(JSON.stringify(out)).not.toContain(secretValue);
854
+ },
855
+ CHILD_PROCESS_TEST_BUDGET_MS,
856
+ );
857
+
858
+ skip(
859
+ "named run resolves config for the invoking agent",
860
+ async () => {
861
+ expect((await upsert({ name: "config-reader", source: readerSource }, leadId)).status).toBe(
862
+ 200,
863
+ );
864
+ const out = await run({ name: "config-reader" }, leadId);
865
+ expect(out.error).toBeUndefined();
866
+ // The worker's agent-scope override does not leak to another agent.
867
+ expect(out.result.override).toBe("from-global");
868
+ expect(out.result.plain).toBe("plain-global");
869
+ expect(out.result.secretMatches).toBe(true);
870
+ expect(out.result.missingIsUndefined).toBe(true);
871
+ },
872
+ CHILD_PROCESS_TEST_BUDGET_MS,
873
+ );
874
+
875
+ skip(
876
+ "a short secret the run read stays out of stdout, result and the stored run row",
877
+ async () => {
878
+ // No `secret:` label in the log line: that prefix triggers structural
879
+ // masking and would hide a missing value-level redaction.
880
+ const source = `
881
+ import type { ScriptContext } from "swarm-sdk";
882
+ export default async (_args: unknown, ctx: ScriptContext) => {
883
+ const raw = ctx.stdlib.Redacted.value(ctx.swarm.config.get("SCRIPT_CFG_SHORT_SECRET")!);
884
+ console.log("read:", raw);
885
+ return { leaked: raw, length: raw.length, [raw]: "as-key" };
886
+ };`;
887
+ const response = await dispatch("/api/scripts/run", {
888
+ method: "POST",
889
+ agentId: workerId,
890
+ body: JSON.stringify({ source, intent: "short config secret redaction" }),
891
+ });
892
+ expect(response.status).toBe(200);
893
+ const out = (await response.json()) as {
894
+ result: Record<string, unknown>;
895
+ stdout: string;
896
+ error?: string;
897
+ };
898
+ expect(out.error).toBeUndefined();
899
+ // The script really read the value: only its rendering is redacted.
900
+ expect(out.result.length).toBe(shortSecretValue.length);
901
+ expect(out.stdout).toContain("read: [REDACTED:config:SCRIPT_CFG_SHORT_SECRET]");
902
+ expect(out.stdout).not.toContain(shortSecretValue);
903
+ expect(out.result.leaked).toBe("[REDACTED:config:SCRIPT_CFG_SHORT_SECRET]");
904
+ expect(JSON.stringify(out.result)).not.toContain(shortSecretValue);
905
+
906
+ const row = await getDbClient().get<{ output: string | null }>(
907
+ "SELECT output FROM script_runs WHERE agentId = ? AND source = ?",
908
+ [workerId, source],
909
+ );
910
+ expect(row?.output).toBeTruthy();
911
+ expect(row?.output).toContain("[REDACTED:config:SCRIPT_CFG_SHORT_SECRET]");
912
+ expect(row?.output).not.toContain(shortSecretValue);
913
+ },
914
+ CHILD_PROCESS_TEST_BUDGET_MS,
915
+ );
916
+
917
+ test("an import rejection quoting a short secret stays out of stderr and the stored run row", async () => {
918
+ // The import check returns before any process spawns, so its diagnostic
919
+ // needs the run-local scrub too.
920
+ const source = `import nope from "${shortSecretValue}"; export default async () => nope;`;
921
+ const response = await dispatch("/api/scripts/run", {
922
+ method: "POST",
923
+ agentId: workerId,
924
+ body: JSON.stringify({ source, intent: "short config secret in import diagnostic" }),
925
+ });
926
+ expect(response.status).toBe(200);
927
+ const out = (await response.json()) as { stderr: string; error?: string };
928
+ expect(out.error).toBe("import_violation");
929
+ expect(out.stderr).toContain("[REDACTED:config:SCRIPT_CFG_SHORT_SECRET]");
930
+ expect(out.stderr).not.toContain(shortSecretValue);
931
+
932
+ const row = await getDbClient().get<{ error: string | null }>(
933
+ "SELECT error FROM script_runs WHERE agentId = ? AND source = ?",
934
+ [workerId, source],
935
+ );
936
+ expect(row?.error).toBeTruthy();
937
+ expect(row?.error).not.toContain(shortSecretValue);
938
+ });
939
+ });
@@ -10,7 +10,7 @@ import type { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/se
10
10
  import { closeDb, createAgent, getDbClient, initDb, upsertSwarmConfig } from "../be/db";
11
11
  import { _resetAutoReloadForTests, flushPendingIntegrationsReload } from "../http/core";
12
12
  import { handleMcp } from "../http/mcp";
13
- import { getBasePrompt } from "../prompts/base-prompt";
13
+ import { getBasePrompt, getTaskContextPrompt } from "../prompts/base-prompt";
14
14
  import { createServer } from "../server";
15
15
  import { resolveScriptsOnlyMode } from "../utils/scripts-only-mode";
16
16
  import { listenOnFreePort } from "./test-net";
@@ -371,9 +371,8 @@ describe("scripts-only prompt gating", () => {
371
371
  });
372
372
 
373
373
  test("uses the scripts-only Slack variant for Slack-originated tasks", async () => {
374
- const prompt = await getBasePrompt({
374
+ const prompt = await getTaskContextPrompt({
375
375
  role: "worker",
376
- agentId: "scripts-only-slack-agent",
377
376
  scriptsOnly: true,
378
377
  slackContext: { channelId: "C123", threadTs: "123.456" },
379
378
  });
@@ -96,54 +96,72 @@ describe("runScript", () => {
96
96
  expect(output.result).toEqual({ status: 200, parsed: { ok: true } });
97
97
  });
98
98
 
99
- spawnTest("ctx.swarm bridge round-trips kv_set then kv_get", async () => {
100
- const entries = new Map<string, unknown>();
101
- const server = Bun.serve({
102
- port: 0,
103
- async fetch(req) {
104
- expect(req.headers.get("authorization")).toBe(
105
- "Bearer example-runtime-test-secret-1234567890",
106
- );
107
- expect(req.headers.get("x-agent-id")).toBe("agent-1");
99
+ spawnTest(
100
+ "ctx.swarm bridge respects URL overrides and PORT fallback",
101
+ async () => {
102
+ const entries = new Map<string, unknown>();
103
+ const agentId = crypto.randomUUID();
104
+ const server = Bun.serve({
105
+ hostname: "localhost",
106
+ port: 0,
107
+ async fetch(req) {
108
+ expect(req.headers.get("authorization")).toBe(
109
+ "Bearer example-runtime-test-secret-1234567890",
110
+ );
111
+ expect(req.headers.get("x-agent-id")).toBe(agentId);
112
+
113
+ const url = new URL(req.url);
114
+ if (req.method === "PUT" && url.pathname.startsWith("/api/kv/")) {
115
+ const key = decodeURIComponent(url.pathname.slice("/api/kv/".length));
116
+ const body = (await req.json()) as { value: unknown };
117
+ entries.set(key, body.value);
118
+ return Response.json({ key, value: body.value });
119
+ }
120
+ if (req.method === "GET" && url.pathname.startsWith("/api/kv/")) {
121
+ const key = decodeURIComponent(url.pathname.slice("/api/kv/".length));
122
+ return Response.json({ key, value: entries.get(key) ?? null });
123
+ }
124
+ return Response.json({ error: "not found" }, { status: 404 });
125
+ },
126
+ });
108
127
 
109
- const url = new URL(req.url);
110
- if (req.method === "PUT" && url.pathname.startsWith("/api/kv/")) {
111
- const key = decodeURIComponent(url.pathname.slice("/api/kv/".length));
112
- const body = (await req.json()) as { value: unknown };
113
- entries.set(key, body.value);
114
- return Response.json({ key, value: body.value });
115
- }
116
- if (req.method === "GET" && url.pathname.startsWith("/api/kv/")) {
117
- const key = decodeURIComponent(url.pathname.slice("/api/kv/".length));
118
- return Response.json({ key, value: entries.get(key) ?? null });
119
- }
120
- return Response.json({ error: "not found" }, { status: 404 });
121
- },
122
- });
128
+ try {
129
+ const baseUrl = `http://localhost:${server.port}`;
130
+ for (const urlSource of ["input", "MCP_BASE_URL", "PORT"] as const) {
131
+ delete process.env.PORT;
132
+ process.env.MCP_BASE_URL = "http://unused.invalid";
133
+ if (urlSource === "MCP_BASE_URL") {
134
+ process.env.MCP_BASE_URL = baseUrl;
135
+ } else if (urlSource === "PORT") {
136
+ delete process.env.MCP_BASE_URL;
137
+ process.env.PORT = String(server.port);
138
+ }
123
139
 
124
- try {
125
- const output = await runScript({
126
- agentId: "agent-1",
127
- mcpBaseUrl: `http://127.0.0.1:${server.port}`,
128
- resources,
129
- source: `
140
+ const output = await runScript({
141
+ agentId,
142
+ mcpBaseUrl: urlSource === "input" ? baseUrl : undefined,
143
+ resources,
144
+ source: `
130
145
  export default async (_args, ctx) => {
131
146
  await ctx.swarm.kv_set({ key: "bridge-smoke", value: { ok: true } });
132
147
  return await ctx.swarm.kv_get({ key: "bridge-smoke" });
133
148
  };
134
149
  `,
135
- });
150
+ });
136
151
 
137
- expect(output.error).toBeUndefined();
138
- expect(output.result).toEqual({
139
- success: true,
140
- status: 200,
141
- data: { key: "bridge-smoke", value: { ok: true } },
142
- });
143
- } finally {
144
- server.stop(true);
145
- }
146
- });
152
+ expect(output.error, `${urlSource}: ${output.stderr}`).toBeUndefined();
153
+ expect(output.result, urlSource).toEqual({
154
+ success: true,
155
+ status: 200,
156
+ data: { key: "bridge-smoke", value: { ok: true } },
157
+ });
158
+ }
159
+ } finally {
160
+ server.stop(true);
161
+ }
162
+ },
163
+ CHILD_PROCESS_TEST_BUDGET_MS,
164
+ );
147
165
 
148
166
  spawnTest("ctx.swarm.kv_get forwards path/offset/limit as a view query", async () => {
149
167
  const seen: string[] = [];
@@ -1,10 +1,14 @@
1
1
  import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
+ import { openAICompatibleKeyVars } from "@desplega/model-catalog";
2
3
  import {
3
4
  clearVolatileSecretsForTesting,
5
+ createValueScrubber,
4
6
  isSensitiveKey,
5
7
  refreshSecretScrubberCache,
8
+ registerEnvSecrets,
6
9
  registerSensitiveKeyName,
7
10
  registerVolatileSecret,
11
+ SCRUBBER_RULES_VERSION,
8
12
  scrubObject,
9
13
  scrubSecrets,
10
14
  unregisterVolatileSecret,
@@ -77,6 +81,33 @@ describe("scrubSecrets — env-based replacement", () => {
77
81
  expect(out).toBe("key=[REDACTED:FOO_SERVICE_API_KEY] tail");
78
82
  });
79
83
 
84
+ test("redacts NaN and Helmcode keys, including the suffix-less HELMCODE_API_NAN alias", () => {
85
+ const values = {
86
+ NAN_API_KEY: `example-nan-${randomToken(24)}`,
87
+ HELMCODE_API_NAN: `example-nanalias-${randomToken(24)}`,
88
+ HELMCODE_API_KEY: `example-helm-${randomToken(24)}`,
89
+ };
90
+ for (const [name, value] of Object.entries(values)) process.env[name] = value;
91
+ refreshSecretScrubberCache();
92
+ for (const [name, value] of Object.entries(values)) {
93
+ expect(isSensitiveKey(name)).toBe(true);
94
+ expect(scrubSecrets(`Bearer ${value} tail`)).toBe(`Bearer [REDACTED:${name}] tail`);
95
+ }
96
+ });
97
+
98
+ test("the registry key vars are pinned to SCRUBBER_RULES_VERSION", () => {
99
+ // Every registry key var is an exact sensitive key, so a new registry
100
+ // entry changes the rules: bump SCRUBBER_RULES_VERSION (the boot sweep
101
+ // re-redacts stored rows once per version), then update this pin.
102
+ expect({
103
+ version: SCRUBBER_RULES_VERSION,
104
+ keyVars: [...openAICompatibleKeyVars()].sort(),
105
+ }).toEqual({
106
+ version: 8,
107
+ keyVars: ["AI_GATEWAY_API_KEY", "HELMCODE_API_KEY", "HELMCODE_API_NAN", "NAN_API_KEY"],
108
+ });
109
+ });
110
+
80
111
  test("redacts any key with _TOKEN suffix", () => {
81
112
  process.env.WEIRD_SERVICE_TOKEN = "example-weirdserviceTOKENvalue_1234567890";
82
113
  refreshSecretScrubberCache();
@@ -390,6 +421,23 @@ describe("registerVolatileSecret", () => {
390
421
  expect(out).not.toContain(secret);
391
422
  });
392
423
 
424
+ test("registerEnvSecrets covers a per-task env that never reached process.env", () => {
425
+ const picked = `opaque${randomToken(20)}`;
426
+ const member = `opaque${randomToken(20)}`;
427
+ const env = {
428
+ HELMCODE_API_KEY: picked,
429
+ HELMCODE_API_NAN: `${member},opaque${randomToken(20)}`,
430
+ HELMCODE_BASE_URL: "http://127.0.0.1:9/v1",
431
+ };
432
+ expect(process.env.HELMCODE_API_KEY).not.toBe(picked);
433
+ expect(scrubSecrets(`echo ${picked}`)).toContain(picked);
434
+ registerEnvSecrets(env);
435
+ expect(scrubSecrets(`echo ${picked}`)).toBe("echo [REDACTED:HELMCODE_API_KEY]");
436
+ expect(scrubSecrets(`echo ${member}`)).toBe("echo [REDACTED:HELMCODE_API_NAN]");
437
+ // A non-secret key stays readable.
438
+ expect(scrubSecrets("http://127.0.0.1:9/v1")).toBe("http://127.0.0.1:9/v1");
439
+ });
440
+
393
441
  test("ignores values shorter than the minimum length", () => {
394
442
  registerVolatileSecret("short", "TOO_SHORT");
395
443
  const out = scrubSecrets("contains short somewhere");
@@ -889,3 +937,41 @@ describe("scrubSecrets — over-redaction guards", () => {
889
937
  { retry: 2 },
890
938
  );
891
939
  });
940
+
941
+ describe("createValueScrubber (per-run exact values)", () => {
942
+ test("redacts values under the global floor without registering them swarm-wide", () => {
943
+ const short = randomToken(5);
944
+ const scrub = createValueScrubber([{ value: short, name: "config:SHORT" }]);
945
+ expect(scrub.text(`got ${short}!`)).toBe("got [REDACTED:config:SHORT]!");
946
+ expect(scrubSecrets(`got ${short}!`)).toBe(`got ${short}!`);
947
+ });
948
+
949
+ test("a one-char value is redacted everywhere in the run's output", () => {
950
+ const scrub = createValueScrubber([{ value: "x", name: "config:ONE" }]);
951
+ expect(scrub.text("x+x")).toBe("[REDACTED:config:ONE]+[REDACTED:config:ONE]");
952
+ });
953
+
954
+ test("one pass, longest first: a marker is never rewritten by another value", () => {
955
+ const scrub = createValueScrubber([
956
+ { value: "RED", name: "config:A" },
957
+ { value: "REDACT", name: "config:B" },
958
+ ]);
959
+ expect(scrub.text("RED REDACT")).toBe("[REDACTED:config:A] [REDACTED:config:B]");
960
+ });
961
+
962
+ test("covers JSON-escaped forms, object keys and nested values", () => {
963
+ const value = 'a"b';
964
+ const scrub = createValueScrubber([{ value, name: "config:Q" }]);
965
+ expect(scrub.text(JSON.stringify({ v: value }))).toBe('{"v":"[REDACTED:config:Q]"}');
966
+ expect(scrub.object({ [value]: [value, 1, null] })).toEqual({
967
+ "[REDACTED:config:Q]": ["[REDACTED:config:Q]", 1, null],
968
+ });
969
+ });
970
+
971
+ test("no values and empty values are a no-op", () => {
972
+ const scrub = createValueScrubber([{ value: "", name: "config:EMPTY" }]);
973
+ expect(scrub.text("unchanged")).toBe("unchanged");
974
+ const obj = { a: 1 };
975
+ expect(scrub.object(obj)).toBe(obj);
976
+ });
977
+ });
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Multi-file (bundled) seeded skills.
3
3
  *
4
- * Regression coverage for the collision that shipped `artifacts` twice — once
4
+ * Regression coverage for the collision that shipped the since-retired
5
+ * `artifacts` skill twice — once
5
6
  * baked into the worker image with an `examples/` directory, once seeded from
6
7
  * `templates/skills/` as a simple skill. The DB copy won at runtime, dropped a
7
8
  * `.swarm-managed` marker, and the next reconcile pass deleted the baked
@@ -58,17 +59,9 @@ describe("seeded skills with bundled files", () => {
58
59
  await removeDbFiles(TEST_DB_PATH);
59
60
  });
60
61
 
61
- test("embedded manifest exposes bundled files for artifacts and pages", async () => {
62
+ test("embedded manifest exposes bundled files for pages", async () => {
62
63
  const skills = await loadSeedSkills();
63
64
 
64
- const artifacts = skills.find((skill) => skill.name === "artifacts");
65
- expect(artifacts?.files.map((file) => file.path).sort()).toEqual([
66
- "examples/approval-flow.ts",
67
- "examples/hono-dashboard.ts",
68
- "examples/multi-artifact.ts",
69
- "examples/static-report.sh",
70
- ]);
71
-
72
65
  const pages = skills.find((skill) => skill.name === "pages");
73
66
  expect(pages?.files.map((file) => file.path).sort()).toEqual([
74
67
  "examples/annotated-pr.html",
@@ -84,7 +77,7 @@ describe("seeded skills with bundled files", () => {
84
77
  const embedded = await loadSeedSkills();
85
78
  const fromDisk = await loadSeedSkills(TEMPLATES_DIR);
86
79
 
87
- for (const name of ["artifacts", "pages"]) {
80
+ for (const name of ["pages", "script-builder"]) {
88
81
  const a = embedded.find((skill) => skill.name === name);
89
82
  const b = fromDisk.find((skill) => skill.name === name);
90
83
  expect(a?.files).toEqual(b?.files ?? []);
@@ -107,18 +100,17 @@ describe("seeded skills with bundled files", () => {
107
100
  });
108
101
 
109
102
  test("seeding writes skill_files rows and marks the skill complex", async () => {
110
- const artifacts = await getSkillByName("artifacts", "swarm");
111
- expect(artifacts).toBeTruthy();
112
- if (!artifacts) return;
103
+ const pages = await getSkillByName("pages", "swarm");
104
+ expect(pages).toBeTruthy();
105
+ if (!pages) return;
113
106
 
114
- expect(artifacts.isComplex).toBe(true);
107
+ expect(pages.isComplex).toBe(true);
115
108
 
116
- const files = await getSkillFiles(artifacts.id);
109
+ const files = await getSkillFiles(pages.id);
117
110
  expect(files.map((file) => file.path).sort()).toEqual([
118
- "examples/approval-flow.ts",
119
- "examples/hono-dashboard.ts",
120
- "examples/multi-artifact.ts",
121
- "examples/static-report.sh",
111
+ "examples/annotated-pr.html",
112
+ "examples/multiplayer-board.html",
113
+ "examples/report-page.html",
122
114
  ]);
123
115
  expect(files.every((file) => file.content.length > 0)).toBe(true);
124
116
  });
@@ -131,24 +123,24 @@ describe("seeded skills with bundled files", () => {
131
123
  });
132
124
 
133
125
  test("a bundled-file edit registers as drift and is preserved", async () => {
134
- const item = (await skillsSeeder.items()).find((candidate) => candidate.key === "artifacts");
126
+ const item = (await skillsSeeder.items()).find((candidate) => candidate.key === "pages");
135
127
  expect(item).toBeDefined();
136
128
  if (!item) return;
137
129
 
138
130
  // Freshly seeded and untouched: upstream hashes to exactly the source.
139
131
  expect(await skillsSeeder.upstreamHash(item)).toBe(item.contentHash);
140
132
 
141
- const artifacts = await getSkillByName("artifacts", "swarm");
142
- if (!artifacts) throw new Error("artifacts skill missing");
143
- const original = (await getSkillFiles(artifacts.id)).find(
144
- (file) => file.path === "examples/hono-dashboard.ts",
133
+ const pages = await getSkillByName("pages", "swarm");
134
+ if (!pages) throw new Error("pages skill missing");
135
+ const original = (await getSkillFiles(pages.id)).find(
136
+ (file) => file.path === "examples/report-page.html",
145
137
  );
146
138
  if (!original) throw new Error("bundled example missing");
147
139
 
148
140
  // Simulate a user editing a bundled file through the skills API.
149
- await upsertSkillFile(artifacts.id, {
141
+ await upsertSkillFile(pages.id, {
150
142
  path: original.path,
151
- content: `${original.content}\n// edited by a user`,
143
+ content: `${original.content}\n<!-- edited by a user -->`,
152
144
  });
153
145
 
154
146
  // The edit must move the upstream hash — otherwise the seeder would think
@@ -159,11 +151,11 @@ describe("seeded skills with bundled files", () => {
159
151
  expect(result.failed).toEqual([]);
160
152
  expect(result.skippedUserModified).toBeGreaterThan(0);
161
153
 
162
- const after = (await getSkillFiles(artifacts.id)).find((file) => file.path === original.path);
163
- expect(after?.content).toContain("// edited by a user");
154
+ const after = (await getSkillFiles(pages.id)).find((file) => file.path === original.path);
155
+ expect(after?.content).toContain("<!-- edited by a user -->");
164
156
 
165
157
  // Restore so later tests see a pristine skill.
166
- await upsertSkillFile(artifacts.id, { path: original.path, content: original.content });
158
+ await upsertSkillFile(pages.id, { path: original.path, content: original.content });
167
159
  });
168
160
 
169
161
  test("a userInvocable flip registers as drift and is preserved", async () => {
@@ -195,24 +187,24 @@ describe("seeded skills with bundled files", () => {
195
187
  // format (no bundled-file section). The new seeder must still recognise it
196
188
  // as pristine and update it — not misread it as user-modified and skip it
197
189
  // forever, which would mean the merged content never reaches production.
198
- const artifacts = await getSkillByName("artifacts", "swarm");
199
- if (!artifacts) throw new Error("artifacts skill missing");
190
+ const pages = await getSkillByName("pages", "swarm");
191
+ if (!pages) throw new Error("pages skill missing");
200
192
 
201
- const legacyContent = "---\nname: artifacts\ndescription: old\n---\n\nold body\n";
202
- await updateSkill(artifacts.id, { content: legacyContent, isComplex: false });
203
- for (const file of await getSkillFiles(artifacts.id)) {
204
- await deleteSkillFile(artifacts.id, file.path);
193
+ const legacyContent = "---\nname: pages\ndescription: old\n---\n\nold body\n";
194
+ await updateSkill(pages.id, { content: legacyContent, isComplex: false });
195
+ for (const file of await getSkillFiles(pages.id)) {
196
+ await deleteSkillFile(pages.id, file.path);
205
197
  }
206
198
 
207
199
  // Old-format hash: base only, no file section. The flag mirrors the seeded
208
- // config so the test does not pin artifacts' systemDefault value.
200
+ // config so the test does not pin pages' systemDefault value.
209
201
  const legacyHash = computeContentHash(
210
- `${legacyContent}\n\n# seed:systemDefault=${artifacts.systemDefault ? 1 : 0}\n`,
202
+ `${legacyContent}\n\n# seed:systemDefault=${pages.systemDefault ? 1 : 0}\n`,
211
203
  );
212
- await recordSeedState("skill", "artifacts", legacyHash);
204
+ await recordSeedState("skill", "pages", legacyHash);
213
205
 
214
- const item = (await skillsSeeder.items()).find((candidate) => candidate.key === "artifacts");
215
- if (!item) throw new Error("artifacts seed item missing");
206
+ const item = (await skillsSeeder.items()).find((candidate) => candidate.key === "pages");
207
+ if (!item) throw new Error("pages seed item missing");
216
208
 
217
209
  // Pristine: upstream (file-less) still hashes in the old format.
218
210
  expect(await skillsSeeder.upstreamHash(item)).toBe(legacyHash);
@@ -221,10 +213,10 @@ describe("seeded skills with bundled files", () => {
221
213
  expect(result.failed).toEqual([]);
222
214
  expect(result.skippedUserModified).toBe(0);
223
215
 
224
- const upgraded = await getSkillByName("artifacts", "swarm");
216
+ const upgraded = await getSkillByName("pages", "swarm");
225
217
  expect(upgraded?.isComplex).toBe(true);
226
218
  expect(upgraded?.content).not.toBe(legacyContent);
227
- expect((await getSkillFiles(upgraded?.id ?? "")).length).toBe(4);
219
+ expect((await getSkillFiles(upgraded?.id ?? "")).length).toBe(3);
228
220
 
229
221
  // And it settles: the next run is a clean no-op.
230
222
  const second = await runSeeder(skillsSeeder, { quiet: true });
@@ -273,18 +265,18 @@ describe("seeded skills with bundled files", () => {
273
265
  });
274
266
 
275
267
  test("bundled files survive repeated filesystem syncs (the reconcile bug)", async () => {
276
- const artifacts = await getSkillByName("artifacts", "swarm");
277
- expect(artifacts).toBeTruthy();
278
- if (!artifacts) return;
268
+ const pages = await getSkillByName("pages", "swarm");
269
+ expect(pages).toBeTruthy();
270
+ if (!pages) return;
279
271
 
280
272
  const entry: SkillFsEntry = {
281
- id: artifacts.id,
282
- name: artifacts.name,
283
- content: artifacts.content,
284
- isComplex: artifacts.isComplex,
273
+ id: pages.id,
274
+ name: pages.name,
275
+ content: pages.content,
276
+ isComplex: pages.isComplex,
285
277
  isEnabled: true,
286
278
  isActive: true,
287
- files: (await getSkillFiles(artifacts.id)).map((file) => ({
279
+ files: (await getSkillFiles(pages.id)).map((file) => ({
288
280
  path: file.path,
289
281
  content: file.content,
290
282
  isBinary: file.isBinary,
@@ -297,9 +289,9 @@ describe("seeded skills with bundled files", () => {
297
289
  FAKE_HOME,
298
290
  ".claude",
299
291
  "skills",
300
- "artifacts",
292
+ "pages",
301
293
  "examples",
302
- "hono-dashboard.ts",
294
+ "report-page.html",
303
295
  );
304
296
  expect(existsSync(examplePath)).toBe(true);
305
297