@desplega.ai/agent-swarm 1.173.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (432) hide show
  1. package/README.md +1 -1
  2. package/dist/{acp-adapter-ccm9xrep.js → acp-adapter-sj2g0m84.js} +17 -16
  3. package/dist/{actions-sgeyv08k.js → actions-fegk44gr.js} +39 -34
  4. package/dist/{amp-adapter-gpxp3kc9.js → amp-adapter-ad8ft89x.js} +8 -7
  5. package/dist/{app-km9qnv2f.js → app-kfvgbvzy.js} +18 -14
  6. package/dist/{assistant-5r8tcvvd.js → assistant-z7zdy9ja.js} +40 -35
  7. package/dist/{boot-reembed-swwzdgd7.js → boot-reembed-5c2z0mbe.js} +10 -9
  8. package/dist/{boot-reembed-16rv9843.js → boot-reembed-j8xa181e.js} +10 -9
  9. package/dist/{boot-scrub-sweep-zkr6vkqb.js → boot-scrub-sweep-hnkn3rgt.js} +11 -10
  10. package/dist/{boot-seeds-d8sp49t2.js → boot-seeds-kwjq4d2z.js} +14 -13
  11. package/dist/{claude-adapter-yzzzh8jp.js → claude-adapter-9vmvpvgd.js} +19 -18
  12. package/dist/{claude-managed-adapter-xgxhqrt2.js → claude-managed-adapter-k9qae05j.js} +7 -5
  13. package/dist/{claude-managed-setup-bnmjarjc.js → claude-managed-setup-q54zx5q2.js} +6 -4
  14. package/dist/{claude-sdk-session-4whxere4.js → claude-sdk-session-9jye6cxr.js} +11881 -11472
  15. package/dist/{cli-94b48jhk.js → cli-03gedhng.js} +16 -14
  16. package/dist/{cli-1w0vj3gw.js → cli-1e4b6bzh.js} +7 -7
  17. package/dist/{cli-d603qf86.js → cli-29jc5q7w.js} +10 -8
  18. package/dist/cli-2m1ktfzz.js +17795 -0
  19. package/dist/{cli-x119kdba.js → cli-3dj8k4vy.js} +1 -1
  20. package/dist/{cli-srqq3gc5.js → cli-3fbmmawm.js} +5 -5
  21. package/dist/{cli-gmdajfw5.js → cli-4jxm2qtz.js} +4987 -1708
  22. package/dist/{cli-tadfgd0t.js → cli-5hkm7whv.js} +33 -4
  23. package/dist/{cli-42qhyvwx.js → cli-5t4f29ez.js} +3 -3
  24. package/dist/{cli-7a0sde2x.js → cli-6cagzc96.js} +9 -0
  25. package/dist/cli-6cvfxs8r.js +47 -0
  26. package/dist/{cli-0kxf81jj.js → cli-6jyj59nb.js} +2 -2
  27. package/dist/{cli-w1apjecb.js → cli-6razj6t4.js} +2 -2
  28. package/dist/{cli-anzdk0z4.js → cli-74ahz61x.js} +2 -2
  29. package/dist/{cli-m89t9jdf.js → cli-7x0g4sn1.js} +15416 -16024
  30. package/dist/{cli-yatxqynm.js → cli-9ha6hs2d.js} +1 -290
  31. package/dist/{cli-81r1wjyv.js → cli-9nfx44eh.js} +1 -1
  32. package/dist/cli-9traj3dn.js +638 -0
  33. package/dist/{cli-smcrrmhy.js → cli-agzebbd5.js} +9 -9
  34. package/dist/{cli-na2pxe4f.js → cli-apx87jne.js} +2 -2
  35. package/dist/{cli-rfz2ve55.js → cli-bzszjf2p.js} +2855 -862
  36. package/dist/{cli-c5km5znr.js → cli-c8r3kzzb.js} +1 -1
  37. package/dist/{cli-hsem16hb.js → cli-cgtksawx.js} +7 -4
  38. package/dist/{cli-db3m6zdt.js → cli-d2qr7aj4.js} +1 -1
  39. package/dist/{cli-rfs4vddv.js → cli-d5f9ed5c.js} +5 -5
  40. package/dist/cli-e3g5mbkz.js +776 -0
  41. package/dist/{cli-7jykv7ef.js → cli-e6mpjhed.js} +1 -1
  42. package/dist/{cli-7a93cv8c.js → cli-ee9gtz70.js} +98 -23
  43. package/dist/{cli-3nxdmjrc.js → cli-eejsgvm4.js} +4 -4
  44. package/dist/{cli-fmhj1ehj.js → cli-ef1hg1wv.js} +470 -51
  45. package/dist/cli-f527ce74.js +781 -0
  46. package/dist/{cli-ppy4nnnh.js → cli-f7kr7svz.js} +2 -2
  47. package/dist/cli-hjwmhcg1.js +10622 -0
  48. package/dist/{cli-2bw3w47j.js → cli-hnhtk6cj.js} +1 -1
  49. package/dist/{cli-ywm1azg9.js → cli-hnmhtzvh.js} +6 -4
  50. package/dist/{cli-h7k0xtry.js → cli-hvqbtna4.js} +4 -1256
  51. package/dist/{cli-s8z6x4b4.js → cli-j0pfptdp.js} +218 -102
  52. package/dist/cli-jta2eyzc.js +619 -0
  53. package/dist/{cli-5er5gjbm.js → cli-k382tshb.js} +10 -11
  54. package/dist/{cli-v3zhnxsb.js → cli-k3dp5tpf.js} +2 -2
  55. package/dist/{cli-wd0hz6ah.js → cli-kbrt8gqa.js} +9 -91
  56. package/dist/cli-kceah1eh.js +92 -0
  57. package/dist/{cli-ne7smj1b.js → cli-ke896fcf.js} +10 -10
  58. package/dist/{cli-xc7p7s3g.js → cli-kt6zqg0e.js} +6 -6
  59. package/dist/{cli-wrh08zjd.js → cli-m50t4309.js} +5 -18
  60. package/dist/cli-n2wq4j7m.js +6985 -0
  61. package/dist/{cli-bv2etw2y.js → cli-n3hzg89b.js} +46 -15
  62. package/dist/{cli-tmhsxhb8.js → cli-n5vwg842.js} +23 -6
  63. package/dist/{cli-qm1448f9.js → cli-ne236afy.js} +8 -8
  64. package/dist/{cli-d161g69q.js → cli-p1x85d2t.js} +50 -2
  65. package/dist/{cli-7pz3b7x5.js → cli-przpmdgr.js} +25 -0
  66. package/dist/{cli-0a7mm3zp.js → cli-q0ke1yww.js} +25 -21
  67. package/dist/{cli-e3pmyt39.js → cli-q50942rs.js} +1 -1
  68. package/dist/{cli-r44t9wqc.js → cli-r106725k.js} +2 -2
  69. package/dist/{cli-phcqp1b9.js → cli-r2pnvj8g.js} +2 -2
  70. package/dist/{cli-jpc4r224.js → cli-r62a35ea.js} +19 -3
  71. package/dist/{cli-cd0hv9nh.js → cli-ra0xzggj.js} +2 -2
  72. package/dist/cli-rhs9pz33.js +294 -0
  73. package/dist/{cli-r4fqfpwf.js → cli-s2sp957d.js} +50 -27
  74. package/dist/{cli-1x7da0de.js → cli-s4setq91.js} +8 -8
  75. package/dist/{cli-6thk4jz3.js → cli-se8w6wny.js} +6 -6
  76. package/dist/cli-skxvvpap.js +10140 -0
  77. package/dist/{cli-014vns2y.js → cli-stfs0k5s.js} +208 -525
  78. package/dist/{cli-0p3enk35.js → cli-tqaazemc.js} +9 -1
  79. package/dist/{cli-nwrdx840.js → cli-tr4sxp68.js} +3 -3
  80. package/dist/{cli-r0byd61h.js → cli-tsde1hdr.js} +15 -15
  81. package/dist/{cli-b07hr3bt.js → cli-txqb9s38.js} +2 -2
  82. package/dist/{cli-6r2f7edk.js → cli-v1yb0rqb.js} +6 -6
  83. package/dist/{cli-saqyage0.js → cli-vatyhshy.js} +179 -27
  84. package/dist/{cli-5wpw4rab.js → cli-vdach5a9.js} +5 -5
  85. package/dist/{cli-7avy4v8w.js → cli-w1q7h2r2.js} +5 -3
  86. package/dist/{cli-rcp5aqdw.js → cli-w7m81tb7.js} +1 -1
  87. package/dist/{cli-c3rk1v0x.js → cli-wayw93nt.js} +3 -3
  88. package/dist/cli-wmgrkscj.js +23254 -0
  89. package/dist/{cli-9xf7fdtp.js → cli-z5jjpaxw.js} +29 -6
  90. package/dist/{cli-451ks6aw.js → cli-zkpks3r3.js} +1 -1
  91. package/dist/cli.js +29 -45
  92. package/dist/{codex-adapter-r3dn4fp5.js → codex-adapter-y81sssk0.js} +15 -14
  93. package/dist/{codex-login-841kv5fk.js → codex-login-f2rcfpp9.js} +3 -2
  94. package/dist/{codex-session-runner-fwt793h4.js → codex-session-runner-8vx4j6a9.js} +15 -14
  95. package/dist/{commands-z8fq9qna.js → commands-kcpvpcrr.js} +11 -10
  96. package/dist/{controller-hq5ggpcp.js → controller-ywrmt6yd.js} +5 -4
  97. package/dist/{controllers-connect-3mf2btyj.js → controllers-connect-kwpvj3rf.js} +42 -37
  98. package/dist/{cursor-adapter-q8pgv5qa.js → cursor-adapter-cehv1qns.js} +7 -6
  99. package/dist/{db-yyhqqrbf.js → db-k8ankj20.js} +12 -11
  100. package/dist/{db-qfpq4bt7.js → db-ww5d7jq3.js} +28 -9
  101. package/dist/{devin-adapter-xgtj680j.js → devin-adapter-x1022qb1.js} +3 -2
  102. package/dist/{dispatcher-x0x7qfcq.js → dispatcher-e8gq5m68.js} +38 -33
  103. package/dist/{docker-f503k9p0.js → docker-ff6y1190.js} +3 -2
  104. package/dist/{dsh-adapter-34etvb5m.js → dsh-adapter-3epjm3af.js} +7 -6
  105. package/dist/{e2b-66xra4az.js → e2b-8ygwhg0f.js} +4 -3
  106. package/dist/{e2b-xwhyj8cw.js → e2b-swhnpw3n.js} +4 -3
  107. package/dist/{event-bus-vjtv82gm.js → event-bus-6axnngdj.js} +3 -2
  108. package/dist/{external-channel-h1fjecwe.js → external-channel-h3359cwn.js} +11 -10
  109. package/dist/{google-generative-ai-szdxbpqg.js → google-generative-ai-g8db7x0f.js} +4 -2
  110. package/dist/{google-vertex-qzw3p36d.js → google-vertex-r2692q0r.js} +4 -2
  111. package/dist/{grok-adapter-e7mr287e.js → grok-adapter-0d4x7gkx.js} +18 -17
  112. package/dist/{handlers-znb9n7d4.js → handlers-an1gzv21.js} +41 -36
  113. package/dist/{heartbeat-bm4qkn8d.js → heartbeat-6dakeb61.js} +43 -38
  114. package/dist/{hook-254r4mza.js → hook-6v6cv6d5.js} +11 -23
  115. package/dist/{hook-xaa3q80s.js → hook-sa5xye4y.js} +12 -11
  116. package/dist/{http-3dgrgp8y.js → http-r7f3eky0.js} +1499 -251
  117. package/dist/index-52j1aja4.js +3465 -0
  118. package/dist/{index-fx3jqq1e.js → index-7ep4gzdk.js} +38 -33
  119. package/dist/index-9gw58n4k.js +193 -0
  120. package/dist/index-9j87mcm8.js +56305 -0
  121. package/dist/index-bsmcresk.js +79424 -0
  122. package/dist/index-mh85qhec.js +475 -0
  123. package/dist/{index-g2byveez.js → index-rmpw9a8q.js} +38 -33
  124. package/dist/{index-2ybk7khy.js → index-s2wxgzr0.js} +468 -683
  125. package/dist/{index-bdsqa799.js → index-v1v6n7hg.js} +43 -38
  126. package/dist/index-we27kd31.js +12810 -0
  127. package/dist/{index-jd7sejm2.js → index-zk96aq1k.js} +10 -9
  128. package/dist/{keepalive-6ywhpja1.js → keepalive-be9vwy5h.js} +12 -11
  129. package/dist/{lead-cw45n21s.js → lead-0861cwrf.js} +61 -56
  130. package/dist/{maintenance-ss1a1ge9.js → maintenance-mzzgc8bn.js} +10 -9
  131. package/dist/{node-fkajaq96.js → node-mn1zs7an.js} +2 -1
  132. package/dist/{oauth-refresh-sweep-3c6f8vyy.js → oauth-refresh-sweep-cpdnfwdj.js} +11 -10
  133. package/dist/{onboard-8hzdhc1k.js → onboard-fh8qs4r3.js} +11 -3
  134. package/dist/{opencode-adapter-qbchbcfz.js → opencode-adapter-nm42wwj8.js} +18 -13
  135. package/dist/{otel-impl-e6mnvc4v.js → otel-impl-x6c1c9g4.js} +242 -868
  136. package/dist/{pi-mono-adapter-3c9g9snh.js → pi-mono-adapter-9n2ya240.js} +244 -106
  137. package/dist/{pricing-refresh-gedkjdw0.js → pricing-refresh-qb0rv0ap.js} +12 -11
  138. package/dist/{rbac-roles-dt4vh1yy.js → rbac-roles-1gzdwbh1.js} +11 -10
  139. package/dist/{rbac-roles-eszdg6pm.js → rbac-roles-3mraey52.js} +12 -11
  140. package/dist/{render-v2-907cy6sa.js → render-v2-agpa45zz.js} +18 -14
  141. package/dist/{resume-42stcaz0.js → resume-x0ydy9s9.js} +25 -21
  142. package/dist/{runtime-qfry88v2.js → runtime-sa2vr16g.js} +2 -2
  143. package/dist/{setup-adbc1c10.js → setup-as9ga0d6.js} +2 -2
  144. package/dist/{virtual-modules-rtmt494q.js → virtual-modules-9gkxqrap.js} +6 -5
  145. package/dist/{work-objects-wpj8ck95.js → work-objects-y5g1y7ev.js} +41 -36
  146. package/dist/{worker-bzdra9rf.js → worker-860c3e38.js} +61 -56
  147. package/dist/{x-qfmygqqv.js → x-m477fr0h.js} +3 -2
  148. package/openapi.json +523 -344
  149. package/package.json +10 -11
  150. package/src/be/config-refs.ts +60 -0
  151. package/src/be/db/page-versions.ts +15 -0
  152. package/src/be/db/runtime.ts +35 -8
  153. package/src/be/db/skill-name-resolution.ts +36 -0
  154. package/src/be/db/tasks/read.ts +230 -0
  155. package/src/be/db/tasks/write.ts +7 -0
  156. package/src/be/db.ts +52 -7
  157. package/src/be/migrations/215_swarm_config_export_to_env.sql +34 -0
  158. package/src/be/migrations/216_task_resolved_effort.sql +5 -0
  159. package/src/be/migrations/217_chat_sdk_state.sql +104 -0
  160. package/src/be/model-catalog-store.ts +5 -1
  161. package/src/be/model-tier-resolution.ts +12 -1
  162. package/src/be/model-validation.ts +5 -2
  163. package/src/be/models-catalog.ts +26 -1
  164. package/src/be/modelsdev-cache.json +198 -63
  165. package/src/be/modelsdev-cache.ts +9 -1
  166. package/src/be/script-credential-broker.ts +39 -2
  167. package/src/be/scripts/run-saved.ts +1 -0
  168. package/src/be/scripts/typecheck.ts +10 -8
  169. package/src/be/seed/runner.ts +13 -1
  170. package/src/be/seed/state-db.ts +5 -0
  171. package/src/be/seed/types.ts +6 -0
  172. package/src/be/seed-pricing.ts +19 -2
  173. package/src/be/seed-scripts/catalog/ops-catalog-audit.inline.ts +1 -1
  174. package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +1 -1
  175. package/src/be/seed-skills/bundled-files.generated.json +0 -18
  176. package/src/be/seed-skills/index.ts +104 -3
  177. package/src/be/swarm-config-guard.ts +52 -0
  178. package/src/cli.tsx +0 -16
  179. package/src/commands/onboard/non-interactive.ts +4 -2
  180. package/src/commands/provider-credentials.ts +52 -6
  181. package/src/commands/runner.ts +190 -54
  182. package/src/e2b/env.ts +5 -0
  183. package/src/extensions/contract-types.generated.ts +1 -1
  184. package/src/extensions/contract.ts +45 -0
  185. package/src/extensions/dispatcher.ts +7 -0
  186. package/src/extensions/post-bridge.ts +40 -0
  187. package/src/github/handlers.ts +11 -4
  188. package/src/heartbeat/templates.ts +8 -5
  189. package/src/hooks/hook.ts +0 -26
  190. package/src/http/all-routes.ts +0 -1
  191. package/src/http/config.ts +36 -17
  192. package/src/http/core.ts +4 -0
  193. package/src/http/index.ts +14 -2
  194. package/src/http/integrations.ts +88 -1
  195. package/src/http/mcp.ts +15 -5
  196. package/src/http/page-proxy.ts +2 -4
  197. package/src/http/pages-public.ts +3 -3
  198. package/src/http/pages.ts +3 -13
  199. package/src/http/poll.ts +28 -68
  200. package/src/http/requested-by.ts +54 -0
  201. package/src/http/scripts.ts +1 -0
  202. package/src/http/stats.ts +1 -30
  203. package/src/http/tasks.ts +61 -5
  204. package/src/http/utils.ts +14 -0
  205. package/src/http/webhooks.ts +159 -1
  206. package/src/http/x.ts +1 -0
  207. package/src/integrations/chat-sdk/adapters.ts +129 -0
  208. package/src/integrations/chat-sdk/attachments.ts +232 -0
  209. package/src/integrations/chat-sdk/background.ts +36 -0
  210. package/src/integrations/chat-sdk/commands.ts +194 -0
  211. package/src/integrations/chat-sdk/config.ts +198 -0
  212. package/src/integrations/chat-sdk/discord-api.ts +63 -0
  213. package/src/integrations/chat-sdk/discord-gateway.ts +185 -0
  214. package/src/integrations/chat-sdk/handlers.ts +28 -0
  215. package/src/integrations/chat-sdk/inbound.ts +426 -0
  216. package/src/integrations/chat-sdk/limits.ts +48 -0
  217. package/src/integrations/chat-sdk/logger.ts +81 -0
  218. package/src/integrations/chat-sdk/outbound.ts +123 -0
  219. package/src/integrations/chat-sdk/relay.ts +172 -0
  220. package/src/integrations/chat-sdk/router.ts +169 -0
  221. package/src/integrations/chat-sdk/runtime.ts +351 -0
  222. package/src/integrations/chat-sdk/schema.ts +245 -0
  223. package/src/integrations/chat-sdk/state.ts +243 -0
  224. package/src/integrations/chat-sdk/status.ts +244 -0
  225. package/src/integrations/chat-sdk/steering.ts +58 -0
  226. package/src/integrations/chat-sdk/teams-origin.ts +77 -0
  227. package/src/integrations/chat-sdk/telegram-api.ts +56 -0
  228. package/src/integrations/chat-sdk/templates.ts +16 -0
  229. package/src/integrations/chat-sdk/test-hooks.ts +37 -0
  230. package/src/integrations/chat-sdk/thread-buffer.ts +100 -0
  231. package/src/integrations/chat-sdk/typing.ts +65 -0
  232. package/src/pages/version.ts +4 -5
  233. package/src/prompts/base-prompt.ts +111 -45
  234. package/src/providers/acp-target-catalog.ts +6 -0
  235. package/src/providers/acp-targets.ts +55 -2
  236. package/src/providers/amp-adapter.ts +1 -1
  237. package/src/providers/claude-adapter.ts +1 -1
  238. package/src/providers/claude-flag-settings.ts +17 -5
  239. package/src/providers/claude-managed-adapter.ts +1 -1
  240. package/src/providers/claude-session-events.ts +6 -1
  241. package/src/providers/dsh-adapter.ts +1 -1
  242. package/src/providers/opencode-adapter.ts +243 -26
  243. package/src/providers/opencode-swarm-plugin.ts +31 -0
  244. package/src/providers/pi-mono-adapter.ts +398 -153
  245. package/src/providers/pricing-sources.md +33 -2
  246. package/src/providers/reasoning-effort.ts +27 -0
  247. package/src/providers/types.ts +6 -2
  248. package/src/rbac/legacy-policy.ts +8 -2
  249. package/src/rbac/permissions.ts +21 -0
  250. package/src/scheduler/scheduler.ts +4 -2
  251. package/src/scripts-runtime/loader.ts +19 -7
  252. package/src/scripts-runtime/sdk-allowlist.ts +8 -6
  253. package/src/scripts-runtime/swarm-sdk.ts +0 -11
  254. package/src/scripts-runtime/types/stdlib.d.ts +27 -15
  255. package/src/scripts-runtime/types/swarm-extension.d.ts +63 -1
  256. package/src/scripts-runtime/types/swarm-sdk.d.ts +27 -15
  257. package/src/server.ts +52 -18
  258. package/src/slack/event-dedup.ts +1 -1
  259. package/src/tasks/additive-buffer.ts +4 -2
  260. package/src/tasks/context-key.ts +60 -1
  261. package/src/tasks/model-pin.ts +67 -0
  262. package/src/tasks/worker-follow-up.ts +4 -31
  263. package/src/tests/acp-adapter.test.ts +42 -0
  264. package/src/tests/api-drain-worker-e2e.test.ts +1 -6
  265. package/src/tests/base-prompt.test.ts +58 -54
  266. package/src/tests/chat-sdk-commands.test.ts +191 -0
  267. package/src/tests/chat-sdk-config.test.ts +334 -0
  268. package/src/tests/chat-sdk-discord-api.test.ts +68 -0
  269. package/src/tests/chat-sdk-discord-gateway.test.ts +252 -0
  270. package/src/tests/chat-sdk-discord-webhook.test.ts +126 -0
  271. package/src/tests/chat-sdk-fields-sync.test.ts +50 -0
  272. package/src/tests/chat-sdk-harness.ts +146 -0
  273. package/src/tests/chat-sdk-inbound.test.ts +363 -0
  274. package/src/tests/chat-sdk-relay.test.ts +470 -0
  275. package/src/tests/chat-sdk-reload.test.ts +128 -0
  276. package/src/tests/chat-sdk-shutdown.test.ts +220 -0
  277. package/src/tests/chat-sdk-slack.test.ts +268 -0
  278. package/src/tests/chat-sdk-state.test.ts +163 -0
  279. package/src/tests/chat-sdk-status-route.test.ts +382 -0
  280. package/src/tests/chat-sdk-teams.test.ts +277 -0
  281. package/src/tests/chat-sdk-tools.test.ts +520 -0
  282. package/src/tests/chat-sdk-typing.test.ts +120 -0
  283. package/src/tests/chat-sdk-webhook-telegram.test.ts +200 -0
  284. package/src/tests/claude-adapter-binary.test.ts +9 -4
  285. package/src/tests/claude-flag-settings.test.ts +11 -3
  286. package/src/tests/claude-managed-adapter.test.ts +1 -1
  287. package/src/tests/claude-sdk-lifecycle.test.ts +9 -3
  288. package/src/tests/claude-thinking-tokens.test.ts +57 -0
  289. package/src/tests/claude-worker-parity.test.ts +2 -7
  290. package/src/tests/config-env-export-chat-sdk.test.ts +11 -0
  291. package/src/tests/config-refs.test.ts +66 -0
  292. package/src/tests/context-key.test.ts +19 -0
  293. package/src/tests/cors-allowlist.test.ts +25 -0
  294. package/src/tests/create-page-tool.test.ts +18 -0
  295. package/src/tests/credential-check.test.ts +139 -0
  296. package/src/tests/credentials.test.ts +21 -0
  297. package/src/tests/db-runtime.test.ts +61 -0
  298. package/src/tests/db-task-read-contract.test.ts +20 -0
  299. package/src/tests/db-task-write-contract.test.ts +10 -0
  300. package/src/tests/docs-index-footprint.test.js +3 -3
  301. package/src/tests/dsh-adapter.test.ts +1 -1
  302. package/src/tests/enabled-capabilities.test.ts +20 -0
  303. package/src/tests/entrypoint-api-readiness.test.ts +14 -8
  304. package/src/tests/entrypoint-config-env-export.test.ts +5 -2
  305. package/src/tests/entrypoint-export-to-env.test.ts +78 -0
  306. package/src/tests/explicit-model-validation.test.ts +105 -1
  307. package/src/tests/extensions-example-route-chat-channel.test.ts +154 -0
  308. package/src/tests/extensions-post-bridge.test.ts +51 -0
  309. package/src/tests/fetch-resolved-env.test.ts +128 -0
  310. package/src/tests/fixtures/chat-sdk/telegram-private-text.json +10 -0
  311. package/src/tests/fixtures/extensions/route-chat-channel-to-agent/hooks.ts +27 -0
  312. package/src/tests/fixtures/extensions/route-chat-channel-to-agent/manifest.json +9 -0
  313. package/src/tests/fixtures/openai-compatible-registry-extension.child.ts +146 -0
  314. package/src/tests/fixtures/runner-legacy-resume-prompt.ts +122 -0
  315. package/src/tests/github-handlers.test.ts +35 -0
  316. package/src/tests/harness-provider-resolution.test.ts +12 -0
  317. package/src/tests/heartbeat-checklist.test.ts +3 -2
  318. package/src/tests/hook-runtime-close.test.ts +0 -2
  319. package/src/tests/http-api-integration.test.ts +0 -25
  320. package/src/tests/migration-046-budgets.test.ts +17 -16
  321. package/src/tests/migration-217-chat-sdk-state.test.ts +113 -0
  322. package/src/tests/model-tier-resolution.test.ts +93 -0
  323. package/src/tests/models-catalog.test.ts +42 -1
  324. package/src/tests/openai-compatible-providers.test.ts +134 -0
  325. package/src/tests/openai-compatible-registry-extension.test.ts +42 -0
  326. package/src/tests/opencode-adapter.test.ts +216 -2
  327. package/src/tests/opencode-helmcode-sdk.test.ts +201 -0
  328. package/src/tests/opencode-shell-env.test.ts +145 -0
  329. package/src/tests/openrouter-base-url.test.ts +101 -151
  330. package/src/tests/page-proxy.test.ts +1 -1
  331. package/src/tests/pages-browser-sdk.test.ts +76 -0
  332. package/src/tests/pages-feedback.test.ts +1 -1
  333. package/src/tests/paused-tasks-requested-by.test.ts +102 -0
  334. package/src/tests/pi-custom-provider-fake-server.test.ts +442 -0
  335. package/src/tests/pi-mono-adapter.test.ts +385 -5
  336. package/src/tests/pi-shell-env.test.ts +170 -0
  337. package/src/tests/prompt-template-remaining.test.ts +35 -0
  338. package/src/tests/rbac-engine.test.ts +11 -3
  339. package/src/tests/runner-exit-after-tasks.test.ts +0 -4
  340. package/src/tests/runner-legacy-resume-prompt.test.ts +64 -0
  341. package/src/tests/script-connections.test.ts +2 -0
  342. package/src/tests/scripts-http.test.ts +201 -1
  343. package/src/tests/scripts-only-gating.test.ts +2 -3
  344. package/src/tests/scripts-runtime.test.ts +58 -40
  345. package/src/tests/secret-scrubber.test.ts +86 -0
  346. package/src/tests/seed-skills-bundled-files.test.ts +45 -53
  347. package/src/tests/seed-skills-retire.test.ts +182 -0
  348. package/src/tests/send-task-requester-gate.test.ts +58 -0
  349. package/src/tests/session-cost-recompute-unit.test.ts +46 -0
  350. package/src/tests/skill-get-name-resolution.test.ts +137 -0
  351. package/src/tests/sonnet-5-5-registry.test.ts +9 -1
  352. package/src/tests/swarm-config-export-to-env.test.ts +267 -0
  353. package/src/tests/swarm-config-reserved-keys.test.ts +16 -0
  354. package/src/tests/swarm-diff.test.ts +1 -1
  355. package/src/tests/task-model-effort-rerun.test.ts +164 -0
  356. package/src/tests/task-session-prompts.test.ts +132 -0
  357. package/src/tests/task-tool-manifest.test.ts +14 -0
  358. package/src/tests/task-tools-ctx.test.ts +2 -0
  359. package/src/tests/tool-annotations.test.ts +6 -6
  360. package/src/tests/ui-logs-parser.test.ts +43 -0
  361. package/src/tests/used-by-route.test.ts +6 -0
  362. package/src/tests/vercel-ai-gateway.test.ts +149 -0
  363. package/src/tests/workflow-swarm-script.test.ts +38 -0
  364. package/src/tests/workflow-wait-filter.test.ts +5 -3
  365. package/src/tools/chat-access.ts +41 -0
  366. package/src/tools/chat-open-dm.ts +59 -0
  367. package/src/tools/chat-post.ts +74 -0
  368. package/src/tools/chat-read-thread.ts +100 -0
  369. package/src/tools/chat-reply.ts +75 -0
  370. package/src/tools/create-page.ts +3 -13
  371. package/src/tools/model-catalog/index.ts +2 -1
  372. package/src/tools/register-chat-channel.ts +139 -0
  373. package/src/tools/send-task.ts +24 -0
  374. package/src/tools/skills/skill-get.ts +17 -9
  375. package/src/tools/swarm-config/get-config.ts +2 -1
  376. package/src/tools/swarm-config/list-config.ts +2 -1
  377. package/src/tools/swarm-config/set-config.ts +16 -3
  378. package/src/tools/tool-config.ts +8 -6
  379. package/src/types.ts +12 -2
  380. package/src/utils/config-env-export.ts +74 -0
  381. package/src/utils/credentials.test.ts +92 -0
  382. package/src/utils/credentials.ts +52 -4
  383. package/src/utils/harness-provider.ts +15 -6
  384. package/src/utils/runtime-model-catalog.ts +11 -1
  385. package/src/utils/secret-scrubber.ts +71 -2
  386. package/src/utils/task-tool-manifest.ts +19 -2
  387. package/src/workflows/executors/swarm-script.ts +1 -0
  388. package/templates/skills/agent-browser/SKILL.md +1 -1
  389. package/templates/skills/agent-browser/content.md +1 -1
  390. package/templates/skills/apps/SKILL.md +8 -1
  391. package/templates/skills/apps/content.md +8 -1
  392. package/templates/skills/chat-sdk/config.json +14 -0
  393. package/templates/skills/chat-sdk/content.md +84 -0
  394. package/templates/skills/composio-gmail/content.md +7 -0
  395. package/templates/skills/composio-google-docs/content.md +1 -1
  396. package/templates/skills/email-domain-health/config.json +14 -0
  397. package/templates/skills/email-domain-health/content.md +131 -0
  398. package/templates/skills/kv-storage/content.md +2 -2
  399. package/templates/skills/pages/content.md +8 -10
  400. package/templates/skills/swarm-extensions/content.md +2 -0
  401. package/templates/skills/swarm-scripts/SKILL.md +4 -2
  402. package/templates/skills/swarm-scripts/config.json +1 -1
  403. package/templates/skills/swarm-scripts/content.md +4 -2
  404. package/templates/skills/workflow-iterate/content.md +38 -0
  405. package/dist/artifact-eczsknc3.js +0 -3843
  406. package/dist/cli-bd957dgb.js +0 -153
  407. package/dist/cli-q51yprfq.js +0 -1104
  408. package/dist/cli-t1ze64h3.js +0 -811
  409. package/dist/cli-tcc4wd5d.js +0 -56471
  410. package/dist/index-9ch1n761.js +0 -1102
  411. package/src/artifact-sdk/index.ts +0 -2
  412. package/src/artifact-sdk/localtunnel.d.ts +0 -20
  413. package/src/artifact-sdk/port.ts +0 -12
  414. package/src/artifact-sdk/server.ts +0 -183
  415. package/src/artifact-sdk/tunnel.ts +0 -19
  416. package/src/commands/artifact.ts +0 -249
  417. package/src/http/ecosystem.ts +0 -77
  418. package/src/tests/artifact-commands.test.ts +0 -92
  419. package/src/tests/artifact-sdk.test.ts +0 -793
  420. package/src/tests/register-service-security.test.ts +0 -194
  421. package/src/tools/list-services.ts +0 -107
  422. package/src/tools/register-service.ts +0 -177
  423. package/src/tools/unregister-service.ts +0 -73
  424. package/src/tools/update-service-status.ts +0 -96
  425. package/templates/skills/artifacts/config.json +0 -14
  426. package/templates/skills/artifacts/content.md +0 -344
  427. package/templates/skills/artifacts/files/examples/approval-flow.ts +0 -34
  428. package/templates/skills/artifacts/files/examples/hono-dashboard.ts +0 -31
  429. package/templates/skills/artifacts/files/examples/multi-artifact.ts +0 -20
  430. package/templates/skills/artifacts/files/examples/static-report.sh +0 -17
  431. /package/src/{artifact-sdk → pages}/browser-sdk.ts +0 -0
  432. /package/src/{artifact-sdk → pages}/feedback-overlay.ts +0 -0
