@desplega.ai/agent-swarm 2.4.4 → 2.5.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 (382) hide show
  1. package/README.md +4 -2
  2. package/dist/{acp-adapter-thq04adh.js → acp-adapter-11gp67nr.js} +8 -6
  3. package/dist/{actions-j6ywh6x5.js → actions-8p2kze8j.js} +24 -22
  4. package/dist/{amp-adapter-k8b5xmj8.js → amp-adapter-z4qvhacj.js} +2 -2
  5. package/dist/{anthropic-messages-g6pqmqk4.js → anthropic-messages-txs190dd.js} +17 -20
  6. package/dist/{app-rb7vc02s.js → app-0qp0a2gk.js} +3 -3
  7. package/dist/{assistant-ftta1zjf.js → assistant-jedd3ktm.js} +27 -25
  8. package/dist/{azure-openai-responses-9dbmj346.js → azure-openai-responses-9kfwhmn7.js} +18 -13
  9. package/dist/{boot-reembed-xns7h5h9.js → boot-reembed-4xwq5tg0.js} +1 -1
  10. package/dist/{boot-reembed-srp6v6af.js → boot-reembed-n4wq5dd2.js} +1 -1
  11. package/dist/{boot-scrub-sweep-3qqrh2mz.js → boot-scrub-sweep-69h90xcq.js} +4 -4
  12. package/dist/{boot-seeds-9ks8hce7.js → boot-seeds-3vn0m9sg.js} +4 -4
  13. package/dist/{claude-adapter-k5ksyres.js → claude-adapter-221mvad5.js} +8 -6
  14. package/dist/{claude-managed-adapter-h2y889ge.js → claude-managed-adapter-gt6pa35d.js} +2 -2
  15. package/dist/{claude-managed-setup-n5a5eqks.js → claude-managed-setup-c3hp36ph.js} +4 -4
  16. package/dist/{claude-sdk-session-2j3j4kwn.js → claude-sdk-session-8thjsk5e.js} +9729 -9628
  17. package/dist/{cli-b9fcxxev.js → cli-007cnhm1.js} +50 -4
  18. package/dist/{cli-j4rqdf2n.js → cli-03vpq45j.js} +2 -2
  19. package/dist/{cli-amyhm3gj.js → cli-0y6f0ckq.js} +1 -1
  20. package/dist/{cli-4dg55wsk.js → cli-0z3vjrpm.js} +1 -1
  21. package/dist/{cli-pccfmtb5.js → cli-179xcyky.js} +11 -11
  22. package/dist/{cli-3r8samv3.js → cli-1f0gp9vz.js} +3 -2
  23. package/dist/{cli-hj3debgw.js → cli-1pr672hz.js} +3 -3
  24. package/dist/{cli-0bqteszy.js → cli-2nf3b08t.js} +328 -25
  25. package/dist/{cli-t7cyef5g.js → cli-36j4jnk3.js} +4 -4
  26. package/dist/{cli-er83cjbn.js → cli-3qv8naq8.js} +527 -231
  27. package/dist/cli-3zwye9sn.js +1193 -0
  28. package/dist/{cli-60cgh1rv.js → cli-459fk97x.js} +7 -7
  29. package/dist/{cli-xj0re5tm.js → cli-4w24f7z3.js} +1 -1
  30. package/dist/{cli-v3yr2gbg.js → cli-5kzk3472.js} +2198 -968
  31. package/dist/{cli-h7gc0b4r.js → cli-5m84zd2k.js} +55 -18
  32. package/dist/{cli-w6nadkh4.js → cli-5rvg1ddk.js} +5 -5
  33. package/dist/{cli-dvkmp9bt.js → cli-6ctwrf1z.js} +20 -10
  34. package/dist/{cli-9qtjfd60.js → cli-6rg2jn2y.js} +3 -2
  35. package/dist/{cli-2v045xyf.js → cli-7bwtc6yx.js} +3 -3
  36. package/dist/{cli-3cgngyd8.js → cli-7g40g8hq.js} +35 -17
  37. package/dist/{cli-q6r7t299.js → cli-92mqenkc.js} +2379 -154
  38. package/dist/{cli-cynvc9fj.js → cli-9ce2xw98.js} +8 -8
  39. package/dist/{cli-gqvh8d8a.js → cli-bgdtv62r.js} +36 -22
  40. package/dist/{cli-wxbn3yfx.js → cli-cz5nmdxt.js} +2 -2
  41. package/dist/{cli-wt645v7q.js → cli-dabehxhp.js} +1 -1
  42. package/dist/{cli-vfha21c9.js → cli-e5bxnjps.js} +2 -2
  43. package/dist/{cli-p20r8w42.js → cli-fcc5qe3f.js} +763 -268
  44. package/dist/{cli-1ephgwnq.js → cli-fes51ya2.js} +3 -3
  45. package/dist/{cli-1d70crsy.js → cli-ftmq4nvk.js} +2 -2
  46. package/dist/{cli-qkhc5aqy.js → cli-fy1jyyym.js} +13 -13
  47. package/dist/{cli-s95dknxh.js → cli-g1cw7xtt.js} +1 -1
  48. package/dist/{cli-qje9k2nh.js → cli-g9ypd5en.js} +12 -2
  49. package/dist/{cli-74c1wpm0.js → cli-gpv60278.js} +1 -1
  50. package/dist/{cli-pemywhfq.js → cli-ha9rmwgq.js} +2 -1
  51. package/dist/{cli-bnz0r3kc.js → cli-j1eghqfm.js} +5 -5
  52. package/dist/{cli-2zqpkvtt.js → cli-jpxsrxcb.js} +1 -1
  53. package/dist/{cli-ejs3m1sx.js → cli-kxpfx2xr.js} +1 -1
  54. package/dist/{cli-37yb7307.js → cli-mbfj11fq.js} +1 -1
  55. package/dist/{cli-pnp2j5zg.js → cli-mmhj8vd7.js} +2 -2
  56. package/dist/{cli-xf234ws8.js → cli-n4c8e3s5.js} +2 -0
  57. package/dist/{cli-a0kcty51.js → cli-n6kmqngw.js} +2 -2
  58. package/dist/{cli-te6b5wvg.js → cli-nnvjb17s.js} +3 -3
  59. package/dist/{cli-1dmcq2eh.js → cli-nzfnt2gs.js} +1 -1
  60. package/dist/{cli-625tb7aq.js → cli-p859hcc8.js} +2 -2
  61. package/dist/{cli-smvcw9z5.js → cli-pasyvjmm.js} +1 -1
  62. package/dist/cli-pkaqk57w.js +128 -0
  63. package/dist/{cli-8nyhf7za.js → cli-qqzmjxkf.js} +129 -105
  64. package/dist/{cli-zrwpctp4.js → cli-r25qpaxt.js} +161 -63
  65. package/dist/{cli-nc35pxb7.js → cli-rh5smt8t.js} +19 -4
  66. package/dist/{cli-qwkcwjdn.js → cli-rycdaj0a.js} +1 -1
  67. package/dist/{cli-esxerzg7.js → cli-s4sntwt4.js} +2 -2
  68. package/dist/{cli-b8k7epj1.js → cli-segs7hx9.js} +8 -8
  69. package/dist/{cli-36ckw0wx.js → cli-stxdrqs2.js} +2 -2
  70. package/dist/{cli-vdm20wej.js → cli-sy8f9705.js} +4 -4
  71. package/dist/{cli-1m17dc9d.js → cli-ta8bd7th.js} +2 -2
  72. package/dist/{cli-2y91wnbh.js → cli-we0ra057.js} +222 -10
  73. package/dist/{cli-55qmbp6z.js → cli-x21dqn18.js} +5 -5
  74. package/dist/{cli-dqb4n629.js → cli-x6vf4sad.js} +11 -11
  75. package/dist/{cli-d2mb5s8a.js → cli-x77gen2p.js} +680 -440
  76. package/dist/{cli-vq97g1jq.js → cli-xawhpgc9.js} +95 -195
  77. package/dist/{cli-82d3nkc3.js → cli-xd4eqtr2.js} +2 -2
  78. package/dist/{cli-9fhkkpj5.js → cli-xt768r5m.js} +2 -2
  79. package/dist/{cli-zrk6jxv8.js → cli-y4x8ww6s.js} +2 -2
  80. package/dist/{cli-xc27fwzg.js → cli-y9jvxfhk.js} +3 -3
  81. package/dist/{cli-44159k28.js → cli-z63a269p.js} +2 -2
  82. package/dist/{cli-sm8rses9.js → cli-zfdp01hn.js} +8 -5
  83. package/dist/cli.js +31 -29
  84. package/dist/{cloudflare-workers-ai-system-one-ch2wxfcp.js → cloudflare-workers-ai-system-one-43v9qkjk.js} +7 -2
  85. package/dist/{codex-adapter-968xwfy7.js → codex-adapter-tcg1q3jk.js} +6 -4
  86. package/dist/{codex-login-m819c41r.js → codex-login-rn7c5gxt.js} +1 -1
  87. package/dist/{codex-session-runner-ht4z99fa.js → codex-session-runner-xfx4wd9x.js} +7 -5
  88. package/dist/{commands-pcymaeg9.js → commands-ew83nj70.js} +2 -2
  89. package/dist/{controller-fpjyk46r.js → controller-gnq1k1mf.js} +4 -4
  90. package/dist/{controllers-connect-mwzx1fh6.js → controllers-connect-tw1dan9r.js} +26 -24
  91. package/dist/{cursor-adapter-cp4s2e2f.js → cursor-adapter-yq5xgfcg.js} +1 -1
  92. package/dist/{db-xsjg9jqq.js → db-7gbc53fd.js} +14 -2
  93. package/dist/{db-sgd12bt9.js → db-t86xrnes.js} +3 -3
  94. package/dist/{devin-adapter-zgqadvnm.js → devin-adapter-4ycy4vrf.js} +1 -1
  95. package/dist/{dispatcher-x0jfw2c9.js → dispatcher-64bdyhps.js} +23 -21
  96. package/dist/{docker-fkgtr75t.js → docker-nfy2gc8t.js} +2 -2
  97. package/dist/{dsh-adapter-jrq7ma7h.js → dsh-adapter-tvhc4k2d.js} +1 -1
  98. package/dist/{e2b-16d6ch39.js → e2b-9gkdhm3c.js} +2 -2
  99. package/dist/{e2b-zn0b4w5w.js → e2b-cjtf8k1z.js} +2 -2
  100. package/dist/{easter-egg-3d-5cgynaja.js → easter-egg-3d-e4qv8dxj.js} +1 -1
  101. package/dist/{event-bus-9vpk8gmq.js → event-bus-3z78ep5n.js} +1 -1
  102. package/dist/{execute-t4e6m3rw.js → execute-0xpz8950.js} +85 -23
  103. package/dist/{external-channel-8njj4h3b.js → external-channel-e9nw24zv.js} +2 -2
  104. package/dist/{google-generative-ai-bmq8gkf3.js → google-generative-ai-mk9vcpe1.js} +15 -15
  105. package/dist/{google-vertex-b1rvp52s.js → google-vertex-q580kz8k.js} +20 -20
  106. package/dist/{grok-adapter-kkr97v2f.js → grok-adapter-v82srhrx.js} +9 -7
  107. package/dist/handlers-c9ajzhbn.js +41 -0
  108. package/dist/{heartbeat-wd03zhn0.js → heartbeat-2vqs1319.js} +26 -24
  109. package/dist/{hook-xzj2ts2p.js → hook-01nk13wx.js} +7 -5
  110. package/dist/{hook-f0vghj42.js → hook-jzpheegg.js} +9 -7
  111. package/dist/{http-4hwxv62p.js → http-fc1k6ec8.js} +859 -252
  112. package/dist/index-07ef7nkm.js +42 -0
  113. package/dist/{index-g6tbb6ve.js → index-31ceyf42.js} +1 -1
  114. package/dist/index-7yts8n9s.js +43 -0
  115. package/dist/{index-c4ykd0f1.js → index-dr94zbnh.js} +13348 -11611
  116. package/dist/index-q3t40ryv.js +46 -0
  117. package/dist/{index-ww2vznkp.js → index-sepja052.js} +1 -1
  118. package/dist/{index-97q4xn03.js → index-zdycv3ja.js} +135 -79
  119. package/dist/{index-g216q6jr.js → index-zsyx51ap.js} +1 -4
  120. package/dist/{keepalive-hrh7xnr9.js → keepalive-5nh2rhmr.js} +4 -4
  121. package/dist/lead-aayq64jn.js +79 -0
  122. package/dist/{llama-cpp-classify-vv43gswy.js → llama-cpp-classify-ee7vmd7q.js} +4 -1
  123. package/dist/{maintenance-e8wnwnqx.js → maintenance-hjhzdt3k.js} +1 -1
  124. package/dist/{mistral-conversations-1medfwv7.js → mistral-conversations-sd0xf1tn.js} +13 -13
  125. package/dist/{node-3v3f08f7.js → node-jzjj7r9x.js} +10 -9
  126. package/dist/{oauth-refresh-sweep-zm0yj4sj.js → oauth-refresh-sweep-y3n1knmd.js} +3 -3
  127. package/dist/{onboard-dtwcq603.js → onboard-1zq3m7db.js} +2 -2
  128. package/dist/{openai-codex-responses-de9pvs4d.js → openai-codex-responses-79s8xnbh.js} +29 -23
  129. package/dist/{openai-completions-mtqge3nf.js → openai-completions-tdbyts0x.js} +21 -20
  130. package/dist/{openai-decisions-ygk4kkxd.js → openai-decisions-bqhnjyfz.js} +2 -1
  131. package/dist/{openai-responses-dkyng71g.js → openai-responses-dm3t5vqp.js} +26 -17
  132. package/dist/{opencode-adapter-x4tqzhkh.js → opencode-adapter-e56086m1.js} +7 -5
  133. package/dist/{openrouter-images-wvv7camj.js → openrouter-images-ssttr1y4.js} +6 -3
  134. package/dist/{otel-impl-w3fbhpn0.js → otel-impl-h9yyza47.js} +2 -2
  135. package/dist/{pi-messages-nhh1pkz2.js → pi-messages-3nf8bq1a.js} +6 -4
  136. package/dist/{pi-mono-adapter-mcfkn9vf.js → pi-mono-adapter-tzps8f38.js} +9 -37
  137. package/dist/{pricing-refresh-j38dehfs.js → pricing-refresh-nevhat8h.js} +3 -3
  138. package/dist/{rbac-roles-ae8yttq4.js → rbac-roles-4y6ngvzq.js} +1 -1
  139. package/dist/{rbac-roles-ejxx9b35.js → rbac-roles-exbg8xb6.js} +2 -2
  140. package/dist/{registry-rxw10xzg.js → registry-ajhexe44.js} +1 -1
  141. package/dist/{render-v2-n16n0phe.js → render-v2-cfsh13z3.js} +3 -3
  142. package/dist/{resume-z0fgjc38.js → resume-5s2t0dbs.js} +10 -8
  143. package/dist/{runtime-xe3aqy4x.js → runtime-bt5pkpm1.js} +1 -1
  144. package/dist/{session-access-cv3mq6mc.js → session-access-dnbyp3cn.js} +6 -4
  145. package/dist/{setup-mafhj3yt.js → setup-1wj1y77j.js} +1 -1
  146. package/dist/{typesafe-system-one-2hgpayzy.js → typesafe-system-one-bre38hvs.js} +4 -2
  147. package/dist/{virtual-modules-4r3zkc20.js → virtual-modules-mnfbh5mr.js} +8 -2
  148. package/dist/{work-objects-8b86mgjp.js → work-objects-7mg6216g.js} +26 -24
  149. package/dist/worker-nwb789mg.js +79 -0
  150. package/dist/{x-y45mthwn.js → x-pbpezr0s.js} +2 -2
  151. package/openapi.json +793 -6
  152. package/package.json +11 -11
  153. package/src/apps/catalog.generated.json +206 -4
  154. package/src/apps/page-validator.ts +13 -1
  155. package/src/assets/key.ts +17 -0
  156. package/src/azure-devops/handlers.ts +1 -1
  157. package/src/be/chunking.ts +42 -9
  158. package/src/be/db/approvals.ts +18 -0
  159. package/src/be/db/session-moves.ts +164 -0
  160. package/src/be/db/session-tree.ts +384 -0
  161. package/src/be/db/tasks/draft-text.ts +15 -0
  162. package/src/be/db/tasks/read.ts +54 -5
  163. package/src/be/db/workflow-runs.ts +96 -0
  164. package/src/be/db.ts +85 -237
  165. package/src/be/memory/index-content.ts +36 -17
  166. package/src/be/memory/providers/openai-embedding.ts +115 -61
  167. package/src/be/memory/providers/sqlite-store.ts +69 -23
  168. package/src/be/memory/reranker.ts +31 -1
  169. package/src/be/memory/search-candidates.ts +47 -0
  170. package/src/be/memory/types.ts +14 -0
  171. package/src/be/onboarding.ts +3 -0
  172. package/src/be/scripts/typecheck.ts +2 -0
  173. package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +106 -54
  174. package/src/be/seed-scripts/index.ts +2 -2
  175. package/src/be/skill-sync.ts +43 -2
  176. package/src/be/swarm-config-guard.ts +25 -1
  177. package/src/be/task-attachment-store.ts +30 -0
  178. package/src/bitbucket/api.ts +81 -0
  179. package/src/bitbucket/auth.ts +64 -0
  180. package/src/bitbucket/handlers.ts +434 -0
  181. package/src/bitbucket/index.ts +28 -0
  182. package/src/bitbucket/templates.ts +134 -0
  183. package/src/bitbucket/types.ts +65 -0
  184. package/src/commands/runner.ts +55 -3
  185. package/src/e2b/env.ts +1 -0
  186. package/src/extensions/catalog.generated.json +4 -4
  187. package/src/extensions/contract-types.generated.ts +1 -1
  188. package/src/extensions/contract.ts +9 -2
  189. package/src/extensions/post-bridge.ts +1 -0
  190. package/src/fs/provider.ts +13 -1
  191. package/src/github/handlers.ts +6 -6
  192. package/src/gitlab/handlers.ts +5 -5
  193. package/src/heartbeat/heartbeat.ts +31 -11
  194. package/src/http/assets.ts +14 -2
  195. package/src/http/core.ts +12 -0
  196. package/src/http/fs.ts +54 -34
  197. package/src/http/index.ts +4 -0
  198. package/src/http/mcp-body.ts +37 -0
  199. package/src/http/mcp-user.ts +3 -5
  200. package/src/http/mcp.ts +6 -7
  201. package/src/http/memory.ts +26 -15
  202. package/src/http/onboarding.ts +1 -0
  203. package/src/http/page-proxy.ts +61 -5
  204. package/src/http/pages-public.ts +27 -44
  205. package/src/http/pages.ts +4 -4
  206. package/src/http/script-runs.ts +2 -0
  207. package/src/http/sessions.ts +366 -12
  208. package/src/http/skills.ts +48 -1
  209. package/src/http/tasks.ts +1 -1
  210. package/src/http/users.ts +13 -1
  211. package/src/http/webhooks.ts +141 -0
  212. package/src/integrations/chat-sdk/adapters.ts +24 -4
  213. package/src/integrations/chat-sdk/attachments.ts +90 -12
  214. package/src/integrations/chat-sdk/facts.ts +138 -0
  215. package/src/integrations/chat-sdk/inbound.ts +156 -8
  216. package/src/integrations/chat-sdk/limits.ts +24 -0
  217. package/src/integrations/chat-sdk/outbound-files.ts +186 -0
  218. package/src/integrations/chat-sdk/outbound.ts +272 -5
  219. package/src/integrations/chat-sdk/relay.ts +4 -1
  220. package/src/integrations/chat-sdk/router.ts +28 -2
  221. package/src/integrations/chat-sdk/steering.ts +9 -0
  222. package/src/integrations/chat-sdk/templates.ts +14 -0
  223. package/src/integrations/chat-sdk/thread-buffer.ts +4 -0
  224. package/src/mcp-limits.ts +20 -0
  225. package/src/pages/browser-sdk.ts +8 -3
  226. package/src/pages/feedback-overlay.ts +1 -1
  227. package/src/providers/acp-targets.ts +1 -1
  228. package/src/providers/amp-adapter.ts +1 -1
  229. package/src/providers/codex-adapter.ts +106 -119
  230. package/src/providers/codex-agents-md.ts +35 -229
  231. package/src/providers/cursor-adapter.ts +1 -1
  232. package/src/providers/pi-mono-adapter.ts +5 -53
  233. package/src/realtime/transport.ts +1 -2
  234. package/src/script-workflows/workflow-ctx.ts +1 -0
  235. package/src/scripts-runtime/types/stdlib.d.ts +2 -0
  236. package/src/scripts-runtime/types/swarm-extension.d.ts +14 -4
  237. package/src/scripts-runtime/types/swarm-sdk.d.ts +2 -0
  238. package/src/server-user.ts +2 -0
  239. package/src/server.ts +2 -0
  240. package/src/slack/assistant.ts +2 -2
  241. package/src/slack/files.ts +2 -0
  242. package/src/slack/handlers.test.ts +158 -0
  243. package/src/slack/handlers.ts +23 -32
  244. package/src/slack/inbound-files.ts +39 -6
  245. package/src/slack/thread-buffer.ts +2 -1
  246. package/src/tasks/context-key.ts +61 -1
  247. package/src/tasks/task-terminal-effects.ts +21 -17
  248. package/src/tests/apps-card-grid.test.ts +197 -0
  249. package/src/tests/apps-catalog-additions.test.ts +120 -0
  250. package/src/tests/apps-tree.test.ts +129 -0
  251. package/src/tests/bitbucket-auth.test.ts +156 -0
  252. package/src/tests/bitbucket-handlers.test.ts +425 -0
  253. package/src/tests/bitbucket-webhook.test.ts +220 -0
  254. package/src/tests/chat-sdk-attachments-reply.test.ts +52 -0
  255. package/src/tests/chat-sdk-config.test.ts +11 -0
  256. package/src/tests/chat-sdk-facts.test.ts +23 -0
  257. package/src/tests/chat-sdk-inbound.test.ts +597 -4
  258. package/src/tests/chat-sdk-outbound-files.test.ts +652 -0
  259. package/src/tests/chat-sdk-telegram-markdown.test.ts +92 -0
  260. package/src/tests/codex-adapter.test.ts +167 -242
  261. package/src/tests/config-api-only-keys.test.ts +9 -0
  262. package/src/tests/context-key.test.ts +50 -0
  263. package/src/tests/core-auth.test.ts +5 -0
  264. package/src/tests/cors-allowlist.test.ts +1 -1
  265. package/src/tests/db-task-read-contract.test.ts +7 -0
  266. package/src/tests/db-task-write-contract.test.ts +6 -0
  267. package/src/tests/docs-index-footprint.test.js +3 -3
  268. package/src/tests/extensions-example-slack-reply-gate.test.ts +262 -91
  269. package/src/tests/fixtures/bitbucket/pullrequest-comment-created-inline.json +174 -0
  270. package/src/tests/fixtures/bitbucket/pullrequest-comment-created.json +170 -0
  271. package/src/tests/fixtures/bitbucket/pullrequest-created-mention.json +132 -0
  272. package/src/tests/fixtures/bitbucket/pullrequest-created-reviewer.json +140 -0
  273. package/src/tests/fixtures/bitbucket/pullrequest-fulfilled.json +134 -0
  274. package/src/tests/fixtures/bitbucket/pullrequest-rejected.json +133 -0
  275. package/src/tests/generate-mcp-docs.test.ts +101 -0
  276. package/src/tests/gitlab-handlers.test.ts +3 -3
  277. package/src/tests/gitlab-vcs-db.test.ts +4 -4
  278. package/src/tests/heartbeat-checklist.test.ts +33 -0
  279. package/src/tests/heartbeat-jev-gate-credentials.test.ts +44 -0
  280. package/src/tests/heartbeat-jev-gate.test.ts +242 -1
  281. package/src/tests/integration-disable-flags.test.ts +36 -0
  282. package/src/tests/kv-page-proxy.test.ts +91 -5
  283. package/src/tests/launch-password-rejection.test.ts +2 -2
  284. package/src/tests/mcp-input-caps.test.ts +294 -0
  285. package/src/tests/mcp-tools.test.ts +1 -1
  286. package/src/tests/memory-chunked-search-starvation.test.ts +200 -0
  287. package/src/tests/memory-embedding-input-cap.test.ts +114 -0
  288. package/src/tests/memory.test.ts +14 -0
  289. package/src/tests/onboarding.test.ts +12 -0
  290. package/src/tests/page-guest-session.test.ts +19 -8
  291. package/src/tests/page-proxy-authed.test.ts +1 -1
  292. package/src/tests/page-proxy.test.ts +17 -10
  293. package/src/tests/page-session.test.ts +102 -1
  294. package/src/tests/pages-authed-mode.test.ts +82 -12
  295. package/src/tests/pages-password-mode.test.ts +5 -5
  296. package/src/tests/pi-codemode.test.ts +8 -2
  297. package/src/tests/preload-env.test.ts +57 -0
  298. package/src/tests/preload.ts +21 -0
  299. package/src/tests/prompt-template-remaining.test.ts +38 -0
  300. package/src/tests/realtime-transport.test.ts +13 -7
  301. package/src/tests/run-root-coverage.test.ts +39 -0
  302. package/src/tests/runner-bitbucket-vcs.test.ts +191 -0
  303. package/src/tests/runner-skills-refresh.test.ts +58 -0
  304. package/src/tests/script-runs-http.test.ts +84 -0
  305. package/src/tests/scripts-http.test.ts +10 -1
  306. package/src/tests/secret-scrubber.test.ts +15 -0
  307. package/src/tests/sessions-move.test.ts +576 -0
  308. package/src/tests/sessions-tree.test.ts +493 -0
  309. package/src/tests/skill-files-http.test.ts +50 -0
  310. package/src/tests/skills-bundle.test.ts +99 -0
  311. package/src/tests/swarm-config-export-to-env.test.ts +23 -3
  312. package/src/tests/task-audio-transcription.test.ts +299 -0
  313. package/src/tests/task-completion-memory-chunking.test.ts +434 -0
  314. package/src/tests/task-terminal-secret-scrub.test.ts +6 -1
  315. package/src/tests/transcription.test.ts +299 -0
  316. package/src/tests/vcs-bitbucket.test.ts +91 -0
  317. package/src/tests/vcs-provider-isolation.test.ts +123 -0
  318. package/src/tests/vcs-provider.test.ts +16 -1
  319. package/src/tests/vcs-tracking.test.ts +3 -3
  320. package/src/tests/workflow-foreach-bodies.test.ts +637 -0
  321. package/src/tests/workflow-foreach-concurrency.test.ts +342 -0
  322. package/src/tests/workflow-foreach-retry.test.ts +537 -0
  323. package/src/tests/workflow-foreach.test.ts +44 -23
  324. package/src/tests/workflow-step-claims.test.ts +6 -6
  325. package/src/tests/workflow-system-one-decision-providers.test.ts +98 -1
  326. package/src/tests/workflow-system-one-decision.test.ts +2 -1
  327. package/src/tests/workflow-tla-races.test.ts +2 -2
  328. package/src/tools/chat-access.ts +10 -0
  329. package/src/tools/chat-post.ts +31 -11
  330. package/src/tools/chat-reply.ts +32 -10
  331. package/src/tools/memory-search.ts +3 -1
  332. package/src/tools/script-delete.ts +1 -1
  333. package/src/tools/system-one-decide.ts +17 -4
  334. package/src/tools/workflows/create-workflow.ts +5 -3
  335. package/src/types.ts +9 -2
  336. package/src/utils/config-env-export.ts +4 -1
  337. package/src/utils/page-session.ts +133 -20
  338. package/src/utils/secret-scrubber.ts +4 -0
  339. package/src/utils/skills-refresh.ts +83 -43
  340. package/src/utils/transcription.ts +246 -0
  341. package/src/vcs/bitbucket.ts +58 -0
  342. package/src/vcs/index.ts +2 -0
  343. package/src/vcs/types.ts +2 -2
  344. package/src/workflows/approval-resolution.ts +4 -1
  345. package/src/workflows/checkpoint.ts +9 -1
  346. package/src/workflows/constants.ts +3 -0
  347. package/src/workflows/definition.ts +48 -17
  348. package/src/workflows/engine.ts +25 -15
  349. package/src/workflows/executors/foreach.ts +45 -99
  350. package/src/workflows/executors/system-one-decision.ts +28 -7
  351. package/src/workflows/executors/system-one-providers.ts +39 -4
  352. package/src/workflows/foreach-body.ts +146 -0
  353. package/src/workflows/foreach-driver.ts +552 -0
  354. package/src/workflows/foreach-join.ts +206 -7
  355. package/src/workflows/node-timeout.ts +21 -0
  356. package/src/workflows/recovery.ts +68 -3
  357. package/src/workflows/resume.ts +221 -43
  358. package/src/workflows/retry-poller.ts +39 -24
  359. package/src/workflows/step-input.ts +59 -0
  360. package/src/workflows/task-step-routing.ts +114 -56
  361. package/templates/extensions/slack-reply-gate/README.md +24 -22
  362. package/templates/extensions/slack-reply-gate/hooks.ts +146 -95
  363. package/templates/extensions/slack-reply-gate/manifest.json +2 -2
  364. package/templates/skills/apps/SKILL.md +28 -6
  365. package/templates/skills/apps/content.md +28 -6
  366. package/templates/skills/chat-sdk/content.md +6 -2
  367. package/templates/skills/code-quality/content.md +2 -1
  368. package/templates/skills/heartbeat-runbook/content.md +5 -5
  369. package/templates/skills/pages/content.md +6 -4
  370. package/templates/skills/slack-interaction/content.md +5 -0
  371. package/templates/skills/swarm-scripts/SKILL.md +2 -2
  372. package/templates/skills/swarm-scripts/content.md +2 -2
  373. package/templates/skills/work-on-task/SKILL.md +1 -1
  374. package/templates/skills/work-on-task/content.md +1 -1
  375. package/dist/cli-51y09c6p.js +0 -2657
  376. package/dist/cli-yw91rnmw.js +0 -13
  377. package/dist/handlers-0pvee9nw.js +0 -39
  378. package/dist/index-gdh8jrdm.js +0 -43
  379. package/dist/index-hg0x2cdv.js +0 -40
  380. package/dist/index-thm0y6a8.js +0 -41
  381. package/dist/lead-ee8vazz2.js +0 -77
  382. package/dist/worker-16s75hm5.js +0 -77
