@desplega.ai/agent-swarm 2.4.5 → 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 (272) hide show
  1. package/README.md +4 -2
  2. package/dist/{acp-adapter-2wdkktv6.js → acp-adapter-11gp67nr.js} +8 -6
  3. package/dist/{actions-c40ebcm4.js → actions-8p2kze8j.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-0qp0a2gk.js} +3 -3
  7. package/dist/{assistant-sj1njh23.js → assistant-jedd3ktm.js} +24 -22
  8. package/dist/{azure-openai-responses-9dbmj346.js → azure-openai-responses-9kfwhmn7.js} +18 -13
  9. package/dist/{boot-reembed-qdkvxeem.js → boot-reembed-4xwq5tg0.js} +1 -1
  10. package/dist/{boot-reembed-b9c3az3c.js → boot-reembed-n4wq5dd2.js} +1 -1
  11. package/dist/{boot-scrub-sweep-m3wh8eks.js → boot-scrub-sweep-69h90xcq.js} +4 -4
  12. package/dist/{boot-seeds-j2273s54.js → boot-seeds-3vn0m9sg.js} +4 -4
  13. package/dist/{claude-adapter-m7q0a085.js → claude-adapter-221mvad5.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-8thjsk5e.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-dk44dxec.js → cli-0y6f0ckq.js} +1 -1
  20. package/dist/{cli-kpw04va0.js → cli-0z3vjrpm.js} +1 -1
  21. package/dist/{cli-94gwyg7m.js → cli-179xcyky.js} +9 -9
  22. package/dist/{cli-3r8samv3.js → cli-1f0gp9vz.js} +3 -2
  23. package/dist/{cli-4zfqgjc5.js → cli-1pr672hz.js} +2 -2
  24. package/dist/{cli-0bqteszy.js → cli-2nf3b08t.js} +328 -25
  25. package/dist/{cli-43x64hv3.js → cli-36j4jnk3.js} +4 -4
  26. package/dist/{cli-k8g16gz3.js → cli-3qv8naq8.js} +115 -119
  27. package/dist/cli-3zwye9sn.js +1193 -0
  28. package/dist/{cli-60cgh1rv.js → cli-459fk97x.js} +7 -7
  29. package/dist/{cli-v8fjakwf.js → cli-4w24f7z3.js} +1 -1
  30. package/dist/{cli-n72rg53p.js → cli-5kzk3472.js} +812 -418
  31. package/dist/{cli-h7gc0b4r.js → cli-5m84zd2k.js} +55 -18
  32. package/dist/{cli-j99azg3g.js → cli-5rvg1ddk.js} +5 -5
  33. package/dist/{cli-dvkmp9bt.js → cli-6ctwrf1z.js} +20 -10
  34. package/dist/{cli-848hxdwj.js → cli-6rg2jn2y.js} +2 -1
  35. package/dist/{cli-fv5hepyy.js → cli-7bwtc6yx.js} +3 -3
  36. package/dist/{cli-ms85309d.js → cli-7g40g8hq.js} +11 -11
  37. package/dist/{cli-xpa64rek.js → cli-92mqenkc.js} +2362 -159
  38. package/dist/{cli-018r4jq6.js → cli-9ce2xw98.js} +8 -8
  39. package/dist/{cli-1syn2xy1.js → cli-bgdtv62r.js} +32 -18
  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-wt0jf9hm.js → cli-fcc5qe3f.js} +553 -193
  44. package/dist/{cli-ftc1g5tj.js → cli-fes51ya2.js} +3 -3
  45. package/dist/{cli-3a45zcgm.js → cli-ftmq4nvk.js} +2 -2
  46. package/dist/{cli-37j04k05.js → cli-fy1jyyym.js} +13 -13
  47. package/dist/{cli-s95dknxh.js → cli-g1cw7xtt.js} +1 -1
  48. package/dist/{cli-74c1wpm0.js → cli-gpv60278.js} +1 -1
  49. package/dist/{cli-pemywhfq.js → cli-ha9rmwgq.js} +2 -1
  50. package/dist/{cli-g35m2xwm.js → cli-j1eghqfm.js} +2 -2
  51. package/dist/{cli-qfxc84gh.js → cli-jpxsrxcb.js} +1 -1
  52. package/dist/{cli-ejs3m1sx.js → cli-kxpfx2xr.js} +1 -1
  53. package/dist/{cli-p14m2cnp.js → cli-mbfj11fq.js} +1 -1
  54. package/dist/{cli-pnp2j5zg.js → cli-mmhj8vd7.js} +2 -2
  55. package/dist/{cli-xf234ws8.js → cli-n4c8e3s5.js} +2 -0
  56. package/dist/{cli-f01h25yt.js → cli-n6kmqngw.js} +2 -2
  57. package/dist/{cli-v0yrfmdh.js → cli-nnvjb17s.js} +3 -3
  58. package/dist/{cli-1dmcq2eh.js → cli-nzfnt2gs.js} +1 -1
  59. package/dist/{cli-625tb7aq.js → cli-p859hcc8.js} +2 -2
  60. package/dist/{cli-smvcw9z5.js → cli-pasyvjmm.js} +1 -1
  61. package/dist/cli-pkaqk57w.js +128 -0
  62. package/dist/{cli-8nyhf7za.js → cli-qqzmjxkf.js} +129 -105
  63. package/dist/{cli-9yqgm9nd.js → cli-r25qpaxt.js} +161 -63
  64. package/dist/{cli-eww120g8.js → cli-rh5smt8t.js} +18 -4
  65. package/dist/{cli-qwkcwjdn.js → cli-rycdaj0a.js} +1 -1
  66. package/dist/{cli-a92r1127.js → cli-s4sntwt4.js} +2 -2
  67. package/dist/{cli-be2161h1.js → cli-segs7hx9.js} +6 -6
  68. package/dist/{cli-36ckw0wx.js → cli-stxdrqs2.js} +2 -2
  69. package/dist/{cli-z7d29rw9.js → cli-sy8f9705.js} +4 -4
  70. package/dist/{cli-1m17dc9d.js → cli-ta8bd7th.js} +2 -2
  71. package/dist/{cli-st8aw1m6.js → cli-we0ra057.js} +4 -4
  72. package/dist/{cli-7c9qs530.js → cli-x21dqn18.js} +5 -5
  73. package/dist/{cli-1sp861s4.js → cli-x6vf4sad.js} +11 -11
  74. package/dist/{cli-d2mb5s8a.js → cli-x77gen2p.js} +680 -440
  75. package/dist/{cli-tw4p90jb.js → cli-xawhpgc9.js} +5 -5
  76. package/dist/{cli-f0g3pgvf.js → cli-xd4eqtr2.js} +2 -2
  77. package/dist/{cli-1f3qzw6y.js → cli-xt768r5m.js} +2 -2
  78. package/dist/{cli-cxxe61f6.js → cli-y4x8ww6s.js} +2 -2
  79. package/dist/{cli-bnes4z6b.js → cli-y9jvxfhk.js} +3 -3
  80. package/dist/{cli-qwhy2qma.js → cli-z63a269p.js} +2 -2
  81. package/dist/{cli-sm8rses9.js → cli-zfdp01hn.js} +8 -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-tcg1q3jk.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-xfx4wd9x.js} +7 -5
  87. package/dist/{commands-t7rz9146.js → commands-ew83nj70.js} +2 -2
  88. package/dist/{controller-m2jasma5.js → controller-gnq1k1mf.js} +4 -4
  89. package/dist/{controllers-connect-9ftpex99.js → controllers-connect-tw1dan9r.js} +25 -23
  90. package/dist/{cursor-adapter-mjv79e4z.js → cursor-adapter-yq5xgfcg.js} +1 -1
  91. package/dist/{db-6sfrn9g3.js → db-7gbc53fd.js} +14 -2
  92. package/dist/{db-rn673r06.js → db-t86xrnes.js} +3 -3
  93. package/dist/{devin-adapter-zgqadvnm.js → devin-adapter-4ycy4vrf.js} +1 -1
  94. package/dist/{dispatcher-c155xz0p.js → dispatcher-64bdyhps.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-e9nw24zv.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-v82srhrx.js} +9 -7
  106. package/dist/handlers-c9ajzhbn.js +41 -0
  107. package/dist/{heartbeat-dyg4npkp.js → heartbeat-2vqs1319.js} +25 -23
  108. package/dist/{hook-x26b31bg.js → hook-01nk13wx.js} +7 -5
  109. package/dist/{hook-drspbwpe.js → hook-jzpheegg.js} +9 -7
  110. package/dist/{http-pk972xyw.js → http-fc1k6ec8.js} +544 -76
  111. package/dist/index-07ef7nkm.js +42 -0
  112. package/dist/{index-g6tbb6ve.js → index-31ceyf42.js} +1 -1
  113. package/dist/index-7yts8n9s.js +43 -0
  114. package/dist/index-q3t40ryv.js +46 -0
  115. package/dist/{index-arytgj5q.js → index-sepja052.js} +1 -1
  116. package/dist/{index-jwxkkex2.js → index-zdycv3ja.js} +13 -11
  117. package/dist/{index-g216q6jr.js → index-zsyx51ap.js} +1 -4
  118. package/dist/{keepalive-7yrng7px.js → keepalive-5nh2rhmr.js} +4 -4
  119. package/dist/lead-aayq64jn.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-hjhzdt3k.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-y3n1knmd.js} +3 -3
  125. package/dist/{onboard-0a9y5yrc.js → onboard-1zq3m7db.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-h9yyza47.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-nevhat8h.js} +3 -3
  136. package/dist/{rbac-roles-nxh5ahwk.js → rbac-roles-4y6ngvzq.js} +1 -1
  137. package/dist/{rbac-roles-hey3yv9d.js → rbac-roles-exbg8xb6.js} +2 -2
  138. package/dist/{render-v2-rr3p2atg.js → render-v2-cfsh13z3.js} +3 -3
  139. package/dist/{resume-2efkf1s1.js → resume-5s2t0dbs.js} +10 -8
  140. package/dist/{runtime-xe3aqy4x.js → runtime-bt5pkpm1.js} +1 -1
  141. package/dist/{session-access-s2mttxpw.js → session-access-dnbyp3cn.js} +6 -4
  142. package/dist/{setup-vypma5yt.js → setup-1wj1y77j.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-7mg6216g.js} +25 -23
  146. package/dist/worker-nwb789mg.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/handlers.ts +5 -5
  174. package/src/http/assets.ts +14 -2
  175. package/src/http/core.ts +12 -0
  176. package/src/http/index.ts +4 -0
  177. package/src/http/onboarding.ts +1 -0
  178. package/src/http/sessions.ts +366 -12
  179. package/src/http/skills.ts +48 -1
  180. package/src/http/tasks.ts +1 -1
  181. package/src/http/users.ts +1 -0
  182. package/src/http/webhooks.ts +141 -0
  183. package/src/integrations/chat-sdk/adapters.ts +19 -3
  184. package/src/integrations/chat-sdk/facts.ts +30 -12
  185. package/src/integrations/chat-sdk/outbound.ts +38 -1
  186. package/src/providers/acp-targets.ts +1 -1
  187. package/src/providers/amp-adapter.ts +1 -1
  188. package/src/providers/pi-mono-adapter.ts +5 -53
  189. package/src/scripts-runtime/types/swarm-extension.d.ts +6 -4
  190. package/src/slack/handlers.test.ts +158 -0
  191. package/src/slack/handlers.ts +19 -28
  192. package/src/tasks/context-key.ts +61 -1
  193. package/src/tests/bitbucket-auth.test.ts +156 -0
  194. package/src/tests/bitbucket-handlers.test.ts +425 -0
  195. package/src/tests/bitbucket-webhook.test.ts +220 -0
  196. package/src/tests/chat-sdk-inbound.test.ts +39 -0
  197. package/src/tests/chat-sdk-telegram-markdown.test.ts +92 -0
  198. package/src/tests/context-key.test.ts +50 -0
  199. package/src/tests/core-auth.test.ts +5 -0
  200. package/src/tests/db-task-read-contract.test.ts +7 -0
  201. package/src/tests/db-task-write-contract.test.ts +6 -0
  202. package/src/tests/docs-index-footprint.test.js +3 -3
  203. package/src/tests/fixtures/bitbucket/pullrequest-comment-created-inline.json +174 -0
  204. package/src/tests/fixtures/bitbucket/pullrequest-comment-created.json +170 -0
  205. package/src/tests/fixtures/bitbucket/pullrequest-created-mention.json +132 -0
  206. package/src/tests/fixtures/bitbucket/pullrequest-created-reviewer.json +140 -0
  207. package/src/tests/fixtures/bitbucket/pullrequest-fulfilled.json +134 -0
  208. package/src/tests/fixtures/bitbucket/pullrequest-rejected.json +133 -0
  209. package/src/tests/generate-mcp-docs.test.ts +101 -0
  210. package/src/tests/gitlab-handlers.test.ts +3 -3
  211. package/src/tests/gitlab-vcs-db.test.ts +4 -4
  212. package/src/tests/integration-disable-flags.test.ts +36 -0
  213. package/src/tests/mcp-tools.test.ts +1 -1
  214. package/src/tests/onboarding.test.ts +12 -0
  215. package/src/tests/pi-codemode.test.ts +8 -2
  216. package/src/tests/preload-env.test.ts +57 -0
  217. package/src/tests/preload.ts +21 -0
  218. package/src/tests/prompt-template-remaining.test.ts +38 -0
  219. package/src/tests/run-root-coverage.test.ts +39 -0
  220. package/src/tests/runner-bitbucket-vcs.test.ts +191 -0
  221. package/src/tests/runner-skills-refresh.test.ts +58 -0
  222. package/src/tests/secret-scrubber.test.ts +15 -0
  223. package/src/tests/sessions-move.test.ts +576 -0
  224. package/src/tests/sessions-tree.test.ts +493 -0
  225. package/src/tests/skill-files-http.test.ts +50 -0
  226. package/src/tests/skills-bundle.test.ts +99 -0
  227. package/src/tests/swarm-config-export-to-env.test.ts +2 -1
  228. package/src/tests/vcs-bitbucket.test.ts +91 -0
  229. package/src/tests/vcs-provider-isolation.test.ts +123 -0
  230. package/src/tests/vcs-provider.test.ts +16 -1
  231. package/src/tests/vcs-tracking.test.ts +3 -3
  232. package/src/tests/workflow-foreach-bodies.test.ts +637 -0
  233. package/src/tests/workflow-foreach-concurrency.test.ts +342 -0
  234. package/src/tests/workflow-foreach-retry.test.ts +537 -0
  235. package/src/tests/workflow-foreach.test.ts +44 -23
  236. package/src/tests/workflow-step-claims.test.ts +6 -6
  237. package/src/tests/workflow-tla-races.test.ts +2 -2
  238. package/src/tools/script-delete.ts +1 -1
  239. package/src/tools/workflows/create-workflow.ts +5 -3
  240. package/src/types.ts +3 -2
  241. package/src/utils/config-env-export.ts +1 -0
  242. package/src/utils/secret-scrubber.ts +4 -0
  243. package/src/utils/skills-refresh.ts +83 -43
  244. package/src/vcs/bitbucket.ts +58 -0
  245. package/src/vcs/index.ts +2 -0
  246. package/src/vcs/types.ts +2 -2
  247. package/src/workflows/approval-resolution.ts +4 -1
  248. package/src/workflows/checkpoint.ts +9 -1
  249. package/src/workflows/constants.ts +3 -0
  250. package/src/workflows/definition.ts +48 -17
  251. package/src/workflows/engine.ts +25 -15
  252. package/src/workflows/executors/foreach.ts +45 -99
  253. package/src/workflows/foreach-body.ts +146 -0
  254. package/src/workflows/foreach-driver.ts +552 -0
  255. package/src/workflows/foreach-join.ts +206 -7
  256. package/src/workflows/node-timeout.ts +21 -0
  257. package/src/workflows/recovery.ts +68 -3
  258. package/src/workflows/resume.ts +221 -43
  259. package/src/workflows/retry-poller.ts +39 -24
  260. package/src/workflows/step-input.ts +59 -0
  261. package/src/workflows/task-step-routing.ts +114 -56
  262. package/templates/skills/slack-interaction/content.md +5 -0
  263. package/templates/skills/work-on-task/SKILL.md +1 -1
  264. package/templates/skills/work-on-task/content.md +1 -1
  265. package/dist/cli-9hstj09h.js +0 -2657
  266. package/dist/cli-yw91rnmw.js +0 -13
  267. package/dist/handlers-7gw64dxw.js +0 -39
  268. package/dist/index-bgdyvcnv.js +0 -40
  269. package/dist/index-gabbcdn2.js +0 -43
  270. package/dist/index-tfb26zhy.js +0 -41
  271. package/dist/lead-7e02z839.js +0 -77
  272. package/dist/worker-5snytpbd.js +0 -77
