@tangle-network/browser-agent-driver 0.33.3 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -1
- package/discovery/llms.txt +27 -0
- package/discovery/tangle-agent.json +36 -0
- package/dist/brain/action-parse.d.ts +16 -0
- package/dist/brain/action-parse.d.ts.map +1 -0
- package/dist/brain/action-parse.js +191 -0
- package/dist/brain/action-parse.js.map +1 -0
- package/dist/brain/decide.d.ts +58 -0
- package/dist/brain/decide.d.ts.map +1 -0
- package/dist/brain/decide.js +418 -0
- package/dist/brain/decide.js.map +1 -0
- package/dist/brain/history-compact.d.ts +19 -0
- package/dist/brain/history-compact.d.ts.map +1 -0
- package/dist/brain/history-compact.js +153 -0
- package/dist/brain/history-compact.js.map +1 -0
- package/dist/brain/index.d.ts +78 -152
- package/dist/brain/index.d.ts.map +1 -1
- package/dist/brain/index.js +62 -2268
- package/dist/brain/index.js.map +1 -1
- package/dist/brain/model-client.d.ts +101 -0
- package/dist/brain/model-client.d.ts.map +1 -0
- package/dist/brain/model-client.js +391 -0
- package/dist/brain/model-client.js.map +1 -0
- package/dist/brain/plan.d.ts +57 -0
- package/dist/brain/plan.d.ts.map +1 -0
- package/dist/brain/plan.js +194 -0
- package/dist/brain/plan.js.map +1 -0
- package/dist/brain/prompts.d.ts +37 -0
- package/dist/brain/prompts.d.ts.map +1 -0
- package/dist/brain/prompts.js +455 -0
- package/dist/brain/prompts.js.map +1 -0
- package/dist/brain/provider-fetch.d.ts +26 -0
- package/dist/brain/provider-fetch.d.ts.map +1 -0
- package/dist/brain/provider-fetch.js +45 -0
- package/dist/brain/provider-fetch.js.map +1 -0
- package/dist/brain/snapshot-budget.d.ts +20 -0
- package/dist/brain/snapshot-budget.d.ts.map +1 -0
- package/dist/brain/snapshot-budget.js +202 -0
- package/dist/brain/snapshot-budget.js.map +1 -0
- package/dist/brain/system-prompt.d.ts +67 -0
- package/dist/brain/system-prompt.d.ts.map +1 -0
- package/dist/brain/system-prompt.js +128 -0
- package/dist/brain/system-prompt.js.map +1 -0
- package/dist/brain/tasks/design-audit.d.ts +33 -0
- package/dist/brain/tasks/design-audit.d.ts.map +1 -0
- package/dist/brain/tasks/design-audit.js +100 -0
- package/dist/brain/tasks/design-audit.js.map +1 -0
- package/dist/brain/tasks/evaluate.d.ts +25 -0
- package/dist/brain/tasks/evaluate.d.ts.map +1 -0
- package/dist/brain/tasks/evaluate.js +55 -0
- package/dist/brain/tasks/evaluate.js.map +1 -0
- package/dist/brain/tasks/goal-verification.d.ts +32 -0
- package/dist/brain/tasks/goal-verification.d.ts.map +1 -0
- package/dist/brain/tasks/goal-verification.js +68 -0
- package/dist/brain/tasks/goal-verification.js.map +1 -0
- package/dist/brain/tasks/knowledge.d.ts +26 -0
- package/dist/brain/tasks/knowledge.d.ts.map +1 -0
- package/dist/brain/tasks/knowledge.js +56 -0
- package/dist/brain/tasks/knowledge.js.map +1 -0
- package/dist/brain/tasks/link-scout.d.ts +37 -0
- package/dist/brain/tasks/link-scout.d.ts.map +1 -0
- package/dist/brain/tasks/link-scout.js +69 -0
- package/dist/brain/tasks/link-scout.js.map +1 -0
- package/dist/brain/types.d.ts +55 -0
- package/dist/brain/types.d.ts.map +1 -0
- package/dist/brain/types.js +7 -0
- package/dist/brain/types.js.map +1 -0
- package/dist/browser/stealth-init-script.d.ts +9 -0
- package/dist/browser/stealth-init-script.d.ts.map +1 -0
- package/dist/browser/stealth-init-script.js +128 -0
- package/dist/browser/stealth-init-script.js.map +1 -0
- package/dist/browser/storage-state.d.ts +29 -0
- package/dist/browser/storage-state.d.ts.map +1 -0
- package/dist/browser/storage-state.js +47 -0
- package/dist/browser/storage-state.js.map +1 -0
- package/dist/cli/args.d.ts +141 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +167 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/benchmark-sync.d.ts +9 -0
- package/dist/cli/benchmark-sync.d.ts.map +1 -0
- package/dist/cli/benchmark-sync.js +43 -0
- package/dist/cli/benchmark-sync.js.map +1 -0
- package/dist/cli/build-config.d.ts +11 -0
- package/dist/cli/build-config.d.ts.map +1 -0
- package/dist/cli/build-config.js +355 -0
- package/dist/cli/build-config.js.map +1 -0
- package/dist/cli/commands/auth.d.ts +3 -0
- package/dist/cli/commands/auth.d.ts.map +1 -0
- package/dist/cli/commands/auth.js +36 -0
- package/dist/cli/commands/auth.js.map +1 -0
- package/dist/cli/commands/chrome-debug.d.ts +7 -0
- package/dist/cli/commands/chrome-debug.d.ts.map +1 -0
- package/dist/cli/commands/chrome-debug.js +11 -0
- package/dist/cli/commands/chrome-debug.js.map +1 -0
- package/dist/cli/commands/design-audit.d.ts +3 -0
- package/dist/cli/commands/design-audit.d.ts.map +1 -0
- package/dist/cli/commands/design-audit.js +68 -0
- package/dist/cli/commands/design-audit.js.map +1 -0
- package/dist/cli/commands/preview.d.ts +14 -0
- package/dist/cli/commands/preview.d.ts.map +1 -0
- package/dist/cli/commands/preview.js +31 -0
- package/dist/cli/commands/preview.js.map +1 -0
- package/dist/cli/commands/run.d.ts +3 -0
- package/dist/cli/commands/run.d.ts.map +1 -0
- package/dist/cli/commands/run.js +728 -0
- package/dist/cli/commands/run.js.map +1 -0
- package/dist/cli/commands/runs.d.ts +8 -0
- package/dist/cli/commands/runs.d.ts.map +1 -0
- package/dist/cli/commands/runs.js +35 -0
- package/dist/cli/commands/runs.js.map +1 -0
- package/dist/cli/commands/share.d.ts +10 -0
- package/dist/cli/commands/share.d.ts.map +1 -0
- package/dist/cli/commands/share.js +32 -0
- package/dist/cli/commands/share.js.map +1 -0
- package/dist/cli/commands/showcase.d.ts +17 -0
- package/dist/cli/commands/showcase.d.ts.map +1 -0
- package/dist/cli/commands/showcase.js +20 -0
- package/dist/cli/commands/showcase.js.map +1 -0
- package/dist/cli/commands/snapshot.d.ts +12 -0
- package/dist/cli/commands/snapshot.d.ts.map +1 -0
- package/dist/cli/commands/snapshot.js +24 -0
- package/dist/cli/commands/snapshot.js.map +1 -0
- package/dist/cli/commands/view.d.ts +7 -0
- package/dist/cli/commands/view.d.ts.map +1 -0
- package/dist/cli/commands/view.js +23 -0
- package/dist/cli/commands/view.js.map +1 -0
- package/dist/cli/constants.d.ts +6 -0
- package/dist/cli/constants.d.ts.map +1 -0
- package/dist/cli/constants.js +3 -0
- package/dist/cli/constants.js.map +1 -0
- package/dist/cli/version.d.ts +7 -0
- package/dist/cli/version.d.ts.map +1 -0
- package/dist/cli/version.js +16 -0
- package/dist/cli/version.js.map +1 -0
- package/dist/cli-design-audit.d.ts +44 -36
- package/dist/cli-design-audit.d.ts.map +1 -1
- package/dist/cli-design-audit.js +137 -1646
- package/dist/cli-design-audit.js.map +1 -1
- package/dist/cli-jobs.js +2 -1
- package/dist/cli-jobs.js.map +1 -1
- package/dist/cli.js +66 -1697
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +5 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -0
- package/dist/config.js.map +1 -1
- package/dist/design/audit/evolve/agent.d.ts +23 -0
- package/dist/design/audit/evolve/agent.d.ts.map +1 -0
- package/dist/design/audit/evolve/agent.js +272 -0
- package/dist/design/audit/evolve/agent.js.map +1 -0
- package/dist/design/audit/evolve/css.d.ts +10 -0
- package/dist/design/audit/evolve/css.d.ts.map +1 -0
- package/dist/design/audit/evolve/css.js +206 -0
- package/dist/design/audit/evolve/css.js.map +1 -0
- package/dist/design/audit/evolve/index.d.ts +13 -0
- package/dist/design/audit/evolve/index.d.ts.map +1 -0
- package/dist/design/audit/evolve/index.js +12 -0
- package/dist/design/audit/evolve/index.js.map +1 -0
- package/dist/design/audit/evolve/report.d.ts +3 -0
- package/dist/design/audit/evolve/report.d.ts.map +1 -0
- package/dist/design/audit/evolve/report.js +45 -0
- package/dist/design/audit/evolve/report.js.map +1 -0
- package/dist/design/audit/evolve/types.d.ts +14 -0
- package/dist/design/audit/evolve/types.d.ts.map +1 -0
- package/dist/design/audit/evolve/types.js +2 -0
- package/dist/design/audit/evolve/types.js.map +1 -0
- package/dist/design/audit/pipeline.d.ts +12 -0
- package/dist/design/audit/pipeline.d.ts.map +1 -1
- package/dist/design/audit/pipeline.js +43 -27
- package/dist/design/audit/pipeline.js.map +1 -1
- package/dist/design/audit/reference/artifact/build.d.ts +33 -0
- package/dist/design/audit/reference/artifact/build.d.ts.map +1 -0
- package/dist/design/audit/reference/artifact/build.js +64 -0
- package/dist/design/audit/reference/artifact/build.js.map +1 -0
- package/dist/design/audit/reference/artifact/render.d.ts +39 -0
- package/dist/design/audit/reference/artifact/render.d.ts.map +1 -0
- package/dist/design/audit/reference/artifact/render.js +213 -0
- package/dist/design/audit/reference/artifact/render.js.map +1 -0
- package/dist/design/audit/reference/artifact/text.d.ts +11 -0
- package/dist/design/audit/reference/artifact/text.d.ts.map +1 -0
- package/dist/design/audit/reference/artifact/text.js +20 -0
- package/dist/design/audit/reference/artifact/text.js.map +1 -0
- package/dist/design/audit/reference/artifact/to-findings.d.ts +57 -0
- package/dist/design/audit/reference/artifact/to-findings.d.ts.map +1 -0
- package/dist/design/audit/reference/artifact/to-findings.js +314 -0
- package/dist/design/audit/reference/artifact/to-findings.js.map +1 -0
- package/dist/design/audit/reference/config.d.ts +50 -0
- package/dist/design/audit/reference/config.d.ts.map +1 -0
- package/dist/design/audit/reference/config.js +108 -0
- package/dist/design/audit/reference/config.js.map +1 -0
- package/dist/design/audit/reference/contracts.d.ts +1030 -0
- package/dist/design/audit/reference/contracts.d.ts.map +1 -0
- package/dist/design/audit/reference/contracts.js +28 -0
- package/dist/design/audit/reference/contracts.js.map +1 -0
- package/dist/design/audit/reference/corpus/build.d.ts +115 -0
- package/dist/design/audit/reference/corpus/build.d.ts.map +1 -0
- package/dist/design/audit/reference/corpus/build.js +152 -0
- package/dist/design/audit/reference/corpus/build.js.map +1 -0
- package/dist/design/audit/reference/corpus/schema.d.ts +33 -0
- package/dist/design/audit/reference/corpus/schema.d.ts.map +1 -0
- package/dist/design/audit/reference/corpus/schema.js +249 -0
- package/dist/design/audit/reference/corpus/schema.js.map +1 -0
- package/dist/design/audit/reference/corpus/store.d.ts +26 -0
- package/dist/design/audit/reference/corpus/store.d.ts.map +1 -0
- package/dist/design/audit/reference/corpus/store.js +104 -0
- package/dist/design/audit/reference/corpus/store.js.map +1 -0
- package/dist/design/audit/reference/dna/delta.d.ts +21 -0
- package/dist/design/audit/reference/dna/delta.d.ts.map +1 -0
- package/dist/design/audit/reference/dna/delta.js +126 -0
- package/dist/design/audit/reference/dna/delta.js.map +1 -0
- package/dist/design/audit/reference/dna/derive.d.ts +31 -0
- package/dist/design/audit/reference/dna/derive.d.ts.map +1 -0
- package/dist/design/audit/reference/dna/derive.js +381 -0
- package/dist/design/audit/reference/dna/derive.js.map +1 -0
- package/dist/design/audit/reference/dna/descriptor.d.ts +36 -0
- package/dist/design/audit/reference/dna/descriptor.d.ts.map +1 -0
- package/dist/design/audit/reference/dna/descriptor.js +209 -0
- package/dist/design/audit/reference/dna/descriptor.js.map +1 -0
- package/dist/design/audit/reference/dna/page-adapter.d.ts +17 -0
- package/dist/design/audit/reference/dna/page-adapter.d.ts.map +1 -0
- package/dist/design/audit/reference/dna/page-adapter.js +35 -0
- package/dist/design/audit/reference/dna/page-adapter.js.map +1 -0
- package/dist/design/audit/reference/dna/scroll-capture.d.ts +93 -0
- package/dist/design/audit/reference/dna/scroll-capture.d.ts.map +1 -0
- package/dist/design/audit/reference/dna/scroll-capture.js +374 -0
- package/dist/design/audit/reference/dna/scroll-capture.js.map +1 -0
- package/dist/design/audit/reference/engine/budget.d.ts +63 -0
- package/dist/design/audit/reference/engine/budget.d.ts.map +1 -0
- package/dist/design/audit/reference/engine/budget.js +110 -0
- package/dist/design/audit/reference/engine/budget.js.map +1 -0
- package/dist/design/audit/reference/engine/core.d.ts +32 -0
- package/dist/design/audit/reference/engine/core.d.ts.map +1 -0
- package/dist/design/audit/reference/engine/core.js +261 -0
- package/dist/design/audit/reference/engine/core.js.map +1 -0
- package/dist/design/audit/reference/engine/guard.d.ts +43 -0
- package/dist/design/audit/reference/engine/guard.d.ts.map +1 -0
- package/dist/design/audit/reference/engine/guard.js +43 -0
- package/dist/design/audit/reference/engine/guard.js.map +1 -0
- package/dist/design/audit/reference/engine/score-core.d.ts +48 -0
- package/dist/design/audit/reference/engine/score-core.d.ts.map +1 -0
- package/dist/design/audit/reference/engine/score-core.js +134 -0
- package/dist/design/audit/reference/engine/score-core.js.map +1 -0
- package/dist/design/audit/reference/engine/wiring.d.ts +35 -0
- package/dist/design/audit/reference/engine/wiring.d.ts.map +1 -0
- package/dist/design/audit/reference/engine/wiring.js +80 -0
- package/dist/design/audit/reference/engine/wiring.js.map +1 -0
- package/dist/design/audit/reference/eval/taste-core.d.ts +77 -0
- package/dist/design/audit/reference/eval/taste-core.d.ts.map +1 -0
- package/dist/design/audit/reference/eval/taste-core.js +95 -0
- package/dist/design/audit/reference/eval/taste-core.js.map +1 -0
- package/dist/design/audit/reference/generate/generator.d.ts +49 -0
- package/dist/design/audit/reference/generate/generator.d.ts.map +1 -0
- package/dist/design/audit/reference/generate/generator.js +94 -0
- package/dist/design/audit/reference/generate/generator.js.map +1 -0
- package/dist/design/audit/reference/generate/parse.d.ts +28 -0
- package/dist/design/audit/reference/generate/parse.d.ts.map +1 -0
- package/dist/design/audit/reference/generate/parse.js +180 -0
- package/dist/design/audit/reference/generate/parse.js.map +1 -0
- package/dist/design/audit/reference/generate/prompt.d.ts +36 -0
- package/dist/design/audit/reference/generate/prompt.d.ts.map +1 -0
- package/dist/design/audit/reference/generate/prompt.js +124 -0
- package/dist/design/audit/reference/generate/prompt.js.map +1 -0
- package/dist/design/audit/reference/index.d.ts +26 -0
- package/dist/design/audit/reference/index.d.ts.map +1 -0
- package/dist/design/audit/reference/index.js +31 -0
- package/dist/design/audit/reference/index.js.map +1 -0
- package/dist/design/audit/reference/judge/image-clamp.d.ts +34 -0
- package/dist/design/audit/reference/judge/image-clamp.d.ts.map +1 -0
- package/dist/design/audit/reference/judge/image-clamp.js +88 -0
- package/dist/design/audit/reference/judge/image-clamp.js.map +1 -0
- package/dist/design/audit/reference/judge/pairwise.d.ts +34 -0
- package/dist/design/audit/reference/judge/pairwise.d.ts.map +1 -0
- package/dist/design/audit/reference/judge/pairwise.js +105 -0
- package/dist/design/audit/reference/judge/pairwise.js.map +1 -0
- package/dist/design/audit/reference/judge/parse.d.ts +31 -0
- package/dist/design/audit/reference/judge/parse.d.ts.map +1 -0
- package/dist/design/audit/reference/judge/parse.js +113 -0
- package/dist/design/audit/reference/judge/parse.js.map +1 -0
- package/dist/design/audit/reference/judge/prompt.d.ts +38 -0
- package/dist/design/audit/reference/judge/prompt.d.ts.map +1 -0
- package/dist/design/audit/reference/judge/prompt.js +111 -0
- package/dist/design/audit/reference/judge/prompt.js.map +1 -0
- package/dist/design/audit/reference/judge/quality.d.ts +28 -0
- package/dist/design/audit/reference/judge/quality.d.ts.map +1 -0
- package/dist/design/audit/reference/judge/quality.js +62 -0
- package/dist/design/audit/reference/judge/quality.js.map +1 -0
- package/dist/design/audit/reference/judge/rank.d.ts +41 -0
- package/dist/design/audit/reference/judge/rank.d.ts.map +1 -0
- package/dist/design/audit/reference/judge/rank.js +196 -0
- package/dist/design/audit/reference/judge/rank.js.map +1 -0
- package/dist/design/audit/reference/judge/text-judge.d.ts +31 -0
- package/dist/design/audit/reference/judge/text-judge.d.ts.map +1 -0
- package/dist/design/audit/reference/judge/text-judge.js +43 -0
- package/dist/design/audit/reference/judge/text-judge.js.map +1 -0
- package/dist/design/audit/reference/judge/vision-judge.d.ts +59 -0
- package/dist/design/audit/reference/judge/vision-judge.d.ts.map +1 -0
- package/dist/design/audit/reference/judge/vision-judge.js +150 -0
- package/dist/design/audit/reference/judge/vision-judge.js.map +1 -0
- package/dist/design/audit/reference/judge/vision-model.d.ts +65 -0
- package/dist/design/audit/reference/judge/vision-model.d.ts.map +1 -0
- package/dist/design/audit/reference/judge/vision-model.js +110 -0
- package/dist/design/audit/reference/judge/vision-model.js.map +1 -0
- package/dist/design/audit/reference/pipeline/evaluate-reference.d.ts +68 -0
- package/dist/design/audit/reference/pipeline/evaluate-reference.d.ts.map +1 -0
- package/dist/design/audit/reference/pipeline/evaluate-reference.js +52 -0
- package/dist/design/audit/reference/pipeline/evaluate-reference.js.map +1 -0
- package/dist/design/audit/reference/reference-context.d.ts +31 -0
- package/dist/design/audit/reference/reference-context.d.ts.map +1 -0
- package/dist/design/audit/reference/reference-context.js +73 -0
- package/dist/design/audit/reference/reference-context.js.map +1 -0
- package/dist/design/audit/reference/retrieval/embedding-hash.d.ts +30 -0
- package/dist/design/audit/reference/retrieval/embedding-hash.d.ts.map +1 -0
- package/dist/design/audit/reference/retrieval/embedding-hash.js +87 -0
- package/dist/design/audit/reference/retrieval/embedding-hash.js.map +1 -0
- package/dist/design/audit/reference/retrieval/embedding-openai.d.ts +38 -0
- package/dist/design/audit/reference/retrieval/embedding-openai.d.ts.map +1 -0
- package/dist/design/audit/reference/retrieval/embedding-openai.js +73 -0
- package/dist/design/audit/reference/retrieval/embedding-openai.js.map +1 -0
- package/dist/design/audit/reference/retrieval/matcher.d.ts +34 -0
- package/dist/design/audit/reference/retrieval/matcher.d.ts.map +1 -0
- package/dist/design/audit/reference/retrieval/matcher.js +107 -0
- package/dist/design/audit/reference/retrieval/matcher.js.map +1 -0
- package/dist/design/audit/reference/run.d.ts +59 -0
- package/dist/design/audit/reference/run.d.ts.map +1 -0
- package/dist/design/audit/reference/run.js +99 -0
- package/dist/design/audit/reference/run.js.map +1 -0
- package/dist/design/audit/report.d.ts +35 -0
- package/dist/design/audit/report.d.ts.map +1 -0
- package/dist/design/audit/report.js +195 -0
- package/dist/design/audit/report.js.map +1 -0
- package/dist/design/audit/tokens/extract.d.ts +71 -0
- package/dist/design/audit/tokens/extract.d.ts.map +1 -0
- package/dist/design/audit/tokens/extract.js +1034 -0
- package/dist/design/audit/tokens/extract.js.map +1 -0
- package/dist/design/audit/types.d.ts +8 -0
- package/dist/design/audit/types.d.ts.map +1 -1
- package/dist/design/compare.d.ts.map +1 -1
- package/dist/design/compare.js +2 -6
- package/dist/design/compare.js.map +1 -1
- package/dist/design/cookie-consent.d.ts +3 -0
- package/dist/design/cookie-consent.d.ts.map +1 -0
- package/dist/design/cookie-consent.js +26 -0
- package/dist/design/cookie-consent.js.map +1 -0
- package/dist/design/viewports.d.ts +18 -0
- package/dist/design/viewports.d.ts.map +1 -0
- package/dist/design/viewports.js +10 -0
- package/dist/design/viewports.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/memory/store.d.ts +19 -1
- package/dist/memory/store.d.ts.map +1 -1
- package/dist/memory/store.js +47 -0
- package/dist/memory/store.js.map +1 -1
- package/dist/provider-defaults.d.ts +18 -0
- package/dist/provider-defaults.d.ts.map +1 -1
- package/dist/provider-defaults.js +33 -0
- package/dist/provider-defaults.js.map +1 -1
- package/dist/runner/allowed-domains.d.ts +11 -0
- package/dist/runner/allowed-domains.d.ts.map +1 -0
- package/dist/runner/allowed-domains.js +14 -0
- package/dist/runner/allowed-domains.js.map +1 -0
- package/dist/runner/batch-fill.d.ts +28 -0
- package/dist/runner/batch-fill.d.ts.map +1 -0
- package/dist/runner/batch-fill.js +80 -0
- package/dist/runner/batch-fill.js.map +1 -0
- package/dist/runner/completion-policy.d.ts +26 -0
- package/dist/runner/completion-policy.d.ts.map +1 -0
- package/dist/runner/completion-policy.js +76 -0
- package/dist/runner/completion-policy.js.map +1 -0
- package/dist/runner/constants.d.ts +14 -0
- package/dist/runner/constants.d.ts.map +1 -0
- package/dist/runner/constants.js +23 -0
- package/dist/runner/constants.js.map +1 -0
- package/dist/runner/decision-screenshot.d.ts +24 -0
- package/dist/runner/decision-screenshot.d.ts.map +1 -0
- package/dist/runner/decision-screenshot.js +23 -0
- package/dist/runner/decision-screenshot.js.map +1 -0
- package/dist/runner/domain-boundary.d.ts +36 -0
- package/dist/runner/domain-boundary.d.ts.map +1 -0
- package/dist/runner/domain-boundary.js +81 -0
- package/dist/runner/domain-boundary.js.map +1 -0
- package/dist/runner/effect-verify.d.ts +28 -0
- package/dist/runner/effect-verify.d.ts.map +1 -0
- package/dist/runner/effect-verify.js +42 -0
- package/dist/runner/effect-verify.js.map +1 -0
- package/dist/runner/execute-plan.d.ts +58 -0
- package/dist/runner/execute-plan.d.ts.map +1 -0
- package/dist/runner/execute-plan.js +426 -0
- package/dist/runner/execute-plan.js.map +1 -0
- package/dist/runner/max-turns-extension.d.ts +19 -0
- package/dist/runner/max-turns-extension.d.ts.map +1 -0
- package/dist/runner/max-turns-extension.js +25 -0
- package/dist/runner/max-turns-extension.js.map +1 -0
- package/dist/runner/micro-plan.d.ts +11 -0
- package/dist/runner/micro-plan.d.ts.map +1 -0
- package/dist/runner/micro-plan.js +29 -0
- package/dist/runner/micro-plan.js.map +1 -0
- package/dist/runner/prompt-snippets.d.ts +5 -0
- package/dist/runner/prompt-snippets.d.ts.map +1 -0
- package/dist/runner/prompt-snippets.js +13 -0
- package/dist/runner/prompt-snippets.js.map +1 -0
- package/dist/runner/replay/contracts.d.ts +300 -0
- package/dist/runner/replay/contracts.d.ts.map +1 -0
- package/dist/runner/replay/contracts.js +42 -0
- package/dist/runner/replay/contracts.js.map +1 -0
- package/dist/runner/replay/controller.d.ts +27 -0
- package/dist/runner/replay/controller.d.ts.map +1 -0
- package/dist/runner/replay/controller.js +197 -0
- package/dist/runner/replay/controller.js.map +1 -0
- package/dist/runner/replay/guard.d.ts +28 -0
- package/dist/runner/replay/guard.d.ts.map +1 -0
- package/dist/runner/replay/guard.js +100 -0
- package/dist/runner/replay/guard.js.map +1 -0
- package/dist/runner/runner.d.ts +44 -51
- package/dist/runner/runner.d.ts.map +1 -1
- package/dist/runner/runner.js +155 -851
- package/dist/runner/runner.js.map +1 -1
- package/dist/runner/scout-feedback.d.ts +51 -0
- package/dist/runner/scout-feedback.d.ts.map +1 -0
- package/dist/runner/scout-feedback.js +149 -0
- package/dist/runner/scout-feedback.js.map +1 -0
- package/dist/skills/macro-loader.d.ts +1 -1
- package/dist/test-runner.d.ts.map +1 -1
- package/dist/test-runner.js +25 -0
- package/dist/test-runner.js.map +1 -1
- package/dist/types/actions.d.ts +241 -0
- package/dist/types/actions.d.ts.map +1 -0
- package/dist/types/actions.js +5 -0
- package/dist/types/actions.js.map +1 -0
- package/dist/types/config.d.ts +211 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +2 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/design-audit.d.ts +92 -0
- package/dist/types/design-audit.d.ts.map +1 -0
- package/dist/types/design-audit.js +5 -0
- package/dist/types/design-audit.js.map +1 -0
- package/dist/types/design-tokens.d.ts +138 -0
- package/dist/types/design-tokens.d.ts.map +1 -0
- package/dist/types/design-tokens.js +5 -0
- package/dist/types/design-tokens.js.map +1 -0
- package/dist/types/page.d.ts +16 -0
- package/dist/types/page.d.ts.map +1 -0
- package/dist/types/page.js +2 -0
- package/dist/types/page.js.map +1 -0
- package/dist/types/plan.d.ts +50 -0
- package/dist/types/plan.d.ts.map +1 -0
- package/dist/types/plan.js +2 -0
- package/dist/types/plan.js.map +1 -0
- package/dist/types/preview.d.ts +9 -0
- package/dist/types/preview.d.ts.map +1 -0
- package/dist/types/preview.js +5 -0
- package/dist/types/preview.js.map +1 -0
- package/dist/types/result.d.ts +62 -0
- package/dist/types/result.d.ts.map +1 -0
- package/dist/types/result.js +2 -0
- package/dist/types/result.js.map +1 -0
- package/dist/types/scenario.d.ts +20 -0
- package/dist/types/scenario.d.ts.map +1 -0
- package/dist/types/scenario.js +5 -0
- package/dist/types/scenario.js.map +1 -0
- package/dist/types/test-runner.d.ts +145 -0
- package/dist/types/test-runner.d.ts.map +1 -0
- package/dist/types/test-runner.js +10 -0
- package/dist/types/test-runner.js.map +1 -0
- package/dist/types/trajectory.d.ts +38 -0
- package/dist/types/trajectory.d.ts.map +1 -0
- package/dist/types/trajectory.js +2 -0
- package/dist/types/trajectory.js.map +1 -0
- package/dist/types/turn.d.ts +50 -0
- package/dist/types/turn.d.ts.map +1 -0
- package/dist/types/turn.js +2 -0
- package/dist/types/turn.js.map +1 -0
- package/dist/types.d.ts +16 -1017
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +15 -8
- package/dist/types.js.map +1 -1
- package/dist/wallet/rpc-interception.d.ts +15 -0
- package/dist/wallet/rpc-interception.d.ts.map +1 -0
- package/dist/wallet/rpc-interception.js +95 -0
- package/dist/wallet/rpc-interception.js.map +1 -0
- package/package.json +6 -2
package/dist/cli.js
CHANGED
|
@@ -10,69 +10,14 @@
|
|
|
10
10
|
* Designed for sandbox/container execution:
|
|
11
11
|
* docker run bad run --cases /data/cases.json --sink /output/
|
|
12
12
|
*/
|
|
13
|
-
import { parseArgs } from 'node:util';
|
|
14
13
|
import * as fs from 'node:fs';
|
|
15
|
-
import * as path from 'node:path';
|
|
16
|
-
import { spawn } from 'node:child_process';
|
|
17
|
-
import { loadConfig, mergeConfig, toAgentConfig } from './config.js';
|
|
18
|
-
import { buildBrowserLaunchPlan } from './browser-launch.js';
|
|
19
|
-
import { runWalletPreflight, startWalletAutoApprover } from './wallet/automation.js';
|
|
20
|
-
import { isPersonaId, listPersonaIds, withPersonaDirective } from './personas.js';
|
|
21
|
-
import { resolveProviderApiKey, resolveProviderModelName } from './provider-defaults.js';
|
|
22
14
|
import { loadLocalEnvFiles } from './env-loader.js';
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import { RunRegistry } from './memory/run-registry.js';
|
|
15
|
+
import { cliError, printStyledHelp } from './cli-ui.js';
|
|
16
|
+
import { listPersonaIds } from './personas.js';
|
|
26
17
|
import { setCliVersion, setInvocation, getTelemetry } from './telemetry/index.js';
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (!storageStatePath)
|
|
31
|
-
return;
|
|
32
|
-
const parsed = JSON.parse(fs.readFileSync(storageStatePath, 'utf-8'));
|
|
33
|
-
const cookies = parsed.cookies ?? [];
|
|
34
|
-
if (cookies.length > 0) {
|
|
35
|
-
await context.addCookies(cookies);
|
|
36
|
-
}
|
|
37
|
-
const origins = parsed.origins ?? [];
|
|
38
|
-
if (origins.length === 0)
|
|
39
|
-
return;
|
|
40
|
-
const existingPages = context.pages();
|
|
41
|
-
const page = existingPages[0] ?? await context.newPage();
|
|
42
|
-
const createdTempPage = existingPages.length === 0;
|
|
43
|
-
try {
|
|
44
|
-
for (const originState of origins) {
|
|
45
|
-
if (!originState?.origin || !Array.isArray(originState.localStorage) || originState.localStorage.length === 0) {
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
await page.goto(originState.origin, { waitUntil: 'domcontentloaded' }).catch(() => { });
|
|
49
|
-
await page.evaluate((entries) => {
|
|
50
|
-
for (const entry of entries) {
|
|
51
|
-
try {
|
|
52
|
-
localStorage.setItem(entry.name, entry.value);
|
|
53
|
-
}
|
|
54
|
-
catch {
|
|
55
|
-
// Best effort: some origins may block storage writes.
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}, originState.localStorage);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
finally {
|
|
62
|
-
if (createdTempPage) {
|
|
63
|
-
await page.close().catch(() => { });
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
function readCliVersion() {
|
|
68
|
-
try {
|
|
69
|
-
const pkg = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
|
|
70
|
-
return pkg.version ?? '0.0.0';
|
|
71
|
-
}
|
|
72
|
-
catch {
|
|
73
|
-
return '0.0.0';
|
|
74
|
-
}
|
|
75
|
-
}
|
|
18
|
+
import { parseCliArgs } from './cli/args.js';
|
|
19
|
+
import { readCliVersion } from './cli/version.js';
|
|
20
|
+
import { RUN_MODES, DRIVER_PROFILES } from './cli/constants.js';
|
|
76
21
|
async function main() {
|
|
77
22
|
loadLocalEnvFiles(process.cwd());
|
|
78
23
|
setCliVersion(readCliVersion());
|
|
@@ -90,150 +35,7 @@ async function main() {
|
|
|
90
35
|
await runReportsCli(subArgs.slice(1));
|
|
91
36
|
process.exit(0);
|
|
92
37
|
}
|
|
93
|
-
const { values, positionals } =
|
|
94
|
-
allowPositionals: true,
|
|
95
|
-
allowNegative: true,
|
|
96
|
-
options: {
|
|
97
|
-
// Config file
|
|
98
|
-
config: { type: 'string' },
|
|
99
|
-
// Test specification
|
|
100
|
-
goal: { type: 'string', short: 'g' },
|
|
101
|
-
url: { type: 'string', short: 'u' },
|
|
102
|
-
cases: { type: 'string', short: 'c' },
|
|
103
|
-
'cases-json': { type: 'string' },
|
|
104
|
-
'allowed-domains': { type: 'string' },
|
|
105
|
-
// LLM configuration
|
|
106
|
-
model: { type: 'string', short: 'm' },
|
|
107
|
-
provider: { type: 'string' },
|
|
108
|
-
'model-adaptive': { type: 'boolean' },
|
|
109
|
-
'nav-model': { type: 'string' },
|
|
110
|
-
'nav-provider': { type: 'string' },
|
|
111
|
-
persona: { type: 'string' },
|
|
112
|
-
mode: { type: 'string' },
|
|
113
|
-
profile: { type: 'string' },
|
|
114
|
-
'prompt-file': { type: 'string' },
|
|
115
|
-
'sandbox-backend-type': { type: 'string' },
|
|
116
|
-
'sandbox-backend-profile': { type: 'string' },
|
|
117
|
-
'sandbox-backend-provider': { type: 'string' },
|
|
118
|
-
'api-key': { type: 'string' },
|
|
119
|
-
'base-url': { type: 'string' },
|
|
120
|
-
// Execution
|
|
121
|
-
browser: { type: 'string' },
|
|
122
|
-
'storage-state': { type: 'string' },
|
|
123
|
-
concurrency: { type: 'string' },
|
|
124
|
-
'max-turns': { type: 'string' },
|
|
125
|
-
'session-id': { type: 'string' },
|
|
126
|
-
'resume-run': { type: 'string' },
|
|
127
|
-
'fork-run': { type: 'string' },
|
|
128
|
-
pages: { type: 'string' },
|
|
129
|
-
'extract-tokens': { type: 'boolean' },
|
|
130
|
-
rip: { type: 'boolean' },
|
|
131
|
-
'design-compare': { type: 'boolean' },
|
|
132
|
-
'compare-url': { type: 'string' },
|
|
133
|
-
evolve: { type: 'string' },
|
|
134
|
-
'evolve-rounds': { type: 'string' },
|
|
135
|
-
'project-dir': { type: 'string' },
|
|
136
|
-
reproducibility: { type: 'boolean' },
|
|
137
|
-
'rubrics-dir': { type: 'string' },
|
|
138
|
-
'audit-passes': { type: 'string' },
|
|
139
|
-
// Layer 7 — domain ethics gate. --skip-ethics bypasses the rollup floor
|
|
140
|
-
// for testing scenarios; --ethics-rules-dir overrides the builtin rule set.
|
|
141
|
-
'skip-ethics': { type: 'boolean' },
|
|
142
|
-
'ethics-rules-dir': { type: 'string' },
|
|
143
|
-
// Layer 6 / 7 — audience predicate hints. Comma-separated.
|
|
144
|
-
audience: { type: 'string' },
|
|
145
|
-
'regulatory-context': { type: 'string' },
|
|
146
|
-
'audience-vulnerability': { type: 'string' },
|
|
147
|
-
modality: { type: 'string' },
|
|
148
|
-
// bad view
|
|
149
|
-
port: { type: 'string' },
|
|
150
|
-
'no-open': { type: 'boolean' },
|
|
151
|
-
// bad run --show-cursor (overlay)
|
|
152
|
-
'show-cursor': { type: 'boolean' },
|
|
153
|
-
// bad run --live (open SSE-streaming live viewer alongside the run)
|
|
154
|
-
live: { type: 'boolean' },
|
|
155
|
-
// bad run --planner: one LLM call generates the full action sequence,
|
|
156
|
-
// then the runner executes it deterministically.
|
|
157
|
-
planner: { type: 'boolean' },
|
|
158
|
-
'planner-mode': { type: 'string' },
|
|
159
|
-
// showcase
|
|
160
|
-
script: { type: 'string' },
|
|
161
|
-
capture: { type: 'string' },
|
|
162
|
-
crop: { type: 'string' },
|
|
163
|
-
highlight: { type: 'string' },
|
|
164
|
-
format: { type: 'string' },
|
|
165
|
-
viewport: { type: 'string' },
|
|
166
|
-
scale: { type: 'string' },
|
|
167
|
-
'color-scheme': { type: 'string' },
|
|
168
|
-
'llm-timeout': { type: 'string' },
|
|
169
|
-
retries: { type: 'string' },
|
|
170
|
-
'retry-delay-ms': { type: 'string' },
|
|
171
|
-
'screenshot-interval': { type: 'string' },
|
|
172
|
-
scout: { type: 'boolean' },
|
|
173
|
-
'scout-model': { type: 'string' },
|
|
174
|
-
'scout-provider': { type: 'string' },
|
|
175
|
-
'scout-vision': { type: 'boolean' },
|
|
176
|
-
'scout-max-candidates': { type: 'string' },
|
|
177
|
-
'scout-min-top-score': { type: 'string' },
|
|
178
|
-
'scout-max-score-gap': { type: 'string' },
|
|
179
|
-
headless: { type: 'boolean' },
|
|
180
|
-
proxy: { type: 'string' },
|
|
181
|
-
timeout: { type: 'string' },
|
|
182
|
-
extension: { type: 'string', multiple: true },
|
|
183
|
-
'user-data-dir': { type: 'string' },
|
|
184
|
-
'profile-dir': { type: 'string' },
|
|
185
|
-
'cdp-url': { type: 'string' },
|
|
186
|
-
attach: { type: 'boolean' },
|
|
187
|
-
'attach-port': { type: 'string' },
|
|
188
|
-
wallet: { type: 'boolean' },
|
|
189
|
-
'wallet-auto-approve': { type: 'boolean' },
|
|
190
|
-
'wallet-password': { type: 'string' },
|
|
191
|
-
'wallet-seed-url': { type: 'string', multiple: true },
|
|
192
|
-
'wallet-preflight': { type: 'boolean' },
|
|
193
|
-
'wallet-chain-id': { type: 'string' },
|
|
194
|
-
'wallet-chain-rpc-url': { type: 'string' },
|
|
195
|
-
memory: { type: 'boolean' },
|
|
196
|
-
'memory-dir': { type: 'string' },
|
|
197
|
-
// Output
|
|
198
|
-
sink: { type: 'string', short: 's' },
|
|
199
|
-
json: { type: 'boolean', default: false },
|
|
200
|
-
quiet: { type: 'boolean', short: 'q', default: false },
|
|
201
|
-
// Feature flags
|
|
202
|
-
'goal-verification': { type: 'boolean' },
|
|
203
|
-
'quality-threshold': { type: 'string' },
|
|
204
|
-
'trace-scoring': { type: 'boolean' },
|
|
205
|
-
'trace-ttl-days': { type: 'string' },
|
|
206
|
-
vision: { type: 'boolean' },
|
|
207
|
-
'vision-strategy': { type: 'string' },
|
|
208
|
-
'observation-mode': { type: 'string' },
|
|
209
|
-
debug: { type: 'boolean', short: 'd', default: false },
|
|
210
|
-
// Resource blocking
|
|
211
|
-
'block-analytics': { type: 'boolean', default: false },
|
|
212
|
-
'block-images': { type: 'boolean', default: false },
|
|
213
|
-
'block-media': { type: 'boolean', default: false },
|
|
214
|
-
// Auth
|
|
215
|
-
fill: { type: 'string', multiple: true },
|
|
216
|
-
cookie: { type: 'string', multiple: true },
|
|
217
|
-
'wait-for': { type: 'string' },
|
|
218
|
-
'wait-timeout': { type: 'string' },
|
|
219
|
-
// `bad share` flags
|
|
220
|
-
visibility: { type: 'string' },
|
|
221
|
-
'bad-app-url': { type: 'string' },
|
|
222
|
-
'no-copy': { type: 'boolean' },
|
|
223
|
-
// preview / stream / interrupt
|
|
224
|
-
'max-steps': { type: 'string' },
|
|
225
|
-
headed: { type: 'boolean' },
|
|
226
|
-
stream: { type: 'string' },
|
|
227
|
-
'stream-token': { type: 'string' },
|
|
228
|
-
interrupt: { type: 'boolean' },
|
|
229
|
-
// `bad snapshot` — headless, no-LLM accessibility dump
|
|
230
|
-
out: { type: 'string' },
|
|
231
|
-
wait: { type: 'string' },
|
|
232
|
-
'dismiss-modals': { type: 'boolean' },
|
|
233
|
-
help: { type: 'boolean', short: 'h', default: false },
|
|
234
|
-
version: { type: 'boolean', short: 'v', default: false },
|
|
235
|
-
},
|
|
236
|
-
});
|
|
38
|
+
const { values, positionals } = parseCliArgs();
|
|
237
39
|
if (values.version) {
|
|
238
40
|
const pkg = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
|
|
239
41
|
console.log(pkg.version);
|
|
@@ -245,146 +47,46 @@ async function main() {
|
|
|
245
47
|
}
|
|
246
48
|
const command = positionals[0];
|
|
247
49
|
if (command === 'view') {
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
try {
|
|
255
|
-
await runViewCli({
|
|
256
|
-
runDir,
|
|
257
|
-
port: values.port ? parseInt(values.port) : undefined,
|
|
258
|
-
noOpen: values['no-open'],
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
|
-
catch (err) {
|
|
262
|
-
if (err instanceof ViewError) {
|
|
263
|
-
cliError(err.message);
|
|
264
|
-
process.exit(1);
|
|
265
|
-
}
|
|
266
|
-
throw err;
|
|
267
|
-
}
|
|
50
|
+
const { runViewCommand } = await import('./cli/commands/view.js');
|
|
51
|
+
await runViewCommand({
|
|
52
|
+
runDir: positionals[1],
|
|
53
|
+
port: values.port,
|
|
54
|
+
noOpen: values['no-open'],
|
|
55
|
+
});
|
|
268
56
|
return;
|
|
269
57
|
}
|
|
270
58
|
if (command === 'design-audit') {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
// --design-compare mode
|
|
276
|
-
if (values['design-compare']) {
|
|
277
|
-
if (!values['compare-url']) {
|
|
278
|
-
cliError('--compare-url is required with --design-compare.');
|
|
279
|
-
process.exit(1);
|
|
280
|
-
}
|
|
281
|
-
const { runDesignCompare } = await import('./design/compare.js');
|
|
282
|
-
await runDesignCompare({
|
|
283
|
-
urlA: values.url,
|
|
284
|
-
urlB: values['compare-url'],
|
|
285
|
-
headless: values.headless,
|
|
286
|
-
outputDir: values.sink,
|
|
287
|
-
});
|
|
288
|
-
process.exit(0);
|
|
289
|
-
}
|
|
290
|
-
// --rip mode
|
|
291
|
-
if (values.rip) {
|
|
292
|
-
const { ripSite } = await import('./design/rip.js');
|
|
293
|
-
await ripSite({
|
|
294
|
-
url: values.url,
|
|
295
|
-
pages: values.pages ? parseInt(values.pages) : undefined,
|
|
296
|
-
headless: values.headless,
|
|
297
|
-
outputDir: values.sink,
|
|
298
|
-
});
|
|
299
|
-
process.exit(0);
|
|
300
|
-
}
|
|
301
|
-
const { runDesignAudit } = await import('./cli-design-audit.js');
|
|
302
|
-
await runDesignAudit({
|
|
303
|
-
url: values.url,
|
|
304
|
-
pages: values.pages ? parseInt(values.pages) : undefined,
|
|
305
|
-
profile: values.profile,
|
|
306
|
-
model: values.model,
|
|
307
|
-
provider: values.provider,
|
|
308
|
-
apiKey: values['api-key'],
|
|
309
|
-
baseUrl: values['base-url'],
|
|
310
|
-
output: values.sink,
|
|
311
|
-
json: values.json,
|
|
312
|
-
headless: values.headless,
|
|
313
|
-
debug: values.debug,
|
|
314
|
-
storageState: values['storage-state'],
|
|
315
|
-
extractTokens: values['extract-tokens'],
|
|
316
|
-
evolve: values.evolve,
|
|
317
|
-
evolveRounds: values['evolve-rounds'] ? parseInt(values['evolve-rounds']) : undefined,
|
|
318
|
-
projectDir: values['project-dir'],
|
|
319
|
-
reproducibility: values.reproducibility,
|
|
320
|
-
rubricsDir: values['rubrics-dir'],
|
|
321
|
-
auditPasses: values['audit-passes'],
|
|
322
|
-
skipEthics: values['skip-ethics'],
|
|
323
|
-
ethicsRulesDir: values['ethics-rules-dir'],
|
|
324
|
-
audience: values.audience,
|
|
325
|
-
regulatoryContext: values['regulatory-context'],
|
|
326
|
-
audienceVulnerability: values['audience-vulnerability'],
|
|
327
|
-
modality: values.modality,
|
|
328
|
-
});
|
|
329
|
-
process.exit(0);
|
|
59
|
+
const { runDesignAuditCommand } = await import('./cli/commands/design-audit.js');
|
|
60
|
+
await runDesignAuditCommand(values);
|
|
61
|
+
return;
|
|
330
62
|
}
|
|
331
63
|
if (command === 'snapshot') {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
process.exit(2);
|
|
335
|
-
}
|
|
336
|
-
const waitArg = values.wait;
|
|
337
|
-
const wait = waitArg === 'load' || waitArg === 'domcontentloaded' || waitArg === 'networkidle' || waitArg === 'commit'
|
|
338
|
-
? waitArg
|
|
339
|
-
: undefined;
|
|
340
|
-
const { handleSnapshotCommand } = await import('./cli-snapshot.js');
|
|
341
|
-
const rc = await handleSnapshotCommand({
|
|
64
|
+
const { runSnapshotCommand } = await import('./cli/commands/snapshot.js');
|
|
65
|
+
await runSnapshotCommand({
|
|
342
66
|
url: values.url,
|
|
343
67
|
json: values.json,
|
|
344
68
|
out: values.out,
|
|
345
|
-
timeout: values.timeout
|
|
346
|
-
wait,
|
|
69
|
+
timeout: values.timeout,
|
|
70
|
+
wait: values.wait,
|
|
347
71
|
dismissModals: values['dismiss-modals'],
|
|
348
72
|
headed: values.headed,
|
|
349
73
|
debug: values.debug,
|
|
350
74
|
});
|
|
351
|
-
|
|
75
|
+
return;
|
|
352
76
|
}
|
|
353
77
|
if (command === 'runs') {
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
78
|
+
const { runRunsCommand } = await import('./cli/commands/runs.js');
|
|
79
|
+
await runRunsCommand({
|
|
80
|
+
memoryDir: values['memory-dir'],
|
|
81
|
+
url: values.url,
|
|
358
82
|
sessionId: values['session-id'],
|
|
359
|
-
|
|
83
|
+
json: values.json,
|
|
360
84
|
});
|
|
361
|
-
|
|
362
|
-
console.log(' No runs found.');
|
|
363
|
-
}
|
|
364
|
-
else if (values.json) {
|
|
365
|
-
console.log(JSON.stringify(runs, null, 2));
|
|
366
|
-
}
|
|
367
|
-
else {
|
|
368
|
-
for (const r of runs) {
|
|
369
|
-
const icon = r.status === 'completed' ? (r.success ? '\u2713' : '\u2717') : '\u25cb';
|
|
370
|
-
const ts = r.startedAt.slice(0, 16).replace('T', ' ');
|
|
371
|
-
const dur = r.completedAt
|
|
372
|
-
? `${Math.round((new Date(r.completedAt).getTime() - new Date(r.startedAt).getTime()) / 1000)}s`
|
|
373
|
-
: 'running';
|
|
374
|
-
const session = r.sessionId ? ` [${r.sessionId}]` : '';
|
|
375
|
-
const parent = r.parentRunId ? ` \u2190 ${r.parentRunId.slice(0, 20)}` : '';
|
|
376
|
-
console.log(` ${icon} ${r.runId.slice(0, 30)} ${ts} ${dur} ${r.goal.slice(0, 50)}${session}${parent}`);
|
|
377
|
-
if (r.summary)
|
|
378
|
-
console.log(` ${r.summary.slice(0, 80)}`);
|
|
379
|
-
if (r.finalUrl)
|
|
380
|
-
console.log(` ${r.finalUrl}`);
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
process.exit(0);
|
|
85
|
+
return;
|
|
384
86
|
}
|
|
385
87
|
if (command === 'showcase') {
|
|
386
|
-
const {
|
|
387
|
-
await
|
|
88
|
+
const { runShowcaseCommand } = await import('./cli/commands/showcase.js');
|
|
89
|
+
await runShowcaseCommand({
|
|
388
90
|
url: values.url,
|
|
389
91
|
script: values.script,
|
|
390
92
|
capture: values.capture,
|
|
@@ -392,58 +94,28 @@ async function main() {
|
|
|
392
94
|
highlight: values.highlight,
|
|
393
95
|
format: values.format,
|
|
394
96
|
viewport: values.viewport,
|
|
395
|
-
|
|
396
|
-
headless: values.headless
|
|
97
|
+
sink: values.sink,
|
|
98
|
+
headless: values.headless,
|
|
397
99
|
colorScheme: values['color-scheme'],
|
|
398
|
-
scale: values.scale
|
|
100
|
+
scale: values.scale,
|
|
399
101
|
storageState: values['storage-state'],
|
|
400
|
-
|
|
102
|
+
qualityThreshold: values['quality-threshold'],
|
|
401
103
|
});
|
|
402
|
-
|
|
104
|
+
return;
|
|
403
105
|
}
|
|
404
106
|
if (command === 'chrome-debug') {
|
|
405
|
-
const {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
port,
|
|
107
|
+
const { handleChromeDebugCommand } = await import('./cli/commands/chrome-debug.js');
|
|
108
|
+
await handleChromeDebugCommand({
|
|
109
|
+
attachPort: values['attach-port'],
|
|
409
110
|
userDataDir: values['user-data-dir'],
|
|
410
111
|
quiet: values.quiet,
|
|
411
112
|
});
|
|
412
|
-
|
|
113
|
+
return;
|
|
413
114
|
}
|
|
414
115
|
if (command === 'auth') {
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
await handleAuthSave({
|
|
419
|
-
url: values.url || positionals[2],
|
|
420
|
-
output: values['storage-state'] || positionals[3],
|
|
421
|
-
});
|
|
422
|
-
process.exit(0);
|
|
423
|
-
}
|
|
424
|
-
if (sub === 'login') {
|
|
425
|
-
const { handleAuthLogin } = await import('./cli-auth.js');
|
|
426
|
-
await handleAuthLogin({
|
|
427
|
-
url: values.url || positionals[2],
|
|
428
|
-
output: values['storage-state'],
|
|
429
|
-
fill: values.fill,
|
|
430
|
-
cookie: values.cookie,
|
|
431
|
-
waitFor: values['wait-for'],
|
|
432
|
-
waitTimeout: values['wait-timeout'] ? parseInt(values['wait-timeout'], 10) : undefined,
|
|
433
|
-
headless: values.headless,
|
|
434
|
-
});
|
|
435
|
-
process.exit(0);
|
|
436
|
-
}
|
|
437
|
-
if (sub === 'check') {
|
|
438
|
-
const { handleAuthCheck } = await import('./cli-auth.js');
|
|
439
|
-
await handleAuthCheck({
|
|
440
|
-
path: values['storage-state'] || positionals[2],
|
|
441
|
-
origin: positionals[3],
|
|
442
|
-
});
|
|
443
|
-
process.exit(0);
|
|
444
|
-
}
|
|
445
|
-
cliError(`Unknown auth subcommand: ${sub || '(none)'}. Use "auth save", "auth login", or "auth check".`);
|
|
446
|
-
process.exit(1);
|
|
116
|
+
const { runAuthCommand } = await import('./cli/commands/auth.js');
|
|
117
|
+
await runAuthCommand(values, positionals);
|
|
118
|
+
return;
|
|
447
119
|
}
|
|
448
120
|
// `bad attach` is a top-level alias for `bad run --attach`. Every other
|
|
449
121
|
// flag on `run` (--goal, --url, --model, --provider, --base-url,
|
|
@@ -458,1343 +130,40 @@ async function main() {
|
|
|
458
130
|
// planner to emit a structured plan, render it, exit. No execution.
|
|
459
131
|
// terraform plan for browser agents.
|
|
460
132
|
if (command === 'preview') {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
json: values.json,
|
|
476
|
-
maxSteps: values['max-steps'] ? parseInt(values['max-steps'], 10) : undefined,
|
|
477
|
-
headed: values.headed,
|
|
478
|
-
});
|
|
479
|
-
process.exit(result.plan ? 0 : 1);
|
|
480
|
-
}
|
|
481
|
-
catch (err) {
|
|
482
|
-
if (err instanceof PreviewError) {
|
|
483
|
-
cliError(err.message);
|
|
484
|
-
process.exit(1);
|
|
485
|
-
}
|
|
486
|
-
throw err;
|
|
487
|
-
}
|
|
133
|
+
const { runPreviewCommand } = await import('./cli/commands/preview.js');
|
|
134
|
+
await runPreviewCommand({
|
|
135
|
+
goal: values.goal,
|
|
136
|
+
url: values.url,
|
|
137
|
+
model: values.model,
|
|
138
|
+
provider: values.provider,
|
|
139
|
+
apiKey: values['api-key'],
|
|
140
|
+
baseUrl: values['base-url'],
|
|
141
|
+
sink: values.sink,
|
|
142
|
+
json: values.json,
|
|
143
|
+
maxSteps: values['max-steps'],
|
|
144
|
+
headed: values.headed,
|
|
145
|
+
});
|
|
146
|
+
return;
|
|
488
147
|
}
|
|
489
148
|
// `bad share <run-id>` — create a bad-app share link, copy to clipboard.
|
|
490
149
|
if (command === 'share') {
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
try {
|
|
502
|
-
await handleShareCommand({
|
|
503
|
-
runId,
|
|
504
|
-
visibility,
|
|
505
|
-
baseUrl: values['bad-app-url'],
|
|
506
|
-
apiKey: values['api-key'],
|
|
507
|
-
noCopy: values['no-copy'],
|
|
508
|
-
json: values.json,
|
|
509
|
-
});
|
|
510
|
-
process.exit(0);
|
|
511
|
-
}
|
|
512
|
-
catch (err) {
|
|
513
|
-
if (err instanceof ShareError) {
|
|
514
|
-
cliError(err.message);
|
|
515
|
-
process.exit(1);
|
|
516
|
-
}
|
|
517
|
-
throw err;
|
|
518
|
-
}
|
|
150
|
+
const { runShareCommand } = await import('./cli/commands/share.js');
|
|
151
|
+
await runShareCommand({
|
|
152
|
+
runId: positionals[1],
|
|
153
|
+
visibility: values.visibility,
|
|
154
|
+
badAppUrl: values['bad-app-url'],
|
|
155
|
+
apiKey: values['api-key'],
|
|
156
|
+
noCopy: values['no-copy'],
|
|
157
|
+
json: values.json,
|
|
158
|
+
});
|
|
159
|
+
return;
|
|
519
160
|
}
|
|
520
161
|
if (command !== 'run' && command !== 'attach') {
|
|
521
162
|
cliError(`Unknown command: ${command}. Use "run", "attach", "preview", "runs", "view", "share", "chrome-debug", "design-audit", "showcase", "auth", "jobs", or "reports".`);
|
|
522
163
|
process.exit(1);
|
|
523
164
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
cliError('provide --goal "..." --url "..." for a single task, --cases ./cases.json (or --cases-json \'[...]\') for a suite, or --resume-run / --fork-run <runId>.');
|
|
527
|
-
process.exit(1);
|
|
528
|
-
}
|
|
529
|
-
// Load config file, then overlay CLI flags
|
|
530
|
-
const fileConfig = await loadConfig(values.config);
|
|
531
|
-
const mode = values.mode;
|
|
532
|
-
if (mode && !RUN_MODES.includes(mode)) {
|
|
533
|
-
cliError(`unknown mode "${mode}". Valid modes: ${RUN_MODES.join(', ')}`);
|
|
534
|
-
process.exit(1);
|
|
535
|
-
}
|
|
536
|
-
const profile = values.profile;
|
|
537
|
-
if (profile && !DRIVER_PROFILES.includes(profile)) {
|
|
538
|
-
cliError(`unknown profile "${profile}". Valid profiles: ${DRIVER_PROFILES.join(', ')}`);
|
|
539
|
-
process.exit(1);
|
|
540
|
-
}
|
|
541
|
-
// Build CLI overrides (only set values that were explicitly passed)
|
|
542
|
-
const cliOverrides = {};
|
|
543
|
-
if (values.model)
|
|
544
|
-
cliOverrides.model = values.model;
|
|
545
|
-
if (values.provider)
|
|
546
|
-
cliOverrides.provider = values.provider;
|
|
547
|
-
if (values['model-adaptive'] !== undefined)
|
|
548
|
-
cliOverrides.adaptiveModelRouting = values['model-adaptive'];
|
|
549
|
-
if (values['nav-model'])
|
|
550
|
-
cliOverrides.navModel = values['nav-model'];
|
|
551
|
-
if (values['nav-provider'])
|
|
552
|
-
cliOverrides.navProvider = values['nav-provider'];
|
|
553
|
-
if (values['sandbox-backend-type'])
|
|
554
|
-
cliOverrides.sandboxBackendType = values['sandbox-backend-type'];
|
|
555
|
-
if (values['sandbox-backend-profile'])
|
|
556
|
-
cliOverrides.sandboxBackendProfile = values['sandbox-backend-profile'];
|
|
557
|
-
if (values['sandbox-backend-provider'])
|
|
558
|
-
cliOverrides.sandboxBackendProvider = values['sandbox-backend-provider'];
|
|
559
|
-
if (values['api-key'])
|
|
560
|
-
cliOverrides.apiKey = values['api-key'];
|
|
561
|
-
if (values['base-url'])
|
|
562
|
-
cliOverrides.baseUrl = values['base-url'];
|
|
563
|
-
if (values['prompt-file']) {
|
|
564
|
-
const promptPath = path.resolve(values['prompt-file']);
|
|
565
|
-
if (!fs.existsSync(promptPath)) {
|
|
566
|
-
cliError(`prompt file not found: ${promptPath}`);
|
|
567
|
-
process.exit(1);
|
|
568
|
-
}
|
|
569
|
-
cliOverrides.systemPrompt = fs.readFileSync(promptPath, 'utf-8').trim();
|
|
570
|
-
if (!cliOverrides.systemPrompt) {
|
|
571
|
-
cliError(`prompt file is empty: ${promptPath}`);
|
|
572
|
-
process.exit(1);
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
if (profile)
|
|
576
|
-
cliOverrides.profile = profile;
|
|
577
|
-
if (values.browser)
|
|
578
|
-
cliOverrides.browser = values.browser;
|
|
579
|
-
if (values['storage-state'])
|
|
580
|
-
cliOverrides.storageState = values['storage-state'];
|
|
581
|
-
if (values.concurrency)
|
|
582
|
-
cliOverrides.concurrency = parseInt(values.concurrency, 10);
|
|
583
|
-
if (values['max-turns'])
|
|
584
|
-
cliOverrides.maxTurns = parseInt(values['max-turns'], 10);
|
|
585
|
-
if (values['llm-timeout'])
|
|
586
|
-
cliOverrides.llmTimeoutMs = parseInt(values['llm-timeout'], 10);
|
|
587
|
-
if (values.retries)
|
|
588
|
-
cliOverrides.retries = parseInt(values.retries, 10);
|
|
589
|
-
if (values['retry-delay-ms'])
|
|
590
|
-
cliOverrides.retryDelayMs = parseInt(values['retry-delay-ms'], 10);
|
|
591
|
-
if (values['screenshot-interval'])
|
|
592
|
-
cliOverrides.screenshotInterval = parseInt(values['screenshot-interval'], 10);
|
|
593
|
-
if (values.scout !== undefined ||
|
|
594
|
-
values['scout-model'] ||
|
|
595
|
-
values['scout-provider'] ||
|
|
596
|
-
values['scout-vision'] !== undefined ||
|
|
597
|
-
values['scout-max-candidates'] ||
|
|
598
|
-
values['scout-min-top-score'] ||
|
|
599
|
-
values['scout-max-score-gap']) {
|
|
600
|
-
cliOverrides.scout = {
|
|
601
|
-
...(cliOverrides.scout ?? {}),
|
|
602
|
-
};
|
|
603
|
-
if (values.scout !== undefined)
|
|
604
|
-
cliOverrides.scout.enabled = values.scout;
|
|
605
|
-
if (values['scout-model'])
|
|
606
|
-
cliOverrides.scout.model = values['scout-model'];
|
|
607
|
-
if (values['scout-provider']) {
|
|
608
|
-
cliOverrides.scout.provider = values['scout-provider'];
|
|
609
|
-
}
|
|
610
|
-
if (values['scout-vision'] !== undefined)
|
|
611
|
-
cliOverrides.scout.useVision = values['scout-vision'];
|
|
612
|
-
if (values['scout-max-candidates'])
|
|
613
|
-
cliOverrides.scout.maxCandidates = parseInt(values['scout-max-candidates'], 10);
|
|
614
|
-
if (values['scout-min-top-score'])
|
|
615
|
-
cliOverrides.scout.minTopScore = parseInt(values['scout-min-top-score'], 10);
|
|
616
|
-
if (values['scout-max-score-gap'])
|
|
617
|
-
cliOverrides.scout.maxScoreGap = parseInt(values['scout-max-score-gap'], 10);
|
|
618
|
-
}
|
|
619
|
-
if (values.timeout)
|
|
620
|
-
cliOverrides.timeoutMs = parseInt(values.timeout, 10);
|
|
621
|
-
if (values['quality-threshold'])
|
|
622
|
-
cliOverrides.qualityThreshold = parseInt(values['quality-threshold'], 10);
|
|
623
|
-
if (values['trace-scoring'] !== undefined || values['trace-ttl-days']) {
|
|
624
|
-
cliOverrides.memory = {
|
|
625
|
-
...(cliOverrides.memory ?? {}),
|
|
626
|
-
};
|
|
627
|
-
if (values['trace-scoring'] !== undefined)
|
|
628
|
-
cliOverrides.memory.traceScoring = values['trace-scoring'];
|
|
629
|
-
if (values['trace-ttl-days'])
|
|
630
|
-
cliOverrides.memory.traceTtlDays = parseInt(values['trace-ttl-days'], 10);
|
|
631
|
-
}
|
|
632
|
-
if (values.sink)
|
|
633
|
-
cliOverrides.outputDir = values.sink;
|
|
634
|
-
if (values.headless !== undefined)
|
|
635
|
-
cliOverrides.headless = values.headless;
|
|
636
|
-
if (values.proxy)
|
|
637
|
-
cliOverrides.proxy = values.proxy;
|
|
638
|
-
if (values.vision !== undefined)
|
|
639
|
-
cliOverrides.vision = values.vision;
|
|
640
|
-
if (values['vision-strategy'])
|
|
641
|
-
cliOverrides.visionStrategy = values['vision-strategy'];
|
|
642
|
-
if (values['observation-mode'])
|
|
643
|
-
cliOverrides.observationMode = values['observation-mode'];
|
|
644
|
-
if (values['goal-verification'] !== undefined)
|
|
645
|
-
cliOverrides.goalVerification = values['goal-verification'];
|
|
646
|
-
if (values.planner === true)
|
|
647
|
-
cliOverrides.plannerEnabled = true;
|
|
648
|
-
if (values['planner-mode']) {
|
|
649
|
-
const plannerMode = values['planner-mode'];
|
|
650
|
-
if (plannerMode !== 'always' && plannerMode !== 'auto') {
|
|
651
|
-
cliError('--planner-mode must be "always" or "auto"');
|
|
652
|
-
process.exit(1);
|
|
653
|
-
}
|
|
654
|
-
cliOverrides.plannerMode = plannerMode;
|
|
655
|
-
}
|
|
656
|
-
if (values.extension?.length ||
|
|
657
|
-
values['user-data-dir'] ||
|
|
658
|
-
values.wallet !== undefined ||
|
|
659
|
-
values['wallet-auto-approve'] !== undefined ||
|
|
660
|
-
values['wallet-password'] ||
|
|
661
|
-
values['wallet-seed-url']?.length ||
|
|
662
|
-
values['wallet-preflight'] !== undefined ||
|
|
663
|
-
values['wallet-chain-id'] ||
|
|
664
|
-
values['wallet-chain-rpc-url']) {
|
|
665
|
-
cliOverrides.wallet = {};
|
|
666
|
-
if (values.extension?.length)
|
|
667
|
-
cliOverrides.wallet.extensionPaths = values.extension;
|
|
668
|
-
if (values['user-data-dir'])
|
|
669
|
-
cliOverrides.wallet.userDataDir = values['user-data-dir'];
|
|
670
|
-
if (values.wallet !== undefined)
|
|
671
|
-
cliOverrides.wallet.enabled = values.wallet;
|
|
672
|
-
if (values['wallet-auto-approve'] !== undefined) {
|
|
673
|
-
cliOverrides.wallet.autoApprove = values['wallet-auto-approve'];
|
|
674
|
-
}
|
|
675
|
-
if (values['wallet-password']) {
|
|
676
|
-
cliOverrides.wallet.password = values['wallet-password'];
|
|
677
|
-
}
|
|
678
|
-
if (values['wallet-seed-url']?.length ||
|
|
679
|
-
values['wallet-preflight'] !== undefined ||
|
|
680
|
-
values['wallet-chain-id'] ||
|
|
681
|
-
values['wallet-chain-rpc-url']) {
|
|
682
|
-
cliOverrides.wallet.preflight = {};
|
|
683
|
-
if (values['wallet-seed-url']?.length) {
|
|
684
|
-
cliOverrides.wallet.preflight.seedUrls = values['wallet-seed-url'];
|
|
685
|
-
}
|
|
686
|
-
if (values['wallet-preflight'] !== undefined) {
|
|
687
|
-
cliOverrides.wallet.preflight.enabled = values['wallet-preflight'];
|
|
688
|
-
}
|
|
689
|
-
if (values['wallet-chain-id'] || values['wallet-chain-rpc-url']) {
|
|
690
|
-
cliOverrides.wallet.preflight.chain = {};
|
|
691
|
-
if (values['wallet-chain-id']) {
|
|
692
|
-
const parsedChainId = parseInt(values['wallet-chain-id'], 10);
|
|
693
|
-
if (!Number.isFinite(parsedChainId)) {
|
|
694
|
-
throw new Error(`Invalid --wallet-chain-id value: ${values['wallet-chain-id']}`);
|
|
695
|
-
}
|
|
696
|
-
cliOverrides.wallet.preflight.chain.id = parsedChainId;
|
|
697
|
-
}
|
|
698
|
-
if (values['wallet-chain-rpc-url']) {
|
|
699
|
-
cliOverrides.wallet.preflight.chain.rpcUrl = values['wallet-chain-rpc-url'];
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
if (values['profile-dir'])
|
|
705
|
-
cliOverrides.profileDir = values['profile-dir'];
|
|
706
|
-
if (values['cdp-url'])
|
|
707
|
-
cliOverrides.cdpUrl = values['cdp-url'];
|
|
708
|
-
// --attach resolves to cdpUrl by probing a running Chrome's DevTools
|
|
709
|
-
// endpoint. Done here (pre-config-merge) so the existing cdpUrl path at
|
|
710
|
-
// cli.ts:916 takes over unchanged. Conflicts with wallet/extension/profile
|
|
711
|
-
// flags are surfaced up-front rather than silently ignored.
|
|
712
|
-
if (values.attach) {
|
|
713
|
-
if (values['cdp-url']) {
|
|
714
|
-
cliError('--attach and --cdp-url are mutually exclusive (both select a CDP endpoint). Use one.');
|
|
715
|
-
process.exit(1);
|
|
716
|
-
}
|
|
717
|
-
const { resolveAttachEndpoint, validateAttachConflicts } = await import('./cli-attach.js');
|
|
718
|
-
const conflicts = validateAttachConflicts({
|
|
719
|
-
walletEnabled: Boolean(cliOverrides.wallet?.enabled) || Boolean(values.extension?.length),
|
|
720
|
-
profileDir: values['profile-dir'],
|
|
721
|
-
extensionPaths: values.extension,
|
|
722
|
-
userDataDir: values['user-data-dir'],
|
|
723
|
-
});
|
|
724
|
-
if (!conflicts.ok) {
|
|
725
|
-
for (const err of conflicts.errors)
|
|
726
|
-
cliError(err);
|
|
727
|
-
process.exit(1);
|
|
728
|
-
}
|
|
729
|
-
const port = values['attach-port'] ? parseInt(values['attach-port'], 10) : undefined;
|
|
730
|
-
if (port !== undefined && !Number.isFinite(port)) {
|
|
731
|
-
cliError(`Invalid --attach-port value: ${values['attach-port']}`);
|
|
732
|
-
process.exit(1);
|
|
733
|
-
}
|
|
734
|
-
const info = await resolveAttachEndpoint({ port }).catch((err) => {
|
|
735
|
-
cliError(err instanceof Error ? err.message : String(err));
|
|
736
|
-
process.exit(1);
|
|
737
|
-
});
|
|
738
|
-
cliOverrides.cdpUrl = info.webSocketDebuggerUrl;
|
|
739
|
-
if (!values.quiet)
|
|
740
|
-
cliLog('attach', `connected to ${info.browser ?? 'Chrome'}`);
|
|
741
|
-
}
|
|
742
|
-
if (values.memory !== undefined || values['memory-dir']) {
|
|
743
|
-
cliOverrides.memory = {
|
|
744
|
-
...(cliOverrides.memory ?? {}),
|
|
745
|
-
};
|
|
746
|
-
if (values.memory !== undefined)
|
|
747
|
-
cliOverrides.memory.enabled = values.memory;
|
|
748
|
-
if (values['memory-dir'])
|
|
749
|
-
cliOverrides.memory.dir = values['memory-dir'];
|
|
750
|
-
}
|
|
751
|
-
// Resource blocking
|
|
752
|
-
if (values['block-analytics'] || values['block-images'] || values['block-media']) {
|
|
753
|
-
cliOverrides.resourceBlocking = {};
|
|
754
|
-
if (values['block-analytics'])
|
|
755
|
-
cliOverrides.resourceBlocking.blockAnalytics = true;
|
|
756
|
-
if (values['block-images'])
|
|
757
|
-
cliOverrides.resourceBlocking.blockImages = true;
|
|
758
|
-
if (values['block-media'])
|
|
759
|
-
cliOverrides.resourceBlocking.blockMedia = true;
|
|
760
|
-
}
|
|
761
|
-
// Profile presets apply only when equivalent flags were not explicitly set.
|
|
762
|
-
if (profile === 'stealth') {
|
|
763
|
-
if (values.headless === undefined)
|
|
764
|
-
cliOverrides.headless = false;
|
|
765
|
-
if (values['goal-verification'] === undefined)
|
|
766
|
-
cliOverrides.goalVerification = true;
|
|
767
|
-
if (!values['screenshot-interval'])
|
|
768
|
-
cliOverrides.screenshotInterval = 2;
|
|
769
|
-
if (!values['block-analytics'] && !values['block-images'] && !values['block-media']) {
|
|
770
|
-
cliOverrides.resourceBlocking = {
|
|
771
|
-
...(cliOverrides.resourceBlocking ?? {}),
|
|
772
|
-
blockAnalytics: true,
|
|
773
|
-
};
|
|
774
|
-
}
|
|
775
|
-
cliOverrides.microPlan = {
|
|
776
|
-
...(cliOverrides.microPlan ?? {}),
|
|
777
|
-
enabled: true,
|
|
778
|
-
maxActionsPerTurn: cliOverrides.microPlan?.maxActionsPerTurn ?? 2,
|
|
779
|
-
};
|
|
780
|
-
}
|
|
781
|
-
else if (profile === 'benchmark-webbench' || profile === 'benchmark-webbench-stealth') {
|
|
782
|
-
if (!values['llm-timeout'])
|
|
783
|
-
cliOverrides.llmTimeoutMs = 20_000;
|
|
784
|
-
cliOverrides.compactFirstTurn = true;
|
|
785
|
-
if (!values.retries)
|
|
786
|
-
cliOverrides.retries = 1;
|
|
787
|
-
if (!values['retry-delay-ms'])
|
|
788
|
-
cliOverrides.retryDelayMs = 250;
|
|
789
|
-
if (values.vision === undefined)
|
|
790
|
-
cliOverrides.vision = false;
|
|
791
|
-
if (!values['screenshot-interval'])
|
|
792
|
-
cliOverrides.screenshotInterval = 0;
|
|
793
|
-
if (values['goal-verification'] === undefined)
|
|
794
|
-
cliOverrides.goalVerification = true;
|
|
795
|
-
if (profile === 'benchmark-webbench-stealth' && values.headless === undefined) {
|
|
796
|
-
cliOverrides.headless = false;
|
|
797
|
-
}
|
|
798
|
-
if (!values['block-analytics'] && !values['block-images'] && !values['block-media']) {
|
|
799
|
-
cliOverrides.resourceBlocking = {
|
|
800
|
-
...(cliOverrides.resourceBlocking ?? {}),
|
|
801
|
-
blockAnalytics: true,
|
|
802
|
-
blockImages: true,
|
|
803
|
-
blockMedia: true,
|
|
804
|
-
};
|
|
805
|
-
}
|
|
806
|
-
cliOverrides.microPlan = {
|
|
807
|
-
...(cliOverrides.microPlan ?? {}),
|
|
808
|
-
enabled: true,
|
|
809
|
-
maxActionsPerTurn: cliOverrides.microPlan?.maxActionsPerTurn ?? 2,
|
|
810
|
-
};
|
|
811
|
-
}
|
|
812
|
-
else if (profile === 'benchmark-webvoyager') {
|
|
813
|
-
if (values.vision === undefined)
|
|
814
|
-
cliOverrides.vision = true;
|
|
815
|
-
if (!values['screenshot-interval'])
|
|
816
|
-
cliOverrides.screenshotInterval = 2;
|
|
817
|
-
if (values['goal-verification'] === undefined)
|
|
818
|
-
cliOverrides.goalVerification = true;
|
|
819
|
-
cliOverrides.microPlan = {
|
|
820
|
-
...(cliOverrides.microPlan ?? {}),
|
|
821
|
-
enabled: true,
|
|
822
|
-
maxActionsPerTurn: cliOverrides.microPlan?.maxActionsPerTurn ?? 2,
|
|
823
|
-
};
|
|
824
|
-
}
|
|
825
|
-
// Mode presets apply only when equivalent flags were not explicitly set
|
|
826
|
-
// AND the profile didn't already set them. This lets benchmark profiles
|
|
827
|
-
// (e.g. benchmark-webvoyager) enable vision/screenshots without fast-explore
|
|
828
|
-
// clobbering those values.
|
|
829
|
-
if (mode === 'fast-explore') {
|
|
830
|
-
if (values.vision === undefined && cliOverrides.vision === undefined)
|
|
831
|
-
cliOverrides.vision = false;
|
|
832
|
-
if (!values['screenshot-interval'] && cliOverrides.screenshotInterval === undefined)
|
|
833
|
-
cliOverrides.screenshotInterval = 0;
|
|
834
|
-
if (values['goal-verification'] === undefined)
|
|
835
|
-
cliOverrides.goalVerification = true;
|
|
836
|
-
if (values['quality-threshold'] === undefined)
|
|
837
|
-
cliOverrides.qualityThreshold = 0;
|
|
838
|
-
if (!values['block-analytics'] && !values['block-images'] && !values['block-media']) {
|
|
839
|
-
cliOverrides.resourceBlocking = {
|
|
840
|
-
...(cliOverrides.resourceBlocking ?? {}),
|
|
841
|
-
blockAnalytics: true,
|
|
842
|
-
};
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
else if (mode === 'full-evidence') {
|
|
846
|
-
if (values.vision === undefined && cliOverrides.vision === undefined)
|
|
847
|
-
cliOverrides.vision = true;
|
|
848
|
-
if (!values['screenshot-interval'] && cliOverrides.screenshotInterval === undefined)
|
|
849
|
-
cliOverrides.screenshotInterval = 3;
|
|
850
|
-
if (values['goal-verification'] === undefined)
|
|
851
|
-
cliOverrides.goalVerification = true;
|
|
852
|
-
}
|
|
853
|
-
// Vision and hybrid observation modes require screenshots and a stable
|
|
854
|
-
// 1024×768 coordinate space.
|
|
855
|
-
if (cliOverrides.observationMode === 'vision' || cliOverrides.observationMode === 'hybrid') {
|
|
856
|
-
if (cliOverrides.vision === undefined)
|
|
857
|
-
cliOverrides.vision = true;
|
|
858
|
-
if (cliOverrides.visionStrategy === undefined)
|
|
859
|
-
cliOverrides.visionStrategy = 'always';
|
|
860
|
-
if (cliOverrides.screenshotInterval === undefined || cliOverrides.screenshotInterval === 0) {
|
|
861
|
-
cliOverrides.screenshotInterval = 1;
|
|
862
|
-
}
|
|
863
|
-
if (!cliOverrides.viewport) {
|
|
864
|
-
cliOverrides.viewport = { width: 1024, height: 768 };
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
const driverConfig = mergeConfig(fileConfig, cliOverrides);
|
|
868
|
-
const launchPlan = buildBrowserLaunchPlan(driverConfig);
|
|
869
|
-
const quiet = values.quiet;
|
|
870
|
-
for (const warning of launchPlan.warnings) {
|
|
871
|
-
if (!quiet) {
|
|
872
|
-
cliWarn(warning);
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
if (launchPlan.errors.length > 0) {
|
|
876
|
-
for (const error of launchPlan.errors) {
|
|
877
|
-
cliError(error);
|
|
878
|
-
}
|
|
879
|
-
process.exit(1);
|
|
880
|
-
}
|
|
881
|
-
// Dynamic imports keep startup fast. Prefer patchright for lower browser
|
|
882
|
-
// automation fingerprinting, falling back to regular Playwright.
|
|
883
|
-
const isStealthProfile = launchPlan.profile.includes('stealth');
|
|
884
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
885
|
-
let browserLib;
|
|
886
|
-
try {
|
|
887
|
-
browserLib = await import('patchright');
|
|
888
|
-
}
|
|
889
|
-
catch {
|
|
890
|
-
browserLib = await import('playwright');
|
|
891
|
-
}
|
|
892
|
-
const { chromium, firefox, webkit } = browserLib;
|
|
893
|
-
const { PlaywrightDriver } = await import('./drivers/playwright.js');
|
|
894
|
-
const { TestRunner } = await import('./test-runner.js');
|
|
895
|
-
const { FilesystemSink } = await import('./artifacts/filesystem-sink.js');
|
|
896
|
-
const { loadExtensions } = await import('./extensions/loader.js');
|
|
897
|
-
const { TurnEventBus } = await import('./runner/events.js');
|
|
898
|
-
// Optional live event bus + SSE viewer. Constructed only when `--live` is
|
|
899
|
-
// passed; otherwise the runner uses an internal no-op bus and pays nothing.
|
|
900
|
-
// The bus is shared across the entire suite so a single SSE connection
|
|
901
|
-
// observes all turns of all test cases. When `--stream <url>` is passed
|
|
902
|
-
// the bus is always created so the webhook streamer can subscribe.
|
|
903
|
-
const liveEnabled = values.live === true;
|
|
904
|
-
const streamUrl = typeof values.stream === 'string' && values.stream.length > 0 ? values.stream : undefined;
|
|
905
|
-
const needsBus = liveEnabled || !!streamUrl;
|
|
906
|
-
const liveBus = needsBus ? new TurnEventBus() : undefined;
|
|
907
|
-
const liveCancelController = liveEnabled ? new AbortController() : undefined;
|
|
908
|
-
let liveViewHandle;
|
|
909
|
-
if (liveEnabled && liveBus) {
|
|
910
|
-
const { runLiveView } = await import('./cli-view-live.js');
|
|
911
|
-
liveViewHandle = await runLiveView({
|
|
912
|
-
bus: liveBus,
|
|
913
|
-
...(liveCancelController ? { cancelController: liveCancelController } : {}),
|
|
914
|
-
port: values.port ? parseInt(values.port, 10) : undefined,
|
|
915
|
-
noOpen: values['no-open'] === true,
|
|
916
|
-
});
|
|
917
|
-
}
|
|
918
|
-
// Webhook streamer. When `--stream <url>` is passed, subscribe
|
|
919
|
-
// to the bus and POST every event to <url> as it fires. Auth via
|
|
920
|
-
// `--stream-token` or $BAD_STREAM_TOKEN. Non-fatal on failure — the
|
|
921
|
-
// canonical record is always events.jsonl on disk.
|
|
922
|
-
let webhookStreamer;
|
|
923
|
-
if (streamUrl && liveBus) {
|
|
924
|
-
const { WebhookStreamer } = await import('./runner/stream-webhook.js');
|
|
925
|
-
const token = values['stream-token'] || process.env.BAD_STREAM_TOKEN;
|
|
926
|
-
const streamId = `stream_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
927
|
-
webhookStreamer = new WebhookStreamer({
|
|
928
|
-
url: streamUrl,
|
|
929
|
-
authToken: token,
|
|
930
|
-
streamId,
|
|
931
|
-
onError: (err, dropped) => {
|
|
932
|
-
if (!values.json)
|
|
933
|
-
cliWarn(`stream: ${err.message} (dropped ${dropped} event${dropped === 1 ? '' : 's'})`);
|
|
934
|
-
},
|
|
935
|
-
}).attach(liveBus);
|
|
936
|
-
if (!values.json)
|
|
937
|
-
cliLog('stream', `POST → ${streamUrl} (id ${streamId})`);
|
|
938
|
-
}
|
|
939
|
-
// Interrupt controller. `--interrupt` enables keyboard pause/
|
|
940
|
-
// resume/abort during a run. No-op in non-TTY (CI, piped output).
|
|
941
|
-
let interruptController;
|
|
942
|
-
let detachInterrupt;
|
|
943
|
-
if (values.interrupt === true && process.stdin.isTTY) {
|
|
944
|
-
const { InterruptController } = await import('./runner/interrupt-controller.js');
|
|
945
|
-
interruptController = new InterruptController({
|
|
946
|
-
onStatus: (msg) => { if (!values.json)
|
|
947
|
-
cliLog('interrupt', msg); },
|
|
948
|
-
});
|
|
949
|
-
detachInterrupt = interruptController.attach();
|
|
950
|
-
}
|
|
951
|
-
// Auto-discover bad.config.{ts,mjs,js,...} from cwd plus any explicit
|
|
952
|
-
// --extension paths. Failures are reported but never fatal: a broken user
|
|
953
|
-
// config should warn, not abort the run.
|
|
954
|
-
const explicitExtPaths = (values.extension ?? []);
|
|
955
|
-
const explicitExtArr = Array.isArray(explicitExtPaths)
|
|
956
|
-
? explicitExtPaths
|
|
957
|
-
: explicitExtPaths
|
|
958
|
-
? [explicitExtPaths]
|
|
959
|
-
: [];
|
|
960
|
-
// Domain skills: markdown-based per-host rule libraries under
|
|
961
|
-
// skills/domain/<host>/SKILL.md. They plumb into the same BadExtension
|
|
962
|
-
// pipeline as user `bad.config.mjs` files via addRulesForDomain, so the
|
|
963
|
-
// existing setExtensionRules injection at brain/index.ts:899 picks them up
|
|
964
|
-
// with no second injection site. Gated by BAD_DOMAIN_SKILLS_DISABLED=1.
|
|
965
|
-
const domainSkillsDisabled = process.env.BAD_DOMAIN_SKILLS_DISABLED === '1';
|
|
966
|
-
let domainSkillExtension;
|
|
967
|
-
let domainSkillsLoadedCount = 0;
|
|
968
|
-
if (!domainSkillsDisabled) {
|
|
969
|
-
const { loadDomainSkills, buildDomainSkillExtension } = await import('./skills/domain-loader.js');
|
|
970
|
-
const domainLoad = await loadDomainSkills();
|
|
971
|
-
if (domainLoad.skills.length > 0) {
|
|
972
|
-
domainSkillExtension = buildDomainSkillExtension(domainLoad.skills);
|
|
973
|
-
domainSkillsLoadedCount = domainLoad.skills.length;
|
|
974
|
-
}
|
|
975
|
-
if (domainLoad.errors.length > 0 && !values.json) {
|
|
976
|
-
for (const err of domainLoad.errors) {
|
|
977
|
-
cliWarn(`domain-skill load failed: ${err.path} — ${err.error}`);
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
const extensionLoad = await loadExtensions({ explicitPaths: explicitExtArr });
|
|
982
|
-
if (extensionLoad.loadedFrom.length > 0 && !values.json) {
|
|
983
|
-
cliLog('extensions', `loaded ${extensionLoad.loadedFrom.length}: ${extensionLoad.loadedFrom.join(', ')}`);
|
|
984
|
-
}
|
|
985
|
-
if (extensionLoad.errors.length > 0 && !values.json) {
|
|
986
|
-
for (const err of extensionLoad.errors) {
|
|
987
|
-
cliWarn(`extension load failed: ${err.path} — ${err.error}`);
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
if (domainSkillExtension && domainSkillsLoadedCount > 0) {
|
|
991
|
-
// Re-resolve the bundle with the domain-skill synthetic extension
|
|
992
|
-
// merged in. Resolving twice is cheap (the list is short) and keeps
|
|
993
|
-
// domainSkillExtension from having to plumb through a separate wire.
|
|
994
|
-
const { resolveExtensions } = await import('./extensions/types.js');
|
|
995
|
-
const combined = resolveExtensions([...extensionLoad.resolved.extensions, domainSkillExtension]);
|
|
996
|
-
extensionLoad.resolved = combined;
|
|
997
|
-
if (!values.json)
|
|
998
|
-
cliLog('skills', `domain: ${domainSkillsLoadedCount} loaded`);
|
|
999
|
-
}
|
|
1000
|
-
// Macros. Loaded alongside domain skills; gated by BAD_MACROS_DISABLED=1.
|
|
1001
|
-
// The registry is passed to PlaywrightDriver and its promptBlock to BrowserAgent.
|
|
1002
|
-
const macrosDisabled = process.env.BAD_MACROS_DISABLED === '1';
|
|
1003
|
-
let macroRegistry;
|
|
1004
|
-
if (!macrosDisabled) {
|
|
1005
|
-
const { loadMacros, buildMacroRegistry } = await import('./skills/macro-loader.js');
|
|
1006
|
-
const macroLoad = await loadMacros();
|
|
1007
|
-
if (macroLoad.errors.length > 0 && !values.json) {
|
|
1008
|
-
for (const err of macroLoad.errors) {
|
|
1009
|
-
cliWarn(`macro load failed: ${err.path} — ${err.error}`);
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
if (macroLoad.macros.length > 0) {
|
|
1013
|
-
macroRegistry = buildMacroRegistry(macroLoad.macros);
|
|
1014
|
-
if (!values.json)
|
|
1015
|
-
cliLog('skills', `macros: ${macroLoad.macros.length} loaded`);
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
const concurrency = launchPlan.concurrency;
|
|
1019
|
-
const maxTurns = driverConfig.maxTurns ?? 30;
|
|
1020
|
-
const screenshotInterval = driverConfig.screenshotInterval ?? 5;
|
|
1021
|
-
const timeoutMs = driverConfig.timeoutMs ?? 600_000;
|
|
1022
|
-
const browserName = driverConfig.browser ?? 'chromium';
|
|
1023
|
-
const debug = values.debug;
|
|
1024
|
-
const sinkDir = driverConfig.outputDir ?? './agent-results';
|
|
1025
|
-
const resolvedProvider = driverConfig.provider || 'openai';
|
|
1026
|
-
const resolvedApiKey = resolveProviderApiKey(resolvedProvider, driverConfig.apiKey);
|
|
1027
|
-
const resolvedModel = resolveProviderModelName(resolvedProvider, driverConfig.model, {
|
|
1028
|
-
sandboxBackendType: resolvedProvider === 'sandbox-backend' ? driverConfig.sandboxBackendType : undefined,
|
|
1029
|
-
});
|
|
1030
|
-
const resolvedNavProvider = driverConfig.navProvider || resolvedProvider;
|
|
1031
|
-
const resolvedNavModel = driverConfig.navModel
|
|
1032
|
-
? resolveProviderModelName(resolvedNavProvider, driverConfig.navModel, {
|
|
1033
|
-
sandboxBackendType: resolvedNavProvider === 'sandbox-backend' ? driverConfig.sandboxBackendType : undefined,
|
|
1034
|
-
})
|
|
1035
|
-
: undefined;
|
|
1036
|
-
const resolvedSupervisorProvider = driverConfig.supervisor?.provider || resolvedProvider;
|
|
1037
|
-
const resolvedSupervisorModel = resolveProviderModelName(resolvedSupervisorProvider, driverConfig.supervisor?.model || resolvedModel, {
|
|
1038
|
-
sandboxBackendType: resolvedSupervisorProvider === 'sandbox-backend' ? driverConfig.sandboxBackendType : undefined,
|
|
1039
|
-
});
|
|
1040
|
-
const config = {
|
|
1041
|
-
...toAgentConfig(driverConfig),
|
|
1042
|
-
model: resolvedModel,
|
|
1043
|
-
...(resolvedNavModel ? { navModel: resolvedNavModel } : {}),
|
|
1044
|
-
apiKey: resolvedApiKey,
|
|
1045
|
-
baseUrl: driverConfig.baseUrl || process.env.LLM_BASE_URL,
|
|
1046
|
-
supervisor: driverConfig.supervisor
|
|
1047
|
-
? {
|
|
1048
|
-
...driverConfig.supervisor,
|
|
1049
|
-
provider: resolvedSupervisorProvider,
|
|
1050
|
-
model: resolvedSupervisorModel,
|
|
1051
|
-
}
|
|
1052
|
-
: undefined,
|
|
1053
|
-
debug,
|
|
1054
|
-
walletMode: Boolean(driverConfig.wallet?.enabled),
|
|
1055
|
-
walletAddress: driverConfig.wallet?.address,
|
|
1056
|
-
};
|
|
1057
|
-
// Create project store for memory + run registry
|
|
1058
|
-
const memoryEnabled = driverConfig.memory?.enabled === true;
|
|
1059
|
-
const projectStore = memoryEnabled
|
|
1060
|
-
? new ProjectStore(driverConfig.memory?.dir)
|
|
1061
|
-
: undefined;
|
|
1062
|
-
const runRegistry = projectStore
|
|
1063
|
-
? new RunRegistry(projectStore.getRoot())
|
|
1064
|
-
: undefined;
|
|
1065
|
-
// Build test cases
|
|
1066
|
-
let cases;
|
|
1067
|
-
if (values['resume-run'] || values['fork-run']) {
|
|
1068
|
-
// Resume or fork from a previous run
|
|
1069
|
-
if (!runRegistry) {
|
|
1070
|
-
cliError('--resume-run and --fork-run require memory to be enabled');
|
|
1071
|
-
process.exit(1);
|
|
1072
|
-
}
|
|
1073
|
-
const isResume = Boolean(values['resume-run']);
|
|
1074
|
-
const sourceRunId = (values['resume-run'] || values['fork-run']);
|
|
1075
|
-
const scenario = isResume
|
|
1076
|
-
? runRegistry.buildResumeScenario(sourceRunId, values.goal)
|
|
1077
|
-
: runRegistry.buildForkScenario(sourceRunId, values.goal || '');
|
|
1078
|
-
if (!scenario) {
|
|
1079
|
-
cliError(`run "${sourceRunId}" not found in registry`);
|
|
1080
|
-
process.exit(1);
|
|
1081
|
-
}
|
|
1082
|
-
if (!isResume && !values.goal) {
|
|
1083
|
-
cliError('--fork-run requires --goal');
|
|
1084
|
-
process.exit(1);
|
|
1085
|
-
}
|
|
1086
|
-
cases = [{
|
|
1087
|
-
id: `${isResume ? 'resume' : 'fork'}-${sourceRunId.slice(0, 20)}`,
|
|
1088
|
-
name: scenario.goal.slice(0, 60),
|
|
1089
|
-
startUrl: values.url || scenario.startUrl,
|
|
1090
|
-
goal: scenario.goal,
|
|
1091
|
-
allowedDomains: parseAllowedDomains(values['allowed-domains']),
|
|
1092
|
-
maxTurns,
|
|
1093
|
-
timeoutMs,
|
|
1094
|
-
priority: 0,
|
|
1095
|
-
sessionId: scenario.sessionId,
|
|
1096
|
-
parentRunId: scenario.parentRunId,
|
|
1097
|
-
}];
|
|
1098
|
-
}
|
|
1099
|
-
else if (values['cases-json'] || values.cases) {
|
|
1100
|
-
const raw = values['cases-json'] || fs.readFileSync(path.resolve(values.cases), 'utf-8');
|
|
1101
|
-
const parsed = JSON.parse(raw);
|
|
1102
|
-
const rawCases = Array.isArray(parsed) ? parsed : [parsed];
|
|
1103
|
-
// Ensure required fields — spread raw case first so explicit fields become defaults
|
|
1104
|
-
cases = rawCases.map((c, i) => ({
|
|
1105
|
-
id: c.id || `case-${i}`,
|
|
1106
|
-
name: c.name || c.goal?.slice(0, 60) || `Case ${i}`,
|
|
1107
|
-
startUrl: c.startUrl || c.url || values.url || '',
|
|
1108
|
-
goal: c.goal || '',
|
|
1109
|
-
allowedDomains: Array.isArray(c.allowedDomains)
|
|
1110
|
-
? c.allowedDomains.filter((domain) => typeof domain === 'string' && domain.length > 0)
|
|
1111
|
-
: parseAllowedDomains(values['allowed-domains']),
|
|
1112
|
-
maxTurns: c.maxTurns || maxTurns,
|
|
1113
|
-
timeoutMs: c.timeoutMs || timeoutMs,
|
|
1114
|
-
priority: c.priority ?? i,
|
|
1115
|
-
}));
|
|
1116
|
-
}
|
|
1117
|
-
else {
|
|
1118
|
-
cases = [{
|
|
1119
|
-
id: 'cli-task',
|
|
1120
|
-
name: values.goal.slice(0, 60),
|
|
1121
|
-
startUrl: values.url || '',
|
|
1122
|
-
goal: values.goal,
|
|
1123
|
-
allowedDomains: parseAllowedDomains(values['allowed-domains']),
|
|
1124
|
-
maxTurns,
|
|
1125
|
-
timeoutMs,
|
|
1126
|
-
priority: 0,
|
|
1127
|
-
sessionId: values['session-id'],
|
|
1128
|
-
}];
|
|
1129
|
-
}
|
|
1130
|
-
// Apply --session-id to all cases from file too
|
|
1131
|
-
if (values['session-id'] && cases.length > 0 && cases[0].id !== 'cli-task') {
|
|
1132
|
-
const sid = values['session-id'];
|
|
1133
|
-
cases = cases.map(c => ({ ...c, sessionId: c.sessionId || sid }));
|
|
1134
|
-
}
|
|
1135
|
-
const persona = values.persona;
|
|
1136
|
-
if (persona) {
|
|
1137
|
-
if (!isPersonaId(persona)) {
|
|
1138
|
-
cliError(`unknown persona "${persona}". Valid personas: ${listPersonaIds().join(', ')}`);
|
|
1139
|
-
process.exit(1);
|
|
1140
|
-
}
|
|
1141
|
-
cases = cases.map((c) => ({
|
|
1142
|
-
...c,
|
|
1143
|
-
goal: withPersonaDirective({
|
|
1144
|
-
persona,
|
|
1145
|
-
goal: c.goal,
|
|
1146
|
-
startUrl: c.startUrl,
|
|
1147
|
-
}),
|
|
1148
|
-
}));
|
|
1149
|
-
}
|
|
1150
|
-
const renderer = (!quiet && !values.json) ? new CliRenderer({ debug }) : null;
|
|
1151
|
-
if (renderer) {
|
|
1152
|
-
const version = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8')).version;
|
|
1153
|
-
renderer.banner({
|
|
1154
|
-
version,
|
|
1155
|
-
provider: config.provider || 'openai',
|
|
1156
|
-
model: config.model,
|
|
1157
|
-
browser: browserName,
|
|
1158
|
-
testCount: cases.length,
|
|
1159
|
-
concurrency,
|
|
1160
|
-
mode: mode || undefined,
|
|
1161
|
-
profile: driverConfig.profile,
|
|
1162
|
-
adaptiveRouting: config.adaptiveModelRouting ? {
|
|
1163
|
-
navProvider: config.navProvider || config.provider || 'openai',
|
|
1164
|
-
navModel: config.navModel || 'gpt-4.1-mini',
|
|
1165
|
-
} : undefined,
|
|
1166
|
-
outputDir: sinkDir,
|
|
1167
|
-
});
|
|
1168
|
-
}
|
|
1169
|
-
// Set up artifact sink
|
|
1170
|
-
const sink = new FilesystemSink(path.resolve(sinkDir));
|
|
1171
|
-
const videoDir = path.join(sinkDir, '_videos');
|
|
1172
|
-
const viewport = launchPlan.viewport;
|
|
1173
|
-
const storageStatePath = driverConfig.storageState
|
|
1174
|
-
? path.resolve(driverConfig.storageState)
|
|
1175
|
-
: undefined;
|
|
1176
|
-
if (storageStatePath && !fs.existsSync(storageStatePath)) {
|
|
1177
|
-
cliError(`storage state file not found: ${storageStatePath}`);
|
|
1178
|
-
process.exit(1);
|
|
1179
|
-
}
|
|
1180
|
-
if (launchPlan.persistentContext && !launchPlan.cdpUrl && browserName !== 'chromium') {
|
|
1181
|
-
const feature = launchPlan.walletMode ? 'Wallet mode' : '--profile-dir';
|
|
1182
|
-
throw new Error(`${feature} requires Chromium. Set --browser chromium.`);
|
|
1183
|
-
}
|
|
1184
|
-
// Ensure clean exit on interrupt
|
|
1185
|
-
process.on('SIGINT', () => { renderer?.destroy(); process.exit(130); });
|
|
1186
|
-
process.on('SIGTERM', () => { renderer?.destroy(); process.exit(143); });
|
|
1187
|
-
renderer?.launchStart(browserName);
|
|
1188
|
-
// Set up browser
|
|
1189
|
-
let browser;
|
|
1190
|
-
let persistentContext;
|
|
1191
|
-
let stopWalletAutoApprover;
|
|
1192
|
-
const launchDiagnostics = {};
|
|
1193
|
-
// CDP connection — attach to an existing browser (Atlas, Chrome, Brave, etc.)
|
|
1194
|
-
let cdpUrl = launchPlan.cdpUrl || process.env.BROWSER_ENDPOINT;
|
|
1195
|
-
let cdpConnected = false;
|
|
1196
|
-
if (cdpUrl) {
|
|
1197
|
-
// Auto-discover WebSocket URL from HTTP endpoint
|
|
1198
|
-
if (cdpUrl.startsWith('http://') || cdpUrl.startsWith('https://')) {
|
|
1199
|
-
try {
|
|
1200
|
-
const versionUrl = cdpUrl.replace(/\/$/, '') + '/json/version';
|
|
1201
|
-
const res = await fetch(versionUrl);
|
|
1202
|
-
const info = await res.json();
|
|
1203
|
-
if (info.webSocketDebuggerUrl) {
|
|
1204
|
-
if (info.Browser && !quiet)
|
|
1205
|
-
cliLog('cdp', `connected to ${info.Browser}`);
|
|
1206
|
-
cdpUrl = info.webSocketDebuggerUrl;
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
catch {
|
|
1210
|
-
// Fall through — try the URL as-is
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
const cdpStartedAt = Date.now();
|
|
1214
|
-
if (cdpUrl.includes('/devtools/') || browserName === 'chromium') {
|
|
1215
|
-
browser = await chromium.connectOverCDP(cdpUrl);
|
|
1216
|
-
}
|
|
1217
|
-
else {
|
|
1218
|
-
const browserType = browserName === 'firefox' ? firefox : browserName === 'webkit' ? webkit : chromium;
|
|
1219
|
-
browser = await browserType.connect(cdpUrl);
|
|
1220
|
-
}
|
|
1221
|
-
launchDiagnostics.browserLaunchMs = Date.now() - cdpStartedAt;
|
|
1222
|
-
launchDiagnostics.cdpUrl = cdpUrl;
|
|
1223
|
-
cdpConnected = true;
|
|
1224
|
-
}
|
|
1225
|
-
else if (launchPlan.persistentContext) {
|
|
1226
|
-
for (const extensionPath of launchPlan.extensionPaths) {
|
|
1227
|
-
if (!fs.existsSync(extensionPath)) {
|
|
1228
|
-
throw new Error(`Wallet extension path does not exist: ${extensionPath}`);
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
1231
|
-
const userDataDir = launchPlan.userDataDir ?? path.resolve(launchPlan.walletMode ? '.agent-wallet-profile' : '.agent-profile');
|
|
1232
|
-
fs.mkdirSync(userDataDir, { recursive: true });
|
|
1233
|
-
const persistentLaunchStartedAt = Date.now();
|
|
1234
|
-
persistentContext = await chromium.launchPersistentContext(userDataDir, {
|
|
1235
|
-
channel: isStealthProfile ? 'chrome' : 'chromium',
|
|
1236
|
-
headless: launchPlan.headless,
|
|
1237
|
-
args: launchPlan.browserArgs,
|
|
1238
|
-
viewport,
|
|
1239
|
-
recordVideo: { dir: videoDir, size: viewport },
|
|
1240
|
-
...(launchPlan.proxyServer
|
|
1241
|
-
? { proxy: { server: launchPlan.proxyServer, ...(launchPlan.proxyBypass ? { bypass: launchPlan.proxyBypass } : {}) } }
|
|
1242
|
-
: {}),
|
|
1243
|
-
...(launchPlan.ignoreHTTPSErrors ? { ignoreHTTPSErrors: true } : {}),
|
|
1244
|
-
});
|
|
1245
|
-
launchDiagnostics.browserLaunchMs = Date.now() - persistentLaunchStartedAt;
|
|
1246
|
-
await applyStorageStateToPersistentContext(persistentContext, storageStatePath);
|
|
1247
|
-
if (launchPlan.walletMode) {
|
|
1248
|
-
const walletConfig = driverConfig.wallet ?? {};
|
|
1249
|
-
// Intercept page-level JSON-RPC so dApps see wallet balances from the
|
|
1250
|
-
// local Anvil fork. Only forward user-specific calls (eth_getBalance
|
|
1251
|
-
// for the wallet, eth_call with wallet address in calldata). Pool data
|
|
1252
|
-
// and protocol calls go to real endpoints for reliability.
|
|
1253
|
-
const walletRpcUrl = walletConfig.preflight?.chain?.rpcUrl;
|
|
1254
|
-
if (walletRpcUrl) {
|
|
1255
|
-
// Default to Anvil's first derived address if no wallet address configured
|
|
1256
|
-
const walletAddrFull = (walletConfig.address ?? '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266').toLowerCase();
|
|
1257
|
-
const walletAddrHex = walletAddrFull.replace('0x', '');
|
|
1258
|
-
await persistentContext.route('**/*', async (route) => {
|
|
1259
|
-
try {
|
|
1260
|
-
const frame = route.request().frame();
|
|
1261
|
-
if (frame && frame.url().startsWith('chrome-extension://')) {
|
|
1262
|
-
await route.continue();
|
|
1263
|
-
return;
|
|
1264
|
-
}
|
|
1265
|
-
}
|
|
1266
|
-
catch {
|
|
1267
|
-
await route.continue();
|
|
1268
|
-
return;
|
|
1269
|
-
}
|
|
1270
|
-
if (route.request().method() !== 'POST') {
|
|
1271
|
-
await route.continue();
|
|
1272
|
-
return;
|
|
1273
|
-
}
|
|
1274
|
-
const ct = route.request().headers()['content-type'] ?? '';
|
|
1275
|
-
if (!ct.includes('json')) {
|
|
1276
|
-
await route.continue();
|
|
1277
|
-
return;
|
|
1278
|
-
}
|
|
1279
|
-
const postData = route.request().postData();
|
|
1280
|
-
if (!postData) {
|
|
1281
|
-
await route.continue();
|
|
1282
|
-
return;
|
|
1283
|
-
}
|
|
1284
|
-
try {
|
|
1285
|
-
const body = JSON.parse(postData);
|
|
1286
|
-
const items = Array.isArray(body) ? body : [body];
|
|
1287
|
-
// Check if any item involves the wallet (balance, contract call, simulation)
|
|
1288
|
-
const isUserQuery = items.some((item) => {
|
|
1289
|
-
const method = item.method;
|
|
1290
|
-
if (!method)
|
|
1291
|
-
return false;
|
|
1292
|
-
if (method === 'eth_getBalance') {
|
|
1293
|
-
const params = item.params;
|
|
1294
|
-
return params?.[0]?.toLowerCase() === walletAddrFull;
|
|
1295
|
-
}
|
|
1296
|
-
if (method === 'eth_call' || method === 'eth_estimateGas') {
|
|
1297
|
-
const params = item.params;
|
|
1298
|
-
const txObj = params?.[0];
|
|
1299
|
-
if (!txObj)
|
|
1300
|
-
return false;
|
|
1301
|
-
const from = txObj.from?.toLowerCase() ?? '';
|
|
1302
|
-
const data = txObj.data?.toLowerCase() ?? '';
|
|
1303
|
-
return from === walletAddrFull || data.includes(walletAddrHex);
|
|
1304
|
-
}
|
|
1305
|
-
if (method === 'eth_getTransactionCount') {
|
|
1306
|
-
const params = item.params;
|
|
1307
|
-
return params?.[0]?.toLowerCase() === walletAddrFull;
|
|
1308
|
-
}
|
|
1309
|
-
return false;
|
|
1310
|
-
});
|
|
1311
|
-
if (!isUserQuery) {
|
|
1312
|
-
await route.continue();
|
|
1313
|
-
return;
|
|
1314
|
-
}
|
|
1315
|
-
// Normalize: some dApps (Aave) omit jsonrpc/id — Anvil requires them
|
|
1316
|
-
let nextId = 1;
|
|
1317
|
-
const normalized = items.map((item) => {
|
|
1318
|
-
const out = { ...item, jsonrpc: '2.0', id: item.id ?? nextId++ };
|
|
1319
|
-
delete out.chainId;
|
|
1320
|
-
return out;
|
|
1321
|
-
});
|
|
1322
|
-
const payload = Array.isArray(body) ? normalized : normalized[0];
|
|
1323
|
-
const res = await fetch(walletRpcUrl, {
|
|
1324
|
-
method: 'POST',
|
|
1325
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1326
|
-
body: JSON.stringify(payload),
|
|
1327
|
-
});
|
|
1328
|
-
await route.fulfill({
|
|
1329
|
-
status: res.status,
|
|
1330
|
-
contentType: 'application/json',
|
|
1331
|
-
body: await res.text(),
|
|
1332
|
-
});
|
|
1333
|
-
}
|
|
1334
|
-
catch {
|
|
1335
|
-
await route.continue();
|
|
1336
|
-
}
|
|
1337
|
-
});
|
|
1338
|
-
}
|
|
1339
|
-
const shouldAutoApprove = walletConfig.autoApprove ?? true;
|
|
1340
|
-
if (shouldAutoApprove) {
|
|
1341
|
-
stopWalletAutoApprover = await startWalletAutoApprover(persistentContext, {
|
|
1342
|
-
password: walletConfig.password,
|
|
1343
|
-
tickMs: walletConfig.tickMs,
|
|
1344
|
-
actionSelectors: walletConfig.actionSelectors,
|
|
1345
|
-
});
|
|
1346
|
-
}
|
|
1347
|
-
const preflightEnabled = walletConfig.preflight?.enabled ?? true;
|
|
1348
|
-
const preflightSeedUrls = walletConfig.preflight?.seedUrls && walletConfig.preflight.seedUrls.length > 0
|
|
1349
|
-
? walletConfig.preflight.seedUrls
|
|
1350
|
-
: [...new Set(cases.map((testCase) => testCase.startUrl).filter(Boolean))];
|
|
1351
|
-
if (preflightEnabled && preflightSeedUrls.length > 0) {
|
|
1352
|
-
const preflight = await runWalletPreflight(persistentContext, {
|
|
1353
|
-
seedUrls: preflightSeedUrls,
|
|
1354
|
-
password: walletConfig.password,
|
|
1355
|
-
actionSelectors: walletConfig.actionSelectors,
|
|
1356
|
-
promptPaths: walletConfig.promptPaths,
|
|
1357
|
-
connectSelectors: walletConfig.connectSelectors,
|
|
1358
|
-
connectorSelectors: walletConfig.connectorSelectors,
|
|
1359
|
-
requestAccounts: walletConfig.preflight?.requestAccounts,
|
|
1360
|
-
accountsTimeoutMs: walletConfig.preflight?.accountsTimeoutMs,
|
|
1361
|
-
maxChainSwitchAttempts: walletConfig.preflight?.maxChainSwitchAttempts,
|
|
1362
|
-
chain: walletConfig.preflight?.chain,
|
|
1363
|
-
log: quiet ? undefined : (message) => cliLog('wallet', message),
|
|
1364
|
-
});
|
|
1365
|
-
if (!preflight.ok) {
|
|
1366
|
-
const failed = preflight.results.find((resultEntry) => !resultEntry.ready);
|
|
1367
|
-
const details = failed?.details ?? 'unknown reason';
|
|
1368
|
-
throw new Error(`Wallet preflight failed for ${preflight.failedUrl ?? 'unknown origin'} (${details})`);
|
|
1369
|
-
}
|
|
1370
|
-
}
|
|
1371
|
-
}
|
|
1372
|
-
}
|
|
1373
|
-
else {
|
|
1374
|
-
const browserType = browserName === 'firefox'
|
|
1375
|
-
? firefox
|
|
1376
|
-
: browserName === 'webkit'
|
|
1377
|
-
? webkit
|
|
1378
|
-
: chromium;
|
|
1379
|
-
const browserLaunchStartedAt = Date.now();
|
|
1380
|
-
browser = await browserType.launch({
|
|
1381
|
-
headless: launchPlan.headless,
|
|
1382
|
-
...(browserName === 'chromium' ? { args: launchPlan.browserArgs } : {}),
|
|
1383
|
-
// System Chrome for stealth profiles only — real TLS/JA3 fingerprint
|
|
1384
|
-
// fixes anti-bot blocking. But system Chrome renders differently than
|
|
1385
|
-
// bundled Chromium on some sites (Allrecipes click timeouts, Amazon
|
|
1386
|
-
// layout shifts), so only enable when anti-bot evasion is needed.
|
|
1387
|
-
...(isStealthProfile && browserName === 'chromium' ? { channel: 'chrome' } : {}),
|
|
1388
|
-
// Residential/SOCKS5/HTTP proxy — routes all traffic through the proxy
|
|
1389
|
-
...(launchPlan.proxyServer
|
|
1390
|
-
? { proxy: { server: launchPlan.proxyServer, ...(launchPlan.proxyBypass ? { bypass: launchPlan.proxyBypass } : {}) } }
|
|
1391
|
-
: {}),
|
|
1392
|
-
});
|
|
1393
|
-
launchDiagnostics.browserLaunchMs = Date.now() - browserLaunchStartedAt;
|
|
1394
|
-
}
|
|
1395
|
-
// Headless Chromium sends "HeadlessChrome/..." in the default User-Agent.
|
|
1396
|
-
// CDNs like Akamai reject this with ERR_HTTP2_PROTOCOL_ERROR before any JS
|
|
1397
|
-
// stealth patches can run. Build a clean UA from the browser version.
|
|
1398
|
-
const headlessUserAgent = launchPlan.headless && browser
|
|
1399
|
-
? (() => {
|
|
1400
|
-
const ver = browser.version();
|
|
1401
|
-
const plat = process.platform;
|
|
1402
|
-
const platformToken = plat === 'win32'
|
|
1403
|
-
? 'Windows NT 10.0; Win64; x64'
|
|
1404
|
-
: plat === 'linux'
|
|
1405
|
-
? 'X11; Linux x86_64'
|
|
1406
|
-
: 'Macintosh; Intel Mac OS X 10_15_7';
|
|
1407
|
-
return `Mozilla/5.0 (${platformToken}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${ver} Safari/537.36`;
|
|
1408
|
-
})()
|
|
1409
|
-
: undefined;
|
|
1410
|
-
const driverFactory = async () => {
|
|
1411
|
-
const contextStartedAt = Date.now();
|
|
1412
|
-
// CDP: reuse the browser's default context (preserves user cookies/sessions).
|
|
1413
|
-
// Persistent context: use the already-opened persistent context.
|
|
1414
|
-
// Default: create a fresh isolated context.
|
|
1415
|
-
let context;
|
|
1416
|
-
if (cdpConnected) {
|
|
1417
|
-
// Reuse the user's existing browser context — cookies, localStorage, extensions intact
|
|
1418
|
-
const contexts = browser.contexts();
|
|
1419
|
-
// A freshly-created CDP context still honors egress; the reused contexts[0] is the user's own
|
|
1420
|
-
// browser context (see the --cdp-url egress warning in buildBrowserLaunchPlan), left untouched.
|
|
1421
|
-
context = contexts[0] ?? await browser.newContext({
|
|
1422
|
-
viewport,
|
|
1423
|
-
...(launchPlan.ignoreHTTPSErrors ? { ignoreHTTPSErrors: true } : {}),
|
|
1424
|
-
});
|
|
1425
|
-
}
|
|
1426
|
-
else if (persistentContext) {
|
|
1427
|
-
context = persistentContext;
|
|
1428
|
-
}
|
|
1429
|
-
else {
|
|
1430
|
-
context = await browser.newContext({
|
|
1431
|
-
viewport,
|
|
1432
|
-
recordVideo: { dir: videoDir, size: viewport },
|
|
1433
|
-
storageState: storageStatePath,
|
|
1434
|
-
locale: 'en-US',
|
|
1435
|
-
timezoneId: 'America/New_York',
|
|
1436
|
-
...(headlessUserAgent ? { userAgent: headlessUserAgent } : {}),
|
|
1437
|
-
// Accept the managed egress proxy's MITM cert (its CA isn't in Chromium's trust store).
|
|
1438
|
-
...(launchPlan.ignoreHTTPSErrors ? { ignoreHTTPSErrors: true } : {}),
|
|
1439
|
-
extraHTTPHeaders: {
|
|
1440
|
-
'Accept-Language': 'en-US,en;q=0.9',
|
|
1441
|
-
},
|
|
1442
|
-
});
|
|
1443
|
-
}
|
|
1444
|
-
// Stealth patches: only for Playwright-controlled contexts (not real user browsers)
|
|
1445
|
-
if (!cdpConnected) {
|
|
1446
|
-
await context.addInitScript(`
|
|
1447
|
-
// navigator.webdriver — explicit override (backup for --disable-blink-features)
|
|
1448
|
-
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
|
|
1449
|
-
// navigator.plugins — empty in headless, non-empty in real browsers
|
|
1450
|
-
Object.defineProperty(navigator, 'plugins', {
|
|
1451
|
-
get: () => [
|
|
1452
|
-
{ name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format' },
|
|
1453
|
-
{ name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '' },
|
|
1454
|
-
{ name: 'Native Client', filename: 'internal-nacl-plugin', description: '' },
|
|
1455
|
-
],
|
|
1456
|
-
});
|
|
1457
|
-
// navigator.languages — must match Accept-Language header
|
|
1458
|
-
Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] });
|
|
1459
|
-
// hardware signals — realistic desktop values
|
|
1460
|
-
Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 8 });
|
|
1461
|
-
Object.defineProperty(navigator, 'deviceMemory', { get: () => 8 });
|
|
1462
|
-
// window.chrome — full stub matching real Chrome
|
|
1463
|
-
if (!window.chrome) window.chrome = {};
|
|
1464
|
-
if (!window.chrome.runtime) window.chrome.runtime = { id: undefined };
|
|
1465
|
-
if (!window.chrome.app) window.chrome.app = { isInstalled: false, InstallState: { DISABLED: 'disabled', INSTALLED: 'installed', NOT_INSTALLED: 'not_installed' }, RunningState: { CANNOT_RUN: 'cannot_run', READY_TO_RUN: 'ready_to_run', RUNNING: 'running' } };
|
|
1466
|
-
if (!window.chrome.csi) window.chrome.csi = function() { return { onloadT: Date.now(), startE: Date.now(), pageT: Date.now() - performance.timing.navigationStart }; };
|
|
1467
|
-
if (!window.chrome.loadTimes) window.chrome.loadTimes = function() { return { commitLoadTime: Date.now() / 1000, connectionInfo: 'h2', finishDocumentLoadTime: Date.now() / 1000, finishLoadTime: Date.now() / 1000, firstPaintAfterLoadTime: 0, firstPaintTime: Date.now() / 1000, navigationType: 'Other', npnNegotiatedProtocol: 'h2', requestTime: Date.now() / 1000 - 0.16, startLoadTime: Date.now() / 1000 - 0.16, wasAlternateProtocolAvailable: false, wasFetchedViaSpdy: true, wasNpnNegotiated: true }; };
|
|
1468
|
-
// WebGL vendor/renderer — match real GPU values
|
|
1469
|
-
try {
|
|
1470
|
-
const getParameter = WebGLRenderingContext.prototype.getParameter;
|
|
1471
|
-
WebGLRenderingContext.prototype.getParameter = function(parameter) {
|
|
1472
|
-
if (parameter === 37445) return 'Intel Inc.';
|
|
1473
|
-
if (parameter === 37446) return 'Intel Iris OpenGL Engine';
|
|
1474
|
-
return getParameter.call(this, parameter);
|
|
1475
|
-
};
|
|
1476
|
-
} catch (_) {}
|
|
1477
|
-
try {
|
|
1478
|
-
const getParameter2 = WebGL2RenderingContext.prototype.getParameter;
|
|
1479
|
-
WebGL2RenderingContext.prototype.getParameter = function(parameter) {
|
|
1480
|
-
if (parameter === 37445) return 'Intel Inc.';
|
|
1481
|
-
if (parameter === 37446) return 'Intel Iris OpenGL Engine';
|
|
1482
|
-
return getParameter2.call(this, parameter);
|
|
1483
|
-
};
|
|
1484
|
-
} catch (_) {}
|
|
1485
|
-
// window.outerWidth/outerHeight — 0 in headless, match viewport in real browsers
|
|
1486
|
-
if (window.outerWidth === 0) Object.defineProperty(window, 'outerWidth', { get: () => window.innerWidth });
|
|
1487
|
-
if (window.outerHeight === 0) Object.defineProperty(window, 'outerHeight', { get: () => window.innerHeight + 85 });
|
|
1488
|
-
// Patch permissions API — cover all permission types bots commonly mis-handle
|
|
1489
|
-
try {
|
|
1490
|
-
const origQuery = navigator.permissions.query.bind(navigator.permissions);
|
|
1491
|
-
navigator.permissions.query = (params) => {
|
|
1492
|
-
const deny = ['notifications', 'geolocation', 'camera', 'microphone', 'payment-handler'];
|
|
1493
|
-
if (deny.includes(params.name))
|
|
1494
|
-
return Promise.resolve({ state: 'denied', onchange: null });
|
|
1495
|
-
return origQuery(params);
|
|
1496
|
-
};
|
|
1497
|
-
} catch (_) {}
|
|
1498
|
-
// Canvas fingerprint noise — add imperceptible per-session noise to canvas readback
|
|
1499
|
-
// so each session produces a unique fingerprint (defeats static fingerprint matching)
|
|
1500
|
-
try {
|
|
1501
|
-
const seed = Math.random() * 0xffff | 0;
|
|
1502
|
-
const noisify = (canvas) => {
|
|
1503
|
-
try {
|
|
1504
|
-
const ctx = canvas.getContext('2d');
|
|
1505
|
-
if (!ctx) return;
|
|
1506
|
-
const { width: w, height: h } = canvas;
|
|
1507
|
-
if (w === 0 || h === 0) return;
|
|
1508
|
-
const img = ctx.getImageData(0, 0, w, h);
|
|
1509
|
-
const d = img.data;
|
|
1510
|
-
for (let i = 0; i < d.length; i += 4) {
|
|
1511
|
-
// deterministic per-pixel noise from seed + position
|
|
1512
|
-
d[i] = d[i] ^ ((seed + i) & 1);
|
|
1513
|
-
}
|
|
1514
|
-
ctx.putImageData(img, 0, 0);
|
|
1515
|
-
} catch (_) {}
|
|
1516
|
-
};
|
|
1517
|
-
const origToDataURL = HTMLCanvasElement.prototype.toDataURL;
|
|
1518
|
-
HTMLCanvasElement.prototype.toDataURL = function(...args) {
|
|
1519
|
-
noisify(this);
|
|
1520
|
-
return origToDataURL.apply(this, args);
|
|
1521
|
-
};
|
|
1522
|
-
const origToBlob = HTMLCanvasElement.prototype.toBlob;
|
|
1523
|
-
HTMLCanvasElement.prototype.toBlob = function(...args) {
|
|
1524
|
-
noisify(this);
|
|
1525
|
-
return origToBlob.apply(this, args);
|
|
1526
|
-
};
|
|
1527
|
-
} catch (_) {}
|
|
1528
|
-
// Fix CDP screenX/screenY bug — CDP Input.dispatchMouseEvent sets
|
|
1529
|
-
// screenX=clientX, screenY=clientY which never happens in real browsers.
|
|
1530
|
-
// Cloudflare Turnstile actively checks this. Add a per-session window
|
|
1531
|
-
// offset so screenX/screenY are realistic and internally consistent.
|
|
1532
|
-
try {
|
|
1533
|
-
const winX = Math.floor(Math.random() * 200) + 50;
|
|
1534
|
-
const winY = Math.floor(Math.random() * 100) + 50;
|
|
1535
|
-
const chrome = 85;
|
|
1536
|
-
Object.defineProperty(MouseEvent.prototype, 'screenX', {
|
|
1537
|
-
get() { return this.clientX + winX; },
|
|
1538
|
-
configurable: true,
|
|
1539
|
-
});
|
|
1540
|
-
Object.defineProperty(MouseEvent.prototype, 'screenY', {
|
|
1541
|
-
get() { return this.clientY + winY + chrome; },
|
|
1542
|
-
configurable: true,
|
|
1543
|
-
});
|
|
1544
|
-
} catch (_) {}
|
|
1545
|
-
// navigator.connection — missing in headless, present in real Chrome
|
|
1546
|
-
try {
|
|
1547
|
-
if (!navigator.connection) {
|
|
1548
|
-
Object.defineProperty(navigator, 'connection', {
|
|
1549
|
-
get: () => ({
|
|
1550
|
-
effectiveType: '4g',
|
|
1551
|
-
rtt: 50,
|
|
1552
|
-
downlink: 10,
|
|
1553
|
-
saveData: false,
|
|
1554
|
-
onchange: null,
|
|
1555
|
-
}),
|
|
1556
|
-
});
|
|
1557
|
-
}
|
|
1558
|
-
} catch (_) {}
|
|
1559
|
-
// Notification.permission — default differs in headless
|
|
1560
|
-
try {
|
|
1561
|
-
if (typeof Notification !== 'undefined' && Notification.permission === 'default') {
|
|
1562
|
-
Object.defineProperty(Notification, 'permission', { get: () => 'denied' });
|
|
1563
|
-
}
|
|
1564
|
-
} catch (_) {}
|
|
1565
|
-
`);
|
|
1566
|
-
}
|
|
1567
|
-
const contextCreateMs = Date.now() - contextStartedAt;
|
|
1568
|
-
const pageStartedAt = Date.now();
|
|
1569
|
-
const page = await context.newPage();
|
|
1570
|
-
const pageCreateMs = Date.now() - pageStartedAt;
|
|
1571
|
-
// Cap per-action timeout so one stuck click can't consume the whole case budget.
|
|
1572
|
-
// Default 30s is fine for long runs; for short cases (120s) use at most 15s.
|
|
1573
|
-
const actionTimeout = Math.min(30_000, Math.max(5_000, Math.floor(timeoutMs / 8)));
|
|
1574
|
-
const driver = new PlaywrightDriver(page, {
|
|
1575
|
-
captureScreenshots: config.vision,
|
|
1576
|
-
screenshotQuality: 50,
|
|
1577
|
-
disableCdp: driverConfig.disableCdp,
|
|
1578
|
-
timeout: actionTimeout,
|
|
1579
|
-
visionStrategy: config.visionStrategy,
|
|
1580
|
-
screenshotInterval,
|
|
1581
|
-
showCursor: values['show-cursor'],
|
|
1582
|
-
...(macroRegistry ? { macros: macroRegistry } : {}),
|
|
1583
|
-
});
|
|
1584
|
-
// Apply resource blocking if configured
|
|
1585
|
-
const resourceBlockingStartedAt = Date.now();
|
|
1586
|
-
if (driverConfig.resourceBlocking) {
|
|
1587
|
-
await driver.setupResourceBlocking(driverConfig.resourceBlocking);
|
|
1588
|
-
}
|
|
1589
|
-
const resourceBlockingSetupMs = driverConfig.resourceBlocking
|
|
1590
|
-
? Date.now() - resourceBlockingStartedAt
|
|
1591
|
-
: 0;
|
|
1592
|
-
const diagnostics = {
|
|
1593
|
-
browserName,
|
|
1594
|
-
headless: launchPlan.headless,
|
|
1595
|
-
walletMode: launchPlan.walletMode,
|
|
1596
|
-
browserLaunchMs: Number(launchDiagnostics.browserLaunchMs ?? 0),
|
|
1597
|
-
contextCreateMs,
|
|
1598
|
-
pageCreateMs,
|
|
1599
|
-
resourceBlockingSetupMs,
|
|
1600
|
-
storageStateApplied: Boolean(storageStatePath) && !cdpConnected,
|
|
1601
|
-
persistentContext: Boolean(persistentContext),
|
|
1602
|
-
cdpConnected,
|
|
1603
|
-
};
|
|
1604
|
-
// Wrap in a Driver that properly tears down context on close
|
|
1605
|
-
const wrappedDriver = {
|
|
1606
|
-
observe: () => driver.observe(),
|
|
1607
|
-
execute: (action) => driver.execute(action),
|
|
1608
|
-
getPage: () => driver.getPage?.(),
|
|
1609
|
-
screenshot: () => driver.screenshot(),
|
|
1610
|
-
getDiagnostics: () => diagnostics,
|
|
1611
|
-
async close() {
|
|
1612
|
-
await driver.close().catch(() => { });
|
|
1613
|
-
await page.close().catch(() => { });
|
|
1614
|
-
if (!persistentContext && !cdpConnected) {
|
|
1615
|
-
await context.close().catch(() => { });
|
|
1616
|
-
}
|
|
1617
|
-
},
|
|
1618
|
-
};
|
|
1619
|
-
return wrappedDriver;
|
|
1620
|
-
};
|
|
1621
|
-
// Create a single driver for sequential mode
|
|
1622
|
-
let singleDriver;
|
|
1623
|
-
if (concurrency <= 1) {
|
|
1624
|
-
singleDriver = await driverFactory();
|
|
1625
|
-
}
|
|
1626
|
-
renderer?.launchDone();
|
|
1627
|
-
const runner = new TestRunner({
|
|
1628
|
-
config,
|
|
1629
|
-
defaultTimeoutMs: timeoutMs,
|
|
1630
|
-
driver: singleDriver,
|
|
1631
|
-
driverFactory: concurrency > 1 ? driverFactory : undefined,
|
|
1632
|
-
enableMemory: memoryEnabled,
|
|
1633
|
-
trajectoryStorePath: driverConfig.memory?.dir,
|
|
1634
|
-
projectStore,
|
|
1635
|
-
concurrency,
|
|
1636
|
-
screenshotInterval,
|
|
1637
|
-
artifactSink: sink,
|
|
1638
|
-
extensions: extensionLoad.resolved,
|
|
1639
|
-
...(macroRegistry ? { macroPromptBlock: macroRegistry.promptBlock } : {}),
|
|
1640
|
-
...(liveBus ? { eventBus: liveBus } : {}),
|
|
1641
|
-
...(interruptController
|
|
1642
|
-
? { beforeTurn: async () => { await interruptController.waitIfPaused(); } }
|
|
1643
|
-
: {}),
|
|
1644
|
-
onProgress: (event) => {
|
|
1645
|
-
if (values.json) {
|
|
1646
|
-
console.log(JSON.stringify(event));
|
|
1647
|
-
return;
|
|
1648
|
-
}
|
|
1649
|
-
if (!renderer)
|
|
1650
|
-
return;
|
|
1651
|
-
switch (event.type) {
|
|
1652
|
-
case 'suite:start':
|
|
1653
|
-
renderer.suiteStart(event.totalTests);
|
|
1654
|
-
break;
|
|
1655
|
-
case 'test:start':
|
|
1656
|
-
renderer.testStart(event.testId, event.testName);
|
|
1657
|
-
break;
|
|
1658
|
-
case 'test:turn':
|
|
1659
|
-
renderer.testTurn(event.testId, event.turn, event.action, event.durationMs, event.modelUsed);
|
|
1660
|
-
break;
|
|
1661
|
-
case 'test:complete':
|
|
1662
|
-
renderer.testComplete(event.testId, event.passed, event.verdict, event.turnsUsed, event.durationMs, event.estimatedCostUsd);
|
|
1663
|
-
break;
|
|
1664
|
-
case 'suite:complete':
|
|
1665
|
-
renderer.suiteComplete(event.passed, event.failed, event.skipped, event.totalMs, event.totalCostUsd, event.manifestUri);
|
|
1666
|
-
break;
|
|
1667
|
-
}
|
|
1668
|
-
},
|
|
1669
|
-
});
|
|
1670
|
-
let result;
|
|
1671
|
-
let runError;
|
|
1672
|
-
try {
|
|
1673
|
-
// Wire interrupt controller. Pressing `q` aborts via the suite-level signal.
|
|
1674
|
-
const interruptSignal = interruptController
|
|
1675
|
-
? (() => {
|
|
1676
|
-
const ac = new AbortController();
|
|
1677
|
-
interruptController.on('abort', () => ac.abort('interrupted by user'));
|
|
1678
|
-
if (interruptController.isAborted)
|
|
1679
|
-
ac.abort('interrupted by user');
|
|
1680
|
-
return ac.signal;
|
|
1681
|
-
})()
|
|
1682
|
-
: undefined;
|
|
1683
|
-
// If --live already provided a signal, merge both. Prefer the user
|
|
1684
|
-
// interrupt signal so `q` wins even when --live is set.
|
|
1685
|
-
const mergedSignal = interruptSignal ?? liveCancelController?.signal;
|
|
1686
|
-
result = await runner.runSuite(cases, mergedSignal ? { signal: mergedSignal } : undefined);
|
|
1687
|
-
// Write reports for each configured format
|
|
1688
|
-
const { generateReport } = await import('./test-report.js');
|
|
1689
|
-
const reporters = driverConfig.reporters ?? ['json'];
|
|
1690
|
-
const reportDir = path.resolve(sinkDir);
|
|
1691
|
-
fs.mkdirSync(reportDir, { recursive: true });
|
|
1692
|
-
const formatMeta = {
|
|
1693
|
-
json: { ext: 'json', contentType: 'application/json' },
|
|
1694
|
-
markdown: { ext: 'md', contentType: 'text/markdown' },
|
|
1695
|
-
html: { ext: 'html', contentType: 'text/html' },
|
|
1696
|
-
junit: { ext: 'xml', contentType: 'application/xml' },
|
|
1697
|
-
};
|
|
1698
|
-
for (const format of reporters) {
|
|
1699
|
-
const meta = formatMeta[format];
|
|
1700
|
-
if (!meta)
|
|
1701
|
-
continue;
|
|
1702
|
-
try {
|
|
1703
|
-
const report = generateReport(result, { format, includeTurns: format === 'markdown' });
|
|
1704
|
-
const reportPath = path.join(reportDir, `report.${meta.ext}`);
|
|
1705
|
-
fs.writeFileSync(reportPath, report);
|
|
1706
|
-
renderer?.report(reportPath);
|
|
1707
|
-
}
|
|
1708
|
-
catch {
|
|
1709
|
-
// Report generation is best-effort
|
|
1710
|
-
}
|
|
1711
|
-
}
|
|
1712
|
-
// Also write report to stdout if JSON mode
|
|
1713
|
-
if (values.json && !quiet) {
|
|
1714
|
-
console.log(JSON.stringify(result, null, 2));
|
|
1715
|
-
}
|
|
1716
|
-
}
|
|
1717
|
-
catch (err) {
|
|
1718
|
-
runError = err;
|
|
1719
|
-
}
|
|
1720
|
-
finally {
|
|
1721
|
-
renderer?.destroy();
|
|
1722
|
-
// Detach interrupt controller first so stdin leaves raw mode before
|
|
1723
|
-
// shutdown logs are written.
|
|
1724
|
-
if (detachInterrupt)
|
|
1725
|
-
detachInterrupt();
|
|
1726
|
-
// Flush and close the stream webhook so the final events (run-completed,
|
|
1727
|
-
// suite-complete) reach the endpoint before the CLI exits.
|
|
1728
|
-
if (webhookStreamer)
|
|
1729
|
-
await webhookStreamer.close().catch(() => { });
|
|
1730
|
-
await singleDriver?.close?.().catch(() => { });
|
|
1731
|
-
stopWalletAutoApprover?.();
|
|
1732
|
-
await persistentContext?.close().catch(() => { });
|
|
1733
|
-
await browser?.close().catch(() => { });
|
|
1734
|
-
}
|
|
1735
|
-
if (runError) {
|
|
1736
|
-
if (liveViewHandle) {
|
|
1737
|
-
await liveViewHandle.close().catch(() => { });
|
|
1738
|
-
}
|
|
1739
|
-
throw runError;
|
|
1740
|
-
}
|
|
1741
|
-
const runLabel = values.cases
|
|
1742
|
-
? `${path.basename(values.cases)} · cli run`
|
|
1743
|
-
: `${(values.goal || values['resume-run'] || values['fork-run'] || 'run').slice(0, 80)} · cli run`;
|
|
1744
|
-
await syncLocalBenchmarkRun(path.resolve(sinkDir), runLabel);
|
|
1745
|
-
// In --live mode, the user usually wants to scrub the final state in the
|
|
1746
|
-
// viewer after the run finishes. Hold the process open until SIGINT and
|
|
1747
|
-
// shut down the live server cleanly when the user hits Ctrl+C.
|
|
1748
|
-
if (liveViewHandle) {
|
|
1749
|
-
cliLog('live', `run complete — viewer at ${liveViewHandle.url} (Ctrl+C to exit)`);
|
|
1750
|
-
await new Promise((resolve) => {
|
|
1751
|
-
const onSigint = () => {
|
|
1752
|
-
liveViewHandle.close().finally(() => resolve());
|
|
1753
|
-
};
|
|
1754
|
-
process.once('SIGINT', onSigint);
|
|
1755
|
-
});
|
|
1756
|
-
}
|
|
1757
|
-
process.exit((result?.summary.failed ?? 1) > 0 ? 1 : 0);
|
|
1758
|
-
}
|
|
1759
|
-
function parseAllowedDomains(value) {
|
|
1760
|
-
if (!value)
|
|
1761
|
-
return undefined;
|
|
1762
|
-
const domains = value
|
|
1763
|
-
.split(',')
|
|
1764
|
-
.map((entry) => entry.trim().toLowerCase())
|
|
1765
|
-
.filter(Boolean);
|
|
1766
|
-
return domains.length > 0 ? [...new Set(domains)] : undefined;
|
|
1767
|
-
}
|
|
1768
|
-
async function syncLocalBenchmarkRun(outPath, label) {
|
|
1769
|
-
if (process.env.ABD_BENCHMARK_SYNC === '0')
|
|
1770
|
-
return;
|
|
1771
|
-
const importerPath = path.resolve(path.join(new URL('.', import.meta.url).pathname, '..', '..', 'abd-app', 'worker', 'scripts', 'import-local-benchmarks.mjs'));
|
|
1772
|
-
if (!fs.existsSync(importerPath))
|
|
1773
|
-
return;
|
|
1774
|
-
if (!fs.existsSync(outPath))
|
|
1775
|
-
return;
|
|
1776
|
-
const args = [importerPath, '--path', outPath, '--label', label];
|
|
1777
|
-
const userEmail = process.env.ABD_BENCHMARK_USER_EMAIL;
|
|
1778
|
-
if (userEmail)
|
|
1779
|
-
args.push('--user-email', userEmail);
|
|
1780
|
-
const exitCode = await new Promise((resolve) => {
|
|
1781
|
-
const child = spawn('node', args, {
|
|
1782
|
-
cwd: path.dirname(importerPath),
|
|
1783
|
-
env: {
|
|
1784
|
-
...process.env,
|
|
1785
|
-
ABD_BENCHMARK_SYNC: '0',
|
|
1786
|
-
},
|
|
1787
|
-
stdio: 'inherit',
|
|
1788
|
-
});
|
|
1789
|
-
child.once('error', () => resolve(1));
|
|
1790
|
-
child.once('close', (code) => resolve(code ?? 1));
|
|
1791
|
-
});
|
|
1792
|
-
if (exitCode !== 0) {
|
|
1793
|
-
if (process.env.ABD_BENCHMARK_SYNC_STRICT === '1') {
|
|
1794
|
-
throw new Error(`abd-app benchmark import failed for ${outPath}`);
|
|
1795
|
-
}
|
|
1796
|
-
cliWarn(`abd-app benchmark import skipped after non-zero exit for ${outPath}`);
|
|
1797
|
-
}
|
|
165
|
+
const { runRunCommand } = await import('./cli/commands/run.js');
|
|
166
|
+
await runRunCommand(values);
|
|
1798
167
|
}
|
|
1799
168
|
// Flush telemetry on every exit path (`process.exit` from inside main, throw, or
|
|
1800
169
|
// natural completion). beforeExit fires before Node decides to terminate, so
|