@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
|
@@ -2,6 +2,7 @@ import { getDb, getDbClient, isSqliteVecAvailable } from "@/be/db";
|
|
|
2
2
|
import { cosineSimilarity, deserializeEmbedding, serializeEmbedding } from "@/be/embedding";
|
|
3
3
|
import { contentSha256 } from "@/commands/profile-sync";
|
|
4
4
|
import type { AgentMemory, AgentMemoryScope, AgentMemorySource } from "@/types";
|
|
5
|
+
import { scrubSecrets } from "@/utils/secret-scrubber";
|
|
5
6
|
import {
|
|
6
7
|
EMBEDDING_DIMENSIONS,
|
|
7
8
|
isHybridSearchEnabled,
|
|
@@ -19,6 +20,7 @@ import type {
|
|
|
19
20
|
MemoryInput,
|
|
20
21
|
MemoryListOptions,
|
|
21
22
|
MemoryRetrievalSource,
|
|
23
|
+
MemoryScrubFields,
|
|
22
24
|
MemorySearchOptions,
|
|
23
25
|
MemoryStats,
|
|
24
26
|
MemoryStore,
|
|
@@ -437,7 +439,12 @@ export class SqliteMemoryStore implements MemoryStore {
|
|
|
437
439
|
const now = new Date().toISOString();
|
|
438
440
|
const key = input.key ?? `${input.scope}/${input.source}/${id}`;
|
|
439
441
|
const expiresAt = computeExpiresAt(input.source, key);
|
|
440
|
-
|
|
442
|
+
// Scrub before the hash, the FTS sync and the caller's embedding (callers
|
|
443
|
+
// embed the returned row's content), so all of them see the same text.
|
|
444
|
+
const name = scrubSecrets(input.name);
|
|
445
|
+
const content = scrubSecrets(input.content);
|
|
446
|
+
const summary = input.summary == null ? null : scrubSecrets(input.summary);
|
|
447
|
+
const contentHash = contentSha256(content);
|
|
441
448
|
const version = 1;
|
|
442
449
|
|
|
443
450
|
const row = await getDbClient().transaction(async (tx) => {
|
|
@@ -449,15 +456,15 @@ export class SqliteMemoryStore implements MemoryStore {
|
|
|
449
456
|
input.agentId ?? null,
|
|
450
457
|
input.scope,
|
|
451
458
|
key,
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
459
|
+
name,
|
|
460
|
+
content,
|
|
461
|
+
summary,
|
|
455
462
|
input.source,
|
|
456
463
|
input.sourceTaskId ?? null,
|
|
457
464
|
input.sourcePath ?? null,
|
|
458
465
|
input.chunkIndex ?? 0,
|
|
459
466
|
input.totalChunks ?? 1,
|
|
460
|
-
JSON.stringify(input.tags ?? []),
|
|
467
|
+
JSON.stringify((input.tags ?? []).map((tag) => scrubSecrets(tag))),
|
|
461
468
|
now,
|
|
462
469
|
now,
|
|
463
470
|
now,
|
|
@@ -478,7 +485,7 @@ export class SqliteMemoryStore implements MemoryStore {
|
|
|
478
485
|
crypto.randomUUID(),
|
|
479
486
|
inserted.id,
|
|
480
487
|
version,
|
|
481
|
-
|
|
488
|
+
content,
|
|
482
489
|
contentHash,
|
|
483
490
|
input.intent ?? "create memory",
|
|
484
491
|
input.agentId ?? null,
|
|
@@ -1009,13 +1016,17 @@ export class SqliteMemoryStore implements MemoryStore {
|
|
|
1009
1016
|
|
|
1010
1017
|
const previousVersion = row.version ?? 1;
|
|
1011
1018
|
const moving = input.newKey !== undefined && input.newKey !== row.key;
|
|
1019
|
+
// Scrub the edited text before the hash and FTS sync; callers embed the
|
|
1020
|
+
// returned content, so the vector matches the stored row.
|
|
1012
1021
|
const nextContent = moveOnly
|
|
1013
1022
|
? row.content
|
|
1014
|
-
:
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1023
|
+
: scrubSecrets(
|
|
1024
|
+
applyEditMode(input.mode, row.content, {
|
|
1025
|
+
content: input.content,
|
|
1026
|
+
oldString: input.oldString,
|
|
1027
|
+
newString: input.newString,
|
|
1028
|
+
}),
|
|
1029
|
+
);
|
|
1019
1030
|
|
|
1020
1031
|
const nextHash = contentSha256(nextContent);
|
|
1021
1032
|
const contentChanged = !moveOnly && nextHash !== row.contentHash;
|
|
@@ -1268,6 +1279,47 @@ export class SqliteMemoryStore implements MemoryStore {
|
|
|
1268
1279
|
}
|
|
1269
1280
|
}
|
|
1270
1281
|
|
|
1282
|
+
async rewriteForScrub(
|
|
1283
|
+
id: string,
|
|
1284
|
+
before: MemoryScrubFields,
|
|
1285
|
+
after: MemoryScrubFields,
|
|
1286
|
+
): Promise<boolean> {
|
|
1287
|
+
const contentChanged = after.content !== before.content;
|
|
1288
|
+
return getDbClient().transaction(async (tx) => {
|
|
1289
|
+
// Compare-and-set: the sweep scrubbed `before` outside the write lock, so
|
|
1290
|
+
// skip the row if a concurrent write moved it since.
|
|
1291
|
+
const result = await tx.run(
|
|
1292
|
+
`UPDATE agent_memory
|
|
1293
|
+
SET name = ?, content = ?, summary = ?, contentHash = ?,
|
|
1294
|
+
embedding = CASE WHEN ? = 1 THEN NULL ELSE embedding END,
|
|
1295
|
+
embeddingModel = CASE WHEN ? = 1 THEN NULL ELSE embeddingModel END
|
|
1296
|
+
WHERE id = ? AND name IS ? AND content IS ? AND summary IS ?`,
|
|
1297
|
+
[
|
|
1298
|
+
after.name,
|
|
1299
|
+
after.content,
|
|
1300
|
+
after.summary,
|
|
1301
|
+
contentSha256(after.content),
|
|
1302
|
+
contentChanged ? 1 : 0,
|
|
1303
|
+
contentChanged ? 1 : 0,
|
|
1304
|
+
id,
|
|
1305
|
+
before.name,
|
|
1306
|
+
before.content,
|
|
1307
|
+
before.summary,
|
|
1308
|
+
],
|
|
1309
|
+
);
|
|
1310
|
+
if (result.changes === 0) return false;
|
|
1311
|
+
// The vector embeds the old (secret-bearing) content. Drop it; the
|
|
1312
|
+
// re-embed backfill picks the row up via `embedding IS NULL`.
|
|
1313
|
+
if (contentChanged && this.vecInitialized && this.getVecTableSchema()) {
|
|
1314
|
+
await tx.run("DELETE FROM memory_vec WHERE memory_id = ?", [id]);
|
|
1315
|
+
}
|
|
1316
|
+
if (after.name !== before.name || contentChanged) {
|
|
1317
|
+
await this.syncFtsRow(id, after.name, after.content);
|
|
1318
|
+
}
|
|
1319
|
+
return true;
|
|
1320
|
+
});
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1271
1323
|
async getStats(agentId: string): Promise<MemoryStats> {
|
|
1272
1324
|
const db = getDbClient();
|
|
1273
1325
|
|
package/src/be/memory/types.ts
CHANGED
|
@@ -40,6 +40,17 @@ export interface MemoryStore {
|
|
|
40
40
|
deleteBySourcePath(sourcePath: string, agentId: string): Promise<number>;
|
|
41
41
|
purgeExpired(): Promise<number>;
|
|
42
42
|
updateEmbedding(id: string, embedding: Float32Array, model: string): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Redact a row in place (boot retro-sweep). Writes only if the row still
|
|
45
|
+
* holds `before`; keeps FTS in sync and drops the embedding when the
|
|
46
|
+
* content changed so the re-embed backfill regenerates it. Returns whether
|
|
47
|
+
* the row was written.
|
|
48
|
+
*/
|
|
49
|
+
rewriteForScrub(
|
|
50
|
+
id: string,
|
|
51
|
+
before: MemoryScrubFields,
|
|
52
|
+
after: MemoryScrubFields,
|
|
53
|
+
): Promise<boolean>;
|
|
43
54
|
getStats(agentId: string): Promise<MemoryStats>;
|
|
44
55
|
getHealth(): MemoryHealth;
|
|
45
56
|
}
|
|
@@ -48,6 +59,9 @@ export interface MemoryStore {
|
|
|
48
59
|
// Supporting types
|
|
49
60
|
// ============================================================================
|
|
50
61
|
|
|
62
|
+
/** The free-text `agent_memory` columns the boot retro-sweep redacts. */
|
|
63
|
+
export type MemoryScrubFields = { name: string; content: string; summary: string | null };
|
|
64
|
+
|
|
51
65
|
export interface MemoryInput {
|
|
52
66
|
agentId: string | null;
|
|
53
67
|
scope: AgentMemoryScope;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
-- Single-use connect codes for the agent-swarm.dev ChatGPT connector.
|
|
2
|
+
--
|
|
3
|
+
-- `POST /api/users/:id/connector-codes` stores a code here. The connector
|
|
4
|
+
-- trades it once at `POST /api/connector/exchange` for a freshly minted
|
|
5
|
+
-- `aswt_` token (src/be/connector-codes.ts). The plaintext code is never
|
|
6
|
+
-- stored. The heartbeat cleanup sweep deletes rows 1h after they expire.
|
|
7
|
+
|
|
8
|
+
CREATE TABLE IF NOT EXISTS connector_codes (
|
|
9
|
+
-- sha256 hex of the plaintext code.
|
|
10
|
+
code_hash TEXT PRIMARY KEY,
|
|
11
|
+
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
12
|
+
label TEXT NOT NULL,
|
|
13
|
+
-- IdentityActor that created the code. The token minted at exchange time
|
|
14
|
+
-- is attributed to this actor in user_identity_events.
|
|
15
|
+
actor_kind TEXT NOT NULL,
|
|
16
|
+
actor_id TEXT NOT NULL,
|
|
17
|
+
created_at TEXT NOT NULL,
|
|
18
|
+
expires_at TEXT NOT NULL,
|
|
19
|
+
used_at TEXT
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
CREATE INDEX IF NOT EXISTS idx_connector_codes_expires_at
|
|
23
|
+
ON connector_codes(expires_at);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
-- Page guest sessions are an RBAC principal, so their decisions are audited.
|
|
2
|
+
-- SQLite cannot change a CHECK in place, so rebuild the table with the 108
|
|
3
|
+
-- shape plus the new principal type. Every row is copied unchanged.
|
|
4
|
+
|
|
5
|
+
CREATE TABLE permission_audit_new (
|
|
6
|
+
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
|
7
|
+
ts TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
8
|
+
principalType TEXT NOT NULL CHECK (principalType IN ('agent','user','operator','guest')),
|
|
9
|
+
principalId TEXT,
|
|
10
|
+
originatorUserId TEXT,
|
|
11
|
+
verb TEXT NOT NULL,
|
|
12
|
+
resourceType TEXT,
|
|
13
|
+
resourceId TEXT,
|
|
14
|
+
decision TEXT NOT NULL CHECK (decision IN ('allow','deny')),
|
|
15
|
+
reason TEXT,
|
|
16
|
+
source TEXT NOT NULL CHECK (source IN ('mcp','http'))
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
INSERT INTO permission_audit_new (
|
|
20
|
+
id, ts, principalType, principalId, originatorUserId,
|
|
21
|
+
verb, resourceType, resourceId, decision, reason, source
|
|
22
|
+
)
|
|
23
|
+
SELECT
|
|
24
|
+
id, ts, principalType, principalId, originatorUserId,
|
|
25
|
+
verb, resourceType, resourceId, decision, reason, source
|
|
26
|
+
FROM permission_audit;
|
|
27
|
+
|
|
28
|
+
DROP TABLE permission_audit;
|
|
29
|
+
ALTER TABLE permission_audit_new RENAME TO permission_audit;
|
|
30
|
+
|
|
31
|
+
CREATE INDEX idx_permission_audit_ts ON permission_audit(ts);
|
|
32
|
+
CREATE INDEX idx_permission_audit_decision_ts ON permission_audit(decision, ts);
|
|
33
|
+
CREATE INDEX idx_permission_audit_principal_ts ON permission_audit(principalId, ts);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
-- Backfill a TTL on legacy auto-generated memories.
|
|
2
|
+
--
|
|
3
|
+
-- task_completion and session_summary rows written before TTL_DEFAULTS existed
|
|
4
|
+
-- (or by paths that skipped computeExpiresAt) carry expiresAt = NULL, so
|
|
5
|
+
-- purgeExpired() never removes them and they keep competing for pre-task
|
|
6
|
+
-- recall slots indefinitely. Give each one a 7-day grace window from the
|
|
7
|
+
-- moment this migration runs; the regular purge then removes them.
|
|
8
|
+
--
|
|
9
|
+
-- Excluded:
|
|
10
|
+
-- * swarm-scope rows: promoted to shared knowledge, not per-agent residue.
|
|
11
|
+
-- * /longterm keys: tierSource() treats them as manual (no TTL).
|
|
12
|
+
--
|
|
13
|
+
-- Data-only. ISO-8601 with milliseconds, matching Date#toISOString() used by
|
|
14
|
+
-- computeExpiresAt() for new rows.
|
|
15
|
+
UPDATE agent_memory
|
|
16
|
+
SET expiresAt = strftime('%Y-%m-%dT%H:%M:%fZ', 'now', '+7 days')
|
|
17
|
+
WHERE source IN ('task_completion', 'session_summary')
|
|
18
|
+
AND expiresAt IS NULL
|
|
19
|
+
AND scope <> 'swarm'
|
|
20
|
+
AND (key IS NULL OR (key <> '/longterm' AND key NOT LIKE '/longterm/%'));
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
-- Byte-exact replay state for workflow runs, sealed at rest.
|
|
2
|
+
--
|
|
3
|
+
-- `workflow_run_steps.output` and `workflow_runs.context` are scrubbed at write
|
|
4
|
+
-- for display and SQL filters (`json_extract(context, '$.swarm.requestedByUserId')`).
|
|
5
|
+
-- Resume, retry, dedup and recovery rebuild the live ctx from these values, so
|
|
6
|
+
-- they need the exact value: these columns hold it, encrypted with `sealJson`
|
|
7
|
+
-- (src/be/sealed-json.ts). NULL for rows written before this migration; their
|
|
8
|
+
-- readers fall back to the plain column.
|
|
9
|
+
ALTER TABLE workflow_run_steps ADD COLUMN output_replay TEXT;
|
|
10
|
+
ALTER TABLE workflow_runs ADD COLUMN context_replay TEXT;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
-- Codex device-login flow state, moved out of `kv_entries`.
|
|
2
|
+
--
|
|
3
|
+
-- `state` is the encrypted flow (device auth id, user code, poll lease). It
|
|
4
|
+
-- lived in `kv_entries` namespace 'codex-oauth-device', which db-query must
|
|
5
|
+
-- keep readable for general KV debugging. A dedicated table lets db-query deny
|
|
6
|
+
-- this store by table name, with no SQL-text parsing.
|
|
7
|
+
--
|
|
8
|
+
-- expires_at: unix-ms. Expired rows are deleted on read and on every write.
|
|
9
|
+
|
|
10
|
+
CREATE TABLE IF NOT EXISTS codex_oauth_device_flows (
|
|
11
|
+
flow_id TEXT PRIMARY KEY,
|
|
12
|
+
state TEXT NOT NULL,
|
|
13
|
+
expires_at INTEGER NOT NULL,
|
|
14
|
+
created_at INTEGER NOT NULL DEFAULT (CAST(unixepoch('subsec') * 1000 AS INTEGER)),
|
|
15
|
+
updated_at INTEGER NOT NULL DEFAULT (CAST(unixepoch('subsec') * 1000 AS INTEGER))
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
INSERT OR IGNORE INTO codex_oauth_device_flows (flow_id, state, expires_at, created_at, updated_at)
|
|
19
|
+
SELECT key, value, expires_at, created_at, updated_at
|
|
20
|
+
FROM kv_entries
|
|
21
|
+
WHERE namespace = 'codex-oauth-device'
|
|
22
|
+
AND expires_at IS NOT NULL;
|
|
23
|
+
|
|
24
|
+
DELETE FROM kv_entries WHERE namespace = 'codex-oauth-device';
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
harnessCatalogSection,
|
|
31
31
|
harnessModelMismatch,
|
|
32
32
|
isAlias,
|
|
33
|
+
isHarnessModelJudged,
|
|
33
34
|
type ModelsDevCatalog,
|
|
34
35
|
type ModelsDevModel,
|
|
35
36
|
parseAlias,
|
|
@@ -340,7 +341,7 @@ export async function resolveTaskModel(
|
|
|
340
341
|
input: ResolveTaskModelInput,
|
|
341
342
|
): Promise<TaskModelResolution | TaskModelHarnessMismatch | null> {
|
|
342
343
|
const harness = input.harnessProvider ?? null;
|
|
343
|
-
const judged = harness !== null &&
|
|
344
|
+
const judged = harness !== null && isHarnessModelJudged(harness);
|
|
344
345
|
const sections = judged
|
|
345
346
|
? ((await loadResolutionCatalog()).catalog as HarnessCatalogSections)
|
|
346
347
|
: {};
|
|
@@ -81,7 +81,7 @@ export interface ExplicitModelCheck {
|
|
|
81
81
|
agentName?: string | null;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
/** Null when `model` runs on `harness`, else the reason. Pure; only judges claude, claude-managed, codex. */
|
|
84
|
+
/** Null when `model` runs on `harness`, else the reason. Pure; only judges claude, claude-managed, codex and grok. */
|
|
85
85
|
export const harnessModelError = harnessModelMismatch;
|
|
86
86
|
|
|
87
87
|
/** `harnessModelError` against the current catalog, for callers with no agent row (claim path). */
|
|
@@ -66,6 +66,9 @@ const ROUTING_PREFIXES_BY_PROVIDER: Record<PricingProvider, readonly string[]> =
|
|
|
66
66
|
amp: ["anthropic/", "openai/", "google-vertex/", "google/"],
|
|
67
67
|
// cursor reports a bare Cursor model id, already the vendor's own id.
|
|
68
68
|
cursor: [],
|
|
69
|
+
// The Grok CLI reports a bare xAI id (`grok-4.6`) or `openrouter/<vendor>/<id>`
|
|
70
|
+
// for an OpenRouter model; stripping the prefix lands it on the openrouter rows.
|
|
71
|
+
grok: ["openrouter/", "xai/"],
|
|
69
72
|
};
|
|
70
73
|
|
|
71
74
|
/** `gpt-5-nano-2025-08-07` -> `gpt-5-nano`. Anthropic's 8-digit dates (`-20251001`) are real catalog ids and are left alone. */
|
package/src/be/rbac-audit.ts
CHANGED
|
@@ -21,7 +21,7 @@ import { isEnvFlagEnabled } from "../utils/env-flag";
|
|
|
21
21
|
import { getDbClient } from "./db";
|
|
22
22
|
|
|
23
23
|
type AuditRow = {
|
|
24
|
-
principalType: "agent" | "user" | "operator";
|
|
24
|
+
principalType: "agent" | "user" | "operator" | "guest";
|
|
25
25
|
principalId: string | null;
|
|
26
26
|
originatorUserId: string | null;
|
|
27
27
|
verb: string;
|
|
@@ -61,6 +61,7 @@ function principalIdOf(principal: RbacCheck["principal"]): string | null {
|
|
|
61
61
|
case "user":
|
|
62
62
|
return principal.userId;
|
|
63
63
|
case "operator":
|
|
64
|
+
case "guest":
|
|
64
65
|
return null;
|
|
65
66
|
}
|
|
66
67
|
}
|
package/src/be/scripts/db.ts
CHANGED
|
@@ -8,10 +8,10 @@ import type {
|
|
|
8
8
|
ScriptScope,
|
|
9
9
|
ScriptVersionRecord,
|
|
10
10
|
} from "../../types";
|
|
11
|
-
import { registerVolatileSecret } from "../../utils/secret-scrubber";
|
|
12
11
|
import { generateBearerToken, generateShortId } from "../../utils/short-id";
|
|
13
12
|
import { decryptSecret, encryptSecret, getEncryptionKey } from "../crypto";
|
|
14
13
|
import { computeContentHash, getDbClient } from "../db";
|
|
14
|
+
import { registerStoredSecret, scriptApiSecretName } from "../secret-registry";
|
|
15
15
|
import { embedScript } from "./embeddings";
|
|
16
16
|
|
|
17
17
|
type ScriptRow = Omit<ScriptRecord, "isScratch" | "typeChecked"> & {
|
|
@@ -517,7 +517,7 @@ export async function createScriptApi(args: {
|
|
|
517
517
|
],
|
|
518
518
|
);
|
|
519
519
|
if (!row) throw new Error("Failed to create script API endpoint");
|
|
520
|
-
|
|
520
|
+
registerStoredSecret(token, scriptApiSecretName(id));
|
|
521
521
|
return { ...rowToScriptApi(row), token };
|
|
522
522
|
} catch (err) {
|
|
523
523
|
if (isUniqueConstraintError(err) && attempt < 4) continue;
|
|
@@ -527,6 +527,30 @@ export async function createScriptApi(args: {
|
|
|
527
527
|
throw new Error("Failed to allocate a unique script API endpoint id");
|
|
528
528
|
}
|
|
529
529
|
|
|
530
|
+
/**
|
|
531
|
+
* Every stored script API bearer token, decrypted, for the secret registry
|
|
532
|
+
* (src/be/secret-registry.ts). A row that fails to decrypt is counted, not thrown.
|
|
533
|
+
*/
|
|
534
|
+
export async function listStoredScriptApiSecrets(): Promise<{
|
|
535
|
+
secrets: { id: string; token: string }[];
|
|
536
|
+
failed: number;
|
|
537
|
+
}> {
|
|
538
|
+
const rows = await getDbClient().query<{ id: string; bearerTokenEncrypted: string }>(
|
|
539
|
+
"SELECT id, bearerTokenEncrypted FROM script_apis WHERE bearerTokenEncrypted IS NOT NULL",
|
|
540
|
+
);
|
|
541
|
+
const secrets: { id: string; token: string }[] = [];
|
|
542
|
+
let failed = 0;
|
|
543
|
+
for (const row of rows) {
|
|
544
|
+
try {
|
|
545
|
+
const token = decryptSecret(row.bearerTokenEncrypted, getEncryptionKey());
|
|
546
|
+
if (token) secrets.push({ id: row.id, token });
|
|
547
|
+
} catch {
|
|
548
|
+
failed++;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
return { secrets, failed };
|
|
552
|
+
}
|
|
553
|
+
|
|
530
554
|
export async function listScriptApisForScript(scriptId: string): Promise<ScriptApiRecord[]> {
|
|
531
555
|
const rows = await getDbClient().query<ScriptApiRow>(
|
|
532
556
|
"SELECT * FROM script_apis WHERE scriptId = ? ORDER BY createdAt DESC",
|
|
@@ -554,7 +578,7 @@ export async function getScriptApiSecret(id: string): Promise<string | null> {
|
|
|
554
578
|
);
|
|
555
579
|
if (!row?.bearerTokenEncrypted) return null;
|
|
556
580
|
const token = decryptSecret(row.bearerTokenEncrypted, getEncryptionKey());
|
|
557
|
-
|
|
581
|
+
registerStoredSecret(token, scriptApiSecretName(id));
|
|
558
582
|
return token;
|
|
559
583
|
}
|
|
560
584
|
|
|
@@ -604,7 +628,7 @@ export async function rotateScriptApiSecret(
|
|
|
604
628
|
[encrypted, updatedBy ?? null, id],
|
|
605
629
|
);
|
|
606
630
|
if (!row) return null;
|
|
607
|
-
|
|
631
|
+
registerStoredSecret(token, scriptApiSecretName(id));
|
|
608
632
|
return { ...rowToScriptApi(row), token };
|
|
609
633
|
}
|
|
610
634
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { findSecretsIn } from "../../utils/secret-scrubber";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Executable source is stored and served verbatim (`scripts.source`,
|
|
5
|
+
* `script_versions.source`, `script_runs.source`): redacting it would change
|
|
6
|
+
* what runs, and sealing it would hide reviewed code. So a source that embeds
|
|
7
|
+
* a secret is refused at write instead, before anything runs or persists.
|
|
8
|
+
*
|
|
9
|
+
* Checked: every registered secret value, in each encoded form the known-value
|
|
10
|
+
* matcher holds, and the gitleaks vendor-token rules. The refusal names the
|
|
11
|
+
* secret's key or the rule id, never the value. Rows written before this check
|
|
12
|
+
* are left as they are.
|
|
13
|
+
*/
|
|
14
|
+
export interface SourceSecretRefusal {
|
|
15
|
+
error: "source_contains_secret";
|
|
16
|
+
message: string;
|
|
17
|
+
findings: { kind: "registered-secret" | "gitleaks-rule"; id: string }[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function refuseSourceWithSecrets(source: string): SourceSecretRefusal | null {
|
|
21
|
+
const { knownValues, gitleaksRules } = findSecretsIn(source);
|
|
22
|
+
if (knownValues.length === 0 && gitleaksRules.length === 0) return null;
|
|
23
|
+
const findings = [
|
|
24
|
+
...knownValues.map((id) => ({ kind: "registered-secret" as const, id })),
|
|
25
|
+
...gitleaksRules.map((id) => ({ kind: "gitleaks-rule" as const, id })),
|
|
26
|
+
];
|
|
27
|
+
const named = findings
|
|
28
|
+
.map((f) =>
|
|
29
|
+
f.kind === "registered-secret" ? `registered secret ${f.id}` : `gitleaks rule ${f.id}`,
|
|
30
|
+
)
|
|
31
|
+
.join(", ");
|
|
32
|
+
return {
|
|
33
|
+
error: "source_contains_secret",
|
|
34
|
+
message: `Script source contains a secret (${named}); nothing was saved or run. Source is stored and shown verbatim, so read secrets at run time instead: a credential binding (ctx.api.<slug> / ctx.mcp.<slug>) or ctx.swarm.config.get("<KEY>").`,
|
|
35
|
+
findings,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -106,6 +106,26 @@ export interface KvEntry<T = unknown> {
|
|
|
106
106
|
expiresAt: number | null;
|
|
107
107
|
createdAt: number;
|
|
108
108
|
updatedAt: number;
|
|
109
|
+
/** Present only on a path/offset/limit read. */
|
|
110
|
+
view?: KvView;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface KvViewArgs {
|
|
114
|
+
/** Dot path into the JSON value, e.g. "outcome.data.rows" or "rows.3". */
|
|
115
|
+
path?: string;
|
|
116
|
+
/** First array item / object key / string char. Default 0. */
|
|
117
|
+
offset?: number;
|
|
118
|
+
/** Max items / keys / chars. Unbounded in scripts. */
|
|
119
|
+
limit?: number;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface KvView {
|
|
123
|
+
path: string;
|
|
124
|
+
type: "object" | "array" | "string" | "number" | "boolean" | "null";
|
|
125
|
+
total?: number;
|
|
126
|
+
offset?: number;
|
|
127
|
+
returned?: number;
|
|
128
|
+
nextOffset?: number | null;
|
|
109
129
|
}
|
|
110
130
|
|
|
111
131
|
export interface KvSdkSuccess<T, TStatus extends number = 200> {
|
|
@@ -175,8 +195,9 @@ export interface SwarmSdk {
|
|
|
175
195
|
task_defer(args: { taskId: string; delayMs?: number; runAt?: string; wakeOn?: { event: "task.completed" | "task.failed" | "settled"; taskId: string }; summary: string; note: string; checks?: string[] }): Promise<unknown>;
|
|
176
196
|
task_poll(args?: Record<string, unknown>): Promise<unknown>;
|
|
177
197
|
// --- kv ---
|
|
178
|
-
|
|
179
|
-
|
|
198
|
+
/** path/offset/limit return a view: \`value\` is the slice at a dot path (JSON strings count as JSON), paged over items/keys/chars; \`view\` describes it. */
|
|
199
|
+
kv_get<T = unknown>(args: { key: string; namespace?: string } & KvViewArgs): Promise<KvSdkResponse<KvEntry<T>>>;
|
|
200
|
+
kv_getOrNull<T = unknown>(args: { key: string; namespace?: string } & KvViewArgs): Promise<KvEntry<T> | null>;
|
|
180
201
|
kv_set<T>(args: KvSetArgsBase & { value: T; valueType?: "json" }): Promise<KvSdkResponse<KvEntry<T>>>;
|
|
181
202
|
kv_set(args: KvSetArgsBase & { value: string; valueType: "string" }): Promise<KvSdkResponse<KvEntry<string>>>;
|
|
182
203
|
kv_set(args: KvSetArgsBase & { value: number | string; valueType: "integer" }): Promise<KvSdkResponse<KvEntry<number>>>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type ScrubbedText, scrubObject, scrubSecrets } from "../utils/secret-scrubber";
|
|
2
|
+
|
|
3
|
+
/** True when `before` parsed as JSON and `after` no longer does. */
|
|
4
|
+
export function breaksJsonValidity(before: string, after: string): boolean {
|
|
5
|
+
try {
|
|
6
|
+
JSON.parse(before);
|
|
7
|
+
} catch {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
JSON.parse(after);
|
|
12
|
+
return false;
|
|
13
|
+
} catch {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Serialize `value` for a JSON TEXT column with secrets redacted.
|
|
20
|
+
*
|
|
21
|
+
* Scrubs the serialized text first, so context-keyed passes (`"password":
|
|
22
|
+
* "..."`, auth headers) see the key next to its value. When a redaction breaks
|
|
23
|
+
* JSON validity (a marker swallowed a quote or brace), falls back to scrubbing
|
|
24
|
+
* each string leaf, which always serializes cleanly. Readers keep `JSON.parse`.
|
|
25
|
+
*/
|
|
26
|
+
export function scrubJsonValue(value: unknown): ScrubbedText {
|
|
27
|
+
const raw = JSON.stringify(value);
|
|
28
|
+
const scrubbed = scrubSecrets(raw);
|
|
29
|
+
if (!breaksJsonValidity(raw, scrubbed)) return scrubbed;
|
|
30
|
+
// Every string leaf went through scrubSecrets; the brand holds.
|
|
31
|
+
return JSON.stringify(scrubObject(value)) as ScrubbedText;
|
|
32
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { decryptSecret, encryptSecret, getEncryptionKey } from "./crypto";
|
|
2
|
+
import { scrubJsonValue } from "./scrub-json";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Sealed JSON: byte-exact replay state, encrypted at rest.
|
|
6
|
+
*
|
|
7
|
+
* Some JSON columns are execution inputs, not records. The supervisor relaunches
|
|
8
|
+
* a durable script run from `script_runs.args`, and the harness replays
|
|
9
|
+
* `script_run_journal.result` as a step's return value. Redacting them would
|
|
10
|
+
* change what the script sees after a restart, and storing them raw leaves a
|
|
11
|
+
* secret at rest. Sealing keeps the exact bytes for the replay readers and
|
|
12
|
+
* nothing readable for anything that scans the database.
|
|
13
|
+
*
|
|
14
|
+
* Readers pick a view: `openSealedJson` (replay, exact value) or
|
|
15
|
+
* `sealedJsonForDisplay` (API/UI, redacted). Rows written before sealing are
|
|
16
|
+
* plain JSON and both readers still accept them.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const SEALED_PREFIX = "sealed:v1:";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Every column a writer fills with `sealJson`. Boot counts a sealed value here
|
|
23
|
+
* as encrypted data, like an encrypted config or OAuth row: with the key
|
|
24
|
+
* missing, startup refuses instead of generating a replacement key that could
|
|
25
|
+
* never open them. Add a column here when a writer starts sealing it.
|
|
26
|
+
*/
|
|
27
|
+
export const SEALED_REPLAY_COLUMNS: readonly { table: string; column: string }[] = [
|
|
28
|
+
{ table: "script_runs", column: "args" },
|
|
29
|
+
{ table: "script_run_journal", column: "result" },
|
|
30
|
+
{ table: "workflow_run_steps", column: "output_replay" },
|
|
31
|
+
{ table: "workflow_runs", column: "context_replay" },
|
|
32
|
+
{ table: "approval_requests", column: "responses" },
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
/** SQL `EXISTS` arms matching a sealed value in each `SEALED_REPLAY_COLUMNS` entry. */
|
|
36
|
+
export function sealedReplayExistsSql(): string[] {
|
|
37
|
+
return SEALED_REPLAY_COLUMNS.map(
|
|
38
|
+
({ table, column }) => `SELECT 1 FROM ${table} WHERE ${column} GLOB '${SEALED_PREFIX}*'`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function sealJson(value: unknown): string {
|
|
43
|
+
return SEALED_PREFIX + encryptSecret(JSON.stringify(value), getEncryptionKey());
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function isSealedJson(text: string): boolean {
|
|
47
|
+
return text.startsWith(SEALED_PREFIX);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** The exact stored value. Only execution and replay paths may call this. */
|
|
51
|
+
export function openSealedJson(text: string): unknown {
|
|
52
|
+
if (!isSealedJson(text)) return JSON.parse(text);
|
|
53
|
+
return JSON.parse(decryptSecret(text.slice(SEALED_PREFIX.length), getEncryptionKey()));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The stored value with secrets redacted, for API responses and the UI. */
|
|
57
|
+
export function sealedJsonForDisplay(text: string): unknown {
|
|
58
|
+
return JSON.parse(scrubJsonValue(openSealedJson(text)));
|
|
59
|
+
}
|