@@ -0,0 +1,91 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { bitbucketFullName, isBitbucketUrl, parseBitbucketRepoUrl } from "../vcs/bitbucket";
3
+ import { detectVcsProvider } from "../vcs/index";
4
+
5
+ describe("Bitbucket Cloud URL helpers", () => {
6
+ test("isBitbucketUrl accepts bitbucket.org remotes only", () => {
7
+ for (const url of [
8
+ "https://bitbucket.org/ws/repo",
9
+ "https://BitBucket.org/ws/repo",
10
+ "https://x-token-auth@bitbucket.org/ws/repo.git",
11
+ "git@bitbucket.org:ws/repo.git",
12
+ "ssh://git@bitbucket.org/ws/repo.git",
13
+ ]) {
14
+ expect(isBitbucketUrl(url)).toBe(true);
15
+ }
16
+ for (const url of [
17
+ "https://github.com/ws/repo",
18
+ "https://notbitbucket.org/ws/repo",
19
+ "https://bitbucket.org.evil.com/ws/repo",
20
+ "https://example.com/bitbucket.org.html",
21
+ "https://gitlab.com/bitbucket.org/repo.git",
22
+ "git@gitlab.com:bitbucket.org/repo.git",
23
+ "https://github.com/acme/bitbucket.org",
24
+ "ws/repo",
25
+ ]) {
26
+ expect(isBitbucketUrl(url)).toBe(false);
27
+ }
28
+ });
29
+
30
+ test("parses https remotes with and without user@ and .git", () => {
31
+ const expected = { workspace: "my-ws", repoSlug: "my-repo" };
32
+ for (const url of [
33
+ "https://bitbucket.org/my-ws/my-repo",
34
+ "https://bitbucket.org/my-ws/my-repo/",
35
+ "https://bitbucket.org/my-ws/my-repo.git",
36
+ "https://alice@bitbucket.org/my-ws/my-repo.git",
37
+ "https://x-token-auth:tok@bitbucket.org/my-ws/my-repo.git",
38
+ " https://bitbucket.org/my-ws/my-repo.git ",
39
+ "https://bitbucket.org/my-ws/my-repo/pull-requests/7",
40
+ ]) {
41
+ expect(parseBitbucketRepoUrl(url)).toEqual(expected);
42
+ }
43
+ });
44
+
45
+ test("parses ssh remotes", () => {
46
+ expect(parseBitbucketRepoUrl("git@bitbucket.org:my-ws/my-repo.git")).toEqual({
47
+ workspace: "my-ws",
48
+ repoSlug: "my-repo",
49
+ });
50
+ expect(parseBitbucketRepoUrl("git@bitbucket.org:my-ws/my-repo")).toEqual({
51
+ workspace: "my-ws",
52
+ repoSlug: "my-repo",
53
+ });
54
+ expect(parseBitbucketRepoUrl("ssh://git@bitbucket.org/my-ws/my-repo.git")).toEqual({
55
+ workspace: "my-ws",
56
+ repoSlug: "my-repo",
57
+ });
58
+ });
59
+
60
+ test("keeps dots inside repository slugs", () => {
61
+ expect(parseBitbucketRepoUrl("https://bitbucket.org/ws/my.site.git")).toEqual({
62
+ workspace: "ws",
63
+ repoSlug: "my.site",
64
+ });
65
+ expect(bitbucketFullName("git@bitbucket.org:ws/my.site.git")).toBe("ws/my.site");
66
+ });
67
+
68
+ test("returns null for non-Bitbucket or incomplete URLs", () => {
69
+ expect(parseBitbucketRepoUrl("https://github.com/ws/repo")).toBeNull();
70
+ expect(parseBitbucketRepoUrl("https://bitbucket.org/ws")).toBeNull();
71
+ expect(parseBitbucketRepoUrl("https://bitbucket.org/")).toBeNull();
72
+ expect(parseBitbucketRepoUrl("git@bitbucket.org:ws.git")).toBeNull();
73
+ expect(parseBitbucketRepoUrl("not a url bitbucket.org")).toBeNull();
74
+ expect(bitbucketFullName("https://gitlab.com/group/project")).toBeNull();
75
+ });
76
+
77
+ test("bitbucketFullName yields workspace/repo_slug", () => {
78
+ expect(bitbucketFullName("https://bitbucket.org/ws/repo.git")).toBe("ws/repo");
79
+ expect(bitbucketFullName("git@bitbucket.org:ws/repo.git")).toBe("ws/repo");
80
+ });
81
+
82
+ test("detectVcsProvider checks Bitbucket before the GitHub owner/repo fallback", () => {
83
+ expect(detectVcsProvider("https://bitbucket.org/ws/repo")).toBe("bitbucket");
84
+ expect(detectVcsProvider("ws/repo")).toBe("github");
85
+ });
86
+
87
+ test("a bitbucket.org path segment on another host keeps that host's provider", () => {
88
+ expect(detectVcsProvider("https://gitlab.com/bitbucket.org/repo.git")).toBe("gitlab");
89
+ expect(parseBitbucketRepoUrl("https://gitlab.com/bitbucket.org/repo.git")).toBeNull();
90
+ });
91
+ });
@@ -0,0 +1,123 @@
1
+ import { afterAll, beforeAll, expect, test } from "bun:test";
2
+ import { unlink } from "node:fs/promises";
3
+ import {
4
+ closeDb,
5
+ createAgent,
6
+ createTaskExtended,
7
+ findTaskByVcs,
8
+ getTaskById,
9
+ initDb,
10
+ } from "../be/db";
11
+ import { handlePullRequestCreated, resetBitbucketDedup } from "../bitbucket/handlers";
12
+ import { handlePullRequest } from "../github/handlers";
13
+ import { GITHUB_BOT_NAME } from "../github/mentions";
14
+ import type { PullRequestEvent } from "../github/types";
15
+ import { handleMergeRequest } from "../gitlab/handlers";
16
+ import type { MergeRequestEvent } from "../gitlab/types";
17
+ import mentionSample from "./fixtures/bitbucket/pullrequest-created-mention.json";
18
+
19
+ // The same `owner/repo` + number can exist on GitHub, GitLab and Bitbucket at
20
+ // once. Each host's webhook must act only on its own tasks.
21
+ const DB = "./test-vcs-provider-isolation.sqlite";
22
+ beforeAll(async () => {
23
+ initDb(DB);
24
+ await createAgent({
25
+ id: crypto.randomUUID(),
26
+ name: "Provider isolation lead",
27
+ status: "idle",
28
+ isLead: true,
29
+ });
30
+ process.env.BITBUCKET_BOT_ACCOUNT_ID = "557058:00000000-0000-4000-8000-0000000000b0";
31
+ });
32
+ afterAll(async () => {
33
+ closeDb();
34
+ delete process.env.BITBUCKET_BOT_ACCOUNT_ID;
35
+ resetBitbucketDedup();
36
+ for (const suffix of ["", "-wal", "-shm"]) await unlink(DB + suffix).catch(() => {});
37
+ });
38
+
39
+ async function createBitbucketTask(id: number) {
40
+ const event = structuredClone(mentionSample);
41
+ event.pullrequest.id = id;
42
+ const result = await handlePullRequestCreated(event);
43
+ expect(result.created).toBe(true);
44
+ return (await getTaskById(result.taskId!))!;
45
+ }
46
+
47
+ function gitlabMerge(iid: number): MergeRequestEvent {
48
+ return {
49
+ object_kind: "merge_request",
50
+ event_type: "merge_request",
51
+ user: { id: 1, name: "Test user", username: "review2339", avatar_url: "" },
52
+ project: {
53
+ id: 1,
54
+ name: "widgets",
55
+ path_with_namespace: "acme-ws/widgets",
56
+ web_url: "https://gitlab.com/acme-ws/widgets",
57
+ default_branch: "main",
58
+ },
59
+ object_attributes: {
60
+ id: 101,
61
+ iid,
62
+ title: "Unrelated GitLab MR",
63
+ description: "",
64
+ state: "merged",
65
+ action: "merge",
66
+ source_branch: "feature",
67
+ target_branch: "main",
68
+ url: `https://gitlab.com/acme-ws/widgets/-/merge_requests/${iid}`,
69
+ last_commit: null,
70
+ author_id: 1,
71
+ },
72
+ };
73
+ }
74
+
75
+ function githubEvent(number: number, action: string): PullRequestEvent {
76
+ return {
77
+ action,
78
+ pull_request: {
79
+ number,
80
+ title: "Unrelated GitHub PR",
81
+ body: "Please review",
82
+ html_url: `https://github.com/acme-ws/widgets/pull/${number}`,
83
+ user: { login: "review2339" },
84
+ head: { ref: "feature", sha: "abc123" },
85
+ base: { ref: "main" },
86
+ merged: false,
87
+ },
88
+ repository: { full_name: "acme-ws/widgets", html_url: "https://github.com/acme-ws/widgets" },
89
+ sender: { login: "review2339" },
90
+ assignee: { login: GITHUB_BOT_NAME, id: 2 },
91
+ requested_reviewer: { login: GITHUB_BOT_NAME, id: 2 },
92
+ };
93
+ }
94
+
95
+ test("GitLab merge must not cancel a real Bitbucket task", async () => {
96
+ const task = await createBitbucketTask(701);
97
+ await handleMergeRequest(gitlabMerge(701));
98
+ expect((await getTaskById(task.id))?.status).toBe(task.status);
99
+ });
100
+
101
+ test("GitHub unassignment must not cancel a real Bitbucket task", async () => {
102
+ const task = await createBitbucketTask(702);
103
+ await handlePullRequest(githubEvent(702, "unassigned"));
104
+ expect((await getTaskById(task.id))?.status).toBe(task.status);
105
+ });
106
+
107
+ test("Bitbucket task must not suppress an unrelated GitHub review request", async () => {
108
+ await createBitbucketTask(703);
109
+ const result = await handlePullRequest(githubEvent(703, "review_requested"));
110
+ expect(result.created).toBe(true);
111
+ });
112
+
113
+ test("GitHub lookup still matches a legacy task with no vcsProvider", async () => {
114
+ const legacy = await createTaskExtended("Legacy GitHub PR task", {
115
+ source: "github",
116
+ vcsRepo: "acme-ws/legacy",
117
+ vcsNumber: 704,
118
+ });
119
+ expect(legacy.vcsProvider).toBeUndefined();
120
+ expect((await findTaskByVcs("acme-ws/legacy", 704, "github"))?.id).toBe(legacy.id);
121
+ expect(await findTaskByVcs("acme-ws/legacy", 704, "bitbucket")).toBeNull();
122
+ expect(await findTaskByVcs("acme-ws/legacy", 704, "gitlab")).toBeNull();
123
+ });
@@ -20,8 +20,23 @@ describe("detectVcsProvider", () => {
20
20
  expect(detectVcsProvider("https://gitlab.internal/group/project")).toBe("gitlab");
21
21
  });
