@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
|
@@ -134,17 +134,21 @@ afterAll(async () => {
|
|
|
134
134
|
describe("proxy allowlist for a guest session", () => {
|
|
135
135
|
test("allows the page's own record and its KV", async () => {
|
|
136
136
|
const own = await fetch(`${BASE}/@swarm/api/pages/${pageId}`, {
|
|
137
|
-
headers: { Cookie: `
|
|
137
|
+
headers: { Cookie: `page_session_${pageId}=${guestCookie}`, "X-Swarm-Page-Id": pageId },
|
|
138
138
|
});
|
|
139
139
|
expect(own.status).toBe(200);
|
|
140
140
|
const put = await fetch(`${BASE}/@swarm/api/kv/probe`, {
|
|
141
141
|
method: "PUT",
|
|
142
|
-
headers: {
|
|
142
|
+
headers: {
|
|
143
|
+
Cookie: `page_session_${pageId}=${guestCookie}`,
|
|
144
|
+
"X-Swarm-Page-Id": pageId,
|
|
145
|
+
"Content-Type": "application/json",
|
|
146
|
+
},
|
|
143
147
|
body: JSON.stringify({ value: "v" }),
|
|
144
148
|
});
|
|
145
149
|
expect(put.status).toBe(200);
|
|
146
150
|
const get = await fetch(`${BASE}/@swarm/api/kv/probe`, {
|
|
147
|
-
headers: { Cookie: `
|
|
151
|
+
headers: { Cookie: `page_session_${pageId}=${guestCookie}`, "X-Swarm-Page-Id": pageId },
|
|
148
152
|
});
|
|
149
153
|
expect(get.status).toBe(200);
|
|
150
154
|
});
|
|
@@ -162,7 +166,11 @@ describe("proxy allowlist for a guest session", () => {
|
|
|
162
166
|
test(`refuses ${method} ${path} with 403`, async () => {
|
|
163
167
|
const res = await fetch(`${BASE}/@swarm/api/${path}`, {
|
|
164
168
|
method,
|
|
165
|
-
headers: {
|
|
169
|
+
headers: {
|
|
170
|
+
Cookie: `page_session_${pageId}=${guestCookie}`,
|
|
171
|
+
"X-Swarm-Page-Id": pageId,
|
|
172
|
+
"Content-Type": "application/json",
|
|
173
|
+
},
|
|
166
174
|
body: method === "POST" ? "{}" : undefined,
|
|
167
175
|
});
|
|
168
176
|
expect(res.status).toBe(403);
|
|
@@ -206,13 +214,16 @@ describe("a password-page guest is isolated from another page", () => {
|
|
|
206
214
|
|
|
207
215
|
const unlock = await fetch(`${BASE}/p/${pageA}?key=open-sesame`);
|
|
208
216
|
expect(unlock.status).toBe(200);
|
|
209
|
-
const cookieA = /
|
|
217
|
+
const cookieA = /page_session_[A-Za-z0-9_-]+=([^;]+)/.exec(
|
|
218
|
+
unlock.headers.get("set-cookie") ?? "",
|
|
219
|
+
)?.[1];
|
|
210
220
|
expect(cookieA).toBeTruthy();
|
|
211
221
|
const asGuest = (path: string, init: RequestInit = {}) =>
|
|
212
222
|
fetch(`${BASE}/@swarm/api/${path}`, {
|
|
213
223
|
...init,
|
|
214
224
|
headers: {
|
|
215
|
-
Cookie: `
|
|
225
|
+
Cookie: `page_session_${pageA}=${cookieA}`,
|
|
226
|
+
"X-Swarm-Page-Id": pageA,
|
|
216
227
|
"Content-Type": "application/json",
|
|
217
228
|
"X-Page-Id": pageB,
|
|
218
229
|
"X-Agent-ID": agentId,
|
|
@@ -377,12 +388,12 @@ describe("server-side gates deny a guest and keep the operator and user", () =>
|
|
|
377
388
|
test("a user session through the proxy and an operator-launched session still work", async () => {
|
|
378
389
|
for (const cookie of [userCookie, operatorCookie]) {
|
|
379
390
|
const res = await fetch(`${BASE}/@swarm/api/tasks`, {
|
|
380
|
-
headers: { Cookie: `
|
|
391
|
+
headers: { Cookie: `page_session_${pageId}=${cookie}`, "X-Swarm-Page-Id": pageId },
|
|
381
392
|
});
|
|
382
393
|
expect(res.status).toBe(200);
|
|
383
394
|
}
|
|
384
395
|
const whoami = await fetch(`${BASE}/@swarm/api/whoami`, {
|
|
385
|
-
headers: { Cookie: `
|
|
396
|
+
headers: { Cookie: `page_session_${pageId}=${userCookie}`, "X-Swarm-Page-Id": pageId },
|
|
386
397
|
});
|
|
387
398
|
expect(((await whoami.json()) as { kind: string }).kind).toBe("user");
|
|
388
399
|
});
|
|
@@ -122,7 +122,7 @@ describe("/@swarm/api/* proxy — authed-mode page", () => {
|
|
|
122
122
|
expect(launch.status).toBe(204);
|
|
123
123
|
const setCookie = launch.headers.get("set-cookie");
|
|
124
124
|
expect(setCookie).toBeTruthy();
|
|
125
|
-
const cookieValue = /
|
|
125
|
+
const cookieValue = /page_session_[A-Za-z0-9_-]+=([^;]+)/.exec(setCookie!)?.[1];
|
|
126
126
|
expect(cookieValue).toBeTruthy();
|
|
127
127
|
|
|
128
128
|
// Drive the proxy with the cookie — should resolve to the page owner's
|
|
@@ -182,11 +182,13 @@ type BrowserSdk = {
|
|
|
182
182
|
};
|
|
183
183
|
};
|
|
184
184
|
|
|
185
|
-
function browserSdk(cookie: string, spoofedAgentId?: string): BrowserSdk {
|
|
185
|
+
function browserSdk(pageId: string, cookie: string, spoofedAgentId?: string): BrowserSdk {
|
|
186
186
|
const window: { swarmSdk?: BrowserSdk } = {};
|
|
187
187
|
const pageFetch = (input: string, init?: RequestInit) => {
|
|
188
188
|
const headers = new Headers(init?.headers);
|
|
189
|
-
|
|
189
|
+
// The SDK must name its own page; the test only supplies the cookie jar.
|
|
190
|
+
if (headers.get("X-Swarm-Page-Id") !== pageId) throw new Error("SDK did not name its page");
|
|
191
|
+
headers.set("Cookie", `page_session_${pageId}=${cookie}`);
|
|
190
192
|
if (spoofedAgentId) headers.set("X-Agent-ID", spoofedAgentId);
|
|
191
193
|
return fetch(new URL(input, BASE), { ...init, headers });
|
|
192
194
|
};
|
|
@@ -195,6 +197,7 @@ function browserSdk(cookie: string, spoofedAgentId?: string): BrowserSdk {
|
|
|
195
197
|
encodeURIComponent,
|
|
196
198
|
fetch: pageFetch,
|
|
197
199
|
URLSearchParams,
|
|
200
|
+
location: { pathname: `/p/${pageId}` },
|
|
198
201
|
window,
|
|
199
202
|
});
|
|
200
203
|
if (!window.swarmSdk) throw new Error("browser SDK did not initialize");
|
|
@@ -207,7 +210,9 @@ async function launchPageCookie(id: string, bearer = API_KEY): Promise<string> {
|
|
|
207
210
|
headers: { Authorization: `Bearer ${bearer}` },
|
|
208
211
|
});
|
|
209
212
|
expect(launch.status).toBe(204);
|
|
210
|
-
const cookie = /
|
|
213
|
+
const cookie = /page_session_[A-Za-z0-9_-]+=([^;]+)/.exec(
|
|
214
|
+
launch.headers.get("set-cookie") ?? "",
|
|
215
|
+
)?.[1];
|
|
211
216
|
if (!cookie) throw new Error("failed to mint page session cookie");
|
|
212
217
|
return cookie;
|
|
213
218
|
}
|
|
@@ -243,14 +248,14 @@ describe("/api/pages/:id/launch", () => {
|
|
|
243
248
|
expect(res.status).toBe(204);
|
|
244
249
|
const cookie = res.headers.get("set-cookie");
|
|
245
250
|
expect(cookie).toBeTruthy();
|
|
246
|
-
expect(cookie!).toContain("
|
|
251
|
+
expect(cookie!).toContain("page_session_");
|
|
247
252
|
expect(cookie!).toContain("HttpOnly");
|
|
248
253
|
expect(cookie!).toContain("Path=/");
|
|
249
254
|
expect(cookie!).toContain("Max-Age=3600");
|
|
250
255
|
// In dev (NODE_ENV != production) the cookie should be SameSite=Lax sans Secure.
|
|
251
256
|
expect(cookie!).toContain("SameSite=Lax");
|
|
252
257
|
expect(cookie!).not.toMatch(/\bSecure\b/);
|
|
253
|
-
const cookieValue = /
|
|
258
|
+
const cookieValue = /page_session_[A-Za-z0-9_-]+=([^;]+)/.exec(cookie!)?.[1];
|
|
254
259
|
const payload = await verifyPageSession(cookieValue);
|
|
255
260
|
expect(payload?.uid).toBeUndefined();
|
|
256
261
|
expect(payload?.name).toMatch(/^guest-/);
|
|
@@ -294,7 +299,9 @@ describe("/@swarm/api/* proxy", () => {
|
|
|
294
299
|
headers: { Authorization: `Bearer ${viewer.token}` },
|
|
295
300
|
});
|
|
296
301
|
expect(launch.status).toBe(204);
|
|
297
|
-
const cookieValue = /
|
|
302
|
+
const cookieValue = /page_session_[A-Za-z0-9_-]+=([^;]+)/.exec(
|
|
303
|
+
launch.headers.get("set-cookie") ?? "",
|
|
304
|
+
)?.[1];
|
|
298
305
|
expect(cookieValue).toBeTruthy();
|
|
299
306
|
const payload = await verifyPageSession(cookieValue);
|
|
300
307
|
expect(payload?.uid).toBe(viewer.id);
|
|
@@ -322,7 +329,7 @@ describe("/@swarm/api/* proxy", () => {
|
|
|
322
329
|
const setCookie = launch.headers.get("set-cookie");
|
|
323
330
|
expect(setCookie).toBeTruthy();
|
|
324
331
|
|
|
325
|
-
const cookieValue = /
|
|
332
|
+
const cookieValue = /page_session_[A-Za-z0-9_-]+=([^;]+)/.exec(setCookie!)?.[1];
|
|
326
333
|
expect(cookieValue).toBeTruthy();
|
|
327
334
|
|
|
328
335
|
const res = await fetch(`${BASE}/@swarm/api/agents/${agentId}`, {
|
|
@@ -410,7 +417,7 @@ describe("/@swarm/api/* page execution context", () => {
|
|
|
410
417
|
|
|
411
418
|
// Execute the shipped browser SDK with a forged client identity. The
|
|
412
419
|
// proxy must ignore that header and derive memory scope from the page.
|
|
413
|
-
const sdk = browserSdk(cookie, otherAgentId);
|
|
420
|
+
const sdk = browserSdk(pageId, cookie, otherAgentId);
|
|
414
421
|
const search = await sdk.memory.search({ query: searchToken, scope: "agent", limit: 10 });
|
|
415
422
|
expect(search.results.map((memory) => memory.id)).toContain(ownerMemoryId);
|
|
416
423
|
expect(search.results.map((memory) => memory.id)).not.toContain(otherMemoryId);
|
|
@@ -432,7 +439,7 @@ describe("/@swarm/api/* page execution context", () => {
|
|
|
432
439
|
const viewer = await createUserToken(`Page Viewer ${randomUUID().slice(0, 8)}`);
|
|
433
440
|
const pageId = await createPage();
|
|
434
441
|
const cookie = await launchPageCookie(pageId, viewer.token);
|
|
435
|
-
const sdk = browserSdk(cookie);
|
|
442
|
+
const sdk = browserSdk(pageId, cookie);
|
|
436
443
|
|
|
437
444
|
const search = await sdk.memory.search({ query: searchToken, scope: "agent", limit: 5 });
|
|
438
445
|
expect(search.results.map((memory) => memory.id)).toContain(ownerMemoryId);
|
|
@@ -506,7 +513,7 @@ describe("/@swarm/api/* proxy — suffix normalization", () => {
|
|
|
506
513
|
headers: { Authorization: `Bearer ${API_KEY}`, "X-Agent-ID": agentId },
|
|
507
514
|
});
|
|
508
515
|
const setCookie = launch.headers.get("set-cookie");
|
|
509
|
-
const cookieValue = /
|
|
516
|
+
const cookieValue = /page_session_[A-Za-z0-9_-]+=([^;]+)/.exec(setCookie!)?.[1];
|
|
510
517
|
if (!cookieValue) throw new Error("failed to mint test cookie");
|
|
511
518
|
return cookieValue;
|
|
512
519
|
}
|
|
@@ -3,7 +3,15 @@ import { existsSync, mkdtempSync, rmSync } from "node:fs";
|
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import type { PageSessionPayload } from "../utils/page-session";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
extractPageSession,
|
|
8
|
+
issuePageSessionCookie,
|
|
9
|
+
MAX_PAGE_SESSION_COOKIES,
|
|
10
|
+
pageSessionSetCookies,
|
|
11
|
+
parseCookieHeader,
|
|
12
|
+
signPageSession,
|
|
13
|
+
verifyPageSession,
|
|
14
|
+
} from "../utils/page-session";
|
|
7
15
|
|
|
8
16
|
const ORIGINAL_SECRET = process.env.PAGE_SESSION_SECRET;
|
|
9
17
|
const ORIGINAL_API_KEY = process.env.API_KEY;
|
|
@@ -266,3 +274,96 @@ describe("parseCookieHeader", () => {
|
|
|
266
274
|
expect(parseCookieHeader("xxpage_session=evil; other=ok", "page_session")).toBeUndefined();
|
|
267
275
|
});
|
|
268
276
|
});
|
|
277
|
+
|
|
278
|
+
describe("per-page session cookies", () => {
|
|
279
|
+
const exp = () => Math.floor(Date.now() / 1000) + 3600;
|
|
280
|
+
const req = (cookie: string) => ({ headers: { cookie } });
|
|
281
|
+
|
|
282
|
+
test("issuance names the cookie after the page", async () => {
|
|
283
|
+
const header = await issuePageSessionCookie("pagea", { dev: true });
|
|
284
|
+
expect(header.startsWith("page_session_pagea=")).toBe(true);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test("each page reads its own cookie from a shared jar", async () => {
|
|
288
|
+
const a = await signPageSession({ pageId: "pagea", exp: exp() });
|
|
289
|
+
const b = await signPageSession({ pageId: "pageb", exp: exp() });
|
|
290
|
+
const jar = req(`page_session_pagea=${a}; page_session_pageb=${b}`);
|
|
291
|
+
expect((await extractPageSession(jar, "pagea"))?.token).toBe(a);
|
|
292
|
+
expect((await extractPageSession(jar, "pageb"))?.token).toBe(b);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
test("the legacy page_session cookie is accepted only for the page it names", async () => {
|
|
296
|
+
const a = await signPageSession({ pageId: "pagea", exp: exp() });
|
|
297
|
+
expect((await extractPageSession(req(`page_session=${a}`), "pagea"))?.payload.pageId).toBe(
|
|
298
|
+
"pagea",
|
|
299
|
+
);
|
|
300
|
+
expect(await extractPageSession(req(`page_session=${a}`), "pageb")).toBeNull();
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test("a token for another page under this page's name is ignored", async () => {
|
|
304
|
+
const a = await signPageSession({ pageId: "pagea", exp: exp() });
|
|
305
|
+
expect(await extractPageSession(req(`page_session_pageb=${a}`), "pageb")).toBeNull();
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test("an id that is not cookie-name safe is refused", async () => {
|
|
309
|
+
const a = await signPageSession({ pageId: "a=b", exp: exp() });
|
|
310
|
+
expect(await extractPageSession(req(`page_session=${a}`), "a=b")).toBeNull();
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
describe("pageSessionSetCookies", () => {
|
|
315
|
+
const now = () => Math.floor(Date.now() / 1000);
|
|
316
|
+
const cookieFor = async (pageId: string, exp = now() + 3600) =>
|
|
317
|
+
`page_session_${pageId}=${await signPageSession({ pageId, exp })}`;
|
|
318
|
+
const evicted = (lines: string[]) =>
|
|
319
|
+
lines.slice(1).map((line) => {
|
|
320
|
+
expect(line).toContain("Max-Age=0");
|
|
321
|
+
return line.split("=")[0];
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
test("issues only the new session when the jar is under the cap", async () => {
|
|
325
|
+
const jar = await cookieFor("pagea");
|
|
326
|
+
const lines = await pageSessionSetCookies({ headers: { cookie: jar } }, "pageb", { dev: true });
|
|
327
|
+
expect(lines).toHaveLength(1);
|
|
328
|
+
expect(lines[0]!.startsWith("page_session_pageb=")).toBe(true);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
test("evicts the oldest sessions beyond the cap", async () => {
|
|
332
|
+
const ids = Array.from({ length: MAX_PAGE_SESSION_COOKIES + 4 }, (_, i) => `page${i}`);
|
|
333
|
+
// page0 expires soonest, so it is the oldest launch.
|
|
334
|
+
const jar = (await Promise.all(ids.map((id, i) => cookieFor(id, now() + 1000 + i)))).join("; ");
|
|
335
|
+
const lines = await pageSessionSetCookies({ headers: { cookie: jar } }, "fresh", { dev: true });
|
|
336
|
+
expect(evicted(lines).sort()).toEqual(
|
|
337
|
+
["page0", "page1", "page2", "page3", "page4"].map((id) => `page_session_${id}`).sort(),
|
|
338
|
+
);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test("breaks same-second ties toward the cookie listed later", async () => {
|
|
342
|
+
const exp = now() + 3600;
|
|
343
|
+
const ids = Array.from({ length: MAX_PAGE_SESSION_COOKIES }, (_, i) => `tie${i}`);
|
|
344
|
+
const jar = (await Promise.all(ids.map((id) => cookieFor(id, exp)))).join("; ");
|
|
345
|
+
const lines = await pageSessionSetCookies({ headers: { cookie: jar } }, "fresh", { dev: true });
|
|
346
|
+
expect(evicted(lines)).toEqual(["page_session_tie0"]);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
test("evicts expired, forged and misnamed sessions but keeps legacy and own cookies", async () => {
|
|
350
|
+
const jar = [
|
|
351
|
+
await cookieFor("expired", now() - 10),
|
|
352
|
+
`page_session_forged=${await forgeToken({ pageId: "forged", exp: now() + 3600 }, "x")}`,
|
|
353
|
+
`page_session_misnamed=${await signPageSession({ pageId: "other", exp: now() + 3600 })}`,
|
|
354
|
+
`page_session=${await signPageSession({ pageId: "legacy", exp: now() + 3600 })}`,
|
|
355
|
+
await cookieFor("fresh"),
|
|
356
|
+
await cookieFor("kept"),
|
|
357
|
+
].join("; ");
|
|
358
|
+
const lines = await pageSessionSetCookies({ headers: { cookie: jar } }, "fresh", {
|
|
359
|
+
dev: false,
|
|
360
|
+
});
|
|
361
|
+
expect(evicted(lines).sort()).toEqual([
|
|
362
|
+
"page_session_expired",
|
|
363
|
+
"page_session_forged",
|
|
364
|
+
"page_session_misnamed",
|
|
365
|
+
]);
|
|
366
|
+
// Prod eviction cookies carry the same SameSite=None; Secure attributes.
|
|
367
|
+
for (const line of lines) expect(line).toContain("SameSite=None; Secure");
|
|
368
|
+
});
|
|
369
|
+
});
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
* 2. `GET /p/:id` without cookie → 401.
|
|
6
6
|
* 3. `POST /api/pages/:id/launch` (bearer) → 204 + Set-Cookie.
|
|
7
7
|
* 4. `GET /p/:id` with cookie → 200 + SDK injected.
|
|
8
|
-
* 5.
|
|
9
|
-
* 6.
|
|
8
|
+
* 5. A session signed for a DIFFERENT page id is ignored → 401.
|
|
9
|
+
* 6. Two launched pages both load from one cookie jar.
|
|
10
|
+
* 7. `GET /p/:id.json` with cookie → 200 + JSON metadata.
|
|
10
11
|
*
|
|
11
12
|
* Uses the same in-process handler wiring as `pages-public-html.test.ts` so
|
|
12
13
|
* we don't have to boot the full http server. The proxy in
|
|
@@ -26,7 +27,7 @@ import { closeDb, initDb } from "../be/db";
|
|
|
26
27
|
import { handlePages } from "../http/pages";
|
|
27
28
|
import { handlePagesPublic } from "../http/pages-public";
|
|
28
29
|
import { getPathSegments, parseQueryParams } from "../http/utils";
|
|
29
|
-
import { signPageSession } from "../utils/page-session";
|
|
30
|
+
import { MAX_PAGE_SESSION_COOKIES, signPageSession } from "../utils/page-session";
|
|
30
31
|
import { listenOnFreePort } from "./test-net";
|
|
31
32
|
|
|
32
33
|
const TEST_DB_PATH = "./test-pages-authed-mode.sqlite";
|
|
@@ -47,7 +48,7 @@ function createTestServer(): Server {
|
|
|
47
48
|
/** Pull the cookie value out of a Set-Cookie header line. */
|
|
48
49
|
function extractCookieValue(setCookie: string | null): string {
|
|
49
50
|
expect(setCookie).toBeTruthy();
|
|
50
|
-
const match = /
|
|
51
|
+
const match = /page_session_[A-Za-z0-9_-]+=([^;]+)/.exec(setCookie!);
|
|
51
52
|
expect(match).toBeTruthy();
|
|
52
53
|
return match![1]!;
|
|
53
54
|
}
|
|
@@ -132,7 +133,7 @@ describe("GET /p/:id — authed mode cookie gate (step-4)", () => {
|
|
|
132
133
|
expect(text).toContain("class SwarmSDK");
|
|
133
134
|
});
|
|
134
135
|
|
|
135
|
-
test("
|
|
136
|
+
test("a session signed for another page is ignored → 401", async () => {
|
|
136
137
|
const idA = await createAuthedPage("page-a");
|
|
137
138
|
const idB = await createAuthedPage("page-b");
|
|
138
139
|
|
|
@@ -140,13 +141,82 @@ describe("GET /p/:id — authed mode cookie gate (step-4)", () => {
|
|
|
140
141
|
const exp = Math.floor(Date.now() / 1000) + 3600;
|
|
141
142
|
const tokenForA = await signPageSession({ pageId: idA, exp });
|
|
142
143
|
|
|
143
|
-
// … and try to use it for page B.
|
|
144
|
-
const
|
|
145
|
-
headers: { Cookie:
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
144
|
+
// … and try to use it for page B, under the legacy name and under B's own name.
|
|
145
|
+
for (const cookie of [`page_session=${tokenForA}`, `page_session_${idB}=${tokenForA}`]) {
|
|
146
|
+
const res = await fetch(`${BASE}/p/${idB}`, { headers: { Cookie: cookie } });
|
|
147
|
+
expect(res.status).toBe(401);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("two launched pages both load from one cookie jar", async () => {
|
|
152
|
+
const idA = await createAuthedPage("tab-a");
|
|
153
|
+
const idB = await createAuthedPage("tab-b");
|
|
154
|
+
const jar: string[] = [];
|
|
155
|
+
for (const id of [idA, idB]) {
|
|
156
|
+
const launch = await fetch(`${BASE}/api/pages/${id}/launch`, {
|
|
157
|
+
method: "POST",
|
|
158
|
+
headers: { "X-Agent-ID": agentId },
|
|
159
|
+
});
|
|
160
|
+
expect(launch.status).toBe(204);
|
|
161
|
+
const setCookie = launch.headers.get("set-cookie")!;
|
|
162
|
+
expect(setCookie.startsWith(`page_session_${id}=`)).toBe(true);
|
|
163
|
+
jar.push(setCookie.split(";")[0]!);
|
|
164
|
+
}
|
|
165
|
+
const cookie = jar.join("; ");
|
|
166
|
+
for (const id of [idA, idB]) {
|
|
167
|
+
const res = await fetch(`${BASE}/p/${id}`, { headers: { Cookie: cookie } });
|
|
168
|
+
expect(res.status).toBe(200);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("90 launches from one browser keep the Cookie header bounded", async () => {
|
|
173
|
+
// A browser jar: Set-Cookie with Max-Age=0 deletes, anything else upserts.
|
|
174
|
+
const jar = new Map<string, string>();
|
|
175
|
+
const cookieHeader = () => [...jar].map(([name, value]) => `${name}=${value}`).join("; ");
|
|
176
|
+
const ids: string[] = [];
|
|
177
|
+
for (let i = 0; i < 90; i++) {
|
|
178
|
+
const id = await createAuthedPage(`overflow-${i}`);
|
|
179
|
+
ids.push(id);
|
|
180
|
+
const launch = await fetch(`${BASE}/api/pages/${id}/launch`, {
|
|
181
|
+
method: "POST",
|
|
182
|
+
headers: { "X-Agent-ID": agentId, Cookie: cookieHeader() },
|
|
183
|
+
});
|
|
184
|
+
expect(launch.status).toBe(204);
|
|
185
|
+
for (const line of launch.headers.getSetCookie()) {
|
|
186
|
+
const [pair] = line.split(";");
|
|
187
|
+
const eq = pair!.indexOf("=");
|
|
188
|
+
const name = pair!.slice(0, eq);
|
|
189
|
+
if (/Max-Age=0(;|$)/.test(line)) jar.delete(name);
|
|
190
|
+
else jar.set(name, pair!.slice(eq + 1));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
expect(jar.size).toBe(MAX_PAGE_SESSION_COOKIES);
|
|
195
|
+
expect(cookieHeader().length).toBeLessThan(8 * 1024);
|
|
196
|
+
// The newest launches keep their sessions; the oldest were evicted.
|
|
197
|
+
for (const id of ids.slice(-MAX_PAGE_SESSION_COOKIES)) {
|
|
198
|
+
const res = await fetch(`${BASE}/p/${id}`, { headers: { Cookie: cookieHeader() } });
|
|
199
|
+
expect(res.status).toBe(200);
|
|
200
|
+
}
|
|
201
|
+
expect(jar.has(`page_session_${ids[0]}`)).toBe(false);
|
|
202
|
+
|
|
203
|
+
// Control: the same sessions, operator-shaped as the dashboard mints them,
|
|
204
|
+
// overflow the 16 KB header limit when nothing evicts them.
|
|
205
|
+
const unbounded = (
|
|
206
|
+
await Promise.all(
|
|
207
|
+
ids.map(async (id) => {
|
|
208
|
+
const token = await signPageSession({
|
|
209
|
+
pageId: id,
|
|
210
|
+
exp: Math.floor(Date.now() / 1000) + 3600,
|
|
211
|
+
name: "guest-0123abcd",
|
|
212
|
+
op: true,
|
|
213
|
+
});
|
|
214
|
+
return `page_session_${id}=${token}`;
|
|
215
|
+
}),
|
|
216
|
+
)
|
|
217
|
+
).join("; ");
|
|
218
|
+
const overflow = await fetch(`${BASE}/p/${ids.at(-1)}`, { headers: { Cookie: unbounded } });
|
|
219
|
+
expect(overflow.status).toBe(431);
|
|
150
220
|
});
|
|
151
221
|
|
|
152
222
|
test("/p/:id.json with cookie → 200 + JSON metadata", async () => {
|
|
@@ -47,7 +47,7 @@ function createTestServer(): Server {
|
|
|
47
47
|
|
|
48
48
|
function extractCookieValue(setCookie: string | null): string {
|
|
49
49
|
expect(setCookie).toBeTruthy();
|
|
50
|
-
const match = /
|
|
50
|
+
const match = /page_session_[A-Za-z0-9_-]+=([^;]+)/.exec(setCookie!);
|
|
51
51
|
expect(match).toBeTruthy();
|
|
52
52
|
return match![1]!;
|
|
53
53
|
}
|
|
@@ -140,7 +140,7 @@ describe("GET /p/:id — password mode (step-5)", () => {
|
|
|
140
140
|
expect(res.status).toBe(200);
|
|
141
141
|
expect(res.headers.get("content-type")?.toLowerCase()).toContain("text/html");
|
|
142
142
|
const cookieHeader = res.headers.get("set-cookie");
|
|
143
|
-
expect(cookieHeader).toContain("
|
|
143
|
+
expect(cookieHeader).toContain("page_session_");
|
|
144
144
|
const text = await res.text();
|
|
145
145
|
expect(text).toContain("<h1>open sesame</h1>");
|
|
146
146
|
// BROWSER_SDK_JS sentinel.
|
|
@@ -158,7 +158,7 @@ describe("GET /p/:id — password mode (step-5)", () => {
|
|
|
158
158
|
headers: { Authorization: `Basic ${basic}` },
|
|
159
159
|
});
|
|
160
160
|
expect(res.status).toBe(200);
|
|
161
|
-
expect(res.headers.get("set-cookie")).toContain("
|
|
161
|
+
expect(res.headers.get("set-cookie")).toContain("page_session_");
|
|
162
162
|
const text = await res.text();
|
|
163
163
|
expect(text).toContain("<h1>via basic</h1>");
|
|
164
164
|
});
|
|
@@ -222,7 +222,7 @@ describe("GET /p/:id — password mode (step-5)", () => {
|
|
|
222
222
|
const id = await createPasswordPage("json-key", "swordfish");
|
|
223
223
|
const res = await fetch(`${BASE}/p/${id}.json?key=swordfish`);
|
|
224
224
|
expect(res.status).toBe(200);
|
|
225
|
-
expect(res.headers.get("set-cookie")).toContain("
|
|
225
|
+
expect(res.headers.get("set-cookie")).toContain("page_session_");
|
|
226
226
|
});
|
|
227
227
|
|
|
228
228
|
test("cross-page cookie reuse → 401 + password prompt (cookie is silently ignored for password mode)", async () => {
|
|
@@ -252,7 +252,7 @@ describe("GET /p/:id — password mode (step-5)", () => {
|
|
|
252
252
|
headers: { Cookie: `page_session=${cookieForA}` },
|
|
253
253
|
});
|
|
254
254
|
expect(recover.status).toBe(200);
|
|
255
|
-
expect(recover.headers.get("set-cookie")).toContain("
|
|
255
|
+
expect(recover.headers.get("set-cookie")).toContain("page_session_");
|
|
256
256
|
});
|
|
257
257
|
|
|
258
258
|
test("malformed Basic header (no colon) → 401 (no crash)", async () => {
|
|
@@ -340,8 +340,14 @@ describe("bounded codemode", () => {
|
|
|
340
340
|
undefined,
|
|
341
341
|
fakeCtx().ctx,
|
|
342
342
|
);
|
|
343
|
-
|
|
344
|
-
expect(
|
|
343
|
+
const elapsed = Date.now() - started;
|
|
344
|
+
expect(elapsed).toBeGreaterThanOrEqual(250);
|
|
345
|
+
expect(elapsed).toBeLessThan(5_000);
|
|
346
|
+
// pi 1.4 queues the script for a worker; our deadline ends the wait.
|
|
347
|
+
expect(textOf(result)).toContain(
|
|
348
|
+
"Script aborted: Codemode script exceeded its 300 ms deadline",
|
|
349
|
+
);
|
|
350
|
+
expect(textOf(result)).toContain("The script did not start.");
|
|
345
351
|
} finally {
|
|
346
352
|
holders.abort(new Error("test done"));
|
|
347
353
|
await Promise.all(holding);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { CHILD_PROCESS_TEST_BUDGET_MS, expectChildOk, runChild } from "./test-proc";
|
|
6
|
+
|
|
7
|
+
test(
|
|
8
|
+
"preload clears inherited worker configuration before test files load",
|
|
9
|
+
async () => {
|
|
10
|
+
const directory = await mkdtemp(join(tmpdir(), "swarm-preload-env-"));
|
|
11
|
+
const fixture = join(directory, "worker-env.test.ts");
|
|
12
|
+
const keys = [
|
|
13
|
+
"SLACK_RENDER_V2",
|
|
14
|
+
"SLACK_STATUS_ICON_COMPLETED",
|
|
15
|
+
"SLACK_FUTURE_SETTING",
|
|
16
|
+
"COMB_ENABLED",
|
|
17
|
+
"APP_URL",
|
|
18
|
+
"AGENT_FS_API_URL",
|
|
19
|
+
"AGENT_FS_LIVE_URL",
|
|
20
|
+
"AGENT_FS_DEFAULT_ORG_ID",
|
|
21
|
+
"AGENT_FS_DEFAULT_DRIVE_ID",
|
|
22
|
+
"SWARM_ORG_NAME",
|
|
23
|
+
"SWARM_ORG_WEBSITE",
|
|
24
|
+
];
|
|
25
|
+
try {
|
|
26
|
+
await Bun.write(
|
|
27
|
+
fixture,
|
|
28
|
+
`import { expect, test } from "bun:test";
|
|
29
|
+
const inherited = ${JSON.stringify(keys)}.filter(key => process.env[key] !== undefined);
|
|
30
|
+
test("worker defaults are cleared before module evaluation", () => {
|
|
31
|
+
expect(inherited).toEqual([]);
|
|
32
|
+
expect(process.env.PRELOAD_ENV_CONTROL).toBe("preserved");
|
|
33
|
+
process.env.SLACK_STATUS_ICON_COMPLETED = "test-icon";
|
|
34
|
+
});
|
|
35
|
+
test("suite configuration survives between tests", () => {
|
|
36
|
+
expect(process.env.SLACK_STATUS_ICON_COMPLETED).toBe("test-icon");
|
|
37
|
+
});
|
|
38
|
+
`,
|
|
39
|
+
);
|
|
40
|
+
const result = expectChildOk(
|
|
41
|
+
await runChild([process.execPath, "test", fixture], {
|
|
42
|
+
cwd: join(import.meta.dir, "../.."),
|
|
43
|
+
env: {
|
|
44
|
+
...process.env,
|
|
45
|
+
...Object.fromEntries(keys.map((key) => [key, "inherited-worker-setting"])),
|
|
46
|
+
PRELOAD_ENV_CONTROL: "preserved",
|
|
47
|
+
},
|
|
48
|
+
}),
|
|
49
|
+
"test runner with inherited worker env",
|
|
50
|
+
);
|
|
51
|
+
expect(result.stderr).toContain("2 pass");
|
|
52
|
+
} finally {
|
|
53
|
+
await rm(directory, { recursive: true, force: true });
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
CHILD_PROCESS_TEST_BUDGET_MS,
|
|
57
|
+
);
|
package/src/tests/preload.ts
CHANGED
|
@@ -22,6 +22,27 @@ import { startHangWatchdog } from "./hang-watchdog";
|
|
|
22
22
|
|
|
23
23
|
startHangWatchdog();
|
|
24
24
|
|
|
25
|
+
// Worker deployments configure Slack rendering, links, and organization defaults.
|
|
26
|
+
// Start tests from the same unset defaults as CI; suites can still opt in by
|
|
27
|
+
// setting their own values after the preload runs.
|
|
28
|
+
for (const key of Object.keys(process.env)) {
|
|
29
|
+
if (
|
|
30
|
+
key.startsWith("SLACK_") ||
|
|
31
|
+
[
|
|
32
|
+
"COMB_ENABLED",
|
|
33
|
+
"APP_URL",
|
|
34
|
+
"AGENT_FS_API_URL",
|
|
35
|
+
"AGENT_FS_LIVE_URL",
|
|
36
|
+
"AGENT_FS_DEFAULT_ORG_ID",
|
|
37
|
+
"AGENT_FS_DEFAULT_DRIVE_ID",
|
|
38
|
+
"SWARM_ORG_NAME",
|
|
39
|
+
"SWARM_ORG_WEBSITE",
|
|
40
|
+
].includes(key)
|
|
41
|
+
) {
|
|
42
|
+
delete process.env[key];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
25
46
|
// The API drain (src/be/api-drain.ts) is off unless API_DRAIN_MAX_MS is set,
|
|
26
47
|
// but a shell can inherit it (a swarm worker container gets the deploy's value).
|
|
27
48
|
// Test servers are stopped with SIGTERM and their fixtures leave in_progress rows
|
|
@@ -6,6 +6,7 @@ import { resolveTemplate } from "../prompts/resolver";
|
|
|
6
6
|
|
|
7
7
|
// Side-effect imports: register all templates from each source
|
|
8
8
|
import "../gitlab/templates";
|
|
9
|
+
import "../bitbucket/templates";
|
|
9
10
|
import "../agentmail/templates";
|
|
10
11
|
import "../linear/templates";
|
|
11
12
|
import "../heartbeat/templates";
|
|
@@ -28,6 +29,7 @@ async function ensureTemplatesRegistered(): Promise<void> {
|
|
|
28
29
|
// module state across parallel test files, a single-template check is racy.
|
|
29
30
|
const ts = Date.now();
|
|
30
31
|
await import(`../gitlab/templates?t=${ts}`);
|
|
32
|
+
await import(`../bitbucket/templates?t=${ts}`);
|
|
31
33
|
await import(`../agentmail/templates?t=${ts}`);
|
|
32
34
|
await import(`../linear/templates?t=${ts}`);
|
|
33
35
|
await import(`../heartbeat/templates?t=${ts}`);
|
|
@@ -94,6 +96,42 @@ describe("template registration — all sources", () => {
|
|
|
94
96
|
expect(eventTypes).toContain("gitlab.pipeline.failed");
|
|
95
97
|
});
|
|
96
98
|
|
|
99
|
+
test("Bitbucket templates are registered (2 common + 3 event)", () => {
|
|
100
|
+
const eventTypes = getAllTemplateDefinitions().map((d) => d.eventType);
|
|
101
|
+
expect(eventTypes).toContain("common.delegation_instruction.bitbucket");
|
|
102
|
+
expect(eventTypes).toContain("common.command_suggestions.bitbucket_pr");
|
|
103
|
+
expect(eventTypes).toContain("bitbucket.pull_request.opened");
|
|
104
|
+
expect(eventTypes).toContain("bitbucket.pull_request.review_requested");
|
|
105
|
+
expect(eventTypes).toContain("bitbucket.comment.mentioned");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("Bitbucket templates resolve with no unresolved variables", () => {
|
|
109
|
+
const shared = {
|
|
110
|
+
pr_id: 42,
|
|
111
|
+
pr_title: "Fix retry",
|
|
112
|
+
repo: "acme-ws/widgets",
|
|
113
|
+
username: "Dana",
|
|
114
|
+
pr_url: "https://bitbucket.org/acme-ws/widgets/pull-requests/42",
|
|
115
|
+
};
|
|
116
|
+
const pr = { ...shared, source_branch: "feat", target_branch: "main", context_section: "" };
|
|
117
|
+
for (const id of ["bitbucket.pull_request.opened", "bitbucket.pull_request.review_requested"]) {
|
|
118
|
+
const result = resolveTemplate(id, pr);
|
|
119
|
+
expect(result.skipped).toBe(false);
|
|
120
|
+
expect(result.unresolved).toEqual([]);
|
|
121
|
+
expect(result.text).toContain("[Bitbucket PR #42]");
|
|
122
|
+
expect(result.text).toContain("api.bitbucket.org/2.0/repositories/acme-ws/widgets");
|
|
123
|
+
}
|
|
124
|
+
const comment = resolveTemplate("bitbucket.comment.mentioned", {
|
|
125
|
+
...shared,
|
|
126
|
+
location_section: "File: src/a.ts:3\n",
|
|
127
|
+
context: "please fix",
|
|
128
|
+
existing_task_note: "",
|
|
129
|
+
});
|
|
130
|
+
expect(comment.unresolved).toEqual([]);
|
|
131
|
+
expect(comment.text).toContain("File: src/a.ts:3");
|
|
132
|
+
expect(comment.text).toContain("please fix");
|
|
133
|
+
});
|
|
134
|
+
|
|
97
135
|
test("AgentMail templates are registered (5 event)", () => {
|
|
98
136
|
const all = getAllTemplateDefinitions();
|
|
99
137
|
const eventTypes = all.map((d) => d.eventType);
|