@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
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import {
|
|
2
|
+
afterAll,
|
|
3
|
+
afterEach,
|
|
4
|
+
beforeAll,
|
|
5
|
+
beforeEach,
|
|
6
|
+
describe,
|
|
7
|
+
expect,
|
|
8
|
+
spyOn,
|
|
9
|
+
test,
|
|
10
|
+
} from "bun:test";
|
|
11
|
+
import { unlink } from "node:fs/promises";
|
|
12
|
+
import {
|
|
13
|
+
createServer as createHttpServer,
|
|
14
|
+
type IncomingMessage,
|
|
15
|
+
type Server,
|
|
16
|
+
type ServerResponse,
|
|
17
|
+
} from "node:http";
|
|
18
|
+
import type { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
19
|
+
import { closeDb, createAgent, getDbClient, initDb, upsertSwarmConfig } from "../be/db";
|
|
20
|
+
import { validateConfigValue } from "../be/swarm-config-guard";
|
|
21
|
+
import {
|
|
22
|
+
_resetAutoReloadForTests,
|
|
23
|
+
flushPendingIntegrationsReload,
|
|
24
|
+
loadGlobalConfigsIntoEnv,
|
|
25
|
+
} from "../http/core";
|
|
26
|
+
import { handleMcp } from "../http/mcp";
|
|
27
|
+
import { createServer } from "../server";
|
|
28
|
+
import { parseEnabledTools } from "../utils/enabled-tools";
|
|
29
|
+
import { listenOnFreePort } from "./test-net";
|
|
30
|
+
|
|
31
|
+
const TEST_DB_PATH = "./test-enabled-tools.sqlite";
|
|
32
|
+
|
|
33
|
+
async function removeDbFiles(path: string): Promise<void> {
|
|
34
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
35
|
+
try {
|
|
36
|
+
await unlink(path + suffix);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function toolNames(opts: Parameters<typeof createServer>[0]): Promise<string[]> {
|
|
44
|
+
const server = (await createServer(opts)) as unknown as {
|
|
45
|
+
_registeredTools: Record<string, unknown>;
|
|
46
|
+
};
|
|
47
|
+
return Object.keys(server._registeredTools).sort();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function parseMcpPayload(text: string): unknown {
|
|
51
|
+
const trimmed = text.trim();
|
|
52
|
+
if (trimmed.startsWith("event:") || trimmed.startsWith("data:")) {
|
|
53
|
+
return JSON.parse(
|
|
54
|
+
trimmed
|
|
55
|
+
.split("\n")
|
|
56
|
+
.filter((line) => line.startsWith("data:"))
|
|
57
|
+
.map((line) => line.slice("data:".length).trim())
|
|
58
|
+
.join("\n"),
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
return JSON.parse(trimmed);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let httpServer: Server;
|
|
65
|
+
let baseUrl: string;
|
|
66
|
+
let savedEnv: string | undefined;
|
|
67
|
+
let savedCapabilities: string | undefined;
|
|
68
|
+
|
|
69
|
+
async function mcpPost(agentId: string, body: Record<string, unknown>, sessionId?: string) {
|
|
70
|
+
const headers: Record<string, string> = {
|
|
71
|
+
Accept: "application/json, text/event-stream",
|
|
72
|
+
"Content-Type": "application/json",
|
|
73
|
+
"X-Agent-ID": agentId,
|
|
74
|
+
};
|
|
75
|
+
if (sessionId) headers["mcp-session-id"] = sessionId;
|
|
76
|
+
const response = await fetch(`${baseUrl}/mcp`, {
|
|
77
|
+
method: "POST",
|
|
78
|
+
headers,
|
|
79
|
+
body: JSON.stringify(body),
|
|
80
|
+
});
|
|
81
|
+
const text = await response.text();
|
|
82
|
+
return { response, payload: text ? parseMcpPayload(text) : null };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function listSessionTools(agentId: string): Promise<string[]> {
|
|
86
|
+
const init = await mcpPost(agentId, {
|
|
87
|
+
jsonrpc: "2.0",
|
|
88
|
+
id: 1,
|
|
89
|
+
method: "initialize",
|
|
90
|
+
params: {
|
|
91
|
+
protocolVersion: "2024-11-05",
|
|
92
|
+
clientInfo: { name: "enabled-tools", version: "1" },
|
|
93
|
+
capabilities: {},
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
const sessionId = init.response.headers.get("mcp-session-id");
|
|
97
|
+
if (!sessionId) throw new Error("missing MCP session ID");
|
|
98
|
+
await mcpPost(agentId, { jsonrpc: "2.0", method: "notifications/initialized" }, sessionId);
|
|
99
|
+
const listed = await mcpPost(
|
|
100
|
+
agentId,
|
|
101
|
+
{ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} },
|
|
102
|
+
sessionId,
|
|
103
|
+
);
|
|
104
|
+
return (listed.payload as { result: { tools: Array<{ name: string }> } }).result.tools
|
|
105
|
+
.map((tool) => tool.name)
|
|
106
|
+
.sort();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
beforeAll(async () => {
|
|
110
|
+
// Drain any config reload a previous suite left queued (see
|
|
111
|
+
// scripts-only-gating.test.ts) before touching the global config rows.
|
|
112
|
+
const originalSlackDisable = process.env.SLACK_DISABLE;
|
|
113
|
+
process.env.SLACK_DISABLE = "true";
|
|
114
|
+
await flushPendingIntegrationsReload();
|
|
115
|
+
_resetAutoReloadForTests();
|
|
116
|
+
if (originalSlackDisable === undefined) delete process.env.SLACK_DISABLE;
|
|
117
|
+
else process.env.SLACK_DISABLE = originalSlackDisable;
|
|
118
|
+
|
|
119
|
+
await removeDbFiles(TEST_DB_PATH);
|
|
120
|
+
closeDb();
|
|
121
|
+
initDb(TEST_DB_PATH);
|
|
122
|
+
|
|
123
|
+
const transports: Record<string, StreamableHTTPServerTransport> = {};
|
|
124
|
+
const sessionAgents: Record<string, string> = {};
|
|
125
|
+
httpServer = createHttpServer(async (req: IncomingMessage, res: ServerResponse) => {
|
|
126
|
+
if (await handleMcp(req, res, transports, {}, sessionAgents)) return;
|
|
127
|
+
res.writeHead(404);
|
|
128
|
+
res.end("Not Found");
|
|
129
|
+
});
|
|
130
|
+
baseUrl = `http://127.0.0.1:${await listenOnFreePort(httpServer, "127.0.0.1")}`;
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
afterAll(async () => {
|
|
134
|
+
await new Promise<void>((resolve) => httpServer.close(() => resolve()));
|
|
135
|
+
closeDb();
|
|
136
|
+
await removeDbFiles(TEST_DB_PATH);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
beforeEach(() => {
|
|
140
|
+
savedEnv = process.env.SWARM_ENABLED_TOOLS;
|
|
141
|
+
delete process.env.SWARM_ENABLED_TOOLS;
|
|
142
|
+
// Pin the default capability set; worker containers export their own.
|
|
143
|
+
savedCapabilities = process.env.CAPABILITIES;
|
|
144
|
+
delete process.env.CAPABILITIES;
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
afterEach(async () => {
|
|
148
|
+
await getDbClient().run("DELETE FROM swarm_config WHERE key = 'SWARM_ENABLED_TOOLS'");
|
|
149
|
+
// Un-inject the global row so it cannot leak into later suites.
|
|
150
|
+
await loadGlobalConfigsIntoEnv(true);
|
|
151
|
+
if (savedEnv === undefined) delete process.env.SWARM_ENABLED_TOOLS;
|
|
152
|
+
else process.env.SWARM_ENABLED_TOOLS = savedEnv;
|
|
153
|
+
if (savedCapabilities === undefined) delete process.env.CAPABILITIES;
|
|
154
|
+
else process.env.CAPABILITIES = savedCapabilities;
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe("parseEnabledTools", () => {
|
|
158
|
+
test.each([
|
|
159
|
+
[undefined, undefined],
|
|
160
|
+
["", undefined],
|
|
161
|
+
[" ", undefined],
|
|
162
|
+
[" , ,", undefined],
|
|
163
|
+
["[]", undefined],
|
|
164
|
+
["get-tasks, store-progress", ["get-tasks", "store-progress"]],
|
|
165
|
+
["get-tasks,get-tasks,", ["get-tasks"]],
|
|
166
|
+
['["get-tasks", " store-progress "]', ["get-tasks", "store-progress"]],
|
|
167
|
+
] as Array<[string | undefined, string[] | undefined]>)("parses %#", (value, expected) => {
|
|
168
|
+
expect(parseEnabledTools(value)).toEqual(expected);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("rejects a JSON value that is not an array of strings", () => {
|
|
172
|
+
expect(() => parseEnabledTools("[1, 2]")).toThrow();
|
|
173
|
+
expect(() => parseEnabledTools("[not json")).toThrow();
|
|
174
|
+
expect(validateConfigValue("SWARM_ENABLED_TOOLS", "[1]")).toContain("SWARM_ENABLED_TOOLS");
|
|
175
|
+
expect(validateConfigValue("SWARM_ENABLED_TOOLS", "get-tasks,nope")).toBeNull();
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe("SWARM_ENABLED_TOOLS on the MCP server", () => {
|
|
180
|
+
test("unset keeps the capability-driven surface", async () => {
|
|
181
|
+
const unset = await toolNames({});
|
|
182
|
+
expect(unset).toContain("send-task");
|
|
183
|
+
// messaging is a default-disabled capability
|
|
184
|
+
expect(unset).not.toContain("post-message");
|
|
185
|
+
expect(await toolNames({ enabledTools: undefined })).toEqual(unset);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("set registers exactly the listed tools, ignoring capabilities", async () => {
|
|
189
|
+
expect(await toolNames({ enabledTools: "get-tasks, post-message" })).toEqual([
|
|
190
|
+
"get-tasks",
|
|
191
|
+
"post-message",
|
|
192
|
+
]);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("reads the deployment env when no session value is passed", async () => {
|
|
196
|
+
process.env.SWARM_ENABLED_TOOLS = "store-progress";
|
|
197
|
+
expect(await toolNames({})).toEqual(["store-progress"]);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("overrides scripts-only mode", async () => {
|
|
201
|
+
expect(await toolNames({ scriptsOnly: true, enabledTools: "get-tasks" })).toEqual([
|
|
202
|
+
"get-tasks",
|
|
203
|
+
]);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("does not affect the full-surface bridge server", async () => {
|
|
207
|
+
const tools = await toolNames({ fullSurface: true, enabledTools: "get-tasks" });
|
|
208
|
+
expect(tools).toContain("post-message");
|
|
209
|
+
expect(tools.length).toBeGreaterThan(1);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("warns once on unknown names and keeps the known ones", async () => {
|
|
213
|
+
const warn = spyOn(console, "warn").mockImplementation(() => {});
|
|
214
|
+
try {
|
|
215
|
+
const value = "get-tasks, no-such-tool-a1, no-such-tool-b2";
|
|
216
|
+
expect(await toolNames({ enabledTools: value })).toEqual(["get-tasks"]);
|
|
217
|
+
await toolNames({ enabledTools: value });
|
|
218
|
+
const messages = warn.mock.calls.map((call) => String(call[0]));
|
|
219
|
+
const unknown = messages.filter((m) => m.includes("unknown tools"));
|
|
220
|
+
expect(unknown).toEqual([
|
|
221
|
+
"[MCP] SWARM_ENABLED_TOOLS names unknown tools, ignoring them: no-such-tool-a1, no-such-tool-b2",
|
|
222
|
+
]);
|
|
223
|
+
} finally {
|
|
224
|
+
warn.mockRestore();
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test("an empty value logs and behaves as unset", async () => {
|
|
229
|
+
const unset = await toolNames({});
|
|
230
|
+
const warn = spyOn(console, "warn").mockImplementation(() => {});
|
|
231
|
+
try {
|
|
232
|
+
expect(await toolNames({ enabledTools: " , " })).toEqual(unset);
|
|
233
|
+
expect(await toolNames({ enabledTools: "" })).toEqual(unset);
|
|
234
|
+
expect(
|
|
235
|
+
warn.mock.calls.some((call) => String(call[0]).includes("lists no tools; ignoring it")),
|
|
236
|
+
).toBe(true);
|
|
237
|
+
} finally {
|
|
238
|
+
warn.mockRestore();
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("an agent-scoped value beats the swarm-wide one, per session", async () => {
|
|
243
|
+
const scoped = await createAgent({ name: "allowlist-agent", isLead: false, status: "idle" });
|
|
244
|
+
const neighbor = await createAgent({
|
|
245
|
+
name: "allowlist-neighbor",
|
|
246
|
+
isLead: false,
|
|
247
|
+
status: "idle",
|
|
248
|
+
});
|
|
249
|
+
await upsertSwarmConfig({ scope: "global", key: "SWARM_ENABLED_TOOLS", value: "get-tasks" });
|
|
250
|
+
await upsertSwarmConfig({
|
|
251
|
+
scope: "agent",
|
|
252
|
+
scopeId: scoped.id,
|
|
253
|
+
key: "SWARM_ENABLED_TOOLS",
|
|
254
|
+
value: "store-progress,post-message",
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
expect(await listSessionTools(scoped.id)).toEqual(["post-message", "store-progress"]);
|
|
258
|
+
expect(await listSessionTools(neighbor.id)).toEqual(["get-tasks"]);
|
|
259
|
+
|
|
260
|
+
// Reloadable: a config change applies on the next session, no restart.
|
|
261
|
+
await upsertSwarmConfig({
|
|
262
|
+
scope: "agent",
|
|
263
|
+
scopeId: scoped.id,
|
|
264
|
+
key: "SWARM_ENABLED_TOOLS",
|
|
265
|
+
value: "store-progress",
|
|
266
|
+
});
|
|
267
|
+
expect(await listSessionTools(scoped.id)).toEqual(["store-progress"]);
|
|
268
|
+
});
|
|
269
|
+
});
|
|
@@ -44,6 +44,7 @@ const STUB_BINARIES = [
|
|
|
44
44
|
"codex",
|
|
45
45
|
"opencode",
|
|
46
46
|
"gemini",
|
|
47
|
+
"copilot",
|
|
47
48
|
"dsh",
|
|
48
49
|
"acp-target-stub",
|
|
49
50
|
"acp target stub",
|
|
@@ -142,6 +143,35 @@ describe("docker-entrypoint.sh: acp target binary verification", () => {
|
|
|
142
143
|
CHILD_PROCESS_TEST_BUDGET_MS,
|
|
143
144
|
);
|
|
144
145
|
|
|
146
|
+
test(
|
|
147
|
+
"resolves the copilot catalog target to the copilot binary",
|
|
148
|
+
async () => {
|
|
149
|
+
const result = await runChain({ HARNESS_PROVIDER: "acp", ACP_TARGET: "copilot" });
|
|
150
|
+
|
|
151
|
+
expect(result.exitCode).toBe(0);
|
|
152
|
+
expect(result.stdout).toContain("ACP target: ");
|
|
153
|
+
expect(result.stdout).toContain("ACP_TARGET='copilot'");
|
|
154
|
+
},
|
|
155
|
+
CHILD_PROCESS_TEST_BUDGET_MS,
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
test(
|
|
159
|
+
"fails boot with an install hint when the copilot binary is missing",
|
|
160
|
+
async () => {
|
|
161
|
+
// No stub dir on PATH, so `copilot` cannot resolve.
|
|
162
|
+
const result = await runChain({
|
|
163
|
+
HARNESS_PROVIDER: "acp",
|
|
164
|
+
ACP_TARGET: "copilot",
|
|
165
|
+
PATH: "/usr/bin:/bin",
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
expect(result.exitCode).not.toBe(0);
|
|
169
|
+
expect(result.stdout).toContain("FATAL: ACP target binary not found: 'copilot'");
|
|
170
|
+
expect(result.stdout).toContain("npm install -g @github/copilot");
|
|
171
|
+
},
|
|
172
|
+
CHILD_PROCESS_TEST_BUDGET_MS,
|
|
173
|
+
);
|
|
174
|
+
|
|
145
175
|
test(
|
|
146
176
|
"resolves a custom target supplied as a command string with argv",
|
|
147
177
|
async () => {
|
|
@@ -191,7 +221,7 @@ describe("docker-entrypoint.sh: acp target binary verification", () => {
|
|
|
191
221
|
|
|
192
222
|
expect(result.exitCode).not.toBe(0);
|
|
193
223
|
expect(result.stdout).toContain(
|
|
194
|
-
"FATAL: unsupported ACP target 'gemini-cli'. Supported targets: opencode, gemini, custom.",
|
|
224
|
+
"FATAL: unsupported ACP target 'gemini-cli'. Supported targets: opencode, gemini, copilot, custom.",
|
|
195
225
|
);
|
|
196
226
|
},
|
|
197
227
|
CHILD_PROCESS_TEST_BUDGET_MS,
|
|
@@ -119,6 +119,7 @@ describe("docker-entrypoint.sh: codex_oauth boot-seed jq transform", () => {
|
|
|
119
119
|
test(
|
|
120
120
|
"blanks refresh_token for a flat {access,refresh,accountId,expires} input",
|
|
121
121
|
async () => {
|
|
122
|
+
const before = Math.floor(Date.now() / 1000) * 1000;
|
|
122
123
|
const result = (await runJqFilter(filter, {
|
|
123
124
|
access: "example-access-tok",
|
|
124
125
|
refresh: "example-live-refresh-tok",
|
|
@@ -126,6 +127,8 @@ describe("docker-entrypoint.sh: codex_oauth boot-seed jq transform", () => {
|
|
|
126
127
|
expires: 1_800_000_000_000,
|
|
127
128
|
})) as {
|
|
128
129
|
auth_mode: string;
|
|
130
|
+
last_refresh: string;
|
|
131
|
+
expires: number;
|
|
129
132
|
tokens: {
|
|
130
133
|
id_token: string;
|
|
131
134
|
access_token: string;
|
|
@@ -139,6 +142,9 @@ describe("docker-entrypoint.sh: codex_oauth boot-seed jq transform", () => {
|
|
|
139
142
|
expect(result.tokens.id_token).toBe("example-access-tok");
|
|
140
143
|
expect(result.tokens.access_token).toBe("example-access-tok");
|
|
141
144
|
expect(result.tokens.account_id).toBe("acct-456");
|
|
145
|
+
expect(Date.parse(result.last_refresh)).toBeGreaterThanOrEqual(before);
|
|
146
|
+
expect(Date.parse(result.last_refresh)).toBeLessThanOrEqual(Date.now());
|
|
147
|
+
expect(result.expires).toBe(1_800_000_000_000);
|
|
142
148
|
},
|
|
143
149
|
CHILD_PROCESS_TEST_BUDGET_MS,
|
|
144
150
|
);
|
|
@@ -182,6 +188,7 @@ describe("docker-entrypoint.sh: codex_oauth boot-seed jq transform", () => {
|
|
|
182
188
|
test(
|
|
183
189
|
"preserves refresh_token (from the flat `refresh` field) for a flat input on the standalone path",
|
|
184
190
|
async () => {
|
|
191
|
+
const before = Math.floor(Date.now() / 1000) * 1000;
|
|
185
192
|
const result = (await runJqFilter(
|
|
186
193
|
filter,
|
|
187
194
|
{
|
|
@@ -193,9 +200,14 @@ describe("docker-entrypoint.sh: codex_oauth boot-seed jq transform", () => {
|
|
|
193
200
|
true,
|
|
194
201
|
)) as {
|
|
195
202
|
tokens: { refresh_token: string };
|
|
203
|
+
last_refresh: string;
|
|
204
|
+
expires: number;
|
|
196
205
|
};
|
|
197
206
|
|
|
198
207
|
expect(result.tokens.refresh_token).toBe("example-live-refresh-tok");
|
|
208
|
+
expect(Date.parse(result.last_refresh)).toBeGreaterThanOrEqual(before);
|
|
209
|
+
expect(Date.parse(result.last_refresh)).toBeLessThanOrEqual(Date.now());
|
|
210
|
+
expect(result.expires).toBe(1_800_000_000_000);
|
|
199
211
|
},
|
|
200
212
|
CHILD_PROCESS_TEST_BUDGET_MS,
|
|
201
213
|
);
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
2
|
+
import { unlink } from "node:fs/promises";
|
|
3
|
+
import { closeDb, getDbClient, initDb } from "../be/db";
|
|
4
|
+
import { createEvent, createEventsBatch } from "../be/events";
|
|
5
|
+
import { randomToken, syntheticSecret } from "./synthetic-secret-helpers";
|
|
6
|
+
|
|
7
|
+
const TEST_DB_PATH = "./test-events-scrub.sqlite";
|
|
8
|
+
|
|
9
|
+
beforeAll(async () => {
|
|
10
|
+
try {
|
|
11
|
+
await unlink(TEST_DB_PATH);
|
|
12
|
+
} catch {}
|
|
13
|
+
initDb(TEST_DB_PATH);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterAll(async () => {
|
|
17
|
+
closeDb();
|
|
18
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
19
|
+
try {
|
|
20
|
+
await unlink(`${TEST_DB_PATH}${suffix}`);
|
|
21
|
+
} catch {}
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
async function readData(sessionId: string): Promise<string[]> {
|
|
26
|
+
const rows = await getDbClient().query<{ data: string | null }>(
|
|
27
|
+
"SELECT data FROM events WHERE sessionId = ? ORDER BY rowid",
|
|
28
|
+
[sessionId],
|
|
29
|
+
);
|
|
30
|
+
return rows.map((r) => r.data ?? "");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe("events.data scrub at write", () => {
|
|
34
|
+
test("createEvent redacts a secret in the payload and keeps valid JSON", async () => {
|
|
35
|
+
const pat = ["ghp", randomToken(36)].join("_");
|
|
36
|
+
const sessionId = crypto.randomUUID();
|
|
37
|
+
await createEvent({
|
|
38
|
+
category: "session",
|
|
39
|
+
event: "session.start",
|
|
40
|
+
source: "api",
|
|
41
|
+
sessionId,
|
|
42
|
+
data: { note: "cloned repo with token", output: `remote: ${pat} ok`, count: 3 },
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const [raw] = await readData(sessionId);
|
|
46
|
+
expect(raw).toBeDefined();
|
|
47
|
+
expect(raw).not.toContain(pat);
|
|
48
|
+
expect(raw).toContain("[REDACTED:");
|
|
49
|
+
const parsed = JSON.parse(raw as string);
|
|
50
|
+
expect(parsed.note).toBe("cloned repo with token");
|
|
51
|
+
expect(parsed.output).toStartWith("remote: ");
|
|
52
|
+
expect(parsed.output).toEndWith(" ok");
|
|
53
|
+
expect(parsed.count).toBe(3);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("createEventsBatch redacts every row and keeps valid JSON", async () => {
|
|
57
|
+
// Registered per test: the preload clears volatile secrets after each one.
|
|
58
|
+
const known = syntheticSecret("evscrub");
|
|
59
|
+
const sessionId = crypto.randomUUID();
|
|
60
|
+
const count = await createEventsBatch([
|
|
61
|
+
{
|
|
62
|
+
category: "tool",
|
|
63
|
+
event: "tool.start",
|
|
64
|
+
source: "worker",
|
|
65
|
+
sessionId,
|
|
66
|
+
data: { tool: "Bash", input: `echo ${known.value}` },
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
category: "tool",
|
|
70
|
+
event: "tool.end",
|
|
71
|
+
source: "worker",
|
|
72
|
+
sessionId,
|
|
73
|
+
data: { tool: "Bash", nested: { lines: ["first", `leaked ${known.value} here`] } },
|
|
74
|
+
},
|
|
75
|
+
]);
|
|
76
|
+
expect(count).toBe(2);
|
|
77
|
+
|
|
78
|
+
const rows = await readData(sessionId);
|
|
79
|
+
expect(rows).toHaveLength(2);
|
|
80
|
+
for (const raw of rows) {
|
|
81
|
+
expect(raw).not.toContain(known.value);
|
|
82
|
+
expect(raw).toContain(`[REDACTED:${known.name}]`);
|
|
83
|
+
expect(JSON.parse(raw).tool).toBe("Bash");
|
|
84
|
+
}
|
|
85
|
+
expect(JSON.parse(rows[0] as string).input).toStartWith("echo ");
|
|
86
|
+
expect(JSON.parse(rows[1] as string).nested.lines[0]).toBe("first");
|
|
87
|
+
});
|
|
88
|
+
});
|