@desplega.ai/agent-swarm 2.4.5 → 2.5.1

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 (275) hide show
  1. package/README.md +4 -2
  2. package/dist/{acp-adapter-2wdkktv6.js → acp-adapter-7g6bpwbk.js} +8 -6
  3. package/dist/{actions-c40ebcm4.js → actions-w8xd014k.js} +23 -21
  4. package/dist/{amp-adapter-wb67m95z.js → amp-adapter-z4qvhacj.js} +2 -2
  5. package/dist/{anthropic-messages-g6pqmqk4.js → anthropic-messages-txs190dd.js} +17 -20
  6. package/dist/{app-353vre8g.js → app-nndcvftq.js} +3 -3
  7. package/dist/{assistant-sj1njh23.js → assistant-hjmqyscx.js} +24 -22
  8. package/dist/{azure-openai-responses-9dbmj346.js → azure-openai-responses-9kfwhmn7.js} +18 -13
  9. package/dist/{boot-reembed-b9c3az3c.js → boot-reembed-r8rhf9h8.js} +1 -1
  10. package/dist/{boot-reembed-qdkvxeem.js → boot-reembed-vwcj2y5d.js} +1 -1
  11. package/dist/{boot-scrub-sweep-m3wh8eks.js → boot-scrub-sweep-fr9q36ne.js} +4 -4
  12. package/dist/{boot-seeds-j2273s54.js → boot-seeds-9jqwnegq.js} +4 -4
  13. package/dist/{claude-adapter-m7q0a085.js → claude-adapter-2f8b85df.js} +8 -6
  14. package/dist/{claude-managed-adapter-5ptb6tzs.js → claude-managed-adapter-gt6pa35d.js} +2 -2
  15. package/dist/{claude-managed-setup-smrqk5j5.js → claude-managed-setup-c3hp36ph.js} +4 -4
  16. package/dist/{claude-sdk-session-38xnz21e.js → claude-sdk-session-05kat8rv.js} +9012 -8928
  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-1sp861s4.js → cli-0jy85zz4.js} +11 -11
  20. package/dist/{cli-j99azg3g.js → cli-1dp1zj04.js} +5 -5
  21. package/dist/{cli-3r8samv3.js → cli-1f0gp9vz.js} +3 -2
  22. package/dist/{cli-94gwyg7m.js → cli-22rmcjkg.js} +9 -9
  23. package/dist/{cli-ms85309d.js → cli-2jg61wab.js} +11 -11
  24. package/dist/{cli-0bqteszy.js → cli-2nf3b08t.js} +328 -25
  25. package/dist/{cli-9yqgm9nd.js → cli-2wba1ac9.js} +161 -63
  26. package/dist/{cli-cxxe61f6.js → cli-3ee5z0q0.js} +2 -2
  27. package/dist/{cli-k8g16gz3.js → cli-3qv8naq8.js} +115 -119
  28. package/dist/{cli-60cgh1rv.js → cli-459fk97x.js} +7 -7
  29. package/dist/{cli-h7gc0b4r.js → cli-5m84zd2k.js} +55 -18
  30. package/dist/{cli-dvkmp9bt.js → cli-6ctwrf1z.js} +20 -10
  31. package/dist/{cli-848hxdwj.js → cli-6rg2jn2y.js} +2 -1
  32. package/dist/{cli-dk44dxec.js → cli-9mm5ade4.js} +1 -1
  33. package/dist/{cli-7c9qs530.js → cli-ajqhxtnz.js} +5 -5
  34. package/dist/{cli-be2161h1.js → cli-cah5detr.js} +6 -6
  35. package/dist/{cli-v8fjakwf.js → cli-cwyafhw1.js} +1 -1
  36. package/dist/{cli-wxbn3yfx.js → cli-cz5nmdxt.js} +2 -2
  37. package/dist/{cli-wt645v7q.js → cli-dabehxhp.js} +1 -1
  38. package/dist/{cli-vfha21c9.js → cli-e5bxnjps.js} +2 -2
  39. package/dist/{cli-37j04k05.js → cli-e9btqw4y.js} +13 -13
  40. package/dist/{cli-1syn2xy1.js → cli-eym3jg7w.js} +32 -18
  41. package/dist/{cli-ftc1g5tj.js → cli-fes51ya2.js} +3 -3
  42. package/dist/{cli-s95dknxh.js → cli-g1cw7xtt.js} +1 -1
  43. package/dist/{cli-74c1wpm0.js → cli-gpv60278.js} +1 -1
  44. package/dist/{cli-1f3qzw6y.js → cli-gwcf52gz.js} +2 -2
  45. package/dist/{cli-pemywhfq.js → cli-ha9rmwgq.js} +2 -1
  46. package/dist/{cli-v0yrfmdh.js → cli-hadv0725.js} +3 -3
  47. package/dist/{cli-g35m2xwm.js → cli-j1eghqfm.js} +2 -2
  48. package/dist/{cli-qfxc84gh.js → cli-jpxsrxcb.js} +1 -1
  49. package/dist/{cli-n72rg53p.js → cli-kcsd1ez8.js} +848 -426
  50. package/dist/{cli-ejs3m1sx.js → cli-kxpfx2xr.js} +1 -1
  51. package/dist/{cli-pnp2j5zg.js → cli-mmhj8vd7.js} +2 -2
  52. package/dist/{cli-st8aw1m6.js → cli-mq7kg1b1.js} +4 -4
  53. package/dist/{cli-bnes4z6b.js → cli-mz2ygeyq.js} +3 -3
  54. package/dist/{cli-xf234ws8.js → cli-n4c8e3s5.js} +2 -0
  55. package/dist/{cli-f01h25yt.js → cli-n6kmqngw.js} +2 -2
  56. package/dist/cli-nj12tn2c.js +1193 -0
  57. package/dist/{cli-1dmcq2eh.js → cli-nzfnt2gs.js} +1 -1
  58. package/dist/{cli-625tb7aq.js → cli-p859hcc8.js} +2 -2
  59. package/dist/{cli-smvcw9z5.js → cli-pasyvjmm.js} +1 -1
  60. package/dist/cli-pkaqk57w.js +128 -0
  61. package/dist/{cli-xpa64rek.js → cli-pyy4c9ch.js} +2362 -159
  62. package/dist/{cli-wt0jf9hm.js → cli-q9pzwxvk.js} +553 -193
  63. package/dist/{cli-z7d29rw9.js → cli-qfjm4gvt.js} +4 -4
  64. package/dist/{cli-p14m2cnp.js → cli-qfm6my86.js} +1 -1
  65. package/dist/{cli-8nyhf7za.js → cli-qqzmjxkf.js} +129 -105
  66. package/dist/{cli-4zfqgjc5.js → cli-r9fy0ddc.js} +2 -2
  67. package/dist/{cli-eww120g8.js → cli-rh5smt8t.js} +18 -4
  68. package/dist/{cli-a92r1127.js → cli-rngde2jv.js} +2 -2
  69. package/dist/{cli-qwkcwjdn.js → cli-rycdaj0a.js} +1 -1
  70. package/dist/{cli-fv5hepyy.js → cli-sn1vbtg2.js} +3 -3
  71. package/dist/{cli-36ckw0wx.js → cli-stxdrqs2.js} +2 -2
  72. package/dist/{cli-1m17dc9d.js → cli-ta8bd7th.js} +2 -2
  73. package/dist/{cli-43x64hv3.js → cli-v2g93k4v.js} +4 -4
  74. package/dist/{cli-d2mb5s8a.js → cli-x77gen2p.js} +680 -440
  75. package/dist/{cli-3a45zcgm.js → cli-xc65x52q.js} +2 -2
  76. package/dist/{cli-f0g3pgvf.js → cli-xd4eqtr2.js} +2 -2
  77. package/dist/{cli-018r4jq6.js → cli-y3zyaee5.js} +8 -8
  78. package/dist/{cli-kpw04va0.js → cli-z2dv25mf.js} +1 -1
  79. package/dist/{cli-qwhy2qma.js → cli-z63a269p.js} +2 -2
  80. package/dist/{cli-sm8rses9.js → cli-zfdp01hn.js} +8 -5
  81. package/dist/{cli-tw4p90jb.js → cli-znvq0k3t.js} +5 -5
  82. package/dist/cli.js +29 -27
  83. package/dist/{cloudflare-workers-ai-system-one-ch2wxfcp.js → cloudflare-workers-ai-system-one-43v9qkjk.js} +7 -2
  84. package/dist/{codex-adapter-tn6k5mkg.js → codex-adapter-p4ptwar5.js} +6 -4
  85. package/dist/{codex-login-m819c41r.js → codex-login-rn7c5gxt.js} +1 -1
  86. package/dist/{codex-session-runner-7vjt7jbw.js → codex-session-runner-964c7dnk.js} +7 -5
  87. package/dist/{commands-t7rz9146.js → commands-mbvhbrcy.js} +2 -2
  88. package/dist/{controller-m2jasma5.js → controller-gnq1k1mf.js} +4 -4
  89. package/dist/{controllers-connect-9ftpex99.js → controllers-connect-6dkm71tg.js} +25 -23
  90. package/dist/{cursor-adapter-mjv79e4z.js → cursor-adapter-yq5xgfcg.js} +1 -1
  91. package/dist/{db-6sfrn9g3.js → db-88wcymxt.js} +14 -2
  92. package/dist/{db-rn673r06.js → db-af9y8nkf.js} +3 -3
  93. package/dist/{devin-adapter-zgqadvnm.js → devin-adapter-4ycy4vrf.js} +1 -1
  94. package/dist/{dispatcher-c155xz0p.js → dispatcher-606ydx5v.js} +22 -20
  95. package/dist/{docker-fkgtr75t.js → docker-nfy2gc8t.js} +2 -2
  96. package/dist/{dsh-adapter-3mmszc1x.js → dsh-adapter-tvhc4k2d.js} +1 -1
  97. package/dist/{e2b-16d6ch39.js → e2b-9gkdhm3c.js} +2 -2
  98. package/dist/{e2b-zn0b4w5w.js → e2b-cjtf8k1z.js} +2 -2
  99. package/dist/{easter-egg-3d-5cgynaja.js → easter-egg-3d-e4qv8dxj.js} +1 -1
  100. package/dist/{event-bus-9vpk8gmq.js → event-bus-3z78ep5n.js} +1 -1
  101. package/dist/{execute-t4e6m3rw.js → execute-0xpz8950.js} +85 -23
  102. package/dist/{external-channel-35k6tkv8.js → external-channel-y8q5z8z2.js} +2 -2
  103. package/dist/{google-generative-ai-bmq8gkf3.js → google-generative-ai-mk9vcpe1.js} +15 -15
  104. package/dist/{google-vertex-b1rvp52s.js → google-vertex-q580kz8k.js} +20 -20
  105. package/dist/{grok-adapter-tfrq7tg1.js → grok-adapter-39aha52t.js} +9 -7
  106. package/dist/handlers-rk9dqech.js +41 -0
  107. package/dist/{heartbeat-dyg4npkp.js → heartbeat-jfe9zfjr.js} +25 -23
  108. package/dist/{hook-drspbwpe.js → hook-b1mez1m8.js} +9 -7
  109. package/dist/{hook-x26b31bg.js → hook-ffevzed1.js} +7 -5
  110. package/dist/{http-pk972xyw.js → http-d5b44f1r.js} +544 -76
  111. package/dist/{index-g6tbb6ve.js → index-31ceyf42.js} +1 -1
  112. package/dist/index-4q3vj1yr.js +46 -0
  113. package/dist/{index-arytgj5q.js → index-5en23s5z.js} +1 -1
  114. package/dist/index-d2qeem97.js +42 -0
  115. package/dist/index-mj46rk9k.js +43 -0
  116. package/dist/{index-jwxkkex2.js → index-nq8hhv28.js} +13 -11
  117. package/dist/{index-g216q6jr.js → index-zsyx51ap.js} +1 -4
  118. package/dist/{keepalive-7yrng7px.js → keepalive-wsby1btq.js} +4 -4
  119. package/dist/lead-paa141j6.js +79 -0
  120. package/dist/{llama-cpp-classify-vv43gswy.js → llama-cpp-classify-ee7vmd7q.js} +4 -1
  121. package/dist/{maintenance-e6cemdqt.js → maintenance-vnshcyv9.js} +1 -1
  122. package/dist/{mistral-conversations-1medfwv7.js → mistral-conversations-sd0xf1tn.js} +13 -13
  123. package/dist/{node-de6akczr.js → node-jzjj7r9x.js} +1 -1
  124. package/dist/{oauth-refresh-sweep-yw3xdbcj.js → oauth-refresh-sweep-d6sqn5np.js} +3 -3
  125. package/dist/{onboard-0a9y5yrc.js → onboard-nr4xhpzy.js} +2 -2
  126. package/dist/{openai-codex-responses-de9pvs4d.js → openai-codex-responses-79s8xnbh.js} +29 -23
  127. package/dist/{openai-completions-mtqge3nf.js → openai-completions-tdbyts0x.js} +21 -20
  128. package/dist/{openai-decisions-ygk4kkxd.js → openai-decisions-bqhnjyfz.js} +2 -1
  129. package/dist/{openai-responses-dkyng71g.js → openai-responses-dm3t5vqp.js} +26 -17
  130. package/dist/{opencode-adapter-x4tqzhkh.js → opencode-adapter-e56086m1.js} +7 -5
  131. package/dist/{openrouter-images-wvv7camj.js → openrouter-images-ssttr1y4.js} +6 -3
  132. package/dist/{otel-impl-trjpkfb7.js → otel-impl-ytn9wyv9.js} +2 -2
  133. package/dist/{pi-messages-nhh1pkz2.js → pi-messages-3nf8bq1a.js} +6 -4
  134. package/dist/{pi-mono-adapter-mcfkn9vf.js → pi-mono-adapter-tzps8f38.js} +9 -37
  135. package/dist/{pricing-refresh-d2b5my0y.js → pricing-refresh-pva04zee.js} +3 -3
  136. package/dist/{rbac-roles-nxh5ahwk.js → rbac-roles-7rc1q3g4.js} +1 -1
  137. package/dist/{rbac-roles-hey3yv9d.js → rbac-roles-ff1ffmec.js} +2 -2
  138. package/dist/{render-v2-rr3p2atg.js → render-v2-pyfsg9zk.js} +3 -3
  139. package/dist/{resume-2efkf1s1.js → resume-phj8xhqr.js} +10 -8
  140. package/dist/{runtime-xe3aqy4x.js → runtime-bt5pkpm1.js} +1 -1
  141. package/dist/{session-access-s2mttxpw.js → session-access-jzx2z9h5.js} +6 -4
  142. package/dist/{setup-vypma5yt.js → setup-rfg0f0sg.js} +1 -1
  143. package/dist/{typesafe-system-one-2hgpayzy.js → typesafe-system-one-bre38hvs.js} +4 -2
  144. package/dist/{virtual-modules-4r3zkc20.js → virtual-modules-mnfbh5mr.js} +8 -2
  145. package/dist/{work-objects-rvhckdkp.js → work-objects-44z2yjg8.js} +25 -23
  146. package/dist/worker-zsfqzxhm.js +79 -0
  147. package/dist/{x-y45mthwn.js → x-pbpezr0s.js} +2 -2
  148. package/openapi.json +785 -5
  149. package/package.json +9 -9
  150. package/src/assets/key.ts +17 -0
  151. package/src/azure-devops/handlers.ts +1 -1
  152. package/src/be/db/approvals.ts +18 -0
  153. package/src/be/db/session-moves.ts +164 -0
  154. package/src/be/db/session-tree.ts +384 -0
  155. package/src/be/db/tasks/read.ts +40 -4
  156. package/src/be/db/workflow-runs.ts +96 -0
  157. package/src/be/db.ts +80 -236
  158. package/src/be/onboarding.ts +3 -0
  159. package/src/be/skill-sync.ts +43 -2
  160. package/src/be/swarm-config-guard.ts +1 -0
  161. package/src/bitbucket/api.ts +81 -0
  162. package/src/bitbucket/auth.ts +64 -0
  163. package/src/bitbucket/handlers.ts +434 -0
  164. package/src/bitbucket/index.ts +28 -0
  165. package/src/bitbucket/templates.ts +134 -0
  166. package/src/bitbucket/types.ts +65 -0
  167. package/src/commands/runner.ts +55 -3
  168. package/src/e2b/env.ts +1 -0
  169. package/src/extensions/contract-types.generated.ts +1 -1
  170. package/src/extensions/contract.ts +2 -2
  171. package/src/extensions/post-bridge.ts +1 -0
  172. package/src/github/handlers.ts +6 -6
  173. package/src/gitlab/auth.ts +8 -2
  174. package/src/gitlab/handlers.ts +63 -15
  175. package/src/gitlab/index.ts +1 -1
  176. package/src/http/assets.ts +14 -2
  177. package/src/http/core.ts +12 -0
  178. package/src/http/index.ts +4 -0
  179. package/src/http/onboarding.ts +1 -0
  180. package/src/http/sessions.ts +366 -12
  181. package/src/http/skills.ts +48 -1
  182. package/src/http/tasks.ts +1 -1
  183. package/src/http/users.ts +1 -0
  184. package/src/http/webhooks.ts +141 -0
  185. package/src/integrations/chat-sdk/adapters.ts +19 -3
  186. package/src/integrations/chat-sdk/facts.ts +30 -12
  187. package/src/integrations/chat-sdk/outbound.ts +38 -1
  188. package/src/providers/acp-targets.ts +1 -1
  189. package/src/providers/amp-adapter.ts +1 -1
  190. package/src/providers/pi-mono-adapter.ts +5 -53
  191. package/src/scripts-runtime/types/swarm-extension.d.ts +6 -4
  192. package/src/slack/handlers.test.ts +158 -0
  193. package/src/slack/handlers.ts +19 -28
  194. package/src/tasks/context-key.ts +61 -1
  195. package/src/tests/bitbucket-auth.test.ts +156 -0
  196. package/src/tests/bitbucket-handlers.test.ts +425 -0
  197. package/src/tests/bitbucket-webhook.test.ts +220 -0
  198. package/src/tests/chat-sdk-inbound.test.ts +39 -0
  199. package/src/tests/chat-sdk-telegram-markdown.test.ts +92 -0
  200. package/src/tests/context-key.test.ts +50 -0
  201. package/src/tests/core-auth.test.ts +5 -0
  202. package/src/tests/db-task-read-contract.test.ts +7 -0
  203. package/src/tests/db-task-write-contract.test.ts +6 -0
  204. package/src/tests/docs-index-footprint.test.js +3 -3
  205. package/src/tests/fixtures/bitbucket/pullrequest-comment-created-inline.json +174 -0
  206. package/src/tests/fixtures/bitbucket/pullrequest-comment-created.json +170 -0
  207. package/src/tests/fixtures/bitbucket/pullrequest-created-mention.json +132 -0
  208. package/src/tests/fixtures/bitbucket/pullrequest-created-reviewer.json +140 -0
  209. package/src/tests/fixtures/bitbucket/pullrequest-fulfilled.json +134 -0
  210. package/src/tests/fixtures/bitbucket/pullrequest-rejected.json +133 -0
  211. package/src/tests/generate-mcp-docs.test.ts +101 -0
  212. package/src/tests/gitlab-auth.test.ts +13 -5
  213. package/src/tests/gitlab-handlers.test.ts +273 -25
  214. package/src/tests/gitlab-vcs-db.test.ts +4 -4
  215. package/src/tests/integration-disable-flags.test.ts +36 -0
  216. package/src/tests/mcp-tools.test.ts +1 -1
  217. package/src/tests/onboarding.test.ts +12 -0
  218. package/src/tests/pi-codemode.test.ts +8 -2
  219. package/src/tests/preload-env.test.ts +57 -0
  220. package/src/tests/preload.ts +21 -0
  221. package/src/tests/prompt-template-remaining.test.ts +38 -0
  222. package/src/tests/run-root-coverage.test.ts +39 -0
  223. package/src/tests/runner-bitbucket-vcs.test.ts +191 -0
  224. package/src/tests/runner-skills-refresh.test.ts +58 -0
  225. package/src/tests/secret-scrubber.test.ts +15 -0
  226. package/src/tests/sessions-move.test.ts +576 -0
  227. package/src/tests/sessions-tree.test.ts +493 -0
  228. package/src/tests/skill-files-http.test.ts +50 -0
  229. package/src/tests/skills-bundle.test.ts +99 -0
  230. package/src/tests/swarm-config-export-to-env.test.ts +2 -1
  231. package/src/tests/vcs-bitbucket.test.ts +91 -0
  232. package/src/tests/vcs-provider-isolation.test.ts +123 -0
  233. package/src/tests/vcs-provider.test.ts +16 -1
  234. package/src/tests/vcs-tracking.test.ts +3 -3
  235. package/src/tests/workflow-foreach-bodies.test.ts +637 -0
  236. package/src/tests/workflow-foreach-concurrency.test.ts +342 -0
  237. package/src/tests/workflow-foreach-retry.test.ts +537 -0
  238. package/src/tests/workflow-foreach.test.ts +44 -23
  239. package/src/tests/workflow-step-claims.test.ts +6 -6
  240. package/src/tests/workflow-tla-races.test.ts +2 -2
  241. package/src/tools/script-delete.ts +1 -1
  242. package/src/tools/workflows/create-workflow.ts +5 -3
  243. package/src/types.ts +3 -2
  244. package/src/utils/config-env-export.ts +1 -0
  245. package/src/utils/secret-scrubber.ts +4 -0
  246. package/src/utils/skills-refresh.ts +83 -43
  247. package/src/vcs/bitbucket.ts +58 -0
  248. package/src/vcs/index.ts +2 -0
  249. package/src/vcs/types.ts +2 -2
  250. package/src/workflows/approval-resolution.ts +4 -1
  251. package/src/workflows/checkpoint.ts +9 -1
  252. package/src/workflows/constants.ts +3 -0
  253. package/src/workflows/definition.ts +48 -17
  254. package/src/workflows/engine.ts +25 -15
  255. package/src/workflows/executors/foreach.ts +45 -99
  256. package/src/workflows/foreach-body.ts +146 -0
  257. package/src/workflows/foreach-driver.ts +552 -0
  258. package/src/workflows/foreach-join.ts +206 -7
  259. package/src/workflows/node-timeout.ts +21 -0
  260. package/src/workflows/recovery.ts +68 -3
  261. package/src/workflows/resume.ts +221 -43
  262. package/src/workflows/retry-poller.ts +39 -24
  263. package/src/workflows/step-input.ts +59 -0
  264. package/src/workflows/task-step-routing.ts +114 -56
  265. package/templates/skills/slack-interaction/content.md +5 -0
  266. package/templates/skills/work-on-task/SKILL.md +1 -1
  267. package/templates/skills/work-on-task/content.md +1 -1
  268. package/dist/cli-9hstj09h.js +0 -2657
  269. package/dist/cli-yw91rnmw.js +0 -13
  270. package/dist/handlers-7gw64dxw.js +0 -39
  271. package/dist/index-bgdyvcnv.js +0 -40
  272. package/dist/index-gabbcdn2.js +0 -43
  273. package/dist/index-tfb26zhy.js +0 -41
  274. package/dist/lead-7e02z839.js +0 -77
  275. package/dist/worker-5snytpbd.js +0 -77
