@desplega.ai/agent-swarm 1.165.0 → 1.167.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/dist/acp-adapter-zpvy6321.js +34 -0
- package/dist/{actions-jna0b3wm.js → actions-fq2zrwtf.js} +30 -34
- package/dist/{amp-adapter-0eve81pz.js → amp-adapter-5gbk7501.js} +7 -7
- package/dist/{anthropic-messages-hst62ws9.js → anthropic-messages-13yn502s.js} +4 -4
- package/dist/{app-75hm5cfa.js → app-nctetn1e.js} +11 -11
- package/dist/{assistant-yzd51j33.js → assistant-0aq8gjvj.js} +31 -35
- package/dist/{azure-openai-responses-mh8tvf78.js → azure-openai-responses-9st34pfz.js} +5 -5
- package/dist/{boot-reembed-z158xq32.js → boot-reembed-1ze5853n.js} +14 -16
- package/dist/{boot-reembed-2ex2mcsh.js → boot-reembed-fmp5ww69.js} +15 -16
- package/dist/boot-scrub-sweep-cbp8xd4e.js +212 -0
- package/dist/{boot-seeds-2bmnh18p.js → boot-seeds-22tx8a53.js} +13 -12
- package/dist/{claude-adapter-jbj1mycz.js → claude-adapter-0jhgeh1d.js} +25 -25
- package/dist/{claude-managed-adapter-17ggz09t.js → claude-managed-adapter-tzwxy803.js} +5 -5
- package/dist/{claude-managed-setup-ptm1nzm2.js → claude-managed-setup-nrhfx5b0.js} +5 -5
- package/dist/{claude-sdk-session-9re1g7r2.js → claude-sdk-session-wh77rh83.js} +2632 -2614
- package/dist/{cli-zsh093hk.js → cli-014vns2y.js} +63 -1
- package/dist/{cli-qvfk83fy.js → cli-13xwcqvj.js} +2 -2
- package/dist/{cli-8gpbwy09.js → cli-1yrcfa5s.js} +5 -5
- package/dist/{cli-xtgtyafv.js → cli-577bxkf6.js} +212 -451
- package/dist/{cli-ma3zvh39.js → cli-5qkcdv8c.js} +6118 -5874
- package/dist/{cli-cwxhbf43.js → cli-5t5my0h8.js} +85 -2
- package/dist/{cli-zvckv750.js → cli-69egj654.js} +2 -2
- package/dist/{cli-9xb89xca.js → cli-6bpvv8h4.js} +9 -9
- package/dist/{cli-90v942q1.js → cli-6qxmjpem.js} +8 -8
- package/dist/{cli-9866bd07.js → cli-6vfz44sr.js} +1 -1
- package/dist/{cli-32bca7nr.js → cli-7avy4v8w.js} +1 -1
- package/dist/{cli-xb3c6aat.js → cli-7mhy3ths.js} +21 -18
- package/dist/{cli-q38n4bq0.js → cli-95zs84qw.js} +3 -3
- package/dist/cli-a35hzdve.js +33 -0
- package/dist/{cli-x3x0qwky.js → cli-bxqez1p0.js} +51 -23
- package/dist/{cli-prt9emct.js → cli-c2tpw9ck.js} +3 -3
- package/dist/{cli-0n4fasz0.js → cli-c8r99akr.js} +16 -16
- package/dist/{cli-5anb0ev3.js → cli-cexctgn5.js} +89 -26
- package/dist/{cli-bns5c8k9.js → cli-cf2vgn9d.js} +105 -91
- package/dist/{cli-ng97fjq7.js → cli-cmxkbzzs.js} +33 -7
- package/dist/{cli-nexwh8zg.js → cli-g7k7zbcz.js} +12 -5
- package/dist/{cli-mmzwwptd.js → cli-hf5s9pgf.js} +1 -1
- package/dist/{cli-nvvfd8bq.js → cli-jbzbr36n.js} +4091 -3793
- package/dist/{cli-zn2v3t8v.js → cli-jvmtxqtc.js} +27 -15
- package/dist/{cli-pkke6geq.js → cli-km4x66j0.js} +83 -65
- package/dist/cli-kw0jcfje.js +516 -0
- package/dist/{cli-28smfvbn.js → cli-m7vmeepg.js} +32 -19
- package/dist/{cli-hpryr6em.js → cli-mb75wpxf.js} +1 -1
- package/dist/{cli-qdv8kgz7.js → cli-mkdsy1nq.js} +14018 -3333
- package/dist/{cli-b0f70z24.js → cli-mn8qkd8v.js} +1 -1
- package/dist/{cli-420bdgw0.js → cli-mv2520nm.js} +1 -1
- package/dist/{cli-n9ehc28t.js → cli-mxry91sa.js} +31 -10
- package/dist/{cli-wfajtmf7.js → cli-mxvnnv5q.js} +596 -13
- package/dist/cli-nkqjj42q.js +104 -0
- package/dist/{cli-e7t4snpa.js → cli-p8rt8spp.js} +5 -5
- package/dist/{cli-gh5yqzpe.js → cli-pkj1w46r.js} +12 -7
- package/dist/cli-q1vnz8wv.js +96 -0
- package/dist/{cli-fj86xr3c.js → cli-qd97v6z2.js} +2 -2
- package/dist/{cli-ba66wyhy.js → cli-qzmzwegq.js} +60 -20
- package/dist/{cli-5b2fjjzh.js → cli-r7eeddkq.js} +14 -9
- package/dist/{cli-8ssjez7e.js → cli-rhy7z1rp.js} +22 -11
- package/dist/{cli-4mevd1hf.js → cli-s9y81s7j.js} +5 -5
- package/dist/{cli-59jn5twa.js → cli-tc5zec7v.js} +7 -7
- package/dist/{cli-ttbsse3f.js → cli-tmhsxhb8.js} +1 -1
- package/dist/{cli-k6xx3pve.js → cli-tx2zvn23.js} +4 -4
- package/dist/{cli-pjxnr1jn.js → cli-ty4bzkqr.js} +5 -5
- package/dist/{cli-nfygw434.js → cli-v1pwxreq.js} +55 -52
- package/dist/{cli-ftry7975.js → cli-v1w7pbd8.js} +3 -2
- package/dist/{cli-10zmp1p7.js → cli-vr5avem2.js} +27 -32
- package/dist/{cli-bzr0ftht.js → cli-wnb9b2g3.js} +19 -0
- package/dist/cli-wt7ccy4r.js +2654 -0
- package/dist/{cli-50eg13rj.js → cli-wydz3feg.js} +549 -321
- package/dist/{cli-krqm5f11.js → cli-xezn9nkh.js} +16 -13
- package/dist/{cli-8kt0bmgx.js → cli-xf713zz0.js} +2 -2
- package/dist/{cli-x7afk5wm.js → cli-xvc3q9br.js} +1 -1
- package/dist/{cli-9s3h836g.js → cli-xvdgasgy.js} +1 -1
- package/dist/{cli-j0pdrmwg.js → cli-y0kjpfm9.js} +3 -3
- package/dist/{cli-6zcfw0x7.js → cli-y195hv1a.js} +1 -1
- package/dist/{cli-amwhs8h2.js → cli-ykt8f7va.js} +22 -9
- package/dist/{cli-1kzfvd1z.js → cli-ymmc1zxg.js} +15 -11
- package/dist/{cli-xnzrzets.js → cli-ypgba1a8.js} +2 -2
- package/dist/{cli-bdv39kgj.js → cli-z1dqhc9y.js} +31 -20
- package/dist/{cli-fw6vdcq5.js → cli-zp67qg5x.js} +34 -1
- package/dist/{cli-qbksc292.js → cli-zy39d433.js} +2 -0
- package/dist/cli.js +36 -32
- package/dist/{cloudflare-workers-ai-system-one-tt484t61.js → cloudflare-workers-ai-system-one-sta45jf9.js} +7 -5
- package/dist/{codex-adapter-3qnxshje.js → codex-adapter-cgesnjkr.js} +20 -20
- package/dist/{codex-login-brkxhhdg.js → codex-login-g1cyt5zm.js} +2 -2
- package/dist/{codex-session-runner-612wnapp.js → codex-session-runner-8kd7ddhr.js} +20 -20
- package/dist/{commands-2ybfcxkj.js → commands-h6zaaxym.js} +11 -10
- package/dist/{cursor-adapter-p3jgdehv.js → cursor-adapter-v8mm6bp5.js} +7 -7
- package/dist/{db-f4ans0jj.js → db-qg3myn53.js} +13 -10
- package/dist/{devin-adapter-dhzt01ff.js → devin-adapter-bffjj5n4.js} +2 -2
- package/dist/{dispatcher-pk182apd.js → dispatcher-7vnwj9qw.js} +28 -32
- package/dist/{dsh-adapter-7twaeps1.js → dsh-adapter-tz6hfp5r.js} +7 -7
- package/dist/{e2b-y8t3jzyz.js → e2b-nd50eccg.js} +1 -1
- package/dist/{easter-egg-3d-mtpnw06s.js → easter-egg-3d-66zxervv.js} +2 -2
- package/dist/{event-bus-w7ac4n3c.js → event-bus-vnfthafg.js} +2 -2
- package/dist/{execute-s4ccjq3n.js → execute-h4jgevyw.js} +61 -11
- package/dist/{google-generative-ai-d6nphg3q.js → google-generative-ai-b8zxrpax.js} +5 -5
- package/dist/{google-vertex-cpshkref.js → google-vertex-nrdd697t.js} +5 -5
- package/dist/grok-adapter-jdt2dz2s.js +39 -0
- package/dist/{handlers-g2wnr4t9.js → handlers-2qtkpncr.js} +31 -35
- package/dist/{hook-f7jt9vnm.js → hook-51yfcgsv.js} +48 -39
- package/dist/{hook-bbdpnkb4.js → hook-qnbdgwvd.js} +15 -17
- package/dist/{http-w40q4w94.js → http-jsmtag08.js} +536 -231
- package/dist/{index-056j5kzn.js → index-2t9kesn1.js} +13 -12
- package/dist/{index-cj1r9q83.js → index-bt8wkv1w.js} +29 -33
- package/dist/{index-7n70jwbb.js → index-cx1sb9bq.js} +42 -35
- package/dist/{index-9bfssf54.js → index-ekywyv2v.js} +28 -32
- package/dist/{index-fdk1t4d6.js → index-n0rxa4ca.js} +11962 -9900
- package/dist/{index-k8xc083e.js → index-t4g06w93.js} +28 -32
- package/dist/{keepalive-1tj10tf4.js → keepalive-4aezx4bj.js} +16 -12
- package/dist/{lead-st0bg7v4.js → lead-r44bkpm5.js} +52 -56
- package/dist/{llama-cpp-classify-tgj35k8d.js → llama-cpp-classify-6erb7wwv.js} +3 -1
- package/dist/{maintenance-nz71zats.js → maintenance-grz5jrzh.js} +12 -13
- package/dist/{mistral-conversations-8445ddjn.js → mistral-conversations-059y51zt.js} +4 -4
- package/dist/{node-f0ykebbv.js → node-qm9eb6z0.js} +25 -7
- package/dist/{oauth-refresh-sweep-aefzwvvq.js → oauth-refresh-sweep-wm4ctjfj.js} +15 -11
- package/dist/{onboard-kgpr9mbn.js → onboard-v60zgqf0.js} +2 -2
- package/dist/{openai-codex-responses-9ynm88zy.js → openai-codex-responses-jzxkta9b.js} +8 -7
- package/dist/{openai-completions-nhqwjmaw.js → openai-completions-663yncfq.js} +4 -4
- package/dist/openai-decisions-vj9nkpf8.js +162 -0
- package/dist/{openai-responses-xx1c4kgm.js → openai-responses-m3g43rrt.js} +5 -5
- package/dist/{opencode-adapter-0c8e70dk.js → opencode-adapter-yjyqynzf.js} +8 -8
- package/dist/{openrouter-images-8yx3487g.js → openrouter-images-kr60n6b9.js} +1 -1
- package/dist/{otel-impl-x8zp4y5a.js → otel-impl-ghh4dyf8.js} +2 -2
- package/dist/{pi-messages-vxdt8v7j.js → pi-messages-xszk613n.js} +1 -1
- package/dist/{pi-mono-adapter-t280ah5c.js → pi-mono-adapter-0eg8kw2c.js} +69 -52
- package/dist/{pricing-refresh-9e0q7cwk.js → pricing-refresh-mjrqvnvw.js} +12 -11
- package/dist/{rbac-roles-790j3ga8.js → rbac-roles-axv4d74x.js} +11 -10
- package/dist/{rbac-roles-kcp6zge2.js → rbac-roles-fd5tskj9.js} +11 -10
- package/dist/{render-v2-tch4nefj.js → render-v2-k50j33qd.js} +11 -11
- package/dist/{runtime-v2t1r79e.js → runtime-h9ha1mq6.js} +4 -4
- package/dist/{setup-rcm303ns.js → setup-4syh0pd2.js} +2 -2
- package/dist/{typesafe-system-one-datfr82p.js → typesafe-system-one-e988vty7.js} +7 -5
- package/dist/{virtual-modules-wm8mxdxx.js → virtual-modules-ftr9kwe8.js} +11 -11
- package/dist/{work-objects-ctg7t70w.js → work-objects-jcdbg8rr.js} +32 -36
- package/dist/{worker-5dp0xm55.js → worker-x8j8c9sx.js} +52 -56
- package/dist/{x-v3yw66zs.js → x-ma4gnfgy.js} +2 -2
- package/openapi.json +5 -2
- package/package.json +15 -11
- package/src/be/automation-preflight.ts +9 -4
- package/src/be/boot-scrub-sweep.ts +302 -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/openai-embedding.ts +7 -2
- package/src/be/memory/providers/sqlite-store.ts +63 -11
- package/src/be/memory/raters/llm-summarizer.ts +7 -2
- package/src/be/memory/types.ts +14 -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/modelsdev-cache.json +61 -0
- package/src/be/scripts/db.ts +28 -4
- package/src/be/scripts/source-secrets.ts +37 -0
- 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 +84 -53
- package/src/be/swarm-config-guard.ts +13 -0
- package/src/be/workflow-replay.ts +77 -0
- package/src/commands/provider-credentials.ts +28 -5
- package/src/commands/runner.ts +7 -12
- package/src/heartbeat/heartbeat.ts +91 -19
- package/src/hooks/hook.ts +72 -27
- package/src/http/codex-oauth-device.ts +8 -10
- 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/index.ts +23 -6
- package/src/http/mcp.ts +5 -0
- package/src/http/onboarding.ts +2 -0
- package/src/http/script-runs.ts +9 -1
- package/src/http/scripts.ts +15 -0
- package/src/http/workflows.ts +3 -2
- package/src/oauth/mcp-wrapper.ts +1 -1
- package/src/providers/acp-adapter.ts +25 -11
- package/src/providers/acp-target-catalog.ts +35 -1
- package/src/providers/acp-targets.ts +78 -7
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/claude-adapter.ts +26 -0
- package/src/providers/codex-adapter.ts +30 -3
- package/src/providers/codex-oauth/auth-json.ts +21 -4
- package/src/providers/codex-oauth/types.ts +2 -0
- package/src/providers/cursor-adapter.ts +1 -1
- package/src/providers/dsh-adapter.ts +10 -1
- package/src/providers/grok-adapter.ts +18 -2
- package/src/providers/opencode-adapter.ts +21 -5
- package/src/providers/pi-mono-extension.ts +56 -25
- package/src/providers/pricing-sources.md +2 -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/server.ts +66 -4
- package/src/tests/acp-adapter.test.ts +277 -12
- package/src/tests/boot-scrub-sweep.test.ts +390 -0
- package/src/tests/check-text-columns.test.ts +145 -0
- package/src/tests/ci-unit-test-report.test.ts +73 -0
- package/src/tests/claude-default-route.test.ts +75 -0
- package/src/tests/claude-hook-output.test.ts +233 -0
- package/src/tests/claude-sdk-process.fixture.ts +13 -2
- package/src/tests/codex-adapter-pool-auth-revalidate.test.ts +4 -2
- package/src/tests/codex-adapter.test.ts +166 -26
- package/src/tests/codex-oauth-auth-timestamps.test.ts +76 -0
- package/src/tests/codex-oauth-device.test.ts +23 -37
- package/src/tests/codex-oauth.test.ts +4 -1
- package/src/tests/context-window.test.ts +2 -2
- package/src/tests/credential-check.test.ts +51 -1
- 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/dsh-adapter.test.ts +23 -1
- package/src/tests/enabled-tools.test.ts +269 -0
- package/src/tests/entrypoint-acp-target-binary.test.ts +31 -1
- package/src/tests/entrypoint-codex-oauth-seed.test.ts +12 -0
- package/src/tests/events-scrub.test.ts +88 -0
- package/src/tests/fixtures/secret-corpus.ts +320 -0
- package/src/tests/grok-adapter.test.ts +13 -0
- package/src/tests/haiku-5-5-pricing.test.ts +38 -0
- package/src/tests/heartbeat-checklist.test.ts +158 -1
- package/src/tests/onboarding.test.ts +44 -0
- package/src/tests/opencode-adapter.test.ts +66 -3
- package/src/tests/opencode-plugin.test.ts +37 -0
- package/src/tests/openrouter-attribution-call-sites.test.ts +204 -0
- package/src/tests/openrouter-base-url.test.ts +30 -39
- package/src/tests/pi-mono-extension.test.ts +161 -2
- package/src/tests/pricing-refresh.test.ts +70 -1
- package/src/tests/reasoning-effort.test.ts +3 -1
- package/src/tests/script-connections.test.ts +5 -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 +88 -1
- package/src/tests/script-runs-scrub.test.ts +275 -0
- package/src/tests/scripts-http.test.ts +82 -1
- 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 +3 -2
- package/src/tests/telemetry-trigger.test.ts +12 -1
- package/src/tests/workflow-llm.test.ts +14 -0
- 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 +33 -0
- package/src/tools/db-query.ts +1 -1
- package/src/tools/inject-learning.ts +1 -1
- package/src/tools/request-human-input.ts +7 -2
- package/src/tools/workflows/get-workflow-run.ts +3 -2
- package/src/types.ts +1 -1
- package/src/utils/enabled-tools.ts +27 -0
- package/src/utils/internal-ai/complete-structured.ts +11 -3
- package/src/utils/openrouter-base-url.ts +23 -0
- package/src/utils/secret-rules.generated.ts +2055 -0
- package/src/utils/secret-scrubber.ts +363 -29
- package/src/workflows/engine.ts +39 -4
- package/src/workflows/executors/raw-llm.ts +1 -0
- package/src/workflows/executors/system-one-decision.ts +2 -0
- package/src/workflows/executors/validate.ts +1 -0
- package/src/workflows/executors/workflow-llm.ts +9 -2
- package/src/workflows/recovery.ts +10 -1
- package/src/workflows/resume.ts +62 -3
- package/src/workflows/retry-poller.ts +3 -0
- package/templates/skills/agent-browser/SKILL.md +2 -0
- package/templates/skills/agent-browser/content.md +2 -0
- package/templates/skills/code-quality/content.md +1 -0
- package/templates/skills/heartbeat-runbook/content.md +1 -1
- package/templates/skills/work-on-task/SKILL.md +4 -0
- package/templates/skills/work-on-task/content.md +4 -0
- package/templates/skills/workflow-iterate/content.md +9 -0
- package/dist/acp-adapter-w6zws6me.js +0 -23
- package/dist/boot-scrub-logs-246yn076.js +0 -99
- package/dist/cli-3a4m1aws.js +0 -572
- package/dist/cli-3dpj7atn.js +0 -11
- package/dist/cli-a87b04k8.js +0 -951
- package/dist/cli-cvk6yzw5.js +0 -40
- package/dist/cli-gvcgmx69.js +0 -183
- package/dist/cli-h1spv8v4.js +0 -427
- package/dist/cli-jqt9hrnz.js +0 -168
- package/dist/cli-yhmwmv0g.js +0 -9281
- package/dist/grok-adapter-yt9pab3k.js +0 -29
- package/src/be/boot-scrub-logs.ts +0 -132
- package/dist/{codex-hook-d2rkvf39.js → codex-hook-15dgc4cb.js} +1 -1
package/src/hooks/hook.ts
CHANGED
|
@@ -168,12 +168,25 @@ export async function postHookProfileUpdate({
|
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
/**
|
|
171
|
-
* Hook
|
|
171
|
+
* Hook JSON responses. Claude Code parses stdout as JSON only when the whole
|
|
172
|
+
* stdout is one JSON object, so these must be the only stdout of the event.
|
|
172
173
|
* See: https://code.claude.com/docs/en/hooks
|
|
173
174
|
*/
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
175
|
+
type HookBlockResponse =
|
|
176
|
+
| {
|
|
177
|
+
hookSpecificOutput: {
|
|
178
|
+
hookEventName: "PreToolUse";
|
|
179
|
+
permissionDecision: "deny";
|
|
180
|
+
permissionDecisionReason: string;
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
| { decision: "block"; reason: string };
|
|
184
|
+
|
|
185
|
+
interface HookContextResponse {
|
|
186
|
+
hookSpecificOutput: {
|
|
187
|
+
hookEventName: "PreToolUse" | "PostToolUse";
|
|
188
|
+
additionalContext: string;
|
|
189
|
+
};
|
|
177
190
|
}
|
|
178
191
|
|
|
179
192
|
/**
|
|
@@ -850,12 +863,33 @@ export async function handleHook(): Promise<void> {
|
|
|
850
863
|
|
|
851
864
|
/**
|
|
852
865
|
* Output a blocking response to stop Claude from continuing.
|
|
853
|
-
*
|
|
866
|
+
* Must be the only stdout of the event: any plain line before it makes
|
|
867
|
+
* Claude Code read the whole stdout as text and run the tool anyway.
|
|
854
868
|
*/
|
|
855
869
|
const outputBlockResponse = (reason: string): void => {
|
|
856
|
-
const response: HookBlockResponse =
|
|
857
|
-
|
|
858
|
-
|
|
870
|
+
const response: HookBlockResponse =
|
|
871
|
+
msg.hook_event_name === "PreToolUse"
|
|
872
|
+
? {
|
|
873
|
+
hookSpecificOutput: {
|
|
874
|
+
hookEventName: "PreToolUse",
|
|
875
|
+
permissionDecision: "deny",
|
|
876
|
+
permissionDecisionReason: reason,
|
|
877
|
+
},
|
|
878
|
+
}
|
|
879
|
+
: { decision: "block", reason };
|
|
880
|
+
console.log(JSON.stringify(response));
|
|
881
|
+
};
|
|
882
|
+
|
|
883
|
+
/**
|
|
884
|
+
* Output context for the model on a tool event. Plain stdout on PreToolUse
|
|
885
|
+
* and PostToolUse never reaches the model; additionalContext does.
|
|
886
|
+
*/
|
|
887
|
+
const outputToolContext = (
|
|
888
|
+
hookEventName: "PreToolUse" | "PostToolUse",
|
|
889
|
+
additionalContext: string,
|
|
890
|
+
): void => {
|
|
891
|
+
const response: HookContextResponse = {
|
|
892
|
+
hookSpecificOutput: { hookEventName, additionalContext },
|
|
859
893
|
};
|
|
860
894
|
console.log(JSON.stringify(response));
|
|
861
895
|
};
|
|
@@ -988,8 +1022,23 @@ export async function handleHook(): Promise<void> {
|
|
|
988
1022
|
// Get current agent info
|
|
989
1023
|
const agentInfo = await getAgentInfo();
|
|
990
1024
|
|
|
991
|
-
//
|
|
992
|
-
|
|
1025
|
+
// A block must be the only stdout of the event, so a cancelled task is
|
|
1026
|
+
// checked before the status line goes out.
|
|
1027
|
+
if (
|
|
1028
|
+
msg.hook_event_name === "UserPromptSubmit" &&
|
|
1029
|
+
agentInfo &&
|
|
1030
|
+
!agentInfo.isLead &&
|
|
1031
|
+
agentInfo.status === "busy"
|
|
1032
|
+
) {
|
|
1033
|
+
if (await checkAndBlockIfCancelled(true)) {
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
// Output agent status with system tray. Skipped on tool events: their plain
|
|
1039
|
+
// stdout never reaches the model and would break a JSON decision.
|
|
1040
|
+
const isToolEvent = msg.hook_event_name === "PreToolUse" || msg.hook_event_name === "PostToolUse";
|
|
1041
|
+
if (agentInfo && !isToolEvent) {
|
|
993
1042
|
// Base status line
|
|
994
1043
|
console.log(
|
|
995
1044
|
`You are registered as ${agentInfo.isLead ? "lead" : "worker"} agent "${agentInfo.name}" (ID: ${agentInfo.id}, status: ${agentInfo.status}).`,
|
|
@@ -1004,7 +1053,7 @@ export async function handleHook(): Promise<void> {
|
|
|
1004
1053
|
}
|
|
1005
1054
|
} else if (
|
|
1006
1055
|
shouldShowRegistrationNudge({
|
|
1007
|
-
agentInfoPresent:
|
|
1056
|
+
agentInfoPresent: Boolean(agentInfo),
|
|
1008
1057
|
eventType: msg.hook_event_name,
|
|
1009
1058
|
hasAgentIdHeader: hasAgentIdHeader(),
|
|
1010
1059
|
})
|
|
@@ -1131,8 +1180,7 @@ export async function handleHook(): Promise<void> {
|
|
|
1131
1180
|
}
|
|
1132
1181
|
|
|
1133
1182
|
// Block `gh pr create|edit` when the body for a public repo leaks internal
|
|
1134
|
-
// identifiers. Exit code 2 blocks the call
|
|
1135
|
-
// above already went to stdout, and stderr reaches the model.
|
|
1183
|
+
// identifiers. Exit code 2 blocks the call and stderr reaches the model.
|
|
1136
1184
|
if (msg.tool_name === "Bash") {
|
|
1137
1185
|
const prBodyBlock = await guardGhPrBody(msg.tool_input, msg.cwd ?? process.cwd());
|
|
1138
1186
|
if (prBodyBlock) {
|
|
@@ -1142,6 +1190,9 @@ export async function handleHook(): Promise<void> {
|
|
|
1142
1190
|
}
|
|
1143
1191
|
}
|
|
1144
1192
|
|
|
1193
|
+
// Emitted last, so a later block stays the only stdout of the event.
|
|
1194
|
+
let loopWarning: string | undefined;
|
|
1195
|
+
|
|
1145
1196
|
// Tool loop detection (workers only, when processing a task)
|
|
1146
1197
|
if (agentInfo && !agentInfo.isLead && agentInfo.status === "busy") {
|
|
1147
1198
|
const loopTaskFile = await readTaskFile();
|
|
@@ -1162,7 +1213,7 @@ export async function handleHook(): Promise<void> {
|
|
|
1162
1213
|
}
|
|
1163
1214
|
|
|
1164
1215
|
if (loopResult.severity === "warning" && loopResult.reason) {
|
|
1165
|
-
|
|
1216
|
+
loopWarning = `Warning: ${loopResult.reason}`;
|
|
1166
1217
|
}
|
|
1167
1218
|
}
|
|
1168
1219
|
}
|
|
@@ -1179,6 +1230,9 @@ export async function handleHook(): Promise<void> {
|
|
|
1179
1230
|
}
|
|
1180
1231
|
}
|
|
1181
1232
|
|
|
1233
|
+
if (loopWarning) {
|
|
1234
|
+
outputToolContext("PreToolUse", loopWarning);
|
|
1235
|
+
}
|
|
1182
1236
|
break;
|
|
1183
1237
|
}
|
|
1184
1238
|
|
|
@@ -1200,7 +1254,8 @@ export async function handleHook(): Promise<void> {
|
|
|
1200
1254
|
method: "PUT",
|
|
1201
1255
|
headers: mcpConfig!.headers,
|
|
1202
1256
|
}).catch((e) => {
|
|
1203
|
-
|
|
1257
|
+
// stderr: stdout on this event is reserved for one JSON object.
|
|
1258
|
+
console.error("Failed to update agent activity timestamp:", e);
|
|
1204
1259
|
});
|
|
1205
1260
|
}
|
|
1206
1261
|
|
|
@@ -1270,7 +1325,8 @@ export async function handleHook(): Promise<void> {
|
|
|
1270
1325
|
if (msg.tool_name?.endsWith("send-task")) {
|
|
1271
1326
|
const maybeTaskId = (msg.tool_response as { task?: { id?: string } })?.task?.id;
|
|
1272
1327
|
|
|
1273
|
-
|
|
1328
|
+
outputToolContext(
|
|
1329
|
+
"PostToolUse",
|
|
1274
1330
|
`Task sent successfully.${maybeTaskId ? ` Task ID: ${maybeTaskId}.` : ""} Monitor progress using the get-task-details tool periodically.`,
|
|
1275
1331
|
);
|
|
1276
1332
|
}
|
|
@@ -1278,17 +1334,6 @@ export async function handleHook(): Promise<void> {
|
|
|
1278
1334
|
}
|
|
1279
1335
|
break;
|
|
1280
1336
|
|
|
1281
|
-
case "UserPromptSubmit": {
|
|
1282
|
-
// For worker agents, check if their task has been cancelled
|
|
1283
|
-
// This catches cancellations at the start of a new iteration
|
|
1284
|
-
if (agentInfo && !agentInfo.isLead && agentInfo.status === "busy") {
|
|
1285
|
-
if (await checkAndBlockIfCancelled(true)) {
|
|
1286
|
-
return; // Exit early
|
|
1287
|
-
}
|
|
1288
|
-
}
|
|
1289
|
-
break;
|
|
1290
|
-
}
|
|
1291
|
-
|
|
1292
1337
|
case "Stop":
|
|
1293
1338
|
// Clean up any artifact tunnels managed by PM2
|
|
1294
1339
|
try {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { decryptSecret, encryptSecret, getEncryptionKey } from "../be/crypto";
|
|
4
|
-
import { getDbClient,
|
|
4
|
+
import { getDbClient, upsertSwarmConfig } from "../be/db";
|
|
5
|
+
import { getCodexDeviceFlow, upsertCodexDeviceFlow } from "../be/db/codex-oauth-device-flows";
|
|
5
6
|
import { updateOnboardingAiFromCodexDevice } from "../be/onboarding";
|
|
6
7
|
import {
|
|
7
8
|
CODEX_DEVICE_VERIFICATION_URL,
|
|
@@ -17,7 +18,6 @@ import { ensureConfigAdmin } from "./config";
|
|
|
17
18
|
import { route } from "./route-def";
|
|
18
19
|
import { jsonError } from "./utils";
|
|
19
20
|
|
|
20
|
-
const FLOW_NAMESPACE = "codex-oauth-device";
|
|
21
21
|
const FLOW_TTL_MS = 15 * 60 * 1_000;
|
|
22
22
|
const POLL_LEASE_MS = 45_000;
|
|
23
23
|
|
|
@@ -85,10 +85,10 @@ function registerFlowSecrets(state: FlowState): void {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
async function readFlow(flowId: string): Promise<FlowState | null> {
|
|
88
|
-
const entry = await
|
|
89
|
-
if (!entry
|
|
88
|
+
const entry = await getCodexDeviceFlow(flowId);
|
|
89
|
+
if (!entry) return null;
|
|
90
90
|
try {
|
|
91
|
-
const plaintext = decryptSecret(entry.
|
|
91
|
+
const plaintext = decryptSecret(entry.state, getEncryptionKey());
|
|
92
92
|
const parsed = FlowStateSchema.safeParse(JSON.parse(plaintext));
|
|
93
93
|
if (!parsed.success) return null;
|
|
94
94
|
registerFlowSecrets(parsed.data);
|
|
@@ -99,11 +99,9 @@ async function readFlow(flowId: string): Promise<FlowState | null> {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
async function writeFlow(flowId: string, state: FlowState): Promise<void> {
|
|
102
|
-
await
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
value: encryptSecret(JSON.stringify(state), getEncryptionKey()),
|
|
106
|
-
valueType: "string",
|
|
102
|
+
await upsertCodexDeviceFlow({
|
|
103
|
+
flowId,
|
|
104
|
+
state: encryptSecret(JSON.stringify(state), getEncryptionKey()),
|
|
107
105
|
expiresAt: state.expiresAt,
|
|
108
106
|
});
|
|
109
107
|
}
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
import { getDb, resolveSqliteVecExtensionPath } from "../be/db";
|
|
38
38
|
import type { DbQueryResult } from "./db-query-shared";
|
|
39
39
|
import {
|
|
40
|
+
assertNoDeniedTables,
|
|
40
41
|
assertSingleStatement,
|
|
41
42
|
DbQueryConcurrencyCapError,
|
|
42
43
|
DbQueryTimeoutError,
|
|
@@ -203,6 +204,8 @@ export async function executeReadOnlyQueryBounded(
|
|
|
203
204
|
maxRows?: number,
|
|
204
205
|
): Promise<DbQueryResult> {
|
|
205
206
|
assertSingleStatement(sql);
|
|
207
|
+
// The child opens its own connection, so the parent checks the program first.
|
|
208
|
+
assertNoDeniedTables(sql);
|
|
206
209
|
acquireBoundedQuerySlot();
|
|
207
210
|
|
|
208
211
|
try {
|
|
@@ -56,6 +56,111 @@ export function assertSingleStatement(sql: string): void {
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Tables whose rows are credentials: OAuth tokens and client secrets, PKCE
|
|
61
|
+
* verifiers, API and session token hashes, encrypted script-API bearers, and
|
|
62
|
+
* encrypted Codex device-login state.
|
|
63
|
+
* `db-query` is open to every authenticated agent, so it refuses any query
|
|
64
|
+
* that reads one of these, however the table is reached (alias, CTE, view,
|
|
65
|
+
* subquery, join or covering index). `swarm_config` stays readable: its
|
|
66
|
+
* secret values are ciphertext and agents query its non-secret keys.
|
|
67
|
+
*/
|
|
68
|
+
export const DB_QUERY_DENIED_TABLES: ReadonlySet<string> = new Set([
|
|
69
|
+
"oauth_authorizations",
|
|
70
|
+
"oauth_apps",
|
|
71
|
+
"oauth_pending",
|
|
72
|
+
"user_tokens",
|
|
73
|
+
"session_tokens",
|
|
74
|
+
"script_apis",
|
|
75
|
+
"codex_oauth_device_flows",
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
/** Thrown when a query would read a table in {@link DB_QUERY_DENIED_TABLES}. */
|
|
79
|
+
export class DbQueryDeniedTableError extends Error {
|
|
80
|
+
constructor(readonly tables: string[]) {
|
|
81
|
+
super(
|
|
82
|
+
`db-query cannot read ${tables.join(", ")}: ${tables.length === 1 ? "this table holds" : "these tables hold"} credentials. ` +
|
|
83
|
+
"Use the dedicated OAuth, token or config tools instead.",
|
|
84
|
+
);
|
|
85
|
+
this.name = "DbQueryDeniedTableError";
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Bytecode opcodes whose p2 is the root page of the b-tree they open. */
|
|
90
|
+
const CURSOR_OPEN_OPCODES = new Set(["OpenRead", "ReopenIdx", "OpenWrite"]);
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Open a read-only connection to the swarm database. User SQL is never
|
|
94
|
+
* prepared on the writable application connection. In-memory databases have
|
|
95
|
+
* no file to reopen, so they get a read-only snapshot instead.
|
|
96
|
+
*/
|
|
97
|
+
function openReadOnlyReader(): Database {
|
|
98
|
+
const database = getDb();
|
|
99
|
+
const reader =
|
|
100
|
+
!database.filename || database.filename === ":memory:"
|
|
101
|
+
? Database.deserialize(database.serialize(), { readonly: true })
|
|
102
|
+
: new Database(database.filename, { readonly: true });
|
|
103
|
+
const vecExtensionPath = resolveSqliteVecExtensionPath();
|
|
104
|
+
if (vecExtensionPath) {
|
|
105
|
+
try {
|
|
106
|
+
reader.loadExtension(vecExtensionPath);
|
|
107
|
+
} catch {
|
|
108
|
+
// Match the bounded path: only vec queries require the extension.
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return reader;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Reject `sql` if it reads a denied table. Works on the compiled program, not
|
|
116
|
+
* the SQL text: `EXPLAIN` lists every b-tree the statement opens by root page,
|
|
117
|
+
* and `sqlite_master` maps root pages (tables and their indexes) back to a
|
|
118
|
+
* table name. Views, CTEs and subqueries are already expanded in the program,
|
|
119
|
+
* so no SQL parsing is needed. A user `EXPLAIN ...` returns bytecode, never
|
|
120
|
+
* row data, so it is allowed as is.
|
|
121
|
+
*/
|
|
122
|
+
export function assertNoDeniedTables(sql: string, reader?: Database): void {
|
|
123
|
+
const statement = stripTrailingSemicolon(sql);
|
|
124
|
+
if (/^explain\b/i.test(stripLeadingComments(statement))) return;
|
|
125
|
+
|
|
126
|
+
const db = reader ?? openReadOnlyReader();
|
|
127
|
+
try {
|
|
128
|
+
const rootPages = new Map<number, string>();
|
|
129
|
+
for (const row of db
|
|
130
|
+
.query("SELECT rootpage, tbl_name FROM main.sqlite_master WHERE rootpage > 0")
|
|
131
|
+
.all() as Array<{ rootpage: number; tbl_name: string }>) {
|
|
132
|
+
rootPages.set(row.rootpage, row.tbl_name.toLowerCase());
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
using program = db.prepare(`EXPLAIN ${statement}`);
|
|
136
|
+
const denied = new Set<string>();
|
|
137
|
+
for (const op of program.all() as Array<{ opcode: string; p2: number; p3: number }>) {
|
|
138
|
+
// p3 is the schema index: 0 = main. Credential tables only live there.
|
|
139
|
+
if (!CURSOR_OPEN_OPCODES.has(op.opcode) || op.p3 !== 0) continue;
|
|
140
|
+
const table = rootPages.get(op.p2);
|
|
141
|
+
if (table && DB_QUERY_DENIED_TABLES.has(table)) denied.add(table);
|
|
142
|
+
}
|
|
143
|
+
if (denied.size > 0) throw new DbQueryDeniedTableError([...denied].sort());
|
|
144
|
+
} finally {
|
|
145
|
+
if (!reader) db.close();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function stripLeadingComments(sql: string): string {
|
|
150
|
+
let rest = sql.trimStart();
|
|
151
|
+
for (;;) {
|
|
152
|
+
if (rest.startsWith("--")) {
|
|
153
|
+
const end = rest.indexOf("\n");
|
|
154
|
+
rest = end === -1 ? "" : rest.slice(end + 1).trimStart();
|
|
155
|
+
} else if (rest.startsWith("/*")) {
|
|
156
|
+
const end = rest.indexOf("*/");
|
|
157
|
+
rest = end === -1 ? "" : rest.slice(end + 2).trimStart();
|
|
158
|
+
} else {
|
|
159
|
+
return rest;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
59
164
|
/**
|
|
60
165
|
* Execute a read-only SQL query against the swarm database, synchronously,
|
|
61
166
|
* on the caller's own thread.
|
|
@@ -72,22 +177,9 @@ export function executeReadOnlyQuery(
|
|
|
72
177
|
maxRows?: number,
|
|
73
178
|
): DbQueryResult {
|
|
74
179
|
assertSingleStatement(sql);
|
|
75
|
-
const
|
|
76
|
-
// Never prepare user SQL on the writable application connection. In-memory
|
|
77
|
-
// databases have no file to reopen, so query a read-only snapshot instead.
|
|
78
|
-
const reader =
|
|
79
|
-
!database.filename || database.filename === ":memory:"
|
|
80
|
-
? Database.deserialize(database.serialize(), { readonly: true })
|
|
81
|
-
: new Database(database.filename, { readonly: true });
|
|
180
|
+
const reader = openReadOnlyReader();
|
|
82
181
|
try {
|
|
83
|
-
|
|
84
|
-
if (vecExtensionPath) {
|
|
85
|
-
try {
|
|
86
|
-
reader.loadExtension(vecExtensionPath);
|
|
87
|
-
} catch {
|
|
88
|
-
// Match the bounded path: only vec queries require the extension.
|
|
89
|
-
}
|
|
90
|
-
}
|
|
182
|
+
assertNoDeniedTables(sql, reader);
|
|
91
183
|
using stmt = reader.prepare(sql);
|
|
92
184
|
|
|
93
185
|
// Require a result set; SQLite readonly mode is the write barrier.
|
package/src/http/db-query.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import { scrubObject } from "../utils/secret-scrubber";
|
|
3
4
|
import { executeReadOnlyQueryBounded } from "./db-query-bounded";
|
|
4
5
|
import {
|
|
5
6
|
assertSingleStatement,
|
|
@@ -18,7 +19,10 @@ import { json, jsonError } from "./utils";
|
|
|
18
19
|
|
|
19
20
|
export type { DbQueryResult } from "./db-query-shared";
|
|
20
21
|
export {
|
|
22
|
+
assertNoDeniedTables,
|
|
21
23
|
assertSingleStatement,
|
|
24
|
+
DB_QUERY_DENIED_TABLES,
|
|
25
|
+
DbQueryDeniedTableError,
|
|
22
26
|
executeReadOnlyQuery,
|
|
23
27
|
getDbQueryHttpBudgetMs,
|
|
24
28
|
getDbQueryHttpMaxRows,
|
|
@@ -104,7 +108,9 @@ const dbQueryRoute = route({
|
|
|
104
108
|
rowLimit: z.number().nullable(),
|
|
105
109
|
}),
|
|
106
110
|
},
|
|
107
|
-
400: {
|
|
111
|
+
400: {
|
|
112
|
+
description: "Invalid or disallowed SQL, including queries that read credential tables",
|
|
113
|
+
},
|
|
108
114
|
408: { description: "Query exceeded its wall-clock budget and was terminated" },
|
|
109
115
|
429: { description: "Too many concurrent bounded db-query executions; retry shortly" },
|
|
110
116
|
},
|
|
@@ -131,7 +137,9 @@ export async function handleDbQuery(
|
|
|
131
137
|
getDbQueryHttpBudgetMs(),
|
|
132
138
|
getDbQueryHttpMaxRows(),
|
|
133
139
|
);
|
|
134
|
-
|
|
140
|
+
// Rows can hold secrets pasted into free text (task output, logs, events).
|
|
141
|
+
// The MCP tool gets this from the registrar; the HTTP route does it here.
|
|
142
|
+
dbQueryRoute.respond(res, 200, scrubObject(result));
|
|
135
143
|
} catch (err: unknown) {
|
|
136
144
|
if (err instanceof DbQueryConcurrencyCapError) {
|
|
137
145
|
// Machine-readable code + Retry-After so a caller can back off instead
|
package/src/http/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
stopAuditWriter,
|
|
27
27
|
} from "../be/rbac-audit";
|
|
28
28
|
import { startScratchScriptGc, stopScratchScriptGc } from "../be/scripts/retention";
|
|
29
|
+
import { loadSecretRegistry } from "../be/secret-registry";
|
|
29
30
|
import { seedLegacyCapabilitiesConfig } from "../be/seed-capabilities";
|
|
30
31
|
import {
|
|
31
32
|
loadEnabledExtensions,
|
|
@@ -589,6 +590,22 @@ try {
|
|
|
589
590
|
}
|
|
590
591
|
warnIfCorsAllowsAnyOrigin();
|
|
591
592
|
|
|
593
|
+
// Register every stored secret (config secrets of all scopes, OAuth and MCP
|
|
594
|
+
// OAuth credentials, script API tokens) with the scrubber before listen, so
|
|
595
|
+
// egress and the boot retro-sweep below can redact values not touched since
|
|
596
|
+
// the last restart. Non-fatal: a decrypt failure only loses redaction coverage.
|
|
597
|
+
try {
|
|
598
|
+
const loaded = await loadSecretRegistry();
|
|
599
|
+
console.log(
|
|
600
|
+
`[secret-registry] registered config=${loaded.config} oauth=${loaded.oauth} scriptApi=${loaded.scriptApi} failed=${loaded.failed}`,
|
|
601
|
+
);
|
|
602
|
+
} catch (err) {
|
|
603
|
+
console.error(
|
|
604
|
+
"[secret-registry] load failed (non-fatal):",
|
|
605
|
+
scrubSecrets(err instanceof Error ? err.message : String(err)),
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
|
|
592
609
|
// Upgrade seed: explicit CAPABILITIES env values that predate capability
|
|
593
610
|
// gating get the previously always-registered groups backfilled into a
|
|
594
611
|
// global swarm_config row (operator-editable; skipped when a row exists).
|
|
@@ -809,13 +826,13 @@ httpServer
|
|
|
809
826
|
console.error("[boot-reembed-scripts] startup backfill failed (non-fatal):", err);
|
|
810
827
|
});
|
|
811
828
|
|
|
812
|
-
//
|
|
813
|
-
//
|
|
814
|
-
// Idempotent, tracked via seed_state.
|
|
815
|
-
import("../be/boot-scrub-
|
|
816
|
-
.then(({
|
|
829
|
+
// Versioned retro-sweep: once per SCRUBBER_RULES_VERSION, redact stored
|
|
830
|
+
// rows (logs, tasks, memory, events, workflow steps) that pre-date the
|
|
831
|
+
// current scrubber rules. Idempotent and resumable, tracked via seed_state.
|
|
832
|
+
import("../be/boot-scrub-sweep")
|
|
833
|
+
.then(({ runBootScrubSweep }) => runBootScrubSweep())
|
|
817
834
|
.catch((err) => {
|
|
818
|
-
console.error("[boot-scrub-
|
|
835
|
+
console.error("[boot-scrub-sweep] startup scrub failed (non-fatal):", err);
|
|
819
836
|
});
|
|
820
837
|
})
|
|
821
838
|
.on("error", (err) => {
|
package/src/http/mcp.ts
CHANGED
|
@@ -244,6 +244,11 @@ export async function handleMcp(
|
|
|
244
244
|
}
|
|
245
245
|
const server = await createServer({
|
|
246
246
|
preloadedTools,
|
|
247
|
+
// Agent row, then global row, then deployment env. A blank agent row
|
|
248
|
+
// resolves to "unset" for that agent.
|
|
249
|
+
enabledTools:
|
|
250
|
+
configs.find((config) => config.key === "SWARM_ENABLED_TOOLS")?.value ??
|
|
251
|
+
process.env.SWARM_ENABLED_TOOLS,
|
|
247
252
|
scriptsOnly: resolveScriptsOnlyMode({
|
|
248
253
|
env: process.env.SCRIPTS_ONLY_MCP,
|
|
249
254
|
configValue,
|
package/src/http/onboarding.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
} from "../be/onboarding";
|
|
22
22
|
import { assertUrlSafe, publicEndpointSsrfOptions } from "../oauth/mcp-wrapper";
|
|
23
23
|
import { ProviderNameSchema } from "../types";
|
|
24
|
+
import { getOpenRouterAttributionHeaders } from "../utils/openrouter-base-url";
|
|
24
25
|
import { ensureConfigAdmin } from "./config";
|
|
25
26
|
import { scheduleIntegrationsReload } from "./core";
|
|
26
27
|
import { route } from "./route-def";
|
|
@@ -255,6 +256,7 @@ async function handleMemoryProbe(body: OnboardingMemoryRequest): Promise<Onboard
|
|
|
255
256
|
const client = new OpenAI({
|
|
256
257
|
baseURL: endpoint.toString(),
|
|
257
258
|
apiKey,
|
|
259
|
+
defaultHeaders: getOpenRouterAttributionHeaders(endpoint.toString()),
|
|
258
260
|
timeout: 15_000,
|
|
259
261
|
maxRetries: 0,
|
|
260
262
|
fetchOptions: { redirect: "manual" },
|
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 {
|
|
@@ -40,6 +41,7 @@ import {
|
|
|
40
41
|
TERMINAL_SCRIPT_RUN_STATUSES,
|
|
41
42
|
} from "../types";
|
|
42
43
|
import { getAppUrl, getMcpBaseUrl } from "../utils/constants";
|
|
44
|
+
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
43
45
|
import {
|
|
44
46
|
executeRawLlm,
|
|
45
47
|
RawLlmConfigSchema,
|
|
@@ -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(
|
|
@@ -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;
|
package/src/http/workflows.ts
CHANGED
|
@@ -901,13 +901,14 @@ export async function handleWorkflows(
|
|
|
901
901
|
if (getWorkflowRunRoute.match(req.method, pathSegments)) {
|
|
902
902
|
const parsed = await getWorkflowRunRoute.parse(req, res, pathSegments, queryParams);
|
|
903
903
|
if (!parsed) return true;
|
|
904
|
-
|
|
904
|
+
// Display view: the scrubbed context and step outputs, never replay state.
|
|
905
|
+
const run = await getWorkflowRun(parsed.params.id, "display");
|
|
905
906
|
if (!run) {
|
|
906
907
|
res.writeHead(404);
|
|
907
908
|
res.end();
|
|
908
909
|
return true;
|
|
909
910
|
}
|
|
910
|
-
const steps = await getWorkflowRunStepsByRunId(parsed.params.id);
|
|
911
|
+
const steps = await getWorkflowRunStepsByRunId(parsed.params.id, "display");
|
|
911
912
|
getWorkflowRunRoute.respond(res, 200, { run, steps });
|
|
912
913
|
return true;
|
|
913
914
|
}
|
package/src/oauth/mcp-wrapper.ts
CHANGED
|
@@ -372,7 +372,7 @@ function formUrlEncodeComponent(value: string): string {
|
|
|
372
372
|
* at each call site means a new sensitive field cannot forget one shape.
|
|
373
373
|
*/
|
|
374
374
|
function redactSentCredentials(text: string, sent: Array<string | null | undefined>): string {
|
|
375
|
-
let out = scrubSecrets(text);
|
|
375
|
+
let out: string = scrubSecrets(text);
|
|
376
376
|
for (const value of sent) {
|
|
377
377
|
if (!value) continue;
|
|
378
378
|
for (const shape of new Set([value, formUrlEncodeComponent(value)])) {
|
|
@@ -410,17 +410,29 @@ export class ACPAdapter implements ProviderAdapter {
|
|
|
410
410
|
async createSession(config: ProviderSessionConfig): Promise<ProviderSession> {
|
|
411
411
|
const target = this.options.target ?? resolveAcpTarget(config);
|
|
412
412
|
await target.writeSystemPromptArtifact(config);
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
413
|
+
const cleanupArtifact = async () => {
|
|
414
|
+
try {
|
|
415
|
+
await target.cleanupSystemPromptArtifact?.(config);
|
|
416
|
+
} catch (err) {
|
|
417
|
+
console.warn(`\x1b[33m[acp]\x1b[0m System prompt cleanup failed: ${formatError(err)}`);
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
let proc: Bun.Subprocess<"pipe", "pipe", "pipe">;
|
|
421
|
+
try {
|
|
422
|
+
proc = registerProcessGroup(
|
|
423
|
+
Bun.spawn(target.command(config), {
|
|
424
|
+
cwd: config.cwd,
|
|
425
|
+
detached: detachedProcessGroup,
|
|
426
|
+
env: target.env(config),
|
|
427
|
+
stdin: "pipe",
|
|
428
|
+
stdout: "pipe",
|
|
429
|
+
stderr: "pipe",
|
|
430
|
+
}),
|
|
431
|
+
);
|
|
432
|
+
} catch (err) {
|
|
433
|
+
await cleanupArtifact();
|
|
434
|
+
throw err;
|
|
435
|
+
}
|
|
424
436
|
|
|
425
437
|
let session: ACPSession | null = null;
|
|
426
438
|
const preSessionEvents: ProviderEvent[] = [];
|
|
@@ -499,6 +511,7 @@ export class ACPAdapter implements ProviderAdapter {
|
|
|
499
511
|
ephemeralToken.tokenId,
|
|
500
512
|
);
|
|
501
513
|
for (const event of preSessionEvents) session.emitFromAcp(event);
|
|
514
|
+
void session.waitForCompletion().finally(cleanupArtifact);
|
|
502
515
|
return session;
|
|
503
516
|
} catch (err) {
|
|
504
517
|
// Revoke the ephemeral token before re-throwing if ACPSession has not yet
|
|
@@ -508,6 +521,7 @@ export class ACPAdapter implements ProviderAdapter {
|
|
|
508
521
|
void revokeAcpSessionToken(config.apiUrl, config.apiKey, ephemeralToken.tokenId);
|
|
509
522
|
}
|
|
510
523
|
await terminateProcessGroup(proc.pid);
|
|
524
|
+
await cleanupArtifact();
|
|
511
525
|
throw new Error(
|
|
512
526
|
`ACP target failed during startup: ${describeTargetError(target, scrubSecrets(formatError(err)))}`,
|
|
513
527
|
);
|