@tangle-network/browser-agent-driver 0.6.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/LICENSE +196 -0
- package/README.md +149 -0
- package/dist/artifacts/composite-sink.d.ts +15 -0
- package/dist/artifacts/composite-sink.d.ts.map +1 -0
- package/dist/artifacts/composite-sink.js +27 -0
- package/dist/artifacts/composite-sink.js.map +1 -0
- package/dist/artifacts/filesystem-sink.d.ts +26 -0
- package/dist/artifacts/filesystem-sink.d.ts.map +1 -0
- package/dist/artifacts/filesystem-sink.js +81 -0
- package/dist/artifacts/filesystem-sink.js.map +1 -0
- package/dist/artifacts/types.d.ts +97 -0
- package/dist/artifacts/types.d.ts.map +1 -0
- package/dist/artifacts/types.js +9 -0
- package/dist/artifacts/types.js.map +1 -0
- package/dist/artifacts/webhook-sink.d.ts +71 -0
- package/dist/artifacts/webhook-sink.d.ts.map +1 -0
- package/dist/artifacts/webhook-sink.js +117 -0
- package/dist/artifacts/webhook-sink.js.map +1 -0
- package/dist/brain/index.d.ts +149 -0
- package/dist/brain/index.d.ts.map +1 -0
- package/dist/brain/index.js +1030 -0
- package/dist/brain/index.js.map +1 -0
- package/dist/browser-launch.d.ts +27 -0
- package/dist/browser-launch.d.ts.map +1 -0
- package/dist/browser-launch.js +92 -0
- package/dist/browser-launch.js.map +1 -0
- package/dist/cli.d.ts +14 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1035 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +143 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +205 -0
- package/dist/config.js.map +1 -0
- package/dist/context-budget.d.ts +14 -0
- package/dist/context-budget.d.ts.map +1 -0
- package/dist/context-budget.js +39 -0
- package/dist/context-budget.js.map +1 -0
- package/dist/design-audit.d.ts +48 -0
- package/dist/design-audit.d.ts.map +1 -0
- package/dist/design-audit.js +172 -0
- package/dist/design-audit.js.map +1 -0
- package/dist/domain-policy.d.ts +4 -0
- package/dist/domain-policy.d.ts.map +1 -0
- package/dist/domain-policy.js +69 -0
- package/dist/domain-policy.js.map +1 -0
- package/dist/drivers/block-patterns.d.ts +11 -0
- package/dist/drivers/block-patterns.d.ts.map +1 -0
- package/dist/drivers/block-patterns.js +123 -0
- package/dist/drivers/block-patterns.js.map +1 -0
- package/dist/drivers/cdp-page-state.d.ts +22 -0
- package/dist/drivers/cdp-page-state.d.ts.map +1 -0
- package/dist/drivers/cdp-page-state.js +38 -0
- package/dist/drivers/cdp-page-state.js.map +1 -0
- package/dist/drivers/cdp-snapshot.d.ts +32 -0
- package/dist/drivers/cdp-snapshot.d.ts.map +1 -0
- package/dist/drivers/cdp-snapshot.js +120 -0
- package/dist/drivers/cdp-snapshot.js.map +1 -0
- package/dist/drivers/playwright.d.ts +84 -0
- package/dist/drivers/playwright.d.ts.map +1 -0
- package/dist/drivers/playwright.js +385 -0
- package/dist/drivers/playwright.js.map +1 -0
- package/dist/drivers/snapshot.d.ts +134 -0
- package/dist/drivers/snapshot.d.ts.map +1 -0
- package/dist/drivers/snapshot.js +354 -0
- package/dist/drivers/snapshot.js.map +1 -0
- package/dist/drivers/types.d.ts +37 -0
- package/dist/drivers/types.d.ts.map +1 -0
- package/dist/drivers/types.js +5 -0
- package/dist/drivers/types.js.map +1 -0
- package/dist/env-loader.d.ts +2 -0
- package/dist/env-loader.d.ts.map +1 -0
- package/dist/env-loader.js +35 -0
- package/dist/env-loader.js.map +1 -0
- package/dist/formatters/slack.d.ts +37 -0
- package/dist/formatters/slack.d.ts.map +1 -0
- package/dist/formatters/slack.js +97 -0
- package/dist/formatters/slack.js.map +1 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +53 -0
- package/dist/index.js.map +1 -0
- package/dist/memory/analyzer.d.ts +67 -0
- package/dist/memory/analyzer.d.ts.map +1 -0
- package/dist/memory/analyzer.js +265 -0
- package/dist/memory/analyzer.js.map +1 -0
- package/dist/memory/knowledge.d.ts +55 -0
- package/dist/memory/knowledge.d.ts.map +1 -0
- package/dist/memory/knowledge.js +115 -0
- package/dist/memory/knowledge.js.map +1 -0
- package/dist/memory/project-store.d.ts +43 -0
- package/dist/memory/project-store.d.ts.map +1 -0
- package/dist/memory/project-store.js +119 -0
- package/dist/memory/project-store.js.map +1 -0
- package/dist/memory/selectors.d.ts +46 -0
- package/dist/memory/selectors.d.ts.map +1 -0
- package/dist/memory/selectors.js +89 -0
- package/dist/memory/selectors.js.map +1 -0
- package/dist/memory/store.d.ts +39 -0
- package/dist/memory/store.d.ts.map +1 -0
- package/dist/memory/store.js +193 -0
- package/dist/memory/store.js.map +1 -0
- package/dist/model-pricing.d.ts +41 -0
- package/dist/model-pricing.d.ts.map +1 -0
- package/dist/model-pricing.js +197 -0
- package/dist/model-pricing.js.map +1 -0
- package/dist/multi-actor.d.ts +108 -0
- package/dist/multi-actor.d.ts.map +1 -0
- package/dist/multi-actor.js +192 -0
- package/dist/multi-actor.js.map +1 -0
- package/dist/override-pipeline.d.ts +57 -0
- package/dist/override-pipeline.d.ts.map +1 -0
- package/dist/override-pipeline.js +26 -0
- package/dist/override-pipeline.js.map +1 -0
- package/dist/personas.d.ts +19 -0
- package/dist/personas.d.ts.map +1 -0
- package/dist/personas.js +86 -0
- package/dist/personas.js.map +1 -0
- package/dist/preview.d.ts +32 -0
- package/dist/preview.d.ts.map +1 -0
- package/dist/preview.js +138 -0
- package/dist/preview.js.map +1 -0
- package/dist/provider-defaults.d.ts +6 -0
- package/dist/provider-defaults.d.ts.map +1 -0
- package/dist/provider-defaults.js +40 -0
- package/dist/provider-defaults.js.map +1 -0
- package/dist/providers/sandbox-backend.d.ts +22 -0
- package/dist/providers/sandbox-backend.d.ts.map +1 -0
- package/dist/providers/sandbox-backend.js +259 -0
- package/dist/providers/sandbox-backend.js.map +1 -0
- package/dist/recovery.d.ts +91 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +500 -0
- package/dist/recovery.js.map +1 -0
- package/dist/reporters/junit.d.ts +8 -0
- package/dist/reporters/junit.d.ts.map +1 -0
- package/dist/reporters/junit.js +99 -0
- package/dist/reporters/junit.js.map +1 -0
- package/dist/run-metrics.d.ts +4 -0
- package/dist/run-metrics.d.ts.map +1 -0
- package/dist/run-metrics.js +48 -0
- package/dist/run-metrics.js.map +1 -0
- package/dist/run-state.d.ts +22 -0
- package/dist/run-state.d.ts.map +1 -0
- package/dist/run-state.js +33 -0
- package/dist/run-state.js.map +1 -0
- package/dist/runner/goal-verification.d.ts +13 -0
- package/dist/runner/goal-verification.d.ts.map +1 -0
- package/dist/runner/goal-verification.js +152 -0
- package/dist/runner/goal-verification.js.map +1 -0
- package/dist/runner/index.d.ts +12 -0
- package/dist/runner/index.d.ts.map +1 -0
- package/dist/runner/index.js +18 -0
- package/dist/runner/index.js.map +1 -0
- package/dist/runner/overrides.d.ts +75 -0
- package/dist/runner/overrides.d.ts.map +1 -0
- package/dist/runner/overrides.js +385 -0
- package/dist/runner/overrides.js.map +1 -0
- package/dist/runner/page-analysis.d.ts +23 -0
- package/dist/runner/page-analysis.d.ts.map +1 -0
- package/dist/runner/page-analysis.js +91 -0
- package/dist/runner/page-analysis.js.map +1 -0
- package/dist/runner/runner.d.ts +66 -0
- package/dist/runner/runner.d.ts.map +1 -0
- package/dist/runner/runner.js +1314 -0
- package/dist/runner/runner.js.map +1 -0
- package/dist/runner/scout.d.ts +29 -0
- package/dist/runner/scout.d.ts.map +1 -0
- package/dist/runner/scout.js +81 -0
- package/dist/runner/scout.js.map +1 -0
- package/dist/runner/search-guidance.d.ts +31 -0
- package/dist/runner/search-guidance.d.ts.map +1 -0
- package/dist/runner/search-guidance.js +210 -0
- package/dist/runner/search-guidance.js.map +1 -0
- package/dist/runner/utils.d.ts +28 -0
- package/dist/runner/utils.d.ts.map +1 -0
- package/dist/runner/utils.js +119 -0
- package/dist/runner/utils.js.map +1 -0
- package/dist/runner.d.ts +8 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +21 -0
- package/dist/runner.js.map +1 -0
- package/dist/supervisor/critic.d.ts +29 -0
- package/dist/supervisor/critic.d.ts.map +1 -0
- package/dist/supervisor/critic.js +320 -0
- package/dist/supervisor/critic.js.map +1 -0
- package/dist/supervisor/policy.d.ts +11 -0
- package/dist/supervisor/policy.d.ts.map +1 -0
- package/dist/supervisor/policy.js +118 -0
- package/dist/supervisor/policy.js.map +1 -0
- package/dist/test-report.d.ts +15 -0
- package/dist/test-report.d.ts.map +1 -0
- package/dist/test-report.js +241 -0
- package/dist/test-report.js.map +1 -0
- package/dist/test-runner.d.ts +99 -0
- package/dist/test-runner.d.ts.map +1 -0
- package/dist/test-runner.js +1075 -0
- package/dist/test-runner.js.map +1 -0
- package/dist/types.d.ts +493 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/wallet/automation.d.ts +52 -0
- package/dist/wallet/automation.d.ts.map +1 -0
- package/dist/wallet/automation.js +507 -0
- package/dist/wallet/automation.js.map +1 -0
- package/package.json +101 -0
- package/scripts/postinstall-provider-patches.mjs +52 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1035 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI for browser-agent-driver — run agent test cases from the command line.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* bad run --goal "Sign up" --url http://localhost:3000
|
|
7
|
+
* bad run --cases ./cases.json --concurrency 4
|
|
8
|
+
* bad run --cases ./cases.json --sink ./results/ --model claude-sonnet-4-20250514
|
|
9
|
+
*
|
|
10
|
+
* Designed for sandbox/container execution:
|
|
11
|
+
* docker run bad run --cases /data/cases.json --sink /output/
|
|
12
|
+
*/
|
|
13
|
+
import { parseArgs } from 'node:util';
|
|
14
|
+
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
|
+
import { loadLocalEnvFiles } from './env-loader.js';
|
|
23
|
+
const RUN_MODES = ['fast-explore', 'full-evidence'];
|
|
24
|
+
const DRIVER_PROFILES = ['default', 'stealth', 'benchmark-webbench', 'benchmark-webbench-stealth', 'benchmark-webvoyager'];
|
|
25
|
+
async function applyStorageStateToPersistentContext(context, storageStatePath) {
|
|
26
|
+
if (!storageStatePath)
|
|
27
|
+
return;
|
|
28
|
+
const parsed = JSON.parse(fs.readFileSync(storageStatePath, 'utf-8'));
|
|
29
|
+
const cookies = parsed.cookies ?? [];
|
|
30
|
+
if (cookies.length > 0) {
|
|
31
|
+
await context.addCookies(cookies);
|
|
32
|
+
}
|
|
33
|
+
const origins = parsed.origins ?? [];
|
|
34
|
+
if (origins.length === 0)
|
|
35
|
+
return;
|
|
36
|
+
const existingPages = context.pages();
|
|
37
|
+
const page = existingPages[0] ?? await context.newPage();
|
|
38
|
+
const createdTempPage = existingPages.length === 0;
|
|
39
|
+
try {
|
|
40
|
+
for (const originState of origins) {
|
|
41
|
+
if (!originState?.origin || !Array.isArray(originState.localStorage) || originState.localStorage.length === 0) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
await page.goto(originState.origin, { waitUntil: 'domcontentloaded' }).catch(() => { });
|
|
45
|
+
await page.evaluate((entries) => {
|
|
46
|
+
for (const entry of entries) {
|
|
47
|
+
try {
|
|
48
|
+
localStorage.setItem(entry.name, entry.value);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// Best effort: some origins may block storage writes.
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}, originState.localStorage);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
if (createdTempPage) {
|
|
59
|
+
await page.close().catch(() => { });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function main() {
|
|
64
|
+
loadLocalEnvFiles(process.cwd());
|
|
65
|
+
const { values, positionals } = parseArgs({
|
|
66
|
+
allowPositionals: true,
|
|
67
|
+
allowNegative: true,
|
|
68
|
+
options: {
|
|
69
|
+
// Config file
|
|
70
|
+
config: { type: 'string' },
|
|
71
|
+
// Test specification
|
|
72
|
+
goal: { type: 'string', short: 'g' },
|
|
73
|
+
url: { type: 'string', short: 'u' },
|
|
74
|
+
cases: { type: 'string', short: 'c' },
|
|
75
|
+
'allowed-domains': { type: 'string' },
|
|
76
|
+
// LLM configuration
|
|
77
|
+
model: { type: 'string', short: 'm' },
|
|
78
|
+
provider: { type: 'string' },
|
|
79
|
+
'model-adaptive': { type: 'boolean' },
|
|
80
|
+
'nav-model': { type: 'string' },
|
|
81
|
+
'nav-provider': { type: 'string' },
|
|
82
|
+
persona: { type: 'string' },
|
|
83
|
+
mode: { type: 'string' },
|
|
84
|
+
profile: { type: 'string' },
|
|
85
|
+
'prompt-file': { type: 'string' },
|
|
86
|
+
'sandbox-backend-type': { type: 'string' },
|
|
87
|
+
'sandbox-backend-profile': { type: 'string' },
|
|
88
|
+
'sandbox-backend-provider': { type: 'string' },
|
|
89
|
+
'api-key': { type: 'string' },
|
|
90
|
+
'base-url': { type: 'string' },
|
|
91
|
+
// Execution
|
|
92
|
+
browser: { type: 'string' },
|
|
93
|
+
'storage-state': { type: 'string' },
|
|
94
|
+
concurrency: { type: 'string' },
|
|
95
|
+
'max-turns': { type: 'string' },
|
|
96
|
+
'llm-timeout': { type: 'string' },
|
|
97
|
+
retries: { type: 'string' },
|
|
98
|
+
'retry-delay-ms': { type: 'string' },
|
|
99
|
+
'screenshot-interval': { type: 'string' },
|
|
100
|
+
scout: { type: 'boolean' },
|
|
101
|
+
'scout-model': { type: 'string' },
|
|
102
|
+
'scout-provider': { type: 'string' },
|
|
103
|
+
'scout-vision': { type: 'boolean' },
|
|
104
|
+
'scout-max-candidates': { type: 'string' },
|
|
105
|
+
'scout-min-top-score': { type: 'string' },
|
|
106
|
+
'scout-max-score-gap': { type: 'string' },
|
|
107
|
+
headless: { type: 'boolean' },
|
|
108
|
+
timeout: { type: 'string' },
|
|
109
|
+
extension: { type: 'string', multiple: true },
|
|
110
|
+
'user-data-dir': { type: 'string' },
|
|
111
|
+
wallet: { type: 'boolean' },
|
|
112
|
+
'wallet-auto-approve': { type: 'boolean' },
|
|
113
|
+
'wallet-password': { type: 'string' },
|
|
114
|
+
'wallet-seed-url': { type: 'string', multiple: true },
|
|
115
|
+
'wallet-preflight': { type: 'boolean' },
|
|
116
|
+
'wallet-chain-id': { type: 'string' },
|
|
117
|
+
'wallet-chain-rpc-url': { type: 'string' },
|
|
118
|
+
memory: { type: 'boolean' },
|
|
119
|
+
'memory-dir': { type: 'string' },
|
|
120
|
+
// Output
|
|
121
|
+
sink: { type: 'string', short: 's' },
|
|
122
|
+
json: { type: 'boolean', default: false },
|
|
123
|
+
quiet: { type: 'boolean', short: 'q', default: false },
|
|
124
|
+
// Feature flags
|
|
125
|
+
'goal-verification': { type: 'boolean' },
|
|
126
|
+
'quality-threshold': { type: 'string' },
|
|
127
|
+
'trace-scoring': { type: 'boolean' },
|
|
128
|
+
'trace-ttl-days': { type: 'string' },
|
|
129
|
+
vision: { type: 'boolean' },
|
|
130
|
+
'vision-strategy': { type: 'string' },
|
|
131
|
+
debug: { type: 'boolean', short: 'd', default: false },
|
|
132
|
+
// Resource blocking
|
|
133
|
+
'block-analytics': { type: 'boolean', default: false },
|
|
134
|
+
'block-images': { type: 'boolean', default: false },
|
|
135
|
+
'block-media': { type: 'boolean', default: false },
|
|
136
|
+
help: { type: 'boolean', short: 'h', default: false },
|
|
137
|
+
version: { type: 'boolean', short: 'v', default: false },
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
if (values.version) {
|
|
141
|
+
const pkg = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
|
|
142
|
+
console.log(pkg.version);
|
|
143
|
+
process.exit(0);
|
|
144
|
+
}
|
|
145
|
+
if (values.help || positionals.length === 0) {
|
|
146
|
+
printHelp();
|
|
147
|
+
process.exit(0);
|
|
148
|
+
}
|
|
149
|
+
const command = positionals[0];
|
|
150
|
+
if (command !== 'run') {
|
|
151
|
+
console.error(`Unknown command: ${command}. Use "run".`);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
// Validate inputs
|
|
155
|
+
if (!values.goal && !values.cases) {
|
|
156
|
+
console.error('Error: provide --goal "..." --url "..." for a single task, or --cases ./cases.json for a suite.');
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
// Load config file, then overlay CLI flags
|
|
160
|
+
const fileConfig = await loadConfig(values.config);
|
|
161
|
+
const mode = values.mode;
|
|
162
|
+
if (mode && !RUN_MODES.includes(mode)) {
|
|
163
|
+
console.error(`Error: unknown mode "${mode}". Valid modes: ${RUN_MODES.join(', ')}`);
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
const profile = values.profile;
|
|
167
|
+
if (profile && !DRIVER_PROFILES.includes(profile)) {
|
|
168
|
+
console.error(`Error: unknown profile "${profile}". Valid profiles: ${DRIVER_PROFILES.join(', ')}`);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
// Build CLI overrides (only set values that were explicitly passed)
|
|
172
|
+
const cliOverrides = {};
|
|
173
|
+
if (values.model)
|
|
174
|
+
cliOverrides.model = values.model;
|
|
175
|
+
if (values.provider)
|
|
176
|
+
cliOverrides.provider = values.provider;
|
|
177
|
+
if (values['model-adaptive'] !== undefined)
|
|
178
|
+
cliOverrides.adaptiveModelRouting = values['model-adaptive'];
|
|
179
|
+
if (values['nav-model'])
|
|
180
|
+
cliOverrides.navModel = values['nav-model'];
|
|
181
|
+
if (values['nav-provider'])
|
|
182
|
+
cliOverrides.navProvider = values['nav-provider'];
|
|
183
|
+
if (values['sandbox-backend-type'])
|
|
184
|
+
cliOverrides.sandboxBackendType = values['sandbox-backend-type'];
|
|
185
|
+
if (values['sandbox-backend-profile'])
|
|
186
|
+
cliOverrides.sandboxBackendProfile = values['sandbox-backend-profile'];
|
|
187
|
+
if (values['sandbox-backend-provider'])
|
|
188
|
+
cliOverrides.sandboxBackendProvider = values['sandbox-backend-provider'];
|
|
189
|
+
if (values['api-key'])
|
|
190
|
+
cliOverrides.apiKey = values['api-key'];
|
|
191
|
+
if (values['base-url'])
|
|
192
|
+
cliOverrides.baseUrl = values['base-url'];
|
|
193
|
+
if (values['prompt-file']) {
|
|
194
|
+
const promptPath = path.resolve(values['prompt-file']);
|
|
195
|
+
if (!fs.existsSync(promptPath)) {
|
|
196
|
+
console.error(`Error: prompt file not found: ${promptPath}`);
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
cliOverrides.systemPrompt = fs.readFileSync(promptPath, 'utf-8').trim();
|
|
200
|
+
if (!cliOverrides.systemPrompt) {
|
|
201
|
+
console.error(`Error: prompt file is empty: ${promptPath}`);
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (profile)
|
|
206
|
+
cliOverrides.profile = profile;
|
|
207
|
+
if (values.browser)
|
|
208
|
+
cliOverrides.browser = values.browser;
|
|
209
|
+
if (values['storage-state'])
|
|
210
|
+
cliOverrides.storageState = values['storage-state'];
|
|
211
|
+
if (values.concurrency)
|
|
212
|
+
cliOverrides.concurrency = parseInt(values.concurrency, 10);
|
|
213
|
+
if (values['max-turns'])
|
|
214
|
+
cliOverrides.maxTurns = parseInt(values['max-turns'], 10);
|
|
215
|
+
if (values['llm-timeout'])
|
|
216
|
+
cliOverrides.llmTimeoutMs = parseInt(values['llm-timeout'], 10);
|
|
217
|
+
if (values.retries)
|
|
218
|
+
cliOverrides.retries = parseInt(values.retries, 10);
|
|
219
|
+
if (values['retry-delay-ms'])
|
|
220
|
+
cliOverrides.retryDelayMs = parseInt(values['retry-delay-ms'], 10);
|
|
221
|
+
if (values['screenshot-interval'])
|
|
222
|
+
cliOverrides.screenshotInterval = parseInt(values['screenshot-interval'], 10);
|
|
223
|
+
if (values.scout !== undefined ||
|
|
224
|
+
values['scout-model'] ||
|
|
225
|
+
values['scout-provider'] ||
|
|
226
|
+
values['scout-vision'] !== undefined ||
|
|
227
|
+
values['scout-max-candidates'] ||
|
|
228
|
+
values['scout-min-top-score'] ||
|
|
229
|
+
values['scout-max-score-gap']) {
|
|
230
|
+
cliOverrides.scout = {
|
|
231
|
+
...(cliOverrides.scout ?? {}),
|
|
232
|
+
};
|
|
233
|
+
if (values.scout !== undefined)
|
|
234
|
+
cliOverrides.scout.enabled = values.scout;
|
|
235
|
+
if (values['scout-model'])
|
|
236
|
+
cliOverrides.scout.model = values['scout-model'];
|
|
237
|
+
if (values['scout-provider']) {
|
|
238
|
+
cliOverrides.scout.provider = values['scout-provider'];
|
|
239
|
+
}
|
|
240
|
+
if (values['scout-vision'] !== undefined)
|
|
241
|
+
cliOverrides.scout.useVision = values['scout-vision'];
|
|
242
|
+
if (values['scout-max-candidates'])
|
|
243
|
+
cliOverrides.scout.maxCandidates = parseInt(values['scout-max-candidates'], 10);
|
|
244
|
+
if (values['scout-min-top-score'])
|
|
245
|
+
cliOverrides.scout.minTopScore = parseInt(values['scout-min-top-score'], 10);
|
|
246
|
+
if (values['scout-max-score-gap'])
|
|
247
|
+
cliOverrides.scout.maxScoreGap = parseInt(values['scout-max-score-gap'], 10);
|
|
248
|
+
}
|
|
249
|
+
if (values.timeout)
|
|
250
|
+
cliOverrides.timeoutMs = parseInt(values.timeout, 10);
|
|
251
|
+
if (values['quality-threshold'])
|
|
252
|
+
cliOverrides.qualityThreshold = parseInt(values['quality-threshold'], 10);
|
|
253
|
+
if (values['trace-scoring'] !== undefined || values['trace-ttl-days']) {
|
|
254
|
+
cliOverrides.memory = {
|
|
255
|
+
...(cliOverrides.memory ?? {}),
|
|
256
|
+
};
|
|
257
|
+
if (values['trace-scoring'] !== undefined)
|
|
258
|
+
cliOverrides.memory.traceScoring = values['trace-scoring'];
|
|
259
|
+
if (values['trace-ttl-days'])
|
|
260
|
+
cliOverrides.memory.traceTtlDays = parseInt(values['trace-ttl-days'], 10);
|
|
261
|
+
}
|
|
262
|
+
if (values.sink)
|
|
263
|
+
cliOverrides.outputDir = values.sink;
|
|
264
|
+
if (values.headless !== undefined)
|
|
265
|
+
cliOverrides.headless = values.headless;
|
|
266
|
+
if (values.vision !== undefined)
|
|
267
|
+
cliOverrides.vision = values.vision;
|
|
268
|
+
if (values['vision-strategy'])
|
|
269
|
+
cliOverrides.visionStrategy = values['vision-strategy'];
|
|
270
|
+
if (values['goal-verification'] !== undefined)
|
|
271
|
+
cliOverrides.goalVerification = values['goal-verification'];
|
|
272
|
+
if (values.extension?.length ||
|
|
273
|
+
values['user-data-dir'] ||
|
|
274
|
+
values.wallet !== undefined ||
|
|
275
|
+
values['wallet-auto-approve'] !== undefined ||
|
|
276
|
+
values['wallet-password'] ||
|
|
277
|
+
values['wallet-seed-url']?.length ||
|
|
278
|
+
values['wallet-preflight'] !== undefined ||
|
|
279
|
+
values['wallet-chain-id'] ||
|
|
280
|
+
values['wallet-chain-rpc-url']) {
|
|
281
|
+
cliOverrides.wallet = {};
|
|
282
|
+
if (values.extension?.length)
|
|
283
|
+
cliOverrides.wallet.extensionPaths = values.extension;
|
|
284
|
+
if (values['user-data-dir'])
|
|
285
|
+
cliOverrides.wallet.userDataDir = values['user-data-dir'];
|
|
286
|
+
if (values.wallet !== undefined)
|
|
287
|
+
cliOverrides.wallet.enabled = values.wallet;
|
|
288
|
+
if (values['wallet-auto-approve'] !== undefined) {
|
|
289
|
+
cliOverrides.wallet.autoApprove = values['wallet-auto-approve'];
|
|
290
|
+
}
|
|
291
|
+
if (values['wallet-password']) {
|
|
292
|
+
cliOverrides.wallet.password = values['wallet-password'];
|
|
293
|
+
}
|
|
294
|
+
if (values['wallet-seed-url']?.length ||
|
|
295
|
+
values['wallet-preflight'] !== undefined ||
|
|
296
|
+
values['wallet-chain-id'] ||
|
|
297
|
+
values['wallet-chain-rpc-url']) {
|
|
298
|
+
cliOverrides.wallet.preflight = {};
|
|
299
|
+
if (values['wallet-seed-url']?.length) {
|
|
300
|
+
cliOverrides.wallet.preflight.seedUrls = values['wallet-seed-url'];
|
|
301
|
+
}
|
|
302
|
+
if (values['wallet-preflight'] !== undefined) {
|
|
303
|
+
cliOverrides.wallet.preflight.enabled = values['wallet-preflight'];
|
|
304
|
+
}
|
|
305
|
+
if (values['wallet-chain-id'] || values['wallet-chain-rpc-url']) {
|
|
306
|
+
cliOverrides.wallet.preflight.chain = {};
|
|
307
|
+
if (values['wallet-chain-id']) {
|
|
308
|
+
const parsedChainId = parseInt(values['wallet-chain-id'], 10);
|
|
309
|
+
if (!Number.isFinite(parsedChainId)) {
|
|
310
|
+
throw new Error(`Invalid --wallet-chain-id value: ${values['wallet-chain-id']}`);
|
|
311
|
+
}
|
|
312
|
+
cliOverrides.wallet.preflight.chain.id = parsedChainId;
|
|
313
|
+
}
|
|
314
|
+
if (values['wallet-chain-rpc-url']) {
|
|
315
|
+
cliOverrides.wallet.preflight.chain.rpcUrl = values['wallet-chain-rpc-url'];
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (values.memory !== undefined || values['memory-dir']) {
|
|
321
|
+
cliOverrides.memory = {
|
|
322
|
+
...(cliOverrides.memory ?? {}),
|
|
323
|
+
};
|
|
324
|
+
if (values.memory !== undefined)
|
|
325
|
+
cliOverrides.memory.enabled = values.memory;
|
|
326
|
+
if (values['memory-dir'])
|
|
327
|
+
cliOverrides.memory.dir = values['memory-dir'];
|
|
328
|
+
}
|
|
329
|
+
// Resource blocking
|
|
330
|
+
if (values['block-analytics'] || values['block-images'] || values['block-media']) {
|
|
331
|
+
cliOverrides.resourceBlocking = {};
|
|
332
|
+
if (values['block-analytics'])
|
|
333
|
+
cliOverrides.resourceBlocking.blockAnalytics = true;
|
|
334
|
+
if (values['block-images'])
|
|
335
|
+
cliOverrides.resourceBlocking.blockImages = true;
|
|
336
|
+
if (values['block-media'])
|
|
337
|
+
cliOverrides.resourceBlocking.blockMedia = true;
|
|
338
|
+
}
|
|
339
|
+
// Profile presets apply only when equivalent flags were not explicitly set.
|
|
340
|
+
if (profile === 'stealth') {
|
|
341
|
+
if (values.headless === undefined)
|
|
342
|
+
cliOverrides.headless = false;
|
|
343
|
+
if (values['goal-verification'] === undefined)
|
|
344
|
+
cliOverrides.goalVerification = true;
|
|
345
|
+
if (!values['screenshot-interval'])
|
|
346
|
+
cliOverrides.screenshotInterval = 2;
|
|
347
|
+
if (!values['block-analytics'] && !values['block-images'] && !values['block-media']) {
|
|
348
|
+
cliOverrides.resourceBlocking = {
|
|
349
|
+
...(cliOverrides.resourceBlocking ?? {}),
|
|
350
|
+
blockAnalytics: true,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
cliOverrides.microPlan = {
|
|
354
|
+
...(cliOverrides.microPlan ?? {}),
|
|
355
|
+
enabled: true,
|
|
356
|
+
maxActionsPerTurn: cliOverrides.microPlan?.maxActionsPerTurn ?? 2,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
else if (profile === 'benchmark-webbench' || profile === 'benchmark-webbench-stealth') {
|
|
360
|
+
if (!values['llm-timeout'])
|
|
361
|
+
cliOverrides.llmTimeoutMs = 20_000;
|
|
362
|
+
cliOverrides.compactFirstTurn = true;
|
|
363
|
+
if (!values.retries)
|
|
364
|
+
cliOverrides.retries = 1;
|
|
365
|
+
if (!values['retry-delay-ms'])
|
|
366
|
+
cliOverrides.retryDelayMs = 250;
|
|
367
|
+
if (values.vision === undefined)
|
|
368
|
+
cliOverrides.vision = false;
|
|
369
|
+
if (!values['screenshot-interval'])
|
|
370
|
+
cliOverrides.screenshotInterval = 0;
|
|
371
|
+
if (values['goal-verification'] === undefined)
|
|
372
|
+
cliOverrides.goalVerification = true;
|
|
373
|
+
if (profile === 'benchmark-webbench-stealth' && values.headless === undefined) {
|
|
374
|
+
cliOverrides.headless = false;
|
|
375
|
+
}
|
|
376
|
+
if (!values['block-analytics'] && !values['block-images'] && !values['block-media']) {
|
|
377
|
+
cliOverrides.resourceBlocking = {
|
|
378
|
+
...(cliOverrides.resourceBlocking ?? {}),
|
|
379
|
+
blockAnalytics: true,
|
|
380
|
+
...(profile === 'benchmark-webbench'
|
|
381
|
+
? {
|
|
382
|
+
blockImages: true,
|
|
383
|
+
blockMedia: true,
|
|
384
|
+
}
|
|
385
|
+
: {}),
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
cliOverrides.microPlan = {
|
|
389
|
+
...(cliOverrides.microPlan ?? {}),
|
|
390
|
+
enabled: true,
|
|
391
|
+
maxActionsPerTurn: cliOverrides.microPlan?.maxActionsPerTurn ?? 2,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
else if (profile === 'benchmark-webvoyager') {
|
|
395
|
+
if (values.vision === undefined)
|
|
396
|
+
cliOverrides.vision = true;
|
|
397
|
+
if (!values['screenshot-interval'])
|
|
398
|
+
cliOverrides.screenshotInterval = 2;
|
|
399
|
+
if (values['goal-verification'] === undefined)
|
|
400
|
+
cliOverrides.goalVerification = true;
|
|
401
|
+
cliOverrides.microPlan = {
|
|
402
|
+
...(cliOverrides.microPlan ?? {}),
|
|
403
|
+
enabled: true,
|
|
404
|
+
maxActionsPerTurn: cliOverrides.microPlan?.maxActionsPerTurn ?? 2,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
// Mode presets apply only when equivalent flags were not explicitly set.
|
|
408
|
+
if (mode === 'fast-explore') {
|
|
409
|
+
if (values.vision === undefined)
|
|
410
|
+
cliOverrides.vision = false;
|
|
411
|
+
if (!values['screenshot-interval'])
|
|
412
|
+
cliOverrides.screenshotInterval = 0;
|
|
413
|
+
if (values['goal-verification'] === undefined)
|
|
414
|
+
cliOverrides.goalVerification = true;
|
|
415
|
+
if (values['quality-threshold'] === undefined)
|
|
416
|
+
cliOverrides.qualityThreshold = 0;
|
|
417
|
+
if (!values['block-analytics'] && !values['block-images'] && !values['block-media']) {
|
|
418
|
+
cliOverrides.resourceBlocking = {
|
|
419
|
+
...(cliOverrides.resourceBlocking ?? {}),
|
|
420
|
+
blockAnalytics: true,
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
else if (mode === 'full-evidence') {
|
|
425
|
+
if (values.vision === undefined)
|
|
426
|
+
cliOverrides.vision = true;
|
|
427
|
+
if (!values['screenshot-interval'])
|
|
428
|
+
cliOverrides.screenshotInterval = 3;
|
|
429
|
+
if (values['goal-verification'] === undefined)
|
|
430
|
+
cliOverrides.goalVerification = true;
|
|
431
|
+
}
|
|
432
|
+
const driverConfig = mergeConfig(fileConfig, cliOverrides);
|
|
433
|
+
const launchPlan = buildBrowserLaunchPlan(driverConfig);
|
|
434
|
+
const quiet = values.quiet;
|
|
435
|
+
for (const warning of launchPlan.warnings) {
|
|
436
|
+
if (!quiet) {
|
|
437
|
+
console.warn(`Warning: ${warning}`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if (launchPlan.errors.length > 0) {
|
|
441
|
+
for (const error of launchPlan.errors) {
|
|
442
|
+
console.error(`Error: ${error}`);
|
|
443
|
+
}
|
|
444
|
+
process.exit(1);
|
|
445
|
+
}
|
|
446
|
+
// Dynamic imports — keeps startup fast and allows tree-shaking.
|
|
447
|
+
// Use patchright (Playwright fork with CDP leak fixes) for stealth profiles
|
|
448
|
+
// to avoid Cloudflare/DataDome detection via Runtime.enable.
|
|
449
|
+
const isStealthProfile = launchPlan.profile.includes('stealth');
|
|
450
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
451
|
+
const { chromium, firefox, webkit } = (isStealthProfile
|
|
452
|
+
? await import('patchright')
|
|
453
|
+
: await import('playwright'));
|
|
454
|
+
const { PlaywrightDriver } = await import('./drivers/playwright.js');
|
|
455
|
+
const { TestRunner } = await import('./test-runner.js');
|
|
456
|
+
const { FilesystemSink } = await import('./artifacts/filesystem-sink.js');
|
|
457
|
+
const concurrency = launchPlan.concurrency;
|
|
458
|
+
const maxTurns = driverConfig.maxTurns ?? 30;
|
|
459
|
+
const screenshotInterval = driverConfig.screenshotInterval ?? 5;
|
|
460
|
+
const timeoutMs = driverConfig.timeoutMs ?? 600_000;
|
|
461
|
+
const browserName = driverConfig.browser ?? 'chromium';
|
|
462
|
+
const debug = values.debug;
|
|
463
|
+
const sinkDir = driverConfig.outputDir ?? './agent-results';
|
|
464
|
+
const resolvedProvider = driverConfig.provider || 'openai';
|
|
465
|
+
const resolvedApiKey = resolveProviderApiKey(resolvedProvider, driverConfig.apiKey);
|
|
466
|
+
const resolvedModel = resolveProviderModelName(resolvedProvider, driverConfig.model, {
|
|
467
|
+
sandboxBackendType: resolvedProvider === 'sandbox-backend' ? driverConfig.sandboxBackendType : undefined,
|
|
468
|
+
});
|
|
469
|
+
const resolvedNavProvider = driverConfig.navProvider || resolvedProvider;
|
|
470
|
+
const resolvedNavModel = driverConfig.navModel
|
|
471
|
+
? resolveProviderModelName(resolvedNavProvider, driverConfig.navModel, {
|
|
472
|
+
sandboxBackendType: resolvedNavProvider === 'sandbox-backend' ? driverConfig.sandboxBackendType : undefined,
|
|
473
|
+
})
|
|
474
|
+
: undefined;
|
|
475
|
+
const resolvedSupervisorProvider = driverConfig.supervisor?.provider || resolvedProvider;
|
|
476
|
+
const resolvedSupervisorModel = resolveProviderModelName(resolvedSupervisorProvider, driverConfig.supervisor?.model || resolvedModel, {
|
|
477
|
+
sandboxBackendType: resolvedSupervisorProvider === 'sandbox-backend' ? driverConfig.sandboxBackendType : undefined,
|
|
478
|
+
});
|
|
479
|
+
const config = {
|
|
480
|
+
...toAgentConfig(driverConfig),
|
|
481
|
+
model: resolvedModel,
|
|
482
|
+
...(resolvedNavModel ? { navModel: resolvedNavModel } : {}),
|
|
483
|
+
apiKey: resolvedApiKey,
|
|
484
|
+
baseUrl: driverConfig.baseUrl || process.env.LLM_BASE_URL,
|
|
485
|
+
supervisor: driverConfig.supervisor
|
|
486
|
+
? {
|
|
487
|
+
...driverConfig.supervisor,
|
|
488
|
+
provider: resolvedSupervisorProvider,
|
|
489
|
+
model: resolvedSupervisorModel,
|
|
490
|
+
}
|
|
491
|
+
: undefined,
|
|
492
|
+
debug,
|
|
493
|
+
};
|
|
494
|
+
// Build test cases
|
|
495
|
+
let cases;
|
|
496
|
+
if (values.cases) {
|
|
497
|
+
const raw = fs.readFileSync(path.resolve(values.cases), 'utf-8');
|
|
498
|
+
const parsed = JSON.parse(raw);
|
|
499
|
+
const rawCases = Array.isArray(parsed) ? parsed : [parsed];
|
|
500
|
+
// Ensure required fields — spread raw case first so explicit fields become defaults
|
|
501
|
+
cases = rawCases.map((c, i) => ({
|
|
502
|
+
id: c.id || `case-${i}`,
|
|
503
|
+
name: c.name || c.goal?.slice(0, 60) || `Case ${i}`,
|
|
504
|
+
startUrl: c.startUrl || c.url || values.url || '',
|
|
505
|
+
goal: c.goal || '',
|
|
506
|
+
allowedDomains: Array.isArray(c.allowedDomains)
|
|
507
|
+
? c.allowedDomains.filter((domain) => typeof domain === 'string' && domain.length > 0)
|
|
508
|
+
: parseAllowedDomains(values['allowed-domains']),
|
|
509
|
+
maxTurns: c.maxTurns || maxTurns,
|
|
510
|
+
timeoutMs: c.timeoutMs || timeoutMs,
|
|
511
|
+
priority: c.priority ?? i,
|
|
512
|
+
}));
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
cases = [{
|
|
516
|
+
id: 'cli-task',
|
|
517
|
+
name: values.goal.slice(0, 60),
|
|
518
|
+
startUrl: values.url || '',
|
|
519
|
+
goal: values.goal,
|
|
520
|
+
allowedDomains: parseAllowedDomains(values['allowed-domains']),
|
|
521
|
+
maxTurns,
|
|
522
|
+
timeoutMs,
|
|
523
|
+
priority: 0,
|
|
524
|
+
}];
|
|
525
|
+
}
|
|
526
|
+
const persona = values.persona;
|
|
527
|
+
if (persona) {
|
|
528
|
+
if (!isPersonaId(persona)) {
|
|
529
|
+
console.error(`Error: unknown persona "${persona}". ` +
|
|
530
|
+
`Valid personas: ${listPersonaIds().join(', ')}`);
|
|
531
|
+
process.exit(1);
|
|
532
|
+
}
|
|
533
|
+
cases = cases.map((c) => ({
|
|
534
|
+
...c,
|
|
535
|
+
goal: withPersonaDirective({
|
|
536
|
+
persona,
|
|
537
|
+
goal: c.goal,
|
|
538
|
+
startUrl: c.startUrl,
|
|
539
|
+
}),
|
|
540
|
+
}));
|
|
541
|
+
}
|
|
542
|
+
if (!quiet && !values.json) {
|
|
543
|
+
console.log(`bad v${JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8')).version}`);
|
|
544
|
+
console.log(`Model: ${config.provider}/${config.model} | Browser: ${browserName} | Tests: ${cases.length} | Concurrency: ${concurrency}`);
|
|
545
|
+
if (mode)
|
|
546
|
+
console.log(`Mode: ${mode}`);
|
|
547
|
+
if (driverConfig.profile && driverConfig.profile !== 'default') {
|
|
548
|
+
console.log(`Profile: ${driverConfig.profile}`);
|
|
549
|
+
}
|
|
550
|
+
if (config.adaptiveModelRouting) {
|
|
551
|
+
const effectiveNavModel = config.navModel || 'gpt-4.1-mini';
|
|
552
|
+
console.log(`Adaptive routing: ON (nav=${config.navProvider || config.provider}/${effectiveNavModel})`);
|
|
553
|
+
}
|
|
554
|
+
console.log(`Output: ${sinkDir}`);
|
|
555
|
+
console.log('');
|
|
556
|
+
}
|
|
557
|
+
// Set up artifact sink
|
|
558
|
+
const sink = new FilesystemSink(path.resolve(sinkDir));
|
|
559
|
+
const videoDir = path.join(sinkDir, '_videos');
|
|
560
|
+
const viewport = launchPlan.viewport;
|
|
561
|
+
const storageStatePath = driverConfig.storageState
|
|
562
|
+
? path.resolve(driverConfig.storageState)
|
|
563
|
+
: undefined;
|
|
564
|
+
if (storageStatePath && !fs.existsSync(storageStatePath)) {
|
|
565
|
+
console.error(`Error: storage state file not found: ${storageStatePath}`);
|
|
566
|
+
process.exit(1);
|
|
567
|
+
}
|
|
568
|
+
if (launchPlan.walletMode && browserName !== 'chromium') {
|
|
569
|
+
console.error('Error: wallet mode currently supports Chromium only. Set --browser chromium.');
|
|
570
|
+
process.exit(1);
|
|
571
|
+
}
|
|
572
|
+
// Set up browser
|
|
573
|
+
let browser;
|
|
574
|
+
let persistentContext;
|
|
575
|
+
let stopWalletAutoApprover;
|
|
576
|
+
const launchDiagnostics = {};
|
|
577
|
+
if (launchPlan.walletMode) {
|
|
578
|
+
for (const extensionPath of launchPlan.extensionPaths) {
|
|
579
|
+
if (!fs.existsSync(extensionPath)) {
|
|
580
|
+
throw new Error(`Wallet extension path does not exist: ${extensionPath}`);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
const userDataDir = launchPlan.userDataDir ?? path.resolve('.agent-wallet-profile');
|
|
584
|
+
fs.mkdirSync(userDataDir, { recursive: true });
|
|
585
|
+
const persistentLaunchStartedAt = Date.now();
|
|
586
|
+
persistentContext = await chromium.launchPersistentContext(userDataDir, {
|
|
587
|
+
channel: isStealthProfile ? 'chrome' : 'chromium',
|
|
588
|
+
headless: false,
|
|
589
|
+
args: launchPlan.browserArgs,
|
|
590
|
+
viewport,
|
|
591
|
+
recordVideo: { dir: videoDir, size: viewport },
|
|
592
|
+
});
|
|
593
|
+
launchDiagnostics.browserLaunchMs = Date.now() - persistentLaunchStartedAt;
|
|
594
|
+
await applyStorageStateToPersistentContext(persistentContext, storageStatePath);
|
|
595
|
+
const walletConfig = driverConfig.wallet ?? {};
|
|
596
|
+
const shouldAutoApprove = walletConfig.autoApprove ?? true;
|
|
597
|
+
if (shouldAutoApprove) {
|
|
598
|
+
stopWalletAutoApprover = await startWalletAutoApprover(persistentContext, {
|
|
599
|
+
password: walletConfig.password,
|
|
600
|
+
tickMs: walletConfig.tickMs,
|
|
601
|
+
actionSelectors: walletConfig.actionSelectors,
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
const preflightEnabled = walletConfig.preflight?.enabled ?? true;
|
|
605
|
+
const preflightSeedUrls = walletConfig.preflight?.seedUrls && walletConfig.preflight.seedUrls.length > 0
|
|
606
|
+
? walletConfig.preflight.seedUrls
|
|
607
|
+
: [...new Set(cases.map((testCase) => testCase.startUrl).filter(Boolean))];
|
|
608
|
+
if (preflightEnabled && preflightSeedUrls.length > 0) {
|
|
609
|
+
const preflight = await runWalletPreflight(persistentContext, {
|
|
610
|
+
seedUrls: preflightSeedUrls,
|
|
611
|
+
password: walletConfig.password,
|
|
612
|
+
actionSelectors: walletConfig.actionSelectors,
|
|
613
|
+
promptPaths: walletConfig.promptPaths,
|
|
614
|
+
connectSelectors: walletConfig.connectSelectors,
|
|
615
|
+
connectorSelectors: walletConfig.connectorSelectors,
|
|
616
|
+
requestAccounts: walletConfig.preflight?.requestAccounts,
|
|
617
|
+
accountsTimeoutMs: walletConfig.preflight?.accountsTimeoutMs,
|
|
618
|
+
maxChainSwitchAttempts: walletConfig.preflight?.maxChainSwitchAttempts,
|
|
619
|
+
chain: walletConfig.preflight?.chain,
|
|
620
|
+
log: quiet ? undefined : (message) => console.log(`[wallet] ${message}`),
|
|
621
|
+
});
|
|
622
|
+
if (!preflight.ok) {
|
|
623
|
+
const failed = preflight.results.find((resultEntry) => !resultEntry.ready);
|
|
624
|
+
const details = failed?.details ?? 'unknown reason';
|
|
625
|
+
throw new Error(`Wallet preflight failed for ${preflight.failedUrl ?? 'unknown origin'} (${details})`);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
else {
|
|
630
|
+
const browserType = browserName === 'firefox'
|
|
631
|
+
? firefox
|
|
632
|
+
: browserName === 'webkit'
|
|
633
|
+
? webkit
|
|
634
|
+
: chromium;
|
|
635
|
+
if (process.env.BROWSER_ENDPOINT) {
|
|
636
|
+
const endpoint = process.env.BROWSER_ENDPOINT;
|
|
637
|
+
const browserLaunchStartedAt = Date.now();
|
|
638
|
+
// CDP endpoint (chrome, android-chrome) vs Playwright endpoint (firefox, webkit)
|
|
639
|
+
if (browserName === 'chromium' || endpoint.includes('/devtools/')) {
|
|
640
|
+
browser = await chromium.connectOverCDP(endpoint);
|
|
641
|
+
}
|
|
642
|
+
else {
|
|
643
|
+
browser = await browserType.connect(endpoint);
|
|
644
|
+
}
|
|
645
|
+
launchDiagnostics.browserLaunchMs = Date.now() - browserLaunchStartedAt;
|
|
646
|
+
}
|
|
647
|
+
else {
|
|
648
|
+
const browserLaunchStartedAt = Date.now();
|
|
649
|
+
browser = await browserType.launch({
|
|
650
|
+
headless: launchPlan.headless,
|
|
651
|
+
...(browserName === 'chromium' ? { args: launchPlan.browserArgs } : {}),
|
|
652
|
+
// Use system Chrome for stealth profiles — real TLS/JA3 fingerprint vs bundled Chromium
|
|
653
|
+
...(isStealthProfile && browserName === 'chromium' ? { channel: 'chrome' } : {}),
|
|
654
|
+
});
|
|
655
|
+
launchDiagnostics.browserLaunchMs = Date.now() - browserLaunchStartedAt;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
const driverFactory = async () => {
|
|
659
|
+
const contextStartedAt = Date.now();
|
|
660
|
+
const context = persistentContext ?? await browser.newContext({
|
|
661
|
+
viewport,
|
|
662
|
+
recordVideo: { dir: videoDir, size: viewport },
|
|
663
|
+
storageState: storageStatePath,
|
|
664
|
+
locale: 'en-US',
|
|
665
|
+
timezoneId: 'America/New_York',
|
|
666
|
+
extraHTTPHeaders: {
|
|
667
|
+
'Accept-Language': 'en-US,en;q=0.9',
|
|
668
|
+
},
|
|
669
|
+
});
|
|
670
|
+
// Stealth: patch navigator/browser properties to reduce automation fingerprint.
|
|
671
|
+
// This runs in the browser context (not Node), so we use a string to avoid TS issues.
|
|
672
|
+
await context.addInitScript(`
|
|
673
|
+
// navigator.webdriver — explicit override (backup for --disable-blink-features)
|
|
674
|
+
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
|
|
675
|
+
// navigator.plugins — empty in headless, non-empty in real browsers
|
|
676
|
+
Object.defineProperty(navigator, 'plugins', {
|
|
677
|
+
get: () => [
|
|
678
|
+
{ name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format' },
|
|
679
|
+
{ name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '' },
|
|
680
|
+
{ name: 'Native Client', filename: 'internal-nacl-plugin', description: '' },
|
|
681
|
+
],
|
|
682
|
+
});
|
|
683
|
+
// navigator.languages — must match Accept-Language header
|
|
684
|
+
Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] });
|
|
685
|
+
// hardware signals — realistic desktop values
|
|
686
|
+
Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 8 });
|
|
687
|
+
Object.defineProperty(navigator, 'deviceMemory', { get: () => 8 });
|
|
688
|
+
// window.chrome — full stub matching real Chrome
|
|
689
|
+
if (!window.chrome) window.chrome = {};
|
|
690
|
+
if (!window.chrome.runtime) window.chrome.runtime = { id: undefined };
|
|
691
|
+
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' } };
|
|
692
|
+
if (!window.chrome.csi) window.chrome.csi = function() { return { onloadT: Date.now(), startE: Date.now(), pageT: Date.now() - performance.timing.navigationStart }; };
|
|
693
|
+
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 }; };
|
|
694
|
+
// WebGL vendor/renderer — match real GPU values
|
|
695
|
+
try {
|
|
696
|
+
const getParameter = WebGLRenderingContext.prototype.getParameter;
|
|
697
|
+
WebGLRenderingContext.prototype.getParameter = function(parameter) {
|
|
698
|
+
if (parameter === 37445) return 'Intel Inc.';
|
|
699
|
+
if (parameter === 37446) return 'Intel Iris OpenGL Engine';
|
|
700
|
+
return getParameter.call(this, parameter);
|
|
701
|
+
};
|
|
702
|
+
} catch (_) {}
|
|
703
|
+
try {
|
|
704
|
+
const getParameter2 = WebGL2RenderingContext.prototype.getParameter;
|
|
705
|
+
WebGL2RenderingContext.prototype.getParameter = function(parameter) {
|
|
706
|
+
if (parameter === 37445) return 'Intel Inc.';
|
|
707
|
+
if (parameter === 37446) return 'Intel Iris OpenGL Engine';
|
|
708
|
+
return getParameter2.call(this, parameter);
|
|
709
|
+
};
|
|
710
|
+
} catch (_) {}
|
|
711
|
+
// window.outerWidth/outerHeight — 0 in headless, match viewport in real browsers
|
|
712
|
+
if (window.outerWidth === 0) Object.defineProperty(window, 'outerWidth', { get: () => window.innerWidth });
|
|
713
|
+
if (window.outerHeight === 0) Object.defineProperty(window, 'outerHeight', { get: () => window.innerHeight + 85 });
|
|
714
|
+
// Patch permissions API for notification checks
|
|
715
|
+
try {
|
|
716
|
+
const origQuery = navigator.permissions.query.bind(navigator.permissions);
|
|
717
|
+
navigator.permissions.query = (params) =>
|
|
718
|
+
params.name === 'notifications'
|
|
719
|
+
? Promise.resolve({ state: 'denied', onchange: null })
|
|
720
|
+
: origQuery(params);
|
|
721
|
+
} catch (_) {}
|
|
722
|
+
`);
|
|
723
|
+
const contextCreateMs = Date.now() - contextStartedAt;
|
|
724
|
+
const pageStartedAt = Date.now();
|
|
725
|
+
const page = await context.newPage();
|
|
726
|
+
const pageCreateMs = Date.now() - pageStartedAt;
|
|
727
|
+
// Cap per-action timeout so one stuck click can't consume the whole case budget.
|
|
728
|
+
// Default 30s is fine for long runs; for short cases (120s) use at most 15s.
|
|
729
|
+
const actionTimeout = Math.min(30_000, Math.max(5_000, Math.floor(timeoutMs / 8)));
|
|
730
|
+
const driver = new PlaywrightDriver(page, {
|
|
731
|
+
captureScreenshots: config.vision,
|
|
732
|
+
screenshotQuality: 50,
|
|
733
|
+
disableCdp: driverConfig.disableCdp,
|
|
734
|
+
timeout: actionTimeout,
|
|
735
|
+
});
|
|
736
|
+
// Apply resource blocking if configured
|
|
737
|
+
const resourceBlockingStartedAt = Date.now();
|
|
738
|
+
if (driverConfig.resourceBlocking) {
|
|
739
|
+
await driver.setupResourceBlocking(driverConfig.resourceBlocking);
|
|
740
|
+
}
|
|
741
|
+
const resourceBlockingSetupMs = driverConfig.resourceBlocking
|
|
742
|
+
? Date.now() - resourceBlockingStartedAt
|
|
743
|
+
: 0;
|
|
744
|
+
const diagnostics = {
|
|
745
|
+
browserName,
|
|
746
|
+
headless: launchPlan.headless,
|
|
747
|
+
walletMode: launchPlan.walletMode,
|
|
748
|
+
browserLaunchMs: Number(launchDiagnostics.browserLaunchMs ?? 0),
|
|
749
|
+
contextCreateMs,
|
|
750
|
+
pageCreateMs,
|
|
751
|
+
resourceBlockingSetupMs,
|
|
752
|
+
storageStateApplied: Boolean(storageStatePath),
|
|
753
|
+
persistentContext: Boolean(persistentContext),
|
|
754
|
+
};
|
|
755
|
+
// Wrap in a Driver that properly tears down context on close
|
|
756
|
+
const wrappedDriver = {
|
|
757
|
+
observe: () => driver.observe(),
|
|
758
|
+
execute: (action) => driver.execute(action),
|
|
759
|
+
getPage: () => driver.getPage?.(),
|
|
760
|
+
screenshot: () => driver.screenshot(),
|
|
761
|
+
getDiagnostics: () => diagnostics,
|
|
762
|
+
async close() {
|
|
763
|
+
await driver.close().catch(() => { });
|
|
764
|
+
await page.close().catch(() => { });
|
|
765
|
+
if (!persistentContext) {
|
|
766
|
+
await context.close().catch(() => { });
|
|
767
|
+
}
|
|
768
|
+
},
|
|
769
|
+
};
|
|
770
|
+
return wrappedDriver;
|
|
771
|
+
};
|
|
772
|
+
// Create a single driver for sequential mode
|
|
773
|
+
let singleDriver;
|
|
774
|
+
if (concurrency <= 1) {
|
|
775
|
+
singleDriver = await driverFactory();
|
|
776
|
+
}
|
|
777
|
+
const runner = new TestRunner({
|
|
778
|
+
config,
|
|
779
|
+
defaultTimeoutMs: timeoutMs,
|
|
780
|
+
driver: singleDriver,
|
|
781
|
+
driverFactory: concurrency > 1 ? driverFactory : undefined,
|
|
782
|
+
enableMemory: driverConfig.memory?.enabled === true,
|
|
783
|
+
trajectoryStorePath: driverConfig.memory?.dir,
|
|
784
|
+
concurrency,
|
|
785
|
+
screenshotInterval,
|
|
786
|
+
artifactSink: sink,
|
|
787
|
+
onProgress: (event) => {
|
|
788
|
+
if (values.json) {
|
|
789
|
+
console.log(JSON.stringify(event));
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
if (quiet)
|
|
793
|
+
return;
|
|
794
|
+
switch (event.type) {
|
|
795
|
+
case 'test:start':
|
|
796
|
+
console.log(` ▶ ${event.testName}`);
|
|
797
|
+
break;
|
|
798
|
+
case 'test:turn':
|
|
799
|
+
if (debug) {
|
|
800
|
+
const modelTag = event.modelUsed ? ` [${event.modelUsed}]` : '';
|
|
801
|
+
console.log(` turn ${event.turn}: ${event.action} (${event.durationMs}ms)${modelTag}`);
|
|
802
|
+
}
|
|
803
|
+
break;
|
|
804
|
+
case 'test:complete': {
|
|
805
|
+
const costStr = event.estimatedCostUsd ? `, $${event.estimatedCostUsd.toFixed(3)}` : '';
|
|
806
|
+
console.log(` ${event.passed ? '✓' : '✗'} ${event.testId} — ${event.verdict.slice(0, 80)} (${event.turnsUsed} turns, ${Math.round(event.durationMs / 1000)}s${costStr})`);
|
|
807
|
+
break;
|
|
808
|
+
}
|
|
809
|
+
case 'suite:complete': {
|
|
810
|
+
console.log('');
|
|
811
|
+
const suiteCostStr = event.totalCostUsd ? ` | $${event.totalCostUsd.toFixed(2)}` : '';
|
|
812
|
+
console.log(`Done: ${event.passed} passed, ${event.failed} failed, ${event.skipped} skipped (${Math.round(event.totalMs / 1000)}s${suiteCostStr})`);
|
|
813
|
+
if (event.manifestUri) {
|
|
814
|
+
console.log(`Artifacts: ${event.manifestUri}`);
|
|
815
|
+
}
|
|
816
|
+
break;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
},
|
|
820
|
+
});
|
|
821
|
+
let result;
|
|
822
|
+
let runError;
|
|
823
|
+
try {
|
|
824
|
+
result = await runner.runSuite(cases);
|
|
825
|
+
// Write reports for each configured format
|
|
826
|
+
const { generateReport } = await import('./test-report.js');
|
|
827
|
+
const reporters = driverConfig.reporters ?? ['json'];
|
|
828
|
+
const reportDir = path.resolve(sinkDir);
|
|
829
|
+
fs.mkdirSync(reportDir, { recursive: true });
|
|
830
|
+
const formatMeta = {
|
|
831
|
+
json: { ext: 'json', contentType: 'application/json' },
|
|
832
|
+
markdown: { ext: 'md', contentType: 'text/markdown' },
|
|
833
|
+
html: { ext: 'html', contentType: 'text/html' },
|
|
834
|
+
junit: { ext: 'xml', contentType: 'application/xml' },
|
|
835
|
+
};
|
|
836
|
+
for (const format of reporters) {
|
|
837
|
+
const meta = formatMeta[format];
|
|
838
|
+
if (!meta)
|
|
839
|
+
continue;
|
|
840
|
+
try {
|
|
841
|
+
const report = generateReport(result, { format, includeTurns: format === 'markdown' });
|
|
842
|
+
const reportPath = path.join(reportDir, `report.${meta.ext}`);
|
|
843
|
+
fs.writeFileSync(reportPath, report);
|
|
844
|
+
if (!quiet && !values.json) {
|
|
845
|
+
console.log(`Report: ${reportPath}`);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
catch {
|
|
849
|
+
// Report generation is best-effort
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
// Also write report to stdout if JSON mode
|
|
853
|
+
if (values.json && !quiet) {
|
|
854
|
+
console.log(JSON.stringify(result, null, 2));
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
catch (err) {
|
|
858
|
+
runError = err;
|
|
859
|
+
}
|
|
860
|
+
finally {
|
|
861
|
+
await singleDriver?.close?.().catch(() => { });
|
|
862
|
+
stopWalletAutoApprover?.();
|
|
863
|
+
await persistentContext?.close().catch(() => { });
|
|
864
|
+
await browser?.close().catch(() => { });
|
|
865
|
+
}
|
|
866
|
+
if (runError) {
|
|
867
|
+
throw runError;
|
|
868
|
+
}
|
|
869
|
+
await syncLocalBenchmarkRun(path.resolve(sinkDir), values.cases
|
|
870
|
+
? `${path.basename(values.cases)} · cli run`
|
|
871
|
+
: `${values.goal.slice(0, 80)} · cli run`);
|
|
872
|
+
process.exit((result?.summary.failed ?? 1) > 0 ? 1 : 0);
|
|
873
|
+
}
|
|
874
|
+
function printHelp() {
|
|
875
|
+
console.log(`
|
|
876
|
+
bad — LLM-driven browser automation CLI
|
|
877
|
+
|
|
878
|
+
USAGE:
|
|
879
|
+
bad run [options]
|
|
880
|
+
|
|
881
|
+
SINGLE TASK:
|
|
882
|
+
bad run --goal "Sign up for account" --url http://localhost:3000
|
|
883
|
+
bad run -g "Build a todo app" -u http://localhost:5173 -m claude-sonnet-4-20250514
|
|
884
|
+
bad run --goal "Create Coinbase blueprint and verify preview" --url https://ai.tangle.tools --persona alice-blueprint-builder
|
|
885
|
+
bad run --goal "Create partner project and verify preview works" --url https://ai.tangle.tools --persona auto
|
|
886
|
+
bad run --goal "Explore key routes quickly" --url https://example.com --mode fast-explore
|
|
887
|
+
|
|
888
|
+
TEST SUITE:
|
|
889
|
+
bad run --cases ./cases.json --concurrency 4
|
|
890
|
+
bad run --cases ./cases.json --sink ./results/ --model gpt-5.4
|
|
891
|
+
|
|
892
|
+
DOCKER:
|
|
893
|
+
docker run -v ./cases.json:/data/cases.json -v ./out:/output \\
|
|
894
|
+
bad run --cases /data/cases.json --sink /output/
|
|
895
|
+
|
|
896
|
+
OPTIONS:
|
|
897
|
+
--config <path> Path to config file (default: auto-detect)
|
|
898
|
+
-g, --goal <text> Natural language goal for single task
|
|
899
|
+
-u, --url <url> Starting URL
|
|
900
|
+
-c, --cases <file> JSON file with test cases array
|
|
901
|
+
--allowed-domains <csv> Comma-separated host allowlist (e.g. www.nih.gov,docs.foo.com)
|
|
902
|
+
--vision-strategy <m> Vision policy: always, never, auto
|
|
903
|
+
-m, --model <name> LLM model (default: gpt-5.4)
|
|
904
|
+
--provider <name> LLM provider: openai, anthropic, google, codex-cli, claude-code, sandbox-backend (default: openai)
|
|
905
|
+
--model-adaptive Enable adaptive model routing for decide() turns
|
|
906
|
+
--nav-model <name> Fast navigation model for adaptive routing
|
|
907
|
+
--nav-provider <name> Provider for nav model (default: same as --provider)
|
|
908
|
+
--persona <id> Append persona directive (${listPersonaIds().join(', ')})
|
|
909
|
+
--mode <name> Mode preset: ${RUN_MODES.join(', ')}
|
|
910
|
+
--profile <name> Execution profile: ${DRIVER_PROFILES.join(', ')}
|
|
911
|
+
--prompt-file <path> Load system prompt from file for experimentable prompt variants
|
|
912
|
+
--sandbox-backend-type <type> Native sidecar backend type for --provider sandbox-backend (e.g. claude-code, codex, opencode:with-web-search)
|
|
913
|
+
--sandbox-backend-profile <id> Optional native backend profile/preset ID
|
|
914
|
+
--sandbox-backend-provider <id> Optional native backend model provider override (mainly for opencode)
|
|
915
|
+
--api-key <key> API key (or set OPENAI_API_KEY / ANTHROPIC_API_KEY; codex-cli can use \`codex login\`; claude-code can use \`claude login\`)
|
|
916
|
+
--base-url <url> Custom LLM endpoint (e.g., LiteLLM proxy)
|
|
917
|
+
--browser <name> Browser: chromium, firefox, webkit (default: chromium)
|
|
918
|
+
--storage-state <file> Playwright storage state JSON for pre-authenticated session
|
|
919
|
+
--concurrency <n> Parallel workers (default: 1)
|
|
920
|
+
--max-turns <n> Max turns per test (default: 30)
|
|
921
|
+
--llm-timeout <ms> Timeout per LLM call in milliseconds
|
|
922
|
+
--retries <n> Retries for observe/LLM/action transient failures
|
|
923
|
+
--retry-delay-ms <ms> Base retry backoff in milliseconds
|
|
924
|
+
--screenshot-interval <n> Capture every N turns (default: 5)
|
|
925
|
+
--scout Enable scout/ranker recommendations on ambiguous pages
|
|
926
|
+
--scout-model <name> Model override for scout recommendations
|
|
927
|
+
--scout-provider <name> Provider override for scout recommendations
|
|
928
|
+
--scout-vision Let scout inspect screenshots when available
|
|
929
|
+
--scout-max-candidates <n> Max visible link candidates sent to the scout
|
|
930
|
+
--scout-min-top-score <n> Skip scout when the top deterministic score is already strong
|
|
931
|
+
--scout-max-score-gap <n> Skip scout when the top-vs-second score gap is already wide
|
|
932
|
+
--headless Run browser headless (default: true)
|
|
933
|
+
--no-headless Show browser window
|
|
934
|
+
--wallet Enable wallet mode (persistent Chromium profile)
|
|
935
|
+
--extension <path> Load unpacked wallet/browser extension (repeatable)
|
|
936
|
+
--user-data-dir <dir> Persistent profile directory for wallet sessions
|
|
937
|
+
--wallet-auto-approve Enable extension prompt auto-approval (default: true in wallet mode)
|
|
938
|
+
--wallet-password <v> Wallet unlock password for auto-approval/preflight
|
|
939
|
+
--wallet-preflight Run wallet origin preflight before tests (default: true in wallet mode)
|
|
940
|
+
--wallet-seed-url <u> Preflight URL to authorize/switch-chain (repeatable)
|
|
941
|
+
--wallet-chain-id <n> Target chain ID for preflight switch/add
|
|
942
|
+
--wallet-chain-rpc-url <u> RPC URL for preflight wallet_addEthereumChain
|
|
943
|
+
--memory Enable trajectory memory reuse
|
|
944
|
+
--memory-dir <dir> Memory directory (default: .agent-memory)
|
|
945
|
+
--timeout <ms> Per-test timeout in ms (default: 600000)
|
|
946
|
+
-s, --sink <dir> Output directory for artifacts (default: ./agent-results)
|
|
947
|
+
--json Output progress as JSON lines (for piping)
|
|
948
|
+
-q, --quiet Suppress all output
|
|
949
|
+
--quality-threshold <n> Min quality score 1-10 (default: 0 = skip)
|
|
950
|
+
--trace-scoring Enable trajectory scoring for reference trace selection
|
|
951
|
+
--trace-ttl-days <n> Retention window for scored traces (default: 30)
|
|
952
|
+
--goal-verification Verify goal completion (default: true)
|
|
953
|
+
--no-goal-verification Skip goal verification
|
|
954
|
+
--vision Enable vision/screenshots (default: true)
|
|
955
|
+
--no-vision Disable vision
|
|
956
|
+
--block-analytics Block analytics/tracking scripts
|
|
957
|
+
--block-images Block image loading
|
|
958
|
+
--block-media Block media loading (video, audio)
|
|
959
|
+
-d, --debug Enable debug logging
|
|
960
|
+
-h, --help Show this help
|
|
961
|
+
-v, --version Show version
|
|
962
|
+
|
|
963
|
+
TEST CASES JSON FORMAT:
|
|
964
|
+
[
|
|
965
|
+
{
|
|
966
|
+
"id": "signup",
|
|
967
|
+
"name": "User signup flow",
|
|
968
|
+
"goal": "Create a new account with email test@example.com",
|
|
969
|
+
"startUrl": "http://localhost:3000/signup",
|
|
970
|
+
"maxTurns": 20,
|
|
971
|
+
"timeoutMs": 300000,
|
|
972
|
+
"successDescription": "Account created and redirected to dashboard"
|
|
973
|
+
}
|
|
974
|
+
]
|
|
975
|
+
|
|
976
|
+
ENVIRONMENT VARIABLES:
|
|
977
|
+
OPENAI_API_KEY OpenAI API key
|
|
978
|
+
ANTHROPIC_API_KEY Anthropic API key
|
|
979
|
+
LLM_BASE_URL Custom LLM endpoint URL
|
|
980
|
+
CODEX_CLI_PATH Optional Codex CLI binary path for --provider codex-cli
|
|
981
|
+
CODEX_ALLOW_NPX Set to 0 to disable npx fallback for --provider codex-cli
|
|
982
|
+
CLAUDE_CODE_CLI_PATH Optional Claude CLI binary path for --provider claude-code
|
|
983
|
+
SANDBOX_BACKEND_TYPE Native backend type for --provider sandbox-backend
|
|
984
|
+
SANDBOX_BACKEND_PROFILE Native backend profile/preset for --provider sandbox-backend
|
|
985
|
+
SANDBOX_BACKEND_PROFILE_ID Legacy alias for SANDBOX_BACKEND_PROFILE
|
|
986
|
+
SANDBOX_BACKEND_MODEL_PROVIDER Native backend provider override for --provider sandbox-backend
|
|
987
|
+
SANDBOX_SIDECAR_URL Sidecar API URL for --provider sandbox-backend (default: http://127.0.0.1:$SIDECAR_PORT)
|
|
988
|
+
SANDBOX_SIDECAR_AUTH_TOKEN Sidecar API bearer token for --provider sandbox-backend
|
|
989
|
+
`);
|
|
990
|
+
}
|
|
991
|
+
function parseAllowedDomains(value) {
|
|
992
|
+
if (!value)
|
|
993
|
+
return undefined;
|
|
994
|
+
const domains = value
|
|
995
|
+
.split(',')
|
|
996
|
+
.map((entry) => entry.trim().toLowerCase())
|
|
997
|
+
.filter(Boolean);
|
|
998
|
+
return domains.length > 0 ? [...new Set(domains)] : undefined;
|
|
999
|
+
}
|
|
1000
|
+
async function syncLocalBenchmarkRun(outPath, label) {
|
|
1001
|
+
if (process.env.ABD_BENCHMARK_SYNC === '0')
|
|
1002
|
+
return;
|
|
1003
|
+
const importerPath = path.resolve(path.join(new URL('.', import.meta.url).pathname, '..', '..', 'abd-app', 'worker', 'scripts', 'import-local-benchmarks.mjs'));
|
|
1004
|
+
if (!fs.existsSync(importerPath))
|
|
1005
|
+
return;
|
|
1006
|
+
if (!fs.existsSync(outPath))
|
|
1007
|
+
return;
|
|
1008
|
+
const args = [importerPath, '--path', outPath, '--label', label];
|
|
1009
|
+
const userEmail = process.env.ABD_BENCHMARK_USER_EMAIL;
|
|
1010
|
+
if (userEmail)
|
|
1011
|
+
args.push('--user-email', userEmail);
|
|
1012
|
+
const exitCode = await new Promise((resolve) => {
|
|
1013
|
+
const child = spawn('node', args, {
|
|
1014
|
+
cwd: path.dirname(importerPath),
|
|
1015
|
+
env: {
|
|
1016
|
+
...process.env,
|
|
1017
|
+
ABD_BENCHMARK_SYNC: '0',
|
|
1018
|
+
},
|
|
1019
|
+
stdio: 'inherit',
|
|
1020
|
+
});
|
|
1021
|
+
child.once('error', () => resolve(1));
|
|
1022
|
+
child.once('close', (code) => resolve(code ?? 1));
|
|
1023
|
+
});
|
|
1024
|
+
if (exitCode !== 0) {
|
|
1025
|
+
if (process.env.ABD_BENCHMARK_SYNC_STRICT === '1') {
|
|
1026
|
+
throw new Error(`abd-app benchmark import failed for ${outPath}`);
|
|
1027
|
+
}
|
|
1028
|
+
console.warn(`abd-app benchmark import skipped after non-zero exit for ${outPath}`);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
main().catch((err) => {
|
|
1032
|
+
console.error(err instanceof Error ? err.message : err);
|
|
1033
|
+
process.exit(1);
|
|
1034
|
+
});
|
|
1035
|
+
//# sourceMappingURL=cli.js.map
|