autokap 1.0.6 → 1.0.8
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/assets/chrome/ios-statusbar-comparison-reference.jpg +0 -0
- package/assets/chrome/ios-statusbar-dark-reference.jpg +0 -0
- package/assets/chrome/ios-statusbar-light-reference.jpg +0 -0
- package/assets/cursors/macos.svg +4 -0
- package/assets/cursors/windows.svg +15 -0
- package/assets/devices/ipad-pro-11-m4.json +52 -0
- package/assets/devices/iphone-16-pro.json +53 -0
- package/assets/devices/macbook-air-13.json +45 -0
- package/assets/frames/MacBook Air 13.svg +242 -0
- package/assets/frames/Status bar - iPhone.png +0 -0
- Menu bar- iPad.png +0 -0
- package/assets/frames/iPad Pro M4 11_.png +0 -0
- package/assets/frames/iPhone 16 Pro.png +0 -0
- package/assets/icons/Cellular Connection.svg +3 -0
- package/assets/icons/Union.svg +6 -0
- package/assets/icons/Wifi.svg +3 -0
- package/assets/icons/battery.svg +5 -0
- package/assets/icons/battery_charging.svg +8 -0
- package/assets/skill/OPCODE-REFERENCE.md +607 -0
- package/assets/skill/README.md +39 -0
- package/assets/skill/SKILL.md +453 -468
- package/assets/skill/STUDIO-SKILL.md +476 -0
- package/assets/skill/references/examples.md +104 -0
- package/assets/skill/references/interactive-demo.md +225 -0
- package/assets/skill/references/mock-data.md +178 -0
- package/dist/abort.d.ts +5 -0
- package/dist/abort.js +44 -0
- package/dist/action-verifier.d.ts +29 -0
- package/dist/action-verifier.js +133 -0
- package/dist/agent-action-recovery.d.ts +45 -0
- package/dist/agent-action-recovery.js +370 -0
- package/dist/agent-message-utils.d.ts +21 -0
- package/dist/agent-message-utils.js +77 -0
- package/dist/agent-url-utils.d.ts +30 -0
- package/dist/agent-url-utils.js +138 -0
- package/dist/agent.d.ts +226 -0
- package/dist/agent.js +6666 -0
- package/dist/ak-tree.d.ts +39 -0
- package/dist/ak-tree.js +368 -0
- package/dist/alt-text.d.ts +26 -0
- package/dist/alt-text.js +55 -0
- package/dist/auth-capture.d.ts +17 -0
- package/dist/auth-capture.js +164 -0
- package/dist/benchmark.d.ts +59 -0
- package/dist/benchmark.js +135 -0
- package/dist/billing-operation-logging.d.ts +38 -0
- package/dist/billing-operation-logging.js +248 -0
- package/dist/browser-bar.d.ts +48 -0
- package/dist/browser-bar.js +284 -0
- package/dist/browser-pool.d.ts +7 -0
- package/dist/browser-pool.js +15 -5
- package/dist/browser-utils.d.ts +31 -0
- package/dist/browser-utils.js +97 -0
- package/dist/browser.d.ts +76 -1
- package/dist/browser.js +1657 -39
- package/dist/capture-alt-text.d.ts +12 -0
- package/dist/capture-alt-text.js +52 -0
- package/dist/capture-encryption.d.ts +10 -0
- package/dist/capture-encryption.js +41 -0
- package/dist/capture-language-preflight.d.ts +41 -0
- package/dist/capture-language-preflight.js +300 -0
- package/dist/capture-llm-page-identity.d.ts +15 -0
- package/dist/capture-llm-page-identity.js +128 -0
- package/dist/capture-model-resolution.d.ts +9 -0
- package/dist/capture-model-resolution.js +21 -0
- package/dist/capture-page-identity.d.ts +7 -0
- package/dist/capture-page-identity.js +352 -0
- package/dist/capture-preset-credentials.d.ts +62 -0
- package/dist/capture-preset-credentials.js +184 -0
- package/dist/capture-request-plan.d.ts +58 -0
- package/dist/capture-request-plan.js +264 -0
- package/dist/capture-run-optimizer.d.ts +139 -0
- package/dist/capture-run-optimizer.js +863 -0
- package/dist/capture-selector-memory.d.ts +31 -0
- package/dist/capture-selector-memory.js +345 -0
- package/dist/capture-session-profile-encryption.d.ts +2 -0
- package/dist/capture-session-profile-encryption.js +22 -0
- package/dist/capture-step-timeout.d.ts +10 -0
- package/dist/capture-step-timeout.js +30 -0
- package/dist/capture-strategy.d.ts +36 -0
- package/dist/capture-strategy.js +95 -0
- package/dist/capture-studio-sync.d.ts +23 -0
- package/dist/capture-studio-sync.js +172 -0
- package/dist/capture-surface-contract.d.ts +36 -0
- package/dist/capture-surface-contract.js +299 -0
- package/dist/capture-transition-engine.d.ts +28 -0
- package/dist/capture-transition-engine.js +292 -0
- package/dist/capture-variant-state.d.ts +56 -0
- package/dist/capture-variant-state.js +182 -0
- package/dist/capture-verification.d.ts +35 -0
- package/dist/capture-verification.js +95 -0
- package/dist/capture-viewport-lock.d.ts +48 -0
- package/dist/capture-viewport-lock.js +74 -0
- package/dist/circuit-breaker.d.ts +42 -0
- package/dist/circuit-breaker.js +119 -0
- package/dist/cli-config.d.ts +8 -1
- package/dist/cli-config.js +62 -6
- package/dist/cli-contract.d.ts +15 -0
- package/dist/cli-contract.js +167 -0
- package/dist/cli-runner-local.d.ts +12 -0
- package/dist/cli-runner-local.js +102 -0
- package/dist/cli-runner.d.ts +34 -0
- package/dist/cli-runner.js +433 -0
- package/dist/cli-utils.d.ts +0 -1
- package/dist/cli-utils.js +2 -5
- package/dist/cli.js +1005 -252
- package/dist/clip-orchestrator.d.ts +148 -0
- package/dist/clip-orchestrator.js +957 -0
- package/dist/clip-postprocess.d.ts +42 -0
- package/dist/clip-postprocess.js +201 -0
- package/dist/cookie-dismiss.d.ts +2 -0
- package/dist/cookie-dismiss.js +48 -13
- package/dist/cost-logging.d.ts +35 -0
- package/dist/cost-logging.js +242 -0
- package/dist/cost-resolution-monitor.d.ts +16 -0
- package/dist/cost-resolution-monitor.js +34 -0
- package/dist/credential-templates.d.ts +5 -0
- package/dist/credential-templates.js +60 -0
- package/dist/cursor-overlay-script.d.ts +6 -0
- package/dist/cursor-overlay-script.js +169 -0
- package/dist/dom-css-purger.d.ts +65 -0
- package/dist/dom-css-purger.js +333 -0
- package/dist/dom-font-inliner.d.ts +45 -0
- package/dist/dom-font-inliner.js +148 -0
- package/dist/dom-patch-resolver.d.ts +52 -0
- package/dist/dom-patch-resolver.js +242 -0
- package/dist/dom-serializer.d.ts +82 -0
- package/dist/dom-serializer.js +378 -0
- package/dist/element-capture.d.ts +13 -0
- package/dist/element-capture.js +522 -0
- package/dist/env-validation.d.ts +5 -0
- package/dist/env-validation.js +29 -0
- package/dist/execution-schema.d.ts +4423 -0
- package/dist/execution-schema.js +507 -0
- package/dist/execution-types.d.ts +886 -0
- package/dist/execution-types.js +65 -0
- package/dist/fonts-loader.d.ts +14 -0
- package/dist/fonts-loader.js +55 -0
- package/dist/hybrid-navigator.d.ts +138 -0
- package/dist/hybrid-navigator.js +468 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +17 -0
- package/dist/legacy/agent-action-recovery.d.ts +45 -0
- package/dist/legacy/agent-action-recovery.js +370 -0
- package/dist/legacy/agent-message-utils.d.ts +21 -0
- package/dist/legacy/agent-message-utils.js +77 -0
- package/dist/legacy/agent-url-utils.d.ts +30 -0
- package/dist/legacy/agent-url-utils.js +138 -0
- package/dist/legacy/agent.d.ts +226 -0
- package/dist/legacy/agent.js +6666 -0
- package/dist/legacy/clip-orchestrator.d.ts +148 -0
- package/dist/legacy/clip-orchestrator.js +957 -0
- package/dist/legacy/credential-templates.d.ts +5 -0
- package/dist/legacy/credential-templates.js +60 -0
- package/dist/legacy/hybrid-navigator.d.ts +138 -0
- package/dist/legacy/hybrid-navigator.js +468 -0
- package/dist/legacy/llm-usage.d.ts +17 -0
- package/dist/legacy/llm-usage.js +45 -0
- package/dist/legacy/prompt-cache.d.ts +10 -0
- package/dist/legacy/prompt-cache.js +24 -0
- package/dist/legacy/prompts.d.ts +175 -0
- package/dist/legacy/prompts.js +1038 -0
- package/dist/legacy/tools.d.ts +4 -0
- package/dist/legacy/tools.js +216 -0
- package/dist/legacy/video-agent.d.ts +143 -0
- package/dist/legacy/video-agent.js +4788 -0
- package/dist/legacy/video-observation.d.ts +36 -0
- package/dist/legacy/video-observation.js +192 -0
- package/dist/legacy/video-planner.d.ts +12 -0
- package/dist/legacy/video-planner.js +501 -0
- package/dist/legacy/video-prompts.d.ts +37 -0
- package/dist/legacy/video-prompts.js +569 -0
- package/dist/legacy/video-tools.d.ts +3 -0
- package/dist/legacy/video-tools.js +59 -0
- package/dist/legacy/video-variant-state.d.ts +29 -0
- package/dist/legacy/video-variant-state.js +80 -0
- package/dist/legacy/vision-model.d.ts +17 -0
- package/dist/legacy/vision-model.js +74 -0
- package/dist/llm-healer.d.ts +63 -0
- package/dist/llm-healer.js +166 -0
- package/dist/llm-provider.d.ts +29 -0
- package/dist/llm-provider.js +80 -0
- package/dist/llm-usage.d.ts +17 -0
- package/dist/llm-usage.js +45 -0
- package/dist/logger.d.ts +6 -2
- package/dist/logger.js +15 -1
- package/dist/mockup-html.d.ts +119 -0
- package/dist/mockup-html.js +263 -0
- package/dist/mockup.d.ts +187 -0
- package/dist/mockup.js +869 -0
- package/dist/mouse-animation.d.ts +46 -0
- package/dist/mouse-animation.js +114 -0
- package/dist/opcode-actions.d.ts +42 -0
- package/dist/opcode-actions.js +511 -0
- package/dist/opcode-runner.d.ts +51 -0
- package/dist/opcode-runner.js +770 -0
- package/dist/openrouter-client.d.ts +40 -0
- package/dist/openrouter-client.js +16 -0
- package/dist/overlay-engine.d.ts +24 -0
- package/dist/overlay-engine.js +176 -0
- package/dist/overlay-utils.d.ts +14 -0
- package/dist/overlay-utils.js +13 -0
- package/dist/postcondition.d.ts +16 -0
- package/dist/postcondition.js +269 -0
- package/dist/posthog.d.ts +4 -0
- package/dist/posthog.js +26 -0
- package/dist/program-patcher.d.ts +25 -0
- package/dist/program-patcher.js +44 -0
- package/dist/prompt-cache.d.ts +10 -0
- package/dist/prompt-cache.js +24 -0
- package/dist/prompts.d.ts +175 -0
- package/dist/prompts.js +1038 -0
- package/dist/provider-config.d.ts +12 -0
- package/dist/provider-config.js +15 -0
- package/dist/recovery-chain.d.ts +37 -0
- package/dist/recovery-chain.js +350 -0
- package/dist/remote-browser.d.ts +215 -0
- package/dist/remote-browser.js +360 -0
- package/dist/safari-browser-bar.d.ts +15 -0
- package/dist/safari-browser-bar.js +95 -0
- package/dist/safari-toolbar-asset.d.ts +15 -0
- package/dist/safari-toolbar-asset.js +12 -0
- package/dist/security.d.ts +21 -0
- package/dist/security.js +608 -0
- package/dist/selector-resolver.d.ts +34 -0
- package/dist/selector-resolver.js +181 -0
- package/dist/semantic-resolver.d.ts +35 -0
- package/dist/semantic-resolver.js +161 -0
- package/dist/server-capture-runtime.d.ts +125 -0
- package/dist/server-capture-runtime.js +585 -0
- package/dist/server-credit-usage.d.ts +12 -0
- package/dist/server-credit-usage.js +41 -0
- package/dist/server-posthog.d.ts +2 -0
- package/dist/server-posthog.js +16 -0
- package/dist/server-project-webhooks.d.ts +59 -0
- package/dist/server-project-webhooks.js +123 -0
- package/dist/server-screenshot-watermark.d.ts +7 -0
- package/dist/server-screenshot-watermark.js +60 -0
- package/dist/session-profile.d.ts +86 -0
- package/dist/session-profile.js +1536 -0
- package/dist/sf-pro-fonts.d.ts +4 -0
- package/dist/sf-pro-fonts.js +7 -0
- package/dist/sf-pro-symbols.d.ts +1 -0
- package/dist/sf-pro-symbols.js +55 -0
- package/dist/skill-packaging.d.ts +28 -0
- package/dist/skill-packaging.js +169 -0
- package/dist/smart-wait.d.ts +27 -0
- package/dist/smart-wait.js +81 -0
- package/dist/status-bar-l10n.d.ts +14 -0
- package/dist/status-bar-l10n.js +177 -0
- package/dist/status-bar-render.d.ts +20 -0
- package/dist/status-bar-render.js +410 -0
- package/dist/status-bar.d.ts +53 -0
- package/dist/status-bar.js +620 -0
- package/dist/svg-browser-bar.d.ts +33 -0
- package/dist/svg-browser-bar.js +206 -0
- package/dist/svg-status-bar.d.ts +36 -0
- package/dist/svg-status-bar.js +597 -0
- package/dist/svg-text.d.ts +61 -0
- package/dist/svg-text.js +118 -0
- package/dist/tools.d.ts +4 -0
- package/dist/tools.js +216 -0
- package/dist/types.d.ts +240 -5
- package/dist/types.js +23 -1
- package/dist/v2/action-verifier.d.ts +29 -0
- package/dist/v2/action-verifier.js +133 -0
- package/dist/v2/alt-text.d.ts +26 -0
- package/dist/v2/alt-text.js +55 -0
- package/dist/v2/benchmark.d.ts +59 -0
- package/dist/v2/benchmark.js +135 -0
- package/dist/v2/capture-strategy.d.ts +30 -0
- package/dist/v2/capture-strategy.js +67 -0
- package/dist/v2/capture-verification.d.ts +35 -0
- package/dist/v2/capture-verification.js +95 -0
- package/dist/v2/circuit-breaker.d.ts +42 -0
- package/dist/v2/circuit-breaker.js +119 -0
- package/dist/v2/cli-runner-local.d.ts +11 -0
- package/dist/v2/cli-runner-local.js +91 -0
- package/dist/v2/cli-runner.d.ts +34 -0
- package/dist/v2/cli-runner.js +300 -0
- package/dist/v2/compiler-prompts.d.ts +27 -0
- package/dist/v2/compiler-prompts.js +123 -0
- package/dist/v2/compiler.d.ts +37 -0
- package/dist/v2/compiler.js +147 -0
- package/dist/v2/explorer.d.ts +41 -0
- package/dist/v2/explorer.js +56 -0
- package/dist/v2/index.d.ts +37 -0
- package/dist/v2/index.js +31 -0
- package/dist/v2/llm-healer.d.ts +62 -0
- package/dist/v2/llm-healer.js +166 -0
- package/dist/v2/llm-provider.d.ts +29 -0
- package/dist/v2/llm-provider.js +80 -0
- package/dist/v2/opcode-runner.d.ts +47 -0
- package/dist/v2/opcode-runner.js +634 -0
- package/dist/v2/overlay-engine.d.ts +24 -0
- package/dist/v2/overlay-engine.js +150 -0
- package/dist/v2/postcondition.d.ts +16 -0
- package/dist/v2/postcondition.js +249 -0
- package/dist/v2/program-patcher.d.ts +25 -0
- package/dist/v2/program-patcher.js +44 -0
- package/dist/v2/recovery-chain.d.ts +30 -0
- package/dist/v2/recovery-chain.js +368 -0
- package/dist/v2/schema.d.ts +2580 -0
- package/dist/v2/schema.js +295 -0
- package/dist/v2/selector-resolver.d.ts +34 -0
- package/dist/v2/selector-resolver.js +181 -0
- package/dist/v2/semantic-resolver.d.ts +35 -0
- package/dist/v2/semantic-resolver.js +161 -0
- package/dist/v2/smart-wait.d.ts +27 -0
- package/dist/v2/smart-wait.js +81 -0
- package/dist/v2/types.d.ts +444 -0
- package/dist/v2/types.js +19 -0
- package/dist/v2/web-playwright-local.d.ts +69 -0
- package/dist/v2/web-playwright-local.js +392 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +5 -0
- package/dist/video-agent.d.ts +143 -0
- package/dist/video-agent.js +4788 -0
- package/dist/video-observation.d.ts +36 -0
- package/dist/video-observation.js +192 -0
- package/dist/video-planner.d.ts +12 -0
- package/dist/video-planner.js +501 -0
- package/dist/video-prompts.d.ts +37 -0
- package/dist/video-prompts.js +554 -0
- package/dist/video-tools.d.ts +3 -0
- package/dist/video-tools.js +59 -0
- package/dist/video-variant-state.d.ts +29 -0
- package/dist/video-variant-state.js +80 -0
- package/dist/vision-model.d.ts +17 -0
- package/dist/vision-model.js +74 -0
- package/dist/web-playwright-local.d.ts +126 -0
- package/dist/web-playwright-local.js +819 -0
- package/dist/ws-auth.d.ts +20 -0
- package/dist/ws-auth.js +70 -0
- package/dist/ws-broadcast.d.ts +34 -0
- package/dist/ws-broadcast.js +85 -0
- package/dist/ws-connection-limits.d.ts +12 -0
- package/dist/ws-connection-limits.js +44 -0
- package/dist/ws-handler-utils.d.ts +32 -0
- package/dist/ws-handler-utils.js +139 -0
- package/dist/ws-handler.d.ts +10 -0
- package/dist/ws-handler.js +1793 -0
- package/dist/ws-metrics-server.d.ts +9 -0
- package/dist/ws-metrics-server.js +31 -0
- package/dist/ws-server.d.ts +9 -0
- package/dist/ws-server.js +92 -0
- package/package.json +142 -71
|
@@ -0,0 +1,634 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capture Agent — Opcode Runner
|
|
3
|
+
*
|
|
4
|
+
* Deterministic execution engine for ExecutionProgram.
|
|
5
|
+
* Executes opcodes sequentially, verifies postconditions,
|
|
6
|
+
* delegates to recovery chain on failure, and respects circuit breaker.
|
|
7
|
+
*/
|
|
8
|
+
import { evaluatePostcondition } from './postcondition.js';
|
|
9
|
+
import { ActionVerifier } from './action-verifier.js';
|
|
10
|
+
import { CircuitBreaker } from './circuit-breaker.js';
|
|
11
|
+
import { smartWaitForStability } from './smart-wait.js';
|
|
12
|
+
import { verifyCaptureQuality } from './capture-verification.js';
|
|
13
|
+
import { generateAltText } from './alt-text.js';
|
|
14
|
+
/** No-op recovery chain for Phase 1 (deterministic-only execution) */
|
|
15
|
+
export class NoOpRecoveryChain {
|
|
16
|
+
async attempt() {
|
|
17
|
+
return { recovered: false, reason: 'no recovery chain configured' };
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
// ── Main execution function ─────────────────────────────────────────
|
|
21
|
+
export async function executeProgram(program, createAdapter, options = {}) {
|
|
22
|
+
const recoveryChain = options.recoveryChain ?? new NoOpRecoveryChain();
|
|
23
|
+
const startTime = Date.now();
|
|
24
|
+
const variantResults = [];
|
|
25
|
+
const healerPatches = [];
|
|
26
|
+
const telemetry = {
|
|
27
|
+
llmCallCount: 0,
|
|
28
|
+
llmCostEur: 0,
|
|
29
|
+
llmStepUsages: [],
|
|
30
|
+
totalOpcodes: 0,
|
|
31
|
+
recoveredOpcodes: 0,
|
|
32
|
+
failedOpcodes: 0,
|
|
33
|
+
healerInvocations: 0,
|
|
34
|
+
circuitBreakerTrips: 0,
|
|
35
|
+
};
|
|
36
|
+
for (const variant of program.variants) {
|
|
37
|
+
if (options.abortSignal?.aborted)
|
|
38
|
+
break;
|
|
39
|
+
options.onProgress?.({
|
|
40
|
+
type: 'variant_start',
|
|
41
|
+
variantId: variant.id,
|
|
42
|
+
message: `starting variant ${variant.id}`,
|
|
43
|
+
});
|
|
44
|
+
const variantResult = await executeVariant(program, variant, createAdapter, recoveryChain, telemetry, healerPatches, options);
|
|
45
|
+
variantResults.push(variantResult);
|
|
46
|
+
options.onProgress?.({
|
|
47
|
+
type: 'variant_end',
|
|
48
|
+
variantId: variant.id,
|
|
49
|
+
status: variantResult.success ? 'ok' : 'failed',
|
|
50
|
+
message: variantResult.success
|
|
51
|
+
? `variant ${variant.id} completed`
|
|
52
|
+
: `variant ${variant.id} failed: ${variantResult.error}`,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const aborted = options.abortSignal?.aborted && variantResults.length < program.variants.length;
|
|
56
|
+
const success = !aborted && variantResults.length > 0 && variantResults.every(v => v.success);
|
|
57
|
+
return {
|
|
58
|
+
programId: program.presetId,
|
|
59
|
+
success,
|
|
60
|
+
variantResults,
|
|
61
|
+
telemetry,
|
|
62
|
+
healerPatches: success ? healerPatches : [], // Only propagate patches on success
|
|
63
|
+
totalDurationMs: Date.now() - startTime,
|
|
64
|
+
error: aborted ? 'aborted' : (success ? undefined : variantResults.find(v => !v.success)?.error),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
// ── Variant execution ───────────────────────────────────────────────
|
|
68
|
+
async function executeVariant(program, variant, createAdapter, recoveryChain, telemetry, healerPatches, options) {
|
|
69
|
+
const startTime = Date.now();
|
|
70
|
+
const breaker = new CircuitBreaker();
|
|
71
|
+
const verifier = new ActionVerifier();
|
|
72
|
+
const opcodeResults = [];
|
|
73
|
+
const artifacts = [];
|
|
74
|
+
const executionState = {};
|
|
75
|
+
let adapter = null;
|
|
76
|
+
try {
|
|
77
|
+
adapter = await createAdapter(variant);
|
|
78
|
+
for (let i = 0; i < program.steps.length; i++) {
|
|
79
|
+
if (options.abortSignal?.aborted) {
|
|
80
|
+
return {
|
|
81
|
+
variantId: variant.id,
|
|
82
|
+
success: false,
|
|
83
|
+
opcodeResults,
|
|
84
|
+
durationMs: Date.now() - startTime,
|
|
85
|
+
artifacts,
|
|
86
|
+
error: 'aborted',
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
// Check circuit breaker before starting
|
|
90
|
+
const breakerState = breaker.isTripped();
|
|
91
|
+
if (breakerState.tripped) {
|
|
92
|
+
telemetry.circuitBreakerTrips++;
|
|
93
|
+
options.onProgress?.({
|
|
94
|
+
type: 'breaker_trip',
|
|
95
|
+
variantId: variant.id,
|
|
96
|
+
opcodeIndex: i,
|
|
97
|
+
message: breakerState.reason,
|
|
98
|
+
});
|
|
99
|
+
return {
|
|
100
|
+
variantId: variant.id,
|
|
101
|
+
success: false,
|
|
102
|
+
opcodeResults,
|
|
103
|
+
durationMs: Date.now() - startTime,
|
|
104
|
+
artifacts,
|
|
105
|
+
error: `circuit breaker tripped: ${breakerState.reason}`,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const opcode = program.steps[i];
|
|
109
|
+
options.onProgress?.({
|
|
110
|
+
type: 'opcode_start',
|
|
111
|
+
variantId: variant.id,
|
|
112
|
+
opcodeIndex: i,
|
|
113
|
+
opcodeKind: opcode.kind,
|
|
114
|
+
message: opcode.description,
|
|
115
|
+
});
|
|
116
|
+
const result = await executeOpcode(opcode, i, adapter, verifier, breaker, recoveryChain, telemetry, healerPatches, artifacts, options, variant.id, executionState, program.artifactPlan, variant);
|
|
117
|
+
opcodeResults.push(result);
|
|
118
|
+
telemetry.totalOpcodes++;
|
|
119
|
+
if (result.status === 'recovered')
|
|
120
|
+
telemetry.recoveredOpcodes++;
|
|
121
|
+
if (result.status === 'failed')
|
|
122
|
+
telemetry.failedOpcodes++;
|
|
123
|
+
options.onProgress?.({
|
|
124
|
+
type: 'opcode_end',
|
|
125
|
+
variantId: variant.id,
|
|
126
|
+
opcodeIndex: i,
|
|
127
|
+
opcodeKind: opcode.kind,
|
|
128
|
+
status: result.status,
|
|
129
|
+
message: result.error ?? `${opcode.kind} ${result.status}`,
|
|
130
|
+
});
|
|
131
|
+
// Abort variant on fatal opcode failure
|
|
132
|
+
if (result.status === 'failed') {
|
|
133
|
+
return {
|
|
134
|
+
variantId: variant.id,
|
|
135
|
+
success: false,
|
|
136
|
+
opcodeResults,
|
|
137
|
+
durationMs: Date.now() - startTime,
|
|
138
|
+
artifacts,
|
|
139
|
+
error: `opcode ${i} (${opcode.kind}) failed: ${result.error}`,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
variantId: variant.id,
|
|
145
|
+
success: true,
|
|
146
|
+
opcodeResults,
|
|
147
|
+
durationMs: Date.now() - startTime,
|
|
148
|
+
artifacts,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
return {
|
|
153
|
+
variantId: variant.id,
|
|
154
|
+
success: false,
|
|
155
|
+
opcodeResults,
|
|
156
|
+
durationMs: Date.now() - startTime,
|
|
157
|
+
artifacts,
|
|
158
|
+
error: `unexpected error: ${err instanceof Error ? err.message : String(err)}`,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
finally {
|
|
162
|
+
if (adapter) {
|
|
163
|
+
try {
|
|
164
|
+
await adapter.close();
|
|
165
|
+
}
|
|
166
|
+
catch { /* ignore close errors */ }
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// ── Single opcode execution ─────────────────────────────────────────
|
|
171
|
+
async function executeOpcode(opcode, index, adapter, verifier, breaker, recoveryChain, telemetry, healerPatches, artifacts, options, variantId, executionState, artifactPlan, currentVariant) {
|
|
172
|
+
const startTime = Date.now();
|
|
173
|
+
const deadlineMs = startTime + opcode.timeoutMs;
|
|
174
|
+
const isInteraction = ['CLICK', 'TYPE', 'PRESS_KEY', 'SCROLL'].includes(opcode.kind);
|
|
175
|
+
// Track page context for circuit breaker
|
|
176
|
+
try {
|
|
177
|
+
const url = await adapter.getCurrentUrl();
|
|
178
|
+
breaker.setPage(url);
|
|
179
|
+
}
|
|
180
|
+
catch { /* ignore */ }
|
|
181
|
+
// Execute with timeout
|
|
182
|
+
try {
|
|
183
|
+
if (isInteraction) {
|
|
184
|
+
await verifier.captureBeforeState(adapter);
|
|
185
|
+
}
|
|
186
|
+
const actionBudgetMs = getRemainingTimeMs(deadlineMs);
|
|
187
|
+
if (actionBudgetMs <= 0) {
|
|
188
|
+
return handleFailure(opcode, index, adapter, breaker, recoveryChain, telemetry, healerPatches, options, variantId, startTime, deadlineMs, `timeout after ${opcode.timeoutMs}ms`);
|
|
189
|
+
}
|
|
190
|
+
const result = await withTimeout(() => executeOpcodeAction(opcode, index, adapter, artifacts, telemetry, currentVariant, executionState, artifactPlan, options), actionBudgetMs);
|
|
191
|
+
if (!result.success) {
|
|
192
|
+
return handleFailure(opcode, index, adapter, breaker, recoveryChain, telemetry, healerPatches, options, variantId, startTime, deadlineMs, result.error ?? 'action failed');
|
|
193
|
+
}
|
|
194
|
+
// Verify postcondition
|
|
195
|
+
const postconditionBudgetMs = getRemainingTimeMs(deadlineMs);
|
|
196
|
+
if (postconditionBudgetMs <= 0) {
|
|
197
|
+
return handleFailure(opcode, index, adapter, breaker, recoveryChain, telemetry, healerPatches, options, variantId, startTime, deadlineMs, `timeout after ${opcode.timeoutMs}ms`);
|
|
198
|
+
}
|
|
199
|
+
const postcondition = await evaluatePostcondition(adapter, withClampedPostconditionTimeout(opcode.postcondition, postconditionBudgetMs));
|
|
200
|
+
if (!postcondition.passed) {
|
|
201
|
+
return handleFailure(opcode, index, adapter, breaker, recoveryChain, telemetry, healerPatches, options, variantId, startTime, deadlineMs, `postcondition failed: ${postcondition.reason}`);
|
|
202
|
+
}
|
|
203
|
+
// Verify action had effect (for interaction opcodes)
|
|
204
|
+
if (isInteraction) {
|
|
205
|
+
const verification = await verifier.verifyAfterAction(adapter);
|
|
206
|
+
if (!verification.hadEffect && opcode.postcondition.type !== 'always' && opcode.postcondition.type !== 'any_change') {
|
|
207
|
+
return handleFailure(opcode, index, adapter, breaker, recoveryChain, telemetry, healerPatches, options, variantId, startTime, deadlineMs, `action had no effect: ${verification.summary}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
breaker.recordSuccess(index);
|
|
211
|
+
return {
|
|
212
|
+
opcodeIndex: index,
|
|
213
|
+
kind: opcode.kind,
|
|
214
|
+
status: 'ok',
|
|
215
|
+
durationMs: Date.now() - startTime,
|
|
216
|
+
recoveryAttempts: 0,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
221
|
+
return handleFailure(opcode, index, adapter, breaker, recoveryChain, telemetry, healerPatches, options, variantId, startTime, deadlineMs, errorMsg);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// ── Failure handling with recovery ──────────────────────────────────
|
|
225
|
+
async function handleFailure(opcode, index, adapter, breaker, recoveryChain, telemetry, healerPatches, options, variantId, startTime, deadlineMs, errorMsg) {
|
|
226
|
+
const breakerState = breaker.recordFailure(index, opcode.maxFailures);
|
|
227
|
+
if (breakerState.tripped) {
|
|
228
|
+
telemetry.circuitBreakerTrips++;
|
|
229
|
+
return {
|
|
230
|
+
opcodeIndex: index,
|
|
231
|
+
kind: opcode.kind,
|
|
232
|
+
status: 'failed',
|
|
233
|
+
durationMs: Date.now() - startTime,
|
|
234
|
+
recoveryAttempts: 0,
|
|
235
|
+
error: `${errorMsg} (circuit breaker: ${breakerState.reason})`,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
const remainingTimeMs = getRemainingTimeMs(deadlineMs);
|
|
239
|
+
if (remainingTimeMs <= 0) {
|
|
240
|
+
return {
|
|
241
|
+
opcodeIndex: index,
|
|
242
|
+
kind: opcode.kind,
|
|
243
|
+
status: 'failed',
|
|
244
|
+
durationMs: Date.now() - startTime,
|
|
245
|
+
recoveryAttempts: 0,
|
|
246
|
+
error: `${errorMsg} (timeout after ${opcode.timeoutMs}ms)`,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
// Attempt recovery
|
|
250
|
+
options.onProgress?.({
|
|
251
|
+
type: 'recovery',
|
|
252
|
+
variantId,
|
|
253
|
+
opcodeIndex: index,
|
|
254
|
+
opcodeKind: opcode.kind,
|
|
255
|
+
message: `recovering from: ${errorMsg}`,
|
|
256
|
+
});
|
|
257
|
+
const recovery = await recoveryChain.attempt(opcode, index, adapter, {
|
|
258
|
+
remainingTimeMs,
|
|
259
|
+
maxDeterministicRetries: Math.max(0, opcode.maxFailures - breakerState.opcodeFailures),
|
|
260
|
+
});
|
|
261
|
+
if (recovery.recovered) {
|
|
262
|
+
if (recovery.patch) {
|
|
263
|
+
healerPatches.push(recovery.patch);
|
|
264
|
+
telemetry.healerInvocations++;
|
|
265
|
+
}
|
|
266
|
+
breaker.recordSuccess(index);
|
|
267
|
+
return {
|
|
268
|
+
opcodeIndex: index,
|
|
269
|
+
kind: opcode.kind,
|
|
270
|
+
status: 'recovered',
|
|
271
|
+
durationMs: Date.now() - startTime,
|
|
272
|
+
recoveryAttempts: 1,
|
|
273
|
+
recoveryStrategy: recovery.strategy,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
opcodeIndex: index,
|
|
278
|
+
kind: opcode.kind,
|
|
279
|
+
status: 'failed',
|
|
280
|
+
durationMs: Date.now() - startTime,
|
|
281
|
+
recoveryAttempts: 1,
|
|
282
|
+
error: `${errorMsg} (recovery failed: ${recovery.reason})`,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
// ── Opcode action dispatch ──────────────────────────────────────────
|
|
286
|
+
async function executeOpcodeAction(opcode, opcodeIndex, adapter, artifacts, telemetry, currentVariant, executionState, artifactPlan, runOptions) {
|
|
287
|
+
try {
|
|
288
|
+
void artifactPlan;
|
|
289
|
+
switch (opcode.kind) {
|
|
290
|
+
case 'NAVIGATE':
|
|
291
|
+
await adapter.navigate(opcode.url);
|
|
292
|
+
break;
|
|
293
|
+
case 'DISMISS_OVERLAYS':
|
|
294
|
+
await adapter.dismissOverlays();
|
|
295
|
+
break;
|
|
296
|
+
case 'ASSERT_ROUTE':
|
|
297
|
+
return evaluateImmediateAssertion(await evaluatePostcondition(adapter, {
|
|
298
|
+
type: 'route_matches',
|
|
299
|
+
pattern: opcode.urlPattern,
|
|
300
|
+
waitMs: 1,
|
|
301
|
+
}), 'ASSERT_ROUTE failed');
|
|
302
|
+
case 'ASSERT_SURFACE':
|
|
303
|
+
return evaluateSurfaceAssertion(adapter, opcode.selectors, opcode.matchAll);
|
|
304
|
+
case 'CLICK':
|
|
305
|
+
try {
|
|
306
|
+
await adapter.click(opcode.selector);
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
if (!opcode.target || !adapter.clickByTarget)
|
|
310
|
+
throw error;
|
|
311
|
+
await adapter.clickByTarget({
|
|
312
|
+
selector: opcode.selector,
|
|
313
|
+
target: opcode.target,
|
|
314
|
+
selectorAlternates: opcode.selectorAlternates,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
break;
|
|
318
|
+
case 'TYPE':
|
|
319
|
+
try {
|
|
320
|
+
await adapter.type(opcode.selector, opcode.text, opcode.clearFirst);
|
|
321
|
+
}
|
|
322
|
+
catch (error) {
|
|
323
|
+
if (!opcode.target || !adapter.typeByTarget)
|
|
324
|
+
throw error;
|
|
325
|
+
await adapter.typeByTarget({
|
|
326
|
+
selector: opcode.selector,
|
|
327
|
+
target: opcode.target,
|
|
328
|
+
selectorAlternates: opcode.selectorAlternates,
|
|
329
|
+
}, opcode.text, opcode.clearFirst);
|
|
330
|
+
}
|
|
331
|
+
break;
|
|
332
|
+
case 'PRESS_KEY':
|
|
333
|
+
await adapter.pressKey(opcode.key);
|
|
334
|
+
break;
|
|
335
|
+
case 'WAIT_FOR': {
|
|
336
|
+
// WAIT_FOR supports both selector and semantic target
|
|
337
|
+
const waitSelector = opcode.selector;
|
|
338
|
+
if (waitSelector) {
|
|
339
|
+
const found = await adapter.waitFor({
|
|
340
|
+
selector: waitSelector,
|
|
341
|
+
state: opcode.state,
|
|
342
|
+
timeoutMs: opcode.timeoutMs,
|
|
343
|
+
});
|
|
344
|
+
if (!found) {
|
|
345
|
+
return { success: false, error: `element "${waitSelector}" not found within timeout` };
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
else if (opcode.target && adapter.waitForTarget) {
|
|
349
|
+
const found = await adapter.waitForTarget({ target: opcode.target }, opcode.timeoutMs);
|
|
350
|
+
if (!found) {
|
|
351
|
+
return { success: false, error: `target "${opcode.target.text ?? opcode.target.role ?? 'unknown'}" not found within timeout` };
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
return { success: false, error: 'WAIT_FOR needs a selector or target' };
|
|
356
|
+
}
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
case 'SET_LOCALE':
|
|
360
|
+
return applyLocaleOpcode(opcode, adapter);
|
|
361
|
+
case 'SET_THEME':
|
|
362
|
+
return applyThemeOpcode(opcode, adapter);
|
|
363
|
+
case 'SCROLL':
|
|
364
|
+
if (opcode.targetSelector) {
|
|
365
|
+
await adapter.scrollIntoView(opcode.targetSelector);
|
|
366
|
+
}
|
|
367
|
+
else if (opcode.target && adapter.scrollIntoViewByTarget) {
|
|
368
|
+
await adapter.scrollIntoViewByTarget({ target: opcode.target });
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
await adapter.scroll(opcode.direction, opcode.amount);
|
|
372
|
+
}
|
|
373
|
+
break;
|
|
374
|
+
case 'CAPTURE_SCREENSHOT': {
|
|
375
|
+
await smartWaitForStability(adapter, { maxWaitMs: 5000 });
|
|
376
|
+
const captureUrl = await adapter.getCurrentUrl();
|
|
377
|
+
const takeBuffer = async () => {
|
|
378
|
+
if (opcode.elementSelector && adapter.takeElementScreenshot) {
|
|
379
|
+
return adapter.takeElementScreenshot(opcode.elementSelector);
|
|
380
|
+
}
|
|
381
|
+
if (opcode.elementSelector) {
|
|
382
|
+
throw new Error(`element capture requires adapter support for selector "${opcode.elementSelector}"`);
|
|
383
|
+
}
|
|
384
|
+
return adapter.takeScreenshot();
|
|
385
|
+
};
|
|
386
|
+
let buffer = await takeBuffer();
|
|
387
|
+
if (runOptions?.llmConfig) {
|
|
388
|
+
const verification = await verifyCaptureQuality(buffer, {
|
|
389
|
+
expectedDescription: opcode.description,
|
|
390
|
+
url: captureUrl,
|
|
391
|
+
locale: currentVariant?.locale,
|
|
392
|
+
theme: currentVariant?.theme,
|
|
393
|
+
}, runOptions.llmConfig);
|
|
394
|
+
if (verification.llmResult) {
|
|
395
|
+
telemetry.llmCallCount++;
|
|
396
|
+
telemetry.llmCostEur += verification.llmResult.costEur;
|
|
397
|
+
telemetry.llmStepUsages.push({
|
|
398
|
+
stepType: 'capture_verification',
|
|
399
|
+
generationId: verification.llmResult.generationId,
|
|
400
|
+
model: verification.llmResult.model,
|
|
401
|
+
promptTokens: verification.llmResult.promptTokens,
|
|
402
|
+
completionTokens: verification.llmResult.completionTokens,
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
if (!verification.passed) {
|
|
406
|
+
await smartWaitForStability(adapter, { maxWaitMs: 8000 });
|
|
407
|
+
const retryBuffer = await takeBuffer();
|
|
408
|
+
const retryVerification = await verifyCaptureQuality(retryBuffer, {
|
|
409
|
+
expectedDescription: opcode.description,
|
|
410
|
+
url: captureUrl,
|
|
411
|
+
locale: currentVariant?.locale,
|
|
412
|
+
theme: currentVariant?.theme,
|
|
413
|
+
}, runOptions.llmConfig);
|
|
414
|
+
if (retryVerification.llmResult) {
|
|
415
|
+
telemetry.llmCallCount++;
|
|
416
|
+
telemetry.llmCostEur += retryVerification.llmResult.costEur;
|
|
417
|
+
telemetry.llmStepUsages.push({
|
|
418
|
+
stepType: 'capture_verification',
|
|
419
|
+
generationId: retryVerification.llmResult.generationId,
|
|
420
|
+
model: retryVerification.llmResult.model,
|
|
421
|
+
promptTokens: retryVerification.llmResult.promptTokens,
|
|
422
|
+
completionTokens: retryVerification.llmResult.completionTokens,
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
if (retryVerification.passed) {
|
|
426
|
+
buffer = retryBuffer;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
let altText;
|
|
431
|
+
if (runOptions?.llmConfig) {
|
|
432
|
+
try {
|
|
433
|
+
const altResult = await generateAltText(buffer, {
|
|
434
|
+
description: opcode.description,
|
|
435
|
+
url: captureUrl,
|
|
436
|
+
locale: currentVariant?.locale,
|
|
437
|
+
presetName: runOptions.presetName,
|
|
438
|
+
}, runOptions.llmConfig);
|
|
439
|
+
altText = altResult.altText;
|
|
440
|
+
if (altResult.llmResult) {
|
|
441
|
+
telemetry.llmCallCount++;
|
|
442
|
+
telemetry.llmCostEur += altResult.llmResult.costEur;
|
|
443
|
+
telemetry.llmStepUsages.push({
|
|
444
|
+
stepType: 'alt_text_generation',
|
|
445
|
+
generationId: altResult.llmResult.generationId,
|
|
446
|
+
model: altResult.llmResult.model,
|
|
447
|
+
promptTokens: altResult.llmResult.promptTokens,
|
|
448
|
+
completionTokens: altResult.llmResult.completionTokens,
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
catch {
|
|
453
|
+
// Alt text generation failed — non-fatal
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
artifacts.push({
|
|
457
|
+
mediaMode: 'screenshot',
|
|
458
|
+
buffer,
|
|
459
|
+
mimeType: 'image/png',
|
|
460
|
+
captureType: opcode.elementSelector ? 'element' : 'fullpage',
|
|
461
|
+
captureUrl,
|
|
462
|
+
dimensions: currentVariant?.viewport,
|
|
463
|
+
captureId: opcode.captureId,
|
|
464
|
+
captureName: opcode.captureName ?? opcode.description,
|
|
465
|
+
elementSelector: opcode.elementSelector,
|
|
466
|
+
altText,
|
|
467
|
+
stepDescription: opcode.description,
|
|
468
|
+
stepIndex: opcodeIndex,
|
|
469
|
+
variantId: currentVariant?.id,
|
|
470
|
+
});
|
|
471
|
+
break;
|
|
472
|
+
}
|
|
473
|
+
case 'BEGIN_CLIP':
|
|
474
|
+
if (executionState.activeClip) {
|
|
475
|
+
return { success: false, error: 'cannot start a new clip before the previous one ends' };
|
|
476
|
+
}
|
|
477
|
+
executionState.activeClip = {
|
|
478
|
+
clipId: opcode.clipId,
|
|
479
|
+
clipName: opcode.clipName,
|
|
480
|
+
};
|
|
481
|
+
await adapter.beginRecording({ mediaMode: 'clip' });
|
|
482
|
+
break;
|
|
483
|
+
case 'END_CLIP': {
|
|
484
|
+
const clipIdentity = resolveClipIdentity(executionState.activeClip, opcode);
|
|
485
|
+
const recording = await adapter.endRecording();
|
|
486
|
+
executionState.activeClip = undefined;
|
|
487
|
+
artifacts.push({
|
|
488
|
+
mediaMode: 'clip',
|
|
489
|
+
buffer: recording.buffer,
|
|
490
|
+
mimeType: recording.mimeType,
|
|
491
|
+
durationMs: recording.durationMs,
|
|
492
|
+
trimStartMs: recording.trimStartMs,
|
|
493
|
+
dimensions: undefined,
|
|
494
|
+
captureType: 'fullpage',
|
|
495
|
+
captureUrl: await adapter.getCurrentUrl(),
|
|
496
|
+
clipId: clipIdentity.clipId,
|
|
497
|
+
clipName: clipIdentity.clipName,
|
|
498
|
+
stepDescription: opcode.description,
|
|
499
|
+
stepIndex: opcodeIndex,
|
|
500
|
+
variantId: currentVariant?.id,
|
|
501
|
+
});
|
|
502
|
+
break;
|
|
503
|
+
}
|
|
504
|
+
default: {
|
|
505
|
+
const _exhaustive = opcode;
|
|
506
|
+
return { success: false, error: `unknown opcode kind: ${opcode.kind}` };
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return { success: true };
|
|
510
|
+
}
|
|
511
|
+
catch (err) {
|
|
512
|
+
return {
|
|
513
|
+
success: false,
|
|
514
|
+
error: err instanceof Error ? err.message : String(err),
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
// ── Helpers ─────────────────────────────────────────────────────────
|
|
519
|
+
async function withTimeout(fn, timeoutMs) {
|
|
520
|
+
return new Promise((resolve, reject) => {
|
|
521
|
+
const timer = setTimeout(() => reject(new Error(`timeout after ${timeoutMs}ms`)), timeoutMs);
|
|
522
|
+
fn()
|
|
523
|
+
.then(result => { clearTimeout(timer); resolve(result); })
|
|
524
|
+
.catch(err => { clearTimeout(timer); reject(err); });
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
function getRemainingTimeMs(deadlineMs) {
|
|
528
|
+
return Math.max(0, deadlineMs - Date.now());
|
|
529
|
+
}
|
|
530
|
+
function resolveClipIdentity(activeClip, opcode) {
|
|
531
|
+
if (activeClip?.clipId && opcode.clipId && activeClip.clipId !== opcode.clipId) {
|
|
532
|
+
throw new Error(`END_CLIP clipId "${opcode.clipId}" does not match active clip "${activeClip.clipId}"`);
|
|
533
|
+
}
|
|
534
|
+
return {
|
|
535
|
+
clipId: opcode.clipId ?? activeClip?.clipId,
|
|
536
|
+
clipName: opcode.clipName ?? activeClip?.clipName ?? opcode.description,
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
function withClampedPostconditionTimeout(spec, maxWaitMs) {
|
|
540
|
+
return {
|
|
541
|
+
...spec,
|
|
542
|
+
waitMs: Math.max(1, Math.min(spec.waitMs ?? maxWaitMs, maxWaitMs)),
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
function evaluateImmediateAssertion(result, prefix) {
|
|
546
|
+
return result.passed
|
|
547
|
+
? { success: true }
|
|
548
|
+
: { success: false, error: `${prefix}: ${result.reason}` };
|
|
549
|
+
}
|
|
550
|
+
async function evaluateSurfaceAssertion(adapter, selectors, matchAll) {
|
|
551
|
+
const checks = await Promise.all(selectors.map(async (selector) => ({
|
|
552
|
+
selector,
|
|
553
|
+
result: await evaluatePostcondition(adapter, {
|
|
554
|
+
type: 'element_visible',
|
|
555
|
+
selector,
|
|
556
|
+
waitMs: 1,
|
|
557
|
+
}),
|
|
558
|
+
})));
|
|
559
|
+
if (matchAll) {
|
|
560
|
+
const failed = checks.find((entry) => !entry.result.passed);
|
|
561
|
+
return failed
|
|
562
|
+
? { success: false, error: `ASSERT_SURFACE failed for "${failed.selector}": ${failed.result.reason}` }
|
|
563
|
+
: { success: true };
|
|
564
|
+
}
|
|
565
|
+
if (checks.some((entry) => entry.result.passed)) {
|
|
566
|
+
return { success: true };
|
|
567
|
+
}
|
|
568
|
+
return {
|
|
569
|
+
success: false,
|
|
570
|
+
error: `ASSERT_SURFACE failed: none of the selectors matched (${selectors.join(', ')})`,
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
async function applyLocaleOpcode(opcode, adapter) {
|
|
574
|
+
if (opcode.method === 'browser_context') {
|
|
575
|
+
await adapter.setLocale(opcode.locale);
|
|
576
|
+
if (adapter.reloadPage) {
|
|
577
|
+
await adapter.reloadPage();
|
|
578
|
+
}
|
|
579
|
+
return { success: true };
|
|
580
|
+
}
|
|
581
|
+
if (opcode.method === 'ui_interaction') {
|
|
582
|
+
if (!opcode.selector) {
|
|
583
|
+
return { success: false, error: 'SET_LOCALE ui_interaction requires selector' };
|
|
584
|
+
}
|
|
585
|
+
await adapter.click(opcode.selector);
|
|
586
|
+
return { success: true };
|
|
587
|
+
}
|
|
588
|
+
if (!opcode.storageHints || opcode.storageHints.length === 0 || !adapter.writeStorageHint) {
|
|
589
|
+
return { success: false, error: 'SET_LOCALE storage mode requires storageHints and adapter storage support' };
|
|
590
|
+
}
|
|
591
|
+
const writes = await Promise.all(opcode.storageHints.map((hint) => adapter.writeStorageHint({
|
|
592
|
+
storage: hint.storage,
|
|
593
|
+
key: hint.key,
|
|
594
|
+
value: hint.value,
|
|
595
|
+
kind: 'locale',
|
|
596
|
+
})));
|
|
597
|
+
if (!writes.some(Boolean)) {
|
|
598
|
+
return { success: false, error: 'SET_LOCALE storage hints did not match any writable locale keys' };
|
|
599
|
+
}
|
|
600
|
+
if (adapter.reloadPage) {
|
|
601
|
+
await adapter.reloadPage();
|
|
602
|
+
}
|
|
603
|
+
return { success: true };
|
|
604
|
+
}
|
|
605
|
+
async function applyThemeOpcode(opcode, adapter) {
|
|
606
|
+
if (opcode.method === 'color_scheme') {
|
|
607
|
+
await adapter.setColorScheme(opcode.theme);
|
|
608
|
+
return { success: true };
|
|
609
|
+
}
|
|
610
|
+
if (opcode.method === 'ui_interaction') {
|
|
611
|
+
if (!opcode.selector) {
|
|
612
|
+
return { success: false, error: 'SET_THEME ui_interaction requires selector' };
|
|
613
|
+
}
|
|
614
|
+
await adapter.click(opcode.selector);
|
|
615
|
+
return { success: true };
|
|
616
|
+
}
|
|
617
|
+
if (!opcode.storageHints || opcode.storageHints.length === 0 || !adapter.writeStorageHint) {
|
|
618
|
+
return { success: false, error: 'SET_THEME storage mode requires storageHints and adapter storage support' };
|
|
619
|
+
}
|
|
620
|
+
const writes = await Promise.all(opcode.storageHints.map((hint) => adapter.writeStorageHint({
|
|
621
|
+
storage: hint.storage,
|
|
622
|
+
key: hint.key,
|
|
623
|
+
value: hint.value,
|
|
624
|
+
kind: 'theme',
|
|
625
|
+
})));
|
|
626
|
+
if (!writes.some(Boolean)) {
|
|
627
|
+
return { success: false, error: 'SET_THEME storage hints did not match any writable theme keys' };
|
|
628
|
+
}
|
|
629
|
+
if (adapter.reloadPage) {
|
|
630
|
+
await adapter.reloadPage();
|
|
631
|
+
}
|
|
632
|
+
return { success: true };
|
|
633
|
+
}
|
|
634
|
+
//# sourceMappingURL=opcode-runner.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capture Agent — Overlay Engine
|
|
3
|
+
*
|
|
4
|
+
* Consolidated deterministic overlay handling.
|
|
5
|
+
* Wraps and extends the existing cookie-dismiss module with
|
|
6
|
+
* additional patterns for newsletter popups, chat widgets, and age gates.
|
|
7
|
+
*
|
|
8
|
+
* This module is used by the DISMISS_OVERLAYS opcode.
|
|
9
|
+
*/
|
|
10
|
+
import type { RuntimeAdapter } from './types.js';
|
|
11
|
+
export interface OverlayDismissResult {
|
|
12
|
+
dismissed: boolean;
|
|
13
|
+
methods: string[];
|
|
14
|
+
overlaysFound: number;
|
|
15
|
+
overlaysRemaining: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Multi-pass overlay dismissal.
|
|
19
|
+
* 1. Delegate to the adapter's built-in dismissOverlays (cookie-dismiss.ts)
|
|
20
|
+
* 2. Check AKTree for remaining blocking overlays
|
|
21
|
+
* 3. Try additional heuristics for newsletter/chat/age gate
|
|
22
|
+
* 4. Final AKTree check
|
|
23
|
+
*/
|
|
24
|
+
export declare function dismissAllOverlays(adapter: RuntimeAdapter): Promise<OverlayDismissResult>;
|