@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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Database } from "bun:sqlite";
|
|
2
2
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
3
3
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import * as db from "../be/db";
|
|
4
5
|
import {
|
|
5
6
|
closeDb,
|
|
6
7
|
createAgent,
|
|
@@ -8,9 +9,16 @@ import {
|
|
|
8
9
|
getApprovalRequestById,
|
|
9
10
|
initDb,
|
|
10
11
|
startTask,
|
|
12
|
+
upsertSwarmConfig,
|
|
11
13
|
} from "../be/db";
|
|
12
14
|
import gate from "../be/seed-scripts/catalog/heartbeat-jev-gate";
|
|
13
15
|
import { registerRequestHumanInputTool } from "../tools/request-human-input";
|
|
16
|
+
import { registerSystemOneDecideTool } from "../tools/system-one-decide";
|
|
17
|
+
import type { ExecutorDependencies } from "../workflows/executors/base";
|
|
18
|
+
import {
|
|
19
|
+
SystemOneDecisionConfigSchema,
|
|
20
|
+
SystemOneDecisionExecutor,
|
|
21
|
+
} from "../workflows/executors/system-one-decision";
|
|
14
22
|
|
|
15
23
|
const LEAD = "11111111-2222-4333-8444-555555555555";
|
|
16
24
|
const NOW = Date.parse("2026-10-15T14:00:00.000Z");
|
|
@@ -176,6 +184,51 @@ const jevFails = (message: string) => async () => ({
|
|
|
176
184
|
status: 200,
|
|
177
185
|
data: { success: false, message },
|
|
178
186
|
});
|
|
187
|
+
const CF_ACCOUNT = "0123456789abcdef0123456789abcdef";
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Stands in for `ctx.swarm.systemOne_decide` with the real executor behind it, as
|
|
191
|
+
* the tool runs it: Workers AI answers in its REST envelope, and the executor
|
|
192
|
+
* unwraps it. `urls` records where each request went.
|
|
193
|
+
*/
|
|
194
|
+
function clefDecide(p: number) {
|
|
195
|
+
const urls: string[] = [];
|
|
196
|
+
const executor = new SystemOneDecisionExecutor({ db } as unknown as ExecutorDependencies, {
|
|
197
|
+
env: {},
|
|
198
|
+
getApiKey: async (id) => (id === "cloudflare" ? "cf-example-token" : null),
|
|
199
|
+
getServerUrl: async (id) => (id === "cloudflare" ? CF_ACCOUNT : null),
|
|
200
|
+
fetch: (async (input: string | URL | Request) => {
|
|
201
|
+
urls.push(String(input));
|
|
202
|
+
return Response.json({
|
|
203
|
+
result: {
|
|
204
|
+
model: "clef",
|
|
205
|
+
answers: { act: { type: "noul", noul: p } },
|
|
206
|
+
usage: { input_tokens: 739, output_tokens: 0 },
|
|
207
|
+
},
|
|
208
|
+
success: true,
|
|
209
|
+
errors: [],
|
|
210
|
+
messages: [],
|
|
211
|
+
});
|
|
212
|
+
}) as typeof fetch,
|
|
213
|
+
});
|
|
214
|
+
const decide = async (args: Record<string, any>) => {
|
|
215
|
+
const returns = Object.fromEntries(
|
|
216
|
+
Object.entries(args.questions).map(([id, q]: [string, any]) => [id, { type: q.type }]),
|
|
217
|
+
);
|
|
218
|
+
const decided = await executor.decide(
|
|
219
|
+
SystemOneDecisionConfigSchema.parse({ ...args, returns }),
|
|
220
|
+
);
|
|
221
|
+
return {
|
|
222
|
+
success: true,
|
|
223
|
+
status: 200,
|
|
224
|
+
data: decided.ok
|
|
225
|
+
? { success: true, ...decided.output }
|
|
226
|
+
: { success: false, message: decided.error },
|
|
227
|
+
};
|
|
228
|
+
};
|
|
229
|
+
return { urls, decide };
|
|
230
|
+
}
|
|
231
|
+
|
|
179
232
|
const run = (ctx: unknown, args: Record<string, unknown> = {}) =>
|
|
180
233
|
gate({ leadAgentId: LEAD, ...args }, ctx) as Promise<any>;
|
|
181
234
|
|
|
@@ -677,6 +730,101 @@ describe("heartbeat-jev-gate with a provider", () => {
|
|
|
677
730
|
});
|
|
678
731
|
});
|
|
679
732
|
|
|
733
|
+
describe("heartbeat-jev-gate on Clef", () => {
|
|
734
|
+
const cloudflareOnly = { CLOUDFLARE_API_TOKEN: "x", CLOUDFLARE_ACCOUNT_ID: CF_ACCOUNT };
|
|
735
|
+
|
|
736
|
+
test("runs on Clef when only the Cloudflare token and account id are set", async () => {
|
|
737
|
+
const clef = clefDecide(0.15);
|
|
738
|
+
const { ctx, calls } = fakeCtx({ configs: cloudflareOnly, jev: clef.decide });
|
|
739
|
+
const verdict = await run(ctx);
|
|
740
|
+
// 0.15 is quiet only under Clef's own measured threshold, not Jev's 0.5 neighbourhood.
|
|
741
|
+
expect(verdict).toEqual({
|
|
742
|
+
quiet: true,
|
|
743
|
+
reason: "clef p(needs action)=0.150 < 0.2 via cloudflare",
|
|
744
|
+
});
|
|
745
|
+
expect(calls.decides[0]).toMatchObject({ provider: "cloudflare", model: "clef" });
|
|
746
|
+
expect(clef.urls).toEqual([
|
|
747
|
+
`https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT}/ai/run/@cf/cloudflare/clef`,
|
|
748
|
+
]);
|
|
749
|
+
expect(calls.prompts).toHaveLength(0);
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
test("at or above 0.2 Clef wakes the Lead, and the summary names Clef", async () => {
|
|
753
|
+
const { ctx } = fakeCtx({ configs: cloudflareOnly, jev: clefDecide(0.25).decide });
|
|
754
|
+
const verdict = await run(ctx);
|
|
755
|
+
expect(verdict.quiet).toBe(false);
|
|
756
|
+
expect(verdict.reason).toBe("clef p(needs action)=0.250 >= 0.2 via cloudflare");
|
|
757
|
+
expect(verdict.summary).toStartWith(
|
|
758
|
+
"Clef p(needs action) = 0.250 (threshold 0.2, cloudflare).",
|
|
759
|
+
);
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
test("CLOUDFLARE_API_KEY is accepted in place of CLOUDFLARE_API_TOKEN", async () => {
|
|
763
|
+
const { ctx } = fakeCtx({
|
|
764
|
+
configs: { CLOUDFLARE_API_KEY: "x", CLOUDFLARE_ACCOUNT_ID: CF_ACCOUNT },
|
|
765
|
+
jev: clefDecide(0.1).decide,
|
|
766
|
+
});
|
|
767
|
+
expect((await run(ctx)).reason).toContain("via cloudflare");
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
test("auto prefers Jev when a Jev key is also set", async () => {
|
|
771
|
+
const { ctx, calls } = fakeCtx({
|
|
772
|
+
configs: { ...cloudflareOnly, OPENROUTER_API_KEY: "x" },
|
|
773
|
+
jev: jevSays(0.1),
|
|
774
|
+
});
|
|
775
|
+
expect((await run(ctx)).reason).toContain("via openrouter");
|
|
776
|
+
expect(calls.decides[0]!.provider).toBe("openrouter");
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
test("an explicit cloudflare provider wins over a Jev key", async () => {
|
|
780
|
+
const { ctx, calls } = fakeCtx({
|
|
781
|
+
configs: {
|
|
782
|
+
...cloudflareOnly,
|
|
783
|
+
TYPESAFE_API_KEY: "x",
|
|
784
|
+
HEARTBEAT_JEV_GATE_PROVIDER: "cloudflare",
|
|
785
|
+
},
|
|
786
|
+
jev: clefDecide(0.1).decide,
|
|
787
|
+
});
|
|
788
|
+
expect((await run(ctx)).reason).toContain("via cloudflare");
|
|
789
|
+
expect(calls.decides[0]!.provider).toBe("cloudflare");
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
test("an unknown provider value falls back to auto, even an Object prototype name", async () => {
|
|
793
|
+
for (const pref of ["constructor", "toString", "jev"]) {
|
|
794
|
+
const { ctx } = fakeCtx({
|
|
795
|
+
configs: { TYPESAFE_API_KEY: "x", HEARTBEAT_JEV_GATE_PROVIDER: pref },
|
|
796
|
+
jev: jevSays(0.1),
|
|
797
|
+
});
|
|
798
|
+
expect((await run(ctx)).reason).toContain("via typesafe");
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
test("a configured threshold applies to Clef too", async () => {
|
|
803
|
+
const { ctx } = fakeCtx({
|
|
804
|
+
configs: { ...cloudflareOnly, HEARTBEAT_JEV_GATE_THRESHOLD: "0.5" },
|
|
805
|
+
jev: clefDecide(0.3).decide,
|
|
806
|
+
});
|
|
807
|
+
const verdict = await run(ctx, { dryRun: true });
|
|
808
|
+
expect(verdict.quiet).toBe(true);
|
|
809
|
+
expect(verdict.threshold).toBe(0.5);
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
test("a token without an account id is not a usable provider", async () => {
|
|
813
|
+
const { ctx, calls } = fakeCtx({ configs: { CLOUDFLARE_API_TOKEN: "x" } });
|
|
814
|
+
const verdict = await run(ctx, { dryRun: true });
|
|
815
|
+
expect(verdict.reason).toStartWith("jev-gate inactive:");
|
|
816
|
+
expect(calls.decides).toHaveLength(0);
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
test("a Clef error throws with the provider named, so the heartbeat fails open", async () => {
|
|
820
|
+
const { ctx } = fakeCtx({
|
|
821
|
+
configs: cloudflareOnly,
|
|
822
|
+
jev: jevFails("CLOUDFLARE_API_TOKEN was rejected by Cloudflare Workers AI (HTTP 401)."),
|
|
823
|
+
});
|
|
824
|
+
await expect(run(ctx)).rejects.toThrow("clef cloudflare: CLOUDFLARE_API_TOKEN was rejected");
|
|
825
|
+
});
|
|
826
|
+
});
|
|
827
|
+
|
|
680
828
|
describe("heartbeat-jev-gate without a provider", () => {
|
|
681
829
|
const inactive = (verdict: any) => {
|
|
682
830
|
expect(verdict.quiet).toBe(false);
|
|
@@ -697,6 +845,8 @@ describe("heartbeat-jev-gate without a provider", () => {
|
|
|
697
845
|
expect(prompt.questions[0].type).toBe("approval");
|
|
698
846
|
expect(prompt.questions[0].description).toContain("TYPESAFE_API_KEY");
|
|
699
847
|
expect(prompt.questions[0].description).toContain("OPENROUTER_API_KEY");
|
|
848
|
+
expect(prompt.questions[0].description).toContain("CLOUDFLARE_API_TOKEN");
|
|
849
|
+
expect(prompt.questions[0].description).toContain("CLOUDFLARE_ACCOUNT_ID");
|
|
700
850
|
expect(prompt.questions[0].description).toContain("$0.00004");
|
|
701
851
|
expect(kv.get("setup")).toMatchObject({ status: "asked", requestId: "req-1" });
|
|
702
852
|
|
|
@@ -755,13 +905,104 @@ describe("heartbeat-jev-gate without a provider", () => {
|
|
|
755
905
|
const verdict = await run(ctx, { dryRun: true });
|
|
756
906
|
inactive(verdict);
|
|
757
907
|
expect(verdict.dryRun).toBe(true);
|
|
758
|
-
expect(verdict.prompt.title).toContain("
|
|
908
|
+
expect(verdict.prompt.title).toContain("decision model");
|
|
759
909
|
expect(verdict.state.agents).toBe("- Online: 6 (4 idle, 2 busy), Offline: 5");
|
|
760
910
|
expect(calls.prompts).toHaveLength(0);
|
|
761
911
|
expect(calls.kvSets).toBe(0);
|
|
762
912
|
});
|
|
763
913
|
});
|
|
764
914
|
|
|
915
|
+
describe("heartbeat-jev-gate on the real system-one-decide tool", () => {
|
|
916
|
+
let agentId: string;
|
|
917
|
+
let prevBase: string | undefined;
|
|
918
|
+
const realFetch = globalThis.fetch;
|
|
919
|
+
beforeAll(async () => {
|
|
920
|
+
initDb(":memory:");
|
|
921
|
+
agentId = (await createAgent({ name: "jev-gate-decide", isLead: true, status: "idle" })).id;
|
|
922
|
+
await upsertSwarmConfig({ scope: "global", key: "CLOUDFLARE_ACCOUNT_ID", value: CF_ACCOUNT });
|
|
923
|
+
await upsertSwarmConfig({
|
|
924
|
+
scope: "global",
|
|
925
|
+
key: "CLOUDFLARE_API_TOKEN",
|
|
926
|
+
value: "cf-example-token",
|
|
927
|
+
isSecret: true,
|
|
928
|
+
});
|
|
929
|
+
prevBase = process.env.OPENROUTER_BASE_URL;
|
|
930
|
+
});
|
|
931
|
+
afterAll(() => {
|
|
932
|
+
globalThis.fetch = realFetch;
|
|
933
|
+
if (prevBase === undefined) delete process.env.OPENROUTER_BASE_URL;
|
|
934
|
+
else process.env.OPENROUTER_BASE_URL = prevBase;
|
|
935
|
+
closeDb();
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
/** `ctx.swarm.systemOne_decide` as the MCP bridge serves it, backed by the registered tool. */
|
|
939
|
+
const viaTool = () => {
|
|
940
|
+
const server = new McpServer({ name: "jev-gate-decide", version: "1" });
|
|
941
|
+
registerSystemOneDecideTool(server);
|
|
942
|
+
const tool = (server as any)._registeredTools["system-one-decide"];
|
|
943
|
+
return async (args: Record<string, any>) => {
|
|
944
|
+
const result = await tool.handler(args, {
|
|
945
|
+
sessionId: "script",
|
|
946
|
+
requestInfo: { headers: { "x-agent-id": agentId } },
|
|
947
|
+
});
|
|
948
|
+
return { success: true, status: 200, data: result.structuredContent };
|
|
949
|
+
};
|
|
950
|
+
};
|
|
951
|
+
|
|
952
|
+
test("auto skips OpenRouter behind a gateway, which the server refuses, and runs on Clef", async () => {
|
|
953
|
+
process.env.OPENROUTER_BASE_URL = "https://llm-gateway.example.com/v1";
|
|
954
|
+
const urls: string[] = [];
|
|
955
|
+
globalThis.fetch = (async (input: string | URL | Request) => {
|
|
956
|
+
urls.push(String(input));
|
|
957
|
+
return Response.json({
|
|
958
|
+
result: {
|
|
959
|
+
model: "clef",
|
|
960
|
+
answers: { act: { type: "noul", noul: 0.1 } },
|
|
961
|
+
usage: { input_tokens: 739, output_tokens: 0 },
|
|
962
|
+
},
|
|
963
|
+
success: true,
|
|
964
|
+
errors: [],
|
|
965
|
+
messages: [],
|
|
966
|
+
});
|
|
967
|
+
}) as typeof fetch;
|
|
968
|
+
const { ctx, calls } = fakeCtx({
|
|
969
|
+
configs: {
|
|
970
|
+
OPENROUTER_API_KEY: "x",
|
|
971
|
+
CLOUDFLARE_API_TOKEN: "x",
|
|
972
|
+
CLOUDFLARE_ACCOUNT_ID: CF_ACCOUNT,
|
|
973
|
+
},
|
|
974
|
+
jev: viaTool(),
|
|
975
|
+
});
|
|
976
|
+
|
|
977
|
+
const verdict = await run(ctx, { dryRun: true });
|
|
978
|
+
expect(verdict).toMatchObject({ quiet: true, provider: "cloudflare", threshold: 0.2 });
|
|
979
|
+
expect(verdict.skipped).toEqual([
|
|
980
|
+
expect.stringContaining("openrouter: OPENROUTER_BASE_URL points at a gateway"),
|
|
981
|
+
]);
|
|
982
|
+
expect(calls.decides.map((d) => d.provider)).toEqual(["openrouter", "cloudflare"]);
|
|
983
|
+
expect(urls).toEqual([
|
|
984
|
+
`https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT}/ai/run/@cf/cloudflare/clef`,
|
|
985
|
+
]);
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
test("an explicit openrouter choice behind a gateway still throws", async () => {
|
|
989
|
+
process.env.OPENROUTER_BASE_URL = "https://llm-gateway.example.com/v1";
|
|
990
|
+
const { ctx, calls } = fakeCtx({
|
|
991
|
+
configs: {
|
|
992
|
+
OPENROUTER_API_KEY: "x",
|
|
993
|
+
CLOUDFLARE_API_TOKEN: "x",
|
|
994
|
+
CLOUDFLARE_ACCOUNT_ID: CF_ACCOUNT,
|
|
995
|
+
HEARTBEAT_JEV_GATE_PROVIDER: "openrouter",
|
|
996
|
+
},
|
|
997
|
+
jev: viaTool(),
|
|
998
|
+
});
|
|
999
|
+
await expect(run(ctx)).rejects.toThrow(
|
|
1000
|
+
"jev openrouter: OPENROUTER_BASE_URL points at a gateway",
|
|
1001
|
+
);
|
|
1002
|
+
expect(calls.decides).toHaveLength(1);
|
|
1003
|
+
});
|
|
1004
|
+
});
|
|
1005
|
+
|
|
765
1006
|
describe("request-human-input detached", () => {
|
|
766
1007
|
beforeAll(() => initDb(":memory:"));
|
|
767
1008
|
afterAll(() => closeDb());
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
14
14
|
import { initAgentMail, isAgentMailEnabled, resetAgentMail } from "../agentmail/app";
|
|
15
|
+
import { validateConfigValue } from "../be/swarm-config-guard";
|
|
16
|
+
import { isBitbucketEnabled, resetBitbucket } from "../bitbucket/auth";
|
|
15
17
|
import { initGitHub, isGitHubEnabled, resetGitHub } from "../github/app";
|
|
16
18
|
import { isJiraEnabled, resetJira } from "../jira/app";
|
|
17
19
|
import { isLinearEnabled, resetLinear } from "../linear/app";
|
|
@@ -28,6 +30,8 @@ const ENV_KEYS = [
|
|
|
28
30
|
"LINEAR_ENABLED",
|
|
29
31
|
"AGENTMAIL_DISABLE",
|
|
30
32
|
"AGENTMAIL_WEBHOOK_SECRET",
|
|
33
|
+
"BITBUCKET_DISABLE",
|
|
34
|
+
"BITBUCKET_WEBHOOK_SECRET",
|
|
31
35
|
] as const;
|
|
32
36
|
const originalEnv = new Map(ENV_KEYS.map((key) => [key, process.env[key]]));
|
|
33
37
|
|
|
@@ -45,6 +49,7 @@ afterEach(() => {
|
|
|
45
49
|
resetAgentMail();
|
|
46
50
|
resetJira();
|
|
47
51
|
resetLinear();
|
|
52
|
+
resetBitbucket();
|
|
48
53
|
for (const key of ENV_KEYS) {
|
|
49
54
|
const value = originalEnv.get(key);
|
|
50
55
|
if (value === undefined) delete process.env[key];
|
|
@@ -185,6 +190,37 @@ describe("AgentMail AGENTMAIL_DISABLE", () => {
|
|
|
185
190
|
});
|
|
186
191
|
});
|
|
187
192
|
|
|
193
|
+
describe("Bitbucket BITBUCKET_DISABLE", () => {
|
|
194
|
+
test("every truthy spelling disables the integration", () => {
|
|
195
|
+
process.env.BITBUCKET_WEBHOOK_SECRET = "secret";
|
|
196
|
+
for (const spelling of TRUTHY_SPELLINGS) {
|
|
197
|
+
process.env.BITBUCKET_DISABLE = spelling;
|
|
198
|
+
expect(isBitbucketEnabled()).toBe(false);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test("non-disabling values keep the integration enabled", () => {
|
|
203
|
+
process.env.BITBUCKET_WEBHOOK_SECRET = "secret";
|
|
204
|
+
for (const value of NON_DISABLING) {
|
|
205
|
+
if (value === undefined) delete process.env.BITBUCKET_DISABLE;
|
|
206
|
+
else process.env.BITBUCKET_DISABLE = value;
|
|
207
|
+
expect(isBitbucketEnabled()).toBe(true);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("stays disabled without a webhook secret", () => {
|
|
212
|
+
expect(isBitbucketEnabled()).toBe(false);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("the config guard validates BITBUCKET_DISABLE as a boolean like GITLAB_DISABLE", () => {
|
|
216
|
+
for (const key of ["BITBUCKET_DISABLE", "GITLAB_DISABLE"]) {
|
|
217
|
+
expect(validateConfigValue(key, "true")).toBeNull();
|
|
218
|
+
expect(validateConfigValue(key, "false")).toBeNull();
|
|
219
|
+
expect(validateConfigValue(key, "maybe")).not.toBeNull();
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
188
224
|
describe("Slack SLACK_DISABLE", () => {
|
|
189
225
|
test("every truthy spelling reports disabled", () => {
|
|
190
226
|
for (const spelling of TRUTHY_SPELLINGS) {
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
* 3. Reading from the agent's `task:agent:*` namespace via the cookie is
|
|
12
12
|
* ALSO forced to the page namespace (the page can't see the agent's
|
|
13
13
|
* scratchpad).
|
|
14
|
+
* 4. Per-page cookies: two pages open in one browser each proxy as
|
|
15
|
+
* themselves, a header naming another page is refused, and the legacy
|
|
16
|
+
* `page_session` cookie still works for its own page.
|
|
14
17
|
*/
|
|
15
18
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
16
19
|
import { randomUUID } from "node:crypto";
|
|
@@ -123,11 +126,94 @@ async function launchPage(pageId: string): Promise<string> {
|
|
|
123
126
|
expect(res.status).toBe(204);
|
|
124
127
|
const setCookie = res.headers.get("set-cookie");
|
|
125
128
|
expect(setCookie).toBeTruthy();
|
|
126
|
-
const cookieValue = /
|
|
129
|
+
const cookieValue = /page_session_[A-Za-z0-9_-]+=([^;]+)/.exec(setCookie!)?.[1];
|
|
127
130
|
expect(cookieValue).toBeTruthy();
|
|
128
131
|
return cookieValue!;
|
|
129
132
|
}
|
|
130
133
|
|
|
134
|
+
/** What the browser SDK sends from `/p/<id>`: its own cookie plus its page id. */
|
|
135
|
+
function pageSessionHeaders(id: string, cookie: string): Record<string, string> {
|
|
136
|
+
return { Cookie: `page_session_${id}=${cookie}`, "X-Swarm-Page-Id": id };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function putClick(headers: Record<string, string>): Promise<Response> {
|
|
140
|
+
return fetch(`${BASE}/@swarm/api/kv/clicks`, {
|
|
141
|
+
method: "PUT",
|
|
142
|
+
headers: { "Content-Type": "application/json", ...headers },
|
|
143
|
+
body: JSON.stringify({ value: 1, valueType: "integer" }),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function namespaceOf(res: Response): Promise<string> {
|
|
148
|
+
expect(res.status).toBe(200);
|
|
149
|
+
return ((await res.json()) as { namespace: string }).namespace;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
describe("page proxy → kv with several pages open in one browser", () => {
|
|
153
|
+
test("two pages launched in one cookie jar each proxy as themselves, concurrently", async () => {
|
|
154
|
+
const idA = await createPage();
|
|
155
|
+
const idB = await createPage();
|
|
156
|
+
const jar = `page_session_${idA}=${await launchPage(idA)}; page_session_${idB}=${await launchPage(idB)}`;
|
|
157
|
+
|
|
158
|
+
const [resA, resB] = await Promise.all([
|
|
159
|
+
putClick({ Cookie: jar, "X-Swarm-Page-Id": idA }),
|
|
160
|
+
putClick({ Cookie: jar, "X-Swarm-Page-Id": idB }),
|
|
161
|
+
]);
|
|
162
|
+
expect(await namespaceOf(resA)).toBe(`task:page:${idA}`);
|
|
163
|
+
expect(await namespaceOf(resB)).toBe(`task:page:${idB}`);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("a header naming page B with only page A's session is rejected", async () => {
|
|
167
|
+
const idA = await createPage();
|
|
168
|
+
const idB = await createPage();
|
|
169
|
+
const cookieA = await launchPage(idA);
|
|
170
|
+
|
|
171
|
+
for (const cookie of [
|
|
172
|
+
`page_session_${idA}=${cookieA}`,
|
|
173
|
+
`page_session_${idB}=${cookieA}`,
|
|
174
|
+
`page_session=${cookieA}`,
|
|
175
|
+
]) {
|
|
176
|
+
const res = await putClick({ Cookie: cookie, "X-Swarm-Page-Id": idB });
|
|
177
|
+
expect(res.status).toBe(401);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("a header that cannot be a page id is rejected", async () => {
|
|
182
|
+
const id = await createPage();
|
|
183
|
+
const cookie = await launchPage(id);
|
|
184
|
+
const res = await putClick({
|
|
185
|
+
Cookie: `page_session_${id}=${cookie}; page_session=${cookie}`,
|
|
186
|
+
"X-Swarm-Page-Id": "../x",
|
|
187
|
+
});
|
|
188
|
+
expect(res.status).toBe(401);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("the legacy page_session cookie still works for its own page", async () => {
|
|
192
|
+
const id = await createPage();
|
|
193
|
+
const cookie = await launchPage(id);
|
|
194
|
+
// With the page named, and from a client that names no page at all.
|
|
195
|
+
const named = await putClick({ Cookie: `page_session=${cookie}`, "X-Swarm-Page-Id": id });
|
|
196
|
+
expect(await namespaceOf(named)).toBe(`task:page:${id}`);
|
|
197
|
+
const unnamed = await putClick({ Cookie: `page_session=${cookie}` });
|
|
198
|
+
expect(await namespaceOf(unnamed)).toBe(`task:page:${id}`);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("a same-host /p/<id> Referer selects the page when no header is sent", async () => {
|
|
202
|
+
const idA = await createPage();
|
|
203
|
+
const idB = await createPage();
|
|
204
|
+
const jar = `page_session_${idA}=${await launchPage(idA)}; page_session_${idB}=${await launchPage(idB)}`;
|
|
205
|
+
|
|
206
|
+
const res = await putClick({ Cookie: jar, Referer: `${BASE}/p/${idB}` });
|
|
207
|
+
expect(await namespaceOf(res)).toBe(`task:page:${idB}`);
|
|
208
|
+
|
|
209
|
+
// A cross-site Referer picks nothing, and no per-page cookie is used unnamed.
|
|
210
|
+
const crossSite = await putClick({ Cookie: jar, Referer: `https://evil.example/p/${idB}` });
|
|
211
|
+
expect(crossSite.status).toBe(401);
|
|
212
|
+
const unnamed = await putClick({ Cookie: jar });
|
|
213
|
+
expect(unnamed.status).toBe(401);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
131
217
|
describe("page proxy → kv", () => {
|
|
132
218
|
test("writes via the proxy land in task:page:<id> automatically", async () => {
|
|
133
219
|
const id = await createPage();
|
|
@@ -137,7 +223,7 @@ describe("page proxy → kv", () => {
|
|
|
137
223
|
method: "PUT",
|
|
138
224
|
headers: {
|
|
139
225
|
"Content-Type": "application/json",
|
|
140
|
-
|
|
226
|
+
...pageSessionHeaders(id, cookie),
|
|
141
227
|
},
|
|
142
228
|
body: JSON.stringify({ value: 1, valueType: "integer" }),
|
|
143
229
|
});
|
|
@@ -161,13 +247,13 @@ describe("page proxy → kv", () => {
|
|
|
161
247
|
const cookie = await launchPage(id);
|
|
162
248
|
const r1 = await fetch(`${BASE}/@swarm/api/kv/votes/incr`, {
|
|
163
249
|
method: "POST",
|
|
164
|
-
headers: { "Content-Type": "application/json",
|
|
250
|
+
headers: { "Content-Type": "application/json", ...pageSessionHeaders(id, cookie) },
|
|
165
251
|
body: JSON.stringify({ by: 2 }),
|
|
166
252
|
});
|
|
167
253
|
expect(r1.status).toBe(200);
|
|
168
254
|
const r2 = await fetch(`${BASE}/@swarm/api/kv/votes/incr`, {
|
|
169
255
|
method: "POST",
|
|
170
|
-
headers: { "Content-Type": "application/json",
|
|
256
|
+
headers: { "Content-Type": "application/json", ...pageSessionHeaders(id, cookie) },
|
|
171
257
|
body: JSON.stringify({}),
|
|
172
258
|
});
|
|
173
259
|
const v = (await r2.json()) as { value: number; namespace: string };
|
|
@@ -184,7 +270,7 @@ describe("page proxy → kv", () => {
|
|
|
184
270
|
const fakeNs = `task:agent:${agentId}`;
|
|
185
271
|
const put = await fetch(`${BASE}/@swarm/api/kv/_/${encodeURIComponent(fakeNs)}/escape`, {
|
|
186
272
|
method: "PUT",
|
|
187
|
-
headers: { "Content-Type": "application/json",
|
|
273
|
+
headers: { "Content-Type": "application/json", ...pageSessionHeaders(id, cookie) },
|
|
188
274
|
body: JSON.stringify({ value: "leaked", valueType: "string" }),
|
|
189
275
|
});
|
|
190
276
|
// The kv handler treats X-Page-Id as the highest-priority namespace
|
|
@@ -112,7 +112,7 @@ describe("POST /api/pages/:id/launch — password mode rejection (step-4)", () =
|
|
|
112
112
|
headers: { "X-Agent-ID": agentId },
|
|
113
113
|
});
|
|
114
114
|
expect(launch.status).toBe(204);
|
|
115
|
-
expect(launch.headers.get("set-cookie")).toContain("
|
|
115
|
+
expect(launch.headers.get("set-cookie")).toContain("page_session_");
|
|
116
116
|
});
|
|
117
117
|
|
|
118
118
|
test("public page → launch still issues a cookie (uniform path)", async () => {
|
|
@@ -135,6 +135,6 @@ describe("POST /api/pages/:id/launch — password mode rejection (step-4)", () =
|
|
|
135
135
|
headers: { "X-Agent-ID": agentId },
|
|
136
136
|
});
|
|
137
137
|
expect(launch.status).toBe(204);
|
|
138
|
-
expect(launch.headers.get("set-cookie")).toContain("
|
|
138
|
+
expect(launch.headers.get("set-cookie")).toContain("page_session_");
|
|
139
139
|
});
|
|
140
140
|
});
|