@@ -13,7 +13,7 @@ import {
13
13
  initDb,
14
14
  } from "../be/db";
15
15
  import { findUserByExternalId, linkIdentity } from "../be/users";
16
- import { GITLAB_BOT_NAME } from "../gitlab/auth";
16
+ import { getGitLabBotName } from "../gitlab/auth";
17
17
  import { handleIssue, handleMergeRequest, handleNote, handlePipeline } from "../gitlab/handlers";
18
18
  import type { IssueEvent, MergeRequestEvent, NoteEvent, PipelineEvent } from "../gitlab/types";
19
19
 
@@ -37,7 +37,7 @@ function makeMREvent(overrides: Partial<MergeRequestEvent> = {}): MergeRequestEv
37
37
  id: 100,
38
38
  iid: 1,
39
39
  title: "Test MR",
40
- description: `@${GITLAB_BOT_NAME} please review`,
40
+ description: `@${getGitLabBotName()} please review`,
41
41
  state: "opened",
42
42
  action: "open",
43
43
  source_branch: "feature",
@@ -66,7 +66,7 @@ function makeIssueEvent(overrides: Partial<IssueEvent> = {}): IssueEvent {
66
66
  id: 200,
67
67
  iid: 10,
68
68
  title: "Test Issue",
69
- description: `@${GITLAB_BOT_NAME} fix this`,
69
+ description: `@${getGitLabBotName()} fix this`,
70
70
  state: "opened",
71
71
  action: "open",
72
72
  url: "https://gitlab.com/group/project/-/issues/10",
@@ -90,7 +90,7 @@ function makeNoteEvent(overrides: Partial<NoteEvent> = {}): NoteEvent {
90
90
  },
91
91
  object_attributes: {
92
92
  id: 300,
93
- note: `@${GITLAB_BOT_NAME} can you help?`,
93
+ note: `@${getGitLabBotName()} can you help?`,
94
94
  noteable_type: "MergeRequest",
95
95
  noteable_id: 100,
96
96
  url: "https://gitlab.com/group/project/-/merge_requests/1#note_300",
@@ -181,7 +181,7 @@ describe("handleMergeRequest", () => {
181
181
  id: 101,
182
182
  iid: 50,
183
183
  title: "MR with mention",
184
- description: `@${GITLAB_BOT_NAME} review this please`,
184
+ description: `@${getGitLabBotName()} review this please`,
185
185
  state: "opened",
186
186
  action: "open",
187
187
  source_branch: "feat-a",
@@ -240,7 +240,7 @@ describe("handleMergeRequest", () => {
240
240
  agentId: "lead-gl-001",
241
241
  });
242
242
 
243
- const existing = await findTaskByVcs("group/project", 60);
243
+ const existing = await findTaskByVcs("group/project", 60, "gitlab");
244
244
  expect(existing).not.toBeNull();
245
245
 
246
246
  const event = makeMREvent({
@@ -297,18 +297,212 @@ describe("handleMergeRequest", () => {
297
297
  const result = await handleMergeRequest(event);
298
298
  expect(result.created).toBe(false);
299
299
 
300
- const existing = await findTaskByVcs("group/project", 61);
300
+ const existing = await findTaskByVcs("group/project", 61, "gitlab");
301
301
  // Should no longer be active (findTaskByVcs filters out completed/failed)
302
302
  expect(existing).toBeNull();
303
303
  });
304
304
 
305
+ test("a merge cancels every active task for the MR, not only the newest", async () => {
306
+ const mrTask = await createTaskExtended("[GitLab MR #62] Test", {
307
+ source: "gitlab",
308
+ vcsProvider: "gitlab",
309
+ vcsRepo: "group/project",
310
+ vcsEventType: "merge_request",
311
+ vcsNumber: 62,
312
+ agentId: "lead-gl-001",
313
+ });
314
+ const noteTask = await createTaskExtended("[GitLab MR #62] Comment", {
315
+ source: "gitlab",
316
+ vcsProvider: "gitlab",
317
+ vcsRepo: "group/project",
318
+ vcsEventType: "note_on_mr",
319
+ vcsNumber: 62,
320
+ agentId: "lead-gl-001",
321
+ parentTaskId: mrTask.id,
322
+ });
323
+
324
+ const event = makeMREvent({
325
+ object_attributes: {
326
+ id: 105,
327
+ iid: 62,
328
+ title: "MR to merge",
329
+ description: null,
330
+ state: "merged",
331
+ action: "merge",
332
+ source_branch: "feat-e",
333
+ target_branch: "main",
334
+ url: "https://gitlab.com/group/project/-/merge_requests/62",
335
+ last_commit: null,
336
+ author_id: 1,
337
+ },
338
+ });
339
+ await handleMergeRequest(event);
340
+
341
+ for (const id of [mrTask.id, noteTask.id]) {
342
+ const task = await getTaskById(id);
343
+ expect(task?.status).toBe("failed");
344
+ expect(task?.failureReason).toBe("MR was merged");
345
+ }
346
+ });
347
+
348
+ test("closing MR !5 leaves issue #5 and its notes active", async () => {
349
+ // GitLab issue and MR iids are separate sequences: issue #5 and MR !5 coexist.
350
+ const issue = await handleIssue(
351
+ makeIssueEvent({
352
+ object_attributes: {
353
+ id: 205,
354
+ iid: 5,
355
+ title: "Issue five",
356
+ description: `@${getGitLabBotName()} fix this`,
357
+ state: "opened",
358
+ action: "open",
359
+ url: "https://gitlab.com/group/project/-/issues/5",
360
+ author_id: 1,
361
+ },
362
+ }),
363
+ );
364
+ const issueNote = await handleNote(
365
+ makeNoteEvent({
366
+ object_attributes: {
367
+ id: 395,
368
+ note: `@${getGitLabBotName()} any update?`,
369
+ noteable_type: "Issue",
370
+ noteable_id: 205,
371
+ url: "https://gitlab.com/group/project/-/issues/5#note_395",
372
+ author_id: 1,
373
+ type: null,
374
+ },
375
+ merge_request: undefined,
376
+ issue: {
377
+ id: 205,
378
+ iid: 5,
379
+ title: "Issue five",
380
+ description: null,
381
+ state: "opened",
382
+ action: "open",
383
+ url: "https://gitlab.com/group/project/-/issues/5",
384
+ author_id: 1,
385
+ },
386
+ }),
387
+ );
388
+ const mr = await handleMergeRequest(
389
+ makeMREvent({
390
+ object_attributes: {
391
+ id: 155,
392
+ iid: 5,
393
+ title: "MR five",
394
+ description: `@${getGitLabBotName()} please review`,
395
+ state: "opened",
396
+ action: "open",
397
+ source_branch: "feat-five",
398
+ target_branch: "main",
399
+ url: "https://gitlab.com/group/project/-/merge_requests/5",
400
+ last_commit: null,
401
+ author_id: 1,
402
+ },
403
+ }),
404
+ );
405
+ const mrNote = await handleNote(
406
+ makeNoteEvent({
407
+ object_attributes: {
408
+ id: 396,
409
+ note: `@${getGitLabBotName()} looks good?`,
410
+ noteable_type: "MergeRequest",
411
+ noteable_id: 155,
412
+ url: "https://gitlab.com/group/project/-/merge_requests/5#note_396",
413
+ author_id: 1,
414
+ type: null,
415
+ },
416
+ merge_request: {
417
+ id: 155,
418
+ iid: 5,
419
+ title: "MR five",
420
+ description: null,
421
+ state: "opened",
422
+ action: "open",
423
+ source_branch: "feat-five",
424
+ target_branch: "main",
425
+ url: "https://gitlab.com/group/project/-/merge_requests/5",
426
+ last_commit: null,
427
+ author_id: 1,
428
+ },
429
+ }),
430
+ );
431
+ // A pre-contextKey issue row must not be swept up either.
432
+ const legacyIssue = await createTaskExtended("[GitLab Issue #5] Legacy", {
433
+ source: "gitlab",
434
+ vcsProvider: "gitlab",
435
+ vcsRepo: "group/project",
436
+ vcsEventType: "issue",
437
+ vcsNumber: 5,
438
+ agentId: "lead-gl-001",
439
+ });
440
+ for (const r of [issue, issueNote, mr, mrNote]) expect(r.created).toBe(true);
441
+
442
+ await handleMergeRequest(
443
+ makeMREvent({
444
+ object_attributes: {
445
+ id: 155,
446
+ iid: 5,
447
+ title: "MR five",
448
+ description: null,
449
+ state: "closed",
450
+ action: "close",
451
+ source_branch: "feat-five",
452
+ target_branch: "main",
453
+ url: "https://gitlab.com/group/project/-/merge_requests/5",
454
+ last_commit: null,
455
+ author_id: 1,
456
+ },
457
+ }),
458
+ );
459
+
460
+ for (const id of [mr.taskId!, mrNote.taskId!]) {
461
+ const task = await getTaskById(id);
462
+ expect(task?.status).toBe("failed");
463
+ expect(task?.failureReason).toBe("MR was closed");
464
+ }
465
+ for (const id of [issue.taskId!, issueNote.taskId!, legacyIssue.id]) {
466
+ const task = await getTaskById(id);
467
+ expect(task?.status).not.toBe("failed");
468
+ }
469
+ });
470
+
471
+ test("reads GITLAB_BOT_NAME at match time, so a config reload applies", async () => {
472
+ const mrEvent = (iid: number, description: string) =>
473
+ makeMREvent({
474
+ object_attributes: {
475
+ id: 1000 + iid,
476
+ iid,
477
+ title: "MR for renamed bot",
478
+ description,
479
+ state: "opened",
480
+ action: "open",
481
+ source_branch: "feat-renamed",
482
+ target_branch: "main",
483
+ url: `https://gitlab.com/group/project/-/merge_requests/${iid}`,
484
+ last_commit: null,
485
+ author_id: 1,
486
+ },
487
+ });
488
+ process.env.GITLAB_BOT_NAME = "renamed-bot";
489
+ try {
490
+ expect((await handleMergeRequest(mrEvent(63, "@renamed-bot review"))).created).toBe(true);
491
+ expect((await handleMergeRequest(mrEvent(64, "@agent-swarm-bot review"))).created).toBe(
492
+ false,
493
+ );
494
+ } finally {
495
+ delete process.env.GITLAB_BOT_NAME;
496
+ }
497
+ });
498
+
305
499
  test("ignores update action when no active task", async () => {
306
500
  const event = makeMREvent({
307
501
  object_attributes: {
308
502
  id: 105,
309
503
  iid: 62,
310
504
  title: "Updated MR",
311
- description: `@${GITLAB_BOT_NAME} review`,
505
+ description: `@${getGitLabBotName()} review`,
312
506
  state: "opened",
313
507
  action: "update",
314
508
  source_branch: "feat-e",
@@ -329,7 +523,7 @@ describe("handleMergeRequest", () => {
329
523
  id: 106,
330
524
  iid: 70,
331
525
  title: "Dedup MR",
332
- description: `@${GITLAB_BOT_NAME} review`,
526
+ description: `@${getGitLabBotName()} review`,
333
527
  state: "opened",
334
528
  action: "open",
335
529
  source_branch: "feat-dedup",
@@ -360,7 +554,7 @@ describe("handleIssue", () => {
360
554
  id: 201,
361
555
  iid: 20,
362
556
  title: "Issue with mention",
363
- description: `@${GITLAB_BOT_NAME} implement this feature`,
557
+ description: `@${getGitLabBotName()} implement this feature`,
364
558
  state: "opened",
365
559
  action: "open",
366
560
  url: "https://gitlab.com/group/project/-/issues/20",
@@ -391,7 +585,7 @@ describe("handleIssue", () => {
391
585
  url: "https://gitlab.com/group/project/-/issues/21",
392
586
  author_id: 1,
393
587
  },
394
- assignees: [{ id: 99, name: "Bot", username: GITLAB_BOT_NAME, avatar_url: "" }],
588
+ assignees: [{ id: 99, name: "Bot", username: getGitLabBotName(), avatar_url: "" }],
395
589
  });
396
590
 
397
591
  const result = await handleIssue(event);
@@ -444,9 +638,63 @@ describe("handleIssue", () => {
444
638
  expect(result.created).toBe(false);
445
639
 
446
640
  // Task should no longer be active
447
- const task = await findTaskByVcs("group/project", 30);
641
+ const task = await findTaskByVcs("group/project", 30, "gitlab");
448
642
  expect(task).toBeNull();
449
643
  });
644
+
645
+ test("closing issue #6 fails the issue task, not the newer MR !6 task", async () => {
646
+ const issue = await handleIssue(
647
+ makeIssueEvent({
648
+ object_attributes: {
649
+ id: 206,
650
+ iid: 6,
651
+ title: "Issue six",
652
+ description: `@${getGitLabBotName()} fix this`,
653
+ state: "opened",
654
+ action: "open",
655
+ url: "https://gitlab.com/group/project/-/issues/6",
656
+ author_id: 1,
657
+ },
658
+ }),
659
+ );
660
+ // Created second, so it is the newest row for vcsNumber 6.
661
+ const mr = await handleMergeRequest(
662
+ makeMREvent({
663
+ object_attributes: {
664
+ id: 156,
665
+ iid: 6,
666
+ title: "MR six",
667
+ description: `@${getGitLabBotName()} please review`,
668
+ state: "opened",
669
+ action: "open",
670
+ source_branch: "feat-six",
671
+ target_branch: "main",
672
+ url: "https://gitlab.com/group/project/-/merge_requests/6",
673
+ last_commit: null,
674
+ author_id: 1,
675
+ },
676
+ }),
677
+ );
678
+ expect(issue.created && mr.created).toBe(true);
679
+
680
+ await handleIssue(
681
+ makeIssueEvent({
682
+ object_attributes: {
683
+ id: 206,
684
+ iid: 6,
685
+ title: "Issue six",
686
+ description: null,
687
+ state: "closed",
688
+ action: "close",
689
+ url: "https://gitlab.com/group/project/-/issues/6",
690
+ author_id: 1,
691
+ },
692
+ }),
693
+ );
694
+
695
+ expect((await getTaskById(issue.taskId!))?.failureReason).toBe("Issue was closed");
696
+ expect((await getTaskById(mr.taskId!))?.status).not.toBe("failed");
697
+ });
450
698
  });
451
699
 
452
700
  // ═══════════════════════════════════════════════════════
@@ -458,7 +706,7 @@ describe("handleNote", () => {
458
706
  const event = makeNoteEvent({
459
707
  object_attributes: {
460
708
  id: 301,
461
- note: `@${GITLAB_BOT_NAME} please check this code`,
709
+ note: `@${getGitLabBotName()} please check this code`,
462
710
  noteable_type: "MergeRequest",
463
711
  noteable_id: 100,
464
712
  url: "https://gitlab.com/group/project/-/merge_requests/1#note_301",
@@ -493,7 +741,7 @@ describe("handleNote", () => {
493
741
  const event = makeNoteEvent({
494
742
  object_attributes: {
495
743
  id: 302,
496
- note: `@${GITLAB_BOT_NAME} what do you think?`,
744
+ note: `@${getGitLabBotName()} what do you think?`,
497
745
  noteable_type: "Issue",
498
746
  noteable_id: 200,
499
747
  url: "https://gitlab.com/group/project/-/issues/40#note_302",
@@ -542,7 +790,7 @@ describe("handleNote", () => {
542
790
  const event = makeNoteEvent({
543
791
  object_attributes: {
544
792
  id: 304,
545
- note: `@${GITLAB_BOT_NAME} review this commit`,
793
+ note: `@${getGitLabBotName()} review this commit`,
546
794
  noteable_type: "Commit",
547
795
  noteable_id: 999,
548
796
  url: "https://gitlab.com/group/project/-/commit/abc#note_304",
@@ -572,7 +820,7 @@ describe("handleNote", () => {
572
820
  const event = makeNoteEvent({
573
821
  object_attributes: {
574
822
  id: 305,
575
- note: `@${GITLAB_BOT_NAME} also fix the linting`,
823
+ note: `@${getGitLabBotName()} also fix the linting`,
576
824
  noteable_type: "MergeRequest",
577
825
  noteable_id: 100,
578
826
  url: "https://gitlab.com/group/project/-/merge_requests/85#note_305",
@@ -742,7 +990,7 @@ describe("identity resolution — MR handler", () => {
742
990
  id: 901,
743
991
  iid: 901,
744
992
  title: "Known MR",
745
- description: `@${GITLAB_BOT_NAME} review`,
993
+ description: `@${getGitLabBotName()} review`,
746
994
  state: "opened",
747
995
  action: "open",
748
996
  source_branch: "feat-known",
@@ -779,7 +1027,7 @@ describe("identity resolution — MR handler", () => {
779
1027
  id: 902,
780
1028
  iid: 902,
781
1029
  title: "Inline-email MR",
782
- description: `@${GITLAB_BOT_NAME} please`,
1030
+ description: `@${getGitLabBotName()} please`,
783
1031
  state: "opened",
784
1032
  action: "open",
785
1033
  source_branch: "feat-inline",
@@ -816,7 +1064,7 @@ describe("identity resolution — MR handler", () => {
816
1064
  id: 903,
817
1065
  iid: 903,
818
1066
  title: "Ghost MR",
819
- description: `@${GITLAB_BOT_NAME} ping`,
1067
+ description: `@${getGitLabBotName()} ping`,
820
1068
  state: "opened",
821
1069
  action: "open",
822
1070
  source_branch: "feat-ghost",
@@ -850,7 +1098,7 @@ describe("identity resolution — MR handler", () => {
850
1098
  id: 910 + i,
851
1099
  iid: 910 + i,
852
1100
  title: `Ghost MR ${i}`,
853
- description: `@${GITLAB_BOT_NAME} ping`,
1101
+ description: `@${getGitLabBotName()} ping`,
854
1102
  state: "opened",
855
1103
  action: "open",
856
1104
  source_branch: `feat-g-${i}`,
@@ -879,7 +1127,7 @@ describe("identity resolution — MR handler", () => {
879
1127
  id: 920,
880
1128
  iid: 920,
881
1129
  title: "Empty email MR",
882
- description: `@${GITLAB_BOT_NAME} review`,
1130
+ description: `@${getGitLabBotName()} review`,
883
1131
  state: "opened",
884
1132
  action: "open",
885
1133
  source_branch: "feat-empty",
@@ -914,7 +1162,7 @@ describe("identity resolution — Issue handler", () => {
914
1162
  id: 950,
915
1163
  iid: 950,
916
1164
  title: "Ghost issue",
917
- description: `@${GITLAB_BOT_NAME} fix`,
1165
+ description: `@${getGitLabBotName()} fix`,
918
1166
  state: "opened",
919
1167
  action: "open",
920
1168
  url: "https://gitlab.com/group/project/-/issues/950",
@@ -942,7 +1190,7 @@ describe("identity resolution — Note handler", () => {
942
1190
  });
943
1191
 
944
1192
  test("unknown user WITHOUT email → unmapped entry tagged 'note'", async () => {
945
- const noteBody = `@${GITLAB_BOT_NAME} can you look at this comment that is somewhat longer than usual to verify the slice(0,100) truncation downstream`;
1193
+ const noteBody = `@${getGitLabBotName()} can you look at this comment that is somewhat longer than usual to verify the slice(0,100) truncation downstream`;
946
1194
  const event = makeNoteEvent({
947
1195
  user: { id: 31, name: "Note Ghost", username: "noteghost", avatar_url: "" },
948
1196
  object_attributes: {
@@ -976,7 +1224,7 @@ describe("identity resolution — Note handler", () => {
976
1224
  expect(meta).not.toBeNull();
977
1225
  expect(meta?.sampleEventType).toBe("note");
978
1226
  expect((meta?.sampleContext as string).length).toBeLessThanOrEqual(100);
979
- expect((meta?.sampleContext as string).startsWith(`@${GITLAB_BOT_NAME}`)).toBe(true);
1227
+ expect((meta?.sampleContext as string).startsWith(`@${getGitLabBotName()}`)).toBe(true);
980
1228
  expect(await getUnmappedCount("noteghost")).toBe(1);
981
1229
 
982
1230
  // The dead-coded `_requestedByUserId` is now wired: undefined for an
@@ -996,7 +1244,7 @@ describe("identity resolution — Note handler", () => {
996
1244
  user: { id: 32, name: "Known Commenter", username: "knowncommenter", avatar_url: "" },
997
1245
  object_attributes: {
998
1246
  id: 961,
999
- note: `@${GITLAB_BOT_NAME} please take a look`,
1247
+ note: `@${getGitLabBotName()} please take a look`,
1000
1248
  noteable_type: "MergeRequest",
1001
1249
  noteable_id: 101,
1002
1250
  url: "https://gitlab.com/group/project/-/merge_requests/2#note_961",
@@ -100,7 +100,7 @@ describe("findTaskByVcs", () => {
100
100
  agentId: "vcs-lead-001",
101
101
  });
102
102
 
103
- const found = await findTaskByVcs("findme/gh-repo", 100);
103
+ const found = await findTaskByVcs("findme/gh-repo", 100, "github");
104
104
  expect(found).not.toBeNull();
105
105
  expect(found?.vcsProvider).toBe("github");
106
106
  });
@@ -114,7 +114,7 @@ describe("findTaskByVcs", () => {
114
114
  agentId: "vcs-lead-001",
115
115
  });
116
116
 
117
- const found = await findTaskByVcs("findme/gl-project", 200);
117
+ const found = await findTaskByVcs("findme/gl-project", 200, "gitlab");
118
118
  expect(found).not.toBeNull();
119
119
  expect(found?.vcsProvider).toBe("gitlab");
120
120
  });
@@ -133,12 +133,12 @@ describe("findTaskByVcs", () => {
133
133
  await db.startTask(task.id);
134
134
  await db.completeTask(task.id, "Done");
135
135
 
136
- const found = await findTaskByVcs("findme/completed", 300);
136
+ const found = await findTaskByVcs("findme/completed", 300, "gitlab");
137
137
  expect(found).toBeNull();
138
138
  });
139
139
 
140
140
  test("returns null for non-existent repo/number", async () => {
141
- const found = await findTaskByVcs("nonexistent/repo", 9999);
141
+ const found = await findTaskByVcs("nonexistent/repo", 9999, "github");
142
142
  expect(found).toBeNull();
143
143
  });
144
144
  });
@@ -12,6 +12,8 @@
12
12
  */
13
13
  import { afterEach, beforeEach, describe, expect, test } from "bun:test";
14
14
  import { initAgentMail, isAgentMailEnabled, resetAgentMail } from "../agentmail/app";
15
+ import { validateConfigValue } from "../be/swarm-config-guard";
16
+ import { isBitbucketEnabled, resetBitbucket } from "../bitbucket/auth";
15
17
  import { initGitHub, isGitHubEnabled, resetGitHub } from "../github/app";
16
18
  import { isJiraEnabled, resetJira } from "../jira/app";
17
19
  import { isLinearEnabled, resetLinear } from "../linear/app";
@@ -28,6 +30,8 @@ const ENV_KEYS = [
28
30
  "LINEAR_ENABLED",
29
31
  "AGENTMAIL_DISABLE",
30
32
  "AGENTMAIL_WEBHOOK_SECRET",
33
+ "BITBUCKET_DISABLE",
34
+ "BITBUCKET_WEBHOOK_SECRET",
31
35
  ] as const;
32
36
  const originalEnv = new Map(ENV_KEYS.map((key) => [key, process.env[key]]));
33
37
 
@@ -45,6 +49,7 @@ afterEach(() => {
45
49
  resetAgentMail();
46
50
  resetJira();
47
51
  resetLinear();
52
+ resetBitbucket();
48
53
  for (const key of ENV_KEYS) {
49
54
  const value = originalEnv.get(key);
50
55
  if (value === undefined) delete process.env[key];
@@ -185,6 +190,37 @@ describe("AgentMail AGENTMAIL_DISABLE", () => {
185
190
  });
186
191
  });
187
192
 
193
+ describe("Bitbucket BITBUCKET_DISABLE", () => {
194
+ test("every truthy spelling disables the integration", () => {
195
+ process.env.BITBUCKET_WEBHOOK_SECRET = "secret";
196
+ for (const spelling of TRUTHY_SPELLINGS) {
197
+ process.env.BITBUCKET_DISABLE = spelling;
198
+ expect(isBitbucketEnabled()).toBe(false);
199
+ }
200
+ });
201
+
202
+ test("non-disabling values keep the integration enabled", () => {
203
+ process.env.BITBUCKET_WEBHOOK_SECRET = "secret";
204
+ for (const value of NON_DISABLING) {
205
+ if (value === undefined) delete process.env.BITBUCKET_DISABLE;
206
+ else process.env.BITBUCKET_DISABLE = value;
207
+ expect(isBitbucketEnabled()).toBe(true);
208
+ }
209
+ });
210
+
211
+ test("stays disabled without a webhook secret", () => {
212
+ expect(isBitbucketEnabled()).toBe(false);
213
+ });
214
+
215
+ test("the config guard validates BITBUCKET_DISABLE as a boolean like GITLAB_DISABLE", () => {
216
+ for (const key of ["BITBUCKET_DISABLE", "GITLAB_DISABLE"]) {
217
+ expect(validateConfigValue(key, "true")).toBeNull();
218
+ expect(validateConfigValue(key, "false")).toBeNull();
219
+ expect(validateConfigValue(key, "maybe")).not.toBeNull();
220
+ }
221
+ });
222
+ });
223
+
188
224
  describe("Slack SLACK_DISABLE", () => {
189
225
  test("every truthy spelling reports disabled", () => {
190
226
  for (const spelling of TRUTHY_SPELLINGS) {
@@ -52,7 +52,7 @@ describe("script MCP tools", () => {
52
52
  "script-upsert":
53
53
  'Typecheck and persist a TypeScript script to the swarm catalog under your agent scope (or global if you\'re a lead). Import `ScriptContext` from "swarm-sdk" for a real context type. Other agents and workflow nodes will be able to find and run it. For local-only scripts, use code-mode `save`.',
54
54
  "script-delete":
55
- "Remove a swarm-shared script from the catalog. Versions table preserves history.",
55
+ "Permanently remove a swarm-shared script and all its versions from the catalog. This cannot be undone.",
56
56
  "script-query-types":
57
57
  // Updated for the frozen SwarmToolResult contract refactor: `name` is
58
58
  // now optional — omit it to get the swarm-wide type surface instead
@@ -34,6 +34,7 @@ const ENV_KEYS = [
34
34
  "EMBEDDING_MODEL",
35
35
  "GITHUB_TOKEN",
36
36
  "GITLAB_TOKEN",
37
+ "BITBUCKET_TOKEN",
37
38
  "SLACK_BOT_TOKEN",
38
39
  "SLACK_APP_TOKEN",
39
40
  "SLACK_MODE",
@@ -380,6 +381,17 @@ describe("onboarding state", () => {
380
381
  expect(body.state.steps.integrations).toMatchObject({ status: "done", method: "github" });
381
382
  });
382
383
 
384
+ test("GET derives the Bitbucket integration from BITBUCKET_TOKEN", async () => {
385
+ process.env.BITBUCKET_TOKEN = "example-bitbucket-token";
386
+ const body = (await (await request("/api/onboarding")).json()) as {
387
+ state: OnboardingState;
388
+ signals: { integrations: Record<string, boolean> };
389
+ };
390
+
391
+ expect(body.signals.integrations.bitbucket).toBe(true);
392
+ expect(body.state.steps.integrations).toMatchObject({ status: "done", method: "bitbucket" });
393
+ });
394
+
383
395
  test("the selected first task completes onboarding after that task completes", async () => {
384
396
  // Onboarding starts before the first task exists (the task alone would mark an existing install).
385
397
  await request("/api/onboarding");
@@ -340,8 +340,14 @@ describe("bounded codemode", () => {
340
340
  undefined,
341
341
  fakeCtx().ctx,
342
342
  );
343
- expect(Date.now() - started).toBeLessThan(5_000);
344
- expect(textOf(result)).toContain("Codemode worker limit reached");
343
+ const elapsed = Date.now() - started;
344
+ expect(elapsed).toBeGreaterThanOrEqual(250);
345
+ expect(elapsed).toBeLessThan(5_000);
346
+ // pi 1.4 queues the script for a worker; our deadline ends the wait.
347
+ expect(textOf(result)).toContain(
348
+ "Script aborted: Codemode script exceeded its 300 ms deadline",
349
+ );
350
+ expect(textOf(result)).toContain("The script did not start.");
345
351
  } finally {
346
352
  holders.abort(new Error("test done"));
347
353
  await Promise.all(holding);
@@ -0,0 +1,57 @@
1
+ import { expect, test } from "bun:test";
2
+ import { mkdtemp, rm } from "node:fs/promises";
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
+ test(
8
+ "preload clears inherited worker configuration before test files load",
9
+ async () => {
10
+ const directory = await mkdtemp(join(tmpdir(), "swarm-preload-env-"));
11
+ const fixture = join(directory, "worker-env.test.ts");
12
+ const keys = [
13
+ "SLACK_RENDER_V2",
14
+ "SLACK_STATUS_ICON_COMPLETED",
15
+ "SLACK_FUTURE_SETTING",
16
+ "COMB_ENABLED",
17
+ "APP_URL",
18
+ "AGENT_FS_API_URL",
19
+ "AGENT_FS_LIVE_URL",
20
+ "AGENT_FS_DEFAULT_ORG_ID",
21
+ "AGENT_FS_DEFAULT_DRIVE_ID",
22
+ "SWARM_ORG_NAME",
23
+ "SWARM_ORG_WEBSITE",
24
+ ];
25
+ try {
26
+ await Bun.write(
27
+ fixture,
28
+ `import { expect, test } from "bun:test";
29
+ const inherited = ${JSON.stringify(keys)}.filter(key => process.env[key] !== undefined);
30
+ test("worker defaults are cleared before module evaluation", () => {
31
+ expect(inherited).toEqual([]);
32
+ expect(process.env.PRELOAD_ENV_CONTROL).toBe("preserved");
33
+ process.env.SLACK_STATUS_ICON_COMPLETED = "test-icon";
34
+ });
35
+ test("suite configuration survives between tests", () => {
36
+ expect(process.env.SLACK_STATUS_ICON_COMPLETED).toBe("test-icon");
37
+ });
38
+ `,
39
+ );
40
+ const result = expectChildOk(
41
+ await runChild([process.execPath, "test", fixture], {
42
+ cwd: join(import.meta.dir, "../.."),
43
+ env: {
44
+ ...process.env,
45
+ ...Object.fromEntries(keys.map((key) => [key, "inherited-worker-setting"])),
46
+ PRELOAD_ENV_CONTROL: "preserved",
47
+ },
48
+ }),
49
+ "test runner with inherited worker env",
50
+ );
51
+ expect(result.stderr).toContain("2 pass");
52
+ } finally {
53
+ await rm(directory, { recursive: true, force: true });
54
+ }
55
+ },
56
+ CHILD_PROCESS_TEST_BUDGET_MS,
57
+ );