@tangle-network/browser-agent-driver 0.33.3 → 0.35.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/browser-launch.d.ts +6 -0
- package/dist/browser-launch.d.ts.map +1 -1
- package/dist/browser-launch.js +3 -0
- package/dist/browser-launch.js.map +1 -1
- 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 +740 -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 +12 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +20 -2
- 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 +273 -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 +100 -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 +220 -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 +172 -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 +123 -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 +27 -1
- 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 +23 -5
package/dist/runner/runner.js
CHANGED
|
@@ -25,12 +25,18 @@ import { ContextBudget } from '../context-budget.js';
|
|
|
25
25
|
import { runOverridePipeline } from '../override-pipeline.js';
|
|
26
26
|
import { withRetry, findElementForRef, safeHostname, pushGoalVerificationEvidence } from './utils.js';
|
|
27
27
|
import { runExtractWithIndex, formatExtractWithIndexResult } from '../drivers/extract-with-index.js';
|
|
28
|
-
import { buildSearchResultsGuidance, buildVisibleLinkRecommendation, getVisibleLinkRecommendation
|
|
28
|
+
import { buildSearchResultsGuidance, buildVisibleLinkRecommendation, getVisibleLinkRecommendation } from './search-guidance.js';
|
|
29
29
|
import { buildGoalVerificationClaim, collectSearchWorkflowEvidence, shouldAcceptSearchWorkflowCompletion, shouldAcceptScriptBackedCompletion, detectCompletionContentTypeMismatch } from './goal-verification.js';
|
|
30
|
-
import { verifyExpectedEffect } from './effect-verification.js';
|
|
31
30
|
import { detectAiTanglePartnerTemplateVisibleState, detectAiTangleVerifiedOutputState, shouldEscalateVision } from './page-analysis.js';
|
|
32
|
-
import { shouldUseVisibleLinkScout, shouldUseVisibleLinkScoutPage, shouldUseBoundedBranchExplorer, inspectBranchPreview, scoreBranchPreview } from './scout.js';
|
|
33
31
|
import { buildOverrideProducers, buildScoutLinkRecommendationText, buildBranchLinkRecommendationText } from './overrides.js';
|
|
32
|
+
import { buildSearchResultsScoutFeedbackImpl, buildVisibleLinkScoutRecommendationImpl, buildBranchLinkRecommendationImpl, } from './scout-feedback.js';
|
|
33
|
+
import { filterScoutCandidatesByAllowedDomainsImpl, inspectDisallowedSearchClickImpl, enforceAllowedDomainBoundaryImpl, } from './domain-boundary.js';
|
|
34
|
+
import { attachDecisionScreenshotImpl } from './decision-screenshot.js';
|
|
35
|
+
import { verifyEffectImpl } from './effect-verify.js';
|
|
36
|
+
import { executePlanImpl } from './execute-plan.js';
|
|
37
|
+
import { verifyExpectedEffect } from './effect-verification.js';
|
|
38
|
+
import { createReplayController } from './replay/controller.js';
|
|
39
|
+
import { createReplayGuard } from './replay/guard.js';
|
|
34
40
|
import { RunRegistry } from '../memory/run-registry.js';
|
|
35
41
|
import { ensureBus } from './events.js';
|
|
36
42
|
import { DecisionCache } from './decision-cache.js';
|
|
@@ -40,81 +46,15 @@ import { matchDeterministicPattern } from './deterministic-patterns.js';
|
|
|
40
46
|
import { deriveDirectStartUrl, shouldAcceptRolledBookingCompletion } from './direct-start.js';
|
|
41
47
|
import { prepareGoogleFlightsSearch, shouldAcceptRolledGoogleFlightsCompletion } from './google-flights-preflight.js';
|
|
42
48
|
import { containsSelfContradictingCompletion } from './completion-language.js';
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
* 3. We haven't already injected this hint in the last turn (to avoid
|
|
53
|
-
* hint loops if the agent ignores it)
|
|
54
|
-
*
|
|
55
|
-
* The detector fires after one type action when two or more unused fields
|
|
56
|
-
* remain, which catches common two-field-per-step forms before the agent
|
|
57
|
-
* burns extra turns.
|
|
58
|
-
*
|
|
59
|
-
* The hint is high-priority (100) so it survives ctxBudget truncation, and
|
|
60
|
-
* it explicitly lists the unused @refs from the current snapshot so the LLM
|
|
61
|
-
* doesn't have to guess. The injection is gated by BAD_BATCH_HINT=0 for
|
|
62
|
-
* rollback.
|
|
63
|
-
*/
|
|
64
|
-
export function detectBatchFillOpportunity(turns, state) {
|
|
65
|
-
if (turns.length === 0)
|
|
66
|
-
return null;
|
|
67
|
-
const lastTurn = turns[turns.length - 1];
|
|
68
|
-
// Last action must be a single-step type on the current URL
|
|
69
|
-
if (lastTurn.action.action !== 'type')
|
|
70
|
-
return null;
|
|
71
|
-
if (lastTurn.state.url !== state.url)
|
|
72
|
-
return null;
|
|
73
|
-
// Collect the @refs the agent has typed into across the ENTIRE run on
|
|
74
|
-
// the same URL. The detector should never ask the agent to re-fill a
|
|
75
|
-
// field it already filled, even if the earlier fill happened many
|
|
76
|
-
// turns ago.
|
|
77
|
-
const usedRefs = new Set();
|
|
78
|
-
for (const t of turns) {
|
|
79
|
-
if (t.state.url !== state.url)
|
|
80
|
-
continue;
|
|
81
|
-
const a = t.action;
|
|
82
|
-
if (a.action === 'type' && 'selector' in a && typeof a.selector === 'string') {
|
|
83
|
-
usedRefs.add(a.selector);
|
|
84
|
-
}
|
|
85
|
-
if (a.action === 'fill') {
|
|
86
|
-
for (const k of Object.keys(a.fields ?? {}))
|
|
87
|
-
usedRefs.add(k);
|
|
88
|
-
for (const k of Object.keys(a.selects ?? {}))
|
|
89
|
-
usedRefs.add(k);
|
|
90
|
-
for (const k of a.checks ?? [])
|
|
91
|
-
usedRefs.add(k);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
// Find unused fillable refs in the current snapshot. We look for textbox,
|
|
95
|
-
// searchbox, combobox, and spinbutton roles — anything that takes text.
|
|
96
|
-
// Snapshot lines look like: ` - textbox "First name" [ref=t1f2a]`
|
|
97
|
-
const unusedRefs = [];
|
|
98
|
-
for (const line of state.snapshot.split('\n')) {
|
|
99
|
-
const match = line.match(/\b(textbox|searchbox|combobox|spinbutton)\b[^"]*"([^"]*)"[^[]*\[ref=([^\]]+)\]/i);
|
|
100
|
-
if (!match)
|
|
101
|
-
continue;
|
|
102
|
-
const role = match[1].toLowerCase();
|
|
103
|
-
const name = match[2];
|
|
104
|
-
const ref = `@${match[3]}`;
|
|
105
|
-
if (usedRefs.has(ref))
|
|
106
|
-
continue;
|
|
107
|
-
unusedRefs.push({ ref, name, role });
|
|
108
|
-
}
|
|
109
|
-
// Need at least 2 unused fields to make batching worthwhile
|
|
110
|
-
if (unusedRefs.length < 2)
|
|
111
|
-
return null;
|
|
112
|
-
const refList = unusedRefs
|
|
113
|
-
.slice(0, 12) // cap so we don't explode the prompt
|
|
114
|
-
.map((u) => ` - ${u.ref} (${u.role}: "${u.name}")`)
|
|
115
|
-
.join('\n');
|
|
116
|
-
return `\n[BATCH FILL REQUIRED]\nYou just typed into a single field, but ${unusedRefs.length} more fillable fields are visible on this same form. STOP. Your NEXT action MUST be a \`fill\` action that batches ALL remaining unused fields on this page in one turn. Do not emit another single-step \`type\` — emit \`fill\` with multiple entries.\n\nUnused fillable @refs from the current snapshot (use these in your \`fill.fields\` map):\n${refList}\n\nExample:\n{\"action\":\"fill\",\"fields\":{\"${unusedRefs[0].ref}\":\"value1\",\"${unusedRefs[1].ref}\":\"value2\"}}\n`;
|
|
117
|
-
}
|
|
49
|
+
import { detectBatchFillOpportunity } from './batch-fill.js';
|
|
50
|
+
import { DEFAULT_MAX_TURNS, DEFAULT_RETRIES, DEFAULT_RETRY_DELAY_MS, DEFAULT_SUPERVISOR, EXTENSION_TURNS_GRANTED, } from './constants.js';
|
|
51
|
+
import { DEFI_BRAIN_CONTEXT } from './prompt-snippets.js';
|
|
52
|
+
import { hasPlaceholderPattern, isMeaningfulRunScriptOutput } from './completion-policy.js';
|
|
53
|
+
import { selectMicroPlanFollowUps } from './micro-plan.js';
|
|
54
|
+
import { decideMaxTurnsExtension } from './max-turns-extension.js';
|
|
55
|
+
// Re-export helpers that moved into sibling modules so existing import paths
|
|
56
|
+
// (e.g. tests importing from './runner.js') keep working unchanged.
|
|
57
|
+
export { detectBatchFillOpportunity, hasPlaceholderPattern, isMeaningfulRunScriptOutput };
|
|
118
58
|
/** Build a structured session record from a completed run */
|
|
119
59
|
function buildSession(scenario, result) {
|
|
120
60
|
const lastTurn = result.turns[result.turns.length - 1];
|
|
@@ -129,99 +69,6 @@ function buildSession(scenario, result) {
|
|
|
129
69
|
durationMs: result.totalMs,
|
|
130
70
|
};
|
|
131
71
|
}
|
|
132
|
-
const DEFAULT_MAX_TURNS = 20;
|
|
133
|
-
const DEFAULT_RETRIES = 3;
|
|
134
|
-
const DEFAULT_RETRY_DELAY_MS = 1000;
|
|
135
|
-
const DEFAULT_MICRO_PLAN_ACTIONS = 2;
|
|
136
|
-
// Safe action verbs for micro-plans emitted by the model.
|
|
137
|
-
const SAFE_MICRO_ACTIONS = new Set(['click', 'type', 'press', 'hover', 'select', 'scroll', 'wait', 'clickAt', 'typeAt', 'clickLabel', 'typeLabel']);
|
|
138
|
-
const DEFAULT_SUPERVISOR = {
|
|
139
|
-
enabled: true,
|
|
140
|
-
useVision: true,
|
|
141
|
-
minTurnsBeforeInvoke: 5,
|
|
142
|
-
cooldownTurns: 3,
|
|
143
|
-
maxInterventions: 2,
|
|
144
|
-
hardStallWindow: 4,
|
|
145
|
-
};
|
|
146
|
-
const DEFI_BRAIN_CONTEXT = '\nWALLET/DeFi MODE ACTIVE — crypto app patterns:\n' +
|
|
147
|
-
'- PERSISTENT WIDGETS: Many DeFi apps have always-on support/chat widgets (bottom-right) that show as alertdialog. These CANNOT be dismissed. Ignore them and interact with the page directly.\n' +
|
|
148
|
-
'- WALLET CONNECTION: Look for "Connect Wallet" button, select MetaMask. The wallet extension handles the approval popup automatically.\n' +
|
|
149
|
-
'- TOKEN SELECTION: If you need to change a token, click the token selector button (usually shows token symbol/icon), search for the token name, and select it from the dropdown.\n' +
|
|
150
|
-
'- TRANSACTION FLOW: Enter amount → wait for quote/estimate to load → click action button (Swap/Supply/etc.) → review dialog appears → stop before confirming in MetaMask.\n' +
|
|
151
|
-
'- LOADING STATES: DeFi apps make many RPC calls. Wait for balances and quotes to appear before clicking action buttons. If a button says "Enter amount" or is disabled, the app is still loading.\n' +
|
|
152
|
-
'- NATIVE ETH: Prefer native ETH over wrapped tokens (WETH) when possible — avoids ERC-20 spending approval steps.\n' +
|
|
153
|
-
'- NETWORK SELECTOR: Do NOT change the network/chain. If a network dropdown opens accidentally, close it immediately.\n' +
|
|
154
|
-
'- COOKIE BANNERS: Dismiss immediately via Escape or Reject button — don\'t spend multiple turns on consent dialogs.\n';
|
|
155
|
-
/**
|
|
156
|
-
* Detect placeholder patterns in a planner-generated complete.result.
|
|
157
|
-
*
|
|
158
|
-
* The planner has to commit to its `complete.result` text BEFORE any prior
|
|
159
|
-
* runScript step actually runs, so on extraction tasks it fabricates
|
|
160
|
-
* placeholders. We detect those patterns and substitute the runScript
|
|
161
|
-
* output (deterministic, no extra LLM call).
|
|
162
|
-
*
|
|
163
|
-
* Patterns we catch:
|
|
164
|
-
* - JSON `null` literals (e.g. `{"x": null, "y": null}`)
|
|
165
|
-
* - "<from prior step>", "<placeholder>", "<value from ...>", "<extracted ...>", "<observed ...>"
|
|
166
|
-
* - "{{...}}" template markers
|
|
167
|
-
*
|
|
168
|
-
* Conservative on purpose — we only substitute when the planner clearly
|
|
169
|
-
* didn't know real values at planning time. A complete.result that contains
|
|
170
|
-
* actual data (no nulls, no placeholder markers) passes through unchanged.
|
|
171
|
-
*/
|
|
172
|
-
export function hasPlaceholderPattern(text) {
|
|
173
|
-
if (typeof text !== 'string' || text.length === 0)
|
|
174
|
-
return false;
|
|
175
|
-
if (/<from prior step>|<placeholder>|<value from|<extracted|<observed|<previous step|<runscript output>/i.test(text)) {
|
|
176
|
-
return true;
|
|
177
|
-
}
|
|
178
|
-
if (/\{\{[^}]+\}\}/.test(text)) {
|
|
179
|
-
return true;
|
|
180
|
-
}
|
|
181
|
-
// JSON-shape detection: a result that parses as JSON and contains null
|
|
182
|
-
// values is almost always a planner-fabricated extraction shell. Pure
|
|
183
|
-
// strings with the word "null" elsewhere don't match because we look
|
|
184
|
-
// for the JSON null literal pattern (`: null` or `[null`).
|
|
185
|
-
if (/:\s*null\b|\[\s*null\b/.test(text)) {
|
|
186
|
-
return true;
|
|
187
|
-
}
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
|
-
/** Returns true only when runScript output contains usable extracted data. */
|
|
191
|
-
export function isMeaningfulRunScriptOutput(output) {
|
|
192
|
-
if (typeof output !== 'string')
|
|
193
|
-
return false;
|
|
194
|
-
const trimmed = output.trim();
|
|
195
|
-
if (trimmed.length === 0)
|
|
196
|
-
return false;
|
|
197
|
-
if (trimmed === 'null' || trimmed === 'undefined' || trimmed === '""' || trimmed === "''")
|
|
198
|
-
return false;
|
|
199
|
-
// Empty JSON shells: `{}`, `[]`, `{"x": null}`, `[null, null]`
|
|
200
|
-
if (trimmed === '{}' || trimmed === '[]')
|
|
201
|
-
return false;
|
|
202
|
-
if (hasPlaceholderPattern(trimmed))
|
|
203
|
-
return false;
|
|
204
|
-
// If the output parses as JSON and EVERY top-level value is null/empty,
|
|
205
|
-
// treat it as not meaningful. This catches `{"x": null, "y": ""}` even
|
|
206
|
-
// though the placeholder regex would already catch the null one.
|
|
207
|
-
try {
|
|
208
|
-
const parsed = JSON.parse(trimmed);
|
|
209
|
-
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
210
|
-
const values = Object.values(parsed);
|
|
211
|
-
if (values.length > 0) {
|
|
212
|
-
const allEmpty = values.every((v) => v === null || v === undefined || v === '' || v === 0);
|
|
213
|
-
if (allEmpty)
|
|
214
|
-
return false;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
if (Array.isArray(parsed) && parsed.length === 0)
|
|
218
|
-
return false;
|
|
219
|
-
}
|
|
220
|
-
catch {
|
|
221
|
-
// Not JSON, that's fine — fall through to "meaningful" if we got here.
|
|
222
|
-
}
|
|
223
|
-
return true;
|
|
224
|
-
}
|
|
225
72
|
export function shouldUsePlannerForScenario(scenario, mode = 'always') {
|
|
226
73
|
if (mode !== 'auto')
|
|
227
74
|
return true;
|
|
@@ -242,6 +89,12 @@ export function shouldUsePlannerForScenario(scenario, mode = 'always') {
|
|
|
242
89
|
return true;
|
|
243
90
|
}
|
|
244
91
|
export class BrowserAgent {
|
|
92
|
+
// Public fields below satisfy the extracted host interfaces (RunnerScoutHost,
|
|
93
|
+
// RunnerDomainHost, RunnerDecisionScreenshotHost, RunnerEffectVerifyHost,
|
|
94
|
+
// RunnerExecuteHost). The free functions in scout-feedback.ts,
|
|
95
|
+
// domain-boundary.ts, decision-screenshot.ts, effect-verify.ts, and
|
|
96
|
+
// execute-plan.ts read them through those interfaces; `implements` makes tsc
|
|
97
|
+
// prove the surface is complete.
|
|
245
98
|
driver;
|
|
246
99
|
brain;
|
|
247
100
|
config;
|
|
@@ -249,11 +102,15 @@ export class BrowserAgent {
|
|
|
249
102
|
beforeTurn;
|
|
250
103
|
onPhaseTiming;
|
|
251
104
|
referenceTrajectory;
|
|
105
|
+
replayPlan;
|
|
252
106
|
projectStore;
|
|
253
107
|
runRegistry;
|
|
254
108
|
knowledge;
|
|
255
109
|
selectorCache;
|
|
110
|
+
// Public so the extracted effect-verify.ts / execute-plan.ts hosts can read
|
|
111
|
+
// and write the cached post-action snapshot.
|
|
256
112
|
cachedPostState;
|
|
113
|
+
// Public so the extracted execute-plan.ts host can emit loop events.
|
|
257
114
|
bus;
|
|
258
115
|
currentRunId = '';
|
|
259
116
|
// In-session decision cache. Lazy-skips brain.decide() when the (snapshot,
|
|
@@ -272,6 +129,7 @@ export class BrowserAgent {
|
|
|
272
129
|
this.beforeTurn = options.beforeTurn;
|
|
273
130
|
this.onPhaseTiming = options.onPhaseTiming;
|
|
274
131
|
this.referenceTrajectory = options.referenceTrajectory;
|
|
132
|
+
this.replayPlan = options.replayPlan;
|
|
275
133
|
this.bus = ensureBus(options.eventBus);
|
|
276
134
|
this.extensions = options.extensions;
|
|
277
135
|
if (this.extensions) {
|
|
@@ -335,9 +193,6 @@ export class BrowserAgent {
|
|
|
335
193
|
// when they reach the configured cap while still making page progress.
|
|
336
194
|
let maxTurns = isVisionMode ? Math.max(baseMaxTurns, 30) : baseMaxTurns;
|
|
337
195
|
let extensionGranted = false;
|
|
338
|
-
const EXTENSION_TURNS_GRANTED = 5;
|
|
339
|
-
const EXTENSION_HARD_CAP = 25;
|
|
340
|
-
const EXTENSION_PROGRESS_LOOKBACK = 3;
|
|
341
196
|
const retries = this.config.retries ?? DEFAULT_RETRIES;
|
|
342
197
|
const retryDelayMs = this.config.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
|
|
343
198
|
const turns = [];
|
|
@@ -499,6 +354,59 @@ export class BrowserAgent {
|
|
|
499
354
|
maxInterventions: this.config.supervisor?.maxInterventions ?? DEFAULT_SUPERVISOR.maxInterventions,
|
|
500
355
|
hardStallWindow: this.config.supervisor?.hardStallWindow ?? DEFAULT_SUPERVISOR.hardStallWindow,
|
|
501
356
|
};
|
|
357
|
+
// Replay-first path (sibling to the planner-first path below): when replay
|
|
358
|
+
// is enabled and a strict-matched plan was supplied, re-execute the recorded
|
|
359
|
+
// steps with ZERO per-action LLM calls. A fully-replayed-and-verified
|
|
360
|
+
// outcome returns immediately; anything else (a self-heal abort, or a clean
|
|
361
|
+
// replay whose goal verification did not confirm achievement) falls through
|
|
362
|
+
// to the planner / per-action loop FROM THE CURRENT PAGE STATE — the page is
|
|
363
|
+
// already navigated above, so there is no re-navigation. Replay appends to
|
|
364
|
+
// the same `turns` array, so the unified timeline is preserved and the loop
|
|
365
|
+
// offset (`plannerStartTurn`) is advanced past the replayed turns.
|
|
366
|
+
let replayTurnsConsumed = 0;
|
|
367
|
+
if (this.config.replay?.enabled && this.replayPlan && scenario.startUrl) {
|
|
368
|
+
const replayOutcome = await createReplayController().replay(this.replayPlan, {
|
|
369
|
+
goal: scenario.goal,
|
|
370
|
+
driver: this.driver,
|
|
371
|
+
guard: createReplayGuard(),
|
|
372
|
+
verifyGoal: (state, goal, claimed) => this.brain.verifyGoalCompletion(state, goal, claimed),
|
|
373
|
+
verifyEffect: (input) => verifyExpectedEffect(input),
|
|
374
|
+
...(scenario.signal ? { signal: scenario.signal } : {}),
|
|
375
|
+
...(this.config.debug ? { debug: true } : {}),
|
|
376
|
+
onEvent: (event) => this.handleReplayEvent(runId, event),
|
|
377
|
+
});
|
|
378
|
+
for (const turn of replayOutcome.turns) {
|
|
379
|
+
turns.push(turn);
|
|
380
|
+
this.onTurn?.(turn);
|
|
381
|
+
}
|
|
382
|
+
replayTurnsConsumed = turns.length;
|
|
383
|
+
if (replayOutcome.kind === 'completed' && replayOutcome.goalVerification.achieved) {
|
|
384
|
+
return buildResult({
|
|
385
|
+
success: true,
|
|
386
|
+
result: replayOutcome.finalResult,
|
|
387
|
+
turns,
|
|
388
|
+
totalMs: Date.now() - startTime,
|
|
389
|
+
goalVerification: replayOutcome.goalVerification,
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
// healed | completed-but-not-achieved → fall through from the live state.
|
|
393
|
+
// (`no-candidate` is impossible here — a plan was supplied.)
|
|
394
|
+
if (replayOutcome.kind !== 'no-candidate') {
|
|
395
|
+
// Only prime the loop's first observe when we captured a usable
|
|
396
|
+
// snapshot; an empty snapshot would otherwise short-circuit observe().
|
|
397
|
+
if (replayOutcome.lastState.snapshot) {
|
|
398
|
+
this.cachedPostState = replayOutcome.lastState;
|
|
399
|
+
}
|
|
400
|
+
const fallbackReason = replayOutcome.kind === 'healed'
|
|
401
|
+
? replayOutcome.reason
|
|
402
|
+
: `replay completed ${replayOutcome.completedSteps}/${replayOutcome.totalSteps} steps but goal verification did not confirm achievement`;
|
|
403
|
+
this.brain.injectFeedback(`[REPLAY] A recorded shortcut for this goal was attempted but did not fully complete it (${fallbackReason}). `
|
|
404
|
+
+ `${replayTurnsConsumed} step(s) were already executed against the live page. Continue toward the goal from the current page state.`);
|
|
405
|
+
if (this.config.debug) {
|
|
406
|
+
console.log(`[Runner] Replay fell through after ${replayTurnsConsumed} step(s): ${fallbackReason}`);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
502
410
|
// Planner-first path: make one LLM call to generate a plan, then execute
|
|
503
411
|
// it deterministically. On deviation, replan from the current page state
|
|
504
412
|
// with deviation context; after the retry budget is exhausted, fall back
|
|
@@ -507,7 +415,7 @@ export class BrowserAgent {
|
|
|
507
415
|
// Plan execution writes to the same `turns` array, so post-run analysis
|
|
508
416
|
// sees a unified timeline regardless of which path completed the run.
|
|
509
417
|
let planFallbackContext = '';
|
|
510
|
-
let plannerStartTurn =
|
|
418
|
+
let plannerStartTurn = replayTurnsConsumed;
|
|
511
419
|
const plannerEnabled = this.config.plannerEnabled === true && process.env.BAD_PLANNER !== '0'
|
|
512
420
|
&& shouldUsePlannerForScenario(scenario, this.config.plannerMode ?? 'always');
|
|
513
421
|
const maxReplans = 3;
|
|
@@ -538,7 +446,9 @@ export class BrowserAgent {
|
|
|
538
446
|
if (initialState) {
|
|
539
447
|
this.cachedPostState = initialState;
|
|
540
448
|
let planLoopState = initialState;
|
|
541
|
-
|
|
449
|
+
// Start past any replayed turns so the planner's turn numbering and the
|
|
450
|
+
// per-action loop offset stay contiguous with the replay timeline.
|
|
451
|
+
let cumulativeTurnsConsumed = replayTurnsConsumed;
|
|
542
452
|
let attempt = 0;
|
|
543
453
|
let lastDeviationReason = '';
|
|
544
454
|
let lastFailedStepIndex = 0;
|
|
@@ -675,24 +585,23 @@ export class BrowserAgent {
|
|
|
675
585
|
for (let i = 1 + plannerStartTurn; i <= maxTurns; i++) {
|
|
676
586
|
// Grant a one-time max-turns extension when the run reaches its cap
|
|
677
587
|
// while still making recent progress.
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
}
|
|
588
|
+
const turnsExtension = decideMaxTurnsExtension({
|
|
589
|
+
turn: i,
|
|
590
|
+
maxTurns,
|
|
591
|
+
extensionGranted,
|
|
592
|
+
isVisionMode,
|
|
593
|
+
lastProgressTurn: runState.lastProgressTurn,
|
|
594
|
+
});
|
|
595
|
+
if (turnsExtension) {
|
|
596
|
+
this.bus.emitNow({
|
|
597
|
+
type: 'recovery-fired',
|
|
598
|
+
runId,
|
|
599
|
+
turn: i,
|
|
600
|
+
strategy: 'max-turns-extension',
|
|
601
|
+
feedback: `Granted +${turnsExtension.extra} extra turns (cap ${maxTurns} → ${turnsExtension.extendedMax}); progress at turn ${runState.lastProgressTurn}.`,
|
|
602
|
+
});
|
|
603
|
+
maxTurns = turnsExtension.extendedMax;
|
|
604
|
+
extensionGranted = true;
|
|
696
605
|
}
|
|
697
606
|
// Honor user-driven pause or abort from the interrupt controller.
|
|
698
607
|
if (this.beforeTurn) {
|
|
@@ -810,6 +719,7 @@ export class BrowserAgent {
|
|
|
810
719
|
}, scenario.signal);
|
|
811
720
|
this.cachedPostState = undefined;
|
|
812
721
|
const observeDurationMs = Date.now() - observeStartedAt;
|
|
722
|
+
phaseTimings.totalObserveMs = (phaseTimings.totalObserveMs ?? 0) + observeDurationMs;
|
|
813
723
|
if (phaseTimings.firstObserveMs === undefined) {
|
|
814
724
|
phaseTimings.firstObserveMs = observeDurationMs;
|
|
815
725
|
this.onPhaseTiming?.('observe', phaseTimings.firstObserveMs);
|
|
@@ -1294,6 +1204,15 @@ export class BrowserAgent {
|
|
|
1294
1204
|
}
|
|
1295
1205
|
}
|
|
1296
1206
|
const decideDurationMs = Date.now() - decideStartedAt;
|
|
1207
|
+
phaseTimings.totalDecideMs = (phaseTimings.totalDecideMs ?? 0) + decideDurationMs;
|
|
1208
|
+
// A pattern-match or cache-hit synthesised the decision with no LLM call;
|
|
1209
|
+
// anything else round-tripped the model. Tracks the lazy-decision hit rate.
|
|
1210
|
+
if (patternMatch || cached) {
|
|
1211
|
+
phaseTimings.decideSkips = (phaseTimings.decideSkips ?? 0) + 1;
|
|
1212
|
+
}
|
|
1213
|
+
else {
|
|
1214
|
+
phaseTimings.decideLlmCalls = (phaseTimings.decideLlmCalls ?? 0) + 1;
|
|
1215
|
+
}
|
|
1297
1216
|
if (phaseTimings.firstDecideMs === undefined) {
|
|
1298
1217
|
phaseTimings.firstDecideMs = decideDurationMs;
|
|
1299
1218
|
this.onPhaseTiming?.('decide', phaseTimings.firstDecideMs);
|
|
@@ -1887,8 +1806,10 @@ export class BrowserAgent {
|
|
|
1887
1806
|
}, scenario.signal);
|
|
1888
1807
|
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error(`Execute wall-clock timeout after ${executeWallClockMs}ms`)), executeWallClockMs));
|
|
1889
1808
|
execResult = await Promise.race([executePromise, timeoutPromise]);
|
|
1809
|
+
const executeDurationMs = Date.now() - executeStartedAt;
|
|
1810
|
+
phaseTimings.totalExecuteMs = (phaseTimings.totalExecuteMs ?? 0) + executeDurationMs;
|
|
1890
1811
|
if (phaseTimings.firstExecuteMs === undefined) {
|
|
1891
|
-
phaseTimings.firstExecuteMs =
|
|
1812
|
+
phaseTimings.firstExecuteMs = executeDurationMs;
|
|
1892
1813
|
this.onPhaseTiming?.('execute', phaseTimings.firstExecuteMs);
|
|
1893
1814
|
}
|
|
1894
1815
|
this.bus.emitNow({
|
|
@@ -2156,27 +2077,37 @@ export class BrowserAgent {
|
|
|
2156
2077
|
});
|
|
2157
2078
|
}
|
|
2158
2079
|
selectFollowUpActions(primaryAction, nextActions) {
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
if (
|
|
2170
|
-
return
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2080
|
+
return selectMicroPlanFollowUps(primaryAction, nextActions, this.config.microPlan);
|
|
2081
|
+
}
|
|
2082
|
+
/**
|
|
2083
|
+
* Surface replay lifecycle events. The structured TurnEventBus union is closed
|
|
2084
|
+
* and replay is opt-in, so rather than widen it we log under debug — the
|
|
2085
|
+
* self-heal decision is never silent (the controller also logs + returns a
|
|
2086
|
+
* reason, and the runner logs the fall-through). `runId` is accepted for
|
|
2087
|
+
* symmetry with bus emits and future wiring.
|
|
2088
|
+
*/
|
|
2089
|
+
handleReplayEvent(_runId, event) {
|
|
2090
|
+
if (!this.config.debug)
|
|
2091
|
+
return;
|
|
2092
|
+
switch (event.type) {
|
|
2093
|
+
case 'replay-started':
|
|
2094
|
+
console.log(`[Replay] start: ${event.totalSteps} step(s), goalSimilarity=${event.goalSimilarity.toFixed(2)}`);
|
|
2095
|
+
break;
|
|
2096
|
+
case 'replay-guard-abort':
|
|
2097
|
+
console.log(`[Replay] guard abort at step ${event.index + 1}: ${event.reason}`);
|
|
2098
|
+
break;
|
|
2099
|
+
case 'replay-effect-failed':
|
|
2100
|
+
console.log(`[Replay] effect failed at step ${event.index + 1}: ${event.reason}`);
|
|
2101
|
+
break;
|
|
2102
|
+
case 'replay-healed':
|
|
2103
|
+
console.log(`[Replay] healed ${event.completedSteps}/${event.totalSteps}: ${event.reason}`);
|
|
2104
|
+
break;
|
|
2105
|
+
case 'replay-completed':
|
|
2106
|
+
console.log(`[Replay] completed ${event.completedSteps} step(s), achieved=${event.achieved}`);
|
|
2107
|
+
break;
|
|
2108
|
+
default:
|
|
2177
2109
|
break;
|
|
2178
2110
|
}
|
|
2179
|
-
return selected;
|
|
2180
2111
|
}
|
|
2181
2112
|
/** Persist knowledge, selector cache, and session history to disk */
|
|
2182
2113
|
saveMemory(scenario, result, turns) {
|
|
@@ -2243,659 +2174,32 @@ export class BrowserAgent {
|
|
|
2243
2174
|
* unified timeline. The Turn's `reasoning` field carries the plan
|
|
2244
2175
|
* step's rationale, and `verified` carries the verification result.
|
|
2245
2176
|
*/
|
|
2246
|
-
async executePlan(plan, scenario, runId, turns, runState, startingTurnIndex,
|
|
2247
|
-
|
|
2248
|
-
* Token usage from the Brain.plan() LLM call that produced this plan.
|
|
2249
|
-
* The plan call's tokens are NOT attached to any per-step turn — there's
|
|
2250
|
-
* one plan call per N steps. To make the run-level cost tally honest,
|
|
2251
|
-
* we attribute the plan call to the FIRST step's Turn artifact so the
|
|
2252
|
-
* downstream sum (in baseline-summary.json / report.json) reflects the
|
|
2253
|
-
* real LLM spend.
|
|
2254
|
-
*/
|
|
2255
|
-
planCallTokens) {
|
|
2256
|
-
let currentTurnIndex = startingTurnIndex;
|
|
2257
|
-
let lastState = turns[turns.length - 1]?.state
|
|
2258
|
-
?? { url: '', title: '', snapshot: '' };
|
|
2259
|
-
// Track the last successful runScript output so placeholder complete
|
|
2260
|
-
// results can be substituted with the real script output.
|
|
2261
|
-
let lastRunScriptOutput = null;
|
|
2262
|
-
// Track extractWithIndex matches for per-action fallback; the LLM must
|
|
2263
|
-
// read the list and pick by index.
|
|
2264
|
-
let lastExtractOutput = null;
|
|
2265
|
-
for (let stepIdx = 0; stepIdx < plan.steps.length; stepIdx++) {
|
|
2266
|
-
if (scenario.signal?.aborted) {
|
|
2267
|
-
return {
|
|
2268
|
-
kind: 'deviated',
|
|
2269
|
-
lastState,
|
|
2270
|
-
failedStepIndex: stepIdx,
|
|
2271
|
-
reason: scenario.signal.reason || 'Cancelled',
|
|
2272
|
-
turnsConsumed: stepIdx,
|
|
2273
|
-
};
|
|
2274
|
-
}
|
|
2275
|
-
const step = plan.steps[stepIdx];
|
|
2276
|
-
const stepStartedAt = Date.now();
|
|
2277
|
-
const turnNumber = currentTurnIndex + 1;
|
|
2278
|
-
currentTurnIndex++;
|
|
2279
|
-
// Refresh the snapshot before EVERY step. The plan was built from a
|
|
2280
|
-
// single observe() call at turn 1; later steps may target a different
|
|
2281
|
-
// page entirely (after navigate / click "Next"). The first observe
|
|
2282
|
-
// here is also what verify-against will eventually consume.
|
|
2283
|
-
const preStepState = await this.driver.observe().catch(() => lastState);
|
|
2284
|
-
lastState = preStepState;
|
|
2285
|
-
// Wrap each plan step in a Turn artifact so post-run analysis (the
|
|
2286
|
-
// viewer, the events.jsonl persistence, the metrics) sees a unified
|
|
2287
|
-
// timeline regardless of whether the runner used the planner or the
|
|
2288
|
-
// per-action loop.
|
|
2289
|
-
//
|
|
2290
|
-
// Token attribution: the first step carries the Brain.plan() LLM call's
|
|
2291
|
-
// token usage so run-level cost includes planning.
|
|
2292
|
-
const isFirstStep = stepIdx === 0;
|
|
2293
|
-
const turn = {
|
|
2294
|
-
turn: turnNumber,
|
|
2295
|
-
state: preStepState,
|
|
2296
|
-
action: step.action,
|
|
2297
|
-
reasoning: step.rationale ?? `Plan step ${stepIdx + 1}/${plan.steps.length}`,
|
|
2298
|
-
expectedEffect: step.expectedEffect,
|
|
2299
|
-
plan: plan.steps.map((s) => s.rationale ?? s.action.action),
|
|
2300
|
-
currentStep: stepIdx,
|
|
2301
|
-
durationMs: 0,
|
|
2302
|
-
...(isFirstStep && planCallTokens?.tokensUsed !== undefined ? { tokensUsed: planCallTokens.tokensUsed } : {}),
|
|
2303
|
-
...(isFirstStep && planCallTokens?.inputTokens !== undefined ? { inputTokens: planCallTokens.inputTokens } : {}),
|
|
2304
|
-
...(isFirstStep && planCallTokens?.outputTokens !== undefined ? { outputTokens: planCallTokens.outputTokens } : {}),
|
|
2305
|
-
...(isFirstStep && planCallTokens?.cacheReadInputTokens !== undefined ? { cacheReadInputTokens: planCallTokens.cacheReadInputTokens } : {}),
|
|
2306
|
-
...(isFirstStep && planCallTokens?.cacheCreationInputTokens !== undefined ? { cacheCreationInputTokens: planCallTokens.cacheCreationInputTokens } : {}),
|
|
2307
|
-
};
|
|
2308
|
-
// Terminal actions: complete and abort don't go through driver.execute
|
|
2309
|
-
// — the runner handles them as the end of the plan.
|
|
2310
|
-
if (step.action.action === 'complete') {
|
|
2311
|
-
// Substitute placeholder complete results with prior runScript output.
|
|
2312
|
-
let resolvedResult = step.action.result;
|
|
2313
|
-
if (lastRunScriptOutput
|
|
2314
|
-
&& typeof resolvedResult === 'string'
|
|
2315
|
-
&& hasPlaceholderPattern(resolvedResult)) {
|
|
2316
|
-
if (this.config.debug) {
|
|
2317
|
-
console.log(`[Runner] Substituting placeholder complete.result with runScript output (${lastRunScriptOutput.length} chars)`);
|
|
2318
|
-
}
|
|
2319
|
-
resolvedResult = lastRunScriptOutput;
|
|
2320
|
-
turn.reasoning = `${turn.reasoning ?? ''} [substituted runScript output]`.trim();
|
|
2321
|
-
}
|
|
2322
|
-
turn.durationMs = Date.now() - stepStartedAt;
|
|
2323
|
-
turns.push(turn);
|
|
2324
|
-
this.onTurn?.(turn);
|
|
2325
|
-
this.bus.emitNow({
|
|
2326
|
-
type: 'plan-step-executed',
|
|
2327
|
-
runId,
|
|
2328
|
-
turn: turnNumber,
|
|
2329
|
-
stepIndex: stepIdx + 1,
|
|
2330
|
-
totalSteps: plan.steps.length,
|
|
2331
|
-
action: step.action,
|
|
2332
|
-
executeSuccess: true,
|
|
2333
|
-
verified: true,
|
|
2334
|
-
durationMs: turn.durationMs,
|
|
2335
|
-
});
|
|
2336
|
-
return {
|
|
2337
|
-
kind: 'completed',
|
|
2338
|
-
lastState,
|
|
2339
|
-
finalResult: resolvedResult,
|
|
2340
|
-
turnsConsumed: stepIdx + 1,
|
|
2341
|
-
};
|
|
2342
|
-
}
|
|
2343
|
-
if (step.action.action === 'abort') {
|
|
2344
|
-
turn.durationMs = Date.now() - stepStartedAt;
|
|
2345
|
-
turns.push(turn);
|
|
2346
|
-
this.onTurn?.(turn);
|
|
2347
|
-
this.bus.emitNow({
|
|
2348
|
-
type: 'plan-deviated',
|
|
2349
|
-
runId,
|
|
2350
|
-
turn: turnNumber,
|
|
2351
|
-
stepIndex: stepIdx + 1,
|
|
2352
|
-
totalSteps: plan.steps.length,
|
|
2353
|
-
reason: `plan aborted: ${step.action.reason}`,
|
|
2354
|
-
});
|
|
2355
|
-
return {
|
|
2356
|
-
kind: 'deviated',
|
|
2357
|
-
lastState,
|
|
2358
|
-
failedStepIndex: stepIdx,
|
|
2359
|
-
reason: `plan aborted: ${step.action.reason}`,
|
|
2360
|
-
turnsConsumed: stepIdx + 1,
|
|
2361
|
-
};
|
|
2362
|
-
}
|
|
2363
|
-
// Execute the action via the existing driver path. This emits
|
|
2364
|
-
// execute-started / execute-completed events on the bus exactly
|
|
2365
|
-
// like the per-action loop does.
|
|
2366
|
-
//
|
|
2367
|
-
// Cap each plan step at 10s so missing selectors fail quickly and hand
|
|
2368
|
-
// control back to per-action mode.
|
|
2369
|
-
this.bus.emitNow({ type: 'execute-started', runId, turn: turnNumber, action: step.action });
|
|
2370
|
-
const execStartedAt = Date.now();
|
|
2371
|
-
const planStepTimeoutMs = 10_000;
|
|
2372
|
-
let execResult;
|
|
2373
|
-
try {
|
|
2374
|
-
execResult = await Promise.race([
|
|
2375
|
-
this.driver.execute(step.action),
|
|
2376
|
-
new Promise((resolve) => setTimeout(() => resolve({ success: false, error: `plan step wall-clock timeout after ${planStepTimeoutMs}ms` }), planStepTimeoutMs)),
|
|
2377
|
-
]);
|
|
2378
|
-
}
|
|
2379
|
-
catch (err) {
|
|
2380
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2381
|
-
execResult = { success: false, error: message };
|
|
2382
|
-
}
|
|
2383
|
-
const execDurationMs = Date.now() - execStartedAt;
|
|
2384
|
-
this.bus.emitNow({
|
|
2385
|
-
type: 'execute-completed',
|
|
2386
|
-
runId,
|
|
2387
|
-
turn: turnNumber,
|
|
2388
|
-
action: step.action,
|
|
2389
|
-
success: execResult.success,
|
|
2390
|
-
...(execResult.error ? { error: execResult.error } : {}),
|
|
2391
|
-
...(execResult.bounds ? { bounds: execResult.bounds } : {}),
|
|
2392
|
-
durationMs: execDurationMs,
|
|
2393
|
-
});
|
|
2394
|
-
if (!execResult.success) {
|
|
2395
|
-
turn.error = execResult.error;
|
|
2396
|
-
turn.durationMs = Date.now() - stepStartedAt;
|
|
2397
|
-
turn.verified = false;
|
|
2398
|
-
turn.verificationFailure = `execute failed: ${execResult.error}`;
|
|
2399
|
-
turns.push(turn);
|
|
2400
|
-
this.onTurn?.(turn);
|
|
2401
|
-
runState.recordError();
|
|
2402
|
-
this.bus.emitNow({
|
|
2403
|
-
type: 'plan-step-executed',
|
|
2404
|
-
runId,
|
|
2405
|
-
turn: turnNumber,
|
|
2406
|
-
stepIndex: stepIdx + 1,
|
|
2407
|
-
totalSteps: plan.steps.length,
|
|
2408
|
-
action: step.action,
|
|
2409
|
-
executeSuccess: false,
|
|
2410
|
-
verified: false,
|
|
2411
|
-
durationMs: Date.now() - stepStartedAt,
|
|
2412
|
-
...(execResult.error ? { verifyReason: execResult.error } : {}),
|
|
2413
|
-
});
|
|
2414
|
-
this.bus.emitNow({
|
|
2415
|
-
type: 'plan-deviated',
|
|
2416
|
-
runId,
|
|
2417
|
-
turn: turnNumber,
|
|
2418
|
-
stepIndex: stepIdx + 1,
|
|
2419
|
-
totalSteps: plan.steps.length,
|
|
2420
|
-
reason: `execute failed: ${execResult.error}`,
|
|
2421
|
-
});
|
|
2422
|
-
return {
|
|
2423
|
-
kind: 'deviated',
|
|
2424
|
-
lastState,
|
|
2425
|
-
failedStepIndex: stepIdx,
|
|
2426
|
-
reason: `execute failed at step ${stepIdx + 1}: ${execResult.error}`,
|
|
2427
|
-
turnsConsumed: stepIdx + 1,
|
|
2428
|
-
};
|
|
2429
|
-
}
|
|
2430
|
-
runState.clearConsecutiveErrors();
|
|
2431
|
-
if (execResult.bounds)
|
|
2432
|
-
turn.actionBounds = execResult.bounds;
|
|
2433
|
-
// Capture runScript output for placeholder substitution and evidence.
|
|
2434
|
-
if (step.action.action === 'runScript' && typeof execResult.data === 'string' && execResult.data.length > 0) {
|
|
2435
|
-
lastRunScriptOutput = execResult.data;
|
|
2436
|
-
if (execResult.data.length > 10) {
|
|
2437
|
-
runState.recordEvidence(`EXTRACTED (turn ${currentTurnIndex}): ${execResult.data.slice(0, 500)}`);
|
|
2438
|
-
}
|
|
2439
|
-
}
|
|
2440
|
-
// Capture extractWithIndex output for per-action fallback.
|
|
2441
|
-
if (step.action.action === 'extractWithIndex' && typeof execResult.data === 'string' && execResult.data.length > 0) {
|
|
2442
|
-
lastExtractOutput = execResult.data;
|
|
2443
|
-
// Also push as goal verification evidence so the verifier sees what
|
|
2444
|
-
// the agent extracted.
|
|
2445
|
-
runState.firstSufficientEvidenceTurn ??= currentTurnIndex;
|
|
2446
|
-
pushGoalVerificationEvidence(runState.goalVerificationEvidence, `EXTRACT RESULT:\n${execResult.data}`);
|
|
2447
|
-
if (execResult.data.length > 10) {
|
|
2448
|
-
runState.recordEvidence(`EXTRACTED (turn ${currentTurnIndex}): ${execResult.data.slice(0, 500)}`);
|
|
2449
|
-
}
|
|
2450
|
-
}
|
|
2451
|
-
// Verify the post-condition. We re-observe to get the post-action
|
|
2452
|
-
// state, then run the same verifyExpectedEffect helper the per-action
|
|
2453
|
-
// loop uses. The fresh observe is also stashed in cachedPostState so
|
|
2454
|
-
// the next step's pre-step observe can reuse it.
|
|
2455
|
-
this.bus.emitNow({
|
|
2456
|
-
type: 'verify-started',
|
|
2457
|
-
runId,
|
|
2458
|
-
turn: turnNumber,
|
|
2459
|
-
expectedEffect: step.expectedEffect,
|
|
2460
|
-
});
|
|
2461
|
-
const verifyStartedAt = Date.now();
|
|
2462
|
-
// Auto-pass list — these actions either don't observably mutate
|
|
2463
|
-
// the page state OR they're self-verifying (the underlying Playwright
|
|
2464
|
-
// call throws on real failure, so a successful return means the
|
|
2465
|
-
// mutation actually happened). Strict expectedEffect verification
|
|
2466
|
-
// would generate false negatives on the per-action loop fallback for
|
|
2467
|
-
// these. Plan execution trusts the execute result.
|
|
2468
|
-
//
|
|
2469
|
-
// - wait / scroll / hover: don't mutate observable snapshot state
|
|
2470
|
-
// - runScript / evaluate / verifyPreview: meta actions
|
|
2471
|
-
// - fill / clickSequence: self-verifying (Playwright throws on miss),
|
|
2472
|
-
// AND input values don't always reflect in the ARIA snapshot, so
|
|
2473
|
-
// the permissive "did state change?" check would also miss them
|
|
2474
|
-
const isAutoPass = step.action.action === 'wait'
|
|
2475
|
-
|| step.action.action === 'scroll'
|
|
2476
|
-
|| step.action.action === 'hover'
|
|
2477
|
-
|| step.action.action === 'runScript'
|
|
2478
|
-
|| step.action.action === 'extractWithIndex'
|
|
2479
|
-
|| step.action.action === 'evaluate'
|
|
2480
|
-
|| step.action.action === 'verifyPreview'
|
|
2481
|
-
|| step.action.action === 'fill'
|
|
2482
|
-
|| step.action.action === 'clickSequence';
|
|
2483
|
-
// Settle wait for mutating actions, mirroring verifyEffect's logic
|
|
2484
|
-
const needsSettleWait = step.action.action === 'click'
|
|
2485
|
-
|| step.action.action === 'navigate'
|
|
2486
|
-
|| step.action.action === 'press'
|
|
2487
|
-
|| step.action.action === 'select'
|
|
2488
|
-
|| step.action.action === 'fill'
|
|
2489
|
-
|| step.action.action === 'clickSequence';
|
|
2490
|
-
const observePromise = this.driver.observe().catch(() => preStepState);
|
|
2491
|
-
if (needsSettleWait) {
|
|
2492
|
-
await Promise.all([
|
|
2493
|
-
observePromise,
|
|
2494
|
-
new Promise((r) => setTimeout(r, 50)),
|
|
2495
|
-
]);
|
|
2496
|
-
}
|
|
2497
|
-
const postStepState = await observePromise;
|
|
2498
|
-
this.cachedPostState = postStepState;
|
|
2499
|
-
lastState = postStepState;
|
|
2500
|
-
// Plan verification is more permissive than per-action verification:
|
|
2501
|
-
// a step passes if (a) it's a non-mutating action, OR (b) the strict
|
|
2502
|
-
// verifier passes, OR (c) the snapshot/url changed in any meaningful
|
|
2503
|
-
// way (the action did SOMETHING). Strict failure-on-no-change is
|
|
2504
|
-
// appropriate for the per-action loop where the agent can recover,
|
|
2505
|
-
// but plan execution needs to push forward unless there's positive
|
|
2506
|
-
// evidence of failure.
|
|
2507
|
-
let verifyResult;
|
|
2508
|
-
if (isAutoPass) {
|
|
2509
|
-
verifyResult = { verified: true };
|
|
2510
|
-
}
|
|
2511
|
-
else {
|
|
2512
|
-
const strictResult = verifyExpectedEffect({
|
|
2513
|
-
expectedEffect: step.expectedEffect,
|
|
2514
|
-
preActionState: preStepState,
|
|
2515
|
-
postActionState: postStepState,
|
|
2516
|
-
});
|
|
2517
|
-
if (strictResult.verified) {
|
|
2518
|
-
verifyResult = strictResult;
|
|
2519
|
-
}
|
|
2520
|
-
else {
|
|
2521
|
-
// Permissive fallback: did the page change at all?
|
|
2522
|
-
const stateChanged = preStepState.url !== postStepState.url
|
|
2523
|
-
|| preStepState.title !== postStepState.title
|
|
2524
|
-
|| preStepState.snapshot !== postStepState.snapshot;
|
|
2525
|
-
if (stateChanged) {
|
|
2526
|
-
verifyResult = { verified: true };
|
|
2527
|
-
}
|
|
2528
|
-
else {
|
|
2529
|
-
verifyResult = strictResult;
|
|
2530
|
-
}
|
|
2531
|
-
}
|
|
2532
|
-
}
|
|
2533
|
-
turn.verified = verifyResult.verified;
|
|
2534
|
-
if (!verifyResult.verified) {
|
|
2535
|
-
turn.verificationFailure = verifyResult.reason;
|
|
2536
|
-
}
|
|
2537
|
-
turn.durationMs = Date.now() - stepStartedAt;
|
|
2538
|
-
turns.push(turn);
|
|
2539
|
-
this.onTurn?.(turn);
|
|
2540
|
-
this.bus.emitNow({
|
|
2541
|
-
type: 'verify-completed',
|
|
2542
|
-
runId,
|
|
2543
|
-
turn: turnNumber,
|
|
2544
|
-
verified: verifyResult.verified,
|
|
2545
|
-
...(verifyResult.reason ? { reason: verifyResult.reason } : {}),
|
|
2546
|
-
durationMs: Date.now() - verifyStartedAt,
|
|
2547
|
-
});
|
|
2548
|
-
this.bus.emitNow({
|
|
2549
|
-
type: 'plan-step-executed',
|
|
2550
|
-
runId,
|
|
2551
|
-
turn: turnNumber,
|
|
2552
|
-
stepIndex: stepIdx + 1,
|
|
2553
|
-
totalSteps: plan.steps.length,
|
|
2554
|
-
action: step.action,
|
|
2555
|
-
executeSuccess: true,
|
|
2556
|
-
verified: verifyResult.verified,
|
|
2557
|
-
durationMs: Date.now() - stepStartedAt,
|
|
2558
|
-
...(verifyResult.reason ? { verifyReason: verifyResult.reason } : {}),
|
|
2559
|
-
});
|
|
2560
|
-
if (!verifyResult.verified) {
|
|
2561
|
-
this.bus.emitNow({
|
|
2562
|
-
type: 'plan-deviated',
|
|
2563
|
-
runId,
|
|
2564
|
-
turn: turnNumber,
|
|
2565
|
-
stepIndex: stepIdx + 1,
|
|
2566
|
-
totalSteps: plan.steps.length,
|
|
2567
|
-
reason: `verification failed at step ${stepIdx + 1}: ${verifyResult.reason ?? 'expected effect not observed'}`,
|
|
2568
|
-
});
|
|
2569
|
-
return {
|
|
2570
|
-
kind: 'deviated',
|
|
2571
|
-
lastState,
|
|
2572
|
-
failedStepIndex: stepIdx,
|
|
2573
|
-
reason: `verification failed at step ${stepIdx + 1}: ${verifyResult.reason ?? 'expected effect not observed'}`,
|
|
2574
|
-
turnsConsumed: stepIdx + 1,
|
|
2575
|
-
};
|
|
2576
|
-
}
|
|
2577
|
-
}
|
|
2578
|
-
// Auto-complete when the plan ends with meaningful runScript output.
|
|
2579
|
-
const lastStep = plan.steps[plan.steps.length - 1];
|
|
2580
|
-
if (lastStep
|
|
2581
|
-
&& lastStep.action.action === 'runScript'
|
|
2582
|
-
&& isMeaningfulRunScriptOutput(lastRunScriptOutput)) {
|
|
2583
|
-
const synthTurnNumber = currentTurnIndex + 1;
|
|
2584
|
-
const synthTurn = {
|
|
2585
|
-
turn: synthTurnNumber,
|
|
2586
|
-
state: lastState,
|
|
2587
|
-
action: { action: 'complete', result: lastRunScriptOutput },
|
|
2588
|
-
reasoning: 'Auto-complete: plan ended after runScript, runner emitted complete with the runScript output',
|
|
2589
|
-
durationMs: 0,
|
|
2590
|
-
};
|
|
2591
|
-
turns.push(synthTurn);
|
|
2592
|
-
this.onTurn?.(synthTurn);
|
|
2593
|
-
this.bus.emitNow({
|
|
2594
|
-
type: 'plan-step-executed',
|
|
2595
|
-
runId,
|
|
2596
|
-
turn: synthTurnNumber,
|
|
2597
|
-
stepIndex: plan.steps.length + 1,
|
|
2598
|
-
totalSteps: plan.steps.length + 1,
|
|
2599
|
-
action: synthTurn.action,
|
|
2600
|
-
executeSuccess: true,
|
|
2601
|
-
verified: true,
|
|
2602
|
-
durationMs: 0,
|
|
2603
|
-
});
|
|
2604
|
-
if (this.config.debug) {
|
|
2605
|
-
console.log(`[Runner] Auto-emitted complete with runScript output (${lastRunScriptOutput.length} chars) after plan exhausted`);
|
|
2606
|
-
}
|
|
2607
|
-
return {
|
|
2608
|
-
kind: 'completed',
|
|
2609
|
-
lastState,
|
|
2610
|
-
finalResult: lastRunScriptOutput,
|
|
2611
|
-
turnsConsumed: plan.steps.length + 1,
|
|
2612
|
-
};
|
|
2613
|
-
}
|
|
2614
|
-
// If the plan produced extractWithIndex matches, fall through with the
|
|
2615
|
-
// match list so the LLM can choose the correct index.
|
|
2616
|
-
if (lastExtractOutput) {
|
|
2617
|
-
return {
|
|
2618
|
-
kind: 'deviated',
|
|
2619
|
-
lastState,
|
|
2620
|
-
failedStepIndex: plan.steps.length,
|
|
2621
|
-
reason: `plan completed extractWithIndex but the LLM must read the matches and pick by index. Match list:\n${lastExtractOutput.slice(0, 4000)}\n\nPick the index whose text matches the goal, then emit complete with result: <picked text>`,
|
|
2622
|
-
turnsConsumed: plan.steps.length,
|
|
2623
|
-
};
|
|
2624
|
-
}
|
|
2625
|
-
// If the plan ends with runScript but output is empty or placeholder-like,
|
|
2626
|
-
// fall through to per-action mode instead of completing with bad data.
|
|
2627
|
-
if (lastStep
|
|
2628
|
-
&& lastStep.action.action === 'runScript'
|
|
2629
|
-
&& !isMeaningfulRunScriptOutput(lastRunScriptOutput)) {
|
|
2630
|
-
if (this.config.debug) {
|
|
2631
|
-
console.log(`[Runner] runScript returned no meaningful output (${JSON.stringify(lastRunScriptOutput).slice(0, 100)}); falling through to per-action loop for two-pass extraction`);
|
|
2632
|
-
}
|
|
2633
|
-
return {
|
|
2634
|
-
kind: 'deviated',
|
|
2635
|
-
lastState,
|
|
2636
|
-
failedStepIndex: plan.steps.length - 1,
|
|
2637
|
-
reason: `runScript returned no meaningful output (got: ${JSON.stringify(lastRunScriptOutput).slice(0, 200)}). The first-pass extraction failed — re-observe the page and try extractWithIndex with a wide query (e.g. 'p, span, dd, code') and a contains filter naming the expected text fragment. Pick-by-content beats pick-by-selector when the planner couldn't see the data at plan time.`,
|
|
2638
|
-
turnsConsumed: plan.steps.length,
|
|
2639
|
-
};
|
|
2640
|
-
}
|
|
2641
|
-
// All steps verified BUT the plan ended without an explicit complete/abort.
|
|
2642
|
-
// This means the planner emitted a finite sequence of "work" steps and
|
|
2643
|
-
// didn't terminate. The right behavior is NOT to fabricate a complete —
|
|
2644
|
-
// we treat plan exhaustion as a deviation that triggers fallback to the
|
|
2645
|
-
// per-action loop. The per-action loop will continue from the current
|
|
2646
|
-
// state and emit `complete` when the goal is genuinely met.
|
|
2647
|
-
return {
|
|
2648
|
-
kind: 'deviated',
|
|
2649
|
-
lastState,
|
|
2650
|
-
failedStepIndex: plan.steps.length,
|
|
2651
|
-
reason: 'plan exhausted without an explicit complete or abort step — falling through to per-action loop to finish the task',
|
|
2652
|
-
turnsConsumed: plan.steps.length,
|
|
2653
|
-
};
|
|
2177
|
+
async executePlan(plan, scenario, runId, turns, runState, startingTurnIndex, planCallTokens) {
|
|
2178
|
+
return executePlanImpl(this, plan, scenario, runId, turns, runState, startingTurnIndex, planCallTokens);
|
|
2654
2179
|
}
|
|
2655
2180
|
async verifyEffect(expectedEffect, preActionState, actionType) {
|
|
2656
|
-
|
|
2657
|
-
// clicks that may trigger XHR/route transitions, form submits). For
|
|
2658
|
-
// pure reads, scrolls, hovers, and waits the page state is already
|
|
2659
|
-
// settled by the time execute returns. The previous unconditional
|
|
2660
|
-
// 100ms wait was pure dead time on every turn.
|
|
2661
|
-
const needsSettleWait = actionType === 'click'
|
|
2662
|
-
|| actionType === 'navigate'
|
|
2663
|
-
|| actionType === 'press'
|
|
2664
|
-
|| actionType === 'select';
|
|
2665
|
-
// Kick observe off immediately and let the settle wait race against it.
|
|
2666
|
-
// observe() polls waitForLoadState internally, so the 50ms settle is
|
|
2667
|
-
// really only there to let click handlers schedule their first XHR; we
|
|
2668
|
-
// don't need to *block* on it before starting observe.
|
|
2669
|
-
const observePromise = this.driver.observe().catch(() => preActionState);
|
|
2670
|
-
if (needsSettleWait) {
|
|
2671
|
-
await Promise.all([
|
|
2672
|
-
observePromise,
|
|
2673
|
-
new Promise(r => setTimeout(r, 50)),
|
|
2674
|
-
]);
|
|
2675
|
-
}
|
|
2676
|
-
const postState = await observePromise;
|
|
2677
|
-
this.cachedPostState = postState;
|
|
2678
|
-
return verifyExpectedEffect({
|
|
2679
|
-
expectedEffect,
|
|
2680
|
-
preActionState,
|
|
2681
|
-
postActionState: postState,
|
|
2682
|
-
});
|
|
2181
|
+
return verifyEffectImpl(this, expectedEffect, preActionState, actionType);
|
|
2683
2182
|
}
|
|
2684
2183
|
async buildSearchResultsScoutFeedback(state, goal, allowedDomains, seenUrls) {
|
|
2685
|
-
|
|
2686
|
-
return '';
|
|
2687
|
-
if (seenUrls.has(state.url))
|
|
2688
|
-
return '';
|
|
2689
|
-
const page = this.driver.getPage?.();
|
|
2690
|
-
if (!page)
|
|
2691
|
-
return '';
|
|
2692
|
-
try {
|
|
2693
|
-
const candidates = await page.evaluate(() => {
|
|
2694
|
-
const items = [];
|
|
2695
|
-
const seen = new Set();
|
|
2696
|
-
const selectors = [
|
|
2697
|
-
'main a[href]',
|
|
2698
|
-
'[role="main"] a[href]',
|
|
2699
|
-
'article a[href]',
|
|
2700
|
-
'.search-results a[href]',
|
|
2701
|
-
'#search-results a[href]',
|
|
2702
|
-
'.results a[href]',
|
|
2703
|
-
'ol a[href]',
|
|
2704
|
-
];
|
|
2705
|
-
const fallbackLinks = Array.from(document.querySelectorAll('a[href]'));
|
|
2706
|
-
const links = selectors
|
|
2707
|
-
.flatMap((selector) => Array.from(document.querySelectorAll(selector)))
|
|
2708
|
-
.concat(fallbackLinks);
|
|
2709
|
-
const noisePattern = /\b(next|previous|page \d+|show more|show fewer|filter|sort|home|contact|privacy|accessibility|search)\b/i;
|
|
2710
|
-
for (const node of links) {
|
|
2711
|
-
const anchor = node;
|
|
2712
|
-
const href = anchor.href?.trim();
|
|
2713
|
-
const title = (anchor.innerText || anchor.textContent || '').replace(/\s+/g, ' ').trim();
|
|
2714
|
-
if (!href || !title || title.length < 12 || title.length > 220)
|
|
2715
|
-
continue;
|
|
2716
|
-
if (href.startsWith('javascript:') || href.startsWith('mailto:') || href.includes('#'))
|
|
2717
|
-
continue;
|
|
2718
|
-
if (href.includes('results.aspx') && !/open government|dataset|data|news release|press release/i.test(title))
|
|
2719
|
-
continue;
|
|
2720
|
-
if (noisePattern.test(title) && !/open government|dataset|data|news release|press release/i.test(title))
|
|
2721
|
-
continue;
|
|
2722
|
-
const key = `${title}::${href}`;
|
|
2723
|
-
if (seen.has(key))
|
|
2724
|
-
continue;
|
|
2725
|
-
seen.add(key);
|
|
2726
|
-
items.push({ title, href });
|
|
2727
|
-
if (items.length >= 24)
|
|
2728
|
-
break;
|
|
2729
|
-
}
|
|
2730
|
-
return items;
|
|
2731
|
-
});
|
|
2732
|
-
if (!Array.isArray(candidates) || candidates.length === 0)
|
|
2733
|
-
return '';
|
|
2734
|
-
const ranked = rankSearchCandidates(goal, candidates, allowedDomains);
|
|
2735
|
-
const recommendation = ranked[0];
|
|
2736
|
-
seenUrls.add(state.url);
|
|
2737
|
-
return [
|
|
2738
|
-
'SEARCH RESULTS CANDIDATES:',
|
|
2739
|
-
...ranked.map((candidate, index) => `${index + 1}. ${candidate.title} — ${candidate.href} (score ${candidate.score})`),
|
|
2740
|
-
recommendation
|
|
2741
|
-
? `BEST MATCH RECOMMENDATION: prefer "${recommendation.title}" because it best matches the requested entity, content type, and host constraints.`
|
|
2742
|
-
: '',
|
|
2743
|
-
].join('\n');
|
|
2744
|
-
}
|
|
2745
|
-
catch {
|
|
2746
|
-
return '';
|
|
2747
|
-
}
|
|
2184
|
+
return buildSearchResultsScoutFeedbackImpl(this, state, goal, allowedDomains, seenUrls);
|
|
2748
2185
|
}
|
|
2749
2186
|
async buildVisibleLinkScoutRecommendation(state, goal, allowedDomains) {
|
|
2750
|
-
|
|
2751
|
-
if (!scoutConfig?.enabled)
|
|
2752
|
-
return undefined;
|
|
2753
|
-
if (!shouldUseVisibleLinkScoutPage(state, goal, allowedDomains))
|
|
2754
|
-
return undefined;
|
|
2755
|
-
const ranked = getRankedVisibleLinkCandidates(state, goal, allowedDomains);
|
|
2756
|
-
const maxCandidates = Math.max(2, Math.min(scoutConfig.maxCandidates ?? 3, 5));
|
|
2757
|
-
const candidates = await this.filterScoutCandidatesByAllowedDomains(ranked.slice(0, maxCandidates), allowedDomains);
|
|
2758
|
-
if (!shouldUseVisibleLinkScout(candidates, scoutConfig))
|
|
2759
|
-
return undefined;
|
|
2760
|
-
const scoutState = scoutConfig.useVision
|
|
2761
|
-
? await this.attachDecisionScreenshot(state)
|
|
2762
|
-
: state;
|
|
2763
|
-
const extraContext = allowedDomains && allowedDomains.length > 0
|
|
2764
|
-
? `Host constraint: prefer only ${allowedDomains.join(', ')}.`
|
|
2765
|
-
: undefined;
|
|
2766
|
-
const recommendation = await this.brain.recommendLinkCandidate(goal, scoutState, candidates, extraContext);
|
|
2767
|
-
const matched = candidates.find((candidate) => candidate.ref === recommendation.selector);
|
|
2768
|
-
if (!matched)
|
|
2769
|
-
return undefined;
|
|
2770
|
-
return {
|
|
2771
|
-
ref: matched.ref,
|
|
2772
|
-
text: matched.text,
|
|
2773
|
-
confidence: recommendation.confidence,
|
|
2774
|
-
reasoning: recommendation.reasoning,
|
|
2775
|
-
};
|
|
2187
|
+
return buildVisibleLinkScoutRecommendationImpl(this, state, goal, allowedDomains);
|
|
2776
2188
|
}
|
|
2777
2189
|
async buildBranchLinkRecommendation(state, goal, allowedDomains) {
|
|
2778
|
-
|
|
2779
|
-
if (!scoutConfig?.enabled || !scoutConfig.readOnlyTop2Challenger)
|
|
2780
|
-
return undefined;
|
|
2781
|
-
if (!shouldUseVisibleLinkScoutPage(state, goal, allowedDomains))
|
|
2782
|
-
return undefined;
|
|
2783
|
-
const ranked = getRankedVisibleLinkCandidates(state, goal, allowedDomains);
|
|
2784
|
-
const candidates = ranked.slice(0, 2);
|
|
2785
|
-
if (!shouldUseBoundedBranchExplorer(candidates, scoutConfig))
|
|
2786
|
-
return undefined;
|
|
2787
|
-
if (!this.driver.inspectSelectorHref || !this.driver.getPage)
|
|
2788
|
-
return undefined;
|
|
2789
|
-
const page = this.driver.getPage();
|
|
2790
|
-
if (!page)
|
|
2791
|
-
return undefined;
|
|
2792
|
-
const settled = await Promise.all(candidates.map(async (candidate) => {
|
|
2793
|
-
const href = await this.driver.inspectSelectorHref(candidate.ref).catch(() => undefined);
|
|
2794
|
-
if (!href)
|
|
2795
|
-
return undefined;
|
|
2796
|
-
const preview = await inspectBranchPreview(page, href, 8000);
|
|
2797
|
-
if (!preview)
|
|
2798
|
-
return undefined;
|
|
2799
|
-
return { ref: candidate.ref, text: candidate.text, href, score: scoreBranchPreview(goal, preview, allowedDomains), preview };
|
|
2800
|
-
}));
|
|
2801
|
-
const previews = settled.filter((r) => r !== undefined);
|
|
2802
|
-
if (previews.length < 2)
|
|
2803
|
-
return undefined;
|
|
2804
|
-
previews.sort((a, b) => b.score - a.score);
|
|
2805
|
-
const [top, second] = previews;
|
|
2806
|
-
if (top.score <= second.score)
|
|
2807
|
-
return undefined;
|
|
2808
|
-
if (top.score < 4)
|
|
2809
|
-
return undefined;
|
|
2810
|
-
return {
|
|
2811
|
-
ref: top.ref,
|
|
2812
|
-
text: top.text,
|
|
2813
|
-
confidence: Math.min(0.95, 0.7 + Math.max(0, top.score - second.score) / 20),
|
|
2814
|
-
reasoning: `Branch preview favored ${top.href} (${top.preview.title || top.preview.finalUrl}) over ${second.href} based on content-type and goal-match signals.`,
|
|
2815
|
-
};
|
|
2190
|
+
return buildBranchLinkRecommendationImpl(this, state, goal, allowedDomains);
|
|
2816
2191
|
}
|
|
2817
2192
|
async filterScoutCandidatesByAllowedDomains(candidates, allowedDomains) {
|
|
2818
|
-
|
|
2819
|
-
return candidates;
|
|
2820
|
-
}
|
|
2821
|
-
const allowedHosts = new Set(allowedDomains.map((domain) => domain.toLowerCase()));
|
|
2822
|
-
const resolved = await Promise.all(candidates.map(async (candidate) => {
|
|
2823
|
-
const href = await this.driver.inspectSelectorHref(candidate.ref).catch(() => undefined);
|
|
2824
|
-
const host = href ? safeHostname(href) : undefined;
|
|
2825
|
-
return { candidate, host };
|
|
2826
|
-
}));
|
|
2827
|
-
return resolved
|
|
2828
|
-
.filter(({ host }) => !host || allowedHosts.has(host))
|
|
2829
|
-
.map(({ candidate }) => candidate);
|
|
2193
|
+
return filterScoutCandidatesByAllowedDomainsImpl(this, candidates, allowedDomains);
|
|
2830
2194
|
}
|
|
2831
2195
|
async inspectDisallowedSearchClick(state, scenario, action) {
|
|
2832
|
-
|
|
2833
|
-
return undefined;
|
|
2834
|
-
if (!scenario.allowedDomains || scenario.allowedDomains.length === 0)
|
|
2835
|
-
return undefined;
|
|
2836
|
-
if (!buildSearchResultsGuidance(state, scenario.goal, scenario.allowedDomains))
|
|
2837
|
-
return undefined;
|
|
2838
|
-
if (!this.driver.inspectSelectorHref)
|
|
2839
|
-
return undefined;
|
|
2840
|
-
const href = await this.driver.inspectSelectorHref(action.selector);
|
|
2841
|
-
const host = href ? safeHostname(href) : undefined;
|
|
2842
|
-
if (!href || !host)
|
|
2843
|
-
return undefined;
|
|
2844
|
-
const allowedLower = scenario.allowedDomains.map((domain) => domain.toLowerCase());
|
|
2845
|
-
if (allowedLower.includes(host))
|
|
2846
|
-
return undefined;
|
|
2847
|
-
// First-party subdomain tolerance
|
|
2848
|
-
const toRoot = (h) => {
|
|
2849
|
-
const parts = h.split('.').filter(Boolean);
|
|
2850
|
-
return parts.length <= 2 ? h : parts.slice(-2).join('.');
|
|
2851
|
-
};
|
|
2852
|
-
if (allowedLower.some((h) => toRoot(h) === toRoot(host)))
|
|
2853
|
-
return undefined;
|
|
2854
|
-
return [
|
|
2855
|
-
`Blocked action: selector ${action.selector} resolves to ${href}, which is outside the allowed host set: ${scenario.allowedDomains.join(', ')}.`,
|
|
2856
|
-
'Choose a result from an allowed host instead, even if the snippet text looks relevant.',
|
|
2857
|
-
].join(' ');
|
|
2196
|
+
return inspectDisallowedSearchClickImpl(this, state, scenario, action);
|
|
2858
2197
|
}
|
|
2859
2198
|
async enforceAllowedDomainBoundary(preActionState, scenario) {
|
|
2860
|
-
|
|
2861
|
-
return undefined;
|
|
2862
|
-
const currentUrl = this.driver.getUrl?.() ?? preActionState.url;
|
|
2863
|
-
const currentHost = safeHostname(currentUrl);
|
|
2864
|
-
if (!currentHost)
|
|
2865
|
-
return undefined;
|
|
2866
|
-
const allowedHosts = scenario.allowedDomains.map((domain) => domain.toLowerCase());
|
|
2867
|
-
if (allowedHosts.includes(currentHost))
|
|
2868
|
-
return undefined;
|
|
2869
|
-
// First-party subdomain tolerance: allow navigation within the same registrable domain
|
|
2870
|
-
const toRoot = (h) => {
|
|
2871
|
-
const parts = h.split('.').filter(Boolean);
|
|
2872
|
-
return parts.length <= 2 ? h : parts.slice(-2).join('.');
|
|
2873
|
-
};
|
|
2874
|
-
const currentRoot = toRoot(currentHost);
|
|
2875
|
-
if (allowedHosts.some((h) => toRoot(h) === currentRoot))
|
|
2876
|
-
return undefined;
|
|
2877
|
-
const previousHost = safeHostname(preActionState.url);
|
|
2878
|
-
if (previousHost && allowedHosts.includes(previousHost)) {
|
|
2879
|
-
await this.driver.execute({ action: 'navigate', url: preActionState.url }).catch(() => { });
|
|
2880
|
-
}
|
|
2881
|
-
else if (scenario.startUrl) {
|
|
2882
|
-
await this.driver.execute({ action: 'navigate', url: scenario.startUrl }).catch(() => { });
|
|
2883
|
-
}
|
|
2884
|
-
return [
|
|
2885
|
-
`Boundary violation: landed on ${currentUrl}, but the allowed host set is ${allowedHosts.join(', ')}.`,
|
|
2886
|
-
'Return to an allowed host and continue from there; do not rely on disallowed subdomains even if their snippet looks relevant.',
|
|
2887
|
-
].join(' ');
|
|
2199
|
+
return enforceAllowedDomainBoundaryImpl(this, preActionState, scenario);
|
|
2888
2200
|
}
|
|
2889
2201
|
async attachDecisionScreenshot(state) {
|
|
2890
|
-
|
|
2891
|
-
return state;
|
|
2892
|
-
try {
|
|
2893
|
-
const screenshot = await this.driver.screenshot();
|
|
2894
|
-
return { ...state, screenshot: screenshot.toString('base64') };
|
|
2895
|
-
}
|
|
2896
|
-
catch {
|
|
2897
|
-
return state;
|
|
2898
|
-
}
|
|
2202
|
+
return attachDecisionScreenshotImpl(this, state);
|
|
2899
2203
|
}
|
|
2900
2204
|
}
|
|
2901
2205
|
/** Convenience function */
|