@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,320 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scrubber corpus: credential shapes that must redact, and real-looking
|
|
3
|
+
* non-secrets that must survive untouched.
|
|
4
|
+
*
|
|
5
|
+
* Positives are built at runtime from random bytes, never written as
|
|
6
|
+
* literals, so repo secret scanners and GitHub push protection never flag
|
|
7
|
+
* this file. Negatives are deterministic (hash-derived where they need to look
|
|
8
|
+
* random) so a false positive reproduces on every run.
|
|
9
|
+
*/
|
|
10
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
11
|
+
|
|
12
|
+
const ALNUM = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
13
|
+
const UPPER_ALNUM = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
14
|
+
const LOWER_ALNUM = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
15
|
+
const LOWER = "abcdefghijklmnopqrstuvwxyz";
|
|
16
|
+
const LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
17
|
+
const HEX = "0123456789abcdef";
|
|
18
|
+
const DIGITS = "0123456789";
|
|
19
|
+
const URLSAFE = `${ALNUM}_-`;
|
|
20
|
+
|
|
21
|
+
/** `len` random chars from `alphabet`. */
|
|
22
|
+
function rand(alphabet: string, len: number): string {
|
|
23
|
+
const bytes = randomBytes(len);
|
|
24
|
+
let out = "";
|
|
25
|
+
for (let i = 0; i < len; i++) out += alphabet[(bytes[i] as number) % alphabet.length];
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const join = (...parts: string[]) => parts.join("");
|
|
30
|
+
|
|
31
|
+
export interface CorpusPositive {
|
|
32
|
+
/** Shape name, for test titles. */
|
|
33
|
+
name: string;
|
|
34
|
+
/** The credential itself; it must not survive `scrubSecrets`. */
|
|
35
|
+
secret: string;
|
|
36
|
+
/** The credential in context, as it would reach a log. */
|
|
37
|
+
text: string;
|
|
38
|
+
/** Set when pass 5 (gitleaks) is the pass that must catch it. */
|
|
39
|
+
gitleaksRule?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function positive(
|
|
43
|
+
name: string,
|
|
44
|
+
secret: string,
|
|
45
|
+
opts: { context?: (secret: string) => string; gitleaksRule?: string } = {},
|
|
46
|
+
): CorpusPositive {
|
|
47
|
+
const context = opts.context ?? ((s: string) => `the response included ${s} in its body`);
|
|
48
|
+
return { name, secret, text: context(secret), gitleaksRule: opts.gitleaksRule };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The vendor shapes an audit probe found unredacted after the structural
|
|
53
|
+
* passes, plus the providers this repo integrates with.
|
|
54
|
+
*/
|
|
55
|
+
export function buildPositives(): CorpusPositive[] {
|
|
56
|
+
return [
|
|
57
|
+
// Audit probe shapes.
|
|
58
|
+
positive("stripe secret key", join("sk", "_live_", rand(ALNUM, 24)), {
|
|
59
|
+
gitleaksRule: "stripe-access-token",
|
|
60
|
+
}),
|
|
61
|
+
positive("stripe restricted key", join("rk", "_live_", rand(ALNUM, 24)), {
|
|
62
|
+
gitleaksRule: "stripe-access-token",
|
|
63
|
+
}),
|
|
64
|
+
positive("sendgrid", join("SG", ".", rand(URLSAFE, 22), ".", rand(URLSAFE, 43)), {
|
|
65
|
+
gitleaksRule: "sendgrid-api-token",
|
|
66
|
+
}),
|
|
67
|
+
positive("hugging face", join("hf", "_", rand(LOWER, 34)), {
|
|
68
|
+
gitleaksRule: "huggingface-access-token",
|
|
69
|
+
}),
|
|
70
|
+
positive(
|
|
71
|
+
"resend",
|
|
72
|
+
join("re", "_", rand(DIGITS, 2), rand(UPPER_ALNUM, 6), "_", rand(ALNUM, 24)),
|
|
73
|
+
),
|
|
74
|
+
positive("google oauth access token", join("ya29", ".", rand(URLSAFE, 120))),
|
|
75
|
+
positive("google refresh token", join("1/", "/0", rand(URLSAFE, 100))),
|
|
76
|
+
positive(
|
|
77
|
+
"discord webhook",
|
|
78
|
+
join("https://discord.com/api/", "webhooks/", rand(DIGITS, 18), "/", rand(URLSAFE, 68)),
|
|
79
|
+
),
|
|
80
|
+
positive(
|
|
81
|
+
"slack webhook",
|
|
82
|
+
join("https://hooks.", "slack.com/services/", rand(UPPER_ALNUM, 44)),
|
|
83
|
+
{ gitleaksRule: "slack-webhook-url" },
|
|
84
|
+
),
|
|
85
|
+
positive(
|
|
86
|
+
"telegram bot token",
|
|
87
|
+
join(rand("123456789", 1), rand(DIGITS, 9), ":", "AA", rand(URLSAFE, 33)),
|
|
88
|
+
),
|
|
89
|
+
positive(
|
|
90
|
+
"telegram bot token by keyword",
|
|
91
|
+
join(rand("123456789", 1), rand(DIGITS, 9), ":", "A", rand(LOWER_ALNUM, 34)),
|
|
92
|
+
{ context: (s) => `telegram bot: ${s}`, gitleaksRule: "telegram-bot-api-token" },
|
|
93
|
+
),
|
|
94
|
+
positive("xai", join("xai", "-", rand(ALNUM, 80))),
|
|
95
|
+
|
|
96
|
+
// Providers we integrate with.
|
|
97
|
+
positive("github classic pat", join("ghp", "_", rand(ALNUM, 36))),
|
|
98
|
+
positive("github fine-grained pat", join("github", "_pat_", rand(ALNUM, 82))),
|
|
99
|
+
positive("github oauth", join("gho", "_", rand(ALNUM, 36))),
|
|
100
|
+
positive("gitlab pat", join("glpat", "-", rand(ALNUM, 20))),
|
|
101
|
+
positive(
|
|
102
|
+
"slack bot token",
|
|
103
|
+
join("xoxb", "-", rand(DIGITS, 12), "-", rand(DIGITS, 12), "-", rand(ALNUM, 24)),
|
|
104
|
+
),
|
|
105
|
+
positive(
|
|
106
|
+
"slack app token",
|
|
107
|
+
join("xapp", "-1-", rand(UPPER_ALNUM, 11), "-", rand(DIGITS, 13), "-", rand(HEX, 64)),
|
|
108
|
+
),
|
|
109
|
+
positive("linear api key", join("lin", "_api_", rand(ALNUM, 40))),
|
|
110
|
+
positive("anthropic api key", join("sk-ant", "-api03-", rand(URLSAFE, 93), "AA")),
|
|
111
|
+
positive(
|
|
112
|
+
"openai project key",
|
|
113
|
+
join("sk-proj", "-", rand(URLSAFE, 58), "T3Blbk", "FJ", rand(URLSAFE, 58)),
|
|
114
|
+
),
|
|
115
|
+
positive("aws access key id", join("AKIA", rand("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", 16))),
|
|
116
|
+
positive("google api key", join("AIza", rand(URLSAFE, 35))),
|
|
117
|
+
positive("npm token", join("npm", "_", rand(ALNUM, 36))),
|
|
118
|
+
positive("atlassian api token", join("ATATT3", rand(URLSAFE, 186))),
|
|
119
|
+
positive("notion", join("ntn", "_", rand(DIGITS, 11), rand(ALNUM, 35)), {
|
|
120
|
+
gitleaksRule: "notion-api-token",
|
|
121
|
+
}),
|
|
122
|
+
positive("fly.io", join("fo1", "_", rand(URLSAFE, 43)), { gitleaksRule: "flyio-access-token" }),
|
|
123
|
+
positive("doppler", join("dp", ".pt.", rand(ALNUM, 43)), { gitleaksRule: "doppler-api-token" }),
|
|
124
|
+
positive("grafana service account", join("glsa", "_", rand(ALNUM, 32), "_", rand(HEX, 8)), {
|
|
125
|
+
gitleaksRule: "grafana-service-account-token",
|
|
126
|
+
}),
|
|
127
|
+
positive("postman", join("PMAK", "-", rand(HEX, 24), "-", rand(HEX, 34)), {
|
|
128
|
+
gitleaksRule: "postman-api-token",
|
|
129
|
+
}),
|
|
130
|
+
positive("shopify", join("shpat", "_", rand(HEX, 32)), {
|
|
131
|
+
gitleaksRule: "shopify-access-token",
|
|
132
|
+
}),
|
|
133
|
+
positive("digitalocean", join("dop", "_v1_", rand(HEX, 64)), {
|
|
134
|
+
gitleaksRule: "digitalocean-pat",
|
|
135
|
+
}),
|
|
136
|
+
positive("databricks", join("dapi", rand(HEX, 32)), { gitleaksRule: "databricks-api-token" }),
|
|
137
|
+
positive("pypi", join("pypi", "-AgEIcHlwaS5vcmc", rand(URLSAFE, 70)), {
|
|
138
|
+
gitleaksRule: "pypi-upload-token",
|
|
139
|
+
}),
|
|
140
|
+
positive("sentry user token", join("sntryu", "_", rand(HEX, 64)), {
|
|
141
|
+
gitleaksRule: "sentry-user-token",
|
|
142
|
+
}),
|
|
143
|
+
positive("heroku", join("HRKU", "-AA", rand(URLSAFE, 58)), {
|
|
144
|
+
gitleaksRule: "heroku-api-key-v2",
|
|
145
|
+
}),
|
|
146
|
+
positive("twilio", join("SK", rand(HEX, 32)), { gitleaksRule: "twilio-api-key" }),
|
|
147
|
+
positive("mailgun", join("key", "-", rand(HEX, 32)), {
|
|
148
|
+
context: (s) => `mailgun: ${s}`,
|
|
149
|
+
gitleaksRule: "mailgun-private-api-token",
|
|
150
|
+
}),
|
|
151
|
+
positive("new relic", join("NRAK", "-", rand(UPPER_ALNUM, 27)), {
|
|
152
|
+
context: (s) => `newrelic = ${s}`,
|
|
153
|
+
gitleaksRule: "new-relic-user-api-key",
|
|
154
|
+
}),
|
|
155
|
+
positive("vault service token", join("hvs", ".", rand(ALNUM, 96)), {
|
|
156
|
+
gitleaksRule: "vault-service-token",
|
|
157
|
+
}),
|
|
158
|
+
// gitleaks' `s\.[A-Za-z]{24}` allowlist used to fire inside a token whose
|
|
159
|
+
// first 24 characters after "hvs." are all letters (~1.5% of real tokens).
|
|
160
|
+
positive(
|
|
161
|
+
"vault service token, letters-only head",
|
|
162
|
+
join("hvs", ".", rand(LETTERS, 24), rand(ALNUM, 72)),
|
|
163
|
+
{
|
|
164
|
+
gitleaksRule: "vault-service-token",
|
|
165
|
+
},
|
|
166
|
+
),
|
|
167
|
+
positive("sourcegraph", join("sgp", "_", rand(HEX, 40)), {
|
|
168
|
+
gitleaksRule: "sourcegraph-access-token",
|
|
169
|
+
}),
|
|
170
|
+
positive("sourcegraph instance", join("sgp", "_", rand(HEX, 16), "_", rand(HEX, 40)), {
|
|
171
|
+
gitleaksRule: "sourcegraph-access-token",
|
|
172
|
+
}),
|
|
173
|
+
];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const sha1 = (seed: string) => createHash("sha1").update(seed).digest("hex");
|
|
177
|
+
const sha256 = (seed: string) => createHash("sha256").update(seed).digest("hex");
|
|
178
|
+
const uuid = (seed: string) => {
|
|
179
|
+
const h = sha256(seed);
|
|
180
|
+
return `${h.slice(0, 8)}-${h.slice(8, 12)}-4${h.slice(13, 16)}-a${h.slice(17, 20)}-${h.slice(20, 32)}`;
|
|
181
|
+
};
|
|
182
|
+
const b64 = (seed: string, bytes: number) => {
|
|
183
|
+
let buf = Buffer.alloc(0);
|
|
184
|
+
for (let i = 0; buf.length < bytes; i++) {
|
|
185
|
+
buf = Buffer.concat([buf, createHash("sha256").update(`${seed}:${i}`).digest()]);
|
|
186
|
+
}
|
|
187
|
+
return buf.subarray(0, bytes).toString("base64");
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
/** Real-looking strings an agent log carries that are not credentials. */
|
|
191
|
+
export function buildNegatives(): string[] {
|
|
192
|
+
const out: string[] = [];
|
|
193
|
+
for (let i = 0; i < 10; i++) {
|
|
194
|
+
out.push(`commit ${sha1(`commit-${i}`)}`);
|
|
195
|
+
out.push(`task ${uuid(`task-${i}`)} completed`);
|
|
196
|
+
}
|
|
197
|
+
for (let i = 0; i < 5; i++) {
|
|
198
|
+
out.push(`git checkout ${sha1(`short-${i}`).slice(0, 7)}`);
|
|
199
|
+
out.push(`image sha256:${sha256(`image-${i}`)}`);
|
|
200
|
+
out.push(`{"id":"${uuid(`json-${i}`)}","parentTaskId":"${uuid(`parent-${i}`)}"}`);
|
|
201
|
+
}
|
|
202
|
+
// Provider keywords in prose next to commit SHAs, digests and UUIDs. The
|
|
203
|
+
// keyword gate only needs the word somewhere in the text, so a rule with a
|
|
204
|
+
// bare hex or UUID branch would redact these.
|
|
205
|
+
for (const [i, kw] of [
|
|
206
|
+
"sourcegraph",
|
|
207
|
+
"heroku",
|
|
208
|
+
"twilio",
|
|
209
|
+
"mailgun",
|
|
210
|
+
"databricks",
|
|
211
|
+
"sentry",
|
|
212
|
+
"datadog",
|
|
213
|
+
"github",
|
|
214
|
+
"gitlab",
|
|
215
|
+
"linear",
|
|
216
|
+
"okta",
|
|
217
|
+
"azure",
|
|
218
|
+
].entries()) {
|
|
219
|
+
out.push(
|
|
220
|
+
`${kw} searched commit ${sha1(`kw-commit-${i}`)} for references`,
|
|
221
|
+
`${kw} deploy ${uuid(`kw-deploy-${i}`)} finished`,
|
|
222
|
+
`${kw} run ${sha256(`kw-run-${i}`)} passed`,
|
|
223
|
+
`${kw} request id ${sha256(`kw-req-${i}`).slice(0, 32)}`,
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
out.push(
|
|
227
|
+
`data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA${b64("png", 600)}`,
|
|
228
|
+
`data:image/jpeg;base64,/9j/4AAQSkZJRgABAQ${b64("jpeg", 900)}`,
|
|
229
|
+
`"thumbnail": "${b64("thumb", 300)}"`,
|
|
230
|
+
"claude-opus-5-5",
|
|
231
|
+
"claude-sonnet-5-5",
|
|
232
|
+
"claude-haiku-4-5-20251001",
|
|
233
|
+
"google/gemini-3-flash-preview",
|
|
234
|
+
"anthropic/claude-sonnet-4.5",
|
|
235
|
+
"openai/gpt-5.1-codex-mini",
|
|
236
|
+
"meta-llama/llama-3.3-70b-instruct",
|
|
237
|
+
"v1.4.0",
|
|
238
|
+
"1.62.3-beta.2+build.4711",
|
|
239
|
+
"^5.9.2",
|
|
240
|
+
"bun 1.4.0 (linux-x64)",
|
|
241
|
+
"https://github.com/desplega-ai/agent-swarm/pull/1918",
|
|
242
|
+
"https://github.com/desplega-ai/agent-swarm/actions/runs/18234567890/job/52012345678",
|
|
243
|
+
"https://docs.stripe.com/keys#obtain-api-keys",
|
|
244
|
+
"https://huggingface.co/meta-llama/Llama-3.3-70B-Instruct/resolve/main/config.json",
|
|
245
|
+
"https://api.telegram.org/bot<token>/getMe",
|
|
246
|
+
"https://hooks.slack.com/services/",
|
|
247
|
+
"https://www.npmjs.com/package/@modelcontextprotocol/sdk?activeTab=versions",
|
|
248
|
+
"http://localhost:3013/api/tasks?status=in_progress&limit=50",
|
|
249
|
+
"postgres://localhost:5432/agent_swarm",
|
|
250
|
+
"/workspace/personal/worktrees/agent-swarm-gitleaks-rules/src/utils/secret-scrubber.ts",
|
|
251
|
+
"src/utils/secret-rules.generated.ts:120:5",
|
|
252
|
+
"2026-10-07T00:36:49.123Z",
|
|
253
|
+
"Wed Oct 7 02:36:49 CEST 2026",
|
|
254
|
+
"NODE_ENV=production",
|
|
255
|
+
"LOG_LEVEL=debug",
|
|
256
|
+
"PORT=3013",
|
|
257
|
+
"MCP_BASE_URL=http://localhost:3013",
|
|
258
|
+
"HARNESS_PROVIDER=claude",
|
|
259
|
+
"export PATH=/usr/local/bin:$PATH",
|
|
260
|
+
"the monkey keyed the lock and they left",
|
|
261
|
+
"okta: enabled for SSO, see the okta admin page",
|
|
262
|
+
"jira: DES-1234 moved to In Review",
|
|
263
|
+
"the sumo wrestler won 3 bouts",
|
|
264
|
+
"telegram channel @desplega_updates has 120 members",
|
|
265
|
+
"heroku: deployed my-app-123 to the eu region",
|
|
266
|
+
"cohere: model command-r-plus-08-2024",
|
|
267
|
+
"slack: posted to #swarm-dev-2",
|
|
268
|
+
"github: desplega-ai/agent-swarm#1918",
|
|
269
|
+
"const keys = Object.keys(this.state.items);",
|
|
270
|
+
"s.length > 0 && s.startsWith('x')",
|
|
271
|
+
"pat.name === 'default'",
|
|
272
|
+
"pip install scikit-learn==1.5.2",
|
|
273
|
+
"sk-learn is not a package name",
|
|
274
|
+
"ask the risky task skill",
|
|
275
|
+
"api-docs and api-reference live under docs-site/",
|
|
276
|
+
"rk_tests_helper.ts",
|
|
277
|
+
"re_validate_payload_and_retry_failed_steps",
|
|
278
|
+
"ya29 is a prefix, not a token",
|
|
279
|
+
"1//0 is not a refresh token",
|
|
280
|
+
"xai-grok-4",
|
|
281
|
+
"the api key is stored in swarm_config, not in env",
|
|
282
|
+
"Authorization header omitted",
|
|
283
|
+
"Bearer token rotation runs nightly",
|
|
284
|
+
"password reset email sent to user@example.com",
|
|
285
|
+
"token count: 128000",
|
|
286
|
+
"secret scanning is enabled on the repo",
|
|
287
|
+
"SKU-2024-ABCDEF-001",
|
|
288
|
+
"AKIA is the prefix of an AWS access key id",
|
|
289
|
+
"AIza is the prefix of a Google API key",
|
|
290
|
+
"ghp_ prefix marks a classic GitHub PAT",
|
|
291
|
+
"C0AR967K0KZ",
|
|
292
|
+
"U0ALZGQCF96",
|
|
293
|
+
"1759797409.123456",
|
|
294
|
+
"*/15 * * * *",
|
|
295
|
+
"192.168.1.10:8080",
|
|
296
|
+
"#3b82f6",
|
|
297
|
+
"rgba(59, 130, 246, 0.5)",
|
|
298
|
+
"@modelcontextprotocol/sdk@1.20.1",
|
|
299
|
+
"node_modules/.bin/biome check --write",
|
|
300
|
+
"refs/heads/feat/scrubber-gitleaks-rules",
|
|
301
|
+
"Merge pull request #1912 from desplega-ai/fix/tasks-scrub",
|
|
302
|
+
"error: ENOENT: no such file or directory, open '/tmp/x.json'",
|
|
303
|
+
"TypeError: Cannot read properties of undefined (reading 'id')",
|
|
304
|
+
'{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"done"}]}}',
|
|
305
|
+
"WARN [heartbeat] task stalled for 300000ms, requeueing",
|
|
306
|
+
"ran 1681 tests across 412 files in 93.4s",
|
|
307
|
+
"SELECT id, name FROM agents WHERE status = 'idle' LIMIT 10",
|
|
308
|
+
"base64: SGVsbG8sIHdvcmxkIQ==",
|
|
309
|
+
"eyes on the prize",
|
|
310
|
+
"they keyed in the hex 0x7f454c46",
|
|
311
|
+
"s.a.r.l. is a company form",
|
|
312
|
+
"hvs is not a vault token without a dot",
|
|
313
|
+
"dapi is short for data api",
|
|
314
|
+
"PMAK stands for Postman API key",
|
|
315
|
+
"ntn_ prefix belongs to Notion",
|
|
316
|
+
"fo1_ prefix belongs to Fly.io",
|
|
317
|
+
"the stripe dashboard shows sk_live_ keys masked",
|
|
318
|
+
);
|
|
319
|
+
return out;
|
|
320
|
+
}
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
buildGrokConfigToml,
|
|
26
26
|
checkGrokCredentials,
|
|
27
27
|
GrokAdapter,
|
|
28
|
+
grokOpenRouterModel,
|
|
28
29
|
installedClaudePluginNames,
|
|
29
30
|
} from "../providers/grok-adapter";
|
|
30
31
|
import type { ProviderEvent, ProviderSessionConfig } from "../providers/types";
|
|
@@ -487,6 +488,7 @@ describe("GrokAdapter", () => {
|
|
|
487
488
|
'base_url = "https://openrouter.ai/api/v1"',
|
|
488
489
|
'env_key = "OPENROUTER_API_KEY"',
|
|
489
490
|
'api_backend = "chat_completions"',
|
|
491
|
+
'extra_headers = { "HTTP-Referer" = "https://agent-swarm.dev", "X-OpenRouter-Title" = "Agent Swarm", "X-OpenRouter-Categories" = "personal-agent,cloud-agent" }',
|
|
490
492
|
].join("\n"),
|
|
491
493
|
);
|
|
492
494
|
} finally {
|
|
@@ -495,6 +497,17 @@ describe("GrokAdapter", () => {
|
|
|
495
497
|
}
|
|
496
498
|
});
|
|
497
499
|
|
|
500
|
+
test("OpenRouter app attribution rides only on direct openrouter.ai models", () => {
|
|
501
|
+
const toml = (env: Record<string, string>) =>
|
|
502
|
+
buildGrokConfigToml([], grokOpenRouterModel("openrouter/acme/thing", env));
|
|
503
|
+
expect(toml({})).toContain('extra_headers = { "HTTP-Referer" = "https://agent-swarm.dev"');
|
|
504
|
+
expect(toml({ OPENROUTER_BASE_URL: "https://gateway.example/v1" })).not.toContain(
|
|
505
|
+
"extra_headers",
|
|
506
|
+
);
|
|
507
|
+
expect(toml({ OPENROUTER_APP_ATTRIBUTION: "false" })).not.toContain("extra_headers");
|
|
508
|
+
expect(buildGrokConfigToml([], null)).not.toContain("extra_headers");
|
|
509
|
+
});
|
|
510
|
+
|
|
498
511
|
test("an xai/-qualified model runs as the bare xAI id", async () => {
|
|
499
512
|
const dir = makeTempDir();
|
|
500
513
|
const grok = writeFakeGrok(dir, "ok");
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { loadModelsDevCache } from "../be/modelsdev-cache";
|
|
3
|
+
import { buildModelsDevSeedRows } from "../be/seed-pricing";
|
|
4
|
+
|
|
5
|
+
function ratesFor(provider: string, model: string) {
|
|
6
|
+
const cache = loadModelsDevCache();
|
|
7
|
+
expect(cache).not.toBeNull();
|
|
8
|
+
return Object.fromEntries(
|
|
9
|
+
buildModelsDevSeedRows(cache!)
|
|
10
|
+
.filter((row) => row.provider === provider && row.model === model)
|
|
11
|
+
.map((row) => [row.tokenClass, row.pricePerMillionUsd]),
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe("Haiku 5.5 pricing seed", () => {
|
|
16
|
+
test("seeds Anthropic rates for the claude harnesses, including the 1h cache write", () => {
|
|
17
|
+
for (const provider of ["claude", "claude-managed"]) {
|
|
18
|
+
expect(ratesFor(provider, "claude-haiku-5-5")).toEqual({
|
|
19
|
+
input: 0.1,
|
|
20
|
+
output: 0.5,
|
|
21
|
+
cached_input: 0.01,
|
|
22
|
+
cache_write: 0.125,
|
|
23
|
+
cache_write_1h: 0.2,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("seeds the same rates without a 1h class for amp and cursor", () => {
|
|
29
|
+
for (const provider of ["amp", "cursor"]) {
|
|
30
|
+
expect(ratesFor(provider, "claude-haiku-5-5")).toEqual({
|
|
31
|
+
input: 0.1,
|
|
32
|
+
output: 0.5,
|
|
33
|
+
cached_input: 0.01,
|
|
34
|
+
cache_write: 0.125,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
afterAll,
|
|
3
|
+
afterEach,
|
|
4
|
+
beforeAll,
|
|
5
|
+
beforeEach,
|
|
6
|
+
describe,
|
|
7
|
+
expect,
|
|
8
|
+
spyOn,
|
|
9
|
+
test,
|
|
10
|
+
} from "bun:test";
|
|
2
11
|
import { unlink } from "node:fs/promises";
|
|
3
12
|
import {
|
|
4
13
|
closeDb,
|
|
@@ -13,6 +22,7 @@ import {
|
|
|
13
22
|
checkHeartbeatChecklist,
|
|
14
23
|
createBootTriageTask,
|
|
15
24
|
gatherSystemStatus,
|
|
25
|
+
getBootTriageFindings,
|
|
16
26
|
isEffectivelyEmpty,
|
|
17
27
|
runRebootSweep,
|
|
18
28
|
} from "../heartbeat/heartbeat";
|
|
@@ -343,6 +353,14 @@ describe("Heartbeat Checklist", () => {
|
|
|
343
353
|
// ==========================================================================
|
|
344
354
|
|
|
345
355
|
describe("createBootTriageTask", () => {
|
|
356
|
+
// These cases cover the task body; the clean-boot skip has its own block.
|
|
357
|
+
beforeEach(() => {
|
|
358
|
+
process.env.HEARTBEAT_BOOT_TRIAGE_ALWAYS = "true";
|
|
359
|
+
});
|
|
360
|
+
afterEach(() => {
|
|
361
|
+
delete process.env.HEARTBEAT_BOOT_TRIAGE_ALWAYS;
|
|
362
|
+
});
|
|
363
|
+
|
|
346
364
|
test("skips when no lead agent registered", async () => {
|
|
347
365
|
await createAgent({ name: "worker", isLead: false, status: "idle" });
|
|
348
366
|
|
|
@@ -505,6 +523,145 @@ describe("Heartbeat Checklist", () => {
|
|
|
505
523
|
});
|
|
506
524
|
});
|
|
507
525
|
|
|
526
|
+
// ==========================================================================
|
|
527
|
+
// createBootTriageTask() — clean-boot skip
|
|
528
|
+
// ==========================================================================
|
|
529
|
+
|
|
530
|
+
describe("createBootTriageTask clean-boot skip", () => {
|
|
531
|
+
const bootTriageCount = async () =>
|
|
532
|
+
(
|
|
533
|
+
await getDbClient().get<{ n: number }>(
|
|
534
|
+
"SELECT COUNT(*) AS n FROM agent_tasks WHERE taskType = 'boot-triage'",
|
|
535
|
+
)
|
|
536
|
+
)?.n ?? 0;
|
|
537
|
+
|
|
538
|
+
beforeEach(async () => {
|
|
539
|
+
delete process.env.HEARTBEAT_BOOT_TRIAGE_ALWAYS;
|
|
540
|
+
// Reset the module-level reboot findings left by earlier tests.
|
|
541
|
+
await runRebootSweep();
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
afterEach(() => {
|
|
545
|
+
delete process.env.HEARTBEAT_BOOT_TRIAGE_ALWAYS;
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
test("clean boot creates no Lead task", async () => {
|
|
549
|
+
await createAgent({ name: "lead", isLead: true, status: "idle" });
|
|
550
|
+
await createAgent({ name: "worker", isLead: false, status: "idle" });
|
|
551
|
+
|
|
552
|
+
expect(await getBootTriageFindings()).toEqual({
|
|
553
|
+
rebootInterrupted: 0,
|
|
554
|
+
stalled: 0,
|
|
555
|
+
orphaned: 0,
|
|
556
|
+
supersededWithoutResume: 0,
|
|
557
|
+
});
|
|
558
|
+
await createBootTriageTask();
|
|
559
|
+
|
|
560
|
+
expect(await bootTriageCount()).toBe(0);
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
test("a seeded orphan creates exactly one Lead task", async () => {
|
|
564
|
+
await createAgent({ name: "lead", isLead: true, status: "idle" });
|
|
565
|
+
const offline = await createAgent({ name: "gone", isLead: false, status: "offline" });
|
|
566
|
+
await createTaskExtended("Orphaned work", { agentId: offline.id });
|
|
567
|
+
|
|
568
|
+
expect((await getBootTriageFindings()).orphaned).toBe(1);
|
|
569
|
+
await createBootTriageTask();
|
|
570
|
+
await createBootTriageTask();
|
|
571
|
+
|
|
572
|
+
expect(await bootTriageCount()).toBe(1);
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
test("reboot-interrupted work creates the Lead task", async () => {
|
|
576
|
+
await createAgent({ name: "lead", isLead: true, status: "idle" });
|
|
577
|
+
const agent = await createAgent({ name: "dead-worker", isLead: false, status: "busy" });
|
|
578
|
+
const task = await createTaskExtended("Interrupted work", { agentId: agent.id });
|
|
579
|
+
await startTask(task.id);
|
|
580
|
+
await getDbClient().run("UPDATE agent_tasks SET lastUpdatedAt = ? WHERE id = ?", [
|
|
581
|
+
new Date(Date.now() - 1000).toISOString(),
|
|
582
|
+
task.id,
|
|
583
|
+
]);
|
|
584
|
+
await runRebootSweep();
|
|
585
|
+
|
|
586
|
+
expect((await getBootTriageFindings()).rebootInterrupted).toBe(1);
|
|
587
|
+
await createBootTriageTask();
|
|
588
|
+
|
|
589
|
+
expect(await bootTriageCount()).toBe(1);
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
test("superseded task without a resume creates the Lead task", async () => {
|
|
593
|
+
await createAgent({ name: "lead", isLead: true, status: "idle" });
|
|
594
|
+
const worker = await createAgent({ name: "worker", isLead: false, status: "idle" });
|
|
595
|
+
const task = await createTaskExtended("Lost resume", { agentId: worker.id });
|
|
596
|
+
await getDbClient().run(
|
|
597
|
+
"UPDATE agent_tasks SET status = 'superseded', finishedAt = ? WHERE id = ?",
|
|
598
|
+
[new Date(Date.now() - 5 * 60 * 1000).toISOString(), task.id],
|
|
599
|
+
);
|
|
600
|
+
|
|
601
|
+
expect((await getBootTriageFindings()).supersededWithoutResume).toBe(1);
|
|
602
|
+
await createBootTriageTask();
|
|
603
|
+
|
|
604
|
+
expect(await bootTriageCount()).toBe(1);
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
test("stalled-only work creates the Lead task", async () => {
|
|
608
|
+
await createAgent({ name: "lead", isLead: true, status: "idle" });
|
|
609
|
+
const agent = await createAgent({ name: "stuck-worker", isLead: false, status: "busy" });
|
|
610
|
+
const task = await createTaskExtended("Stalled work", { agentId: agent.id });
|
|
611
|
+
await startTask(task.id);
|
|
612
|
+
await getDbClient().run("UPDATE agent_tasks SET lastUpdatedAt = ? WHERE id = ?", [
|
|
613
|
+
new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(),
|
|
614
|
+
task.id,
|
|
615
|
+
]);
|
|
616
|
+
|
|
617
|
+
expect(await getBootTriageFindings()).toEqual({
|
|
618
|
+
rebootInterrupted: 0,
|
|
619
|
+
stalled: 1,
|
|
620
|
+
orphaned: 0,
|
|
621
|
+
supersededWithoutResume: 0,
|
|
622
|
+
});
|
|
623
|
+
await createBootTriageTask();
|
|
624
|
+
|
|
625
|
+
expect(await bootTriageCount()).toBe(1);
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
test("a persistent reader error still creates exactly one Lead task", async () => {
|
|
629
|
+
await createAgent({ name: "lead", isLead: true, status: "idle" });
|
|
630
|
+
const client = getDbClient();
|
|
631
|
+
const query = client.query.bind(client);
|
|
632
|
+
const failStalled = spyOn(client, "query").mockImplementation((sql, params) => {
|
|
633
|
+
if (sql.includes("status = 'in_progress' AND lastUpdatedAt < ?")) {
|
|
634
|
+
throw new Error("stalled read unavailable");
|
|
635
|
+
}
|
|
636
|
+
return query(sql, params);
|
|
637
|
+
});
|
|
638
|
+
const errors = spyOn(console, "error").mockImplementation(() => {});
|
|
639
|
+
try {
|
|
640
|
+
await createBootTriageTask();
|
|
641
|
+
await createBootTriageTask();
|
|
642
|
+
} finally {
|
|
643
|
+
failStalled.mockRestore();
|
|
644
|
+
errors.mockRestore();
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
expect(await bootTriageCount()).toBe(1);
|
|
648
|
+
const task = await getDbClient().get<{ task: string }>(
|
|
649
|
+
"SELECT task FROM agent_tasks WHERE taskType = 'boot-triage'",
|
|
650
|
+
);
|
|
651
|
+
expect(task?.task).toContain("System status unavailable");
|
|
652
|
+
expect(task?.task).toContain("stalled read unavailable");
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
test("HEARTBEAT_BOOT_TRIAGE_ALWAYS creates the task on a clean boot", async () => {
|
|
656
|
+
await createAgent({ name: "lead", isLead: true, status: "idle" });
|
|
657
|
+
process.env.HEARTBEAT_BOOT_TRIAGE_ALWAYS = "true";
|
|
658
|
+
|
|
659
|
+
await createBootTriageTask();
|
|
660
|
+
|
|
661
|
+
expect(await bootTriageCount()).toBe(1);
|
|
662
|
+
});
|
|
663
|
+
});
|
|
664
|
+
|
|
508
665
|
// ==========================================================================
|
|
509
666
|
// gatherSystemStatus() — boot triage sections
|
|
510
667
|
// ==========================================================================
|
|
@@ -580,6 +580,50 @@ describe("onboarding memory probe", () => {
|
|
|
580
580
|
});
|
|
581
581
|
});
|
|
582
582
|
|
|
583
|
+
test("only an openrouter.ai endpoint gets OpenRouter app attribution", async () => {
|
|
584
|
+
const referers: Record<string, string | null> = {};
|
|
585
|
+
const fake = Bun.serve({
|
|
586
|
+
hostname: "127.0.0.1",
|
|
587
|
+
port: 0,
|
|
588
|
+
fetch(req) {
|
|
589
|
+
referers.local = req.headers.get("http-referer");
|
|
590
|
+
return embeddingResponse(512);
|
|
591
|
+
},
|
|
592
|
+
});
|
|
593
|
+
const origFetch = globalThis.fetch;
|
|
594
|
+
// The probe builds a fresh OpenAI client per call, which picks up this stub.
|
|
595
|
+
globalThis.fetch = (async (input: string | URL | Request, init?: RequestInit) => {
|
|
596
|
+
const req = new Request(input, init);
|
|
597
|
+
if (new URL(req.url).hostname !== "openrouter.ai") return origFetch(input, init);
|
|
598
|
+
referers[process.env.OPENROUTER_APP_ATTRIBUTION ? "optedOut" : "openrouter"] =
|
|
599
|
+
req.headers.get("http-referer");
|
|
600
|
+
return embeddingResponse(512);
|
|
601
|
+
}) as typeof fetch;
|
|
602
|
+
const probe = (body: Record<string, unknown>) =>
|
|
603
|
+
request("/api/onboarding/memory", {
|
|
604
|
+
method: "POST",
|
|
605
|
+
body: { model: "test-embedding-model", apiKey: "example-key", ...body },
|
|
606
|
+
});
|
|
607
|
+
try {
|
|
608
|
+
expect((await probe({ preset: "openrouter" })).status).toBe(200);
|
|
609
|
+
process.env.OPENROUTER_APP_ATTRIBUTION = "false";
|
|
610
|
+
expect((await probe({ preset: "openrouter" })).status).toBe(200);
|
|
611
|
+
delete process.env.OPENROUTER_APP_ATTRIBUTION;
|
|
612
|
+
expect(
|
|
613
|
+
(await probe({ preset: "custom", baseUrl: `http://127.0.0.1:${fake.port}/v1` })).status,
|
|
614
|
+
).toBe(200);
|
|
615
|
+
} finally {
|
|
616
|
+
globalThis.fetch = origFetch;
|
|
617
|
+
delete process.env.OPENROUTER_APP_ATTRIBUTION;
|
|
618
|
+
fake.stop(true);
|
|
619
|
+
}
|
|
620
|
+
expect(referers).toEqual({
|
|
621
|
+
openrouter: "https://agent-swarm.dev",
|
|
622
|
+
optedOut: null,
|
|
623
|
+
local: null,
|
|
624
|
+
});
|
|
625
|
+
});
|
|
626
|
+
|
|
583
627
|
test("classifies a local 401 embeddings response as auth", async () => {
|
|
584
628
|
const fake = startEmbeddingServer(() =>
|
|
585
629
|
Response.json(
|