@@ -49,6 +49,10 @@ const LEAD_ONLY_VERBS: PermissionVerb[] = [
49
49
  "memory.write.consolidated",
50
50
  "channel.delete",
51
51
  "integration.kapso.manage",
52
+ "integration.chat.manage",
53
+ "integration.chat.post",
54
+ "integration.chat.read",
55
+ "integration.chat.dm",
52
56
  "integration.slack.post",
53
57
  "integration.slack.read",
54
58
  "integration.slack.thread.start",
@@ -140,6 +144,7 @@ const REQUESTER_OWNS_TASK_VERBS: PermissionVerb[] = [
140
144
  const COMPOSITE_VERBS: PermissionVerb[] = [
141
145
  "memory.delete.any",
142
146
  "task.fs.mutate",
147
+ "task.parent.attach",
143
148
  "task.progress.write",
144
149
  ];
145
150
 
@@ -570,9 +575,12 @@ describe("memory.delete.any composite (human, lead for swarm, owner for agent sc
570
575
  });
571
576
  });
572
577
 
573
- describe("task.fs.mutate composite (operator OR user OR lead OR assignee OR creator)", () => {
574
- test("only an unrelated worker agent is denied", () => {
575
- expectDecisions("task.fs.mutate", TASK_RESOURCE, {
578
+ describe("task.fs.mutate and task.parent.attach composite (operator OR user OR lead OR assignee OR creator)", () => {
579
+ test.each([
580
+ "task.fs.mutate",
581
+ "task.parent.attach",
582
+ ] as const)("%s: only an unrelated worker agent is denied", (verb) => {
583
+ expectDecisions(verb, TASK_RESOURCE, {
576
584
  lead: true,
577
585
  worker: false,
578
586
  ownerWorker: true, // assignee (task.agentId)
@@ -122,8 +122,6 @@ test(
122
122
  const tempDir = `/tmp/runner-exit-shutdown-${crypto.randomUUID()}`;
123
123
  await Bun.$`mkdir -p ${tempDir}`.quiet();
124
124
  try {
125
- await Bun.write(`${tempDir}/pm2`, '#!/bin/sh\nprintf "%s" "$1" > "$SHUTDOWN_TEST_PM2_LOG"\n');
126
- await Bun.$`chmod +x ${tempDir}/pm2`.quiet();
127
125
  await Bun.write(
128
126
  `${tempDir}/runner.ts`,
129
127
  `
@@ -141,14 +139,12 @@ test(
141
139
  ...process.env,
142
140
  PATH: tempDir,
143
141
  ANONYMIZED_TELEMETRY: "false",
144
- SHUTDOWN_TEST_PM2_LOG: `${tempDir}/pm2.log`,
145
142
  },
146
143
  }),
147
144
  "runner shutdown",
148
145
  );
149
146
  expect(result.stdout).toContain("shutdown callback");
150
147
  expect(result.stdout).not.toContain("unexpected return");
151
- expect(await Bun.file(`${tempDir}/pm2.log`).text()).toBe("save");
152
148
  expect(requests).toHaveLength(1);
153
149
  expect(requests[0]?.path).toBe("/close");
154
150
  expect(requests[0]?.headers["x-agent-id"]).toBe(agentId);
@@ -0,0 +1,64 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { mkdtempSync, readFileSync, rmSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { CHILD_PROCESS_TEST_BUDGET_MS, expectChildOk, runChild } from "./test-proc";
6
+
7
+ // Must match src/tests/fixtures/runner-legacy-resume-prompt.ts.
8
+ const REQUESTER_SENTINEL = "RESUME_REQUESTER_SENTINEL_7f3a";
9
+ const MISSING_DIR = "/nonexistent/legacy-resume-dir-7f3a";
10
+
11
+ describe("runner legacy paused-task resume prompt", () => {
12
+ test(
13
+ "resumed session gets the requester profile and missing-dir warning in its task prompt",
14
+ async () => {
15
+ const directory = mkdtempSync(join(tmpdir(), "runner-legacy-resume-"));
16
+ try {
17
+ expectChildOk(
18
+ await runChild(
19
+ [
20
+ process.execPath,
21
+ join(import.meta.dir, "fixtures/runner-legacy-resume-prompt.ts"),
22
+ directory,
23
+ ],
24
+ {
25
+ cwd: directory,
26
+ env: {
27
+ PATH: process.env.PATH,
28
+ HOME: directory,
29
+ AGENT_ID: "11111111-1111-4111-8111-111111111111",
30
+ API_KEY: "example-fixture-key",
31
+ MCP_BASE_URL: "http://runner.invalid",
32
+ HARNESS_PROVIDER: "pi",
33
+ CRED_CHECK_DISABLE: "1",
34
+ DATABASE_PATH: ":memory:",
35
+ ANONYMIZED_TELEMETRY: "false",
36
+ STEERING_ENABLED: "false",
37
+ },
38
+ },
39
+ ),
40
+ "Runner legacy resume fixture",
41
+ );
42
+ const result = JSON.parse(readFileSync(join(directory, "result.json"), "utf8")) as {
43
+ taskId: string;
44
+ prompt: string;
45
+ systemPrompt: string;
46
+ };
47
+
48
+ expect(result.taskId).toBe("44444444-4444-4444-8444-444444444444");
49
+ expect(result.prompt).toContain(REQUESTER_SENTINEL);
50
+ expect(result.prompt).toContain("Fixture Requester");
51
+ // The same text the fresh task path sends (missingTaskDirWarning).
52
+ expect(result.prompt).toContain(
53
+ `Note: The task requested working directory "${MISSING_DIR}" but it does not exist. Falling back to default directory.`,
54
+ );
55
+ // Per-task context stays out of the cacheable system prompt.
56
+ expect(result.systemPrompt).not.toContain(REQUESTER_SENTINEL);
57
+ expect(result.systemPrompt).not.toContain(MISSING_DIR);
58
+ } finally {
59
+ rmSync(directory, { recursive: true, force: true });
60
+ }
61
+ },
62
+ CHILD_PROCESS_TEST_BUDGET_MS,
63
+ );
64
+ });
@@ -626,6 +626,8 @@ describe("script connections", () => {
626
626
  markMigrationApplied(database, "207_runtime_session_tokens.sql");
627
627
  // 213 rebuilds script_connections with the post-117 columns this fixture skips.
628
628
  markMigrationApplied(database, "213_script_connections_sql.sql");
629
+ // 215 alters swarm_config, which this migration-112-only fixture does not create.
630
+ markMigrationApplied(database, "215_swarm_config_export_to_env.sql");
629
631
 
630
632
  const id = crypto.randomUUID();
631
633
  const now = new Date().toISOString();
@@ -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
+ });