autokap 1.0.5 → 1.0.7
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/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/dist/abort.d.ts +5 -0
- package/dist/abort.js +44 -0
- package/dist/agent.d.ts +142 -0
- package/dist/agent.js +4511 -0
- package/dist/billing-operation-logging.d.ts +38 -0
- package/dist/billing-operation-logging.js +248 -0
- package/dist/browser-bar.d.ts +40 -0
- package/dist/browser-bar.js +147 -0
- package/dist/browser.d.ts +25 -0
- package/dist/browser.js +177 -9
- package/dist/capture-alt-text.d.ts +12 -0
- package/dist/capture-alt-text.js +51 -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 +286 -0
- package/dist/capture-llm-page-identity.d.ts +15 -0
- package/dist/capture-llm-page-identity.js +116 -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 +9 -0
- package/dist/capture-page-identity.js +219 -0
- package/dist/capture-preset-credentials.d.ts +12 -0
- package/dist/capture-preset-credentials.js +57 -0
- package/dist/capture-request-plan.d.ts +58 -0
- package/dist/capture-request-plan.js +216 -0
- package/dist/capture-run-optimizer.d.ts +139 -0
- package/dist/capture-run-optimizer.js +848 -0
- package/dist/capture-selector-memory.d.ts +26 -0
- package/dist/capture-selector-memory.js +327 -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-studio-sync.d.ts +22 -0
- package/dist/capture-studio-sync.js +166 -0
- package/dist/capture-variant-state.d.ts +54 -0
- package/dist/capture-variant-state.js +156 -0
- package/dist/cli.js +21 -0
- package/dist/clip-orchestrator.d.ts +148 -0
- package/dist/clip-orchestrator.js +950 -0
- package/dist/clip-postprocess.d.ts +42 -0
- package/dist/clip-postprocess.js +192 -0
- package/dist/cost-logging.d.ts +27 -0
- package/dist/cost-logging.js +128 -0
- package/dist/credential-templates.d.ts +5 -0
- package/dist/credential-templates.js +60 -0
- package/dist/element-capture.d.ts +53 -0
- package/dist/element-capture.js +766 -0
- package/dist/hybrid-navigator.d.ts +138 -0
- package/dist/hybrid-navigator.js +468 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +11 -0
- package/dist/llm-usage.d.ts +17 -0
- package/dist/llm-usage.js +45 -0
- package/dist/mockup-html.d.ts +119 -0
- package/dist/mockup-html.js +253 -0
- package/dist/mockup.d.ts +94 -0
- package/dist/mockup.js +608 -0
- package/dist/mouse-animation.d.ts +46 -0
- package/dist/mouse-animation.js +100 -0
- package/dist/overlay-utils.d.ts +14 -0
- package/dist/overlay-utils.js +13 -0
- package/dist/posthog.d.ts +4 -0
- package/dist/posthog.js +26 -0
- package/dist/prompt-cache.d.ts +10 -0
- package/dist/prompt-cache.js +24 -0
- package/dist/prompts.d.ts +167 -0
- package/dist/prompts.js +1165 -0
- package/dist/remote-browser.d.ts +191 -0
- package/dist/remote-browser.js +305 -0
- package/dist/security.d.ts +20 -0
- package/dist/security.js +569 -0
- package/dist/server-capture-runtime.d.ts +123 -0
- package/dist/server-capture-runtime.js +638 -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 +45 -0
- package/dist/server-project-webhooks.js +97 -0
- package/dist/server-screenshot-watermark.d.ts +7 -0
- package/dist/server-screenshot-watermark.js +38 -0
- package/dist/session-profile.d.ts +86 -0
- package/dist/session-profile.js +1373 -0
- package/dist/sf-pro-fonts.d.ts +4 -0
- package/dist/sf-pro-fonts.js +7 -0
- package/dist/status-bar-l10n.d.ts +14 -0
- package/dist/status-bar-l10n.js +177 -0
- package/dist/status-bar.d.ts +44 -0
- package/dist/status-bar.js +336 -0
- package/dist/tools.d.ts +4 -0
- package/dist/tools.js +578 -0
- package/dist/video-agent.d.ts +143 -0
- package/dist/video-agent.js +4783 -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 +500 -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/ws-auth.d.ts +20 -0
- package/dist/ws-auth.js +67 -0
- package/dist/ws-handler.d.ts +10 -0
- package/dist/ws-handler.js +1663 -0
- package/dist/ws-server.d.ts +9 -0
- package/dist/ws-server.js +52 -0
- package/package.json +93 -39
package/dist/browser.js
CHANGED
|
@@ -366,15 +366,57 @@ export class Browser {
|
|
|
366
366
|
headless: !this.options.headed,
|
|
367
367
|
args: CHROMIUM_ARGS,
|
|
368
368
|
});
|
|
369
|
-
this.context = await this.browser.newContext(
|
|
370
|
-
viewport: this.options.viewport,
|
|
371
|
-
deviceScaleFactor: normalizeDeviceScaleFactor(this.options.deviceScaleFactor),
|
|
372
|
-
locale: langToLocale(this.options.lang ?? 'en'),
|
|
373
|
-
colorScheme: this.options.colorScheme ?? 'light',
|
|
374
|
-
storageState: this.options.storageState,
|
|
375
|
-
});
|
|
369
|
+
this.context = await this.browser.newContext(this.buildContextOptions());
|
|
376
370
|
this.page = await this.context.newPage();
|
|
377
371
|
}
|
|
372
|
+
async recreateContext(options = {}) {
|
|
373
|
+
if (this.poolContext) {
|
|
374
|
+
throw new Error('Cannot recreate the context for a pooled browser instance');
|
|
375
|
+
}
|
|
376
|
+
this.options = {
|
|
377
|
+
...this.options,
|
|
378
|
+
...options,
|
|
379
|
+
viewport: options.viewport ?? this.options.viewport,
|
|
380
|
+
deviceScaleFactor: options.deviceScaleFactor ?? this.options.deviceScaleFactor,
|
|
381
|
+
lang: options.lang ?? this.options.lang,
|
|
382
|
+
colorScheme: options.colorScheme ?? this.options.colorScheme,
|
|
383
|
+
storageState: options.storageState ?? this.options.storageState,
|
|
384
|
+
};
|
|
385
|
+
if (!this.browser) {
|
|
386
|
+
await this.launch();
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
if (this.page) {
|
|
390
|
+
try {
|
|
391
|
+
await this.page.close();
|
|
392
|
+
}
|
|
393
|
+
catch { /* ignore */ }
|
|
394
|
+
this.page = null;
|
|
395
|
+
}
|
|
396
|
+
if (this.context) {
|
|
397
|
+
try {
|
|
398
|
+
await this.context.close();
|
|
399
|
+
}
|
|
400
|
+
catch { /* ignore */ }
|
|
401
|
+
this.context = null;
|
|
402
|
+
}
|
|
403
|
+
this.context = await this.browser.newContext(this.buildContextOptions());
|
|
404
|
+
this.page = await this.context.newPage();
|
|
405
|
+
this.elementMap.clear();
|
|
406
|
+
}
|
|
407
|
+
async setDeviceScaleFactor(deviceScaleFactor) {
|
|
408
|
+
const normalizedScale = normalizeDeviceScaleFactor(deviceScaleFactor);
|
|
409
|
+
if (normalizeDeviceScaleFactor(this.options.deviceScaleFactor) === normalizedScale) {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
const sessionStorage = this.page
|
|
413
|
+
? await this.exportSessionStorage().catch(() => undefined)
|
|
414
|
+
: undefined;
|
|
415
|
+
await this.recreateContext({ deviceScaleFactor: normalizedScale });
|
|
416
|
+
if (sessionStorage && Object.keys(sessionStorage).length > 0) {
|
|
417
|
+
await this.prepareSessionStorage(sessionStorage, { replace: false });
|
|
418
|
+
}
|
|
419
|
+
}
|
|
378
420
|
async addCookies(cookies) {
|
|
379
421
|
const context = this.ensureContext();
|
|
380
422
|
await context.addCookies(cookies.map(c => ({
|
|
@@ -987,6 +1029,116 @@ export class Browser {
|
|
|
987
1029
|
[snapshot.origin]: snapshot.entries,
|
|
988
1030
|
};
|
|
989
1031
|
}
|
|
1032
|
+
async reloadCurrentPage(options = {}) {
|
|
1033
|
+
const page = this.ensurePage();
|
|
1034
|
+
await page.reload({
|
|
1035
|
+
waitUntil: options.waitUntil ?? 'domcontentloaded',
|
|
1036
|
+
timeout: options.timeout ?? 15_000,
|
|
1037
|
+
}).catch(async () => {
|
|
1038
|
+
await this.navigateTo(page.url());
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
async writeStorageHintCandidate(params) {
|
|
1042
|
+
const page = this.ensurePage();
|
|
1043
|
+
return page.evaluate(({ storageName, key, candidate, kind }) => {
|
|
1044
|
+
const storage = storageName === 'localStorage' ? window.localStorage : window.sessionStorage;
|
|
1045
|
+
const current = storage.getItem(key);
|
|
1046
|
+
if (current == null)
|
|
1047
|
+
return false;
|
|
1048
|
+
if (current === candidate)
|
|
1049
|
+
return true;
|
|
1050
|
+
const LOCALE_KEY_WHITELIST = ['lang', 'locale', 'language', 'i18n', 'intl', 'i18n-locale', 'next-i18next', 'NEXT_LOCALE', 'nuxt-i18n-locale'];
|
|
1051
|
+
const THEME_KEY_WHITELIST = ['theme', 'color-scheme', 'colorScheme', 'dark-mode', 'darkMode', 'appearance'];
|
|
1052
|
+
const whitelist = kind === 'locale' ? LOCALE_KEY_WHITELIST : THEME_KEY_WHITELIST;
|
|
1053
|
+
const isAllowedKey = (inputKey) => {
|
|
1054
|
+
const lower = inputKey.toLowerCase();
|
|
1055
|
+
return whitelist.some((entry) => {
|
|
1056
|
+
const normalized = entry.toLowerCase();
|
|
1057
|
+
if (lower === normalized)
|
|
1058
|
+
return true;
|
|
1059
|
+
const re = new RegExp(`(?:^|[^a-zA-Z0-9])${normalized.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')}(?:$|[^a-zA-Z0-9])`, 'i');
|
|
1060
|
+
return re.test(lower);
|
|
1061
|
+
});
|
|
1062
|
+
};
|
|
1063
|
+
const looksLikeTargetValue = (value) => {
|
|
1064
|
+
if (kind === 'locale') {
|
|
1065
|
+
return /^[a-z]{2,3}(-[a-zA-Z]{2,4})?$/.test(value.trim());
|
|
1066
|
+
}
|
|
1067
|
+
return /^(light|dark|auto|system|dim|high-contrast)$/i.test(value.trim());
|
|
1068
|
+
};
|
|
1069
|
+
const rewrite = (input, parentKeyAllowed = false) => {
|
|
1070
|
+
if (typeof input === 'string') {
|
|
1071
|
+
if (!looksLikeTargetValue(input))
|
|
1072
|
+
return { changed: false, value: input };
|
|
1073
|
+
return { changed: input !== candidate, value: candidate };
|
|
1074
|
+
}
|
|
1075
|
+
if (Array.isArray(input)) {
|
|
1076
|
+
if (!parentKeyAllowed)
|
|
1077
|
+
return { changed: false, value: input };
|
|
1078
|
+
let changed = false;
|
|
1079
|
+
const next = input.map((entry) => {
|
|
1080
|
+
const rewritten = rewrite(entry, true);
|
|
1081
|
+
changed = changed || rewritten.changed;
|
|
1082
|
+
return rewritten.value;
|
|
1083
|
+
});
|
|
1084
|
+
return { changed, value: next };
|
|
1085
|
+
}
|
|
1086
|
+
if (input && typeof input === 'object') {
|
|
1087
|
+
let changed = false;
|
|
1088
|
+
const next = {};
|
|
1089
|
+
for (const [entryKey, entryValue] of Object.entries(input)) {
|
|
1090
|
+
const keyAllowed = isAllowedKey(entryKey);
|
|
1091
|
+
if (keyAllowed && typeof entryValue === 'string' && looksLikeTargetValue(entryValue)) {
|
|
1092
|
+
if (entryValue !== candidate)
|
|
1093
|
+
changed = true;
|
|
1094
|
+
next[entryKey] = candidate;
|
|
1095
|
+
continue;
|
|
1096
|
+
}
|
|
1097
|
+
const rewritten = rewrite(entryValue, keyAllowed);
|
|
1098
|
+
changed = changed || rewritten.changed;
|
|
1099
|
+
next[entryKey] = rewritten.value;
|
|
1100
|
+
}
|
|
1101
|
+
return { changed, value: next };
|
|
1102
|
+
}
|
|
1103
|
+
return { changed: false, value: input };
|
|
1104
|
+
};
|
|
1105
|
+
let nextValue = candidate;
|
|
1106
|
+
try {
|
|
1107
|
+
const parsed = JSON.parse(current);
|
|
1108
|
+
const rewritten = rewrite(parsed);
|
|
1109
|
+
if (rewritten.changed) {
|
|
1110
|
+
nextValue = JSON.stringify(rewritten.value);
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
catch {
|
|
1114
|
+
nextValue = candidate;
|
|
1115
|
+
}
|
|
1116
|
+
storage.setItem(key, nextValue);
|
|
1117
|
+
return true;
|
|
1118
|
+
}, params);
|
|
1119
|
+
}
|
|
1120
|
+
async probeSelector(selector) {
|
|
1121
|
+
const page = this.ensurePage();
|
|
1122
|
+
try {
|
|
1123
|
+
return await page.locator(selector).first().evaluate((node) => ({
|
|
1124
|
+
tag: node.tagName.toLowerCase(),
|
|
1125
|
+
role: node.getAttribute('role') || '',
|
|
1126
|
+
href: node instanceof HTMLAnchorElement ? node.href : node.getAttribute('href'),
|
|
1127
|
+
label: (node.getAttribute('aria-label')
|
|
1128
|
+
|| node.getAttribute('title')
|
|
1129
|
+
|| node.innerText
|
|
1130
|
+
|| node.textContent
|
|
1131
|
+
|| '').replace(/\s+/g, ' ').trim().slice(0, 120),
|
|
1132
|
+
inputType: node instanceof HTMLInputElement ? node.type : node.getAttribute('type'),
|
|
1133
|
+
ariaExpanded: node.getAttribute('aria-expanded'),
|
|
1134
|
+
ariaControls: node.getAttribute('aria-controls'),
|
|
1135
|
+
ariaHasPopup: node.getAttribute('aria-haspopup'),
|
|
1136
|
+
}));
|
|
1137
|
+
}
|
|
1138
|
+
catch {
|
|
1139
|
+
return null;
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
990
1142
|
async prepareSessionStorage(bundle, options = {}) {
|
|
991
1143
|
if (!bundle || Object.keys(bundle).length === 0)
|
|
992
1144
|
return;
|
|
@@ -2797,9 +2949,16 @@ export class Browser {
|
|
|
2797
2949
|
}
|
|
2798
2950
|
async resizeViewport(width, height) {
|
|
2799
2951
|
const page = this.ensurePage();
|
|
2952
|
+
this.options = {
|
|
2953
|
+
...this.options,
|
|
2954
|
+
viewport: {
|
|
2955
|
+
width: normalizeViewportDimension(width),
|
|
2956
|
+
height: normalizeViewportDimension(height),
|
|
2957
|
+
},
|
|
2958
|
+
};
|
|
2800
2959
|
await page.setViewportSize({
|
|
2801
|
-
width:
|
|
2802
|
-
height:
|
|
2960
|
+
width: this.options.viewport.width,
|
|
2961
|
+
height: this.options.viewport.height,
|
|
2803
2962
|
});
|
|
2804
2963
|
}
|
|
2805
2964
|
get currentPage() {
|
|
@@ -2898,5 +3057,14 @@ export class Browser {
|
|
|
2898
3057
|
throw new Error('Browser not launched. Call launch() first.');
|
|
2899
3058
|
return this.context;
|
|
2900
3059
|
}
|
|
3060
|
+
buildContextOptions() {
|
|
3061
|
+
return {
|
|
3062
|
+
viewport: this.options.viewport,
|
|
3063
|
+
deviceScaleFactor: normalizeDeviceScaleFactor(this.options.deviceScaleFactor),
|
|
3064
|
+
locale: langToLocale(this.options.lang ?? 'en'),
|
|
3065
|
+
colorScheme: this.options.colorScheme ?? 'light',
|
|
3066
|
+
storageState: this.options.storageState,
|
|
3067
|
+
};
|
|
3068
|
+
}
|
|
2901
3069
|
}
|
|
2902
3070
|
//# sourceMappingURL=browser.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
interface GenerateAltTextParams {
|
|
2
|
+
url: string;
|
|
3
|
+
prompt: string;
|
|
4
|
+
lang: string;
|
|
5
|
+
theme: 'light' | 'dark';
|
|
6
|
+
targetLabel?: string;
|
|
7
|
+
elementName?: string;
|
|
8
|
+
model: string;
|
|
9
|
+
apiKey: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function generateAltText(params: GenerateAltTextParams): Promise<string | null>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export async function generateAltText(params) {
|
|
2
|
+
const { url, prompt, lang, theme, targetLabel, elementName, model, apiKey } = params;
|
|
3
|
+
const contextParts = [
|
|
4
|
+
`URL: ${url}`,
|
|
5
|
+
`Capture intent: "${prompt}"`,
|
|
6
|
+
`Theme: ${theme}`,
|
|
7
|
+
];
|
|
8
|
+
if (targetLabel)
|
|
9
|
+
contextParts.push(`Device: ${targetLabel}`);
|
|
10
|
+
if (elementName)
|
|
11
|
+
contextParts.push(`Isolated element: ${elementName}`);
|
|
12
|
+
const systemPrompt = `You generate concise alt text for web page screenshots, for accessibility purposes.
|
|
13
|
+
Write in ${lang}. Output ONLY the alt text, nothing else. Keep it under 150 characters.
|
|
14
|
+
Describe what the screenshot represents based on the context provided.`;
|
|
15
|
+
try {
|
|
16
|
+
const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
|
|
17
|
+
method: 'POST',
|
|
18
|
+
headers: {
|
|
19
|
+
Authorization: `Bearer ${apiKey}`,
|
|
20
|
+
'Content-Type': 'application/json',
|
|
21
|
+
'HTTP-Referer': 'https://autokap.app',
|
|
22
|
+
'X-Title': 'Screenshot Agent',
|
|
23
|
+
},
|
|
24
|
+
body: JSON.stringify({
|
|
25
|
+
model,
|
|
26
|
+
max_tokens: 100,
|
|
27
|
+
stream: false,
|
|
28
|
+
messages: [
|
|
29
|
+
{ role: 'system', content: systemPrompt },
|
|
30
|
+
{ role: 'user', content: contextParts.join('\n') },
|
|
31
|
+
],
|
|
32
|
+
provider: { zdr: true },
|
|
33
|
+
}),
|
|
34
|
+
signal: AbortSignal.timeout(5_000),
|
|
35
|
+
});
|
|
36
|
+
if (!res.ok) {
|
|
37
|
+
console.warn(`[generate-alt-text] LLM call failed: HTTP ${res.status}`);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const json = (await res.json());
|
|
41
|
+
const content = json.choices?.[0]?.message?.content?.trim();
|
|
42
|
+
if (!content)
|
|
43
|
+
return null;
|
|
44
|
+
return content.replace(/^["']|["']$/g, '');
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
console.warn('[generate-alt-text] ALT text generation failed');
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=capture-alt-text.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface EncryptedEnvelope {
|
|
2
|
+
__encrypted: true;
|
|
3
|
+
version: 1;
|
|
4
|
+
iv: string;
|
|
5
|
+
tag: string;
|
|
6
|
+
ciphertext: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function encrypt(plaintext: string, secret: string): EncryptedEnvelope;
|
|
9
|
+
export declare function decrypt(envelope: EncryptedEnvelope, secret: string): string;
|
|
10
|
+
export declare function isEncryptedEnvelope(value: unknown): value is EncryptedEnvelope;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
function deriveKey(secret) {
|
|
3
|
+
return crypto.createHash('sha256').update(secret).digest();
|
|
4
|
+
}
|
|
5
|
+
export function encrypt(plaintext, secret) {
|
|
6
|
+
const key = deriveKey(secret);
|
|
7
|
+
const iv = crypto.randomBytes(12);
|
|
8
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
|
|
9
|
+
const ciphertext = Buffer.concat([
|
|
10
|
+
cipher.update(plaintext, 'utf8'),
|
|
11
|
+
cipher.final(),
|
|
12
|
+
]);
|
|
13
|
+
const tag = cipher.getAuthTag();
|
|
14
|
+
return {
|
|
15
|
+
__encrypted: true,
|
|
16
|
+
version: 1,
|
|
17
|
+
iv: iv.toString('base64'),
|
|
18
|
+
tag: tag.toString('base64'),
|
|
19
|
+
ciphertext: ciphertext.toString('base64'),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function decrypt(envelope, secret) {
|
|
23
|
+
const key = deriveKey(secret);
|
|
24
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', key, Buffer.from(envelope.iv, 'base64'));
|
|
25
|
+
decipher.setAuthTag(Buffer.from(envelope.tag, 'base64'));
|
|
26
|
+
return Buffer.concat([
|
|
27
|
+
decipher.update(Buffer.from(envelope.ciphertext, 'base64')),
|
|
28
|
+
decipher.final(),
|
|
29
|
+
]).toString('utf8');
|
|
30
|
+
}
|
|
31
|
+
export function isEncryptedEnvelope(value) {
|
|
32
|
+
if (!value || typeof value !== 'object')
|
|
33
|
+
return false;
|
|
34
|
+
const candidate = value;
|
|
35
|
+
return (candidate.__encrypted === true
|
|
36
|
+
&& candidate.version === 1
|
|
37
|
+
&& typeof candidate.iv === 'string'
|
|
38
|
+
&& typeof candidate.tag === 'string'
|
|
39
|
+
&& typeof candidate.ciphertext === 'string');
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=capture-encryption.js.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Browser } from "./browser.js";
|
|
2
|
+
import { type RequestedLanguageState, type RequestedThemeState } from "./session-profile.js";
|
|
3
|
+
import type { AgentResult, LoginCredentials, StepUsage, ValidatedSessionProfile, VideoPageSignals } from "./types.js";
|
|
4
|
+
import { type ScreenshotSelectorMemoryUpdate } from "./capture-selector-memory.js";
|
|
5
|
+
interface LanguageObservation {
|
|
6
|
+
currentUrl: string;
|
|
7
|
+
signals: VideoPageSignals | null;
|
|
8
|
+
languageState: RequestedLanguageState | null;
|
|
9
|
+
themeState: RequestedThemeState | null;
|
|
10
|
+
}
|
|
11
|
+
export interface ScreenshotLanguagePreflightResult {
|
|
12
|
+
ok: boolean;
|
|
13
|
+
resolvedBy: "already_active" | "deterministic_repair" | "language_preflight_agent" | "blocked";
|
|
14
|
+
observation: LanguageObservation;
|
|
15
|
+
usage: StepUsage[];
|
|
16
|
+
selectorUpdates: ScreenshotSelectorMemoryUpdate[];
|
|
17
|
+
rebasedToStartUrl: boolean;
|
|
18
|
+
reason?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function ensureScreenshotVariantLanguage(params: {
|
|
21
|
+
browser: Browser;
|
|
22
|
+
requestedLang: string;
|
|
23
|
+
requestedTheme: "light" | "dark";
|
|
24
|
+
startUrl?: string;
|
|
25
|
+
profile?: ValidatedSessionProfile;
|
|
26
|
+
credentials?: LoginCredentials;
|
|
27
|
+
selectorMemory?: Record<string, string[]>;
|
|
28
|
+
langInstructions?: string;
|
|
29
|
+
themeInstructions?: string;
|
|
30
|
+
onLog?: (message: string) => void;
|
|
31
|
+
performRepair?: () => Promise<{
|
|
32
|
+
repaired: boolean;
|
|
33
|
+
updates: ScreenshotSelectorMemoryUpdate[];
|
|
34
|
+
} | null>;
|
|
35
|
+
rebaseToStartUrl?: (currentUrl: string, startUrl: string) => Promise<void>;
|
|
36
|
+
runLanguageSwitchAgent?: (state: {
|
|
37
|
+
languageState: RequestedLanguageState | null;
|
|
38
|
+
themeState: RequestedThemeState | null;
|
|
39
|
+
}) => Promise<AgentResult | null>;
|
|
40
|
+
}): Promise<ScreenshotLanguagePreflightResult>;
|
|
41
|
+
export {};
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { evaluateRequestedLanguageState, evaluateRequestedThemeState, performDeterministicSessionRepair, } from "./session-profile.js";
|
|
2
|
+
import { extractSelectorUpdates, } from "./capture-selector-memory.js";
|
|
3
|
+
import { isCaptureStepTimeoutError, withCaptureStepTimeout, } from "./capture-step-timeout.js";
|
|
4
|
+
import { urlMatchesCaptureTarget } from "./capture-run-optimizer.js";
|
|
5
|
+
const LANGUAGE_OBSERVATION_TIMEOUT_MS = 12000;
|
|
6
|
+
const LANGUAGE_REPAIR_TIMEOUT_MS = 15000;
|
|
7
|
+
const LANGUAGE_STABILITY_DELAY_MS = 900;
|
|
8
|
+
function urlsRoughlyMatch(expectedUrl, currentUrl) {
|
|
9
|
+
if (!expectedUrl || !currentUrl)
|
|
10
|
+
return false;
|
|
11
|
+
return urlMatchesCaptureTarget(currentUrl, expectedUrl) || urlMatchesCaptureTarget(expectedUrl, currentUrl);
|
|
12
|
+
}
|
|
13
|
+
async function waitForVariantStability(browser, delayMs) {
|
|
14
|
+
if (typeof browser.wait === "function") {
|
|
15
|
+
await browser.wait(delayMs);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const pageWait = browser.currentPage.waitForTimeout;
|
|
19
|
+
if (typeof pageWait === "function") {
|
|
20
|
+
await pageWait.call(browser.currentPage, delayMs);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
24
|
+
}
|
|
25
|
+
function summarizeRequestedLanguageState(state) {
|
|
26
|
+
if (!state)
|
|
27
|
+
return "language_state=unavailable";
|
|
28
|
+
return [
|
|
29
|
+
state.requested ? `requested=${state.requested}` : null,
|
|
30
|
+
state.detected ? `detected=${state.detected}` : "detected=unknown",
|
|
31
|
+
`ambiguous=${state.ambiguous}`,
|
|
32
|
+
...state.reasons.slice(0, 3),
|
|
33
|
+
]
|
|
34
|
+
.filter(Boolean)
|
|
35
|
+
.join("; ");
|
|
36
|
+
}
|
|
37
|
+
function summarizeRequestedThemeState(state) {
|
|
38
|
+
if (!state)
|
|
39
|
+
return "theme_state=unavailable";
|
|
40
|
+
return [
|
|
41
|
+
state.requested ? `requested=${state.requested}` : null,
|
|
42
|
+
state.detected ? `detected=${state.detected}` : "detected=unknown",
|
|
43
|
+
`ambiguous=${state.ambiguous}`,
|
|
44
|
+
...state.reasons.slice(0, 3),
|
|
45
|
+
]
|
|
46
|
+
.filter(Boolean)
|
|
47
|
+
.join("; ");
|
|
48
|
+
}
|
|
49
|
+
function summarizeVariantObservation(observation) {
|
|
50
|
+
return [
|
|
51
|
+
summarizeRequestedLanguageState(observation.languageState),
|
|
52
|
+
summarizeRequestedThemeState(observation.themeState),
|
|
53
|
+
].join(" | ");
|
|
54
|
+
}
|
|
55
|
+
function isObservationReady(observation) {
|
|
56
|
+
const languageReady = !!(observation.languageState?.active
|
|
57
|
+
&& !observation.languageState.ambiguous);
|
|
58
|
+
const themeReady = !!(observation.themeState?.active
|
|
59
|
+
&& !observation.themeState.ambiguous);
|
|
60
|
+
return languageReady && themeReady;
|
|
61
|
+
}
|
|
62
|
+
function buildFailureReason(requestedLang, requestedTheme, observation) {
|
|
63
|
+
const failures = [];
|
|
64
|
+
if (!observation.languageState) {
|
|
65
|
+
failures.push(`unable to inspect the current UI language for "${requestedLang}"`);
|
|
66
|
+
}
|
|
67
|
+
else if (!observation.languageState.active || observation.languageState.ambiguous) {
|
|
68
|
+
failures.push(`requested "${requestedLang}", detected "${observation.languageState.detected ?? "unknown"}" (${observation.languageState.reasons.join("; ")})`);
|
|
69
|
+
}
|
|
70
|
+
if (!observation.themeState) {
|
|
71
|
+
failures.push(`unable to inspect the current UI theme for "${requestedTheme}"`);
|
|
72
|
+
}
|
|
73
|
+
else if (!observation.themeState.active || observation.themeState.ambiguous) {
|
|
74
|
+
failures.push(`theme requested "${requestedTheme}", detected "${observation.themeState.detected ?? "unknown"}" (${observation.themeState.reasons.join("; ")})`);
|
|
75
|
+
}
|
|
76
|
+
if (failures.length === 0) {
|
|
77
|
+
return "";
|
|
78
|
+
}
|
|
79
|
+
return `Language preflight failed: ${failures.join(" | ")}.`;
|
|
80
|
+
}
|
|
81
|
+
async function observeLanguageState(browser, requestedLang, params) {
|
|
82
|
+
const currentUrl = browser.currentPage.url();
|
|
83
|
+
const phase = params?.phase ?? "inspection";
|
|
84
|
+
params?.onLog?.(`Language guard: inspecting fixed UI chrome during ${phase}.`);
|
|
85
|
+
const signals = await withCaptureStepTimeout(() => browser.capturePageSignals(), {
|
|
86
|
+
stepLabel: `inspecting fixed UI chrome during ${phase}`,
|
|
87
|
+
timeoutMs: LANGUAGE_OBSERVATION_TIMEOUT_MS,
|
|
88
|
+
}).catch((error) => {
|
|
89
|
+
if (isCaptureStepTimeoutError(error)) {
|
|
90
|
+
params?.onLog?.(`Language guard: fixed UI inspection timed out after ${error.timeoutMs}ms during ${phase}.`);
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
params?.onLog?.(`Language guard: fixed UI inspection failed during ${phase} (${error instanceof Error ? error.message : String(error)}).`);
|
|
94
|
+
return null;
|
|
95
|
+
});
|
|
96
|
+
return {
|
|
97
|
+
currentUrl,
|
|
98
|
+
signals,
|
|
99
|
+
languageState: signals
|
|
100
|
+
? evaluateRequestedLanguageState({
|
|
101
|
+
currentUrl,
|
|
102
|
+
requestedLang,
|
|
103
|
+
signals,
|
|
104
|
+
})
|
|
105
|
+
: null,
|
|
106
|
+
themeState: signals
|
|
107
|
+
? evaluateRequestedThemeState({
|
|
108
|
+
requestedTheme: params?.requestedTheme,
|
|
109
|
+
signals,
|
|
110
|
+
})
|
|
111
|
+
: null,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
async function confirmStableLanguageState(params) {
|
|
115
|
+
if (!isObservationReady(params.observation)) {
|
|
116
|
+
return params.observation;
|
|
117
|
+
}
|
|
118
|
+
params.onLog?.(`Language guard: waiting ${LANGUAGE_STABILITY_DELAY_MS}ms to confirm the variant remains stable during ${params.phase}.`);
|
|
119
|
+
await waitForVariantStability(params.browser, LANGUAGE_STABILITY_DELAY_MS);
|
|
120
|
+
return observeLanguageState(params.browser, params.requestedLang, {
|
|
121
|
+
onLog: params.onLog,
|
|
122
|
+
phase: `${params.phase} stability verification`,
|
|
123
|
+
requestedTheme: params.requestedTheme,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
export async function ensureScreenshotVariantLanguage(params) {
|
|
127
|
+
const usage = [];
|
|
128
|
+
const selectorUpdates = [];
|
|
129
|
+
const requestedLang = params.requestedLang.trim().toLowerCase();
|
|
130
|
+
let rebasedToStartUrl = false;
|
|
131
|
+
const rebaseIfNeeded = async (observation) => {
|
|
132
|
+
if (!params.startUrl
|
|
133
|
+
|| !params.rebaseToStartUrl
|
|
134
|
+
|| urlsRoughlyMatch(params.startUrl, observation.currentUrl)) {
|
|
135
|
+
return observation;
|
|
136
|
+
}
|
|
137
|
+
params.onLog?.(`Language guard: rebasing back to ${params.startUrl} after language switch from ${observation.currentUrl}.`);
|
|
138
|
+
await params.rebaseToStartUrl(observation.currentUrl, params.startUrl);
|
|
139
|
+
rebasedToStartUrl = true;
|
|
140
|
+
return observeLanguageState(params.browser, requestedLang, {
|
|
141
|
+
onLog: params.onLog,
|
|
142
|
+
phase: "post-rebase verification",
|
|
143
|
+
requestedTheme: params.requestedTheme,
|
|
144
|
+
});
|
|
145
|
+
};
|
|
146
|
+
let observation = await observeLanguageState(params.browser, requestedLang, {
|
|
147
|
+
onLog: params.onLog,
|
|
148
|
+
phase: "initial preflight",
|
|
149
|
+
requestedTheme: params.requestedTheme,
|
|
150
|
+
});
|
|
151
|
+
if (isObservationReady(observation)) {
|
|
152
|
+
return {
|
|
153
|
+
ok: true,
|
|
154
|
+
resolvedBy: "already_active",
|
|
155
|
+
observation,
|
|
156
|
+
usage,
|
|
157
|
+
selectorUpdates,
|
|
158
|
+
rebasedToStartUrl,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
params.onLog?.(`Language guard: requested variant "${requestedLang}/${params.requestedTheme}" is not active yet; ${summarizeVariantObservation(observation)}.`);
|
|
162
|
+
params.onLog?.("Language guard: running deterministic variant repair before the main workflow.");
|
|
163
|
+
const repair = await withCaptureStepTimeout(async () => (params.performRepair
|
|
164
|
+
?? (async () => {
|
|
165
|
+
const result = await performDeterministicSessionRepair(params.browser, {
|
|
166
|
+
startUrl: params.startUrl,
|
|
167
|
+
requestedLang,
|
|
168
|
+
requestedTheme: params.requestedTheme,
|
|
169
|
+
credentials: params.credentials,
|
|
170
|
+
profile: params.profile,
|
|
171
|
+
selectorMemory: params.selectorMemory,
|
|
172
|
+
}).catch(() => null);
|
|
173
|
+
return result
|
|
174
|
+
? {
|
|
175
|
+
repaired: result.repaired,
|
|
176
|
+
updates: result.updates.map((update) => ({
|
|
177
|
+
...update,
|
|
178
|
+
source: "deterministic",
|
|
179
|
+
})),
|
|
180
|
+
}
|
|
181
|
+
: null;
|
|
182
|
+
}))(), {
|
|
183
|
+
stepLabel: "running deterministic variant repair",
|
|
184
|
+
timeoutMs: LANGUAGE_REPAIR_TIMEOUT_MS,
|
|
185
|
+
}).catch((error) => {
|
|
186
|
+
if (isCaptureStepTimeoutError(error)) {
|
|
187
|
+
params.onLog?.(`Language guard: deterministic repair timed out after ${error.timeoutMs}ms.`);
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
params.onLog?.(`Language guard: deterministic repair failed (${error instanceof Error ? error.message : String(error)}).`);
|
|
191
|
+
return null;
|
|
192
|
+
});
|
|
193
|
+
if (repair?.updates.length) {
|
|
194
|
+
selectorUpdates.push(...repair.updates);
|
|
195
|
+
}
|
|
196
|
+
observation = await observeLanguageState(params.browser, requestedLang, {
|
|
197
|
+
onLog: params.onLog,
|
|
198
|
+
phase: "post-repair verification",
|
|
199
|
+
requestedTheme: params.requestedTheme,
|
|
200
|
+
});
|
|
201
|
+
if (isObservationReady(observation)) {
|
|
202
|
+
observation = await rebaseIfNeeded(observation);
|
|
203
|
+
observation = await confirmStableLanguageState({
|
|
204
|
+
browser: params.browser,
|
|
205
|
+
requestedLang,
|
|
206
|
+
requestedTheme: params.requestedTheme,
|
|
207
|
+
observation,
|
|
208
|
+
onLog: params.onLog,
|
|
209
|
+
phase: "post-deterministic-repair",
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
if (isObservationReady(observation)) {
|
|
213
|
+
params.onLog?.(`Language guard: fixed UI variant switched successfully after deterministic repair; ${summarizeVariantObservation(observation)}.`);
|
|
214
|
+
return {
|
|
215
|
+
ok: true,
|
|
216
|
+
resolvedBy: "deterministic_repair",
|
|
217
|
+
observation,
|
|
218
|
+
usage,
|
|
219
|
+
selectorUpdates,
|
|
220
|
+
rebasedToStartUrl,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
if (!params.runLanguageSwitchAgent) {
|
|
224
|
+
const reason = buildFailureReason(requestedLang, params.requestedTheme, observation);
|
|
225
|
+
params.onLog?.(`Language guard: blocking variant before main workflow. ${reason}`);
|
|
226
|
+
return {
|
|
227
|
+
ok: false,
|
|
228
|
+
resolvedBy: "blocked",
|
|
229
|
+
observation,
|
|
230
|
+
usage,
|
|
231
|
+
selectorUpdates,
|
|
232
|
+
rebasedToStartUrl,
|
|
233
|
+
reason,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
params.onLog?.(`Language guard: deterministic repair was insufficient; launching dedicated variant preflight. ${summarizeVariantObservation(observation)}.`);
|
|
237
|
+
const preflightAgentResult = await params.runLanguageSwitchAgent({
|
|
238
|
+
languageState: observation.languageState,
|
|
239
|
+
themeState: observation.themeState,
|
|
240
|
+
}).catch(() => null);
|
|
241
|
+
if (preflightAgentResult?.usage?.length) {
|
|
242
|
+
usage.push(...preflightAgentResult.usage);
|
|
243
|
+
}
|
|
244
|
+
if (preflightAgentResult?.actions?.length) {
|
|
245
|
+
selectorUpdates.push(...extractSelectorUpdates(preflightAgentResult.actions));
|
|
246
|
+
}
|
|
247
|
+
observation = await observeLanguageState(params.browser, requestedLang, {
|
|
248
|
+
onLog: params.onLog,
|
|
249
|
+
phase: "post-variant-switch verification",
|
|
250
|
+
requestedTheme: params.requestedTheme,
|
|
251
|
+
});
|
|
252
|
+
if (isObservationReady(observation)) {
|
|
253
|
+
observation = await rebaseIfNeeded(observation);
|
|
254
|
+
observation = await confirmStableLanguageState({
|
|
255
|
+
browser: params.browser,
|
|
256
|
+
requestedLang,
|
|
257
|
+
requestedTheme: params.requestedTheme,
|
|
258
|
+
observation,
|
|
259
|
+
onLog: params.onLog,
|
|
260
|
+
phase: "post-variant-switch",
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
if (isObservationReady(observation)) {
|
|
264
|
+
params.onLog?.(`Language guard: fixed UI variant switched successfully after dedicated variant preflight. ${summarizeVariantObservation(observation)}.`);
|
|
265
|
+
return {
|
|
266
|
+
ok: true,
|
|
267
|
+
resolvedBy: "language_preflight_agent",
|
|
268
|
+
observation,
|
|
269
|
+
usage,
|
|
270
|
+
selectorUpdates,
|
|
271
|
+
rebasedToStartUrl,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
const reason = buildFailureReason(requestedLang, params.requestedTheme, observation);
|
|
275
|
+
params.onLog?.(`Language guard: blocking variant before main workflow. ${reason}`);
|
|
276
|
+
return {
|
|
277
|
+
ok: false,
|
|
278
|
+
resolvedBy: "blocked",
|
|
279
|
+
observation,
|
|
280
|
+
usage,
|
|
281
|
+
selectorUpdates,
|
|
282
|
+
rebasedToStartUrl,
|
|
283
|
+
reason,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
//# sourceMappingURL=capture-language-preflight.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { CapturePageIdentity, StepUsage } from "./types.js";
|
|
2
|
+
import type { ScreenshotPageRunInput } from "./capture-run-optimizer.js";
|
|
3
|
+
export interface PageIdentityInferenceResult {
|
|
4
|
+
identities: Record<string, CapturePageIdentity>;
|
|
5
|
+
usage: StepUsage | null;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Use a cheap LLM call to classify all pages in a capture run.
|
|
9
|
+
* Falls back to regex heuristics if the LLM call fails.
|
|
10
|
+
*/
|
|
11
|
+
export declare function inferPageIdentitiesWithLLM(pageRuns: ScreenshotPageRunInput[], model: string, apiKey: string, providerPrefs?: Record<string, {
|
|
12
|
+
order?: string[];
|
|
13
|
+
require?: string[];
|
|
14
|
+
disallow?: string[];
|
|
15
|
+
}>): Promise<PageIdentityInferenceResult>;
|