@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.
- package/README.md +4 -2
- package/dist/{acp-adapter-2wdkktv6.js → acp-adapter-11gp67nr.js} +8 -6
- package/dist/{actions-c40ebcm4.js → actions-8p2kze8j.js} +23 -21
- package/dist/{amp-adapter-wb67m95z.js → amp-adapter-z4qvhacj.js} +2 -2
- package/dist/{anthropic-messages-g6pqmqk4.js → anthropic-messages-txs190dd.js} +17 -20
- package/dist/{app-353vre8g.js → app-0qp0a2gk.js} +3 -3
- package/dist/{assistant-sj1njh23.js → assistant-jedd3ktm.js} +24 -22
- package/dist/{azure-openai-responses-9dbmj346.js → azure-openai-responses-9kfwhmn7.js} +18 -13
- package/dist/{boot-reembed-qdkvxeem.js → boot-reembed-4xwq5tg0.js} +1 -1
- package/dist/{boot-reembed-b9c3az3c.js → boot-reembed-n4wq5dd2.js} +1 -1
- package/dist/{boot-scrub-sweep-m3wh8eks.js → boot-scrub-sweep-69h90xcq.js} +4 -4
- package/dist/{boot-seeds-j2273s54.js → boot-seeds-3vn0m9sg.js} +4 -4
- package/dist/{claude-adapter-m7q0a085.js → claude-adapter-221mvad5.js} +8 -6
- package/dist/{claude-managed-adapter-5ptb6tzs.js → claude-managed-adapter-gt6pa35d.js} +2 -2
- package/dist/{claude-managed-setup-smrqk5j5.js → claude-managed-setup-c3hp36ph.js} +4 -4
- package/dist/{claude-sdk-session-38xnz21e.js → claude-sdk-session-8thjsk5e.js} +9012 -8928
- package/dist/{cli-b9fcxxev.js → cli-007cnhm1.js} +50 -4
- package/dist/{cli-j4rqdf2n.js → cli-03vpq45j.js} +2 -2
- package/dist/{cli-dk44dxec.js → cli-0y6f0ckq.js} +1 -1
- package/dist/{cli-kpw04va0.js → cli-0z3vjrpm.js} +1 -1
- package/dist/{cli-94gwyg7m.js → cli-179xcyky.js} +9 -9
- package/dist/{cli-3r8samv3.js → cli-1f0gp9vz.js} +3 -2
- package/dist/{cli-4zfqgjc5.js → cli-1pr672hz.js} +2 -2
- package/dist/{cli-0bqteszy.js → cli-2nf3b08t.js} +328 -25
- package/dist/{cli-43x64hv3.js → cli-36j4jnk3.js} +4 -4
- package/dist/{cli-k8g16gz3.js → cli-3qv8naq8.js} +115 -119
- package/dist/cli-3zwye9sn.js +1193 -0
- package/dist/{cli-60cgh1rv.js → cli-459fk97x.js} +7 -7
- package/dist/{cli-v8fjakwf.js → cli-4w24f7z3.js} +1 -1
- package/dist/{cli-n72rg53p.js → cli-5kzk3472.js} +812 -418
- package/dist/{cli-h7gc0b4r.js → cli-5m84zd2k.js} +55 -18
- package/dist/{cli-j99azg3g.js → cli-5rvg1ddk.js} +5 -5
- package/dist/{cli-dvkmp9bt.js → cli-6ctwrf1z.js} +20 -10
- package/dist/{cli-848hxdwj.js → cli-6rg2jn2y.js} +2 -1
- package/dist/{cli-fv5hepyy.js → cli-7bwtc6yx.js} +3 -3
- package/dist/{cli-ms85309d.js → cli-7g40g8hq.js} +11 -11
- package/dist/{cli-xpa64rek.js → cli-92mqenkc.js} +2362 -159
- package/dist/{cli-018r4jq6.js → cli-9ce2xw98.js} +8 -8
- package/dist/{cli-1syn2xy1.js → cli-bgdtv62r.js} +32 -18
- 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-wt0jf9hm.js → cli-fcc5qe3f.js} +553 -193
- package/dist/{cli-ftc1g5tj.js → cli-fes51ya2.js} +3 -3
- package/dist/{cli-3a45zcgm.js → cli-ftmq4nvk.js} +2 -2
- package/dist/{cli-37j04k05.js → cli-fy1jyyym.js} +13 -13
- package/dist/{cli-s95dknxh.js → cli-g1cw7xtt.js} +1 -1
- package/dist/{cli-74c1wpm0.js → cli-gpv60278.js} +1 -1
- package/dist/{cli-pemywhfq.js → cli-ha9rmwgq.js} +2 -1
- package/dist/{cli-g35m2xwm.js → cli-j1eghqfm.js} +2 -2
- package/dist/{cli-qfxc84gh.js → cli-jpxsrxcb.js} +1 -1
- package/dist/{cli-ejs3m1sx.js → cli-kxpfx2xr.js} +1 -1
- package/dist/{cli-p14m2cnp.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-f01h25yt.js → cli-n6kmqngw.js} +2 -2
- package/dist/{cli-v0yrfmdh.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-9yqgm9nd.js → cli-r25qpaxt.js} +161 -63
- package/dist/{cli-eww120g8.js → cli-rh5smt8t.js} +18 -4
- package/dist/{cli-qwkcwjdn.js → cli-rycdaj0a.js} +1 -1
- package/dist/{cli-a92r1127.js → cli-s4sntwt4.js} +2 -2
- package/dist/{cli-be2161h1.js → cli-segs7hx9.js} +6 -6
- package/dist/{cli-36ckw0wx.js → cli-stxdrqs2.js} +2 -2
- package/dist/{cli-z7d29rw9.js → cli-sy8f9705.js} +4 -4
- package/dist/{cli-1m17dc9d.js → cli-ta8bd7th.js} +2 -2
- package/dist/{cli-st8aw1m6.js → cli-we0ra057.js} +4 -4
- package/dist/{cli-7c9qs530.js → cli-x21dqn18.js} +5 -5
- package/dist/{cli-1sp861s4.js → cli-x6vf4sad.js} +11 -11
- package/dist/{cli-d2mb5s8a.js → cli-x77gen2p.js} +680 -440
- package/dist/{cli-tw4p90jb.js → cli-xawhpgc9.js} +5 -5
- package/dist/{cli-f0g3pgvf.js → cli-xd4eqtr2.js} +2 -2
- package/dist/{cli-1f3qzw6y.js → cli-xt768r5m.js} +2 -2
- package/dist/{cli-cxxe61f6.js → cli-y4x8ww6s.js} +2 -2
- package/dist/{cli-bnes4z6b.js → cli-y9jvxfhk.js} +3 -3
- package/dist/{cli-qwhy2qma.js → cli-z63a269p.js} +2 -2
- package/dist/{cli-sm8rses9.js → cli-zfdp01hn.js} +8 -5
- package/dist/cli.js +29 -27
- package/dist/{cloudflare-workers-ai-system-one-ch2wxfcp.js → cloudflare-workers-ai-system-one-43v9qkjk.js} +7 -2
- package/dist/{codex-adapter-tn6k5mkg.js → codex-adapter-tcg1q3jk.js} +6 -4
- package/dist/{codex-login-m819c41r.js → codex-login-rn7c5gxt.js} +1 -1
- package/dist/{codex-session-runner-7vjt7jbw.js → codex-session-runner-xfx4wd9x.js} +7 -5
- package/dist/{commands-t7rz9146.js → commands-ew83nj70.js} +2 -2
- package/dist/{controller-m2jasma5.js → controller-gnq1k1mf.js} +4 -4
- package/dist/{controllers-connect-9ftpex99.js → controllers-connect-tw1dan9r.js} +25 -23
- package/dist/{cursor-adapter-mjv79e4z.js → cursor-adapter-yq5xgfcg.js} +1 -1
- package/dist/{db-6sfrn9g3.js → db-7gbc53fd.js} +14 -2
- package/dist/{db-rn673r06.js → db-t86xrnes.js} +3 -3
- package/dist/{devin-adapter-zgqadvnm.js → devin-adapter-4ycy4vrf.js} +1 -1
- package/dist/{dispatcher-c155xz0p.js → dispatcher-64bdyhps.js} +22 -20
- package/dist/{docker-fkgtr75t.js → docker-nfy2gc8t.js} +2 -2
- package/dist/{dsh-adapter-3mmszc1x.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-35k6tkv8.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-tfrq7tg1.js → grok-adapter-v82srhrx.js} +9 -7
- package/dist/handlers-c9ajzhbn.js +41 -0
- package/dist/{heartbeat-dyg4npkp.js → heartbeat-2vqs1319.js} +25 -23
- package/dist/{hook-x26b31bg.js → hook-01nk13wx.js} +7 -5
- package/dist/{hook-drspbwpe.js → hook-jzpheegg.js} +9 -7
- package/dist/{http-pk972xyw.js → http-fc1k6ec8.js} +544 -76
- 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-q3t40ryv.js +46 -0
- package/dist/{index-arytgj5q.js → index-sepja052.js} +1 -1
- package/dist/{index-jwxkkex2.js → index-zdycv3ja.js} +13 -11
- package/dist/{index-g216q6jr.js → index-zsyx51ap.js} +1 -4
- package/dist/{keepalive-7yrng7px.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-e6cemdqt.js → maintenance-hjhzdt3k.js} +1 -1
- package/dist/{mistral-conversations-1medfwv7.js → mistral-conversations-sd0xf1tn.js} +13 -13
- package/dist/{node-de6akczr.js → node-jzjj7r9x.js} +1 -1
- package/dist/{oauth-refresh-sweep-yw3xdbcj.js → oauth-refresh-sweep-y3n1knmd.js} +3 -3
- package/dist/{onboard-0a9y5yrc.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-trjpkfb7.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-d2b5my0y.js → pricing-refresh-nevhat8h.js} +3 -3
- package/dist/{rbac-roles-nxh5ahwk.js → rbac-roles-4y6ngvzq.js} +1 -1
- package/dist/{rbac-roles-hey3yv9d.js → rbac-roles-exbg8xb6.js} +2 -2
- package/dist/{render-v2-rr3p2atg.js → render-v2-cfsh13z3.js} +3 -3
- package/dist/{resume-2efkf1s1.js → resume-5s2t0dbs.js} +10 -8
- package/dist/{runtime-xe3aqy4x.js → runtime-bt5pkpm1.js} +1 -1
- package/dist/{session-access-s2mttxpw.js → session-access-dnbyp3cn.js} +6 -4
- package/dist/{setup-vypma5yt.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-rvhckdkp.js → work-objects-7mg6216g.js} +25 -23
- package/dist/worker-nwb789mg.js +79 -0
- package/dist/{x-y45mthwn.js → x-pbpezr0s.js} +2 -2
- package/openapi.json +785 -5
- package/package.json +9 -9
- package/src/assets/key.ts +17 -0
- package/src/azure-devops/handlers.ts +1 -1
- 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/read.ts +40 -4
- package/src/be/db/workflow-runs.ts +96 -0
- package/src/be/db.ts +80 -236
- package/src/be/onboarding.ts +3 -0
- package/src/be/skill-sync.ts +43 -2
- package/src/be/swarm-config-guard.ts +1 -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/contract-types.generated.ts +1 -1
- package/src/extensions/contract.ts +2 -2
- package/src/extensions/post-bridge.ts +1 -0
- package/src/github/handlers.ts +6 -6
- package/src/gitlab/handlers.ts +5 -5
- package/src/http/assets.ts +14 -2
- package/src/http/core.ts +12 -0
- package/src/http/index.ts +4 -0
- package/src/http/onboarding.ts +1 -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 +1 -0
- package/src/http/webhooks.ts +141 -0
- package/src/integrations/chat-sdk/adapters.ts +19 -3
- package/src/integrations/chat-sdk/facts.ts +30 -12
- package/src/integrations/chat-sdk/outbound.ts +38 -1
- package/src/providers/acp-targets.ts +1 -1
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/pi-mono-adapter.ts +5 -53
- package/src/scripts-runtime/types/swarm-extension.d.ts +6 -4
- package/src/slack/handlers.test.ts +158 -0
- package/src/slack/handlers.ts +19 -28
- package/src/tasks/context-key.ts +61 -1
- 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-inbound.test.ts +39 -0
- package/src/tests/chat-sdk-telegram-markdown.test.ts +92 -0
- package/src/tests/context-key.test.ts +50 -0
- package/src/tests/core-auth.test.ts +5 -0
- 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/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/integration-disable-flags.test.ts +36 -0
- package/src/tests/mcp-tools.test.ts +1 -1
- package/src/tests/onboarding.test.ts +12 -0
- 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/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/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 +2 -1
- 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-tla-races.test.ts +2 -2
- package/src/tools/script-delete.ts +1 -1
- package/src/tools/workflows/create-workflow.ts +5 -3
- package/src/types.ts +3 -2
- package/src/utils/config-env-export.ts +1 -0
- package/src/utils/secret-scrubber.ts +4 -0
- package/src/utils/skills-refresh.ts +83 -43
- 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/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/skills/slack-interaction/content.md +5 -0
- package/templates/skills/work-on-task/SKILL.md +1 -1
- package/templates/skills/work-on-task/content.md +1 -1
- package/dist/cli-9hstj09h.js +0 -2657
- package/dist/cli-yw91rnmw.js +0 -13
- package/dist/handlers-7gw64dxw.js +0 -39
- package/dist/index-bgdyvcnv.js +0 -40
- package/dist/index-gabbcdn2.js +0 -43
- package/dist/index-tfb26zhy.js +0 -41
- package/dist/lead-7e02z839.js +0 -77
- package/dist/worker-5snytpbd.js +0 -77
package/src/slack/handlers.ts
CHANGED
|
@@ -79,8 +79,6 @@ const allowedUserIds = (process.env.SLACK_ALLOWED_USER_IDS || "")
|
|
|
79
79
|
.map((id) => id.trim())
|
|
80
80
|
.filter(Boolean);
|
|
81
81
|
|
|
82
|
-
const filteringEnabled = allowedEmailDomains.length > 0 || allowedUserIds.length > 0;
|
|
83
|
-
|
|
84
82
|
/**
|
|
85
83
|
* Configuration for user filtering.
|
|
86
84
|
*/
|
|
@@ -89,6 +87,8 @@ export interface UserFilterConfig {
|
|
|
89
87
|
allowedUserIds: string[];
|
|
90
88
|
}
|
|
91
89
|
|
|
90
|
+
const envUserFilterConfig: UserFilterConfig = { allowedEmailDomains, allowedUserIds };
|
|
91
|
+
|
|
92
92
|
/**
|
|
93
93
|
* Core logic for checking if a user is allowed based on email and/or user ID.
|
|
94
94
|
* Exported for testing.
|
|
@@ -137,40 +137,31 @@ export function checkUserAccess(
|
|
|
137
137
|
/**
|
|
138
138
|
* Check if a user is allowed to interact with the swarm.
|
|
139
139
|
* Returns true if filtering is disabled, user is in whitelist, or user's email domain is allowed.
|
|
140
|
+
* The allow/deny decision is `checkUserAccess`; this wrapper only adds the lazy email lookup
|
|
141
|
+
* and the deny logs. The config comes from the SLACK_ALLOWED_* env vars, read at module load.
|
|
140
142
|
*/
|
|
141
143
|
export async function isUserAllowed(client: WebClient, userId: string): Promise<boolean> {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (allowedUserIds.includes(userId)) {
|
|
149
|
-
return true;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// No email domains configured and not in user whitelist
|
|
153
|
-
if (allowedEmailDomains.length === 0) {
|
|
154
|
-
return false;
|
|
144
|
+
const config = envUserFilterConfig;
|
|
145
|
+
// Decide without an email first: filtering off and allowlisted IDs pass, and with no
|
|
146
|
+
// email domains configured an unlisted ID is denied. Only domain checks need a lookup.
|
|
147
|
+
const allowedWithoutEmail = checkUserAccess(userId, null, config);
|
|
148
|
+
if (allowedWithoutEmail || config.allowedEmailDomains.length === 0) {
|
|
149
|
+
return allowedWithoutEmail;
|
|
155
150
|
}
|
|
156
151
|
|
|
157
152
|
// Check email domain — uses kv-backed enrichment (24h TTL, only success cached).
|
|
158
153
|
const email = await enrichSlackUserEmail(client, userId);
|
|
154
|
+
const allowed = checkUserAccess(userId, email, config);
|
|
159
155
|
|
|
160
|
-
if (!email) {
|
|
161
|
-
console.log(`[Slack] User ${userId} has no email, denying access`);
|
|
162
|
-
return false;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const domain = email.split("@")[1]?.toLowerCase();
|
|
166
|
-
if (!domain) {
|
|
167
|
-
console.log(`[Slack] User ${userId} has invalid email format, denying access`);
|
|
168
|
-
return false;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const allowed = allowedEmailDomains.includes(domain);
|
|
172
156
|
if (!allowed) {
|
|
173
|
-
|
|
157
|
+
const domain = email?.split("@")[1]?.toLowerCase();
|
|
158
|
+
if (!email) {
|
|
159
|
+
console.log(`[Slack] User ${userId} has no email, denying access`);
|
|
160
|
+
} else if (!domain) {
|
|
161
|
+
console.log(`[Slack] User ${userId} has invalid email format, denying access`);
|
|
162
|
+
} else {
|
|
163
|
+
console.log(`[Slack] User ${userId} email domain "${domain}" not in allowed list`);
|
|
164
|
+
}
|
|
174
165
|
}
|
|
175
166
|
return allowed;
|
|
176
167
|
}
|
package/src/tasks/context-key.ts
CHANGED
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
* task:trackers:gitlab:{projectId}:{mr|issue}:{iid}
|
|
22
22
|
* task:trackers:azure-devops:{repositoryId}:pr:{pullRequestId}
|
|
23
23
|
* task:trackers:azure-devops:{organization}:workitem:{workItemId}
|
|
24
|
+
* task:trackers:bitbucket:{repoUuid}:pr:{pullRequestId}
|
|
25
|
+
* (repo UUID with Bitbucket's `{}` braces
|
|
26
|
+
* stripped and lowercased, so renames keep
|
|
27
|
+
* task threads)
|
|
24
28
|
* task:trackers:linear:{issueIdentifier} (e.g. DES-42 — case preserved)
|
|
25
29
|
* task:trackers:jira:{issueIdentifier} (e.g. PROJ-123 — case preserved)
|
|
26
30
|
* task:schedule:{scheduleId}
|
|
@@ -50,7 +54,13 @@ export type ContextKeyFamily =
|
|
|
50
54
|
| "schedule"
|
|
51
55
|
| "workflow";
|
|
52
56
|
|
|
53
|
-
export type TrackerProvider =
|
|
57
|
+
export type TrackerProvider =
|
|
58
|
+
| "github"
|
|
59
|
+
| "gitlab"
|
|
60
|
+
| "azure-devops"
|
|
61
|
+
| "bitbucket"
|
|
62
|
+
| "linear"
|
|
63
|
+
| "jira";
|
|
54
64
|
|
|
55
65
|
export type ParsedContextKey =
|
|
56
66
|
| { family: "slack"; parts: { channelId: string; threadTs: string } }
|
|
@@ -76,6 +86,11 @@ export type ParsedContextKey =
|
|
|
76
86
|
subFamily: "azure-devops";
|
|
77
87
|
parts: { organization: string; kind: "workitem"; workItemId: number };
|
|
78
88
|
}
|
|
89
|
+
| {
|
|
90
|
+
family: "trackers";
|
|
91
|
+
subFamily: "bitbucket";
|
|
92
|
+
parts: { repoUuid: string; kind: "pr"; pullRequestId: number };
|
|
93
|
+
}
|
|
79
94
|
| {
|
|
80
95
|
family: "trackers";
|
|
81
96
|
subFamily: "linear";
|
|
@@ -224,6 +239,34 @@ export function azureDevOpsWorkItemContextKey(input: {
|
|
|
224
239
|
return ["task", "trackers", "azure-devops", organization, "workitem", workItemId].join(SEPARATOR);
|
|
225
240
|
}
|
|
226
241
|
|
|
242
|
+
/**
|
|
243
|
+
* Bitbucket Cloud wraps repository UUIDs in braces (`{1a2b...}`). Strip them
|
|
244
|
+
* and lowercase so the key is stable regardless of which payload field the
|
|
245
|
+
* UUID came from.
|
|
246
|
+
*/
|
|
247
|
+
export function bitbucketContextKey({
|
|
248
|
+
repoUuid,
|
|
249
|
+
pullRequestId,
|
|
250
|
+
}: {
|
|
251
|
+
repoUuid: string;
|
|
252
|
+
pullRequestId: number | string;
|
|
253
|
+
}): string {
|
|
254
|
+
const rawUuid = typeof repoUuid === "string" ? repoUuid.trim().replace(/^\{|\}$/g, "") : repoUuid;
|
|
255
|
+
const uuid = assertSafePart(rawUuid, "repoUuid").toLowerCase();
|
|
256
|
+
if (/[{}]/.test(uuid)) {
|
|
257
|
+
throw new Error(
|
|
258
|
+
`context-key: bitbucket "repoUuid" has unbalanced braces (got ${JSON.stringify(repoUuid)})`,
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
const id = assertSafePart(pullRequestId, "pullRequestId");
|
|
262
|
+
if (!/^\d+$/.test(id)) {
|
|
263
|
+
throw new Error(
|
|
264
|
+
`context-key: bitbucket "pullRequestId" must be a positive integer (got ${JSON.stringify(id)})`,
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
return ["task", "trackers", "bitbucket", uuid, "pr", id].join(SEPARATOR);
|
|
268
|
+
}
|
|
269
|
+
|
|
227
270
|
export function linearContextKey(input: { issueIdentifier: string }): string {
|
|
228
271
|
const issueIdentifier = assertSafePart(input.issueIdentifier, "issueIdentifier");
|
|
229
272
|
return ["task", "trackers", "linear", issueIdentifier].join(SEPARATOR);
|
|
@@ -401,6 +444,23 @@ export function parseContextKey(key: string): ParsedContextKey {
|
|
|
401
444
|
parts: { repositoryId: parts[3] as string, kind: "pr", pullRequestId },
|
|
402
445
|
};
|
|
403
446
|
}
|
|
447
|
+
if (subFamily === "bitbucket") {
|
|
448
|
+
if (parts.length !== 6 || parts[4] !== "pr" || !parts[3]) {
|
|
449
|
+
throw new Error(`context-key: malformed bitbucket key: ${JSON.stringify(key)}`);
|
|
450
|
+
}
|
|
451
|
+
const idStr = parts[5] as string;
|
|
452
|
+
const pullRequestId = Number.parseInt(idStr, 10);
|
|
453
|
+
if (!/^\d+$/.test(idStr) || !Number.isFinite(pullRequestId)) {
|
|
454
|
+
throw new Error(
|
|
455
|
+
`context-key: malformed bitbucket pullRequestId "${idStr}": ${JSON.stringify(key)}`,
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
return {
|
|
459
|
+
family: "trackers",
|
|
460
|
+
subFamily: "bitbucket",
|
|
461
|
+
parts: { repoUuid: parts[3] as string, kind: "pr", pullRequestId },
|
|
462
|
+
};
|
|
463
|
+
}
|
|
404
464
|
if (subFamily === "linear") {
|
|
405
465
|
if (parts.length !== 4) {
|
|
406
466
|
throw new Error(`context-key: malformed linear key: ${JSON.stringify(key)}`);
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { createHmac } from "node:crypto";
|
|
3
|
+
import { resetBitbucketBotCache, resolveBitbucketBotAccountId } from "../bitbucket/api";
|
|
4
|
+
import {
|
|
5
|
+
getBitbucketToken,
|
|
6
|
+
initBitbucket,
|
|
7
|
+
isBitbucketEnabled,
|
|
8
|
+
resetBitbucket,
|
|
9
|
+
verifyBitbucketWebhook,
|
|
10
|
+
} from "../bitbucket/auth";
|
|
11
|
+
|
|
12
|
+
const ENV_KEYS = [
|
|
13
|
+
"BITBUCKET_WEBHOOK_SECRET",
|
|
14
|
+
"BITBUCKET_TOKEN",
|
|
15
|
+
"BITBUCKET_DISABLE",
|
|
16
|
+
"BITBUCKET_BOT_ACCOUNT_ID",
|
|
17
|
+
] as const;
|
|
18
|
+
|
|
19
|
+
function sign(secret: string, body: string): string {
|
|
20
|
+
return `sha256=${createHmac("sha256", secret).update(body).digest("hex")}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
for (const key of ENV_KEYS) delete process.env[key];
|
|
25
|
+
resetBitbucket();
|
|
26
|
+
resetBitbucketBotCache();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe("isBitbucketEnabled", () => {
|
|
30
|
+
test("disabled without a webhook secret", () => {
|
|
31
|
+
expect(isBitbucketEnabled()).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("enabled with a webhook secret", () => {
|
|
35
|
+
process.env.BITBUCKET_WEBHOOK_SECRET = "example-secret";
|
|
36
|
+
expect(isBitbucketEnabled()).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("BITBUCKET_DISABLE wins over the secret", () => {
|
|
40
|
+
process.env.BITBUCKET_WEBHOOK_SECRET = "example-secret";
|
|
41
|
+
process.env.BITBUCKET_DISABLE = "true";
|
|
42
|
+
expect(isBitbucketEnabled()).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe("verifyBitbucketWebhook", () => {
|
|
47
|
+
test("matches Atlassian's documented test vector", () => {
|
|
48
|
+
process.env.BITBUCKET_WEBHOOK_SECRET = "It's a Secret to Everybody";
|
|
49
|
+
initBitbucket();
|
|
50
|
+
expect(
|
|
51
|
+
verifyBitbucketWebhook(
|
|
52
|
+
"Hello World!",
|
|
53
|
+
"sha256=a4771c39fbe90f317c7824e83ddef3caae9cb3d976c214ace1f2937e133263c9",
|
|
54
|
+
),
|
|
55
|
+
).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("rejects a signature made with the wrong secret", () => {
|
|
59
|
+
process.env.BITBUCKET_WEBHOOK_SECRET = "example-secret";
|
|
60
|
+
initBitbucket();
|
|
61
|
+
expect(verifyBitbucketWebhook("{}", sign("other-secret", "{}"))).toBe(false);
|
|
62
|
+
expect(verifyBitbucketWebhook("{}", sign("example-secret", "{}"))).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("rejects a tampered body", () => {
|
|
66
|
+
process.env.BITBUCKET_WEBHOOK_SECRET = "example-secret";
|
|
67
|
+
initBitbucket();
|
|
68
|
+
expect(verifyBitbucketWebhook('{"a":2}', sign("example-secret", '{"a":1}'))).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("rejects a missing header", () => {
|
|
72
|
+
process.env.BITBUCKET_WEBHOOK_SECRET = "example-secret";
|
|
73
|
+
initBitbucket();
|
|
74
|
+
expect(verifyBitbucketWebhook("{}", undefined)).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("rejects a bare hex digest without the sha256= prefix", () => {
|
|
78
|
+
process.env.BITBUCKET_WEBHOOK_SECRET = "example-secret";
|
|
79
|
+
initBitbucket();
|
|
80
|
+
const bare = sign("example-secret", "{}").slice("sha256=".length);
|
|
81
|
+
expect(verifyBitbucketWebhook("{}", bare)).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("rejects everything before init and after reset", () => {
|
|
85
|
+
process.env.BITBUCKET_WEBHOOK_SECRET = "example-secret";
|
|
86
|
+
const sig = sign("example-secret", "{}");
|
|
87
|
+
expect(verifyBitbucketWebhook("{}", sig)).toBe(false);
|
|
88
|
+
initBitbucket();
|
|
89
|
+
expect(verifyBitbucketWebhook("{}", sig)).toBe(true);
|
|
90
|
+
resetBitbucket();
|
|
91
|
+
expect(verifyBitbucketWebhook("{}", sig)).toBe(false);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe("initBitbucket", () => {
|
|
96
|
+
test("does not initialize when disabled", () => {
|
|
97
|
+
process.env.BITBUCKET_WEBHOOK_SECRET = "example-secret";
|
|
98
|
+
process.env.BITBUCKET_TOKEN = "example-token";
|
|
99
|
+
process.env.BITBUCKET_DISABLE = "true";
|
|
100
|
+
initBitbucket();
|
|
101
|
+
expect(getBitbucketToken()).toBeNull();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("loads the API token", () => {
|
|
105
|
+
process.env.BITBUCKET_WEBHOOK_SECRET = "example-secret";
|
|
106
|
+
process.env.BITBUCKET_TOKEN = "example-token";
|
|
107
|
+
initBitbucket();
|
|
108
|
+
expect(getBitbucketToken()).toBe("example-token");
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe("resolveBitbucketBotAccountId", () => {
|
|
113
|
+
test("prefers BITBUCKET_BOT_ACCOUNT_ID", async () => {
|
|
114
|
+
process.env.BITBUCKET_BOT_ACCOUNT_ID = "557058:env-bot";
|
|
115
|
+
expect(await resolveBitbucketBotAccountId()).toBe("557058:env-bot");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("returns null without a token", async () => {
|
|
119
|
+
expect(await resolveBitbucketBotAccountId()).toBeNull();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("resolves GET /user with a Bearer token and caches it", async () => {
|
|
123
|
+
process.env.BITBUCKET_WEBHOOK_SECRET = "example-secret";
|
|
124
|
+
process.env.BITBUCKET_TOKEN = "example-token";
|
|
125
|
+
initBitbucket();
|
|
126
|
+
const calls: Array<{ url: string; auth?: string }> = [];
|
|
127
|
+
const realFetch = globalThis.fetch;
|
|
128
|
+
globalThis.fetch = (async (input: string | URL | Request, init?: RequestInit) => {
|
|
129
|
+
const headers = (init?.headers ?? {}) as Record<string, string>;
|
|
130
|
+
calls.push({ url: String(input), auth: headers.Authorization });
|
|
131
|
+
return new Response(JSON.stringify({ account_id: "557058:api-bot" }), { status: 200 });
|
|
132
|
+
}) as typeof fetch;
|
|
133
|
+
try {
|
|
134
|
+
expect(await resolveBitbucketBotAccountId()).toBe("557058:api-bot");
|
|
135
|
+
expect(await resolveBitbucketBotAccountId()).toBe("557058:api-bot");
|
|
136
|
+
} finally {
|
|
137
|
+
globalThis.fetch = realFetch;
|
|
138
|
+
}
|
|
139
|
+
expect(calls).toEqual([
|
|
140
|
+
{ url: "https://api.bitbucket.org/2.0/user", auth: "Bearer example-token" },
|
|
141
|
+
]);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("returns null on a failed lookup", async () => {
|
|
145
|
+
process.env.BITBUCKET_WEBHOOK_SECRET = "example-secret";
|
|
146
|
+
process.env.BITBUCKET_TOKEN = "example-token";
|
|
147
|
+
initBitbucket();
|
|
148
|
+
const realFetch = globalThis.fetch;
|
|
149
|
+
globalThis.fetch = (async () => new Response("nope", { status: 401 })) as typeof fetch;
|
|
150
|
+
try {
|
|
151
|
+
expect(await resolveBitbucketBotAccountId()).toBeNull();
|
|
152
|
+
} finally {
|
|
153
|
+
globalThis.fetch = realFetch;
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
});
|