22
22
 
23
+ test("detects bitbucket.org URLs in https, ssh and .git forms", () => {
24
+ for (const url of [
25
+ "https://bitbucket.org/org/repo",
26
+ "https://bitbucket.org/org/repo.git",
27
+ "https://user@bitbucket.org/org/repo.git",
28
+ "git@bitbucket.org:org/repo.git",
29
+ "ssh://git@bitbucket.org/org/repo.git",
30
+ // A slug containing "github" or "gitlab" must not fall through to those hosts.
31
+ "https://bitbucket.org/org/github-mirror",
32
+ "git@bitbucket.org:gitlab-team/repo.git",
33
+ ]) {
34
+ expect(detectVcsProvider(url)).toBe("bitbucket");
35
+ }
36
+ });
37
+
23
38
  test("returns null for unrecognised URLs", () => {
24
- expect(detectVcsProvider("https://bitbucket.org/org/repo")).toBeNull();
25
39
  expect(detectVcsProvider("https://example.com")).toBeNull();
40
+ expect(detectVcsProvider("https://notbitbucket.org/org/repo")).toBeNull();
26
41
  });
27
42
  });
@@ -122,7 +122,7 @@ describe("updateTaskVcs", () => {
122
122
  });
123
123
 
124
124
  // Before update — no VCS fields, shouldn't be found
125
- const notFound = await findTaskByVcs("owner/findme", 99);
125
+ const notFound = await findTaskByVcs("owner/findme", 99, "github");
126
126
  expect(notFound).toBeNull();
127
127
 
128
128
  await updateTaskVcs(task.id, {
@@ -133,7 +133,7 @@ describe("updateTaskVcs", () => {
133
133
  });
134
134
 
135
135
  // After update — should be found (task is pending, not completed/failed)
136
- const found = await findTaskByVcs("owner/findme", 99);
136
+ const found = await findTaskByVcs("owner/findme", 99, "github");
137
137
  expect(found).not.toBeNull();
138
138
  expect(found!.id).toBe(task.id);
139
139
  });
@@ -171,7 +171,7 @@ describe("updateTaskVcs", () => {
171
171
  await startTask(task.id);
172
172
  await c.terminate(task.id);
173
173
 
174
- const found = await findTaskByVcs("owner/terminal", c.number);
174
+ const found = await findTaskByVcs("owner/terminal", c.number, "github");
175
175
  expect(found).toBeNull();
176
176
  }
177
177
  });