@@ -75,9 +75,11 @@ async function createPage(
75
75
  async function launchPage(pageId: string, agentId: string): Promise<string> {
76
76
  const response = await api("POST", `/api/pages/${pageId}/launch`, { agentId });
77
77
  expect(response.status).toBe(204);
78
- const cookie = response.headers.get("set-cookie")?.match(/page_session=([^;]+)/)?.[1];
78
+ const cookie = response.headers
79
+ .get("set-cookie")
80
+ ?.match(/page_session_[A-Za-z0-9_-]+=([^;]+)/)?.[1];
79
81
  expect(cookie).toBeTruthy();
80
- return `page_session=${cookie}`;
82
+ return `page_session_${pageId}=${cookie}`;
81
83
  }
82
84
 
83
85
  async function createUserToken(name: string): Promise<{ id: string; token: string }> {
@@ -107,17 +109,21 @@ async function launchPageAsUser(pageId: string, token: string): Promise<string>
107
109
  headers: { Authorization: `Bearer ${token}` },
108
110
  });
109
111
  expect(response.status).toBe(204);
110
- const cookie = response.headers.get("set-cookie")?.match(/page_session=([^;]+)/)?.[1];
112
+ const cookie = response.headers
113
+ .get("set-cookie")
114
+ ?.match(/page_session_[A-Za-z0-9_-]+=([^;]+)/)?.[1];
111
115
  expect(cookie).toBeTruthy();
