@desplega.ai/agent-swarm 1.164.1 → 1.166.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 +1 -1
- package/dist/acp-adapter-yzfxn8vj.js +23 -0
- package/dist/{actions-hp2h32dx.js → actions-qz3m692w.js} +24 -28
- package/dist/{amp-adapter-zfd7djyx.js → amp-adapter-j1xbmsqz.js} +7 -7
- package/dist/{app-w2d26h4f.js → app-43vrdmv8.js} +9 -10
- package/dist/{assistant-45pc4en7.js → assistant-ertffmka.js} +25 -29
- package/dist/{boot-reembed-k1q2s4fc.js → boot-reembed-998jtpdg.js} +12 -15
- package/dist/{boot-reembed-6rhcpsjf.js → boot-reembed-s7k7c6v2.js} +13 -15
- package/dist/boot-scrub-sweep-nepsk4t1.js +211 -0
- package/dist/{boot-seeds-ex93edx7.js → boot-seeds-vvzs1zhk.js} +12 -12
- package/dist/{claude-adapter-hcxd0szm.js → claude-adapter-2zvmekrp.js} +15 -17
- package/dist/{claude-managed-adapter-fz7xps2r.js → claude-managed-adapter-s49wbk8t.js} +5 -5
- package/dist/{claude-managed-setup-xx6f15h9.js → claude-managed-setup-cvhqq8qn.js} +4 -4
- package/dist/{claude-sdk-session-6nb9raem.js → claude-sdk-session-kkbxtw5q.js} +2624 -2606
- package/dist/{cli-etbrt8x1.js → cli-0nhdwxd5.js} +4 -4
- package/dist/{cli-jeefgppq.js → cli-0qjcsxgz.js} +7 -7
- package/dist/{cli-6behs2bt.js → cli-32bca7nr.js} +1 -1
- package/dist/{cli-gt8v96qx.js → cli-3ddybbbx.js} +4 -4
- package/dist/{acp-adapter-rxvek4ym.js → cli-3qn4r334.js} +317 -35
- package/dist/{cli-1d3w4xm3.js → cli-4r1sjfc4.js} +14 -6
- package/dist/{cli-691f2rxm.js → cli-66zh26sp.js} +68 -16
- package/dist/{cli-et8wdyqh.js → cli-68r1j4sg.js} +9 -9
- package/dist/{cli-xb7kyanp.js → cli-8kgvmkak.js} +3 -3
- package/dist/{cli-fdpkw8vk.js → cli-8t229cmr.js} +8 -8
- package/dist/{cli-gc54qc0p.js → cli-aqcey741.js} +1 -1
- package/dist/{cli-4q4cmr4t.js → cli-cpfmqz3n.js} +8 -8
- package/dist/{cli-a9bw0xxt.js → cli-df56tqdw.js} +997 -408
- package/dist/{cli-n7h1kqpf.js → cli-dmtrxqhg.js} +6 -1
- package/dist/{cli-xfb7333f.js → cli-dw2q9f7g.js} +5 -5
- package/dist/{cli-4drstbb9.js → cli-ecknp7p1.js} +81 -23
- package/dist/{cli-v0pvvhfq.js → cli-efnd2kce.js} +36 -1
- package/dist/{cli-1wetf34q.js → cli-f2n17zhp.js} +1 -1
- package/dist/{cli-2c8eky5y.js → cli-feh3dkah.js} +1 -1
- package/dist/cli-fn697bnt.js +126 -0
- package/dist/{cli-v5pr2fgt.js → cli-gt8azyet.js} +3 -3
- package/dist/{cli-dbszf3rw.js → cli-h3x613ze.js} +4 -4
- package/dist/{cli-nwhb8y2e.js → cli-hf5s9pgf.js} +1 -1
- package/dist/cli-j2jbr8af.js +516 -0
- package/dist/{cli-agjzafns.js → cli-jhm6hzjn.js} +593 -10
- package/dist/{cli-j0tmc908.js → cli-jvj6a0cv.js} +484 -477
- package/dist/{cli-xxz89c2n.js → cli-mb75wpxf.js} +1 -1
- package/dist/{cli-07s39g1n.js → cli-mv2520nm.js} +1 -1
- package/dist/{cli-v90vfwyb.js → cli-mwegs4af.js} +5 -5
- package/dist/{cli-bz1gbac9.js → cli-p4ztb80h.js} +2 -2
- package/dist/{cli-1wd1n2ya.js → cli-rfphkzh5.js} +1 -1
- package/dist/{cli-976r1zqx.js → cli-rggdq8wq.js} +5 -5
- package/dist/{cli-3ajw5v5v.js → cli-rsy933yz.js} +6 -6
- package/dist/{cli-yj790e19.js → cli-tadfgd0t.js} +8 -1
- package/dist/{cli-cn53e7vc.js → cli-ttbsse3f.js} +6 -1
- package/dist/{cli-dyqsrqcf.js → cli-v2a8x45d.js} +4 -4
- package/dist/{cli-wbvnn6tk.js → cli-v6vv7dy8.js} +91 -9
- package/dist/{cli-z2ap2c0c.js → cli-vzc73wet.js} +90 -55
- package/dist/cli-wt7ccy4r.js +2654 -0
- package/dist/{cli-4cn19jvn.js → cli-x7kpr72a.js} +3 -3
- package/dist/{cli-003785v9.js → cli-xvc3q9br.js} +1 -1
- package/dist/{cli-v0e7jpr5.js → cli-xvdgasgy.js} +13 -4
- package/dist/{cli-asqeg4ve.js → cli-y59h2pk9.js} +2 -2
- package/dist/{cli-0wn3ypqv.js → cli-yshaazs1.js} +14121 -3478
- package/dist/{cli-wg8yjqtw.js → cli-ysnyhws9.js} +16 -3
- package/dist/cli-zp67qg5x.js +92 -0
- package/dist/{cli-g3qgrzck.js → cli-zsh093hk.js} +71 -3
- package/dist/{cli-ma3zvh39.js → cli-zy7vwsne.js} +6066 -5829
- package/dist/cli.js +79 -26
- package/dist/{codex-adapter-pcxdn10v.js → codex-adapter-dw1wxqt8.js} +10 -10
- package/dist/{codex-hook-b1fb7c2k.js → codex-hook-d2rkvf39.js} +1 -1
- package/dist/{codex-login-059nm145.js → codex-login-terbdsqq.js} +2 -2
- package/dist/{codex-session-runner-xzwwddp8.js → codex-session-runner-3cgb8y1g.js} +10 -10
- package/dist/{commands-98at8mkn.js → commands-mhxam6z2.js} +9 -9
- package/dist/{cursor-adapter-dkg6qnkp.js → cursor-adapter-2qda3st7.js} +6 -6
- package/dist/{db-01g8wtvw.js → db-sjks418k.js} +11 -9
- package/dist/{devin-adapter-qmttt2fz.js → devin-adapter-bffjj5n4.js} +2 -2
- package/dist/{dispatcher-nn2k1wxd.js → dispatcher-1es759rn.js} +22 -26
- package/dist/{dsh-adapter-w4d4vwdq.js → dsh-adapter-bek4cyw9.js} +6 -6
- package/dist/{e2b-9e4g2hvt.js → e2b-nd50eccg.js} +1 -1
- package/dist/{event-bus-z7v2t26e.js → event-bus-vnfthafg.js} +2 -2
- package/dist/grok-adapter-ar9cw9jg.js +29 -0
- package/dist/{handlers-b0kqsbg8.js → handlers-1gkbhktr.js} +25 -29
- package/dist/{hook-sap7v1aj.js → hook-ds2zxq9v.js} +16 -18
- package/dist/{hook-pz50gqep.js → hook-vxa3tcsy.js} +10 -12
- package/dist/{http-ncr8rmd0.js → http-3h21z9md.js} +679 -227
- package/dist/{index-c4aqcyz1.js → index-9b65j1m5.js} +11 -11
- package/dist/{index-r46s01nq.js → index-bn6cy8ps.js} +22 -26
- package/dist/{index-38kqdt3e.js → index-dkfxpkmm.js} +23 -27
- package/dist/{index-630kj5vd.js → index-f8xnqrye.js} +55 -43
- package/dist/{index-fdk1t4d6.js → index-n0rxa4ca.js} +11962 -9900
- package/dist/{index-9nrj7797.js → index-n99fycq7.js} +22 -26
- package/dist/{keepalive-31613r2h.js → keepalive-3xspa9jw.js} +14 -11
- package/dist/{lead-mx59tr5s.js → lead-6qphc124.js} +43 -43
- package/dist/{maintenance-55ff39ca.js → maintenance-0teem1bb.js} +10 -12
- package/dist/{node-f0ykebbv.js → node-9sg7ajby.js} +25 -7
- package/dist/{oauth-refresh-sweep-anma954w.js → oauth-refresh-sweep-sh9q25xp.js} +13 -10
- package/dist/{onboard-b2fy8ftb.js → onboard-54qmnqxe.js} +4 -3
- package/dist/{opencode-adapter-p1s92tmy.js → opencode-adapter-wjfa2wvd.js} +6 -6
- package/dist/{otel-impl-9brvt7xv.js → otel-impl-ycqzp2a1.js} +2 -2
- package/dist/{pi-mono-adapter-kz4rrwvd.js → pi-mono-adapter-e77e9zst.js} +6 -6
- package/dist/{pricing-refresh-7w8mbmva.js → pricing-refresh-atnkngqs.js} +10 -10
- package/dist/{rbac-roles-ejp7qwvb.js → rbac-roles-9pjphh9q.js} +10 -10
- package/dist/{rbac-roles-nhk4r7bc.js → rbac-roles-j78wdksm.js} +10 -10
- package/dist/{render-v2-2gzt8skq.js → render-v2-pxjs49fx.js} +9 -10
- package/dist/{setup-5et8t412.js → setup-ymej0xtx.js} +2 -2
- package/dist/{work-objects-tb0er7qa.js → work-objects-3nh4ymzb.js} +26 -30
- package/dist/{worker-62cv075d.js → worker-qa9fkz5s.js} +43 -43
- package/dist/{x-vbxb31jr.js → x-ma4gnfgy.js} +2 -2
- package/openapi.json +457 -31
- package/package.json +14 -6
- package/plugin/commands/create-pr.md +7 -1
- package/plugin/pi-skills/create-pr/SKILL.md +7 -1
- package/src/agentmail/app.ts +5 -4
- package/src/be/automation-preflight.ts +11 -9
- package/src/be/boot-scrub-sweep.ts +302 -0
- package/src/be/connector-codes.ts +81 -0
- package/src/be/crypto/key-bootstrap.ts +1 -1
- package/src/be/db/approvals.ts +15 -1
- package/src/be/db/codex-oauth-device-flows.ts +40 -0
- package/src/be/db/config-secrets.ts +31 -0
- package/src/be/db/runtime.ts +4 -0
- package/src/be/db/script-runs.ts +15 -0
- package/src/be/db/tasks/write.ts +1 -1
- package/src/be/db-queries/mcp-oauth.ts +5 -0
- package/src/be/db-queries/oauth.ts +77 -0
- package/src/be/db.ts +162 -59
- package/src/be/events.ts +17 -1
- package/src/be/memory/index-content.ts +2 -2
- package/src/be/memory/providers/sqlite-store.ts +63 -11
- package/src/be/memory/types.ts +14 -0
- package/src/be/migrations/197_connector_codes.sql +23 -0
- package/src/be/migrations/198_permission_audit_guest.sql +33 -0
- package/src/be/migrations/200_backfill_auto_memory_expiry.sql +20 -0
- package/src/be/migrations/201_workflow_replay_columns.sql +10 -0
- package/src/be/migrations/202_codex_oauth_device_flows.sql +24 -0
- package/src/be/model-tier-resolution.ts +2 -1
- package/src/be/model-validation.ts +1 -1
- package/src/be/pricing-normalize.ts +3 -0
- package/src/be/rbac-audit.ts +2 -1
- package/src/be/scripts/db.ts +28 -4
- package/src/be/scripts/source-secrets.ts +37 -0
- package/src/be/scripts/typecheck.ts +23 -2
- package/src/be/scrub-json.ts +32 -0
- package/src/be/sealed-json.ts +59 -0
- package/src/be/secret-registry.ts +131 -0
- package/src/be/seed-pricing.ts +14 -0
- package/src/be/users.ts +7 -1
- package/src/be/workflow-replay.ts +77 -0
- package/src/cli.tsx +7 -0
- package/src/commands/provider-credentials.ts +46 -8
- package/src/commands/runner.ts +2 -10
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/github/app.ts +4 -4
- package/src/heartbeat/heartbeat.ts +20 -8
- package/src/hooks/pr-body-guard.ts +1 -1
- package/src/http/agents.ts +1 -0
- package/src/http/approval-requests.ts +1 -0
- package/src/http/apps.ts +3 -0
- package/src/http/auth.ts +4 -1
- package/src/http/codex-oauth-device.ts +8 -10
- package/src/http/codex-oauth-keep-warm.ts +2 -2
- package/src/http/config-values.ts +59 -0
- package/src/http/config.ts +5 -0
- package/src/http/db-query-bounded.ts +3 -0
- package/src/http/db-query-shared.ts +107 -15
- package/src/http/db-query.ts +10 -2
- package/src/http/extensions.ts +1 -0
- package/src/http/index.ts +23 -6
- package/src/http/integrations.ts +1 -38
- package/src/http/ip-rate-limit.ts +82 -0
- package/src/http/kv.ts +38 -4
- package/src/http/mcp-servers.ts +1 -0
- package/src/http/memory.ts +9 -0
- package/src/http/models-catalog.ts +2 -0
- package/src/http/operator-actor.ts +4 -0
- package/src/http/page-proxy.ts +22 -0
- package/src/http/pages.ts +4 -2
- package/src/http/realtime.ts +1 -1
- package/src/http/request-principal.ts +14 -1
- package/src/http/script-connections.ts +8 -0
- package/src/http/script-runs.ts +12 -4
- package/src/http/scripts.ts +15 -0
- package/src/http/session-cost-recompute.ts +3 -2
- package/src/http/tasks.ts +9 -0
- package/src/http/users.ts +226 -0
- package/src/http/workflows.ts +3 -2
- package/src/http.ts +1 -0
- package/src/integrations/kapso/client.ts +3 -1
- package/src/jira/app.ts +2 -2
- package/src/kv-view.ts +324 -0
- package/src/linear/app.ts +2 -2
- package/src/oauth/mcp-wrapper.ts +1 -1
- package/src/prompts/memories.ts +42 -1
- package/src/providers/acp-adapter.ts +85 -24
- package/src/providers/acp-target-catalog.ts +54 -1
- package/src/providers/acp-targets.ts +358 -4
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/cursor-adapter.ts +1 -1
- package/src/providers/grok-adapter.ts +186 -0
- package/src/providers/index.ts +5 -1
- package/src/providers/pricing-sources.md +6 -0
- package/src/providers/reasoning-effort.ts +17 -1
- package/src/providers/types.ts +2 -1
- package/src/rbac/can.ts +4 -3
- package/src/rbac/legacy-policy.ts +3 -3
- package/src/rbac/types.ts +6 -0
- package/src/script-workflows/supervisor.ts +11 -2
- package/src/scripts-runtime/eval-harness.ts +2 -12
- package/src/scripts-runtime/executors/quickjs-bundle.ts +152 -0
- package/src/scripts-runtime/executors/quickjs-prelude.ts +285 -0
- package/src/scripts-runtime/executors/quickjs-runner.ts +580 -0
- package/src/scripts-runtime/executors/quickjs-worker.ts +81 -0
- package/src/scripts-runtime/executors/quickjs.ts +196 -0
- package/src/scripts-runtime/executors/registry.ts +2 -0
- package/src/scripts-runtime/executors/sourcemap.ts +79 -0
- package/src/scripts-runtime/script-error-hints.ts +14 -0
- package/src/scripts-runtime/swarm-sdk.ts +9 -2
- package/src/scripts-runtime/types/stdlib.d.ts +27 -8
- package/src/scripts-runtime/types/swarm-extension.d.ts +3 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +27 -8
- package/src/server-user.ts +2 -20
- package/src/server.ts +3 -1
- package/src/slack/app.ts +4 -0
- package/src/slack/config.ts +3 -1
- package/src/slack/egress-scrub.ts +87 -0
- package/src/tests/acp-adapter.test.ts +191 -2
- package/src/tests/boot-scrub-sweep.test.ts +390 -0
- package/src/tests/budget-user-scope.test.ts +7 -21
- package/src/tests/check-pr-body.test.ts +149 -6
- package/src/tests/check-text-columns.test.ts +145 -0
- package/src/tests/claude-adapter-binary.test.ts +1 -1
- package/src/tests/claude-sdk-process.fixture.ts +13 -2
- package/src/tests/codex-oauth-device.test.ts +23 -37
- package/src/tests/codex-oauth-keep-warm.test.ts +29 -0
- package/src/tests/console-scrub.test.ts +141 -0
- package/src/tests/credential-check.test.ts +90 -0
- package/src/tests/db-query-denylist.test.ts +202 -0
- package/src/tests/db-task-read-contract.test.ts +1 -0
- package/src/tests/db-task-write-contract.test.ts +1 -0
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/entrypoint-acp-target-binary.test.ts +44 -1
- package/src/tests/events-scrub.test.ts +88 -0
- package/src/tests/explicit-model-validation.test.ts +81 -0
- package/src/tests/extensions-pre-task.test.ts +33 -1
- package/src/tests/fixtures/grok/acp-updates-recorded.jsonl +53 -0
- package/src/tests/fixtures/grok/prompt-response-meta.json +36 -0
- package/src/tests/fixtures/secret-corpus.ts +320 -0
- package/src/tests/grok-adapter.test.ts +647 -0
- package/src/tests/heartbeat-checklist.test.ts +44 -0
- package/src/tests/integration-disable-flags.test.ts +156 -0
- package/src/tests/kv-http.test.ts +34 -0
- package/src/tests/kv-tool.test.ts +274 -1
- package/src/tests/kv-view.test.ts +147 -0
- package/src/tests/mcp-user-route.test.ts +257 -15
- package/src/tests/memory-injection-outcome.test.ts +143 -0
- package/src/tests/model-tier-resolution.test.ts +76 -3
- package/src/tests/page-guest-session.test.ts +424 -0
- package/src/tests/page-proxy.test.ts +1 -1
- package/src/tests/page-viewer-auth.test.ts +22 -1
- package/src/tests/pr-body-leaks.test.ts +157 -2
- package/src/tests/preload.ts +10 -0
- package/src/tests/provider-registration.test.ts +3 -1
- package/src/tests/rbac-audit.test.ts +32 -0
- package/src/tests/rbac-mcp-admission-e2e.test.ts +4 -62
- package/src/tests/script-connections.test.ts +9 -0
- package/src/tests/script-executor-conformance.test.ts +3 -0
- package/src/tests/script-executor-quickjs.test.ts +442 -0
- package/src/tests/script-executor-registry.test.ts +6 -1
- package/src/tests/script-runs-http.test.ts +126 -1
- package/src/tests/script-runs-scrub.test.ts +275 -0
- package/src/tests/scripts-http.test.ts +82 -1
- package/src/tests/scripts-mcp-e2e.test.ts +42 -6
- package/src/tests/scripts-runtime.test.ts +37 -0
- package/src/tests/scrubbed-text-12e.test.ts +289 -0
- package/src/tests/sealed-key-guard.test.ts +166 -0
- package/src/tests/secret-canary-sweep.test.ts +958 -0
- package/src/tests/secret-registry.test.ts +355 -0
- package/src/tests/secret-rules.test.ts +230 -0
- package/src/tests/secret-scrubber.test.ts +317 -1
- package/src/tests/session-costs-golden.test.ts +17 -0
- package/src/tests/slack-egress-scrub.test.ts +216 -0
- package/src/tests/swarm-tool-result-gate.test.ts +143 -30
- package/src/tests/task-tools-ctx.test.ts +3 -0
- package/src/tests/user-token-routes.test.ts +342 -3
- package/src/tests/workflow-replay.test.ts +518 -0
- package/src/tests/workflow-scrub.test.ts +242 -0
- package/src/tests/workflow-system-one-decision-providers.test.ts +737 -0
- package/src/tools/db-query.ts +1 -1
- package/src/tools/inject-learning.ts +1 -1
- package/src/tools/kv/kv-get.ts +136 -6
- package/src/tools/send-task.ts +61 -17
- package/src/tools/utils.ts +186 -43
- package/src/tools/workflows/get-workflow-run.ts +3 -2
- package/src/types.ts +16 -1
- package/src/utils/console-scrub-install.ts +5 -0
- package/src/utils/console-scrub.ts +57 -0
- package/src/utils/credentials.ts +8 -0
- package/src/utils/page-session.ts +6 -2
- package/src/utils/pr-body-leaks.ts +149 -10
- package/src/utils/request-auth-context.ts +2 -0
- package/src/utils/secret-rules.generated.ts +2055 -0
- package/src/utils/secret-scrubber.ts +662 -36
- package/src/workflows/engine.ts +39 -4
- package/src/workflows/executors/notify.ts +6 -1
- package/src/workflows/executors/system-one-decision.ts +31 -12
- package/src/workflows/executors/system-one-openai-wire.ts +168 -0
- package/src/workflows/executors/system-one-providers.ts +112 -11
- package/src/workflows/executors/system-one-wire.ts +79 -0
- package/src/workflows/recovery.ts +10 -1
- package/src/workflows/resume.ts +62 -3
- package/src/workflows/retry-poller.ts +3 -0
- package/templates/ai-toolbox.manifest.json +2 -2
- package/templates/skills/agent-browser/SKILL.md +5 -5
- package/templates/skills/agent-browser/config.json +1 -1
- package/templates/skills/agent-browser/content.md +4 -4
- package/templates/skills/artifacts/content.md +17 -7
- package/templates/skills/jira-interaction/config.json +1 -1
- package/templates/skills/jira-interaction/content.md +56 -205
- package/templates/skills/linear-interaction/config.json +1 -1
- package/templates/skills/linear-interaction/content.md +31 -50
- package/templates/skills/pages/content.md +8 -4
- package/templates/skills/qa/content.md +2 -2
- package/templates/skills/workflow-iterate/content.md +4 -4
- package/tsconfig.json +3 -3
- package/dist/boot-scrub-logs-t8xg7wd7.js +0 -99
- package/dist/cli-0a7r5331.js +0 -168
- package/dist/cli-93rxw9yd.js +0 -951
- package/dist/cli-cvk6yzw5.js +0 -40
- package/dist/cli-dgnj0zcc.js +0 -40
- package/dist/cli-qt5wzq6v.js +0 -256
- package/dist/cli-v7fqehqd.js +0 -9281
- package/dist/cli-vbr859rz.js +0 -572
- package/src/be/boot-scrub-logs.ts +0 -132
|
@@ -359,6 +359,79 @@ describe("claim-time harness guard", () => {
|
|
|
359
359
|
});
|
|
360
360
|
});
|
|
361
361
|
|
|
362
|
+
describe("claim-time harness guard on grok", () => {
|
|
363
|
+
async function grokWorker(name: string) {
|
|
364
|
+
return createAgent({
|
|
365
|
+
name,
|
|
366
|
+
isLead: false,
|
|
367
|
+
status: "idle",
|
|
368
|
+
maxTasks: 1,
|
|
369
|
+
harnessProvider: "grok",
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
test("a directed task pinning another vendor's model or alias fails fast", async () => {
|
|
374
|
+
for (const model of ["claude-opus-5-5", "anthropic/claude-opus-5-5", "latest:anthropic/opus"]) {
|
|
375
|
+
const worker = await grokWorker(`w-grok-mismatch-${model}`);
|
|
376
|
+
const task = await createTaskExtended("review", { agentId: worker.id, model });
|
|
377
|
+
const trigger = await callPoll(worker.id, header({}));
|
|
378
|
+
expect(trigger?.taskId).toBe(task.id);
|
|
379
|
+
expect(String(trigger?.task.modelUnsupported)).toStartWith(
|
|
380
|
+
`[model-harness-mismatch] Model "${model}" does not run on the grok harness of this worker.`,
|
|
381
|
+
);
|
|
382
|
+
expect(trigger?.task.resolvedModel).toBeUndefined();
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
test("a directed xAI or OpenRouter model resolves as pinned", async () => {
|
|
387
|
+
await seedCatalog([
|
|
388
|
+
entry("anthropic", "claude-opus-5-5", daysAgo(30)),
|
|
389
|
+
entry("openrouter", "deepseek/deepseek-v4.1-flash", daysAgo(30)),
|
|
390
|
+
]);
|
|
391
|
+
for (const [model, resolved] of [
|
|
392
|
+
["grok-4.6", "grok-4.6"],
|
|
393
|
+
["openrouter/deepseek/deepseek-v4.1-flash", "openrouter/deepseek/deepseek-v4.1-flash"],
|
|
394
|
+
["latest:openrouter/deepseek/deepseek-v4.1-*", "openrouter/deepseek/deepseek-v4.1-flash"],
|
|
395
|
+
]) {
|
|
396
|
+
const worker = await grokWorker(`w-grok-ok-${model}`);
|
|
397
|
+
const task = await createTaskExtended("review", { agentId: worker.id, model });
|
|
398
|
+
const trigger = await callPoll(worker.id, header({}));
|
|
399
|
+
expect(trigger?.taskId).toBe(task.id);
|
|
400
|
+
expect(trigger?.task.modelUnsupported).toBeUndefined();
|
|
401
|
+
expect(trigger?.task.resolvedModel).toBe(resolved);
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
test("an Anthropic worker-env tier value is skipped for the grok tier default", async () => {
|
|
406
|
+
const worker = await grokWorker("w-grok-env");
|
|
407
|
+
const task = await createTaskExtended("smart", { agentId: worker.id, modelTier: "smart" });
|
|
408
|
+
await callPoll(worker.id, header({ grok: { smart: "claude-opus-5-5" } }));
|
|
409
|
+
expect(await taskRow(task.id)).toEqual({
|
|
410
|
+
resolvedModel: "grok-4.6",
|
|
411
|
+
modelSource: "tier-default",
|
|
412
|
+
modelAlias: null,
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
test("pool: Grok skips an Anthropic task and claims the xAI and OpenRouter ones", async () => {
|
|
417
|
+
const vendor = await createTaskExtended("opus pool", {
|
|
418
|
+
model: "claude-opus-5-5",
|
|
419
|
+
priority: 90,
|
|
420
|
+
});
|
|
421
|
+
const xai = await createTaskExtended("xai pool", { model: "grok-4.6", priority: 50 });
|
|
422
|
+
const openrouter = await createTaskExtended("or pool", {
|
|
423
|
+
model: "openrouter/deepseek/deepseek-v4.1-flash",
|
|
424
|
+
priority: 10,
|
|
425
|
+
});
|
|
426
|
+
const first = await callPoll((await grokWorker("w-grok-pool-1")).id, header({}));
|
|
427
|
+
expect(first?.taskId).toBe(xai.id);
|
|
428
|
+
const second = await callPoll((await grokWorker("w-grok-pool-2")).id, header({}));
|
|
429
|
+
expect(second?.taskId).toBe(openrouter.id);
|
|
430
|
+
const third = await callPoll((await grokWorker("w-grok-pool-3")).id, header({}));
|
|
431
|
+
expect(third?.type === "task_assigned" && third.taskId === vendor.id).toBe(false);
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
|
|
362
435
|
describe("task list summaries", () => {
|
|
363
436
|
test("carry the claim-time resolution the dashboard table shows", async () => {
|
|
364
437
|
const worker = await createAgent({
|
|
@@ -508,8 +581,8 @@ describe("previewModelTiers", () => {
|
|
|
508
581
|
|
|
509
582
|
// acp has no portable tier mapping, so it has nothing to preview.
|
|
510
583
|
expect(rows.some((r) => r.provider === "acp")).toBe(false);
|
|
511
|
-
expect(new Set(rows.map((r) => r.provider)).size).toBe(
|
|
512
|
-
expect(rows).toHaveLength(
|
|
584
|
+
expect(new Set(rows.map((r) => r.provider)).size).toBe(10);
|
|
585
|
+
expect(rows).toHaveLength(40);
|
|
513
586
|
});
|
|
514
587
|
|
|
515
588
|
test("an alias that resolves to nothing reports the default it falls back to", async () => {
|
|
@@ -564,7 +637,7 @@ describe("GET /api/models-catalog/tiers", () => {
|
|
|
564
637
|
expect(handled).toBe(true);
|
|
565
638
|
expect(status).toBe(200);
|
|
566
639
|
const body = JSON.parse(bodyStr) as { tiers: { provider: string; tier: string }[] };
|
|
567
|
-
expect(body.tiers).toHaveLength(
|
|
640
|
+
expect(body.tiers).toHaveLength(40);
|
|
568
641
|
expect(body.tiers.find((t) => t.provider === "claude" && t.tier === "smart")).toMatchObject({
|
|
569
642
|
key: "MODEL_TIER_CLAUDE_SMART",
|
|
570
643
|
source: "tier-config",
|
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A page session with no signed-in user and no operator launch is a guest. A guest
|
|
3
|
+
* reaches only its own page record and its own KV through the proxy, and every
|
|
4
|
+
* server-side gate denies it.
|
|
5
|
+
* A user session and an operator-launched session keep working.
|
|
6
|
+
*/
|
|
7
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
8
|
+
import { randomUUID } from "node:crypto";
|
|
9
|
+
import { unlink } from "node:fs/promises";
|
|
10
|
+
import { type IncomingMessage, ServerResponse } from "node:http";
|
|
11
|
+
import type { Subprocess } from "bun";
|
|
12
|
+
import { getOperatorActor } from "../http/operator-actor";
|
|
13
|
+
import { isGuestAllowedProxyPath } from "../http/page-proxy";
|
|
14
|
+
import { agentFirstPrincipal, requestPrincipal } from "../http/request-principal";
|
|
15
|
+
import { can } from "../rbac";
|
|
16
|
+
import { signPageSession } from "../utils/page-session";
|
|
17
|
+
import { setRequestAuth } from "../utils/request-auth-context";
|
|
18
|
+
import { getFreePort, SERVER_BOOT_HOOK_TIMEOUT_MS, waitForServer } from "./test-net";
|
|
19
|
+
|
|
20
|
+
let BASE = "";
|
|
21
|
+
const TEST_DB_PATH = `/tmp/test-page-guest-${Date.now()}.sqlite`;
|
|
22
|
+
const API_KEY = "example-test-page-guest-key-12345";
|
|
23
|
+
const PAGE_SECRET = "example-test-page-guest-secret-67890";
|
|
24
|
+
let serverProc: Subprocess;
|
|
25
|
+
const agentId = randomUUID();
|
|
26
|
+
|
|
27
|
+
const exp = () => Math.floor(Date.now() / 1000) + 3600;
|
|
28
|
+
|
|
29
|
+
async function api(path: string, init: RequestInit & { headers?: Record<string, string> } = {}) {
|
|
30
|
+
return fetch(`${BASE}${path}`, {
|
|
31
|
+
...init,
|
|
32
|
+
headers: {
|
|
33
|
+
Authorization: `Bearer ${API_KEY}`,
|
|
34
|
+
"Content-Type": "application/json",
|
|
35
|
+
...init.headers,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function createPage(): Promise<string> {
|
|
41
|
+
const res = await api("/api/pages", {
|
|
42
|
+
method: "POST",
|
|
43
|
+
headers: { "X-Agent-ID": agentId },
|
|
44
|
+
body: JSON.stringify({
|
|
45
|
+
slug: `g-${randomUUID().slice(0, 8)}`,
|
|
46
|
+
title: "Guest Test",
|
|
47
|
+
contentType: "text/html",
|
|
48
|
+
authMode: "public",
|
|
49
|
+
body: "<h1>guest</h1>",
|
|
50
|
+
}),
|
|
51
|
+
});
|
|
52
|
+
expect(res.status).toBe(201);
|
|
53
|
+
return ((await res.json()) as { id: string }).id;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Call the API the way the page proxy does: shared key plus the signed page session. */
|
|
57
|
+
function asSession(cookie: string, pageId: string, path: string, init: RequestInit = {}) {
|
|
58
|
+
return api(path, {
|
|
59
|
+
...init,
|
|
60
|
+
headers: { "X-Page-Session": cookie, "X-Page-Id": pageId },
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let pageId = "";
|
|
65
|
+
let guestCookie = "";
|
|
66
|
+
let operatorCookie = "";
|
|
67
|
+
let userCookie = "";
|
|
68
|
+
|
|
69
|
+
beforeAll(async () => {
|
|
70
|
+
const port = await getFreePort();
|
|
71
|
+
BASE = `http://localhost:${port}`;
|
|
72
|
+
process.env.PAGE_SESSION_SECRET = PAGE_SECRET;
|
|
73
|
+
serverProc = Bun.spawn(["bun", "src/http.ts"], {
|
|
74
|
+
cwd: `${import.meta.dir}/../..`,
|
|
75
|
+
env: {
|
|
76
|
+
...process.env,
|
|
77
|
+
PORT: String(port),
|
|
78
|
+
DATABASE_PATH: TEST_DB_PATH,
|
|
79
|
+
API_KEY,
|
|
80
|
+
AGENT_SWARM_API_KEY: API_KEY,
|
|
81
|
+
PAGE_SESSION_SECRET: PAGE_SECRET,
|
|
82
|
+
MCP_BASE_URL: `http://127.0.0.1:${port}`,
|
|
83
|
+
CAPABILITIES: "core,task-pool,messaging,profiles,services,scheduling,memory",
|
|
84
|
+
EMBEDDING_API_KEY: "",
|
|
85
|
+
OPENAI_API_KEY: "",
|
|
86
|
+
SLACK_BOT_TOKEN: "",
|
|
87
|
+
GITHUB_WEBHOOK_SECRET: "",
|
|
88
|
+
AGENTMAIL_API_KEY: "",
|
|
89
|
+
},
|
|
90
|
+
stdout: "ignore",
|
|
91
|
+
stderr: "ignore",
|
|
92
|
+
});
|
|
93
|
+
await waitForServer(`${BASE}/health`);
|
|
94
|
+
await api("/api/agents", {
|
|
95
|
+
method: "POST",
|
|
96
|
+
headers: { "X-Agent-ID": agentId },
|
|
97
|
+
body: JSON.stringify({
|
|
98
|
+
name: "Owner",
|
|
99
|
+
isLead: false,
|
|
100
|
+
role: "worker",
|
|
101
|
+
capabilities: ["core"],
|
|
102
|
+
maxTasks: 1,
|
|
103
|
+
}),
|
|
104
|
+
});
|
|
105
|
+
pageId = await createPage();
|
|
106
|
+
|
|
107
|
+
const created = await api("/api/users", {
|
|
108
|
+
method: "POST",
|
|
109
|
+
body: JSON.stringify({ name: "Viewer" }),
|
|
110
|
+
});
|
|
111
|
+
const userId = ((await created.json()) as { user: { id: string } }).user.id;
|
|
112
|
+
userCookie = await signPageSession({ pageId, exp: exp(), uid: userId, name: "Viewer" });
|
|
113
|
+
guestCookie = await signPageSession({ pageId, exp: exp() });
|
|
114
|
+
operatorCookie = await signPageSession({ pageId, exp: exp(), name: "guest-op", op: true });
|
|
115
|
+
|
|
116
|
+
await api("/api/config", {
|
|
117
|
+
method: "PUT",
|
|
118
|
+
body: JSON.stringify({
|
|
119
|
+
scope: "global",
|
|
120
|
+
key: "GUEST_PROBE_SECRET",
|
|
121
|
+
value: "s3cret-value",
|
|
122
|
+
isSecret: true,
|
|
123
|
+
}),
|
|
124
|
+
});
|
|
125
|
+
}, SERVER_BOOT_HOOK_TIMEOUT_MS);
|
|
126
|
+
|
|
127
|
+
afterAll(async () => {
|
|
128
|
+
serverProc?.kill();
|
|
129
|
+
await serverProc?.exited.catch(() => {});
|
|
130
|
+
for (const suffix of ["", "-wal", "-shm"])
|
|
131
|
+
await unlink(`${TEST_DB_PATH}${suffix}`).catch(() => {});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe("proxy allowlist for a guest session", () => {
|
|
135
|
+
test("allows the page's own record and its KV", async () => {
|
|
136
|
+
const own = await fetch(`${BASE}/@swarm/api/pages/${pageId}`, {
|
|
137
|
+
headers: { Cookie: `page_session=${guestCookie}` },
|
|
138
|
+
});
|
|
139
|
+
expect(own.status).toBe(200);
|
|
140
|
+
const put = await fetch(`${BASE}/@swarm/api/kv/probe`, {
|
|
141
|
+
method: "PUT",
|
|
142
|
+
headers: { Cookie: `page_session=${guestCookie}`, "Content-Type": "application/json" },
|
|
143
|
+
body: JSON.stringify({ value: "v" }),
|
|
144
|
+
});
|
|
145
|
+
expect(put.status).toBe(200);
|
|
146
|
+
const get = await fetch(`${BASE}/@swarm/api/kv/probe`, {
|
|
147
|
+
headers: { Cookie: `page_session=${guestCookie}` },
|
|
148
|
+
});
|
|
149
|
+
expect(get.status).toBe(200);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
for (const [method, path] of [
|
|
153
|
+
["GET", "config?includeSecrets=true"],
|
|
154
|
+
["GET", "tasks"],
|
|
155
|
+
["GET", `agents/${agentId}`],
|
|
156
|
+
["GET", "whoami"],
|
|
157
|
+
["POST", "memory/search"],
|
|
158
|
+
["GET", "pages/not-this-page"],
|
|
159
|
+
["GET", "pages"],
|
|
160
|
+
["DELETE", "pages/not-this-page"],
|
|
161
|
+
] as const) {
|
|
162
|
+
test(`refuses ${method} ${path} with 403`, async () => {
|
|
163
|
+
const res = await fetch(`${BASE}/@swarm/api/${path}`, {
|
|
164
|
+
method,
|
|
165
|
+
headers: { Cookie: `page_session=${guestCookie}`, "Content-Type": "application/json" },
|
|
166
|
+
body: method === "POST" ? "{}" : undefined,
|
|
167
|
+
});
|
|
168
|
+
expect(res.status).toBe(403);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
test("allowlist helper", () => {
|
|
173
|
+
expect(isGuestAllowedProxyPath("GET", `pages/${pageId}`, pageId)).toBe(true);
|
|
174
|
+
expect(isGuestAllowedProxyPath("GET", "kv/a/incr", pageId)).toBe(true);
|
|
175
|
+
expect(isGuestAllowedProxyPath("PUT", `pages/${pageId}`, pageId)).toBe(false);
|
|
176
|
+
expect(isGuestAllowedProxyPath("GET", `pages/${pageId}/versions`, pageId)).toBe(false);
|
|
177
|
+
expect(isGuestAllowedProxyPath("GET", "kvx", pageId)).toBe(false);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe("a password-page guest is isolated from another page", () => {
|
|
182
|
+
test("cannot read page B's record or KV, and gets its own KV without passwordHash", async () => {
|
|
183
|
+
const created = await api("/api/pages", {
|
|
184
|
+
method: "POST",
|
|
185
|
+
headers: { "X-Agent-ID": agentId },
|
|
186
|
+
body: JSON.stringify({
|
|
187
|
+
slug: `pw-${randomUUID().slice(0, 8)}`,
|
|
188
|
+
title: "Password Page",
|
|
189
|
+
contentType: "text/html",
|
|
190
|
+
authMode: "password",
|
|
191
|
+
password: "open-sesame",
|
|
192
|
+
body: "<h1>a</h1>",
|
|
193
|
+
}),
|
|
194
|
+
});
|
|
195
|
+
expect(created.status).toBe(201);
|
|
196
|
+
const pageA = ((await created.json()) as { id: string }).id;
|
|
197
|
+
const pageB = await createPage();
|
|
198
|
+
const nsB = `task:page:${pageB}`;
|
|
199
|
+
|
|
200
|
+
const seedB = await api("/api/kv/shared", {
|
|
201
|
+
method: "PUT",
|
|
202
|
+
headers: { "X-Page-Id": pageB },
|
|
203
|
+
body: JSON.stringify({ value: "b-value" }),
|
|
204
|
+
});
|
|
205
|
+
expect(seedB.status).toBe(200);
|
|
206
|
+
|
|
207
|
+
const unlock = await fetch(`${BASE}/p/${pageA}?key=open-sesame`);
|
|
208
|
+
expect(unlock.status).toBe(200);
|
|
209
|
+
const cookieA = /page_session=([^;]+)/.exec(unlock.headers.get("set-cookie") ?? "")?.[1];
|
|
210
|
+
expect(cookieA).toBeTruthy();
|
|
211
|
+
const asGuest = (path: string, init: RequestInit = {}) =>
|
|
212
|
+
fetch(`${BASE}/@swarm/api/${path}`, {
|
|
213
|
+
...init,
|
|
214
|
+
headers: {
|
|
215
|
+
Cookie: `page_session=${cookieA}`,
|
|
216
|
+
"Content-Type": "application/json",
|
|
217
|
+
"X-Page-Id": pageB,
|
|
218
|
+
"X-Agent-ID": agentId,
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const putA = await asGuest("kv/shared", {
|
|
223
|
+
method: "PUT",
|
|
224
|
+
body: JSON.stringify({ value: "a-value" }),
|
|
225
|
+
});
|
|
226
|
+
expect(putA.status).toBe(200);
|
|
227
|
+
|
|
228
|
+
expect((await asGuest(`pages/${pageB}`)).status).toBe(403);
|
|
229
|
+
|
|
230
|
+
for (const path of ["kv/shared", `kv/_/${encodeURIComponent(nsB)}/shared`]) {
|
|
231
|
+
const res = await asGuest(path);
|
|
232
|
+
expect(res.status).toBe(200);
|
|
233
|
+
expect(await res.json()).toMatchObject({
|
|
234
|
+
namespace: `task:page:${pageA}`,
|
|
235
|
+
key: "shared",
|
|
236
|
+
value: "a-value",
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const list = await asGuest(`kv/_/${encodeURIComponent(nsB)}`);
|
|
241
|
+
expect(list.status).toBe(200);
|
|
242
|
+
expect(JSON.stringify(await list.json())).not.toContain("b-value");
|
|
243
|
+
|
|
244
|
+
const own = await asGuest(`pages/${pageA}`);
|
|
245
|
+
expect(own.status).toBe(200);
|
|
246
|
+
expect(await own.json()).not.toHaveProperty("passwordHash");
|
|
247
|
+
|
|
248
|
+
const stillB = await api("/api/kv/shared", { headers: { "X-Page-Id": pageB } });
|
|
249
|
+
expect(await stillB.json()).toMatchObject({ namespace: nsB, value: "b-value" });
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
describe("server-side gates deny a guest and keep the operator and user", () => {
|
|
254
|
+
test("config reads by session kind", async () => {
|
|
255
|
+
const guest = await (
|
|
256
|
+
await asSession(guestCookie, pageId, "/api/config?includeSecrets=true&key=GUEST_PROBE_SECRET")
|
|
257
|
+
).json();
|
|
258
|
+
expect(JSON.stringify(guest)).not.toContain("s3cret-value");
|
|
259
|
+
const op = await (
|
|
260
|
+
await asSession(
|
|
261
|
+
operatorCookie,
|
|
262
|
+
pageId,
|
|
263
|
+
"/api/config?includeSecrets=true&key=GUEST_PROBE_SECRET",
|
|
264
|
+
)
|
|
265
|
+
).json();
|
|
266
|
+
expect(JSON.stringify(op)).toContain("s3cret-value");
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test("config resolved: guest masked", async () => {
|
|
270
|
+
const guest = await (
|
|
271
|
+
await asSession(
|
|
272
|
+
guestCookie,
|
|
273
|
+
pageId,
|
|
274
|
+
"/api/config/resolved?includeSecrets=true&key=GUEST_PROBE_SECRET",
|
|
275
|
+
)
|
|
276
|
+
).json();
|
|
277
|
+
expect(JSON.stringify(guest)).not.toContain("s3cret-value");
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test("config write (ensureConfigAdmin)", async () => {
|
|
281
|
+
const body = JSON.stringify({ scope: "global", key: "GUEST_WRITE", value: "x" });
|
|
282
|
+
expect(
|
|
283
|
+
(await asSession(guestCookie, pageId, "/api/config", { method: "PUT", body })).status,
|
|
284
|
+
).toBe(403);
|
|
285
|
+
expect(
|
|
286
|
+
(await asSession(operatorCookie, pageId, "/api/config", { method: "PUT", body })).status,
|
|
287
|
+
).toBe(200);
|
|
288
|
+
expect(
|
|
289
|
+
(await asSession(userCookie, pageId, "/api/config", { method: "PUT", body })).status,
|
|
290
|
+
).toBe(200);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test("tasks (resolveTaskWritePrincipal, canSteerTask, canActOnOwnTask)", async () => {
|
|
294
|
+
const id = randomUUID();
|
|
295
|
+
for (const path of [
|
|
296
|
+
`/api/tasks/${id}/cancel`,
|
|
297
|
+
`/api/tasks/${id}/progress`,
|
|
298
|
+
`/api/tasks/${id}/steer`,
|
|
299
|
+
`/api/tasks/${id}/promote-draft`,
|
|
300
|
+
]) {
|
|
301
|
+
expect(
|
|
302
|
+
(await asSession(guestCookie, pageId, path, { method: "POST", body: "{}" })).status,
|
|
303
|
+
).toBe(403);
|
|
304
|
+
}
|
|
305
|
+
expect(
|
|
306
|
+
(
|
|
307
|
+
await asSession(operatorCookie, pageId, `/api/tasks/${id}/cancel`, {
|
|
308
|
+
method: "POST",
|
|
309
|
+
body: "{}",
|
|
310
|
+
})
|
|
311
|
+
).status,
|
|
312
|
+
).toBe(404);
|
|
313
|
+
expect(
|
|
314
|
+
(
|
|
315
|
+
await asSession(userCookie, pageId, `/api/tasks/${id}/cancel`, {
|
|
316
|
+
method: "POST",
|
|
317
|
+
body: "{}",
|
|
318
|
+
})
|
|
319
|
+
).status,
|
|
320
|
+
).toBe(404);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
test("memory (ingestPrincipal)", async () => {
|
|
324
|
+
const body = JSON.stringify({ content: "c", name: "n", scope: "swarm", source: "manual" });
|
|
325
|
+
expect(
|
|
326
|
+
(await asSession(guestCookie, pageId, "/api/memory/index", { method: "POST", body })).status,
|
|
327
|
+
).toBe(403);
|
|
328
|
+
expect(
|
|
329
|
+
(await asSession(guestCookie, pageId, "/api/memory/list", { method: "POST", body: "{}" }))
|
|
330
|
+
.status,
|
|
331
|
+
).toBe(403);
|
|
332
|
+
expect(
|
|
333
|
+
(await asSession(operatorCookie, pageId, "/api/memory/index", { method: "POST", body }))
|
|
334
|
+
.status,
|
|
335
|
+
).not.toBe(403);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test("admin surfaces: script-connections, mcp-servers, extensions, apps", async () => {
|
|
339
|
+
const probes: [string, string, string][] = [
|
|
340
|
+
["POST", `/api/script-connections/${randomUUID()}/refresh`, "{}"],
|
|
341
|
+
[
|
|
342
|
+
"POST",
|
|
343
|
+
"/api/mcp-servers",
|
|
344
|
+
JSON.stringify({ name: "m", transport: "http", url: "http://127.0.0.1:1" }),
|
|
345
|
+
],
|
|
346
|
+
["POST", `/api/extensions/${randomUUID()}/enable`, "{}"],
|
|
347
|
+
["POST", "/api/apps", JSON.stringify({ name: "a", definition: {} })],
|
|
348
|
+
];
|
|
349
|
+
for (const [method, path, body] of probes) {
|
|
350
|
+
const guest = await asSession(guestCookie, pageId, path, { method, body });
|
|
351
|
+
expect([method, path, guest.status]).toEqual([method, path, 403]);
|
|
352
|
+
const op = await asSession(operatorCookie, pageId, path, { method, body });
|
|
353
|
+
expect([method, path, op.status === 403]).toEqual([method, path, false]);
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test("approval cancel (approvalCancelPrincipal)", async () => {
|
|
358
|
+
const created = await api("/api/approval-requests", {
|
|
359
|
+
method: "POST",
|
|
360
|
+
body: JSON.stringify({
|
|
361
|
+
title: "t",
|
|
362
|
+
questions: [{ id: "q", type: "approval", label: "ok" }],
|
|
363
|
+
approvers: { policy: "any" },
|
|
364
|
+
}),
|
|
365
|
+
});
|
|
366
|
+
expect(created.status).toBe(201);
|
|
367
|
+
const { approvalRequest } = (await created.json()) as { approvalRequest: { id: string } };
|
|
368
|
+
const path = `/api/approval-requests/${approvalRequest.id}/cancel`;
|
|
369
|
+
expect(
|
|
370
|
+
(await asSession(guestCookie, pageId, path, { method: "POST", body: "{}" })).status,
|
|
371
|
+
).toBe(403);
|
|
372
|
+
expect(
|
|
373
|
+
(await asSession(operatorCookie, pageId, path, { method: "POST", body: "{}" })).status,
|
|
374
|
+
).toBe(200);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
test("a user session through the proxy and an operator-launched session still work", async () => {
|
|
378
|
+
for (const cookie of [userCookie, operatorCookie]) {
|
|
379
|
+
const res = await fetch(`${BASE}/@swarm/api/tasks`, {
|
|
380
|
+
headers: { Cookie: `page_session=${cookie}` },
|
|
381
|
+
});
|
|
382
|
+
expect(res.status).toBe(200);
|
|
383
|
+
}
|
|
384
|
+
const whoami = await fetch(`${BASE}/@swarm/api/whoami`, {
|
|
385
|
+
headers: { Cookie: `page_session=${userCookie}` },
|
|
386
|
+
});
|
|
387
|
+
expect(((await whoami.json()) as { kind: string }).kind).toBe("user");
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
describe("in-process principal helpers", () => {
|
|
392
|
+
const fakeReq = () => ({ headers: {} }) as unknown as IncomingMessage;
|
|
393
|
+
|
|
394
|
+
test("requestPrincipal and agentFirstPrincipal map a guest to guest", async () => {
|
|
395
|
+
const req = fakeReq();
|
|
396
|
+
setRequestAuth(req, { kind: "guest" });
|
|
397
|
+
expect(await requestPrincipal(req, undefined)).toEqual({ kind: "guest" });
|
|
398
|
+
expect(await agentFirstPrincipal(req, undefined)).toEqual({ kind: "guest" });
|
|
399
|
+
const op = fakeReq();
|
|
400
|
+
setRequestAuth(op, { kind: "operator", fingerprint: "fp" });
|
|
401
|
+
expect(await requestPrincipal(op, undefined)).toEqual({ kind: "operator" });
|
|
402
|
+
expect(await agentFirstPrincipal(op, undefined)).toEqual({ kind: "operator" });
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
test("getOperatorActor refuses a guest", () => {
|
|
406
|
+
const req = fakeReq();
|
|
407
|
+
setRequestAuth(req, { kind: "guest" });
|
|
408
|
+
const res = new ServerResponse(req);
|
|
409
|
+
expect(getOperatorActor(req, res)).toBeNull();
|
|
410
|
+
expect(res.statusCode).toBe(403);
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
test("can() denies every verb for a guest and still allows the operator", () => {
|
|
414
|
+
expect(
|
|
415
|
+
can({ principal: { kind: "guest" }, verb: "config.read.secrets", source: "http" }).allow,
|
|
416
|
+
).toBe(false);
|
|
417
|
+
expect(
|
|
418
|
+
can({ principal: { kind: "guest" }, verb: "memory.read.any", source: "http" }).allow,
|
|
419
|
+
).toBe(false);
|
|
420
|
+
expect(
|
|
421
|
+
can({ principal: { kind: "operator" }, verb: "memory.read.any", source: "http" }).allow,
|
|
422
|
+
).toBe(true);
|
|
423
|
+
});
|
|
424
|
+
});
|
|
@@ -389,7 +389,7 @@ describe("/@swarm/api/* proxy", () => {
|
|
|
389
389
|
test("proxy does NOT require a bearer header (cookie is the auth)", async () => {
|
|
390
390
|
const id = await createPage();
|
|
391
391
|
const exp = Math.floor(Date.now() / 1000) + 3600;
|
|
392
|
-
const token = await signPageSession({ pageId: id, exp });
|
|
392
|
+
const token = await signPageSession({ pageId: id, exp, op: true });
|
|
393
393
|
// Send WITHOUT Authorization header — pure cookie auth.
|
|
394
394
|
const res = await fetch(`${BASE}/@swarm/api/agents/${agentId}`, {
|
|
395
395
|
headers: { Cookie: `page_session=${token}` },
|
|
@@ -84,7 +84,7 @@ describe("page-session viewer auth", () => {
|
|
|
84
84
|
expect(forged?.page).toBeUndefined();
|
|
85
85
|
});
|
|
86
86
|
|
|
87
|
-
test("guest sessions
|
|
87
|
+
test("guest sessions resolve to guest", async () => {
|
|
88
88
|
const session = await signPageSession({
|
|
89
89
|
pageId: PAGE_ID,
|
|
90
90
|
exp: Math.floor(Date.now() / 1000) + 3600,
|
|
@@ -98,6 +98,27 @@ describe("page-session viewer auth", () => {
|
|
|
98
98
|
}),
|
|
99
99
|
API_KEY,
|
|
100
100
|
);
|
|
101
|
+
expect(auth).toMatchObject({
|
|
102
|
+
kind: "guest",
|
|
103
|
+
page: { id: PAGE_ID, executionAgentId: PAGE_AGENT_ID },
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("operator-launched sessions stay operator", async () => {
|
|
108
|
+
const session = await signPageSession({
|
|
109
|
+
pageId: PAGE_ID,
|
|
110
|
+
exp: Math.floor(Date.now() / 1000) + 3600,
|
|
111
|
+
name: "guest-test",
|
|
112
|
+
op: true,
|
|
113
|
+
});
|
|
114
|
+
const auth = await resolveHttpRequestAuth(
|
|
115
|
+
request({
|
|
116
|
+
authorization: `Bearer ${API_KEY}`,
|
|
117
|
+
"x-page-session": session,
|
|
118
|
+
"x-page-id": PAGE_ID,
|
|
119
|
+
}),
|
|
120
|
+
API_KEY,
|
|
121
|
+
);
|
|
101
122
|
expect(auth).toMatchObject({
|
|
102
123
|
kind: "operator",
|
|
103
124
|
page: { id: PAGE_ID, executionAgentId: PAGE_AGENT_ID },
|