@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.
- package/README.md +4 -2
- package/dist/{acp-adapter-thq04adh.js → acp-adapter-11gp67nr.js} +8 -6
- package/dist/{actions-j6ywh6x5.js → actions-8p2kze8j.js} +24 -22
- package/dist/{amp-adapter-k8b5xmj8.js → amp-adapter-z4qvhacj.js} +2 -2
- package/dist/{anthropic-messages-g6pqmqk4.js → anthropic-messages-txs190dd.js} +17 -20
- package/dist/{app-rb7vc02s.js → app-0qp0a2gk.js} +3 -3
- package/dist/{assistant-ftta1zjf.js → assistant-jedd3ktm.js} +27 -25
- package/dist/{azure-openai-responses-9dbmj346.js → azure-openai-responses-9kfwhmn7.js} +18 -13
- package/dist/{boot-reembed-xns7h5h9.js → boot-reembed-4xwq5tg0.js} +1 -1
- package/dist/{boot-reembed-srp6v6af.js → boot-reembed-n4wq5dd2.js} +1 -1
- package/dist/{boot-scrub-sweep-3qqrh2mz.js → boot-scrub-sweep-69h90xcq.js} +4 -4
- package/dist/{boot-seeds-9ks8hce7.js → boot-seeds-3vn0m9sg.js} +4 -4
- package/dist/{claude-adapter-k5ksyres.js → claude-adapter-221mvad5.js} +8 -6
- package/dist/{claude-managed-adapter-h2y889ge.js → claude-managed-adapter-gt6pa35d.js} +2 -2
- package/dist/{claude-managed-setup-n5a5eqks.js → claude-managed-setup-c3hp36ph.js} +4 -4
- package/dist/{claude-sdk-session-2j3j4kwn.js → claude-sdk-session-8thjsk5e.js} +9729 -9628
- package/dist/{cli-b9fcxxev.js → cli-007cnhm1.js} +50 -4
- package/dist/{cli-j4rqdf2n.js → cli-03vpq45j.js} +2 -2
- package/dist/{cli-amyhm3gj.js → cli-0y6f0ckq.js} +1 -1
- package/dist/{cli-4dg55wsk.js → cli-0z3vjrpm.js} +1 -1
- package/dist/{cli-pccfmtb5.js → cli-179xcyky.js} +11 -11
- package/dist/{cli-3r8samv3.js → cli-1f0gp9vz.js} +3 -2
- package/dist/{cli-hj3debgw.js → cli-1pr672hz.js} +3 -3
- package/dist/{cli-0bqteszy.js → cli-2nf3b08t.js} +328 -25
- package/dist/{cli-t7cyef5g.js → cli-36j4jnk3.js} +4 -4
- package/dist/{cli-er83cjbn.js → cli-3qv8naq8.js} +527 -231
- package/dist/cli-3zwye9sn.js +1193 -0
- package/dist/{cli-60cgh1rv.js → cli-459fk97x.js} +7 -7
- package/dist/{cli-xj0re5tm.js → cli-4w24f7z3.js} +1 -1
- package/dist/{cli-v3yr2gbg.js → cli-5kzk3472.js} +2198 -968
- package/dist/{cli-h7gc0b4r.js → cli-5m84zd2k.js} +55 -18
- package/dist/{cli-w6nadkh4.js → cli-5rvg1ddk.js} +5 -5
- package/dist/{cli-dvkmp9bt.js → cli-6ctwrf1z.js} +20 -10
- package/dist/{cli-9qtjfd60.js → cli-6rg2jn2y.js} +3 -2
- package/dist/{cli-2v045xyf.js → cli-7bwtc6yx.js} +3 -3
- package/dist/{cli-3cgngyd8.js → cli-7g40g8hq.js} +35 -17
- package/dist/{cli-q6r7t299.js → cli-92mqenkc.js} +2379 -154
- package/dist/{cli-cynvc9fj.js → cli-9ce2xw98.js} +8 -8
- package/dist/{cli-gqvh8d8a.js → cli-bgdtv62r.js} +36 -22
- package/dist/{cli-wxbn3yfx.js → cli-cz5nmdxt.js} +2 -2
- package/dist/{cli-wt645v7q.js → cli-dabehxhp.js} +1 -1
- package/dist/{cli-vfha21c9.js → cli-e5bxnjps.js} +2 -2
- package/dist/{cli-p20r8w42.js → cli-fcc5qe3f.js} +763 -268
- package/dist/{cli-1ephgwnq.js → cli-fes51ya2.js} +3 -3
- package/dist/{cli-1d70crsy.js → cli-ftmq4nvk.js} +2 -2
- package/dist/{cli-qkhc5aqy.js → cli-fy1jyyym.js} +13 -13
- package/dist/{cli-s95dknxh.js → cli-g1cw7xtt.js} +1 -1
- package/dist/{cli-qje9k2nh.js → cli-g9ypd5en.js} +12 -2
- package/dist/{cli-74c1wpm0.js → cli-gpv60278.js} +1 -1
- package/dist/{cli-pemywhfq.js → cli-ha9rmwgq.js} +2 -1
- package/dist/{cli-bnz0r3kc.js → cli-j1eghqfm.js} +5 -5
- package/dist/{cli-2zqpkvtt.js → cli-jpxsrxcb.js} +1 -1
- package/dist/{cli-ejs3m1sx.js → cli-kxpfx2xr.js} +1 -1
- package/dist/{cli-37yb7307.js → cli-mbfj11fq.js} +1 -1
- package/dist/{cli-pnp2j5zg.js → cli-mmhj8vd7.js} +2 -2
- package/dist/{cli-xf234ws8.js → cli-n4c8e3s5.js} +2 -0
- package/dist/{cli-a0kcty51.js → cli-n6kmqngw.js} +2 -2
- package/dist/{cli-te6b5wvg.js → cli-nnvjb17s.js} +3 -3
- package/dist/{cli-1dmcq2eh.js → cli-nzfnt2gs.js} +1 -1
- package/dist/{cli-625tb7aq.js → cli-p859hcc8.js} +2 -2
- package/dist/{cli-smvcw9z5.js → cli-pasyvjmm.js} +1 -1
- package/dist/cli-pkaqk57w.js +128 -0
- package/dist/{cli-8nyhf7za.js → cli-qqzmjxkf.js} +129 -105
- package/dist/{cli-zrwpctp4.js → cli-r25qpaxt.js} +161 -63
- package/dist/{cli-nc35pxb7.js → cli-rh5smt8t.js} +19 -4
- package/dist/{cli-qwkcwjdn.js → cli-rycdaj0a.js} +1 -1
- package/dist/{cli-esxerzg7.js → cli-s4sntwt4.js} +2 -2
- package/dist/{cli-b8k7epj1.js → cli-segs7hx9.js} +8 -8
- package/dist/{cli-36ckw0wx.js → cli-stxdrqs2.js} +2 -2
- package/dist/{cli-vdm20wej.js → cli-sy8f9705.js} +4 -4
- package/dist/{cli-1m17dc9d.js → cli-ta8bd7th.js} +2 -2
- package/dist/{cli-2y91wnbh.js → cli-we0ra057.js} +222 -10
- package/dist/{cli-55qmbp6z.js → cli-x21dqn18.js} +5 -5
- package/dist/{cli-dqb4n629.js → cli-x6vf4sad.js} +11 -11
- package/dist/{cli-d2mb5s8a.js → cli-x77gen2p.js} +680 -440
- package/dist/{cli-vq97g1jq.js → cli-xawhpgc9.js} +95 -195
- package/dist/{cli-82d3nkc3.js → cli-xd4eqtr2.js} +2 -2
- package/dist/{cli-9fhkkpj5.js → cli-xt768r5m.js} +2 -2
- package/dist/{cli-zrk6jxv8.js → cli-y4x8ww6s.js} +2 -2
- package/dist/{cli-xc27fwzg.js → cli-y9jvxfhk.js} +3 -3
- package/dist/{cli-44159k28.js → cli-z63a269p.js} +2 -2
- package/dist/{cli-sm8rses9.js → cli-zfdp01hn.js} +8 -5
- package/dist/cli.js +31 -29
- package/dist/{cloudflare-workers-ai-system-one-ch2wxfcp.js → cloudflare-workers-ai-system-one-43v9qkjk.js} +7 -2
- package/dist/{codex-adapter-968xwfy7.js → codex-adapter-tcg1q3jk.js} +6 -4
- package/dist/{codex-login-m819c41r.js → codex-login-rn7c5gxt.js} +1 -1
- package/dist/{codex-session-runner-ht4z99fa.js → codex-session-runner-xfx4wd9x.js} +7 -5
- package/dist/{commands-pcymaeg9.js → commands-ew83nj70.js} +2 -2
- package/dist/{controller-fpjyk46r.js → controller-gnq1k1mf.js} +4 -4
- package/dist/{controllers-connect-mwzx1fh6.js → controllers-connect-tw1dan9r.js} +26 -24
- package/dist/{cursor-adapter-cp4s2e2f.js → cursor-adapter-yq5xgfcg.js} +1 -1
- package/dist/{db-xsjg9jqq.js → db-7gbc53fd.js} +14 -2
- package/dist/{db-sgd12bt9.js → db-t86xrnes.js} +3 -3
- package/dist/{devin-adapter-zgqadvnm.js → devin-adapter-4ycy4vrf.js} +1 -1
- package/dist/{dispatcher-x0jfw2c9.js → dispatcher-64bdyhps.js} +23 -21
- package/dist/{docker-fkgtr75t.js → docker-nfy2gc8t.js} +2 -2
- package/dist/{dsh-adapter-jrq7ma7h.js → dsh-adapter-tvhc4k2d.js} +1 -1
- package/dist/{e2b-16d6ch39.js → e2b-9gkdhm3c.js} +2 -2
- package/dist/{e2b-zn0b4w5w.js → e2b-cjtf8k1z.js} +2 -2
- package/dist/{easter-egg-3d-5cgynaja.js → easter-egg-3d-e4qv8dxj.js} +1 -1
- package/dist/{event-bus-9vpk8gmq.js → event-bus-3z78ep5n.js} +1 -1
- package/dist/{execute-t4e6m3rw.js → execute-0xpz8950.js} +85 -23
- package/dist/{external-channel-8njj4h3b.js → external-channel-e9nw24zv.js} +2 -2
- package/dist/{google-generative-ai-bmq8gkf3.js → google-generative-ai-mk9vcpe1.js} +15 -15
- package/dist/{google-vertex-b1rvp52s.js → google-vertex-q580kz8k.js} +20 -20
- package/dist/{grok-adapter-kkr97v2f.js → grok-adapter-v82srhrx.js} +9 -7
- package/dist/handlers-c9ajzhbn.js +41 -0
- package/dist/{heartbeat-wd03zhn0.js → heartbeat-2vqs1319.js} +26 -24
- package/dist/{hook-xzj2ts2p.js → hook-01nk13wx.js} +7 -5
- package/dist/{hook-f0vghj42.js → hook-jzpheegg.js} +9 -7
- package/dist/{http-4hwxv62p.js → http-fc1k6ec8.js} +859 -252
- package/dist/index-07ef7nkm.js +42 -0
- package/dist/{index-g6tbb6ve.js → index-31ceyf42.js} +1 -1
- package/dist/index-7yts8n9s.js +43 -0
- package/dist/{index-c4ykd0f1.js → index-dr94zbnh.js} +13348 -11611
- package/dist/index-q3t40ryv.js +46 -0
- package/dist/{index-ww2vznkp.js → index-sepja052.js} +1 -1
- package/dist/{index-97q4xn03.js → index-zdycv3ja.js} +135 -79
- package/dist/{index-g216q6jr.js → index-zsyx51ap.js} +1 -4
- package/dist/{keepalive-hrh7xnr9.js → keepalive-5nh2rhmr.js} +4 -4
- package/dist/lead-aayq64jn.js +79 -0
- package/dist/{llama-cpp-classify-vv43gswy.js → llama-cpp-classify-ee7vmd7q.js} +4 -1
- package/dist/{maintenance-e8wnwnqx.js → maintenance-hjhzdt3k.js} +1 -1
- package/dist/{mistral-conversations-1medfwv7.js → mistral-conversations-sd0xf1tn.js} +13 -13
- package/dist/{node-3v3f08f7.js → node-jzjj7r9x.js} +10 -9
- package/dist/{oauth-refresh-sweep-zm0yj4sj.js → oauth-refresh-sweep-y3n1knmd.js} +3 -3
- package/dist/{onboard-dtwcq603.js → onboard-1zq3m7db.js} +2 -2
- package/dist/{openai-codex-responses-de9pvs4d.js → openai-codex-responses-79s8xnbh.js} +29 -23
- package/dist/{openai-completions-mtqge3nf.js → openai-completions-tdbyts0x.js} +21 -20
- package/dist/{openai-decisions-ygk4kkxd.js → openai-decisions-bqhnjyfz.js} +2 -1
- package/dist/{openai-responses-dkyng71g.js → openai-responses-dm3t5vqp.js} +26 -17
- package/dist/{opencode-adapter-x4tqzhkh.js → opencode-adapter-e56086m1.js} +7 -5
- package/dist/{openrouter-images-wvv7camj.js → openrouter-images-ssttr1y4.js} +6 -3
- package/dist/{otel-impl-w3fbhpn0.js → otel-impl-h9yyza47.js} +2 -2
- package/dist/{pi-messages-nhh1pkz2.js → pi-messages-3nf8bq1a.js} +6 -4
- package/dist/{pi-mono-adapter-mcfkn9vf.js → pi-mono-adapter-tzps8f38.js} +9 -37
- package/dist/{pricing-refresh-j38dehfs.js → pricing-refresh-nevhat8h.js} +3 -3
- package/dist/{rbac-roles-ae8yttq4.js → rbac-roles-4y6ngvzq.js} +1 -1
- package/dist/{rbac-roles-ejxx9b35.js → rbac-roles-exbg8xb6.js} +2 -2
- package/dist/{registry-rxw10xzg.js → registry-ajhexe44.js} +1 -1
- package/dist/{render-v2-n16n0phe.js → render-v2-cfsh13z3.js} +3 -3
- package/dist/{resume-z0fgjc38.js → resume-5s2t0dbs.js} +10 -8
- package/dist/{runtime-xe3aqy4x.js → runtime-bt5pkpm1.js} +1 -1
- package/dist/{session-access-cv3mq6mc.js → session-access-dnbyp3cn.js} +6 -4
- package/dist/{setup-mafhj3yt.js → setup-1wj1y77j.js} +1 -1
- package/dist/{typesafe-system-one-2hgpayzy.js → typesafe-system-one-bre38hvs.js} +4 -2
- package/dist/{virtual-modules-4r3zkc20.js → virtual-modules-mnfbh5mr.js} +8 -2
- package/dist/{work-objects-8b86mgjp.js → work-objects-7mg6216g.js} +26 -24
- package/dist/worker-nwb789mg.js +79 -0
- package/dist/{x-y45mthwn.js → x-pbpezr0s.js} +2 -2
- package/openapi.json +793 -6
- package/package.json +11 -11
- package/src/apps/catalog.generated.json +206 -4
- package/src/apps/page-validator.ts +13 -1
- package/src/assets/key.ts +17 -0
- package/src/azure-devops/handlers.ts +1 -1
- package/src/be/chunking.ts +42 -9
- package/src/be/db/approvals.ts +18 -0
- package/src/be/db/session-moves.ts +164 -0
- package/src/be/db/session-tree.ts +384 -0
- package/src/be/db/tasks/draft-text.ts +15 -0
- package/src/be/db/tasks/read.ts +54 -5
- package/src/be/db/workflow-runs.ts +96 -0
- package/src/be/db.ts +85 -237
- package/src/be/memory/index-content.ts +36 -17
- package/src/be/memory/providers/openai-embedding.ts +115 -61
- package/src/be/memory/providers/sqlite-store.ts +69 -23
- package/src/be/memory/reranker.ts +31 -1
- package/src/be/memory/search-candidates.ts +47 -0
- package/src/be/memory/types.ts +14 -0
- package/src/be/onboarding.ts +3 -0
- package/src/be/scripts/typecheck.ts +2 -0
- package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +106 -54
- package/src/be/seed-scripts/index.ts +2 -2
- package/src/be/skill-sync.ts +43 -2
- package/src/be/swarm-config-guard.ts +25 -1
- package/src/be/task-attachment-store.ts +30 -0
- package/src/bitbucket/api.ts +81 -0
- package/src/bitbucket/auth.ts +64 -0
- package/src/bitbucket/handlers.ts +434 -0
- package/src/bitbucket/index.ts +28 -0
- package/src/bitbucket/templates.ts +134 -0
- package/src/bitbucket/types.ts +65 -0
- package/src/commands/runner.ts +55 -3
- package/src/e2b/env.ts +1 -0
- package/src/extensions/catalog.generated.json +4 -4
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/extensions/contract.ts +9 -2
- package/src/extensions/post-bridge.ts +1 -0
- package/src/fs/provider.ts +13 -1
- package/src/github/handlers.ts +6 -6
- package/src/gitlab/handlers.ts +5 -5
- package/src/heartbeat/heartbeat.ts +31 -11
- package/src/http/assets.ts +14 -2
- package/src/http/core.ts +12 -0
- package/src/http/fs.ts +54 -34
- package/src/http/index.ts +4 -0
- package/src/http/mcp-body.ts +37 -0
- package/src/http/mcp-user.ts +3 -5
- package/src/http/mcp.ts +6 -7
- package/src/http/memory.ts +26 -15
- package/src/http/onboarding.ts +1 -0
- package/src/http/page-proxy.ts +61 -5
- package/src/http/pages-public.ts +27 -44
- package/src/http/pages.ts +4 -4
- package/src/http/script-runs.ts +2 -0
- package/src/http/sessions.ts +366 -12
- package/src/http/skills.ts +48 -1
- package/src/http/tasks.ts +1 -1
- package/src/http/users.ts +13 -1
- package/src/http/webhooks.ts +141 -0
- package/src/integrations/chat-sdk/adapters.ts +24 -4
- package/src/integrations/chat-sdk/attachments.ts +90 -12
- package/src/integrations/chat-sdk/facts.ts +138 -0
- package/src/integrations/chat-sdk/inbound.ts +156 -8
- package/src/integrations/chat-sdk/limits.ts +24 -0
- package/src/integrations/chat-sdk/outbound-files.ts +186 -0
- package/src/integrations/chat-sdk/outbound.ts +272 -5
- package/src/integrations/chat-sdk/relay.ts +4 -1
- package/src/integrations/chat-sdk/router.ts +28 -2
- package/src/integrations/chat-sdk/steering.ts +9 -0
- package/src/integrations/chat-sdk/templates.ts +14 -0
- package/src/integrations/chat-sdk/thread-buffer.ts +4 -0
- package/src/mcp-limits.ts +20 -0
- package/src/pages/browser-sdk.ts +8 -3
- package/src/pages/feedback-overlay.ts +1 -1
- package/src/providers/acp-targets.ts +1 -1
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/codex-adapter.ts +106 -119
- package/src/providers/codex-agents-md.ts +35 -229
- package/src/providers/cursor-adapter.ts +1 -1
- package/src/providers/pi-mono-adapter.ts +5 -53
- package/src/realtime/transport.ts +1 -2
- package/src/script-workflows/workflow-ctx.ts +1 -0
- package/src/scripts-runtime/types/stdlib.d.ts +2 -0
- package/src/scripts-runtime/types/swarm-extension.d.ts +14 -4
- package/src/scripts-runtime/types/swarm-sdk.d.ts +2 -0
- package/src/server-user.ts +2 -0
- package/src/server.ts +2 -0
- package/src/slack/assistant.ts +2 -2
- package/src/slack/files.ts +2 -0
- package/src/slack/handlers.test.ts +158 -0
- package/src/slack/handlers.ts +23 -32
- package/src/slack/inbound-files.ts +39 -6
- package/src/slack/thread-buffer.ts +2 -1
- package/src/tasks/context-key.ts +61 -1
- package/src/tasks/task-terminal-effects.ts +21 -17
- package/src/tests/apps-card-grid.test.ts +197 -0
- package/src/tests/apps-catalog-additions.test.ts +120 -0
- package/src/tests/apps-tree.test.ts +129 -0
- package/src/tests/bitbucket-auth.test.ts +156 -0
- package/src/tests/bitbucket-handlers.test.ts +425 -0
- package/src/tests/bitbucket-webhook.test.ts +220 -0
- package/src/tests/chat-sdk-attachments-reply.test.ts +52 -0
- package/src/tests/chat-sdk-config.test.ts +11 -0
- package/src/tests/chat-sdk-facts.test.ts +23 -0
- package/src/tests/chat-sdk-inbound.test.ts +597 -4
- package/src/tests/chat-sdk-outbound-files.test.ts +652 -0
- package/src/tests/chat-sdk-telegram-markdown.test.ts +92 -0
- package/src/tests/codex-adapter.test.ts +167 -242
- package/src/tests/config-api-only-keys.test.ts +9 -0
- package/src/tests/context-key.test.ts +50 -0
- package/src/tests/core-auth.test.ts +5 -0
- package/src/tests/cors-allowlist.test.ts +1 -1
- package/src/tests/db-task-read-contract.test.ts +7 -0
- package/src/tests/db-task-write-contract.test.ts +6 -0
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/extensions-example-slack-reply-gate.test.ts +262 -91
- package/src/tests/fixtures/bitbucket/pullrequest-comment-created-inline.json +174 -0
- package/src/tests/fixtures/bitbucket/pullrequest-comment-created.json +170 -0
- package/src/tests/fixtures/bitbucket/pullrequest-created-mention.json +132 -0
- package/src/tests/fixtures/bitbucket/pullrequest-created-reviewer.json +140 -0
- package/src/tests/fixtures/bitbucket/pullrequest-fulfilled.json +134 -0
- package/src/tests/fixtures/bitbucket/pullrequest-rejected.json +133 -0
- package/src/tests/generate-mcp-docs.test.ts +101 -0
- package/src/tests/gitlab-handlers.test.ts +3 -3
- package/src/tests/gitlab-vcs-db.test.ts +4 -4
- package/src/tests/heartbeat-checklist.test.ts +33 -0
- package/src/tests/heartbeat-jev-gate-credentials.test.ts +44 -0
- package/src/tests/heartbeat-jev-gate.test.ts +242 -1
- package/src/tests/integration-disable-flags.test.ts +36 -0
- package/src/tests/kv-page-proxy.test.ts +91 -5
- package/src/tests/launch-password-rejection.test.ts +2 -2
- package/src/tests/mcp-input-caps.test.ts +294 -0
- package/src/tests/mcp-tools.test.ts +1 -1
- package/src/tests/memory-chunked-search-starvation.test.ts +200 -0
- package/src/tests/memory-embedding-input-cap.test.ts +114 -0
- package/src/tests/memory.test.ts +14 -0
- package/src/tests/onboarding.test.ts +12 -0
- package/src/tests/page-guest-session.test.ts +19 -8
- package/src/tests/page-proxy-authed.test.ts +1 -1
- package/src/tests/page-proxy.test.ts +17 -10
- package/src/tests/page-session.test.ts +102 -1
- package/src/tests/pages-authed-mode.test.ts +82 -12
- package/src/tests/pages-password-mode.test.ts +5 -5
- package/src/tests/pi-codemode.test.ts +8 -2
- package/src/tests/preload-env.test.ts +57 -0
- package/src/tests/preload.ts +21 -0
- package/src/tests/prompt-template-remaining.test.ts +38 -0
- package/src/tests/realtime-transport.test.ts +13 -7
- package/src/tests/run-root-coverage.test.ts +39 -0
- package/src/tests/runner-bitbucket-vcs.test.ts +191 -0
- package/src/tests/runner-skills-refresh.test.ts +58 -0
- package/src/tests/script-runs-http.test.ts +84 -0
- package/src/tests/scripts-http.test.ts +10 -1
- package/src/tests/secret-scrubber.test.ts +15 -0
- package/src/tests/sessions-move.test.ts +576 -0
- package/src/tests/sessions-tree.test.ts +493 -0
- package/src/tests/skill-files-http.test.ts +50 -0
- package/src/tests/skills-bundle.test.ts +99 -0
- package/src/tests/swarm-config-export-to-env.test.ts +23 -3
- package/src/tests/task-audio-transcription.test.ts +299 -0
- package/src/tests/task-completion-memory-chunking.test.ts +434 -0
- package/src/tests/task-terminal-secret-scrub.test.ts +6 -1
- package/src/tests/transcription.test.ts +299 -0
- package/src/tests/vcs-bitbucket.test.ts +91 -0
- package/src/tests/vcs-provider-isolation.test.ts +123 -0
- package/src/tests/vcs-provider.test.ts +16 -1
- package/src/tests/vcs-tracking.test.ts +3 -3
- package/src/tests/workflow-foreach-bodies.test.ts +637 -0
- package/src/tests/workflow-foreach-concurrency.test.ts +342 -0
- package/src/tests/workflow-foreach-retry.test.ts +537 -0
- package/src/tests/workflow-foreach.test.ts +44 -23
- package/src/tests/workflow-step-claims.test.ts +6 -6
- package/src/tests/workflow-system-one-decision-providers.test.ts +98 -1
- package/src/tests/workflow-system-one-decision.test.ts +2 -1
- package/src/tests/workflow-tla-races.test.ts +2 -2
- package/src/tools/chat-access.ts +10 -0
- package/src/tools/chat-post.ts +31 -11
- package/src/tools/chat-reply.ts +32 -10
- package/src/tools/memory-search.ts +3 -1
- package/src/tools/script-delete.ts +1 -1
- package/src/tools/system-one-decide.ts +17 -4
- package/src/tools/workflows/create-workflow.ts +5 -3
- package/src/types.ts +9 -2
- package/src/utils/config-env-export.ts +4 -1
- package/src/utils/page-session.ts +133 -20
- package/src/utils/secret-scrubber.ts +4 -0
- package/src/utils/skills-refresh.ts +83 -43
- package/src/utils/transcription.ts +246 -0
- package/src/vcs/bitbucket.ts +58 -0
- package/src/vcs/index.ts +2 -0
- package/src/vcs/types.ts +2 -2
- package/src/workflows/approval-resolution.ts +4 -1
- package/src/workflows/checkpoint.ts +9 -1
- package/src/workflows/constants.ts +3 -0
- package/src/workflows/definition.ts +48 -17
- package/src/workflows/engine.ts +25 -15
- package/src/workflows/executors/foreach.ts +45 -99
- package/src/workflows/executors/system-one-decision.ts +28 -7
- package/src/workflows/executors/system-one-providers.ts +39 -4
- package/src/workflows/foreach-body.ts +146 -0
- package/src/workflows/foreach-driver.ts +552 -0
- package/src/workflows/foreach-join.ts +206 -7
- package/src/workflows/node-timeout.ts +21 -0
- package/src/workflows/recovery.ts +68 -3
- package/src/workflows/resume.ts +221 -43
- package/src/workflows/retry-poller.ts +39 -24
- package/src/workflows/step-input.ts +59 -0
- package/src/workflows/task-step-routing.ts +114 -56
- package/templates/extensions/slack-reply-gate/README.md +24 -22
- package/templates/extensions/slack-reply-gate/hooks.ts +146 -95
- package/templates/extensions/slack-reply-gate/manifest.json +2 -2
- package/templates/skills/apps/SKILL.md +28 -6
- package/templates/skills/apps/content.md +28 -6
- package/templates/skills/chat-sdk/content.md +6 -2
- package/templates/skills/code-quality/content.md +2 -1
- package/templates/skills/heartbeat-runbook/content.md +5 -5
- package/templates/skills/pages/content.md +6 -4
- package/templates/skills/slack-interaction/content.md +5 -0
- package/templates/skills/swarm-scripts/SKILL.md +2 -2
- package/templates/skills/swarm-scripts/content.md +2 -2
- package/templates/skills/work-on-task/SKILL.md +1 -1
- package/templates/skills/work-on-task/content.md +1 -1
- package/dist/cli-51y09c6p.js +0 -2657
- package/dist/cli-yw91rnmw.js +0 -13
- package/dist/handlers-0pvee9nw.js +0 -39
- package/dist/index-gdh8jrdm.js +0 -43
- package/dist/index-hg0x2cdv.js +0 -40
- package/dist/index-thm0y6a8.js +0 -41
- package/dist/lead-ee8vazz2.js +0 -77
- package/dist/worker-16s75hm5.js +0 -77
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
isTranscriptionConfigured,
|
|
4
|
+
NOT_TRANSCRIBED_NOTE,
|
|
5
|
+
resolveTranscriptionConfig,
|
|
6
|
+
TRANSCRIPTION_ENV_KEYS,
|
|
7
|
+
TranscriptionError,
|
|
8
|
+
transcribeAudio,
|
|
9
|
+
transcribeAudioNotes,
|
|
10
|
+
} from "../utils/transcription";
|
|
11
|
+
|
|
12
|
+
// Includes the provider keys the fallback reads: a worker shell often has
|
|
13
|
+
// OPENAI_API_KEY or ELEVENLABS_API_KEY set, which would leak into every test.
|
|
14
|
+
const ENV_KEYS = TRANSCRIPTION_ENV_KEYS;
|
|
15
|
+
const savedEnv = new Map(ENV_KEYS.map((key) => [key, process.env[key]]));
|
|
16
|
+
|
|
17
|
+
type Captured = {
|
|
18
|
+
path: string;
|
|
19
|
+
authorization: string | null;
|
|
20
|
+
xiApiKey: string | null;
|
|
21
|
+
fields: string[];
|
|
22
|
+
model: FormDataEntryValue | null;
|
|
23
|
+
modelId: FormDataEntryValue | null;
|
|
24
|
+
filename: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const captured: Captured[] = [];
|
|
28
|
+
// The file part's Content-Type as sent. Bun's `formData()` re-derives a File's
|
|
29
|
+
// type from its extension (`.webm` reads back as video/webm), so read it raw.
|
|
30
|
+
const sentFileTypes: string[] = [];
|
|
31
|
+
let reply: () => Response = () => Response.json({ text: " hello " });
|
|
32
|
+
let stt: ReturnType<typeof Bun.serve>;
|
|
33
|
+
|
|
34
|
+
beforeAll(() => {
|
|
35
|
+
for (const key of ENV_KEYS) delete process.env[key];
|
|
36
|
+
stt = Bun.serve({
|
|
37
|
+
port: 0,
|
|
38
|
+
hostname: "127.0.0.1",
|
|
39
|
+
async fetch(req) {
|
|
40
|
+
const raw = await req.clone().text();
|
|
41
|
+
sentFileTypes.push(
|
|
42
|
+
/name="file"; filename="[^"]*"\r\nContent-Type: ([^\r\n]+)/.exec(raw)?.[1] ?? "",
|
|
43
|
+
);
|
|
44
|
+
const form = await req.formData();
|
|
45
|
+
captured.push({
|
|
46
|
+
path: new URL(req.url).pathname,
|
|
47
|
+
authorization: req.headers.get("authorization"),
|
|
48
|
+
xiApiKey: req.headers.get("xi-api-key"),
|
|
49
|
+
fields: [...form.keys()].sort(),
|
|
50
|
+
model: form.get("model"),
|
|
51
|
+
modelId: form.get("model_id"),
|
|
52
|
+
filename: (form.get("file") as File).name,
|
|
53
|
+
});
|
|
54
|
+
return reply();
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
afterAll(() => {
|
|
60
|
+
stt.stop(true);
|
|
61
|
+
for (const [key, value] of savedEnv) {
|
|
62
|
+
if (value === undefined) delete process.env[key];
|
|
63
|
+
else process.env[key] = value;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
afterEach(() => {
|
|
68
|
+
captured.length = 0;
|
|
69
|
+
sentFileTypes.length = 0;
|
|
70
|
+
reply = () => Response.json({ text: " hello " });
|
|
71
|
+
for (const key of ENV_KEYS) delete process.env[key];
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
function configure(provider?: string) {
|
|
75
|
+
process.env.TRANSCRIPTION_API_KEY = "stt-test-key-123456";
|
|
76
|
+
process.env.TRANSCRIPTION_BASE_URL = `http://127.0.0.1:${stt.port}/v1`;
|
|
77
|
+
if (provider) process.env.TRANSCRIPTION_PROVIDER = provider;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const audio = {
|
|
81
|
+
data: new Blob([new Uint8Array([1, 2, 3])]),
|
|
82
|
+
filename: "note.ogg",
|
|
83
|
+
mimeType: "audio/ogg",
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
describe("transcribeAudio request shape", () => {
|
|
87
|
+
test("elevenlabs posts to /speech-to-text with xi-api-key and model_id", async () => {
|
|
88
|
+
configure("elevenlabs");
|
|
89
|
+
expect(await transcribeAudio(audio)).toBe("hello");
|
|
90
|
+
expect(captured).toEqual([
|
|
91
|
+
{
|
|
92
|
+
path: "/v1/speech-to-text",
|
|
93
|
+
authorization: null,
|
|
94
|
+
xiApiKey: "stt-test-key-123456",
|
|
95
|
+
fields: ["file", "model_id"],
|
|
96
|
+
model: null,
|
|
97
|
+
modelId: "scribe_v2",
|
|
98
|
+
filename: "note.ogg",
|
|
99
|
+
},
|
|
100
|
+
]);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("elevenlabs honours TRANSCRIPTION_MODEL in model_id", async () => {
|
|
104
|
+
configure("ElevenLabs");
|
|
105
|
+
process.env.TRANSCRIPTION_MODEL = "scribe_v1";
|
|
106
|
+
await transcribeAudio(audio);
|
|
107
|
+
expect(captured[0]).toMatchObject({ fields: ["file", "model_id"], modelId: "scribe_v1" });
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("negative control: the default provider keeps the OpenAI shape", async () => {
|
|
111
|
+
configure();
|
|
112
|
+
expect(await transcribeAudio(audio)).toBe("hello");
|
|
113
|
+
expect(captured).toEqual([
|
|
114
|
+
{
|
|
115
|
+
path: "/v1/audio/transcriptions",
|
|
116
|
+
authorization: "Bearer stt-test-key-123456",
|
|
117
|
+
xiApiKey: null,
|
|
118
|
+
fields: ["file", "model"],
|
|
119
|
+
model: "whisper-1",
|
|
120
|
+
modelId: null,
|
|
121
|
+
filename: "note.ogg",
|
|
122
|
+
},
|
|
123
|
+
]);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("an unknown provider fails before any request", async () => {
|
|
127
|
+
configure("deepgram");
|
|
128
|
+
await expect(transcribeAudio(audio)).rejects.toThrow(
|
|
129
|
+
new TranscriptionError(
|
|
130
|
+
'TRANSCRIPTION_PROVIDER "deepgram" is not supported (use openai, groq, elevenlabs)',
|
|
131
|
+
),
|
|
132
|
+
);
|
|
133
|
+
expect(captured).toHaveLength(0);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("an elevenlabs error detail is surfaced with the key scrubbed", async () => {
|
|
137
|
+
configure("elevenlabs");
|
|
138
|
+
reply = () =>
|
|
139
|
+
Response.json(
|
|
140
|
+
{ detail: { status: "invalid_api_key", message: "Invalid key stt-test-key-123456" } },
|
|
141
|
+
{ status: 401 },
|
|
142
|
+
);
|
|
143
|
+
await expect(transcribeAudio(audio)).rejects.toThrow(
|
|
144
|
+
"Invalid key [REDACTED:TRANSCRIPTION_API_KEY]",
|
|
145
|
+
);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe("provider key fallback", () => {
|
|
150
|
+
// Built at runtime so the secret scanner does not read them as keys.
|
|
151
|
+
const fake = (provider: string) => [provider, "fixture", "value"].join("-");
|
|
152
|
+
const ELEVEN_FAKE = fake("eleven");
|
|
153
|
+
const OPENAI_FAKE = fake("openai");
|
|
154
|
+
const GROQ_FAKE = fake("groq");
|
|
155
|
+
|
|
156
|
+
// Auto-detected keys always go to the provider's public endpoint, so record
|
|
157
|
+
// that URL and forward the request to the local stub on the same path.
|
|
158
|
+
const realFetch = globalThis.fetch;
|
|
159
|
+
const calledUrls: string[] = [];
|
|
160
|
+
beforeEach(() => {
|
|
161
|
+
calledUrls.length = 0;
|
|
162
|
+
globalThis.fetch = (async (input: string | URL | Request, init?: RequestInit) => {
|
|
163
|
+
const url = new URL(String(input));
|
|
164
|
+
calledUrls.push(url.href);
|
|
165
|
+
return realFetch(`http://127.0.0.1:${stt.port}${url.pathname}`, init);
|
|
166
|
+
}) as typeof fetch;
|
|
167
|
+
});
|
|
168
|
+
afterEach(() => {
|
|
169
|
+
globalThis.fetch = realFetch;
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("no key at all means not configured", async () => {
|
|
173
|
+
expect(resolveTranscriptionConfig()).toBeNull();
|
|
174
|
+
expect(isTranscriptionConfigured()).toBe(false);
|
|
175
|
+
const notes = await transcribeAudioNotes([{ key: "a", name: "a.ogg", data: audio.data }], "t");
|
|
176
|
+
expect(notes.get("a")).toBe(NOT_TRANSCRIBED_NOTE);
|
|
177
|
+
expect(NOT_TRANSCRIBED_NOTE).toBe(
|
|
178
|
+
"(not transcribed: no transcription provider configured, set TRANSCRIPTION_API_KEY or one of ELEVENLABS_API_KEY, OPENAI_API_KEY, GROQ_API_KEY)",
|
|
179
|
+
);
|
|
180
|
+
expect(calledUrls).toHaveLength(0);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("ELEVENLABS_API_KEY alone uses the elevenlabs shape", async () => {
|
|
184
|
+
process.env.ELEVENLABS_API_KEY = ELEVEN_FAKE;
|
|
185
|
+
expect(resolveTranscriptionConfig()).toMatchObject({
|
|
186
|
+
provider: "elevenlabs",
|
|
187
|
+
keyEnv: "ELEVENLABS_API_KEY",
|
|
188
|
+
source: "auto",
|
|
189
|
+
});
|
|
190
|
+
expect(await transcribeAudio(audio)).toBe("hello");
|
|
191
|
+
expect(calledUrls).toEqual(["https://api.elevenlabs.io/v1/speech-to-text"]);
|
|
192
|
+
expect(captured[0]).toMatchObject({
|
|
193
|
+
xiApiKey: ELEVEN_FAKE,
|
|
194
|
+
authorization: null,
|
|
195
|
+
modelId: "scribe_v2",
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("OPENAI_API_KEY alone uses the openai shape", async () => {
|
|
200
|
+
process.env.OPENAI_API_KEY = OPENAI_FAKE;
|
|
201
|
+
expect(resolveTranscriptionConfig()).toMatchObject({ provider: "openai", source: "auto" });
|
|
202
|
+
await transcribeAudio(audio);
|
|
203
|
+
expect(calledUrls).toEqual(["https://api.openai.com/v1/audio/transcriptions"]);
|
|
204
|
+
expect(captured[0]).toMatchObject({
|
|
205
|
+
authorization: `Bearer ${OPENAI_FAKE}`,
|
|
206
|
+
model: "whisper-1",
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("GROQ_API_KEY alone uses Groq's OpenAI-compatible endpoint and whisper model", async () => {
|
|
211
|
+
process.env.GROQ_API_KEY = GROQ_FAKE;
|
|
212
|
+
expect(resolveTranscriptionConfig()).toMatchObject({
|
|
213
|
+
provider: "groq",
|
|
214
|
+
keyEnv: "GROQ_API_KEY",
|
|
215
|
+
});
|
|
216
|
+
await transcribeAudio(audio);
|
|
217
|
+
expect(calledUrls).toEqual(["https://api.groq.com/openai/v1/audio/transcriptions"]);
|
|
218
|
+
expect(captured[0]).toMatchObject({
|
|
219
|
+
authorization: `Bearer ${GROQ_FAKE}`,
|
|
220
|
+
model: "whisper-large-v3-turbo",
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("ElevenLabs wins over OpenAI and Groq when all are set", async () => {
|
|
225
|
+
process.env.OPENAI_API_KEY = OPENAI_FAKE;
|
|
226
|
+
process.env.GROQ_API_KEY = GROQ_FAKE;
|
|
227
|
+
process.env.ELEVENLABS_API_KEY = ELEVEN_FAKE;
|
|
228
|
+
await transcribeAudio(audio);
|
|
229
|
+
expect(captured[0]).toMatchObject({ xiApiKey: ELEVEN_FAKE, authorization: null });
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test("OpenAI wins over Groq", () => {
|
|
233
|
+
process.env.GROQ_API_KEY = GROQ_FAKE;
|
|
234
|
+
process.env.OPENAI_API_KEY = OPENAI_FAKE;
|
|
235
|
+
expect(resolveTranscriptionConfig()?.provider).toBe("openai");
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("an explicit TRANSCRIPTION_API_KEY overrides both detected keys", async () => {
|
|
239
|
+
process.env.OPENAI_API_KEY = OPENAI_FAKE;
|
|
240
|
+
process.env.ELEVENLABS_API_KEY = ELEVEN_FAKE;
|
|
241
|
+
configure();
|
|
242
|
+
expect(resolveTranscriptionConfig()).toMatchObject({
|
|
243
|
+
provider: "openai",
|
|
244
|
+
keyEnv: "TRANSCRIPTION_API_KEY",
|
|
245
|
+
source: "explicit",
|
|
246
|
+
});
|
|
247
|
+
await transcribeAudio(audio);
|
|
248
|
+
// The explicit key honours TRANSCRIPTION_BASE_URL.
|
|
249
|
+
expect(calledUrls).toEqual([`http://127.0.0.1:${stt.port}/v1/audio/transcriptions`]);
|
|
250
|
+
expect(captured[0]).toMatchObject({
|
|
251
|
+
authorization: "Bearer stt-test-key-123456",
|
|
252
|
+
xiApiKey: null,
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test("an auto-detected key never goes to TRANSCRIPTION_BASE_URL", async () => {
|
|
257
|
+
process.env.ELEVENLABS_API_KEY = ELEVEN_FAKE;
|
|
258
|
+
process.env.TRANSCRIPTION_BASE_URL = "https://collector.example/v1";
|
|
259
|
+
await transcribeAudio(audio);
|
|
260
|
+
expect(calledUrls).toEqual(["https://api.elevenlabs.io/v1/speech-to-text"]);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test("a pinned provider uses only its own key", async () => {
|
|
264
|
+
process.env.ELEVENLABS_API_KEY = ELEVEN_FAKE;
|
|
265
|
+
process.env.OPENAI_API_KEY = OPENAI_FAKE;
|
|
266
|
+
process.env.TRANSCRIPTION_PROVIDER = "openai";
|
|
267
|
+
await transcribeAudio(audio);
|
|
268
|
+
expect(captured[0]).toMatchObject({ authorization: `Bearer ${OPENAI_FAKE}` });
|
|
269
|
+
|
|
270
|
+
delete process.env.OPENAI_API_KEY;
|
|
271
|
+
expect(isTranscriptionConfigured()).toBe(false);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test("a detected key is scrubbed from errors under its own name", async () => {
|
|
275
|
+
process.env.ELEVENLABS_API_KEY = ELEVEN_FAKE;
|
|
276
|
+
reply = () => Response.json({ detail: `bad key ${ELEVEN_FAKE}` }, { status: 401 });
|
|
277
|
+
await expect(transcribeAudio(audio)).rejects.toThrow("bad key [REDACTED:ELEVENLABS_API_KEY]");
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// The composer's mic button records webm in Chrome and Firefox; the upload
|
|
281
|
+
// route drops `;codecs=` before it gets here. Each provider reads the format
|
|
282
|
+
// from the file name, and all three list webm as supported.
|
|
283
|
+
test.each([
|
|
284
|
+
["elevenlabs", "ELEVENLABS_API_KEY", "https://api.elevenlabs.io/v1/speech-to-text"],
|
|
285
|
+
["openai", "OPENAI_API_KEY", "https://api.openai.com/v1/audio/transcriptions"],
|
|
286
|
+
["groq", "GROQ_API_KEY", "https://api.groq.com/openai/v1/audio/transcriptions"],
|
|
287
|
+
])("a recorded webm clip reaches %s as a .webm audio/webm file", async (name, keyEnv, url) => {
|
|
288
|
+
process.env[keyEnv] = fake(name);
|
|
289
|
+
const clip = new Blob([new Uint8Array([0x1a, 0x45, 0xdf, 0xa3])], { type: "audio/webm" });
|
|
290
|
+
const notes = await transcribeAudioNotes(
|
|
291
|
+
[{ key: "c", name: "recording-2026-10-21-143005.webm", data: clip, mimeType: "audio/webm" }],
|
|
292
|
+
"t",
|
|
293
|
+
);
|
|
294
|
+
expect(notes.get("c")).toBe("[Transcript of recording-2026-10-21-143005.webm]\nhello");
|
|
295
|
+
expect(calledUrls).toEqual([url]);
|
|
296
|
+
expect(captured[0]).toMatchObject({ filename: "recording-2026-10-21-143005.webm" });
|
|
297
|
+
expect(sentFileTypes).toEqual(["audio/webm"]);
|
|
298
|
+
});
|
|
299
|
+
});
|
|
@@ -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
|
});
|