112
- return `page_session=${cookie}`;
116
+ return `page_session_${pageId}=${cookie}`;
113
117
  }
114
118
 
115
119
  async function unlockPasswordPage(pageId: string, password: string): Promise<string> {
116
120
  const response = await fetch(`${baseUrl}/p/${pageId}?key=${encodeURIComponent(password)}`);
117
121
  expect(response.status).toBe(200);
118
- const cookie = response.headers.get("set-cookie")?.match(/page_session=([^;]+)/)?.[1];
122
+ const cookie = response.headers
123
+ .get("set-cookie")
124
+ ?.match(/page_session_[A-Za-z0-9_-]+=([^;]+)/)?.[1];
119
125
  expect(cookie).toBeTruthy();
120
- return `page_session=${cookie}`;
126
+ return `page_session_${pageId}=${cookie}`;
121
127
  }
122
128
 
123
129
  function openSocket(
@@ -443,7 +449,7 @@ describe("realtime transport", () => {
443
449
  const passwordPage = await createPage(agentId, "password", "secret");
444
450
  expect(await socketAccepted({ Origin: baseUrl }, pageSocketUrl(passwordPage))).toBe(false);
445
451
  const passwordCookie = await unlockPasswordPage(passwordPage, "secret");
446
- expect(passwordCookie).toContain("page_session=");
452
+ expect(passwordCookie).toContain("page_session_");
447
453
  });
448
454
 
449
455
  test("persists room state across an API restart and cascades page deletion", async () => {
@@ -0,0 +1,39 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { mergeLcov } from "../../scripts/run-root-coverage";
3
+
4
+ describe("mergeLcov", () => {
5
+ test("disjoint function hits remain a conservative lower bound without FN/FNDA identities", () => {
6
+ // a.test.ts calls a(); b.test.ts calls b() in the same two-function module.
7
+ const report = "SF:src/example.ts\nFNF:2\nFNH:1\nend_of_record\n";
8
+ const merged = mergeLcov([report, report]);
9
+
10
+ expect(merged).toContain("FNF:2\nFNH:1\n");
11
+ });
12
+
13
+ test("DA hits sum across reports and LH counts lines hit in any report", () => {
14
+ const merged = mergeLcov([
15
+ "SF:src/example.ts\nDA:1,2\nDA:2,0\nDA:3,0\nend_of_record\n",
16
+ "SF:src/example.ts\nDA:1,3\nDA:2,1\nDA:3,0\nend_of_record\n",
17
+ ]);
18
+
19
+ expect(merged).toBe(
20
+ "TN:\nSF:src/example.ts\nFNF:0\nFNH:0\nDA:1,5\nDA:2,1\nDA:3,0\nLF:3\nLH:2\nend_of_record\n",
21
+ );
22
+ });
23
+
24
+ test("FNH never exceeds FNF when merging function totals", () => {
25
+ const reports = [
26
+ "SF:src/example.ts\nFNF:2\nFNH:2\nend_of_record\n",
27
+ "SF:src/example.ts\nFNF:3\nFNH:1\nend_of_record\n",
28
+ ];
29
+ for (const inputs of [reports, reports.toReversed(), [...reports, ...reports]]) {
30
+ const merged = mergeLcov(inputs);
31
+ const found = Number(merged.match(/^FNF:(\d+)$/m)![1]);
32
+ const hit = Number(merged.match(/^FNH:(\d+)$/m)![1]);
33
+
34
+ expect(found).toBe(3);
35
+ expect(hit).toBe(2);
36
+ expect(hit).toBeLessThanOrEqual(found);
37
+ }
38
+ });
39
+ });
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Worker-side Bitbucket PR linking. There is no Bitbucket CLI in the worker
3
+ * image, so `detectVcsForTask` asks the Bitbucket REST API (via `fetch`) for an
4
+ * open PR on the current branch and PATCHes `/api/tasks/:id/vcs` when it finds
5
+ * one. Both calls go through `globalThis.fetch`, which these tests stub, so no
6
+ * server or port is involved. The git side runs against a real temp repo.
7
+ */
8
+ import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test";
9
+ import { mkdtemp, rm } from "node:fs/promises";
10
+ import { tmpdir } from "node:os";
11
+ import { join } from "node:path";
12
+ import { detectVcsForTask, findBitbucketOpenPullRequest } from "../commands/runner";
13
+ import { expectChildOk, runChild } from "./test-proc";
14
+
15
+ const API_URL = "http://swarm.invalid";
16
+ const BRANCH = "feature/bb-link";
17
+ const PR_HTML = "https://bitbucket.org/acme-ws/web.app/pull-requests/17";
18
+
19
+ type Call = { url: string; method: string; headers: Headers; body: string };
20
+ type BitbucketReply = { status: number; body: unknown } | "throw";
21
+
22
+ const originalFetch = globalThis.fetch;
23
+ const originalToken = process.env.BITBUCKET_TOKEN;
24
+ let calls: Call[] = [];
25
+ let bitbucketReply: BitbucketReply = { status: 200, body: { values: [] } };
26
+ let repoDir = "";
27
+ let taskSeq = 0;
28
+
29
+ function nextTaskId(): string {
30
+ taskSeq += 1;
31
+ return `00000000-0000-4000-8000-${String(taskSeq).padStart(12, "0")}`;
32
+ }
33
+
34
+ async function git(args: string[]): Promise<void> {
35
+ expectChildOk(await runChild(["git", "-C", repoDir, ...args]), `git ${args.join(" ")}`);
36
+ }
37
+
38
+ function stubFetch(): void {
39
+ globalThis.fetch = (async (input: string | URL | Request, init?: RequestInit) => {
40
+ const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
41
+ calls.push({
42
+ url,
43
+ method: init?.method ?? "GET",
44
+ headers: new Headers(init?.headers),
45
+ body: typeof init?.body === "string" ? init.body : "",
46
+ });
47
+ if (url.startsWith("https://api.bitbucket.org/")) {
48
+ if (bitbucketReply === "throw") throw new TypeError("network down");
49
+ return new Response(JSON.stringify(bitbucketReply.body), {
50
+ status: bitbucketReply.status,
51
+ headers: { "Content-Type": "application/json" },
52
+ });
53
+ }
54
+ return new Response("{}", { status: 200, headers: { "Content-Type": "application/json" } });
55
+ }) as typeof fetch;
56
+ }
57
+
58
+ const bitbucketCalls = () => calls.filter((c) => c.url.startsWith("https://api.bitbucket.org/"));
59
+ const patchCalls = () => calls.filter((c) => c.method === "PATCH");
60
+
61
+ beforeAll(async () => {
62
+ repoDir = await mkdtemp(join(tmpdir(), "runner-bb-vcs-"));
63
+ await git(["init", "--quiet"]);
64
+ await git(["checkout", "--quiet", "-b", BRANCH]);
65
+ await git(["remote", "add", "origin", "https://bitbucket.org/acme-ws/web.app.git"]);
66
+ });
67
+
68
+ afterAll(async () => {
69
+ await rm(repoDir, { recursive: true, force: true });
70
+ });
71
+
72
+ beforeEach(() => {
73
+ calls = [];
74
+ bitbucketReply = { status: 200, body: { values: [] } };
75
+ process.env.BITBUCKET_TOKEN = "example-bb-token";
76
+ stubFetch();
77
+ });
78
+
79
+ afterEach(() => {
80
+ globalThis.fetch = originalFetch;
81
+ if (originalToken === undefined) delete process.env.BITBUCKET_TOKEN;
82
+ else process.env.BITBUCKET_TOKEN = originalToken;
83
+ });
84
+
85
+ describe("detectVcsForTask on a Bitbucket remote", () => {
86
+ test("an open PR for the branch links the task", async () => {
87
+ bitbucketReply = {
88
+ status: 200,
89
+ body: { values: [{ id: 17, links: { html: { href: PR_HTML } } }] },
90
+ };
91
+ const taskId = nextTaskId();
92
+ await detectVcsForTask(API_URL, "swarm-key", taskId, repoDir);
93
+
94
+ const [lookup] = bitbucketCalls();
95
+ expect(lookup).toBeDefined();
96
+ const lookupUrl = new URL(lookup!.url);
97
+ expect(lookupUrl.pathname).toBe("/2.0/repositories/acme-ws/web.app/pullrequests");
98
+ expect(lookupUrl.searchParams.get("q")).toBe(`source.branch.name="${BRANCH}" AND state="OPEN"`);
99
+ expect(lookupUrl.searchParams.get("pagelen")).toBe("1");
100
+ expect(lookup!.headers.get("authorization")).toBe("Bearer example-bb-token");
101
+
102
+ const patches = patchCalls();
103
+ expect(patches).toHaveLength(1);
104
+ expect(patches[0]!.url).toBe(`${API_URL}/api/tasks/${taskId}/vcs`);
105
+ expect(patches[0]!.headers.get("authorization")).toBe("Bearer swarm-key");
106
+ expect(JSON.parse(patches[0]!.body)).toEqual({
107
+ vcsProvider: "bitbucket",
108
+ vcsRepo: "acme-ws/web.app",
109
+ vcsNumber: 17,
110
+ vcsUrl: PR_HTML,
111
+ });
112
+ });
113
+
114
+ test("no open PR sends no PATCH", async () => {
115
+ await detectVcsForTask(API_URL, "swarm-key", nextTaskId(), repoDir);
116
+ expect(bitbucketCalls()).toHaveLength(1);
117
+ expect(patchCalls()).toHaveLength(0);
118
+ });
119
+
120
+ test("a non-200 answer is swallowed and sends no PATCH", async () => {
121
+ bitbucketReply = { status: 401, body: { type: "error", error: { message: "Unauthorized" } } };
122
+ await expect(
123
+ detectVcsForTask(API_URL, "swarm-key", nextTaskId(), repoDir),
124
+ ).resolves.toBeUndefined();
125
+ expect(bitbucketCalls()).toHaveLength(1);
126
+ expect(patchCalls()).toHaveLength(0);
127
+ });
128
+
129
+ test("a network failure is swallowed and sends no PATCH", async () => {
130
+ bitbucketReply = "throw";
131
+ await expect(
132
+ detectVcsForTask(API_URL, "swarm-key", nextTaskId(), repoDir),
133
+ ).resolves.toBeUndefined();
134
+ expect(patchCalls()).toHaveLength(0);
135
+ });
136
+
137
+ test("without BITBUCKET_TOKEN it calls nothing", async () => {
138
+ delete process.env.BITBUCKET_TOKEN;
139
+ await detectVcsForTask(API_URL, "swarm-key", nextTaskId(), repoDir);
140
+ expect(calls).toHaveLength(0);
141
+ });
142
+ });
143
+
144
+ describe("findBitbucketOpenPullRequest", () => {
145
+ test("escapes quotes in the branch name inside the BBQL query", async () => {
146
+ let seen = "";
147
+ const fetchImpl = (async (input: string | URL | Request) => {
148
+ seen = String(input);
149
+ return new Response(JSON.stringify({ values: [] }), { status: 200 });
150
+ }) as typeof fetch;
151
+ await findBitbucketOpenPullRequest("git@bitbucket.org:ws/repo.git", 'we"ird', {
152
+ token: "t",
153
+ fetchImpl,
154
+ });
155
+ expect(new URL(seen).searchParams.get("q")).toBe(
156
+ 'source.branch.name="we\\"ird" AND state="OPEN"',
157
+ );
158
+ });
159
+
160
+ test("rejects a reply without a numeric id or an html link", async () => {
161
+ for (const values of [
162
+ [{ id: "17", links: { html: { href: PR_HTML } } }],
163
+ [{ id: 17, links: {} }],
164
+ [{}],
165
+ ]) {
166
+ const fetchImpl = (async () =>
167
+ new Response(JSON.stringify({ values }), { status: 200 })) as unknown as typeof fetch;
168
+ expect(
169
+ await findBitbucketOpenPullRequest("https://bitbucket.org/ws/repo", "b", {
170
+ token: "t",
171
+ fetchImpl,
172
+ }),
173
+ ).toBeNull();
174
+ }
175
+ });
176
+
177
+ test("returns null for a non-Bitbucket remote without calling fetch", async () => {
178
+ let called = false;
179
+ const fetchImpl = (async () => {
180
+ called = true;
181
+ return new Response("{}");
182
+ }) as unknown as typeof fetch;
183
+ expect(
184
+ await findBitbucketOpenPullRequest("https://github.com/ws/repo", "b", {
185
+ token: "t",
186
+ fetchImpl,
187
+ }),
188
+ ).toBeNull();
189
+ expect(called).toBe(false);
190
+ });
191
+ });
@@ -34,6 +34,9 @@ type StubState = {
34
34
  };
35
35
  calls: { signature: number; list: number; sync: number };
36
36
  skillFilesStatus: number;
37
+ /** 404 by default so the legacy list path stays covered; bundle tests opt in. */
38
+ bundleStatus: number;
39
+ bundleCalls: number;
37
40
  };
38
41
 
39
42
  const FAKE_HOME = join(tmpdir(), `runner-refresh-test-${process.pid}`);
@@ -67,6 +70,8 @@ const state: StubState = {
67
70
  signature: "hash-v1",
68
71
  },
69
72
  calls: { signature: 0, list: 0, sync: 0 },
73
+ bundleStatus: 404,
74
+ bundleCalls: 0,
70
75
  };
71
76
 
72
77
  let server: ReturnType<typeof Bun.serve> | null = null;
@@ -91,6 +96,22 @@ describe("refreshSkillsIfChanged", () => {
91
96
  generatedAt: new Date().toISOString(),
92
97
  });
93
98
  }
