@desplega.ai/agent-swarm 2.6.0 → 2.7.1
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 +2 -0
- package/dist/{acp-adapter-dgfakfpw.js → acp-adapter-8dn5tq56.js} +6 -6
- package/dist/{actions-fbqma8aa.js → actions-3r6gw7dy.js} +18 -18
- package/dist/{amp-adapter-en0jy6hs.js → amp-adapter-wjtq413p.js} +3 -3
- package/dist/{anthropic-messages-txs190dd.js → anthropic-messages-n4cby1rz.js} +4 -4
- package/dist/{app-k8a5cgpp.js → app-d53yhykk.js} +3 -3
- package/dist/{assistant-a16433gr.js → assistant-ksrawj3k.js} +20 -20
- package/dist/{boot-reembed-6c1sjfnr.js → boot-reembed-fxfw17m2.js} +11 -3
- package/dist/{boot-reembed-f4nn561n.js → boot-reembed-k5y5xbj0.js} +15 -8
- package/dist/{boot-scrub-sweep-d4w9gajf.js → boot-scrub-sweep-se41j9fd.js} +3 -3
- package/dist/{boot-seeds-9bmkspyw.js → boot-seeds-r3kwwv8b.js} +4 -4
- package/dist/{claude-adapter-nys1kjxa.js → claude-adapter-ahmyf231.js} +8 -8
- package/dist/{claude-managed-adapter-55207973.js → claude-managed-adapter-5knamegc.js} +3 -3
- package/dist/{claude-managed-setup-3cm35gey.js → claude-managed-setup-mdc7dy6b.js} +3 -3
- package/dist/{claude-sdk-session-nw5yd8cp.js → claude-sdk-session-n5wen9zk.js} +8729 -8600
- package/dist/{cli-683y0b3a.js → cli-0dqtv2jg.js} +2 -2
- package/dist/{cli-a34pr3w1.js → cli-0tww7dnz.js} +2 -2
- package/dist/{cli-w2656ka1.js → cli-0w4e4v58.js} +7 -7
- package/dist/{cli-5sckpqcx.js → cli-2sjm3t4r.js} +1 -1
- package/dist/{cli-gf2850q7.js → cli-3cywb44q.js} +18 -1
- package/dist/{cli-8wkb09q5.js → cli-3k9jej0c.js} +2 -2
- package/dist/{cli-3aqd4jaa.js → cli-3v2cyqb8.js} +3 -3
- package/dist/{cli-emhjcj2y.js → cli-5qt9vzmq.js} +89 -20
- package/dist/{cli-abkm71vr.js → cli-5rp1w4cr.js} +1 -1
- package/dist/{cli-gf95q3bm.js → cli-6vwjsetx.js} +4 -4
- package/dist/{cli-k21gx5ht.js → cli-6w2yrrc0.js} +10 -10
- package/dist/{cli-m70bajzr.js → cli-75tdydgv.js} +1 -1
- package/dist/{cli-5rj83bam.js → cli-8feswf2v.js} +47 -36
- package/dist/{cli-sxgtf5fw.js → cli-8wd3mbyj.js} +1 -1
- package/dist/{cli-ca6banwz.js → cli-9csfthbw.js} +10 -8
- package/dist/{cli-dqygeeh5.js → cli-b41p764a.js} +31 -3
- package/dist/{cli-m5a9m0ak.js → cli-c1t533hh.js} +3 -3
- package/dist/{cli-2sjhpsns.js → cli-c28jxpcv.js} +1 -1
- package/dist/{cli-bqztfyve.js → cli-cb50g9cd.js} +1 -1
- package/dist/{cli-ag2yrnt5.js → cli-d8gaphtr.js} +2 -2
- package/dist/{cli-9abdgn5t.js → cli-dgtfysne.js} +3 -3
- package/dist/{cli-nq677csy.js → cli-dscnhn18.js} +1 -1
- package/dist/{cli-3fwqy71j.js → cli-dv11pgpv.js} +39 -32
- package/dist/{cli-v49fkghp.js → cli-ekptgk2m.js} +6 -6
- package/dist/{cli-c2habvsf.js → cli-f3mzy662.js} +17 -17
- package/dist/{cli-zqne1qcj.js → cli-fk7k8s9x.js} +2 -2
- package/dist/{cli-1tem6ns9.js → cli-gt4h8cxw.js} +3 -2
- package/dist/{cli-vbhgkw9h.js → cli-h3e7bhvn.js} +3 -3
- package/dist/{cli-ffvsj2qw.js → cli-hkxqdmnb.js} +3 -3
- package/dist/{cli-a2rr0fmg.js → cli-hpkvgdad.js} +7 -4
- package/dist/{cli-art7c1zh.js → cli-hq2v4rr5.js} +37 -9
- package/dist/{cli-sqma6vw1.js → cli-k1zb7raw.js} +5 -5
- package/dist/{cli-4rvr7zfx.js → cli-k4awgvzm.js} +5 -0
- package/dist/{cli-3ndmpxxv.js → cli-k7d3hvya.js} +2 -2
- package/dist/{cli-3gfvmzht.js → cli-kbtdftbw.js} +74 -26
- package/dist/{cli-hfeb68z4.js → cli-kj69b9tw.js} +34 -6
- package/dist/{cli-az25fn5f.js → cli-kk9jy4nm.js} +2 -2
- package/dist/{cli-39aet655.js → cli-m52t4cta.js} +1 -1
- package/dist/{cli-cz37jqss.js → cli-mdadewqp.js} +5 -5
- package/dist/{cli-esakngh8.js → cli-nf6k60sn.js} +1 -1
- package/dist/{cli-j6rktc91.js → cli-nzdf09js.js} +1 -1
- package/dist/{cli-rr7zx19j.js → cli-prcyzecg.js} +12 -12
- package/dist/{cli-4hxrf0p4.js → cli-pzafyqtk.js} +393 -62
- package/dist/{cli-g8ka5j4j.js → cli-qvaa1gjp.js} +173 -52
- package/dist/{cli-d2dpf95n.js → cli-rg4b2g6j.js} +1 -1
- package/dist/{cli-kf4jree9.js → cli-rm66vqdz.js} +1 -1
- package/dist/{cli-4rf0f5vg.js → cli-s504c3w7.js} +5 -3
- package/dist/{cli-nn6vx2mq.js → cli-swkx9jm2.js} +7 -5
- package/dist/{cli-g20jjwt4.js → cli-thsaq4pd.js} +16 -16
- package/dist/{cli-aj8djyqt.js → cli-twjnyvs8.js} +2 -2
- package/dist/{cli-03gg9pd1.js → cli-typdpe8g.js} +4 -4
- package/dist/{cli-mtnj4fwa.js → cli-vg8pwmsd.js} +6 -6
- package/dist/{cli-gnsb1zxa.js → cli-vhpckzvp.js} +1 -1
- package/dist/{cli-7enjwya9.js → cli-vz78s5wm.js} +1 -1
- package/dist/{cli-dv97kdzv.js → cli-wdxgsdz5.js} +2 -2
- package/dist/{cli-7a6p4e65.js → cli-wtjg6je3.js} +1 -1
- package/dist/{cli-6y51ryva.js → cli-x3cngb1h.js} +44 -12
- package/dist/{cli-n7asmce6.js → cli-xbz6mcg4.js} +3 -3
- package/dist/{cli-24j1jeeb.js → cli-y4tdxsqb.js} +3 -3
- package/dist/{cli-mvjz5an3.js → cli-yqrsp3ny.js} +7 -7
- package/dist/cli.js +31 -21
- package/dist/{codex-adapter-d95q4qsn.js → codex-adapter-7wepzq15.js} +6 -6
- package/dist/{codex-hook-brpnycn5.js → codex-hook-pbdh4xhx.js} +2 -2
- package/dist/{codex-session-runner-gdwd8h0c.js → codex-session-runner-02sb49e4.js} +6 -6
- package/dist/{commands-jqmgk54b.js → commands-3rcspxct.js} +2 -2
- package/dist/{controller-z3jq589f.js → controller-8ar4avd6.js} +3 -3
- package/dist/{controllers-connect-r2za39ef.js → controllers-connect-ajvh9h0v.js} +19 -19
- package/dist/{cursor-adapter-4mr4jgkw.js → cursor-adapter-gd0sa7bh.js} +1 -1
- package/dist/{db-w36wwb5g.js → db-dv9j7ts5.js} +2 -2
- package/dist/{db-ppv2tze7.js → db-z26vgdvj.js} +3 -3
- package/dist/{dispatcher-pv5h77vf.js → dispatcher-8m0htjq8.js} +17 -17
- package/dist/{dsh-adapter-d4pdpb3r.js → dsh-adapter-y8gyk130.js} +1 -1
- package/dist/{e2b-fn3zacvt.js → e2b-74vg9b7e.js} +1 -1
- package/dist/{e2b-222hsajc.js → e2b-jyay8wzn.js} +1 -1
- package/dist/{easter-egg-3d-4sp4dvmc.js → easter-egg-3d-28f880xx.js} +1 -1
- package/dist/{execute-jev839y1.js → execute-9seb79vh.js} +1 -1
- package/dist/{external-channel-y6nkzhnn.js → external-channel-smz7jyth.js} +2 -2
- package/dist/{getMachineId-bsd-brn0xym3.js → getMachineId-bsd-whfre6gr.js} +3 -3
- package/dist/{getMachineId-darwin-t339sfxm.js → getMachineId-darwin-5zjynb19.js} +3 -3
- package/dist/{getMachineId-linux-5eq95d8b.js → getMachineId-linux-8mksrrsq.js} +3 -3
- package/dist/{getMachineId-unsupported-qavrtc7j.js → getMachineId-unsupported-fxtqsdt2.js} +3 -3
- package/dist/{getMachineId-win-q7fe1wx7.js → getMachineId-win-nzfmz857.js} +3 -3
- package/dist/{grok-adapter-t08b9ntm.js → grok-adapter-z0nnvtbn.js} +7 -7
- package/dist/handlers-pspryh6g.js +41 -0
- package/dist/{heartbeat-vn31xwdk.js → heartbeat-f928aqyp.js} +20 -20
- package/dist/{hook-cpjs8q51.js → hook-d7c605r4.js} +7 -7
- package/dist/{hook-6d0zbvvt.js → hook-q9hv5xf9.js} +5 -5
- package/dist/{http-9c6medda.js → http-q6j4k989.js} +1000 -251
- package/dist/{index-k0yt1tvq.js → index-5nb266jr.js} +17 -17
- package/dist/{index-ysd45kgc.js → index-8fxt5eah.js} +10 -10
- package/dist/{index-zgvqzjsw.js → index-cekzwhez.js} +5 -5
- package/dist/{index-38t35h0n.js → index-ch0c1xff.js} +17 -17
- package/dist/{index-pkma1yf8.js → index-ebnjhxh1.js} +1 -1
- package/dist/{index-yqgbfgaj.js → index-ed8dy8rn.js} +20 -20
- package/dist/{index-qhjf2htm.js → index-ejqdkkqx.js} +262 -96
- package/dist/{index-dr94zbnh.js → index-farm1zsn.js} +32661 -29443
- package/dist/{index-g6k9tctj.js → index-yn1yxg85.js} +1 -1
- package/dist/{keepalive-em0117bp.js → keepalive-zzc3hks1.js} +3 -3
- package/dist/lead-c0pjcaw4.js +79 -0
- package/dist/{maintenance-kbme3qy9.js → maintenance-kqsxzpzx.js} +1 -1
- package/dist/{oauth-refresh-sweep-xysj4yse.js → oauth-refresh-sweep-7qt0g55z.js} +8 -3
- package/dist/{onboard-p6q0vz97.js → onboard-k7x13fqg.js} +121 -8
- package/dist/{opencode-adapter-esv3k9yn.js → opencode-adapter-tqvrtter.js} +5 -5
- package/dist/{otel-impl-54qhqh1z.js → otel-impl-sqkf6k3b.js} +97 -97
- package/dist/{pi-mono-adapter-mymbwhj0.js → pi-mono-adapter-m2qnafxm.js} +89 -14
- package/dist/{pricing-refresh-yb75wv33.js → pricing-refresh-wkpwhgvs.js} +3 -3
- package/dist/{rbac-roles-dexhc9x8.js → rbac-roles-dpn0c0wj.js} +7 -4
- package/dist/{rbac-roles-1vp2mw4p.js → rbac-roles-wk7xd6a6.js} +2 -2
- package/dist/{render-v2-tjay5hhf.js → render-v2-ex2m4g2k.js} +3 -3
- package/dist/{resume-8ayf37g5.js → resume-725g9b0b.js} +7 -7
- package/dist/{runtime-hqe5cqcd.js → runtime-kajas15g.js} +1 -1
- package/dist/{session-access-fdhp987s.js → session-access-0j2ezjje.js} +8 -4
- package/dist/{session-source-backfill-qgtc86z1.js → session-source-backfill-62202awe.js} +2 -2
- package/dist/{setup-4rrmqcq3.js → setup-9sj11ax6.js} +1 -1
- package/dist/{virtual-modules-52km00d6.js → virtual-modules-c6fcq6zr.js} +2 -2
- package/dist/{work-objects-p28fgd70.js → work-objects-0y07495y.js} +20 -20
- package/dist/worker-d4q9sqbc.js +79 -0
- package/openapi.json +1536 -407
- package/package.json +7 -5
- package/src/be/automation-preflight.ts +2 -0
- package/src/be/connector-codes.ts +65 -22
- package/src/be/db/api-keys.ts +9 -0
- package/src/be/db/workflow-runs.ts +36 -5
- package/src/be/db.ts +80 -22
- package/src/be/memory/boot-reembed.ts +4 -1
- package/src/be/memory/constants.ts +6 -0
- package/src/be/memory/embedding-recovery.ts +48 -0
- package/src/be/memory/index-content.ts +5 -2
- package/src/be/memory/providers/openai-embedding.ts +65 -14
- package/src/be/memory/task-learning.ts +257 -0
- package/src/be/memory/types.ts +13 -2
- package/src/be/migrations/245_script_executor.sql +3 -0
- package/src/be/migrations/246_connector_code_kind.sql +11 -0
- package/src/be/oauth-refresh-sweep.ts +2 -1
- package/src/be/onboarding.ts +125 -15
- package/src/be/rbac-roles.ts +4 -1
- package/src/be/scripts/boot-reembed.ts +2 -0
- package/src/be/scripts/db.ts +14 -4
- package/src/be/scripts/embeddings.ts +4 -2
- package/src/be/scripts/run-saved.ts +1 -0
- package/src/be/scripts/typecheck.ts +9 -3
- package/src/be/seed/agent-fs-provision.ts +75 -14
- package/src/be/seed-skills/index.ts +7 -0
- package/src/be/swarm-config-guard.ts +3 -0
- package/src/cli.tsx +9 -0
- package/src/comb/config.ts +6 -1
- package/src/commands/onboard/agent-fs-version.ts +5 -0
- package/src/commands/onboard/compose-generator.ts +117 -4
- package/src/commands/onboard/env-generator.ts +9 -0
- package/src/commands/onboard/tei-image.ts +12 -0
- package/src/commands/onboard/types.ts +7 -0
- package/src/commands/onboard.tsx +11 -1
- package/src/commands/provider-credentials.ts +2 -1
- package/src/commands/runner.ts +14 -1
- package/src/heartbeat/heartbeat.ts +14 -1
- package/src/heartbeat/script-runner-probe.ts +47 -0
- package/src/http/agentfs-proxy.ts +369 -0
- package/src/http/all-routes.ts +1 -0
- package/src/http/api-keys.ts +24 -9
- package/src/http/codex-oauth-device.ts +8 -1
- package/src/http/fs.ts +64 -10
- package/src/http/idempotency-key.ts +1 -1
- package/src/http/inbox-state.ts +27 -1
- package/src/http/index.ts +7 -4
- package/src/http/memory.ts +7 -4
- package/src/http/onboarding.ts +139 -11
- package/src/http/pages-public.ts +38 -5
- package/src/http/scripts.ts +27 -0
- package/src/http/session-access.ts +24 -0
- package/src/http/sessions.ts +10 -6
- package/src/http/tasks.ts +85 -11
- package/src/http/telemetry-ui.ts +1 -1
- package/src/http/user-roles.ts +4 -1
- package/src/http/users.ts +272 -3
- package/src/http/utils.ts +22 -3
- package/src/http/workflows.ts +10 -0
- package/src/http/x.ts +5 -0
- package/src/prompts/base-prompt.ts +45 -10
- package/src/prompts/session-templates.ts +43 -5
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/codex-adapter.ts +6 -1
- package/src/providers/cursor-adapter.ts +1 -1
- package/src/providers/dsh-adapter.ts +1 -1
- package/src/providers/pi-mono-adapter.ts +99 -14
- package/src/rbac/legacy-policy.ts +1 -0
- package/src/rbac/permissions.ts +4 -0
- package/src/scheduler/scheduler.ts +2 -1
- package/src/scripts-runtime/eval-harness.ts +8 -1
- package/src/scripts-runtime/executors/native.ts +22 -16
- package/src/scripts-runtime/executors/quickjs-prelude.ts +103 -1
- package/src/scripts-runtime/executors/quickjs-runner.ts +2 -1
- package/src/scripts-runtime/executors/quickjs.ts +7 -0
- package/src/scripts-runtime/executors/registry.ts +32 -0
- package/src/scripts-runtime/executors/types.ts +7 -0
- package/src/scripts-runtime/host-validated-args.ts +133 -0
- package/src/scripts-runtime/loader.ts +30 -3
- package/src/scripts-runtime/swarm-sdk.ts +9 -2
- package/src/scripts-runtime/types/stdlib.d.ts +9 -1
- package/src/scripts-runtime/types/swarm-sdk.d.ts +9 -1
- package/src/tests/agent-fs-provision-seeder.test.ts +261 -92
- package/src/tests/agentfs-proxy.test.ts +1106 -0
- package/src/tests/api-key-status-by-suffix.test.ts +242 -0
- package/src/tests/base-prompt.test.ts +139 -4
- package/src/tests/codex-adapter.test.ts +46 -8
- package/src/tests/codex-oauth-device.test.ts +12 -1
- package/src/tests/comb-config-guard.test.ts +20 -0
- package/src/tests/comb-links.test.ts +79 -1
- package/src/tests/comb-status.test.ts +44 -0
- package/src/tests/community-linear-delivery-loop.test.ts +2099 -0
- package/src/tests/credential-check.test.ts +4 -2
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/docs-index.test.ts +135 -0
- package/src/tests/dsh-adapter.test.ts +1 -1
- package/src/tests/fetch-resolved-env.test.ts +16 -2
- package/src/tests/fixtures/onboard/baseline-states.ts +68 -0
- package/src/tests/fixtures/onboard/content.compose.yml.txt +190 -0
- package/src/tests/fixtures/onboard/content.env.txt +29 -0
- package/src/tests/fixtures/onboard/dev.compose.yml.txt +155 -0
- package/src/tests/fixtures/onboard/dev.env.txt +28 -0
- package/src/tests/fixtures/onboard/full-all.compose.yml.txt +582 -0
- package/src/tests/fixtures/onboard/full-all.env.txt +59 -0
- package/src/tests/fixtures/onboard/full.compose.yml.txt +435 -0
- package/src/tests/fixtures/onboard/full.env.txt +36 -0
- package/src/tests/fixtures/onboard/research.compose.yml.txt +155 -0
- package/src/tests/fixtures/onboard/research.env.txt +28 -0
- package/src/tests/fixtures/onboard/solo.compose.yml.txt +85 -0
- package/src/tests/fixtures/onboard/solo.env.txt +26 -0
- package/src/tests/heartbeat-checklist.test.ts +89 -0
- package/src/tests/heartbeat-script-runner-probe.test.ts +83 -0
- package/src/tests/http-log-scrubbing.test.ts +34 -1
- package/src/tests/http-task-write-ownership.test.ts +5 -1
- package/src/tests/invites-http.test.ts +464 -0
- package/src/tests/memory-embedding-input-cap.test.ts +122 -1
- package/src/tests/memory-embedding-recovery.test.ts +137 -0
- package/src/tests/oauth-refresh-sweep.test.ts +34 -1
- package/src/tests/onboard-compose.test.ts +162 -0
- package/src/tests/onboard-env.test.ts +24 -0
- package/src/tests/onboard-non-interactive.test.ts +29 -0
- package/src/tests/onboard-tei-image.test.ts +36 -0
- package/src/tests/onboarding.test.ts +451 -4
- package/src/tests/openai-embedding-provider.test.ts +105 -2
- package/src/tests/opencode-adapter.test.ts +2 -1
- package/src/tests/pages-frame-ancestors.test.ts +162 -0
- package/src/tests/pi-codemode.test.ts +2 -1
- package/src/tests/pi-mono-adapter.test.ts +54 -0
- package/src/tests/pi-mono-session-cost-on-abort.test.ts +270 -0
- package/src/tests/pi-native-mcp.test.ts +13 -1
- package/src/tests/pi-tool-exposure.test.ts +8 -8
- package/src/tests/preload-env.test.ts +4 -0
- package/src/tests/preload.ts +4 -0
- package/src/tests/probe-tool-schema-tokens.test.ts +144 -0
- package/src/tests/prompt-template-session.test.ts +20 -3
- package/src/tests/rbac-admission-e2e.test.ts +22 -6
- package/src/tests/rbac-admission.test.ts +342 -4
- package/src/tests/rbac-engine.test.ts +1 -0
- package/src/tests/rbac-roles.test.ts +9 -1
- package/src/tests/request-composition-derive.test.ts +329 -0
- package/src/tests/script-connections.test.ts +2 -0
- package/src/tests/script-executor-conformance.test.ts +36 -1
- package/src/tests/script-executor-quickjs-pool.test.ts +50 -0
- package/src/tests/script-executor-quickjs.test.ts +140 -0
- package/src/tests/script-host-validated-args-native.test.ts +196 -0
- package/src/tests/script-per-script-executor.test.ts +415 -0
- package/src/tests/scripts-external-api.test.ts +45 -0
- package/src/tests/sdk-allowlist.test.ts +3 -2
- package/src/tests/self-improvement.test.ts +4 -4
- package/src/tests/send-task-requester-gate.test.ts +64 -3
- package/src/tests/sessions-move.test.ts +39 -1
- package/src/tests/store-progress-learning.test.ts +381 -0
- package/src/tests/ui-logs-parser.test.ts +180 -0
- package/src/tests/whoami-route.test.ts +41 -0
- package/src/tests/workflow-engine-v2.test.ts +116 -1
- package/src/tests/workflow-mcp-trigger-schema.test.ts +209 -0
- package/src/tests/workflow-run-finished-event.test.ts +263 -0
- package/src/tests/workflow-swarm-script.test.ts +34 -0
- package/src/tools/inject-learning.ts +1 -1
- package/src/tools/memory-edit.ts +1 -1
- package/src/tools/memory-search.ts +1 -1
- package/src/tools/script-common.ts +1 -0
- package/src/tools/script-run.ts +7 -0
- package/src/tools/script-upsert.ts +7 -0
- package/src/tools/store-progress.ts +71 -1
- package/src/tools/utils.ts +12 -1
- package/src/tools/workflows/trigger-workflow.ts +104 -10
- package/src/types.ts +12 -1
- package/src/utils/constants.ts +39 -0
- package/src/utils/credentials.ts +5 -2
- package/src/utils/task-attachment-links.ts +15 -3
- package/src/workflows/engine.ts +18 -16
- package/src/workflows/executors/swarm-script.ts +5 -0
- package/src/workflows/foreach-driver.ts +5 -11
- package/src/workflows/node-timeout.ts +30 -0
- package/templates/community/linear-delivery-loop/PLAYBOOK.md +323 -0
- package/templates/community/linear-delivery-loop/README.md +38 -0
- package/templates/community/linear-delivery-loop/config.example.json +31 -0
- package/templates/community/linear-delivery-loop/lead-prompt.md +34 -0
- package/templates/community/linear-delivery-loop/schedule.json +10 -0
- package/templates/community/linear-delivery-loop/scripts/linear-delivery-setup.ts +148 -0
- package/templates/community/linear-delivery-loop/scripts/linear-delivery-tick.ts +1781 -0
- package/templates/skills/swarm-docs/config.json +14 -0
- package/templates/skills/swarm-docs/content.md +144 -0
- package/dist/handlers-eqsnmv95.js +0 -41
- package/dist/lead-23e7z59t.js +0 -79
- package/dist/worker-e3w7vye1.js +0 -79
- package/dist/{azure-openai-responses-9kfwhmn7.js → azure-openai-responses-43asa23s.js} +0 -0
- package/dist/{node-vxx896fa.js → node-t28cc56b.js} +0 -0
- package/dist/{openai-codex-responses-79s8xnbh.js → openai-codex-responses-jhr3sjtm.js} +3 -3
- package/dist/{openai-completions-tdbyts0x.js → openai-completions-gs6yn37k.js} +3 -3
- /package/dist/{openai-responses-dm3t5vqp.js → openai-responses-jqm9vwwk.js} +0 -0
package/src/http/fs.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { IncomingMessage, ServerResponse } from "node:http";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { deleteTaskAttachment, getTaskAttachments, getTaskById } from "../be/db";
|
|
4
4
|
import { appendDraftTaskText } from "../be/db/tasks/draft-text";
|
|
5
|
+
import { getUserGrant } from "../be/rbac-roles";
|
|
5
6
|
import {
|
|
6
7
|
ensureAgentFsCredentialsForAgent,
|
|
7
8
|
inviteEmailToSharedOrg,
|
|
@@ -14,10 +15,10 @@ import {
|
|
|
14
15
|
} from "../be/task-attachment-store";
|
|
15
16
|
import { type FileObject, FilesError, normalizeFilesError } from "../fs/provider";
|
|
16
17
|
import { getFileStorageProvider } from "../fs/registry";
|
|
17
|
-
import { can, type RbacResource } from "../rbac";
|
|
18
|
+
import { can, isRbacEnabled, type RbacResource } from "../rbac";
|
|
18
19
|
import { type TaskAttachment, TaskAttachmentSchema } from "../types";
|
|
19
20
|
import { attachmentContentDisposition } from "../utils/content-disposition";
|
|
20
|
-
import { getCurrentRequestAuth } from "../utils/request-auth-context";
|
|
21
|
+
import { getCurrentRequestAuth, getRequestAuth } from "../utils/request-auth-context";
|
|
21
22
|
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
22
23
|
import { transcribeAudioNotes } from "../utils/transcription";
|
|
23
24
|
import { requestPrincipal } from "./request-principal";
|
|
@@ -66,7 +67,20 @@ const agentFsCredentialStateSchema = z.object({
|
|
|
66
67
|
|
|
67
68
|
const agentFsInviteStateSchema = z.object({
|
|
68
69
|
orgId: z.string(),
|
|
69
|
-
invited: z.boolean(),
|
|
70
|
+
invited: z.boolean().describe("False only when the email already holds the requested role."),
|
|
71
|
+
status: z
|
|
72
|
+
.enum(["already_member", "added", "pending"])
|
|
73
|
+
.describe(
|
|
74
|
+
"already_member: no change. added: an existing agent-fs account joined the org. pending: the email has no account, so agent-fs created an invite the person must accept.",
|
|
75
|
+
),
|
|
76
|
+
inviteId: z.string().optional().describe("Pending invite id (status pending)."),
|
|
77
|
+
expiresAt: z.string().optional().describe("ISO time the accept link expires (status pending)."),
|
|
78
|
+
acceptUrl: z
|
|
79
|
+
.string()
|
|
80
|
+
.optional()
|
|
81
|
+
.describe(
|
|
82
|
+
"Link the invitee opens to accept (status pending). agent-fs sends no email, so hand it to the person. Absent when agent-fs has no app URL configured.",
|
|
83
|
+
),
|
|
70
84
|
});
|
|
71
85
|
|
|
72
86
|
const signedUrlResponseSchema = z.object({
|
|
@@ -112,22 +126,26 @@ const inviteMemberRoute = route({
|
|
|
112
126
|
pattern: ["api", "fs", "members", "invite"],
|
|
113
127
|
summary: "Invite an external member into the agent-fs shared org",
|
|
114
128
|
description:
|
|
115
|
-
"The API server performs the invite with its own bootstrap credentials (which are API-only and never served over HTTP), provisioning the shared org/drive first when needed. Intended for the cloud control plane's Connect-to-Drive flow. No keys are returned; the invitee obtains their own key via agent-fs registration.",
|
|
129
|
+
"The API server performs the invite with its own bootstrap credentials (which are API-only and never served over HTTP), provisioning the shared org/drive first when needed. Intended for the cloud control plane's Connect-to-Drive flow. An email with an agent-fs account is added directly (status added). An email without one gets a pending invite (status pending) with an acceptUrl when agent-fs returns one; agent-fs sends no email, so the caller must hand the link to the invitee. No keys are returned; the invitee obtains their own key via agent-fs registration.",
|
|
116
130
|
tags: ["FS"],
|
|
117
131
|
body: z.object({
|
|
118
132
|
email: z.email(),
|
|
119
133
|
role: z.enum(["viewer", "editor", "admin"]).default("editor"),
|
|
120
134
|
}),
|
|
121
135
|
responses: {
|
|
122
|
-
200: {
|
|
136
|
+
200: {
|
|
137
|
+
description: "Invite state ({ orgId, invited, status, acceptUrl? })",
|
|
138
|
+
schema: agentFsInviteStateSchema,
|
|
139
|
+
},
|
|
123
140
|
400: { description: "Invalid body" },
|
|
141
|
+
403: { description: "A non-admin user token named an email other than its own" },
|
|
124
142
|
500: { description: "Provisioning or invite failed" },
|
|
125
143
|
},
|
|
126
144
|
auth: { apiKey: true },
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
},
|
|
145
|
+
// The operator key and grant-all users invite any email at any role. Other
|
|
146
|
+
// users may only join with their own account email, at a role the handler
|
|
147
|
+
// derives from their grant.
|
|
148
|
+
rbac: { permission: "fs.join.own" },
|
|
131
149
|
});
|
|
132
150
|
|
|
133
151
|
const listTaskFilesRoute = route({
|
|
@@ -234,6 +252,37 @@ const deleteTaskFileRoute = route({
|
|
|
234
252
|
rbac: { permission: "task.fs.mutate" },
|
|
235
253
|
});
|
|
236
254
|
|
|
255
|
+
type InviteRole = "viewer" | "editor" | "admin";
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* The email and role an invite runs with. The operator key and grant-all users
|
|
259
|
+
* keep the body as sent. Any other user joins only as itself: the body email
|
|
260
|
+
* must match the account email (ignoring case), the stored spelling is used,
|
|
261
|
+
* and the role is `editor` when the user may change task files, else `viewer`.
|
|
262
|
+
*/
|
|
263
|
+
async function resolveInvite(
|
|
264
|
+
req: IncomingMessage,
|
|
265
|
+
body: { email: string; role: InviteRole },
|
|
266
|
+
): Promise<{ email: string; role: InviteRole } | { denied: string }> {
|
|
267
|
+
const auth = getRequestAuth(req);
|
|
268
|
+
if (auth?.kind !== "user" || !isRbacEnabled()) return body;
|
|
269
|
+
const grant = await getUserGrant(auth.userId);
|
|
270
|
+
if (grant.grantsAll) return body;
|
|
271
|
+
const decision = can({
|
|
272
|
+
principal: { kind: "user", userId: auth.userId },
|
|
273
|
+
verb: "fs.join.own",
|
|
274
|
+
resource: { kind: "none" },
|
|
275
|
+
source: "http",
|
|
276
|
+
});
|
|
277
|
+
// Admission already required the role to hold `fs.join.own`.
|
|
278
|
+
if (!decision.allow) return { denied: decision.reason };
|
|
279
|
+
const ownEmail = auth.user.email?.trim();
|
|
280
|
+
if (!ownEmail || ownEmail.toLowerCase() !== body.email.trim().toLowerCase()) {
|
|
281
|
+
return { denied: "You can only join the shared drive with your own account email" };
|
|
282
|
+
}
|
|
283
|
+
return { email: ownEmail, role: grant.verbs.has("task.fs.mutate") ? "editor" : "viewer" };
|
|
284
|
+
}
|
|
285
|
+
|
|
237
286
|
export async function handleFs(
|
|
238
287
|
req: IncomingMessage,
|
|
239
288
|
res: ServerResponse,
|
|
@@ -264,8 +313,13 @@ export async function handleFs(
|
|
|
264
313
|
if (inviteMemberRoute.match(req.method, pathSegments)) {
|
|
265
314
|
const parsed = await inviteMemberRoute.parse(req, res, pathSegments, queryParams);
|
|
266
315
|
if (!parsed) return true;
|
|
316
|
+
const invite = await resolveInvite(req, parsed.body);
|
|
317
|
+
if ("denied" in invite) {
|
|
318
|
+
jsonError(res, invite.denied, 403);
|
|
319
|
+
return true;
|
|
320
|
+
}
|
|
267
321
|
try {
|
|
268
|
-
const result = await inviteEmailToSharedOrg(
|
|
322
|
+
const result = await inviteEmailToSharedOrg(invite.email, invite.role);
|
|
269
323
|
inviteMemberRoute.respond(res, 200, result);
|
|
270
324
|
} catch (error) {
|
|
271
325
|
const message = scrubSecrets(error instanceof Error ? error.message : String(error));
|
|
@@ -8,7 +8,7 @@ import { getRequestAuth } from "../utils/request-auth-context";
|
|
|
8
8
|
import { jsonError } from "./utils";
|
|
9
9
|
|
|
10
10
|
/** Visible ASCII, at most 255 characters: room for a UUID, a ULID or a client's own scheme. */
|
|
11
|
-
const IDEMPOTENCY_KEY_PATTERN = /^[\x21-\x7E]{1,255}$/;
|
|
11
|
+
export const IDEMPOTENCY_KEY_PATTERN = /^[\x21-\x7E]{1,255}$/;
|
|
12
12
|
|
|
13
13
|
/** Set on a response answered from an earlier accepted request. */
|
|
14
14
|
export const IDEMPOTENT_REPLAYED_HEADER = "Idempotent-Replayed";
|
package/src/http/inbox-state.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { getDbClient, listInboxState, upsertInboxState } from "../be/db";
|
|
4
|
+
import { getUserGrant } from "../be/rbac-roles";
|
|
5
|
+
import { isRbacEnabled } from "../rbac";
|
|
4
6
|
import { InboxItemStateSchema, InboxItemStatusSchema, InboxItemTypeSchema } from "../types";
|
|
7
|
+
import { getRequestAuth } from "../utils/request-auth-context";
|
|
5
8
|
import { route } from "./route-def";
|
|
6
9
|
import { jsonError } from "./utils";
|
|
7
10
|
|
|
@@ -59,8 +62,10 @@ const upsertState = route({
|
|
|
59
62
|
},
|
|
60
63
|
400: { description: "Validation error" },
|
|
61
64
|
401: { description: "Unauthorized" },
|
|
65
|
+
403: { description: "A non-admin user token wrote another user's state" },
|
|
62
66
|
},
|
|
63
67
|
auth: { apiKey: true },
|
|
68
|
+
rbac: { permission: "favorite.write.own" },
|
|
64
69
|
});
|
|
65
70
|
|
|
66
71
|
const bulkUpsertState = route({
|
|
@@ -80,13 +85,26 @@ const bulkUpsertState = route({
|
|
|
80
85
|
},
|
|
81
86
|
400: { description: "Validation error" },
|
|
82
87
|
401: { description: "Unauthorized" },
|
|
88
|
+
403: { description: "A non-admin user token wrote another user's state" },
|
|
83
89
|
},
|
|
84
90
|
auth: { apiKey: true },
|
|
85
|
-
rbac: {
|
|
91
|
+
rbac: { permission: "favorite.write.own" },
|
|
86
92
|
});
|
|
87
93
|
|
|
88
94
|
// ─── Handler ─────────────────────────────────────────────────────────────────
|
|
89
95
|
|
|
96
|
+
const OTHER_USER_STATE_DENIED = "You can only change your own inbox state";
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* True when a user token without a grant-all role names another user's
|
|
100
|
+
* `userId`. Operator-key and admin callers keep writing any user's state.
|
|
101
|
+
*/
|
|
102
|
+
async function writesOtherUsersState(req: IncomingMessage, userId: string): Promise<boolean> {
|
|
103
|
+
const auth = getRequestAuth(req);
|
|
104
|
+
if (auth?.kind !== "user" || auth.userId === userId || !isRbacEnabled()) return false;
|
|
105
|
+
return !(await getUserGrant(auth.userId)).grantsAll;
|
|
106
|
+
}
|
|
107
|
+
|
|
90
108
|
export async function handleInboxState(
|
|
91
109
|
req: IncomingMessage,
|
|
92
110
|
res: ServerResponse,
|
|
@@ -108,6 +126,10 @@ export async function handleInboxState(
|
|
|
108
126
|
if (bulkUpsertState.match(req.method, pathSegments)) {
|
|
109
127
|
const parsed = await bulkUpsertState.parse(req, res, pathSegments, queryParams);
|
|
110
128
|
if (!parsed) return true;
|
|
129
|
+
if (await writesOtherUsersState(req, parsed.body.userId)) {
|
|
130
|
+
jsonError(res, OTHER_USER_STATE_DENIED, 403);
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
111
133
|
try {
|
|
112
134
|
const items = await getDbClient().transaction(async () => {
|
|
113
135
|
const rows = [];
|
|
@@ -126,6 +148,10 @@ export async function handleInboxState(
|
|
|
126
148
|
if (upsertState.match(req.method, pathSegments)) {
|
|
127
149
|
const parsed = await upsertState.parse(req, res, pathSegments, queryParams);
|
|
128
150
|
if (!parsed) return true;
|
|
151
|
+
if (await writesOtherUsersState(req, parsed.body.userId)) {
|
|
152
|
+
jsonError(res, OTHER_USER_STATE_DENIED, 403);
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
129
155
|
try {
|
|
130
156
|
const item = await upsertInboxState({
|
|
131
157
|
userId: parsed.body.userId,
|
package/src/http/index.ts
CHANGED
|
@@ -70,6 +70,7 @@ import { isEnvFlagEnabled } from "../utils/env-flag";
|
|
|
70
70
|
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
71
71
|
import { initWorkflows } from "../workflows";
|
|
72
72
|
import { handleActiveSessions } from "./active-sessions";
|
|
73
|
+
import { handleAgentFsProxy } from "./agentfs-proxy";
|
|
73
74
|
import { handleAgentRegister, handleAgentsRest } from "./agents";
|
|
74
75
|
import { handleApiKeys } from "./api-keys";
|
|
75
76
|
import { handleApprovalRequests } from "./approval-requests";
|
|
@@ -147,6 +148,7 @@ import {
|
|
|
147
148
|
getPathSegments,
|
|
148
149
|
httpServerSemconvAttributes,
|
|
149
150
|
parseQueryParams,
|
|
151
|
+
redactShareTokenInPath,
|
|
150
152
|
safeRequestUrlForLog,
|
|
151
153
|
setCorsHeaders,
|
|
152
154
|
warnIfCorsAllowsAnyOrigin,
|
|
@@ -319,7 +321,7 @@ const httpServer = createHttpServer(async (req, res) => {
|
|
|
319
321
|
spanName,
|
|
320
322
|
{
|
|
321
323
|
"http.request.method": req.method ?? "",
|
|
322
|
-
"url.path": reqPath,
|
|
324
|
+
"url.path": redactShareTokenInPath(reqPath),
|
|
323
325
|
"url.scheme": semconv["url.scheme"],
|
|
324
326
|
"http.route": httpRoute,
|
|
325
327
|
"server.address": semconv["server.address"],
|
|
@@ -373,6 +375,7 @@ const httpServer = createHttpServer(async (req, res) => {
|
|
|
373
375
|
() => handlePoolKeys(req, res, pathSegments, queryParams),
|
|
374
376
|
() => handleConfig(req, res, pathSegments, queryParams),
|
|
375
377
|
() => handleFs(req, res, pathSegments, queryParams, myAgentId),
|
|
378
|
+
() => handleAgentFsProxy(req, res),
|
|
376
379
|
() => handleComb(req, res, pathSegments, queryParams, myAgentId),
|
|
377
380
|
() => handleKv(req, res, pathSegments, queryParams),
|
|
378
381
|
() => handleRooms(req, res, pathSegments, queryParams),
|
|
@@ -517,7 +520,7 @@ async function shutdown() {
|
|
|
517
520
|
await stopChatSdk();
|
|
518
521
|
|
|
519
522
|
// Stop OAuth keepalive
|
|
520
|
-
if (
|
|
523
|
+
if (!isEnvFlagEnabled("OAUTH_KEEPALIVE_DISABLE", false)) {
|
|
521
524
|
const { stopOAuthKeepalive } = await import("../oauth/keepalive");
|
|
522
525
|
await stopOAuthKeepalive();
|
|
523
526
|
}
|
|
@@ -847,7 +850,7 @@ httpServer
|
|
|
847
850
|
startRuntimeSupervisor();
|
|
848
851
|
|
|
849
852
|
// Start OAuth token keepalive (proactive refresh to prevent expiry)
|
|
850
|
-
if (
|
|
853
|
+
if (!isEnvFlagEnabled("OAUTH_KEEPALIVE_DISABLE", false)) {
|
|
851
854
|
const { startOAuthKeepalive } = await import("../oauth/keepalive");
|
|
852
855
|
startOAuthKeepalive();
|
|
853
856
|
}
|
|
@@ -855,7 +858,7 @@ httpServer
|
|
|
855
858
|
// Start generic OAuth token refresh sweep (all oauth_apps providers,
|
|
856
859
|
// 15-min tick, first run ~1 min after boot). Complements the tracker-only
|
|
857
860
|
// keepalive above — see src/be/oauth-refresh-sweep.ts.
|
|
858
|
-
if (
|
|
861
|
+
if (!isEnvFlagEnabled("OAUTH_REFRESH_SWEEP_DISABLE", false)) {
|
|
859
862
|
const { startOAuthRefreshSweep } = await import("../be/oauth-refresh-sweep");
|
|
860
863
|
startOAuthRefreshSweep();
|
|
861
864
|
}
|
package/src/http/memory.ts
CHANGED
|
@@ -914,7 +914,7 @@ export async function handleMemory(
|
|
|
914
914
|
try {
|
|
915
915
|
const provider = getEmbeddingProvider();
|
|
916
916
|
const store = getMemoryStore();
|
|
917
|
-
const queryEmbedding = await provider.embed(query);
|
|
917
|
+
const queryEmbedding = await provider.embed(query, { kind: "query" });
|
|
918
918
|
|
|
919
919
|
const candidateLimit = Math.min(limit, 20) * CANDIDATE_SET_MULTIPLIER;
|
|
920
920
|
const candidates = await searchMemoryCandidates(
|
|
@@ -1039,7 +1039,7 @@ export async function handleMemory(
|
|
|
1039
1039
|
try {
|
|
1040
1040
|
if (query && query.trim().length > 0) {
|
|
1041
1041
|
const provider = getEmbeddingProvider();
|
|
1042
|
-
const queryEmbedding = await provider.embed(query.trim());
|
|
1042
|
+
const queryEmbedding = await provider.embed(query.trim(), { kind: "query" });
|
|
1043
1043
|
|
|
1044
1044
|
const candidateLimit = Math.min(
|
|
1045
1045
|
4096,
|
|
@@ -1289,7 +1289,7 @@ export async function handleMemory(
|
|
|
1289
1289
|
});
|
|
1290
1290
|
if (result.changed) {
|
|
1291
1291
|
const provider = getEmbeddingProvider();
|
|
1292
|
-
const embedding = await provider.embed(result.memory.content);
|
|
1292
|
+
const embedding = await provider.embed(result.memory.content, { kind: "document" });
|
|
1293
1293
|
if (embedding) await store.updateEmbedding(result.memory.id, embedding, provider.name);
|
|
1294
1294
|
try {
|
|
1295
1295
|
// Edit path: prune links derived from removed content (sequel links survive).
|
|
@@ -1378,7 +1378,10 @@ export async function handleMemory(
|
|
|
1378
1378
|
for (let i = 0; i < memories.length; i += batchSize) {
|
|
1379
1379
|
const batch = memories.slice(i, i + batchSize);
|
|
1380
1380
|
try {
|
|
1381
|
-
const embeddings = await provider.embedBatch(
|
|
1381
|
+
const embeddings = await provider.embedBatch(
|
|
1382
|
+
batch.map((m) => m.content),
|
|
1383
|
+
{ kind: "document" },
|
|
1384
|
+
);
|
|
1382
1385
|
for (let j = 0; j < embeddings.length; j++) {
|
|
1383
1386
|
if (embeddings[j]) {
|
|
1384
1387
|
await store.updateEmbedding(batch[j]!.id, embeddings[j]!, provider.name);
|
package/src/http/onboarding.ts
CHANGED
|
@@ -2,12 +2,15 @@ import type { IncomingMessage, ServerResponse } from "node:http";
|
|
|
2
2
|
import OpenAI from "openai";
|
|
3
3
|
import { getAutomationSetupStates } from "../be/automation-preflight";
|
|
4
4
|
import { getAgentHarnessProviders, getReadyAgentCounts, getTaskById } from "../be/db";
|
|
5
|
-
import { getEmbeddingProvider } from "../be/memory";
|
|
5
|
+
import { getEmbeddingProvider, resetEmbeddingProvider } from "../be/memory";
|
|
6
6
|
import { EMBEDDING_DIMENSIONS } from "../be/memory/constants";
|
|
7
7
|
import {
|
|
8
|
+
EMBEDDING_PREFIX_KEYS,
|
|
9
|
+
type MemoryConfigChange,
|
|
8
10
|
OnboardingActionSchema,
|
|
9
11
|
type OnboardingErrorClass,
|
|
10
12
|
type OnboardingMemoryPreset,
|
|
13
|
+
type OnboardingMemoryProvider,
|
|
11
14
|
type OnboardingMemoryRequest,
|
|
12
15
|
OnboardingMemoryRequestSchema,
|
|
13
16
|
type OnboardingMemoryResponse,
|
|
@@ -21,16 +24,89 @@ import {
|
|
|
21
24
|
} from "../be/onboarding";
|
|
22
25
|
import { assertUrlSafe, publicEndpointSsrfOptions } from "../oauth/mcp-wrapper";
|
|
23
26
|
import { ProviderNameSchema } from "../types";
|
|
27
|
+
import { isCombEnabled } from "../utils/constants";
|
|
24
28
|
import { splitCredentialPool } from "../utils/credentials";
|
|
29
|
+
import { isPrivateAddress } from "../utils/egress-guard";
|
|
25
30
|
import { getOpenRouterAttributionHeaders } from "../utils/openrouter-base-url";
|
|
26
31
|
import { ensureConfigAdmin } from "./config";
|
|
27
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
getDeploymentEnvValue,
|
|
34
|
+
loadGlobalConfigsIntoEnv,
|
|
35
|
+
scheduleIntegrationsReload,
|
|
36
|
+
} from "./core";
|
|
28
37
|
import { route } from "./route-def";
|
|
29
38
|
import { rollupCredStatusForProvider } from "./status";
|
|
30
39
|
import { jsonError } from "./utils";
|
|
31
40
|
|
|
32
41
|
export type { OnboardingState } from "../be/onboarding";
|
|
33
42
|
|
|
43
|
+
/**
|
|
44
|
+
* The embeddings endpoint runs on this server or its private network: a
|
|
45
|
+
* loopback name, a private IP, or a single-label host such as the bundled
|
|
46
|
+
* `tei` service. Unset means the hosted OpenAI default.
|
|
47
|
+
*/
|
|
48
|
+
function isLocalEndpoint(baseUrl: string | undefined): boolean {
|
|
49
|
+
if (!baseUrl?.trim()) return false;
|
|
50
|
+
let host: string;
|
|
51
|
+
try {
|
|
52
|
+
host = new URL(baseUrl.trim()).hostname.toLowerCase().replace(/\.$/, "");
|
|
53
|
+
} catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
if (!host) return false;
|
|
57
|
+
if (host === "localhost" || host.endsWith(".localhost")) return true;
|
|
58
|
+
if (isPrivateAddress(host)) return true;
|
|
59
|
+
// IPv6 literals hold colons: only a plain name can be a single label.
|
|
60
|
+
return !host.includes(".") && !host.includes(":");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** The hosts of the hosted memory presets (the UI's preset list). */
|
|
64
|
+
const PRESET_HOSTS: ReadonlyArray<[OnboardingMemoryProvider, (host: string) => boolean]> = [
|
|
65
|
+
["openai", (host) => host === "api.openai.com"],
|
|
66
|
+
["openrouter", (host) => host === "openrouter.ai"],
|
|
67
|
+
["vercel", (host) => host === "ai-gateway.vercel.sh"],
|
|
68
|
+
[
|
|
69
|
+
"azure",
|
|
70
|
+
(host) =>
|
|
71
|
+
[".services.ai.azure.com", ".openai.azure.com", ".cognitiveservices.azure.com"].some(
|
|
72
|
+
(suffix) => host.endsWith(suffix),
|
|
73
|
+
),
|
|
74
|
+
],
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
/** Where an embeddings endpoint runs, from its host. Unset means the hosted OpenAI default. */
|
|
78
|
+
function embeddingsProvider(baseUrl: string | undefined): OnboardingMemoryProvider {
|
|
79
|
+
if (isLocalEndpoint(baseUrl)) return "local";
|
|
80
|
+
if (!baseUrl?.trim()) return "openai";
|
|
81
|
+
let host: string;
|
|
82
|
+
try {
|
|
83
|
+
host = new URL(baseUrl.trim()).hostname.toLowerCase().replace(/\.$/, "");
|
|
84
|
+
} catch {
|
|
85
|
+
return "custom";
|
|
86
|
+
}
|
|
87
|
+
return PRESET_HOSTS.find(([, matches]) => matches(host))?.[0] ?? "custom";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The deployment environment's own embeddings setup, before a stored config
|
|
92
|
+
* replaced it: its base URL, model, and key. The key can also be a stored
|
|
93
|
+
* OPENAI_API_KEY, as for the setup the server runs.
|
|
94
|
+
*/
|
|
95
|
+
function deploymentEmbeddings() {
|
|
96
|
+
return {
|
|
97
|
+
baseUrl: getDeploymentEnvValue("EMBEDDING_API_BASE_URL"),
|
|
98
|
+
model: getDeploymentEnvValue("EMBEDDING_MODEL") ?? "text-embedding-3-small",
|
|
99
|
+
apiKey: getDeploymentEnvValue("EMBEDDING_API_KEY") ?? process.env.OPENAI_API_KEY,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** The local setup of the deployment environment, when it has a local endpoint and a key. */
|
|
104
|
+
function localDefault(): { model: string } | null {
|
|
105
|
+
const deployment = deploymentEmbeddings();
|
|
106
|
+
if (!isLocalEndpoint(deployment.baseUrl) || !deployment.apiKey) return null;
|
|
107
|
+
return { model: deployment.model };
|
|
108
|
+
}
|
|
109
|
+
|
|
34
110
|
async function buildSignals(state: OnboardingState): Promise<OnboardingSignals> {
|
|
35
111
|
const providers: OnboardingSignals["providers"] = [];
|
|
36
112
|
for (const entry of await getAgentHarnessProviders()) {
|
|
@@ -51,12 +127,18 @@ async function buildSignals(state: OnboardingState): Promise<OnboardingSignals>
|
|
|
51
127
|
const integrations = await getAutomationSetupStates();
|
|
52
128
|
const agentCounts = await getReadyAgentCounts();
|
|
53
129
|
const firstTask = state.firstTaskId ? await getTaskById(state.firstTaskId) : null;
|
|
130
|
+
const embeddingsOn = getEmbeddingProvider().isConfigured();
|
|
54
131
|
|
|
55
132
|
return {
|
|
56
133
|
providers,
|
|
57
134
|
embeddings: {
|
|
58
|
-
configured:
|
|
135
|
+
configured: embeddingsOn,
|
|
59
136
|
dimensions: EMBEDDING_DIMENSIONS,
|
|
137
|
+
// The model id and the host class only: never the key or the URL.
|
|
138
|
+
model: embeddingsOn ? (process.env.EMBEDDING_MODEL ?? "text-embedding-3-small") : null,
|
|
139
|
+
local: embeddingsOn && isLocalEndpoint(process.env.EMBEDDING_API_BASE_URL),
|
|
140
|
+
provider: embeddingsOn ? embeddingsProvider(process.env.EMBEDDING_API_BASE_URL) : null,
|
|
141
|
+
localDefault: localDefault(),
|
|
60
142
|
},
|
|
61
143
|
integrations: {
|
|
62
144
|
slack: integrations.slack === "configured",
|
|
@@ -71,6 +153,7 @@ async function buildSignals(state: OnboardingState): Promise<OnboardingSignals>
|
|
|
71
153
|
workersOnline: agentCounts.workers,
|
|
72
154
|
},
|
|
73
155
|
firstTask: firstTask ? { id: firstTask.id, status: firstTask.status } : null,
|
|
156
|
+
files: { enabled: isCombEnabled() },
|
|
74
157
|
};
|
|
75
158
|
}
|
|
76
159
|
|
|
@@ -195,10 +278,31 @@ function memoryErrorMessage(errorClass: OnboardingErrorClass, status?: number):
|
|
|
195
278
|
async function updateMemoryState(
|
|
196
279
|
preset: OnboardingMemoryPreset,
|
|
197
280
|
result: { ok: true } | { ok: false; errorClass: OnboardingErrorClass },
|
|
198
|
-
|
|
281
|
+
change?: MemoryConfigChange,
|
|
199
282
|
): Promise<void> {
|
|
200
|
-
const
|
|
201
|
-
if (
|
|
283
|
+
const configChanged = await updateOnboardingMemory(preset, result, change);
|
|
284
|
+
if (!configChanged) return;
|
|
285
|
+
if (change?.kind === "restore") {
|
|
286
|
+
// Apply the deployment setup before the response: the next GET shows it
|
|
287
|
+
// in use. Memory search stays on, so the reload starts no backfill.
|
|
288
|
+
await loadGlobalConfigsIntoEnv(true);
|
|
289
|
+
resetEmbeddingProvider();
|
|
290
|
+
}
|
|
291
|
+
scheduleIntegrationsReload();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* The task prefixes of a saved setup. A prefix of the deployment environment
|
|
296
|
+
* belongs to its model (nomic needs `search_query: `): another model gets an
|
|
297
|
+
* empty stored prefix over it, and the same model gets the deployment prefix
|
|
298
|
+
* back (the stored row is deleted).
|
|
299
|
+
*/
|
|
300
|
+
function prefixChanges(model: string): Extract<MemoryConfigChange, { kind: "save" }>["prefixes"] {
|
|
301
|
+
const sameModel = model === getDeploymentEnvValue("EMBEDDING_MODEL");
|
|
302
|
+
return EMBEDDING_PREFIX_KEYS.filter((key) => getDeploymentEnvValue(key)).map((key) => ({
|
|
303
|
+
key,
|
|
304
|
+
value: sameModel ? null : "",
|
|
305
|
+
}));
|
|
202
306
|
}
|
|
203
307
|
|
|
204
308
|
async function handleMemoryProbe(body: OnboardingMemoryRequest): Promise<OnboardingMemoryResponse> {
|
|
@@ -207,21 +311,39 @@ async function handleMemoryProbe(body: OnboardingMemoryRequest): Promise<Onboard
|
|
|
207
311
|
body.preset === "openai" || body.preset === "openrouter" || body.preset === "vercel"
|
|
208
312
|
? MEMORY_PRESETS[body.preset]
|
|
209
313
|
: undefined;
|
|
314
|
+
// `existing` tests the setup the server runs. With `restoreDefault` it
|
|
315
|
+
// tests the deployment environment's setup, which applies once the stored
|
|
316
|
+
// config is gone.
|
|
317
|
+
const restore = body.preset === "existing" && body.restoreDefault === true;
|
|
318
|
+
const deployment = restore ? deploymentEmbeddings() : null;
|
|
210
319
|
const baseUrl =
|
|
211
320
|
body.preset === "existing"
|
|
212
|
-
?
|
|
321
|
+
? deployment
|
|
322
|
+
? deployment.baseUrl
|
|
323
|
+
: process.env.EMBEDDING_API_BASE_URL
|
|
213
324
|
: (body.baseUrl ??
|
|
214
325
|
preset?.baseUrl ??
|
|
215
326
|
process.env.EMBEDDING_API_BASE_URL ??
|
|
216
327
|
"https://api.openai.com/v1");
|
|
217
328
|
const model =
|
|
218
329
|
body.preset === "existing"
|
|
219
|
-
? (process.env.EMBEDDING_MODEL ?? "text-embedding-3-small")
|
|
330
|
+
? (deployment?.model ?? process.env.EMBEDDING_MODEL ?? "text-embedding-3-small")
|
|
220
331
|
: (body.model ?? preset?.model ?? process.env.EMBEDDING_MODEL ?? "text-embedding-3-small");
|
|
221
332
|
|
|
333
|
+
// Preset `existing` tests the server's own EMBEDDING_API_BASE_URL. When it is
|
|
334
|
+
// the deployment environment's value, the URL is operator config, not request
|
|
335
|
+
// input, so a private endpoint (a local TEI at http://tei:80/v1) is allowed. A
|
|
336
|
+
// value stored through `/api/config` and every URL that can come from the
|
|
337
|
+
// request still pass the SSRF check.
|
|
338
|
+
const configuredEndpoint =
|
|
339
|
+
body.preset === "existing" &&
|
|
340
|
+
!!baseUrl &&
|
|
341
|
+
baseUrl === getDeploymentEnvValue("EMBEDDING_API_BASE_URL");
|
|
222
342
|
let endpoint: URL;
|
|
223
343
|
try {
|
|
224
|
-
endpoint =
|
|
344
|
+
endpoint = configuredEndpoint
|
|
345
|
+
? new URL(baseUrl ?? "")
|
|
346
|
+
: assertUrlSafe(baseUrl ?? "", publicEndpointSsrfOptions());
|
|
225
347
|
} catch {
|
|
226
348
|
// Input rejections are not probe outcomes: the step state stays as is.
|
|
227
349
|
return {
|
|
@@ -243,7 +365,9 @@ async function handleMemoryProbe(body: OnboardingMemoryRequest): Promise<Onboard
|
|
|
243
365
|
!explicitKey && body.reuseKey && canReuseKey ? process.env[body.reuseKey] : undefined;
|
|
244
366
|
const implicitKey =
|
|
245
367
|
body.preset === "existing"
|
|
246
|
-
?
|
|
368
|
+
? deployment
|
|
369
|
+
? deployment.apiKey
|
|
370
|
+
: (process.env.EMBEDDING_API_KEY ?? process.env.OPENAI_API_KEY)
|
|
247
371
|
: undefined;
|
|
248
372
|
// Test and save the same pool member.
|
|
249
373
|
const apiKey = splitCredentialPool(explicitKey ?? reusedKey ?? implicitKey ?? "")[0];
|
|
@@ -290,11 +414,15 @@ async function handleMemoryProbe(body: OnboardingMemoryRequest): Promise<Onboard
|
|
|
290
414
|
body.preset,
|
|
291
415
|
{ ok: true },
|
|
292
416
|
body.preset === "existing"
|
|
293
|
-
?
|
|
417
|
+
? restore
|
|
418
|
+
? { kind: "restore" }
|
|
419
|
+
: undefined
|
|
294
420
|
: {
|
|
421
|
+
kind: "save",
|
|
295
422
|
baseUrl: baseUrl ?? "https://api.openai.com/v1",
|
|
296
423
|
model,
|
|
297
424
|
apiKey: keyToSave,
|
|
425
|
+
prefixes: prefixChanges(model),
|
|
298
426
|
},
|
|
299
427
|
);
|
|
300
428
|
return { ok: true, dimensions, latencyMs };
|
package/src/http/pages-public.ts
CHANGED
|
@@ -308,13 +308,45 @@ function getAppBaseUrl(): string {
|
|
|
308
308
|
return getAppUrl(LOCAL_PAGE_APP_URL);
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
+
// CSP host-source without a path: optional http(s) scheme, a host that may
|
|
312
|
+
// start with a `*.` wildcard label, and an optional port (digits or `*`).
|
|
313
|
+
// Bare `*`, keywords (`'none'`, `'self'`) and anything carrying `;` or a path
|
|
314
|
+
// are rejected so a bad env value cannot widen or break the policy.
|
|
315
|
+
const FRAME_ANCESTOR_SOURCE_RE =
|
|
316
|
+
/^(?:https?:\/\/)?(?:\*\.)?[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)*(?::(?:\d{1,5}|\*))?$/i;
|
|
317
|
+
|
|
318
|
+
let cachedFrameAncestors: { raw: string; origins: string[] } | null = null;
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Parse `PAGES_FRAME_ANCESTORS`: extra origins allowed to iframe PUBLIC pages
|
|
322
|
+
* (for example a marketing site embedding a chart). Comma or space separated.
|
|
323
|
+
* Invalid entries are dropped with one warning per distinct env value; the
|
|
324
|
+
* parse is cached on the raw string so a config reload picks up changes
|
|
325
|
+
* without re-warning on every request.
|
|
326
|
+
*/
|
|
327
|
+
export function getPageFrameAncestors(): string[] {
|
|
328
|
+
const raw = process.env.PAGES_FRAME_ANCESTORS ?? "";
|
|
329
|
+
if (cachedFrameAncestors?.raw === raw) return cachedFrameAncestors.origins;
|
|
330
|
+
const origins: string[] = [];
|
|
331
|
+
for (const entry of raw.split(/[\s,]+/)) {
|
|
332
|
+
const value = entry.replace(/\/+$/, "");
|
|
333
|
+
if (!value) continue;
|
|
334
|
+
if (FRAME_ANCESTOR_SOURCE_RE.test(value)) origins.push(value);
|
|
335
|
+
else console.warn(`[pages] ignoring invalid PAGES_FRAME_ANCESTORS entry: ${entry}`);
|
|
336
|
+
}
|
|
337
|
+
cachedFrameAncestors = { raw, origins };
|
|
338
|
+
return origins;
|
|
339
|
+
}
|
|
340
|
+
|
|
311
341
|
/**
|
|
312
342
|
* Build the `Content-Security-Policy` for the served HTML. Allows inline
|
|
313
343
|
* scripts (required for `BROWSER_SDK_JS`) but locks down everything else to
|
|
314
344
|
* `'self'`. The SPA iframes the page in step-6 with `sandbox="allow-scripts
|
|
315
|
-
* allow-forms"`; the CSP is a defence-in-depth layer.
|
|
345
|
+
* allow-forms"`; the CSP is a defence-in-depth layer. `PAGES_FRAME_ANCESTORS`
|
|
346
|
+
* applies only to public pages: authed and password pages stay embeddable by
|
|
347
|
+
* the app origins alone.
|
|
316
348
|
*/
|
|
317
|
-
function buildCsp(): string {
|
|
349
|
+
function buildCsp(page: Page): string {
|
|
318
350
|
// `frame-ancestors` lists every origin allowed to iframe `/p/:id`. We must
|
|
319
351
|
// include the SPA origin(s). Configured app URLs may be comma-separated so
|
|
320
352
|
// portless dev (`https://ui.swarm.localhost`), a Vite port (`http://localhost:5274`),
|
|
@@ -333,7 +365,8 @@ function buildCsp(): string {
|
|
|
333
365
|
"https://*.localhost",
|
|
334
366
|
"http://*.localhost",
|
|
335
367
|
];
|
|
336
|
-
const
|
|
368
|
+
const embedders = page.authMode === "public" ? getPageFrameAncestors() : [];
|
|
369
|
+
const ancestors = Array.from(new Set([...configured, ...devFallbacks, ...embedders]));
|
|
337
370
|
// Allow Tailwind Play CDN (`cdn.tailwindcss.com`) for scripts, Google
|
|
338
371
|
// Fonts (`fonts.googleapis.com` stylesheets + `fonts.gstatic.com` font
|
|
339
372
|
// files) for the swarm default typography, and same-origin /@swarm/api/*
|
|
@@ -609,7 +642,7 @@ export async function handlePagesPublic(
|
|
|
609
642
|
const headers: Record<string, string | string[]> = {
|
|
610
643
|
"Content-Type": "text/html; charset=utf-8",
|
|
611
644
|
"Cache-Control": "no-store",
|
|
612
|
-
"Content-Security-Policy": buildCsp(),
|
|
645
|
+
"Content-Security-Policy": buildCsp(page),
|
|
613
646
|
"X-Content-Type-Options": "nosniff",
|
|
614
647
|
};
|
|
615
648
|
if (inlineSetCookie.length > 0) headers["Set-Cookie"] = inlineSetCookie;
|
|
@@ -642,7 +675,7 @@ export async function handlePagesPublic(
|
|
|
642
675
|
const headers: Record<string, string | string[]> = {
|
|
643
676
|
"Content-Type": "text/html; charset=utf-8",
|
|
644
677
|
"Cache-Control": "no-store",
|
|
645
|
-
"Content-Security-Policy": buildCsp(),
|
|
678
|
+
"Content-Security-Policy": buildCsp(page),
|
|
646
679
|
// Defence-in-depth: prevent MIME sniffing and clickjacking outside the SPA.
|
|
647
680
|
"X-Content-Type-Options": "nosniff",
|
|
648
681
|
};
|