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,292 @@
|
|
|
1
|
+
import { evaluateSurfaceContract } from "./capture-surface-contract.js";
|
|
2
|
+
import { evaluateRequestedLanguageState, evaluateRequestedThemeState, } from "./session-profile.js";
|
|
3
|
+
function normalizePath(value) {
|
|
4
|
+
if (!value)
|
|
5
|
+
return null;
|
|
6
|
+
try {
|
|
7
|
+
const parsed = new URL(value);
|
|
8
|
+
const pathname = parsed.pathname.replace(/\/+$/, "") || "/";
|
|
9
|
+
return `${parsed.origin}${pathname}`;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return value.trim().replace(/\/+$/, "") || null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function sameOrigin(left, right) {
|
|
16
|
+
if (!left || !right)
|
|
17
|
+
return false;
|
|
18
|
+
try {
|
|
19
|
+
return new URL(left).origin === new URL(right).origin;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function urlMatchesPolicy(currentUrl, targetUrl, policy) {
|
|
26
|
+
if (policy === "ignore")
|
|
27
|
+
return true;
|
|
28
|
+
if (!currentUrl || !targetUrl)
|
|
29
|
+
return false;
|
|
30
|
+
try {
|
|
31
|
+
const current = new URL(currentUrl);
|
|
32
|
+
const target = new URL(targetUrl);
|
|
33
|
+
if (current.origin !== target.origin)
|
|
34
|
+
return false;
|
|
35
|
+
if (policy === "same_origin")
|
|
36
|
+
return true;
|
|
37
|
+
const normalizedTarget = target.pathname.replace(/\/+$/, "") || "/";
|
|
38
|
+
const normalizedCurrent = current.pathname.replace(/\/+$/, "") || "/";
|
|
39
|
+
if (normalizedCurrent === normalizedTarget)
|
|
40
|
+
return true;
|
|
41
|
+
return normalizedCurrent.startsWith(`${normalizedTarget}/`);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return normalizePath(currentUrl) === normalizePath(targetUrl);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function detectLikelyAuthMismatch(signals, profile) {
|
|
48
|
+
if (!signals || profile?.authState !== "authenticated")
|
|
49
|
+
return false;
|
|
50
|
+
const hasAuthenticatedHints = (signals.authHints?.logoutButtons.length ?? 0) > 0
|
|
51
|
+
|| (signals.authHints?.accountMenuLabels.length ?? 0) > 0
|
|
52
|
+
|| (signals.authHints?.accountLikeText.length ?? 0) > 0;
|
|
53
|
+
const hasAnonymousHints = signals.authHints?.hasAuthForm
|
|
54
|
+
|| signals.authHints?.hasPasswordField
|
|
55
|
+
|| ((signals.authHints?.loginButtons.length ?? 0) > 0 && !hasAuthenticatedHints);
|
|
56
|
+
return !!hasAnonymousHints && !hasAuthenticatedHints;
|
|
57
|
+
}
|
|
58
|
+
function detectLikelyVariantMismatch(params) {
|
|
59
|
+
const { signals, requestedLang, requestedTheme } = params;
|
|
60
|
+
if (!signals)
|
|
61
|
+
return false;
|
|
62
|
+
const languageState = requestedLang
|
|
63
|
+
? evaluateRequestedLanguageState({
|
|
64
|
+
currentUrl: signals.url || signals.canonicalUrl || "",
|
|
65
|
+
requestedLang,
|
|
66
|
+
signals,
|
|
67
|
+
})
|
|
68
|
+
: null;
|
|
69
|
+
const themeState = requestedTheme
|
|
70
|
+
? evaluateRequestedThemeState({
|
|
71
|
+
requestedTheme,
|
|
72
|
+
signals,
|
|
73
|
+
})
|
|
74
|
+
: null;
|
|
75
|
+
const langMismatch = !!languageState?.requested
|
|
76
|
+
&& languageState.detected !== null
|
|
77
|
+
&& languageState.active === false
|
|
78
|
+
&& languageState.ambiguous === false
|
|
79
|
+
&& languageState.confidence === "high";
|
|
80
|
+
const themeMismatch = !!themeState?.requested
|
|
81
|
+
&& themeState.detected !== null
|
|
82
|
+
&& themeState.active === false
|
|
83
|
+
&& themeState.ambiguous === false
|
|
84
|
+
&& themeState.confidence === "high";
|
|
85
|
+
return langMismatch || themeMismatch;
|
|
86
|
+
}
|
|
87
|
+
export function deriveCapturePageContract(params) {
|
|
88
|
+
const identity = params.pageIdentity ?? null;
|
|
89
|
+
switch (identity?.kind) {
|
|
90
|
+
case "modal_selection":
|
|
91
|
+
return {
|
|
92
|
+
expectedSurfaceKind: "dialog_selection",
|
|
93
|
+
dialogPolicy: "must_be_open",
|
|
94
|
+
routePolicy: "same_origin",
|
|
95
|
+
reusePolicy: "verified_only",
|
|
96
|
+
variantPolicy: "must_match",
|
|
97
|
+
};
|
|
98
|
+
case "modal_configuration":
|
|
99
|
+
return {
|
|
100
|
+
expectedSurfaceKind: "dialog_configuration",
|
|
101
|
+
dialogPolicy: "must_be_open",
|
|
102
|
+
routePolicy: "same_origin",
|
|
103
|
+
reusePolicy: "verified_only",
|
|
104
|
+
variantPolicy: "must_match",
|
|
105
|
+
};
|
|
106
|
+
case "editor_route":
|
|
107
|
+
return {
|
|
108
|
+
expectedSurfaceKind: "editor_route",
|
|
109
|
+
dialogPolicy: "must_be_closed",
|
|
110
|
+
routePolicy: "exact_or_descendant",
|
|
111
|
+
reusePolicy: "same_surface_only",
|
|
112
|
+
variantPolicy: "must_match",
|
|
113
|
+
};
|
|
114
|
+
case "detail_route":
|
|
115
|
+
return {
|
|
116
|
+
expectedSurfaceKind: "detail_route",
|
|
117
|
+
dialogPolicy: identity.dedicatedRoute ? "must_be_closed" : "allow_either",
|
|
118
|
+
routePolicy: identity.dedicatedRoute ? "exact_or_descendant" : "same_origin",
|
|
119
|
+
reusePolicy: "same_surface_only",
|
|
120
|
+
variantPolicy: "must_match",
|
|
121
|
+
};
|
|
122
|
+
case "gallery":
|
|
123
|
+
return {
|
|
124
|
+
expectedSurfaceKind: "gallery",
|
|
125
|
+
dialogPolicy: identity.dialogTarget ? "must_be_open" : "must_be_closed",
|
|
126
|
+
routePolicy: identity.dedicatedRoute ? "exact_or_descendant" : "same_origin",
|
|
127
|
+
reusePolicy: "same_surface_only",
|
|
128
|
+
variantPolicy: "must_match",
|
|
129
|
+
};
|
|
130
|
+
default:
|
|
131
|
+
return {
|
|
132
|
+
expectedSurfaceKind: "route",
|
|
133
|
+
dialogPolicy: identity?.dialogTarget ? "must_be_open" : "allow_either",
|
|
134
|
+
routePolicy: identity?.dedicatedRoute ? "exact_or_descendant" : "same_origin",
|
|
135
|
+
reusePolicy: "same_surface_only",
|
|
136
|
+
variantPolicy: "prefer_match",
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
export function buildCaptureCheckpointEvidence(params) {
|
|
141
|
+
const contract = deriveCapturePageContract({ pageIdentity: params.pageIdentity });
|
|
142
|
+
const reasons = [];
|
|
143
|
+
const urlMatched = urlMatchesPolicy(params.currentUrl ?? params.observation.url, params.targetUrl, contract.routePolicy);
|
|
144
|
+
if (!urlMatched) {
|
|
145
|
+
reasons.push("current route does not match the target route policy");
|
|
146
|
+
}
|
|
147
|
+
const dialogMatched = contract.dialogPolicy === "allow_either"
|
|
148
|
+
? true
|
|
149
|
+
: contract.dialogPolicy === "must_be_open"
|
|
150
|
+
? params.observation.dialogCount > 0
|
|
151
|
+
: params.observation.dialogCount === 0;
|
|
152
|
+
if (!dialogMatched) {
|
|
153
|
+
reasons.push(contract.dialogPolicy === "must_be_open"
|
|
154
|
+
? "expected dialog/configuration surface is not open"
|
|
155
|
+
: "unexpected dialog/overlay is still open");
|
|
156
|
+
}
|
|
157
|
+
const authMatched = !detectLikelyAuthMismatch(params.pageSignals ?? null, params.profile);
|
|
158
|
+
if (!authMatched) {
|
|
159
|
+
reasons.push("authenticated session looks degraded on the current page");
|
|
160
|
+
}
|
|
161
|
+
const variantMatched = !detectLikelyVariantMismatch({
|
|
162
|
+
signals: params.pageSignals ?? null,
|
|
163
|
+
requestedLang: params.requestedLang,
|
|
164
|
+
requestedTheme: params.requestedTheme,
|
|
165
|
+
});
|
|
166
|
+
if (!variantMatched && contract.variantPolicy === "must_match") {
|
|
167
|
+
reasons.push("current page hints do not match the requested language/theme variant");
|
|
168
|
+
}
|
|
169
|
+
const strongSurfaceObserved = !!params.observation.strongSurfaceSignature
|
|
170
|
+
&& (!!params.observation.primarySurface
|
|
171
|
+
|| !!params.observation.overlaySurface
|
|
172
|
+
|| !!params.observation.configurationSurface);
|
|
173
|
+
if (!strongSurfaceObserved) {
|
|
174
|
+
reasons.push("current page surface is too weakly identified for a strong checkpoint");
|
|
175
|
+
}
|
|
176
|
+
const surfaceContract = evaluateSurfaceContract({
|
|
177
|
+
contract,
|
|
178
|
+
currentUrl: params.currentUrl,
|
|
179
|
+
observation: params.observation,
|
|
180
|
+
pageSignals: params.pageSignals ?? null,
|
|
181
|
+
pageIdentity: params.pageIdentity ?? null,
|
|
182
|
+
});
|
|
183
|
+
if (!surfaceContract.matched) {
|
|
184
|
+
reasons.push(surfaceContract.reason ?? "current page surface does not match the expected capture contract");
|
|
185
|
+
}
|
|
186
|
+
const evidence = {
|
|
187
|
+
urlMatched,
|
|
188
|
+
dialogMatched,
|
|
189
|
+
authMatched,
|
|
190
|
+
variantMatched: contract.variantPolicy === "ignore" ? true : variantMatched,
|
|
191
|
+
strongSurfaceObserved,
|
|
192
|
+
surfaceMatched: surfaceContract.matched,
|
|
193
|
+
strongSurfaceSignature: params.observation.strongSurfaceSignature ?? params.observation.surfaceSignature ?? null,
|
|
194
|
+
primarySurface: params.observation.primarySurface ?? null,
|
|
195
|
+
overlaySurface: params.observation.overlaySurface ?? null,
|
|
196
|
+
navigationSurface: params.observation.navigationSurface ?? null,
|
|
197
|
+
configurationSurface: params.observation.configurationSurface ?? null,
|
|
198
|
+
reasons: [...reasons],
|
|
199
|
+
};
|
|
200
|
+
const mode = evidence.urlMatched
|
|
201
|
+
&& evidence.dialogMatched
|
|
202
|
+
&& evidence.authMatched
|
|
203
|
+
&& evidence.variantMatched
|
|
204
|
+
&& (evidence.surfaceMatched ?? true)
|
|
205
|
+
&& evidence.strongSurfaceObserved
|
|
206
|
+
? "verified"
|
|
207
|
+
: !evidence.authMatched
|
|
208
|
+
? "dirty"
|
|
209
|
+
: "degraded";
|
|
210
|
+
return {
|
|
211
|
+
mode,
|
|
212
|
+
contract,
|
|
213
|
+
evidence,
|
|
214
|
+
reasons,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
export function decideCaptureTransition(params) {
|
|
218
|
+
const contract = deriveCapturePageContract({ pageIdentity: params.pageIdentity });
|
|
219
|
+
if (!params.observation) {
|
|
220
|
+
return {
|
|
221
|
+
mode: "degraded",
|
|
222
|
+
contract,
|
|
223
|
+
evidence: {
|
|
224
|
+
urlMatched: false,
|
|
225
|
+
dialogMatched: contract.dialogPolicy === "allow_either",
|
|
226
|
+
authMatched: true,
|
|
227
|
+
variantMatched: true,
|
|
228
|
+
strongSurfaceObserved: false,
|
|
229
|
+
surfaceMatched: false,
|
|
230
|
+
strongSurfaceSignature: null,
|
|
231
|
+
primarySurface: null,
|
|
232
|
+
overlaySurface: null,
|
|
233
|
+
navigationSurface: null,
|
|
234
|
+
configurationSurface: null,
|
|
235
|
+
reasons: ["baseline observation unavailable"],
|
|
236
|
+
},
|
|
237
|
+
reasons: ["baseline observation unavailable"],
|
|
238
|
+
shouldNavigateToCanonical: true,
|
|
239
|
+
shouldRecreateContext: false,
|
|
240
|
+
allowFastPath: false,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
const checkpointDecision = buildCaptureCheckpointEvidence({
|
|
244
|
+
currentUrl: params.currentUrl ?? params.observation.url,
|
|
245
|
+
targetUrl: params.resumeUrl ?? params.targetUrl,
|
|
246
|
+
pageIdentity: params.pageIdentity,
|
|
247
|
+
observation: params.observation,
|
|
248
|
+
pageSignals: params.pageSignals ?? null,
|
|
249
|
+
profile: params.profile,
|
|
250
|
+
requestedLang: params.requestedLang,
|
|
251
|
+
requestedTheme: params.requestedTheme,
|
|
252
|
+
});
|
|
253
|
+
const reasons = [...checkpointDecision.reasons];
|
|
254
|
+
const handoffTrust = params.handoffContext?.trust ?? "degraded";
|
|
255
|
+
const sameSessionOrigin = sameOrigin(params.currentUrl ?? params.observation.url, params.targetUrl)
|
|
256
|
+
|| sameOrigin(params.currentUrl ?? params.observation.url, params.resumeUrl ?? null);
|
|
257
|
+
const derivedFromRepair = params.handoffContext?.derivedFromRepair === true;
|
|
258
|
+
let mode = checkpointDecision.mode;
|
|
259
|
+
if (params.pageIndex === 0) {
|
|
260
|
+
mode = checkpointDecision.mode === "dirty" ? "dirty" : "degraded";
|
|
261
|
+
reasons.push("first page in variant must rebase canonically before capture");
|
|
262
|
+
}
|
|
263
|
+
else if (derivedFromRepair) {
|
|
264
|
+
mode = checkpointDecision.mode === "dirty" ? "dirty" : "degraded";
|
|
265
|
+
reasons.push("previous page state came from repair/recovery and cannot be reused as trusted handoff");
|
|
266
|
+
}
|
|
267
|
+
else if (handoffTrust !== "verified") {
|
|
268
|
+
mode = checkpointDecision.mode === "dirty" ? "dirty" : "degraded";
|
|
269
|
+
reasons.push("handoff context is not strongly trusted");
|
|
270
|
+
}
|
|
271
|
+
else if (!sameSessionOrigin) {
|
|
272
|
+
mode = "dirty";
|
|
273
|
+
reasons.push("current live state belongs to a different origin");
|
|
274
|
+
}
|
|
275
|
+
else if (checkpointDecision.mode !== "verified") {
|
|
276
|
+
mode = checkpointDecision.mode;
|
|
277
|
+
}
|
|
278
|
+
else if (contract.reusePolicy === "never") {
|
|
279
|
+
mode = "degraded";
|
|
280
|
+
reasons.push("current page contract forbids opportunistic live-state reuse");
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
mode,
|
|
284
|
+
contract,
|
|
285
|
+
evidence: checkpointDecision.evidence,
|
|
286
|
+
reasons,
|
|
287
|
+
shouldNavigateToCanonical: mode === "degraded",
|
|
288
|
+
shouldRecreateContext: mode === "dirty",
|
|
289
|
+
allowFastPath: mode === "verified",
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
//# sourceMappingURL=capture-transition-engine.js.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { CaptureCheckpoint, CapturePageIdentity, VariantCaptureRepairRecord, VariantCaptureManifest, VariantCaptureStatus, VariantValidatedCapture } from "./types.js";
|
|
2
|
+
import type { ScreenshotPageRunInput } from "./capture-run-optimizer.js";
|
|
3
|
+
export interface VariantCaptureState {
|
|
4
|
+
expectedPageIds: string[];
|
|
5
|
+
pageIdentities: Record<string, CapturePageIdentity>;
|
|
6
|
+
pagePromptFingerprints: Record<string, string>;
|
|
7
|
+
validatedCaptures: VariantValidatedCapture[];
|
|
8
|
+
captureStatuses: Record<string, VariantCaptureStatus>;
|
|
9
|
+
lastCheckpointId: string | null;
|
|
10
|
+
blockedReasons: Record<string, string | null>;
|
|
11
|
+
recoveryAttempts: Record<string, number>;
|
|
12
|
+
repairHistory: VariantCaptureRepairRecord[];
|
|
13
|
+
}
|
|
14
|
+
export declare function buildPageRunPromptFingerprint(pageRun: Pick<ScreenshotPageRunInput, "prompt" | "url">, pageIdentity?: CapturePageIdentity | null): string;
|
|
15
|
+
export declare function createVariantCaptureState(pageRuns: ScreenshotPageRunInput[], precomputedIdentities?: Record<string, CapturePageIdentity>): VariantCaptureState;
|
|
16
|
+
export declare function buildVariantManifestContext(params: {
|
|
17
|
+
state: VariantCaptureState;
|
|
18
|
+
currentPageRun: ScreenshotPageRunInput;
|
|
19
|
+
}): VariantCaptureManifest;
|
|
20
|
+
export declare function recordValidatedVariantCapture(params: {
|
|
21
|
+
state: VariantCaptureState;
|
|
22
|
+
capture: VariantValidatedCapture;
|
|
23
|
+
checkpointId?: string | null;
|
|
24
|
+
}): {
|
|
25
|
+
state: VariantCaptureState;
|
|
26
|
+
duplicateOfPageId: string | null;
|
|
27
|
+
};
|
|
28
|
+
export declare function validateVariantCaptureState(state: VariantCaptureState): {
|
|
29
|
+
ok: boolean;
|
|
30
|
+
missingPages: string[];
|
|
31
|
+
blockedPages: Array<{
|
|
32
|
+
pageId: string;
|
|
33
|
+
reason: string | null;
|
|
34
|
+
}>;
|
|
35
|
+
duplicatePageIds: Array<{
|
|
36
|
+
pageId: string;
|
|
37
|
+
duplicateOfPageId: string;
|
|
38
|
+
}>;
|
|
39
|
+
};
|
|
40
|
+
export declare function hasValidatedVariantPage(state: VariantCaptureState, pageId: string | null | undefined): boolean;
|
|
41
|
+
export declare function getVariantCaptureStatus(state: VariantCaptureState, pageId: string | null | undefined): VariantCaptureStatus;
|
|
42
|
+
export declare function markVariantCaptureInProgress(state: VariantCaptureState, pageId: string | null | undefined): VariantCaptureState;
|
|
43
|
+
export declare function markVariantCaptureBlocked(params: {
|
|
44
|
+
state: VariantCaptureState;
|
|
45
|
+
pageId: string | null | undefined;
|
|
46
|
+
reason: string;
|
|
47
|
+
}): VariantCaptureState;
|
|
48
|
+
export declare function recordVariantRecoveryAttempt(state: VariantCaptureState, pageId: string | null | undefined): VariantCaptureState;
|
|
49
|
+
export declare function recordVariantRepair(params: {
|
|
50
|
+
state: VariantCaptureState;
|
|
51
|
+
repair: VariantCaptureRepairRecord;
|
|
52
|
+
}): VariantCaptureState;
|
|
53
|
+
export declare function recordVariantCheckpoint(params: {
|
|
54
|
+
state: VariantCaptureState;
|
|
55
|
+
checkpoint: CaptureCheckpoint;
|
|
56
|
+
}): VariantCaptureState;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { createHash } from "crypto";
|
|
2
|
+
import { inferCapturePageIdentity } from "./capture-page-identity.js";
|
|
3
|
+
function normalizePageId(pageId) {
|
|
4
|
+
return pageId ?? "main";
|
|
5
|
+
}
|
|
6
|
+
export function buildPageRunPromptFingerprint(pageRun, pageIdentity) {
|
|
7
|
+
return createHash("sha1")
|
|
8
|
+
.update(pageRun.prompt ?? "")
|
|
9
|
+
.update("\n")
|
|
10
|
+
.update(pageRun.url ?? "")
|
|
11
|
+
.update("\n")
|
|
12
|
+
.update(pageIdentity?.summary ?? "")
|
|
13
|
+
.digest("hex");
|
|
14
|
+
}
|
|
15
|
+
export function createVariantCaptureState(pageRuns, precomputedIdentities) {
|
|
16
|
+
const pageIdentities = precomputedIdentities ?? Object.fromEntries(pageRuns.map((pageRun) => [
|
|
17
|
+
normalizePageId(pageRun.pageId),
|
|
18
|
+
inferCapturePageIdentity(pageRun),
|
|
19
|
+
]));
|
|
20
|
+
const pagePromptFingerprints = Object.fromEntries(pageRuns.map((pageRun) => {
|
|
21
|
+
const pageId = normalizePageId(pageRun.pageId);
|
|
22
|
+
return [
|
|
23
|
+
pageId,
|
|
24
|
+
buildPageRunPromptFingerprint(pageRun, pageIdentities[pageId] ?? null),
|
|
25
|
+
];
|
|
26
|
+
}));
|
|
27
|
+
return {
|
|
28
|
+
expectedPageIds: pageRuns.map((pageRun) => normalizePageId(pageRun.pageId)),
|
|
29
|
+
pageIdentities,
|
|
30
|
+
pagePromptFingerprints,
|
|
31
|
+
validatedCaptures: [],
|
|
32
|
+
captureStatuses: Object.fromEntries(pageRuns.map((pageRun) => [normalizePageId(pageRun.pageId), "pending"])),
|
|
33
|
+
lastCheckpointId: null,
|
|
34
|
+
blockedReasons: {},
|
|
35
|
+
recoveryAttempts: {},
|
|
36
|
+
repairHistory: [],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export function buildVariantManifestContext(params) {
|
|
40
|
+
const currentPageId = normalizePageId(params.currentPageRun.pageId);
|
|
41
|
+
const completedPages = params.state.validatedCaptures.map((capture) => capture.pageId);
|
|
42
|
+
return {
|
|
43
|
+
expectedPageIds: [...params.state.expectedPageIds],
|
|
44
|
+
currentPageId,
|
|
45
|
+
currentPageIdentity: params.state.pageIdentities[currentPageId] ?? null,
|
|
46
|
+
promptFingerprint: params.state.pagePromptFingerprints[currentPageId] ?? null,
|
|
47
|
+
pagePromptFingerprints: { ...params.state.pagePromptFingerprints },
|
|
48
|
+
completedPages,
|
|
49
|
+
remainingPages: params.state.expectedPageIds.filter((pageId) => !completedPages.includes(pageId)),
|
|
50
|
+
previousValidatedCaptures: [...params.state.validatedCaptures],
|
|
51
|
+
captureStatuses: { ...params.state.captureStatuses },
|
|
52
|
+
lastCheckpointId: params.state.lastCheckpointId,
|
|
53
|
+
blockedReason: params.state.blockedReasons[currentPageId] ?? null,
|
|
54
|
+
recoveryAttempts: { ...params.state.recoveryAttempts },
|
|
55
|
+
repairHistory: [...params.state.repairHistory],
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export function recordValidatedVariantCapture(params) {
|
|
59
|
+
const pageId = normalizePageId(params.capture.pageId);
|
|
60
|
+
const duplicateOfPageId = params.capture.fingerprint
|
|
61
|
+
? params.state.validatedCaptures.find((existing) => existing.pageId !== pageId
|
|
62
|
+
&& existing.fingerprint
|
|
63
|
+
&& existing.fingerprint === params.capture.fingerprint)?.pageId ?? null
|
|
64
|
+
: null;
|
|
65
|
+
const nextValidated = [
|
|
66
|
+
...params.state.validatedCaptures.filter((existing) => existing.pageId !== pageId),
|
|
67
|
+
{
|
|
68
|
+
...params.capture,
|
|
69
|
+
pageId,
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
return {
|
|
73
|
+
state: {
|
|
74
|
+
expectedPageIds: [...params.state.expectedPageIds],
|
|
75
|
+
pageIdentities: { ...params.state.pageIdentities },
|
|
76
|
+
pagePromptFingerprints: { ...params.state.pagePromptFingerprints },
|
|
77
|
+
validatedCaptures: nextValidated,
|
|
78
|
+
captureStatuses: {
|
|
79
|
+
...params.state.captureStatuses,
|
|
80
|
+
[pageId]: "completed",
|
|
81
|
+
},
|
|
82
|
+
lastCheckpointId: params.checkpointId ?? params.state.lastCheckpointId,
|
|
83
|
+
blockedReasons: {
|
|
84
|
+
...params.state.blockedReasons,
|
|
85
|
+
[pageId]: null,
|
|
86
|
+
},
|
|
87
|
+
recoveryAttempts: { ...params.state.recoveryAttempts },
|
|
88
|
+
repairHistory: [...params.state.repairHistory],
|
|
89
|
+
},
|
|
90
|
+
duplicateOfPageId,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
export function validateVariantCaptureState(state) {
|
|
94
|
+
const missingPages = state.expectedPageIds.filter((pageId) => !state.validatedCaptures.some((capture) => capture.pageId === pageId)
|
|
95
|
+
&& state.captureStatuses[pageId] !== "blocked");
|
|
96
|
+
const blockedPages = state.expectedPageIds
|
|
97
|
+
.filter((pageId) => state.captureStatuses[pageId] === "blocked")
|
|
98
|
+
.map((pageId) => ({ pageId, reason: state.blockedReasons[pageId] ?? null }));
|
|
99
|
+
const duplicatePageIds = [];
|
|
100
|
+
const seenFingerprints = new Map();
|
|
101
|
+
for (const capture of state.validatedCaptures) {
|
|
102
|
+
if (!capture.fingerprint)
|
|
103
|
+
continue;
|
|
104
|
+
const existingPageId = seenFingerprints.get(capture.fingerprint);
|
|
105
|
+
if (existingPageId && existingPageId !== capture.pageId) {
|
|
106
|
+
duplicatePageIds.push({ pageId: capture.pageId, duplicateOfPageId: existingPageId });
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
seenFingerprints.set(capture.fingerprint, capture.pageId);
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
ok: missingPages.length === 0 && blockedPages.length === 0 && duplicatePageIds.length === 0,
|
|
113
|
+
missingPages,
|
|
114
|
+
blockedPages,
|
|
115
|
+
duplicatePageIds,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
export function hasValidatedVariantPage(state, pageId) {
|
|
119
|
+
const normalized = normalizePageId(pageId);
|
|
120
|
+
return state.validatedCaptures.some((capture) => capture.pageId === normalized);
|
|
121
|
+
}
|
|
122
|
+
export function getVariantCaptureStatus(state, pageId) {
|
|
123
|
+
const normalized = normalizePageId(pageId);
|
|
124
|
+
return state.captureStatuses[normalized] ?? "pending";
|
|
125
|
+
}
|
|
126
|
+
export function markVariantCaptureInProgress(state, pageId) {
|
|
127
|
+
const normalized = normalizePageId(pageId);
|
|
128
|
+
return {
|
|
129
|
+
...state,
|
|
130
|
+
pagePromptFingerprints: { ...state.pagePromptFingerprints },
|
|
131
|
+
captureStatuses: {
|
|
132
|
+
...state.captureStatuses,
|
|
133
|
+
[normalized]: "in_progress",
|
|
134
|
+
},
|
|
135
|
+
blockedReasons: {
|
|
136
|
+
...state.blockedReasons,
|
|
137
|
+
[normalized]: null,
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
export function markVariantCaptureBlocked(params) {
|
|
142
|
+
const normalized = normalizePageId(params.pageId);
|
|
143
|
+
return {
|
|
144
|
+
...params.state,
|
|
145
|
+
pagePromptFingerprints: { ...params.state.pagePromptFingerprints },
|
|
146
|
+
captureStatuses: {
|
|
147
|
+
...params.state.captureStatuses,
|
|
148
|
+
[normalized]: "blocked",
|
|
149
|
+
},
|
|
150
|
+
blockedReasons: {
|
|
151
|
+
...params.state.blockedReasons,
|
|
152
|
+
[normalized]: params.reason,
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
export function recordVariantRecoveryAttempt(state, pageId) {
|
|
157
|
+
const normalized = normalizePageId(pageId);
|
|
158
|
+
return {
|
|
159
|
+
...state,
|
|
160
|
+
pagePromptFingerprints: { ...state.pagePromptFingerprints },
|
|
161
|
+
recoveryAttempts: {
|
|
162
|
+
...state.recoveryAttempts,
|
|
163
|
+
[normalized]: (state.recoveryAttempts[normalized] ?? 0) + 1,
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
export function recordVariantRepair(params) {
|
|
168
|
+
const nextHistory = [...params.state.repairHistory, params.repair];
|
|
169
|
+
return {
|
|
170
|
+
...params.state,
|
|
171
|
+
pagePromptFingerprints: { ...params.state.pagePromptFingerprints },
|
|
172
|
+
repairHistory: nextHistory.slice(-12),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
export function recordVariantCheckpoint(params) {
|
|
176
|
+
return {
|
|
177
|
+
...params.state,
|
|
178
|
+
pagePromptFingerprints: { ...params.state.pagePromptFingerprints },
|
|
179
|
+
lastCheckpointId: params.checkpoint.id,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=capture-variant-state.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capture Agent — Capture Verification
|
|
3
|
+
*
|
|
4
|
+
* Post-capture quality check via LLM vision.
|
|
5
|
+
* Detects: blank pages, error pages, loading spinners, skeletons,
|
|
6
|
+
* obstructing overlays, and other visual issues.
|
|
7
|
+
*
|
|
8
|
+
* This is the ONLY place where AutoKap uses AI at runtime.
|
|
9
|
+
* Cost: ~0.002 EUR per check (1 image + short prompt).
|
|
10
|
+
*/
|
|
11
|
+
import { type LLMProviderConfig, type LLMCallResult } from './llm-provider.js';
|
|
12
|
+
export interface VerificationResult {
|
|
13
|
+
/** Whether the screenshot is acceptable for delivery */
|
|
14
|
+
passed: boolean;
|
|
15
|
+
/** Issue detected (if failed) */
|
|
16
|
+
issue?: 'blank_page' | 'error_page' | 'loading' | 'overlay_blocking' | 'wrong_content' | 'other';
|
|
17
|
+
/** Human-readable explanation */
|
|
18
|
+
reason: string;
|
|
19
|
+
/** LLM call metadata */
|
|
20
|
+
llmResult?: LLMCallResult;
|
|
21
|
+
}
|
|
22
|
+
export interface VerificationContext {
|
|
23
|
+
/** What was this screenshot supposed to show? */
|
|
24
|
+
expectedDescription: string;
|
|
25
|
+
/** Target URL */
|
|
26
|
+
url: string;
|
|
27
|
+
/** Locale of this variant */
|
|
28
|
+
locale?: string;
|
|
29
|
+
/** Theme of this variant */
|
|
30
|
+
theme?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Verifies a captured screenshot is clean and ready for delivery.
|
|
34
|
+
*/
|
|
35
|
+
export declare function verifyCaptureQuality(screenshot: Buffer, context: VerificationContext, llmConfig: LLMProviderConfig): Promise<VerificationResult>;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capture Agent — Capture Verification
|
|
3
|
+
*
|
|
4
|
+
* Post-capture quality check via LLM vision.
|
|
5
|
+
* Detects: blank pages, error pages, loading spinners, skeletons,
|
|
6
|
+
* obstructing overlays, and other visual issues.
|
|
7
|
+
*
|
|
8
|
+
* This is the ONLY place where AutoKap uses AI at runtime.
|
|
9
|
+
* Cost: ~0.002 EUR per check (1 image + short prompt).
|
|
10
|
+
*/
|
|
11
|
+
import { callLLM } from './llm-provider.js';
|
|
12
|
+
const SYSTEM_PROMPT = `You are a screenshot quality checker for a web capture tool. You receive a screenshot of a web page and must verify it is clean and ready for delivery.
|
|
13
|
+
|
|
14
|
+
Check for these issues:
|
|
15
|
+
1. BLANK - The page is completely white/blank or has no meaningful content
|
|
16
|
+
2. ERROR - The page shows an error message (404, 500, "something went wrong", crash screen)
|
|
17
|
+
3. LOADING - The page has visible loading indicators (spinners, skeletons, progress bars, "Loading..." text)
|
|
18
|
+
4. OVERLAY - A modal, popup, cookie banner, or other overlay is blocking the main content
|
|
19
|
+
5. WRONG - The content doesn't match what was expected
|
|
20
|
+
|
|
21
|
+
Respond with EXACTLY one line in this format:
|
|
22
|
+
PASS: Screenshot looks clean and ready.
|
|
23
|
+
or
|
|
24
|
+
FAIL:<issue>: <explanation>
|
|
25
|
+
|
|
26
|
+
Where <issue> is one of: blank_page, error_page, loading, overlay_blocking, wrong_content, other
|
|
27
|
+
|
|
28
|
+
Examples:
|
|
29
|
+
PASS: Screenshot looks clean and ready.
|
|
30
|
+
FAIL:loading: A skeleton loader is visible in the main content area.
|
|
31
|
+
FAIL:overlay_blocking: A cookie consent banner covers the bottom of the page.
|
|
32
|
+
FAIL:error_page: The page shows a 404 Not Found error.`;
|
|
33
|
+
/**
|
|
34
|
+
* Verifies a captured screenshot is clean and ready for delivery.
|
|
35
|
+
*/
|
|
36
|
+
export async function verifyCaptureQuality(screenshot, context, llmConfig) {
|
|
37
|
+
const userPrompt = buildUserPrompt(context);
|
|
38
|
+
try {
|
|
39
|
+
const result = await callLLM(llmConfig, SYSTEM_PROMPT, userPrompt, screenshot);
|
|
40
|
+
return parseVerificationResponse(result.text, result);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
// If LLM fails, pass the screenshot — don't block delivery because of LLM issues
|
|
44
|
+
return {
|
|
45
|
+
passed: true,
|
|
46
|
+
reason: `verification skipped (LLM error: ${err instanceof Error ? err.message : String(err)})`,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function buildUserPrompt(context) {
|
|
51
|
+
const parts = [
|
|
52
|
+
`Expected content: ${context.expectedDescription}`,
|
|
53
|
+
`URL: ${context.url}`,
|
|
54
|
+
];
|
|
55
|
+
if (context.locale)
|
|
56
|
+
parts.push(`Locale: ${context.locale}`);
|
|
57
|
+
if (context.theme)
|
|
58
|
+
parts.push(`Theme: ${context.theme}`);
|
|
59
|
+
parts.push('\nIs this screenshot clean and ready for delivery?');
|
|
60
|
+
return parts.join('\n');
|
|
61
|
+
}
|
|
62
|
+
function parseVerificationResponse(text, llmResult) {
|
|
63
|
+
const trimmed = text.trim();
|
|
64
|
+
if (trimmed.startsWith('PASS')) {
|
|
65
|
+
return {
|
|
66
|
+
passed: true,
|
|
67
|
+
reason: trimmed.replace(/^PASS:?\s*/, ''),
|
|
68
|
+
llmResult,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
if (trimmed.startsWith('FAIL')) {
|
|
72
|
+
const match = trimmed.match(/^FAIL:(\w+):\s*(.+)/);
|
|
73
|
+
if (match) {
|
|
74
|
+
return {
|
|
75
|
+
passed: false,
|
|
76
|
+
issue: match[1],
|
|
77
|
+
reason: match[2],
|
|
78
|
+
llmResult,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
passed: false,
|
|
83
|
+
issue: 'other',
|
|
84
|
+
reason: trimmed.replace(/^FAIL:?\s*/, ''),
|
|
85
|
+
llmResult,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// Ambiguous response — assume pass
|
|
89
|
+
return {
|
|
90
|
+
passed: true,
|
|
91
|
+
reason: `ambiguous LLM response: "${trimmed.slice(0, 100)}"`,
|
|
92
|
+
llmResult,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=capture-verification.js.map
|