99
+ if (url.pathname.endsWith("/skills/bundle")) {
100
+ state.bundleCalls++;
101
+ if (state.bundleStatus !== 200) {
102
+ return new Response("not found", { status: state.bundleStatus });
103
+ }
104
+ return Response.json({
105
+ skills: state.skillsBody.skills.map((skill) => ({
106
+ ...skill,
107
+ files: skill.isComplex
108
+ ? [{ path: "refs/notes.md", content: "bundled note", isBinary: false }]
109
+ : [],
110
+ })),
111
+ total: state.skillsBody.skills.length,
112
+ signature: state.skillsBody.signature,
113
+ });
114
+ }
94
115
  if (url.pathname.match(/\/api\/agents\/[^/]+\/skills$/)) {
95
116
  state.calls.list++;
96
117
  if (state.listStatus !== 200) {
@@ -337,6 +358,43 @@ describe("refreshSkillsIfChanged", () => {
337
358
  rmSync(cleanHome, { recursive: true, force: true });
338
359
  });
339
360
 
361
+ test("bundle endpoint replaces the list call and the per-file fan-out", async () => {
362
+ state.calls = { signature: 0, list: 0, sync: 0 };
363
+ state.bundleCalls = 0;
364
+ state.bundleStatus = 200;
365
+ state.signatureHash = "hash-bundle";
366
+ state.skillsBody.signature = "hash-bundle";
367
+ state.skillsBody.skills = [
368
+ {
369
+ id: "skill-complex",
370
+ name: "complex-skill",
371
+ description: "has bundled files",
372
+ content: "---\nname: complex-skill\n---\n\nComplex body.",
373
+ isComplex: true,
374
+ isActive: true,
375
+ isEnabled: true,
376
+ },
377
+ ];
378
+
379
+ const home = join(FAKE_HOME, "bundle-home");
380
+ mkdirSync(home, { recursive: true });
381
+ const lastHash = { current: null as string | null };
382
+ const result = await refreshWithHome(makeCtx(), lastHash, home);
383
+
384
+ expect(result.changed).toBe(true);
385
+ expect(lastHash.current).toBe("hash-bundle");
386
+ // One signature probe + one bundle call. No list call, no manifest and no
387
+ // per-file fetches.
388
+ expect(state.bundleCalls).toBe(1);
389
+ expect(state.calls).toEqual({ signature: 1, list: 0, sync: 0 });
390
+ expect(
391
+ readFileSync(join(home, ".claude", "skills", "complex-skill", "refs", "notes.md"), "utf-8"),
392
+ ).toBe("bundled note");
393
+
394
+ state.bundleStatus = 404;
395
+ rmSync(home, { recursive: true, force: true });
396
+ });
397
+
340
398
  test("list fetch failure after signature drift leaves pre-existing skills on disk and does not advance hash", async () => {
341
399
  // Arrange: fresh isolated home with a pre-existing swarm-managed skill
342
400
  const isolatedHome = join(FAKE_HOME, "list-fail-home");
@@ -11,6 +11,7 @@ import {
11
11
  getDbClient,
12
12
  getLatestScriptRunStepTaskByContextKey,
13
13
  getLatestTaskByContextKey,
14
+ getPendingTaskForAgent,
14
15
  initDb,
15
16
  updateScriptRun,
16
17
  } from "../be/db";
@@ -20,6 +21,7 @@ import { handleScriptRuns } from "../http/script-runs";
20
21
  import { getPathSegments, parseQueryParams } from "../http/utils";
21
22
  import { localProcessScriptExecutor } from "../script-workflows/executor";
22
23
  import { setScriptRunExecutor } from "../script-workflows/supervisor";
24
+ import type { AgentTask } from "../types";
23
25
  import {
24
26
  clearVolatileSecretsForTesting,
25
27
  refreshSecretScrubberCache,
@@ -537,6 +539,88 @@ describe("/api/script-runs HTTP", () => {
537
539
  });
538
540
  });
539
541
 
542
+ async function dispatchStepFor(
543
+ stepKey: string,
544
+ stepAgentId: string,
545
+ extra: Record<string, unknown> = {},
546
+ ): Promise<{ response: Promise<TestResponse>; task: AgentTask }> {
547
+ const created = await dispatch("/api/script-runs", {
548
+ method: "POST",
549
+ agentId,
550
+ body: createBody(),
551
+ });
552
+ const { id: runId } = (await created.json()) as { id: string };
553
+ const response = dispatch(`/api/internal/script-runs/${runId}/agent-task`, {
554
+ method: "POST",
555
+ agentId,
556
+ body: JSON.stringify({ stepKey, task: `do ${stepKey}`, agentId: stepAgentId, ...extra }),
557
+ });
558
+ const contextKey = `script-run:${runId}:${stepKey}`;
559
+ let task = await getLatestScriptRunStepTaskByContextKey(contextKey);
560
+ for (let i = 0; i < 50 && !task; i++) {
561
+ await Bun.sleep(20);
562
+ task = await getLatestScriptRunStepTaskByContextKey(contextKey);
563
+ }
564
+ expect(task).not.toBeNull();
565
+ return { response, task: task! };
566
+ }
567
+
568
+ test("agent-task dispatch persists dependsOn and holds the task until the dependency completes", async () => {
569
+ const stepAgent = await createAgent({
570
+ name: "deps-step-worker",
571
+ isLead: false,
572
+ status: "idle",
573
+ });
574
+ const dependency = await createTaskExtended("upstream fix", { source: "mcp" });
575
+
576
+ const { response, task } = await dispatchStepFor("after-fix", stepAgent.id, {
577
+ dependsOn: [dependency.id],
578
+ });
579
+
580
+ expect(task.dependsOn).toEqual([dependency.id]);
581
+ expect(await getPendingTaskForAgent(stepAgent.id)).toBeNull();
582
+
583
+ await completeTask(dependency.id, "fixed");
584
+ expect((await getPendingTaskForAgent(stepAgent.id))?.id).toBe(task.id);
585
+
586
+ await completeTask(task.id, "done");
587
+ expect((await response).status).toBe(200);
588
+ });
589
+
590
+ test("agent-task dispatch without dependsOn is startable at once", async () => {
591
+ const stepAgent = await createAgent({
592
+ name: "no-deps-step-worker",
593
+ isLead: false,
594
+ status: "idle",
595
+ });
596
+
597
+ const { response, task } = await dispatchStepFor("no-deps", stepAgent.id);
598
+
599
+ expect(task.dependsOn).toEqual([]);
600
+ expect((await getPendingTaskForAgent(stepAgent.id))?.id).toBe(task.id);
601
+
602
+ await completeTask(task.id, "done");
603
+ expect((await response).status).toBe(200);
604
+ });
605
+
606
+ test("agent-task dispatch rejects a dependsOn entry that is not a task UUID", async () => {
607
+ const created = await dispatch("/api/script-runs", {
608
+ method: "POST",
609
+ agentId,
610
+ body: createBody(),
611
+ });
612
+ const { id: runId } = (await created.json()) as { id: string };
613
+
614
+ const response = await dispatch(`/api/internal/script-runs/${runId}/agent-task`, {
615
+ method: "POST",
616
+ agentId,
617
+ body: JSON.stringify({ stepKey: "bad-deps", task: "x", dependsOn: ["not-a-uuid"] }),
618
+ });
619
+
620
+ expect(response.status).toBe(400);
621
+ expect(await getLatestScriptRunStepTaskByContextKey(`script-run:${runId}:bad-deps`)).toBeNull();
622
+ });
623
+
540
624
  test("agent-task polling returns the step output when a newer completed follow-up shares its context key", async () => {
541
625
  const created = await dispatch("/api/script-runs", {
542
626
  method: "POST",
@@ -785,6 +785,7 @@ describe("ctx.swarm.config.get through POST /api/scripts/run", () => {
785
785
  missingIsUndefined: ctx.swarm.config.get("SCRIPT_CFG_NOT_SET") === undefined,
786
786
  fallback: read("SCRIPT_CFG_NOT_SET") ?? "fallback",
787
787
  apiOnly: read("SLACK_SIGNING_SECRET"),
788
+ transcriptionKey: read("TRANSCRIPTION_API_KEY"),
788
789
  proto: read("__proto__"),
789
790
  };
790
791
  };`;
@@ -798,6 +799,7 @@ describe("ctx.swarm.config.get through POST /api/scripts/run", () => {
798
799
  missingIsUndefined: boolean;
799
800
  fallback: string;
800
801
  apiOnly: string | null;
802
+ transcriptionKey: string | null;
801
803
  proto: string | null;
802
804
  };
803
805
 
@@ -829,11 +831,17 @@ describe("ctx.swarm.config.get through POST /api/scripts/run", () => {
829
831
  value: `signing-${randomToken(24)}`,
830
832
  isSecret: true,
831
833
  });
834
+ await upsertSwarmConfig({
835
+ scope: "global",
836
+ key: "TRANSCRIPTION_API_KEY",
837
+ value: `stt-${randomToken(24)}`,
838
+ isSecret: true,
839
+ });
832
840
  });
833
841
 
834
842
  afterAll(async () => {
835
843
  await getDbClient().run(
836
- "DELETE FROM swarm_config WHERE key LIKE 'SCRIPT_CFG_%' OR key IN ('SLACK_SIGNING_SECRET', '__proto__')",
844
+ "DELETE FROM swarm_config WHERE key LIKE 'SCRIPT_CFG_%' OR key IN ('SLACK_SIGNING_SECRET', 'TRANSCRIPTION_API_KEY', '__proto__')",
837
845
  );
838
846
  clearVolatileSecretsForTesting();
839
847
  });
@@ -862,6 +870,7 @@ describe("ctx.swarm.config.get through POST /api/scripts/run", () => {
862
870
  missingIsUndefined: true,
863
871
  fallback: "fallback",
864
872
  apiOnly: null,
873
+ transcriptionKey: null,
865
874
  proto: "proto-global",
866
875
  });
867
876
  // The unwrapped secret is scrubbed from everything the run returns.
@@ -74,6 +74,21 @@ describe("scrubSecrets — env-based replacement", () => {
74
74
  expect(out).toBe("Authorization: Bearer [REDACTED:GITHUB_TOKEN] end");
75
75
  });
76
76
 
77
+ test("redacts the Bitbucket token and webhook secret as exact keys", () => {
78
+ const values = {
79
+ BITBUCKET_TOKEN: `example-bitbucket-${randomToken(24)}`,
80
+ BITBUCKET_WEBHOOK_SECRET: `example-bbhook-${randomToken(24)}`,
81
+ };
82
+ for (const [name, value] of Object.entries(values)) process.env[name] = value;
83
+ refreshSecretScrubberCache();
84
+ for (const [name, value] of Object.entries(values)) {
85
+ expect(isSensitiveKey(name)).toBe(true);
86
+ expect(scrubSecrets(`Authorization: Bearer ${value} end`)).toBe(
87
+ `Authorization: Bearer [REDACTED:${name}] end`,
88
+ );
89
+ }
90
+ });
91
+
77
92
  test("redacts any key with _API_KEY suffix", () => {
78
93
  process.env.FOO_SERVICE_API_KEY = "example-supersecretFooApiKey_longerthan12chars";
79
94
  refreshSecretScrubberCache();