@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
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small in-memory per-IP token bucket for unauthenticated routes. State is
|
|
3
|
+
* per process and resets on restart, which is fine for defense-in-depth on
|
|
4
|
+
* routes whose credential is already unguessable.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { IncomingMessage } from "node:http";
|
|
8
|
+
|
|
9
|
+
/** Bucket count that triggers a prune of idle buckets. */
|
|
10
|
+
const PRUNE_THRESHOLD = 10_000;
|
|
11
|
+
/** Minimum time between prunes, so a flood of new IPs cannot force a scan per request. */
|
|
12
|
+
const PRUNE_INTERVAL_MS = 60_000;
|
|
13
|
+
/** Hard cap on tracked buckets. New keys past it evict the oldest-inserted bucket in O(1). */
|
|
14
|
+
const MAX_BUCKETS = 50_000;
|
|
15
|
+
|
|
16
|
+
/** Loopback, RFC1918, link-local and IPv6 unique-local addresses (IPv4-mapped forms included). */
|
|
17
|
+
function isPrivateAddress(address: string): boolean {
|
|
18
|
+
const ip = address.startsWith("::ffff:") ? address.slice(7) : address;
|
|
19
|
+
if (ip === "::1" || ip.startsWith("127.") || ip.startsWith("10.")) return true;
|
|
20
|
+
if (ip.startsWith("192.168.") || ip.startsWith("169.254.")) return true;
|
|
21
|
+
const match = /^172\.(\d+)\./.exec(ip);
|
|
22
|
+
if (match && Number(match[1]) >= 16 && Number(match[1]) <= 31) return true;
|
|
23
|
+
const lower = ip.toLowerCase();
|
|
24
|
+
return lower.startsWith("fc") || lower.startsWith("fd") || lower.startsWith("fe80:");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Client IP for rate limiting. The rightmost `X-Forwarded-For` hop (the
|
|
29
|
+
* address the nearest proxy saw) is trusted only when the socket peer is a
|
|
30
|
+
* private address, i.e. a reverse proxy on the same host or network. A client
|
|
31
|
+
* that reaches the API directly is keyed on its socket address, so a spoofed
|
|
32
|
+
* header cannot buy it a fresh bucket.
|
|
33
|
+
*/
|
|
34
|
+
export function clientIp(req: IncomingMessage): string {
|
|
35
|
+
const peer = req.socket.remoteAddress ?? "unknown";
|
|
36
|
+
if (!isPrivateAddress(peer)) return peer;
|
|
37
|
+
const raw = req.headers["x-forwarded-for"];
|
|
38
|
+
const header = Array.isArray(raw) ? raw[raw.length - 1] : raw;
|
|
39
|
+
const hop = header?.split(",").pop()?.trim();
|
|
40
|
+
return hop || peer;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function createIpRateLimiter(options: { capacity: number; refillPerMs: number }) {
|
|
44
|
+
const buckets = new Map<string, { tokens: number; updatedAt: number }>();
|
|
45
|
+
let lastPruneAt = 0;
|
|
46
|
+
|
|
47
|
+
function refill(bucket: { tokens: number; updatedAt: number }, now: number): void {
|
|
48
|
+
const elapsed = now - bucket.updatedAt;
|
|
49
|
+
bucket.tokens = Math.min(options.capacity, bucket.tokens + elapsed * options.refillPerMs);
|
|
50
|
+
bucket.updatedAt = now;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
/** Take one token for `ip`. Returns false when the bucket is empty. */
|
|
55
|
+
take(ip: string, now = Date.now()): boolean {
|
|
56
|
+
if (buckets.size > PRUNE_THRESHOLD && now - lastPruneAt >= PRUNE_INTERVAL_MS) {
|
|
57
|
+
lastPruneAt = now;
|
|
58
|
+
for (const [key, bucket] of buckets) {
|
|
59
|
+
refill(bucket, now);
|
|
60
|
+
if (bucket.tokens >= options.capacity) buckets.delete(key);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
let bucket = buckets.get(ip);
|
|
64
|
+
if (!bucket) {
|
|
65
|
+
if (buckets.size >= MAX_BUCKETS) {
|
|
66
|
+
const oldest = buckets.keys().next().value;
|
|
67
|
+
if (oldest !== undefined) buckets.delete(oldest);
|
|
68
|
+
}
|
|
69
|
+
bucket = { tokens: options.capacity, updatedAt: now };
|
|
70
|
+
buckets.set(ip, bucket);
|
|
71
|
+
}
|
|
72
|
+
refill(bucket, now);
|
|
73
|
+
if (bucket.tokens < 1) return false;
|
|
74
|
+
bucket.tokens -= 1;
|
|
75
|
+
return true;
|
|
76
|
+
},
|
|
77
|
+
reset(): void {
|
|
78
|
+
buckets.clear();
|
|
79
|
+
lastPruneAt = 0;
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
package/src/http/kv.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from "../be/db";
|
|
14
14
|
import { mcpOverflowAuthError } from "../kv-overflow";
|
|
15
15
|
import { reservedNamespaceError, reservedRoomKeyError } from "../kv-reserved-namespaces";
|
|
16
|
+
import { hasKvViewArgs, resolveKvView } from "../kv-view";
|
|
16
17
|
import { can } from "../rbac";
|
|
17
18
|
import { agentContextKey, pageContextKey } from "../tasks/context-key";
|
|
18
19
|
import { KvEntrySchema, KvKeySchema, KvNamespaceSchema, KvValueTypeSchema } from "../types";
|
|
@@ -69,12 +70,33 @@ const kvListQuerySchema = z.object({
|
|
|
69
70
|
offset: z.coerce.number().int().nonnegative().optional(),
|
|
70
71
|
});
|
|
71
72
|
|
|
73
|
+
// Targeted read of one entry: dot path into a JSON value + offset/limit page.
|
|
74
|
+
// Unbounded by design: these routes back ctx.swarm.kv_get inside the script
|
|
75
|
+
// sandbox; the model-facing kv-get MCP tool applies its own size cap.
|
|
76
|
+
const kvGetQuerySchema = z.object({
|
|
77
|
+
path: z.string().max(1024).optional(),
|
|
78
|
+
offset: z.coerce.number().int().nonnegative().optional(),
|
|
79
|
+
limit: z.coerce.number().int().positive().optional(),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const kvViewSchema = z.object({
|
|
83
|
+
path: z.string(),
|
|
84
|
+
type: z.enum(["object", "array", "string", "number", "boolean", "null"]),
|
|
85
|
+
total: z.number().int().nonnegative().optional(),
|
|
86
|
+
offset: z.number().int().nonnegative().optional(),
|
|
87
|
+
returned: z.number().int().nonnegative().optional(),
|
|
88
|
+
nextOffset: z.number().int().nonnegative().nullable().optional(),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// With path/offset/limit, `value` is the resolved slice and `view` describes it.
|
|
92
|
+
const kvEntryViewSchema = KvEntrySchema.extend({ view: kvViewSchema.optional() });
|
|
93
|
+
|
|
72
94
|
// ─── Routes ──────────────────────────────────────────────────────────────────
|
|
73
95
|
|
|
74
96
|
const RESPONSES_GET = {
|
|
75
|
-
200: { description: "KV entry", schema:
|
|
97
|
+
200: { description: "KV entry (or a path/offset/limit view of it)", schema: kvEntryViewSchema },
|
|
76
98
|
404: { description: "KV entry not found or expired" },
|
|
77
|
-
400: { description: "Validation error or
|
|
99
|
+
400: { description: "Validation error, unresolvable namespace, or invalid view path" },
|
|
78
100
|
} as const;
|
|
79
101
|
|
|
80
102
|
const RESPONSES_PUT = {
|
|
@@ -106,6 +128,7 @@ const getKvHeader = route({
|
|
|
106
128
|
summary: "Get a KV entry by key (namespace resolved from request headers)",
|
|
107
129
|
tags: ["KV"],
|
|
108
130
|
params: z.object({ key: KvKeySchema }),
|
|
131
|
+
query: kvGetQuerySchema,
|
|
109
132
|
responses: RESPONSES_GET,
|
|
110
133
|
});
|
|
111
134
|
|
|
@@ -167,6 +190,7 @@ const getKvExplicit = route({
|
|
|
167
190
|
summary: "Get a KV entry by explicit namespace + key",
|
|
168
191
|
tags: ["KV"],
|
|
169
192
|
params: z.object({ namespace: KvNamespaceSchema, key: KvKeySchema }),
|
|
193
|
+
query: kvGetQuerySchema,
|
|
170
194
|
responses: RESPONSES_GET,
|
|
171
195
|
});
|
|
172
196
|
|
|
@@ -471,7 +495,7 @@ export async function handleKv(
|
|
|
471
495
|
if (!ns) return true;
|
|
472
496
|
const key = decodeKvSegment(res, parsed.params.key, "key");
|
|
473
497
|
if (!key) return true;
|
|
474
|
-
return sendGet(req, res, ns, key, getKvExplicit.respond);
|
|
498
|
+
return sendGet(req, res, ns, key, parsed.query, getKvExplicit.respond);
|
|
475
499
|
}
|
|
476
500
|
if (putKvExplicit.match(req.method, pathSegments)) {
|
|
477
501
|
if (enforceContentLengthCap(req, res, MAX_KV_BODY_BYTES) === BODY_TOO_LARGE) return true;
|
|
@@ -511,7 +535,7 @@ export async function handleKv(
|
|
|
511
535
|
}
|
|
512
536
|
const key = decodeKvSegment(res, parsed.params.key, "key");
|
|
513
537
|
if (!key) return true;
|
|
514
|
-
return sendGet(req, res, ns, key, getKvHeader.respond);
|
|
538
|
+
return sendGet(req, res, ns, key, parsed.query, getKvHeader.respond);
|
|
515
539
|
}
|
|
516
540
|
if (putKvHeader.match(req.method, pathSegments)) {
|
|
517
541
|
if (enforceContentLengthCap(req, res, MAX_KV_BODY_BYTES) === BODY_TOO_LARGE) return true;
|
|
@@ -638,6 +662,7 @@ async function sendGet(
|
|
|
638
662
|
res: ServerResponse,
|
|
639
663
|
namespace: string,
|
|
640
664
|
key: string,
|
|
665
|
+
query: z.infer<typeof kvGetQuerySchema>,
|
|
641
666
|
respond: GetKvRespond,
|
|
642
667
|
): Promise<boolean> {
|
|
643
668
|
const authErr = authorizeRead(namespace, await buildAuthCtx(req));
|
|
@@ -650,6 +675,15 @@ async function sendGet(
|
|
|
650
675
|
jsonError(res, "not found", 404);
|
|
651
676
|
return true;
|
|
652
677
|
}
|
|
678
|
+
if (hasKvViewArgs(query)) {
|
|
679
|
+
const view = resolveKvView(entry.value, query);
|
|
680
|
+
if (!view.ok) {
|
|
681
|
+
jsonError(res, view.error, 400);
|
|
682
|
+
return true;
|
|
683
|
+
}
|
|
684
|
+
respond(res, 200, { ...entry, value: view.value, view: view.view });
|
|
685
|
+
return true;
|
|
686
|
+
}
|
|
653
687
|
respond(res, 200, entry);
|
|
654
688
|
return true;
|
|
655
689
|
}
|
package/src/http/mcp-servers.ts
CHANGED
|
@@ -253,6 +253,7 @@ async function mcpServerPrincipal(req: IncomingMessage): Promise<RbacPrincipal>
|
|
|
253
253
|
const auth = getRequestAuth(req);
|
|
254
254
|
if (auth?.kind === "operator") return { kind: "operator" };
|
|
255
255
|
if (auth?.kind === "user") return { kind: "user", userId: auth.userId };
|
|
256
|
+
if (auth?.kind === "guest") return { kind: "guest" };
|
|
256
257
|
return { kind: "agent", agentId: "", isLead: false };
|
|
257
258
|
}
|
|
258
259
|
|
package/src/http/memory.ts
CHANGED
|
@@ -43,6 +43,7 @@ import { can, type RbacPrincipal } from "../rbac";
|
|
|
43
43
|
import { AgentMemorySchema, AgentMemoryScopeSchema, AgentMemorySourceSchema } from "../types";
|
|
44
44
|
import { getRequestAuth } from "../utils/request-auth-context";
|
|
45
45
|
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
46
|
+
import { rejectGuest } from "./request-principal";
|
|
46
47
|
import { route } from "./route-def";
|
|
47
48
|
import { jsonError, parseQueryParams } from "./utils";
|
|
48
49
|
|
|
@@ -108,6 +109,8 @@ const MemorySearchResultItemSchema = z.object({
|
|
|
108
109
|
scope: AgentMemoryScopeSchema,
|
|
109
110
|
tags: z.array(z.string()),
|
|
110
111
|
accessCount: z.number(),
|
|
112
|
+
summary: z.string().nullable().optional(),
|
|
113
|
+
createdAt: z.string().optional(),
|
|
111
114
|
});
|
|
112
115
|
|
|
113
116
|
const searchMemory = route({
|
|
@@ -787,6 +790,7 @@ async function ingestPrincipal(
|
|
|
787
790
|
): Promise<RbacPrincipal> {
|
|
788
791
|
const auth = getRequestAuth(req);
|
|
789
792
|
if (auth?.kind === "user") return { kind: "user", userId: auth.userId };
|
|
793
|
+
if (auth?.kind === "guest") return { kind: "guest" };
|
|
790
794
|
const agentId = auth?.kind === "agent" ? auth.agentId : agentIdHeader;
|
|
791
795
|
if (agentId) {
|
|
792
796
|
const agent = await getAgentById(agentId);
|
|
@@ -810,6 +814,9 @@ export async function handleMemory(
|
|
|
810
814
|
): Promise<boolean> {
|
|
811
815
|
// Page memory operations use the owner's scope. Authentication and audit retain the signed viewer.
|
|
812
816
|
const myAgentId = getRequestAuth(req)?.page?.executionAgentId ?? callerAgentId;
|
|
817
|
+
if (pathSegments[0] === "api" && pathSegments[1] === "memory" && rejectGuest(req, res)) {
|
|
818
|
+
return true;
|
|
819
|
+
}
|
|
813
820
|
if (indexMemory.match(req.method, pathSegments)) {
|
|
814
821
|
const parsed = await indexMemory.parse(req, res, pathSegments, new URLSearchParams());
|
|
815
822
|
if (!parsed) return true;
|
|
@@ -996,6 +1003,8 @@ export async function handleMemory(
|
|
|
996
1003
|
scope: r.scope,
|
|
997
1004
|
tags: r.tags,
|
|
998
1005
|
accessCount: (r.accessCount ?? 0) + (consumedIdSet.has(r.id) ? 1 : 0),
|
|
1006
|
+
summary: r.summary,
|
|
1007
|
+
createdAt: r.createdAt,
|
|
999
1008
|
})),
|
|
1000
1009
|
});
|
|
1001
1010
|
} catch (err) {
|
|
@@ -275,6 +275,8 @@ async function ensureCatalogWriter(
|
|
|
275
275
|
let principal: RbacPrincipal;
|
|
276
276
|
if (auth?.kind === "user") {
|
|
277
277
|
principal = { kind: "user", userId: auth.userId };
|
|
278
|
+
} else if (auth?.kind === "guest") {
|
|
279
|
+
principal = { kind: "guest" };
|
|
278
280
|
} else if (agentId) {
|
|
279
281
|
// An X-Agent-ID wins over the shared API key: a worker holds that key, so treating it as the
|
|
280
282
|
// operator would let any worker skip the verb's lead-only rule.
|
|
@@ -48,6 +48,10 @@ export function getOperatorActor(req: IncomingMessage, res: ServerResponse): Ide
|
|
|
48
48
|
if (auth?.kind === "operator") {
|
|
49
49
|
return { kind: "operator", id: auth.fingerprint };
|
|
50
50
|
}
|
|
51
|
+
if (auth?.kind === "guest") {
|
|
52
|
+
jsonError(res, "Forbidden", 403);
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
51
55
|
|
|
52
56
|
const rawKey = extractBearer(req);
|
|
53
57
|
const swarmKey = getApiKey();
|
package/src/http/page-proxy.ts
CHANGED
|
@@ -149,6 +149,22 @@ function isSafeProxySuffix(suffix: string): boolean {
|
|
|
149
149
|
return true;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
/**
|
|
153
|
+
* What a guest page session (a password-page cookie with no signed-in user) may
|
|
154
|
+
* reach. Everything else is refused before it is forwarded:
|
|
155
|
+
* - `GET pages/<its own id>`: the page reads its own record.
|
|
156
|
+
* - `kv`, `kv/*`: the page's key-value store. The KV handler pins every such
|
|
157
|
+
* request to `task:page:<own id>` through the `X-Page-Id` header set below.
|
|
158
|
+
* Exported for tests.
|
|
159
|
+
*/
|
|
160
|
+
export function isGuestAllowedProxyPath(method: string, suffix: string, pageId: string): boolean {
|
|
161
|
+
const segments = suffix.split("/").map((seg) => decodeURIComponent(seg));
|
|
162
|
+
if (segments[0] === "kv") return true;
|
|
163
|
+
return (
|
|
164
|
+
method === "GET" && segments.length === 2 && segments[0] === "pages" && segments[1] === pageId
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
152
168
|
/**
|
|
153
169
|
* Handle `/@swarm/api/*` requests. Returns `true` if the request was handled
|
|
154
170
|
* (response sent), `false` if not — caller continues to the next handler.
|
|
@@ -194,6 +210,12 @@ export async function handlePageProxy(req: IncomingMessage, res: ServerResponse)
|
|
|
194
210
|
return true;
|
|
195
211
|
}
|
|
196
212
|
|
|
213
|
+
// A password-page session carries no user, so only the allowlist above is reachable.
|
|
214
|
+
if (!payload.uid && !payload.op && !isGuestAllowedProxyPath(method, suffix, page.id)) {
|
|
215
|
+
jsonError(res, "forbidden", 403);
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
|
|
197
219
|
// ─── Rewrite + forward ─────────────────────────────────────────────────────
|
|
198
220
|
// `/@swarm/api/me` → `/api/me`. Preserve query string.
|
|
199
221
|
//
|
package/src/http/pages.ts
CHANGED
|
@@ -660,7 +660,9 @@ export async function handlePages(
|
|
|
660
660
|
}
|
|
661
661
|
const favoriteScope = (await resolveHttpFavoriteOwner(req, myAgentId))?.scope;
|
|
662
662
|
const [decorated] = await withFavoriteFlags([page], { favoriteScope, itemType: "page" });
|
|
663
|
-
|
|
663
|
+
const body = withShareUrls(decorated ?? page);
|
|
664
|
+
if (getRequestAuth(req)?.kind === "guest") delete body.passwordHash;
|
|
665
|
+
getPageRoute.respond(res, 200, body);
|
|
664
666
|
return true;
|
|
665
667
|
}
|
|
666
668
|
|
|
@@ -808,7 +810,7 @@ export async function handlePages(
|
|
|
808
810
|
auth?.kind === "user"
|
|
809
811
|
? { uid: auth.userId, name: auth.user.name }
|
|
810
812
|
: auth?.kind === "operator"
|
|
811
|
-
? { name: `guest-${randomUUID().slice(0, 8)}
|
|
813
|
+
? { name: `guest-${randomUUID().slice(0, 8)}`, op: true as const }
|
|
812
814
|
: undefined;
|
|
813
815
|
const cookie = await issuePageSessionCookie(page.id, {
|
|
814
816
|
dev: isDevRequest(req),
|
package/src/http/realtime.ts
CHANGED
|
@@ -53,7 +53,7 @@ export async function handleRealtimeTicket(
|
|
|
53
53
|
const parsed = await realtimeTicketRoute.parse(req, res, pathSegments, queryParams);
|
|
54
54
|
if (!parsed) return true;
|
|
55
55
|
const auth = getRequestAuth(req);
|
|
56
|
-
if (!auth || auth.kind === "agent") {
|
|
56
|
+
if (!auth || auth.kind === "agent" || auth.kind === "guest") {
|
|
57
57
|
jsonError(res, "Comb presence requires dashboard authentication", 403);
|
|
58
58
|
return true;
|
|
59
59
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { IncomingMessage } from "node:http";
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
2
|
import { getAgentById } from "../be/db";
|
|
3
3
|
import type { RbacPrincipal } from "../rbac";
|
|
4
4
|
import { getRequestAuth } from "../utils/request-auth-context";
|
|
5
|
+
import { jsonError } from "./utils";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* The RBAC principal of an HTTP request, for a handler-side `can()` check.
|
|
@@ -16,6 +17,7 @@ export async function requestPrincipal(
|
|
|
16
17
|
const auth = getRequestAuth(req);
|
|
17
18
|
if (auth?.kind === "operator") return { kind: "operator" };
|
|
18
19
|
if (auth?.kind === "user") return { kind: "user", userId: auth.userId };
|
|
20
|
+
if (auth?.kind === "guest") return { kind: "guest" };
|
|
19
21
|
if (!myAgentId) return null;
|
|
20
22
|
const agent = await getAgentById(myAgentId);
|
|
21
23
|
return { kind: "agent", agentId: myAgentId, isLead: agent?.isLead ?? false };
|
|
@@ -34,8 +36,19 @@ export async function agentFirstPrincipal(
|
|
|
34
36
|
): Promise<RbacPrincipal> {
|
|
35
37
|
const auth = getRequestAuth(req);
|
|
36
38
|
if (auth?.kind === "user") return { kind: "user", userId: auth.userId };
|
|
39
|
+
if (auth?.kind === "guest") return { kind: "guest" };
|
|
37
40
|
const agentId = auth?.kind === "agent" ? auth.agentId : myAgentId;
|
|
38
41
|
if (!agentId) return { kind: "operator" };
|
|
39
42
|
const agent = await getAgentById(agentId);
|
|
40
43
|
return { kind: "agent", agentId, isLead: agent?.isLead === true };
|
|
41
44
|
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Refuse a guest page session on a handler that has no per-route guest rule.
|
|
48
|
+
* Writes a 403 and returns true when the request is a guest's.
|
|
49
|
+
*/
|
|
50
|
+
export function rejectGuest(req: IncomingMessage, res: ServerResponse): boolean {
|
|
51
|
+
if (getRequestAuth(req)?.kind !== "guest") return false;
|
|
52
|
+
jsonError(res, "Forbidden", 403);
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
@@ -1047,6 +1047,10 @@ async function ensureConnectionAdmin(
|
|
|
1047
1047
|
): Promise<boolean> {
|
|
1048
1048
|
const auth = getRequestAuth(req);
|
|
1049
1049
|
if (auth?.kind === "operator" || auth?.kind === "user") return true;
|
|
1050
|
+
if (auth?.kind === "guest") {
|
|
1051
|
+
jsonError(res, "Forbidden", 403);
|
|
1052
|
+
return false;
|
|
1053
|
+
}
|
|
1050
1054
|
|
|
1051
1055
|
const callerAgentId = agentId ?? singleHeader(req, "x-agent-id");
|
|
1052
1056
|
const agent = callerAgentId ? await getAgentById(callerAgentId) : undefined;
|
|
@@ -1081,6 +1085,10 @@ async function ensureVerbAdmin(
|
|
|
1081
1085
|
): Promise<boolean> {
|
|
1082
1086
|
const auth = getRequestAuth(req);
|
|
1083
1087
|
if (auth?.kind === "operator" || auth?.kind === "user") return true;
|
|
1088
|
+
if (auth?.kind === "guest") {
|
|
1089
|
+
jsonError(res, "Forbidden", 403);
|
|
1090
|
+
return false;
|
|
1091
|
+
}
|
|
1084
1092
|
|
|
1085
1093
|
const callerAgentId = agentId ?? singleHeader(req, "x-agent-id");
|
|
1086
1094
|
const agent = callerAgentId ? await getAgentById(callerAgentId) : undefined;
|
package/src/http/script-runs.ts
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
upsertScriptRunJournalStep,
|
|
24
24
|
} from "../be/db";
|
|
25
25
|
import { explicitModelErrorForAgent } from "../be/model-validation";
|
|
26
|
+
import { refuseSourceWithSecrets } from "../be/scripts/source-secrets";
|
|
26
27
|
import { lintWorkflowLabels } from "../script-workflows/label-lint";
|
|
27
28
|
import { scriptRunMaxAgentTasks, scriptRunMaxSteps } from "../script-workflows/limits";
|
|
28
29
|
import {
|
|
@@ -39,14 +40,15 @@ import {
|
|
|
39
40
|
ScriptRunStatusSchema,
|
|
40
41
|
TERMINAL_SCRIPT_RUN_STATUSES,
|
|
41
42
|
} from "../types";
|
|
42
|
-
import { getAppUrl } from "../utils/constants";
|
|
43
|
+
import { getAppUrl, getMcpBaseUrl } from "../utils/constants";
|
|
44
|
+
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
43
45
|
import {
|
|
44
46
|
executeRawLlm,
|
|
45
47
|
RawLlmConfigSchema,
|
|
46
48
|
RawLlmOutputSchema,
|
|
47
49
|
} from "../workflows/executors/raw-llm";
|
|
48
50
|
import { route } from "./route-def";
|
|
49
|
-
import {
|
|
51
|
+
import { json, jsonError } from "./utils";
|
|
50
52
|
|
|
51
53
|
const DEFAULT_SCRIPT_RUN_CONCURRENCY_CAP = 10;
|
|
52
54
|
|
|
@@ -369,6 +371,12 @@ export async function handleScriptRuns(
|
|
|
369
371
|
const agent = await requireAgent(res, agentId);
|
|
370
372
|
if (!agent) return true;
|
|
371
373
|
|
|
374
|
+
const secretRefusal = refuseSourceWithSecrets(parsed.body.source);
|
|
375
|
+
if (secretRefusal) {
|
|
376
|
+
json(res, secretRefusal, 400);
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
379
|
+
|
|
372
380
|
const lint = lintWorkflowLabels(parsed.body.source);
|
|
373
381
|
if (!lint.ok) {
|
|
374
382
|
json(
|
|
@@ -418,7 +426,7 @@ export async function handleScriptRuns(
|
|
|
418
426
|
const { run, existing } = creation;
|
|
419
427
|
|
|
420
428
|
if (!existing && parsed.body.background) {
|
|
421
|
-
startScriptRunProcess(run,
|
|
429
|
+
startScriptRunProcess(run, getMcpBaseUrl(), bearerToken(req)).catch(async (err) => {
|
|
422
430
|
await updateScriptRun(run.id, {
|
|
423
431
|
status: "failed",
|
|
424
432
|
pid: null,
|
|
@@ -545,7 +553,7 @@ export async function handleScriptRuns(
|
|
|
545
553
|
config: parsed.body.config ?? {},
|
|
546
554
|
status: parsed.body.status,
|
|
547
555
|
result: parsed.body.result,
|
|
548
|
-
error: parsed.body.error,
|
|
556
|
+
error: parsed.body.error === undefined ? undefined : scrubSecrets(parsed.body.error),
|
|
549
557
|
durationMs: parsed.body.durationMs,
|
|
550
558
|
});
|
|
551
559
|
return assertRunWithinLimits(run.id);
|
package/src/http/scripts.ts
CHANGED
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
} from "../be/scripts/db";
|
|
33
33
|
import { searchScripts } from "../be/scripts/embeddings";
|
|
34
34
|
import { extractArgsJsonSchema } from "../be/scripts/extract-schema";
|
|
35
|
+
import { refuseSourceWithSecrets } from "../be/scripts/source-secrets";
|
|
35
36
|
import {
|
|
36
37
|
scriptSdkTypesWithGeneratedApis,
|
|
37
38
|
scriptStdlibTypesWithGeneratedApis,
|
|
@@ -649,6 +650,12 @@ export async function handleScripts(
|
|
|
649
650
|
}
|
|
650
651
|
}
|
|
651
652
|
|
|
653
|
+
const secretRefusal = refuseSourceWithSecrets(parsed.body.source);
|
|
654
|
+
if (secretRefusal) {
|
|
655
|
+
json(res, secretRefusal, 400);
|
|
656
|
+
return true;
|
|
657
|
+
}
|
|
658
|
+
|
|
652
659
|
const typecheck = await typecheckScript(parsed.body.source, { agentId: agent.id });
|
|
653
660
|
if (!typecheck.ok) {
|
|
654
661
|
json(
|
|
@@ -716,6 +723,14 @@ export async function handleScripts(
|
|
|
716
723
|
req.headers["x-runtime-instance-id"],
|
|
717
724
|
);
|
|
718
725
|
|
|
726
|
+
// Inline source is saved as a scratch script and recorded on the run row:
|
|
727
|
+
// refuse an embedded secret before it executes or persists.
|
|
728
|
+
const secretRefusal = parsed.body.source ? refuseSourceWithSecrets(parsed.body.source) : null;
|
|
729
|
+
if (secretRefusal) {
|
|
730
|
+
json(res, secretRefusal, 400);
|
|
731
|
+
return true;
|
|
732
|
+
}
|
|
733
|
+
|
|
719
734
|
let source = parsed.body.source;
|
|
720
735
|
let fsMode = parsed.body.fsMode;
|
|
721
736
|
let namedScript: ScriptRecord | null = null;
|
|
@@ -185,8 +185,9 @@ export async function recomputeSessionCost(
|
|
|
185
185
|
// Amp's own recorded cost (`amp threads usage`) is what it billed, including
|
|
186
186
|
// subagent threads the token counts miss. The adapter sends it only when
|
|
187
187
|
// every request was billed through Amp, so it wins over the token price,
|
|
188
|
-
// which still fills the per-model breakdown.
|
|
189
|
-
|
|
188
|
+
// which still fills the per-model breakdown. Grok's `costUsdTicks` is the
|
|
189
|
+
// same kind of report: what xAI billed, long-context tiers included.
|
|
190
|
+
if ((input.provider === "amp" || input.provider === "grok") && input.harnessCostUsd > 0) {
|
|
190
191
|
const priced = await recomputeSessionCost({ ...input, harnessCostUsd: 0 }, lookupRate);
|
|
191
192
|
return {
|
|
192
193
|
totalCostUsd: input.harnessCostUsd,
|
package/src/http/tasks.ts
CHANGED
|
@@ -75,6 +75,7 @@ import {
|
|
|
75
75
|
import { ensure } from "../utils/business-use";
|
|
76
76
|
import { getRequestAuth } from "../utils/request-auth-context";
|
|
77
77
|
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
78
|
+
import { rejectGuest } from "./request-principal";
|
|
78
79
|
import { route } from "./route-def";
|
|
79
80
|
import { jsonError, parseBody } from "./utils";
|
|
80
81
|
|
|
@@ -713,6 +714,8 @@ async function canSteerTask(
|
|
|
713
714
|
} else if (auth?.kind === "user") {
|
|
714
715
|
principal = { kind: "user", userId: auth.userId };
|
|
715
716
|
verb = "task.steer.own";
|
|
717
|
+
} else if (auth?.kind === "guest") {
|
|
718
|
+
return false;
|
|
716
719
|
} else {
|
|
717
720
|
if (!myAgentId) return false;
|
|
718
721
|
const agent = await getAgentById(myAgentId);
|
|
@@ -750,6 +753,8 @@ async function canActOnOwnTask(
|
|
|
750
753
|
principal = { kind: "operator" };
|
|
751
754
|
} else if (auth?.kind === "user") {
|
|
752
755
|
principal = { kind: "user", userId: auth.userId };
|
|
756
|
+
} else if (auth?.kind === "guest") {
|
|
757
|
+
return false;
|
|
753
758
|
} else {
|
|
754
759
|
if (!myAgentId) return false;
|
|
755
760
|
const agent = await getAgentById(myAgentId);
|
|
@@ -772,6 +777,7 @@ async function resolveTaskWritePrincipal(
|
|
|
772
777
|
): Promise<RbacPrincipal> {
|
|
773
778
|
const auth = getRequestAuth(req);
|
|
774
779
|
if (auth?.kind === "user") return { kind: "user", userId: auth.userId };
|
|
780
|
+
if (auth?.kind === "guest") return { kind: "guest" };
|
|
775
781
|
const agentId = auth?.kind === "agent" ? auth.agentId : myAgentId;
|
|
776
782
|
if (!agentId) return { kind: "operator" };
|
|
777
783
|
const agent = await getAgentById(agentId);
|
|
@@ -787,6 +793,9 @@ export async function handleTasks(
|
|
|
787
793
|
queryParams: URLSearchParams,
|
|
788
794
|
myAgentId: string | undefined,
|
|
789
795
|
): Promise<boolean> {
|
|
796
|
+
if (pathSegments[0] === "api" && pathSegments[1] === "tasks" && rejectGuest(req, res)) {
|
|
797
|
+
return true;
|
|
798
|
+
}
|
|
790
799
|
if (listTasks.match(req.method, pathSegments)) {
|
|
791
800
|
const parsed = await listTasks.parse(req, res, pathSegments, queryParams);
|
|
792
801
|
if (!parsed) return true;
|