autokap 1.0.6 → 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.
Files changed (130) hide show
  1. package/assets/chrome/ios-statusbar-comparison-reference.jpg +0 -0
  2. package/assets/chrome/ios-statusbar-dark-reference.jpg +0 -0
  3. package/assets/chrome/ios-statusbar-light-reference.jpg +0 -0
  4. package/assets/devices/ipad-pro-11-m4.json +52 -0
  5. package/assets/devices/iphone-16-pro.json +53 -0
  6. package/assets/devices/macbook-air-13.json +45 -0
  7. package/assets/frames/MacBook Air 13.svg +242 -0
  8. package/assets/frames/Status bar - iPhone.png +0 -0
  9. Menu bar- iPad.png +0 -0
  10. package/assets/frames/iPad Pro M4 11_.png +0 -0
  11. package/assets/frames/iPhone 16 Pro.png +0 -0
  12. package/assets/icons/Cellular Connection.svg +3 -0
  13. package/assets/icons/Union.svg +6 -0
  14. package/assets/icons/Wifi.svg +3 -0
  15. package/assets/icons/battery.svg +5 -0
  16. package/assets/icons/battery_charging.svg +8 -0
  17. package/dist/abort.d.ts +5 -0
  18. package/dist/abort.js +44 -0
  19. package/dist/agent.d.ts +142 -0
  20. package/dist/agent.js +4511 -0
  21. package/dist/billing-operation-logging.d.ts +38 -0
  22. package/dist/billing-operation-logging.js +248 -0
  23. package/dist/browser-bar.d.ts +40 -0
  24. package/dist/browser-bar.js +147 -0
  25. package/dist/browser.d.ts +25 -0
  26. package/dist/browser.js +177 -9
  27. package/dist/capture-alt-text.d.ts +12 -0
  28. package/dist/capture-alt-text.js +51 -0
  29. package/dist/capture-encryption.d.ts +10 -0
  30. package/dist/capture-encryption.js +41 -0
  31. package/dist/capture-language-preflight.d.ts +41 -0
  32. package/dist/capture-language-preflight.js +286 -0
  33. package/dist/capture-llm-page-identity.d.ts +15 -0
  34. package/dist/capture-llm-page-identity.js +116 -0
  35. package/dist/capture-model-resolution.d.ts +9 -0
  36. package/dist/capture-model-resolution.js +21 -0
  37. package/dist/capture-page-identity.d.ts +9 -0
  38. package/dist/capture-page-identity.js +219 -0
  39. package/dist/capture-preset-credentials.d.ts +12 -0
  40. package/dist/capture-preset-credentials.js +57 -0
  41. package/dist/capture-request-plan.d.ts +58 -0
  42. package/dist/capture-request-plan.js +216 -0
  43. package/dist/capture-run-optimizer.d.ts +139 -0
  44. package/dist/capture-run-optimizer.js +848 -0
  45. package/dist/capture-selector-memory.d.ts +26 -0
  46. package/dist/capture-selector-memory.js +327 -0
  47. package/dist/capture-session-profile-encryption.d.ts +2 -0
  48. package/dist/capture-session-profile-encryption.js +22 -0
  49. package/dist/capture-step-timeout.d.ts +10 -0
  50. package/dist/capture-step-timeout.js +30 -0
  51. package/dist/capture-studio-sync.d.ts +22 -0
  52. package/dist/capture-studio-sync.js +166 -0
  53. package/dist/capture-variant-state.d.ts +54 -0
  54. package/dist/capture-variant-state.js +156 -0
  55. package/dist/cli.js +15 -0
  56. package/dist/clip-orchestrator.d.ts +148 -0
  57. package/dist/clip-orchestrator.js +950 -0
  58. package/dist/clip-postprocess.d.ts +42 -0
  59. package/dist/clip-postprocess.js +192 -0
  60. package/dist/cost-logging.d.ts +27 -0
  61. package/dist/cost-logging.js +128 -0
  62. package/dist/credential-templates.d.ts +5 -0
  63. package/dist/credential-templates.js +60 -0
  64. package/dist/element-capture.d.ts +53 -0
  65. package/dist/element-capture.js +766 -0
  66. package/dist/hybrid-navigator.d.ts +138 -0
  67. package/dist/hybrid-navigator.js +468 -0
  68. package/dist/index.d.ts +15 -0
  69. package/dist/index.js +11 -0
  70. package/dist/llm-usage.d.ts +17 -0
  71. package/dist/llm-usage.js +45 -0
  72. package/dist/mockup-html.d.ts +119 -0
  73. package/dist/mockup-html.js +253 -0
  74. package/dist/mockup.d.ts +94 -0
  75. package/dist/mockup.js +608 -0
  76. package/dist/mouse-animation.d.ts +46 -0
  77. package/dist/mouse-animation.js +100 -0
  78. package/dist/overlay-utils.d.ts +14 -0
  79. package/dist/overlay-utils.js +13 -0
  80. package/dist/posthog.d.ts +4 -0
  81. package/dist/posthog.js +26 -0
  82. package/dist/prompt-cache.d.ts +10 -0
  83. package/dist/prompt-cache.js +24 -0
  84. package/dist/prompts.d.ts +167 -0
  85. package/dist/prompts.js +1165 -0
  86. package/dist/remote-browser.d.ts +191 -0
  87. package/dist/remote-browser.js +305 -0
  88. package/dist/security.d.ts +20 -0
  89. package/dist/security.js +569 -0
  90. package/dist/server-capture-runtime.d.ts +123 -0
  91. package/dist/server-capture-runtime.js +638 -0
  92. package/dist/server-credit-usage.d.ts +12 -0
  93. package/dist/server-credit-usage.js +41 -0
  94. package/dist/server-posthog.d.ts +2 -0
  95. package/dist/server-posthog.js +16 -0
  96. package/dist/server-project-webhooks.d.ts +45 -0
  97. package/dist/server-project-webhooks.js +97 -0
  98. package/dist/server-screenshot-watermark.d.ts +7 -0
  99. package/dist/server-screenshot-watermark.js +38 -0
  100. package/dist/session-profile.d.ts +86 -0
  101. package/dist/session-profile.js +1373 -0
  102. package/dist/sf-pro-fonts.d.ts +4 -0
  103. package/dist/sf-pro-fonts.js +7 -0
  104. package/dist/status-bar-l10n.d.ts +14 -0
  105. package/dist/status-bar-l10n.js +177 -0
  106. package/dist/status-bar.d.ts +44 -0
  107. package/dist/status-bar.js +336 -0
  108. package/dist/tools.d.ts +4 -0
  109. package/dist/tools.js +578 -0
  110. package/dist/video-agent.d.ts +143 -0
  111. package/dist/video-agent.js +4783 -0
  112. package/dist/video-observation.d.ts +36 -0
  113. package/dist/video-observation.js +192 -0
  114. package/dist/video-planner.d.ts +12 -0
  115. package/dist/video-planner.js +500 -0
  116. package/dist/video-prompts.d.ts +37 -0
  117. package/dist/video-prompts.js +554 -0
  118. package/dist/video-tools.d.ts +3 -0
  119. package/dist/video-tools.js +59 -0
  120. package/dist/video-variant-state.d.ts +29 -0
  121. package/dist/video-variant-state.js +80 -0
  122. package/dist/vision-model.d.ts +17 -0
  123. package/dist/vision-model.js +74 -0
  124. package/dist/ws-auth.d.ts +20 -0
  125. package/dist/ws-auth.js +67 -0
  126. package/dist/ws-handler.d.ts +10 -0
  127. package/dist/ws-handler.js +1663 -0
  128. package/dist/ws-server.d.ts +9 -0
  129. package/dist/ws-server.js +52 -0
  130. package/package.json +93 -39
@@ -0,0 +1,848 @@
1
+ const GENERIC_NAV_TOKENS = new Set([
2
+ "page",
3
+ "pages",
4
+ "open",
5
+ "view",
6
+ "link",
7
+ "button",
8
+ "click",
9
+ "menu",
10
+ "nav",
11
+ "navigation",
12
+ "section",
13
+ "screen",
14
+ "capture",
15
+ "prepare",
16
+ "show",
17
+ "the",
18
+ "and",
19
+ "for",
20
+ "with",
21
+ ]);
22
+ const ELEMENT_ASSIGNMENT_STOP_WORDS = new Set([
23
+ ...GENERIC_NAV_TOKENS,
24
+ "card",
25
+ "cards",
26
+ "grid",
27
+ "rectangular",
28
+ "title",
29
+ "containing",
30
+ "component",
31
+ "element",
32
+ "entire",
33
+ "full",
34
+ "inside",
35
+ "within",
36
+ "target",
37
+ "titre",
38
+ "contenant",
39
+ "carte",
40
+ "cartes",
41
+ "grille",
42
+ "rectangulaire",
43
+ "composant",
44
+ "element",
45
+ "élément",
46
+ "capturez",
47
+ "capturer",
48
+ "dans",
49
+ "sur",
50
+ ]);
51
+ const ELEMENT_DOMAIN_HINTS = [
52
+ {
53
+ element: /\b(gallery|screenshot|screenshots|capture|captures|thumbnail|download all|filter by preset|filtrer par preset)\b/i,
54
+ page: /\b(gallery|screenshot|screenshots|capture|captures)\b/i,
55
+ score: 5,
56
+ },
57
+ {
58
+ element: /\b(preset|presets|template|templates|mockup|iphone|instructions|editor|configuration|settings)\b/i,
59
+ page: /\b(preset|presets|template|templates|mockup|editor|configuration|settings)\b/i,
60
+ score: 5,
61
+ },
62
+ {
63
+ element: /\b(assistant|chat|conversation|prompt ia|ai assistant)\b/i,
64
+ page: /\b(assistant|chat|conversation)\b/i,
65
+ score: 5,
66
+ },
67
+ {
68
+ element: /\b(home|homepage|dashboard|accueil)\b/i,
69
+ page: /\b(home|homepage|dashboard|accueil)\b/i,
70
+ score: 4,
71
+ },
72
+ ];
73
+ const MODAL_CAPTURE_RE = /\b(modal|dialog|popup|drawer|template|picker|gallery|selection|select|choose)\b/i;
74
+ const CONFIG_CAPTURE_RE = /\b(edit|editor|config|configuration|configure|settings|instruction|instructions|textarea|form|detail|details|modify|modifier|edition)\b/i;
75
+ const MODAL_OPENER_RE = /\b(new|nouveau|create|creer|cr[eé]er|add|ajouter|open|ouvrir|plus)\b/i;
76
+ function uniqueInOrder(values) {
77
+ const seen = new Set();
78
+ const result = [];
79
+ for (const value of values) {
80
+ if (seen.has(value))
81
+ continue;
82
+ seen.add(value);
83
+ result.push(value);
84
+ }
85
+ return result;
86
+ }
87
+ function truncate(value, max) {
88
+ return value.length > max ? `${value.slice(0, max - 1)}...` : value;
89
+ }
90
+ function normalizeNavigationTokens(values) {
91
+ const tokens = [];
92
+ for (const value of values) {
93
+ if (!value)
94
+ continue;
95
+ const parts = value
96
+ .toLowerCase()
97
+ .split(/[^a-z0-9]+/i)
98
+ .map((part) => part.trim())
99
+ .filter((part) => part.length >= 3 && part.length <= 32 && !GENERIC_NAV_TOKENS.has(part));
100
+ tokens.push(...parts);
101
+ }
102
+ return uniqueInOrder(tokens).slice(0, 8);
103
+ }
104
+ function normalizeElementAssignmentTokens(values) {
105
+ const tokens = [];
106
+ for (const value of values) {
107
+ if (!value)
108
+ continue;
109
+ const parts = value
110
+ .toLowerCase()
111
+ .normalize("NFD")
112
+ .replace(/[\u0300-\u036f]/g, "")
113
+ .split(/[^a-z0-9]+/i)
114
+ .map((part) => part.trim())
115
+ .filter((part) => part.length >= 3 && part.length <= 32 && !ELEMENT_ASSIGNMENT_STOP_WORDS.has(part));
116
+ tokens.push(...parts);
117
+ }
118
+ return uniqueInOrder(tokens).slice(0, 16);
119
+ }
120
+ function extractPathTokens(rawUrl) {
121
+ if (!rawUrl)
122
+ return [];
123
+ try {
124
+ const url = new URL(rawUrl);
125
+ return normalizeNavigationTokens(url.pathname.split("/"));
126
+ }
127
+ catch {
128
+ return normalizeNavigationTokens(rawUrl.split("/"));
129
+ }
130
+ }
131
+ function countOverlap(left, right) {
132
+ if (left.length === 0 || right.length === 0)
133
+ return 0;
134
+ const rightSet = new Set(right);
135
+ return left.filter((token) => rightSet.has(token)).length;
136
+ }
137
+ function inferCaptureIntent(values) {
138
+ const joined = values.filter(Boolean).join(" ");
139
+ return {
140
+ tokens: normalizeNavigationTokens(values),
141
+ modalSelection: MODAL_CAPTURE_RE.test(joined),
142
+ configuration: CONFIG_CAPTURE_RE.test(joined),
143
+ domain: inferCaptureDomain(joined),
144
+ };
145
+ }
146
+ function inferCaptureDomain(value) {
147
+ if (/\b(assistant|chat|conversation|copilot|prompt ia|ai assistant)\b/i.test(value)) {
148
+ return "assistant";
149
+ }
150
+ if (/\b(gallery|screenshots?|captures?|thumbnail|download all|filter by preset)\b/i.test(value)) {
151
+ return "gallery";
152
+ }
153
+ if (/\b(preset|template|editor|mockup|instructions?|configuration|configure)\b/i.test(value)) {
154
+ return "preset";
155
+ }
156
+ if (/\b(settings|preferences|appearance|language|theme|param[eè]tres)\b/i.test(value)) {
157
+ return "settings";
158
+ }
159
+ if (/\b(home|homepage|dashboard|accueil)\b/i.test(value)) {
160
+ return "home";
161
+ }
162
+ return "unknown";
163
+ }
164
+ export function getUrlOrigin(value) {
165
+ if (!value)
166
+ return null;
167
+ try {
168
+ return new URL(value).origin;
169
+ }
170
+ catch {
171
+ return null;
172
+ }
173
+ }
174
+ export function urlMatchesCaptureTarget(currentUrl, targetUrl) {
175
+ if (!currentUrl)
176
+ return false;
177
+ try {
178
+ const current = new URL(currentUrl);
179
+ const target = new URL(targetUrl);
180
+ if (current.origin !== target.origin)
181
+ return false;
182
+ const currentSegments = current.pathname
183
+ .split("/")
184
+ .map((segment) => decodeURIComponent(segment).trim().toLowerCase())
185
+ .filter(Boolean);
186
+ const targetSegments = target.pathname
187
+ .split("/")
188
+ .map((segment) => decodeURIComponent(segment).trim().toLowerCase())
189
+ .filter(Boolean);
190
+ if (targetSegments.length === 0) {
191
+ return currentSegments.length === 0;
192
+ }
193
+ if (currentSegments.length < targetSegments.length)
194
+ return false;
195
+ return targetSegments.every((segment, index) => currentSegments[index] === segment);
196
+ }
197
+ catch {
198
+ const normalize = (value) => value
199
+ .trim()
200
+ .replace(/[?#].*$/, "")
201
+ .replace(/\/+$/, "")
202
+ .toLowerCase();
203
+ const current = normalize(currentUrl);
204
+ const target = normalize(targetUrl);
205
+ return current === target || current.startsWith(`${target}/`);
206
+ }
207
+ }
208
+ export function urlsShareOrigin(left, right) {
209
+ const leftOrigin = getUrlOrigin(left);
210
+ const rightOrigin = getUrlOrigin(right);
211
+ return !!leftOrigin && !!rightOrigin && leftOrigin === rightOrigin;
212
+ }
213
+ export function resolveCaptureResumeUrl(params) {
214
+ const normalizedPageId = params.pageId ?? "main";
215
+ const checkpoint = [...params.checkpoints]
216
+ .reverse()
217
+ .find((entry) => entry.pageId === normalizedPageId);
218
+ const candidates = [
219
+ params.cursorResumeUrl,
220
+ checkpoint?.resumeUrl,
221
+ checkpoint?.url,
222
+ checkpoint?.canonicalUrl,
223
+ params.currentUrl,
224
+ params.sessionProfile?.lastKnownUrl,
225
+ params.sessionProfile?.validatedStartUrl,
226
+ params.pageUrl,
227
+ ];
228
+ for (const candidate of candidates) {
229
+ if (!candidate)
230
+ continue;
231
+ if (urlMatchesCaptureTarget(candidate, params.pageUrl))
232
+ return candidate;
233
+ }
234
+ if (params.pageIndex > 0) {
235
+ for (const candidate of candidates) {
236
+ if (!candidate)
237
+ continue;
238
+ if (urlsShareOrigin(candidate, params.pageUrl))
239
+ return candidate;
240
+ }
241
+ }
242
+ return params.pageUrl;
243
+ }
244
+ export function shouldNormalizeDialogsBeforeTargetReuse(params) {
245
+ // Always close leftover dialogs/overlays when transitioning between pages.
246
+ // Even if the next page expects a dialog, we should start clean — the agent
247
+ // will open the correct dialog as part of its navigation plan.
248
+ return (params.currentDialogCount ?? 0) > 0;
249
+ }
250
+ export function buildScreenshotVariantPlan(params) {
251
+ const langs = uniqueInOrder(params.langs);
252
+ const themes = uniqueInOrder(params.themes);
253
+ return langs.flatMap((lang) => themes.map((theme) => ({
254
+ key: `${lang}:${theme}`,
255
+ lang,
256
+ theme,
257
+ pages: params.pages,
258
+ })));
259
+ }
260
+ function scoreIsolatedElementForPage(params) {
261
+ const elementText = `${params.element.name} ${params.element.description}`;
262
+ const pageText = `${params.pageRun.pageId ?? "main"} ${params.pageRun.prompt} ${params.pageRun.url}`;
263
+ const elementTokens = normalizeElementAssignmentTokens([elementText]);
264
+ const pageTokens = normalizeElementAssignmentTokens([
265
+ params.pageRun.pageId,
266
+ params.pageRun.prompt,
267
+ ...extractPathTokens(params.pageRun.url),
268
+ ]);
269
+ let score = countOverlap(elementTokens, pageTokens) * 3;
270
+ const normalizedPageId = (params.pageRun.pageId ?? "main").toLowerCase();
271
+ if (params.element.sourcePageId
272
+ && normalizedPageId === params.element.sourcePageId.trim().toLowerCase()) {
273
+ return 100;
274
+ }
275
+ if (normalizeElementAssignmentTokens([params.pageRun.pageId]).some((token) => elementTokens.includes(token))) {
276
+ score += 3;
277
+ }
278
+ for (const hint of ELEMENT_DOMAIN_HINTS) {
279
+ if (hint.element.test(elementText) && hint.page.test(pageText)) {
280
+ score += hint.score;
281
+ }
282
+ }
283
+ if (/\b(current|currently|already|active|visible|open)\b/i.test(elementText)) {
284
+ score += 1;
285
+ }
286
+ return score;
287
+ }
288
+ export function resolveIsolatedElementAssignments(params) {
289
+ const finalPageId = params.pageRuns[params.pageRuns.length - 1]?.pageId ?? "main";
290
+ return params.elements.map((element) => {
291
+ const explicitPageId = element.sourcePageId?.trim();
292
+ if (explicitPageId) {
293
+ const matchingPage = params.pageRuns.find((pageRun) => (pageRun.pageId ?? "main").toLowerCase() === explicitPageId.toLowerCase());
294
+ if (matchingPage) {
295
+ return {
296
+ element,
297
+ pageId: matchingPage.pageId ?? "main",
298
+ explicit: true,
299
+ score: 100,
300
+ };
301
+ }
302
+ }
303
+ let best = null;
304
+ let runnerUp = Number.NEGATIVE_INFINITY;
305
+ for (const pageRun of params.pageRuns) {
306
+ const pageId = pageRun.pageId ?? "main";
307
+ const score = scoreIsolatedElementForPage({ element, pageRun });
308
+ if (!best || score > best.score) {
309
+ runnerUp = best?.score ?? Number.NEGATIVE_INFINITY;
310
+ best = { pageId, score };
311
+ continue;
312
+ }
313
+ if (score > runnerUp) {
314
+ runnerUp = score;
315
+ }
316
+ }
317
+ const bestScore = best?.score ?? Number.NEGATIVE_INFINITY;
318
+ const uniqueBest = best && bestScore >= 5 && bestScore >= runnerUp + 2;
319
+ const resolvedPageId = uniqueBest && best ? best.pageId : finalPageId;
320
+ return {
321
+ element,
322
+ pageId: resolvedPageId,
323
+ explicit: false,
324
+ score: uniqueBest ? bestScore : 0,
325
+ };
326
+ });
327
+ }
328
+ export function describeScreenshotVariantPlan(plan) {
329
+ if (plan.length === 0)
330
+ return "none";
331
+ return plan
332
+ .map((variant) => `${variant.lang}/${variant.theme} (${variant.pages.length} page${variant.pages.length > 1 ? "s" : ""})`)
333
+ .join(" -> ");
334
+ }
335
+ export function shouldReuseLivePageState(params) {
336
+ if (params.pageIndex === 0)
337
+ return false;
338
+ if (!params.isAuthenticated)
339
+ return false;
340
+ if (!params.currentUrl)
341
+ return false;
342
+ const currentOrigin = getUrlOrigin(params.currentUrl);
343
+ const pageOrigin = getUrlOrigin(params.pageUrl);
344
+ if (!currentOrigin || !pageOrigin || currentOrigin !== pageOrigin) {
345
+ return false;
346
+ }
347
+ // Only skip the reset when the page uses the preset root URL.
348
+ // Explicit per-page URLs still navigate directly for determinism.
349
+ return params.pageUrl === params.presetRootUrl;
350
+ }
351
+ export function shouldUseSequentialPageHandoff(params) {
352
+ if (params.pageIndex === 0)
353
+ return false;
354
+ if (!params.currentUrl)
355
+ return false;
356
+ const currentOrigin = getUrlOrigin(params.currentUrl);
357
+ const pageOrigin = getUrlOrigin(params.pageUrl);
358
+ if (!currentOrigin || !pageOrigin)
359
+ return false;
360
+ if (currentOrigin !== pageOrigin)
361
+ return false;
362
+ if (params.previousPageId && params.pageId && params.previousPageId === params.pageId) {
363
+ return true;
364
+ }
365
+ const currentPathTokens = extractPathTokens(params.currentUrl);
366
+ const targetPathTokens = extractPathTokens(params.pageUrl);
367
+ const pathOverlap = countOverlap(currentPathTokens, targetPathTokens);
368
+ const previousIntent = inferCaptureIntent([params.previousPageId, params.previousPrompt]);
369
+ const targetIntent = inferCaptureIntent([params.pageId, params.pagePrompt]);
370
+ const promptOverlap = countOverlap(previousIntent.tokens, targetIntent.tokens);
371
+ if (pathOverlap > 0 && promptOverlap > 0) {
372
+ return true;
373
+ }
374
+ if (promptOverlap >= 2) {
375
+ return true;
376
+ }
377
+ if (targetIntent.modalSelection && !previousIntent.configuration) {
378
+ return true;
379
+ }
380
+ if (previousIntent.modalSelection && targetIntent.configuration && !targetIntent.modalSelection) {
381
+ return false;
382
+ }
383
+ return false;
384
+ }
385
+ export function buildSequentialHandoffArtifacts(actions) {
386
+ const selectorHints = [];
387
+ const selectorMemory = {};
388
+ const seenSelectors = new Set();
389
+ for (let index = actions.length - 1; index >= 0; index -= 1) {
390
+ const action = actions[index];
391
+ if (!action.success)
392
+ continue;
393
+ if (action.stateChanged === false)
394
+ continue;
395
+ if (action.action !== "click"
396
+ && action.action !== "safe_expand"
397
+ && action.action !== "select_option"
398
+ && action.action !== "scroll"
399
+ && action.action !== "press_key"
400
+ && action.action !== "dismiss_overlays") {
401
+ continue;
402
+ }
403
+ const selector = String(action.params.selector ?? "").trim();
404
+ if (!selector || selector.startsWith("[index:") || seenSelectors.has(selector))
405
+ continue;
406
+ seenSelectors.add(selector);
407
+ const label = String(action.params.elementLabel ?? "").trim();
408
+ const href = String(action.params.href ?? "").trim();
409
+ const detail = [label, href].filter(Boolean).join(" -> ");
410
+ selectorMemory[`handoff:recent_${selectorHints.length + 1}`] = [selector];
411
+ selectorHints.push(detail ? `${selector} (${truncate(detail, 120)})` : selector);
412
+ if (selectorHints.length >= 6)
413
+ break;
414
+ }
415
+ return { selectorHints, selectorMemory };
416
+ }
417
+ export function buildTargetNavigationArtifacts(params) {
418
+ if (!params.selectorMemory || Object.keys(params.selectorMemory).length === 0) {
419
+ return { navigationHints: [], selectorMemory: {} };
420
+ }
421
+ const targetTokens = uniqueInOrder([
422
+ ...extractPathTokens(params.pageUrl),
423
+ ...normalizeNavigationTokens([params.pageId ?? null, params.prompt]),
424
+ ]).slice(0, 8);
425
+ if (targetTokens.length === 0) {
426
+ return { navigationHints: [], selectorMemory: {} };
427
+ }
428
+ const hints = [];
429
+ const selectorMemory = {};
430
+ const seenSelectors = new Set();
431
+ for (const token of targetTokens) {
432
+ const candidateSelectors = [
433
+ ...(params.selectorMemory[`nav:path:${token}`] ?? []),
434
+ ...(params.selectorMemory[`nav:label:${token}`] ?? []),
435
+ ];
436
+ for (const selector of candidateSelectors) {
437
+ const normalizedSelector = selector.trim();
438
+ if (!normalizedSelector || seenSelectors.has(normalizedSelector))
439
+ continue;
440
+ seenSelectors.add(normalizedSelector);
441
+ selectorMemory[`nav:target:${token}`] = [normalizedSelector];
442
+ hints.push(`${token} -> ${truncate(normalizedSelector, 120)}`);
443
+ if (hints.length >= 6) {
444
+ return { navigationHints: hints, selectorMemory };
445
+ }
446
+ break;
447
+ }
448
+ }
449
+ return { navigationHints: hints, selectorMemory };
450
+ }
451
+ export function buildDeterministicNavigationPlan(params) {
452
+ if (!params.selectorMemory || Object.keys(params.selectorMemory).length === 0) {
453
+ return [];
454
+ }
455
+ const targetArtifacts = buildTargetNavigationArtifacts(params);
456
+ const targetSelectors = uniqueInOrder(Object.values(targetArtifacts.selectorMemory).flat()).slice(0, 2);
457
+ const openerSelectors = uniqueInOrder([
458
+ ...(params.selectorMemory["menu:primary"] ?? []),
459
+ ...(params.selectorMemory["account:menu"] ?? []),
460
+ ]).slice(0, 2);
461
+ const candidates = [];
462
+ for (const targetSelector of targetSelectors) {
463
+ candidates.push({
464
+ description: `direct:${truncate(targetSelector, 80)}`,
465
+ selectors: [targetSelector],
466
+ });
467
+ }
468
+ if (openerSelectors.length > 0 && targetSelectors.length > 0) {
469
+ for (const openerSelector of openerSelectors.slice(0, 2)) {
470
+ candidates.push({
471
+ description: `open_then_target:${truncate(openerSelector, 60)} -> ${truncate(targetSelectors[0], 60)}`,
472
+ selectors: [openerSelector, targetSelectors[0]],
473
+ });
474
+ }
475
+ }
476
+ const deduped = new Map();
477
+ for (const candidate of candidates) {
478
+ const key = candidate.selectors.join(" -> ");
479
+ if (!deduped.has(key)) {
480
+ deduped.set(key, candidate);
481
+ }
482
+ }
483
+ return Array.from(deduped.values()).slice(0, 4);
484
+ }
485
+ export function buildVisibleControlNavigationPlan(params) {
486
+ if (!params.interactiveElements || params.interactiveElements.length === 0) {
487
+ return [];
488
+ }
489
+ const targetIntent = inferCaptureIntent([params.pageId, params.prompt]);
490
+ const targetTokens = targetIntent.tokens;
491
+ const candidates = new Map();
492
+ for (const element of params.interactiveElements) {
493
+ if (!element.selector)
494
+ continue;
495
+ if (!element.visible && element.visibilityState === "offscreen")
496
+ continue;
497
+ const label = [element.text, element.ariaLabel, element.title]
498
+ .filter((value) => typeof value === "string" && value.trim().length > 0)
499
+ .join(" ")
500
+ .trim();
501
+ if (!label)
502
+ continue;
503
+ const labelTokens = normalizeNavigationTokens([label]);
504
+ const overlap = countOverlap(labelTokens, targetTokens);
505
+ const normalizedLabel = label.toLowerCase();
506
+ let score = overlap * 4;
507
+ if (targetIntent.modalSelection && MODAL_OPENER_RE.test(normalizedLabel)) {
508
+ score += 5;
509
+ }
510
+ if (/\bpreset|template\b/i.test(`${params.pageId ?? ""} ${params.prompt}`) && /\bpreset|template\b/i.test(normalizedLabel)) {
511
+ score += 4;
512
+ }
513
+ if (/\bgallery|capture|captures|screenshot|screenshots\b/i.test(`${params.pageId ?? ""} ${params.prompt}`) && /\bgallery|capture|captures|screenshot|screenshots\b/i.test(normalizedLabel)) {
514
+ score += 4;
515
+ }
516
+ if (element.visibilityState === "full")
517
+ score += 1;
518
+ if (element.role === "button" || element.role === "link")
519
+ score += 1;
520
+ if (score <= 0)
521
+ continue;
522
+ const current = candidates.get(element.selector);
523
+ const description = `visible:${truncate(label.replace(/\s+/g, " "), 80)}`;
524
+ if (!current || score > current.score) {
525
+ candidates.set(element.selector, {
526
+ score,
527
+ candidate: {
528
+ description,
529
+ selectors: [element.selector],
530
+ },
531
+ });
532
+ }
533
+ }
534
+ const ranked = Array.from(candidates.values())
535
+ .sort((left, right) => right.score - left.score);
536
+ const bestScore = ranked[0]?.score ?? 0;
537
+ const scoreFloor = Math.max(4, bestScore - 2);
538
+ return ranked
539
+ .filter((entry) => entry.score >= scoreFloor)
540
+ .map((entry) => entry.candidate)
541
+ .slice(0, 4);
542
+ }
543
+ export function shouldAttemptSequentialReadyCheck(params) {
544
+ if (!params.currentUrl)
545
+ return false;
546
+ const currentOrigin = getUrlOrigin(params.currentUrl);
547
+ const pageOrigin = getUrlOrigin(params.pageUrl);
548
+ if (!currentOrigin || !pageOrigin || currentOrigin !== pageOrigin)
549
+ return false;
550
+ const previousIntent = inferCaptureIntent([params.previousPageId, params.previousPrompt]);
551
+ const targetIntent = inferCaptureIntent([params.pageId, params.pagePrompt]);
552
+ const samePageId = !!params.previousPageId && !!params.pageId && params.previousPageId === params.pageId;
553
+ const currentPathTokens = extractPathTokens(params.currentUrl);
554
+ const targetPathTokens = extractPathTokens(params.pageUrl);
555
+ if (samePageId) {
556
+ return true;
557
+ }
558
+ if (targetIntent.modalSelection)
559
+ return false;
560
+ if (previousIntent.modalSelection && targetIntent.configuration && !targetIntent.modalSelection) {
561
+ return false;
562
+ }
563
+ const intentOverlap = countOverlap(previousIntent.tokens, targetIntent.tokens);
564
+ if (countOverlap(currentPathTokens, targetPathTokens) > 0) {
565
+ // Many AutoKap captures share the same base route (for example /home) while
566
+ // representing materially different UI states. Do not use the fast ready
567
+ // check on same-path transitions unless the target is clearly a close
568
+ // continuation of the previous page.
569
+ if (previousIntent.domain !== "unknown"
570
+ && targetIntent.domain !== "unknown"
571
+ && previousIntent.domain !== targetIntent.domain) {
572
+ return false;
573
+ }
574
+ return intentOverlap >= 2;
575
+ }
576
+ return intentOverlap >= 2;
577
+ }
578
+ export function shouldUseLiveStateFastPath(params) {
579
+ if (params.bootstrapMode !== "sequential_handoff"
580
+ && params.bootstrapMode !== "reused_live_state") {
581
+ return false;
582
+ }
583
+ return shouldAttemptSequentialReadyCheck({
584
+ currentUrl: params.currentUrl,
585
+ pageUrl: params.pageUrl,
586
+ previousPageId: params.previousPageId,
587
+ pageId: params.pageId,
588
+ previousPrompt: params.previousPrompt,
589
+ pagePrompt: params.pagePrompt,
590
+ });
591
+ }
592
+ export function deriveRunSharedAuthProfile(profile) {
593
+ if (!profile?.storageState || profile.authState !== "authenticated") {
594
+ return undefined;
595
+ }
596
+ const url = profile.lastKnownUrl ?? profile.validatedStartUrl ?? null;
597
+ const parts = [
598
+ "scope=run_auth",
599
+ "auth=authenticated",
600
+ profile.accountLabel ? `account=${profile.accountLabel}` : null,
601
+ profile.detectedLang ? `lang=${profile.detectedLang}` : null,
602
+ profile.detectedTheme ? `theme=${profile.detectedTheme}` : null,
603
+ url ? `url=${url}` : null,
604
+ ].filter(Boolean);
605
+ return {
606
+ storageState: profile.storageState,
607
+ sessionStorage: profile.sessionStorage,
608
+ authState: "authenticated",
609
+ accountLabel: profile.accountLabel ?? null,
610
+ detectedLang: profile.detectedLang ?? null,
611
+ detectedTheme: profile.detectedTheme ?? null,
612
+ validatedStartUrl: profile.validatedStartUrl ?? url,
613
+ lastKnownUrl: url,
614
+ summary: truncate(parts.join("; "), 180),
615
+ validationStatus: profile.validationStatus ?? "unknown",
616
+ lastUsedAt: profile.lastUsedAt ?? new Date().toISOString(),
617
+ profileVersion: profile.profileVersion ?? 1,
618
+ };
619
+ }
620
+ export function buildSessionBootstrapProfile(params) {
621
+ const { runAuthProfile, persistedVariantProfile } = params;
622
+ const requestedLang = params.requestedLang.trim().toLowerCase();
623
+ const runLangMatches = runAuthProfile?.detectedLang?.trim().toLowerCase() === requestedLang;
624
+ const persistedLangMatches = persistedVariantProfile?.detectedLang?.trim().toLowerCase() === requestedLang;
625
+ const runThemeMatches = runAuthProfile?.detectedTheme === params.requestedTheme;
626
+ const persistedThemeMatches = persistedVariantProfile?.detectedTheme === params.requestedTheme;
627
+ const runVariantMatches = runLangMatches && runThemeMatches;
628
+ const persistedVariantMatches = persistedLangMatches && persistedThemeMatches;
629
+ if (runAuthProfile) {
630
+ const preferredLiveUrl = runAuthProfile.lastKnownUrl ?? persistedVariantProfile?.lastKnownUrl ?? null;
631
+ const sameOriginLiveUrl = preferredLiveUrl && params.startUrl
632
+ ? getUrlOrigin(preferredLiveUrl) === getUrlOrigin(params.startUrl)
633
+ : !!preferredLiveUrl;
634
+ const url = (sameOriginLiveUrl ? preferredLiveUrl : null)
635
+ ?? params.startUrl
636
+ ?? persistedVariantProfile?.validatedStartUrl
637
+ ?? runAuthProfile.validatedStartUrl
638
+ ?? preferredLiveUrl
639
+ ?? null;
640
+ const parts = [
641
+ "scope=bootstrap",
642
+ "source=run_shared_auth",
643
+ `auth=${runAuthProfile.authState}`,
644
+ runAuthProfile.accountLabel ? `account=${runAuthProfile.accountLabel}` : null,
645
+ `target=${params.requestedLang}/${params.requestedTheme}`,
646
+ url ? `url=${url}` : null,
647
+ ].filter(Boolean);
648
+ return {
649
+ source: "run_shared_auth",
650
+ profile: {
651
+ storageState: runAuthProfile.storageState ?? persistedVariantProfile?.storageState,
652
+ sessionStorage: runAuthProfile.sessionStorage ?? persistedVariantProfile?.sessionStorage,
653
+ authState: runAuthProfile.authState,
654
+ accountLabel: runAuthProfile.accountLabel ?? persistedVariantProfile?.accountLabel ?? null,
655
+ detectedLang: (runLangMatches ? runAuthProfile.detectedLang : null)
656
+ ?? (persistedLangMatches ? persistedVariantProfile?.detectedLang ?? null : null),
657
+ detectedTheme: (runThemeMatches ? runAuthProfile.detectedTheme : null)
658
+ ?? (persistedThemeMatches ? persistedVariantProfile?.detectedTheme ?? null : null),
659
+ validatedStartUrl: url,
660
+ lastKnownUrl: preferredLiveUrl ?? url,
661
+ summary: truncate(parts.join("; "), 200),
662
+ validationStatus: runVariantMatches && runAuthProfile.validationStatus !== "invalid"
663
+ ? runAuthProfile.validationStatus
664
+ : persistedVariantMatches && persistedVariantProfile?.validationStatus !== "invalid"
665
+ ? persistedVariantProfile.validationStatus
666
+ : "unknown",
667
+ lastUsedAt: runAuthProfile.lastUsedAt ?? persistedVariantProfile?.lastUsedAt ?? null,
668
+ profileVersion: Math.max(runAuthProfile.profileVersion ?? 1, persistedVariantProfile?.profileVersion ?? 1),
669
+ },
670
+ };
671
+ }
672
+ if (persistedVariantProfile) {
673
+ return {
674
+ source: "persisted_variant",
675
+ profile: persistedVariantProfile,
676
+ };
677
+ }
678
+ return { source: "none" };
679
+ }
680
+ const SELECTOR_MEMORY_SCOPE_DELIMITER = "::";
681
+ function sanitizeSelectorMemoryScopePart(value) {
682
+ return value
683
+ .trim()
684
+ .toLowerCase()
685
+ .replace(/[^a-z0-9_-]+/g, "-")
686
+ .replace(/-+/g, "-")
687
+ .replace(/^-|-$/g, "");
688
+ }
689
+ function buildScopedSelectorMemorySignature(scopeKey, stepSignature) {
690
+ return `${scopeKey}${SELECTOR_MEMORY_SCOPE_DELIMITER}${stepSignature}`;
691
+ }
692
+ function parseScopedSelectorMemorySignature(signature) {
693
+ const delimiterIndex = signature.indexOf(SELECTOR_MEMORY_SCOPE_DELIMITER);
694
+ if (delimiterIndex <= 0)
695
+ return null;
696
+ return {
697
+ scopeKey: signature.slice(0, delimiterIndex),
698
+ stepSignature: signature.slice(delimiterIndex + SELECTOR_MEMORY_SCOPE_DELIMITER.length),
699
+ };
700
+ }
701
+ function shouldKeepSelectorMemoryGlobal(signature) {
702
+ return signature === "menu:primary"
703
+ || signature === "account:menu"
704
+ || signature === "search:field"
705
+ || signature.startsWith("login:")
706
+ || signature.startsWith("lang:")
707
+ || signature.startsWith("theme:");
708
+ }
709
+ function shouldSuppressGenericSelectorMemory(signature, hasScopedContext) {
710
+ return hasScopedContext && signature.startsWith("nav:");
711
+ }
712
+ export function buildSelectorMemoryScopeKeys(context) {
713
+ if (!context)
714
+ return [];
715
+ const scopeKeys = [];
716
+ const origin = getUrlOrigin(context.pageUrl);
717
+ if (origin) {
718
+ const normalizedOrigin = sanitizeSelectorMemoryScopePart(origin.replace(/^https?:\/\//i, ""));
719
+ if (normalizedOrigin) {
720
+ scopeKeys.push(`origin:${normalizedOrigin}`);
721
+ }
722
+ }
723
+ const normalizedPageId = sanitizeSelectorMemoryScopePart(context.pageId ?? "main");
724
+ if (normalizedPageId) {
725
+ scopeKeys.push(`page:${normalizedPageId}`);
726
+ }
727
+ const identityKind = context.pageIdentity?.kind && context.pageIdentity.kind !== "unknown"
728
+ ? sanitizeSelectorMemoryScopePart(context.pageIdentity.kind)
729
+ : "";
730
+ if (identityKind) {
731
+ const subjectTokenKey = uniqueInOrder((context.pageIdentity?.subjectTokens ?? [])
732
+ .map((token) => sanitizeSelectorMemoryScopePart(token))
733
+ .filter(Boolean))
734
+ .slice(0, 3)
735
+ .join("-");
736
+ if (subjectTokenKey) {
737
+ scopeKeys.push(`identity:${identityKind}:${subjectTokenKey}`);
738
+ }
739
+ scopeKeys.push(`identity:${identityKind}`);
740
+ }
741
+ return uniqueInOrder(scopeKeys);
742
+ }
743
+ export function scopeSelectorMemoryUpdates(updates, context) {
744
+ const scopeKeys = buildSelectorMemoryScopeKeys(context);
745
+ if (scopeKeys.length === 0) {
746
+ return updates.map((update) => ({ ...update }));
747
+ }
748
+ const scopedUpdates = new Map();
749
+ for (const update of updates) {
750
+ const signature = update.stepSignature.trim();
751
+ const selector = update.selector.trim();
752
+ if (!signature || !selector)
753
+ continue;
754
+ const keepGlobal = shouldKeepSelectorMemoryGlobal(signature);
755
+ if (keepGlobal) {
756
+ scopedUpdates.set(`${signature}|${selector}|global`, {
757
+ ...update,
758
+ stepSignature: signature,
759
+ selector,
760
+ });
761
+ }
762
+ for (const scopeKey of scopeKeys) {
763
+ const scopedSignature = buildScopedSelectorMemorySignature(scopeKey, signature);
764
+ scopedUpdates.set(`${scopedSignature}|${selector}|${scopeKey}`, {
765
+ ...update,
766
+ stepSignature: scopedSignature,
767
+ selector,
768
+ });
769
+ }
770
+ }
771
+ return Array.from(scopedUpdates.values());
772
+ }
773
+ export function resolveScopedSelectorMemory(memory, context) {
774
+ if (!memory || Object.keys(memory).length === 0) {
775
+ return {};
776
+ }
777
+ const scopeKeys = buildSelectorMemoryScopeKeys(context);
778
+ const merged = {};
779
+ const appendSelectors = (signature, selectors) => {
780
+ for (const selector of selectors) {
781
+ const normalizedSelector = selector.trim();
782
+ if (!normalizedSelector)
783
+ continue;
784
+ if (!merged[signature]) {
785
+ merged[signature] = [];
786
+ }
787
+ if (!merged[signature].includes(normalizedSelector)) {
788
+ merged[signature].push(normalizedSelector);
789
+ }
790
+ }
791
+ };
792
+ for (const scopeKey of scopeKeys) {
793
+ for (const [signature, selectors] of Object.entries(memory)) {
794
+ const parsed = parseScopedSelectorMemorySignature(signature);
795
+ if (!parsed || parsed.scopeKey !== scopeKey)
796
+ continue;
797
+ appendSelectors(parsed.stepSignature, selectors);
798
+ }
799
+ }
800
+ for (const [signature, selectors] of Object.entries(memory)) {
801
+ if (parseScopedSelectorMemorySignature(signature))
802
+ continue;
803
+ if (shouldSuppressGenericSelectorMemory(signature, scopeKeys.length > 0))
804
+ continue;
805
+ appendSelectors(signature, selectors);
806
+ }
807
+ return merged;
808
+ }
809
+ export function mergeSelectorMemory(...sources) {
810
+ const merged = {};
811
+ for (const source of sources) {
812
+ if (!source)
813
+ continue;
814
+ for (const [signature, selectors] of Object.entries(source)) {
815
+ for (const selector of selectors) {
816
+ const normalizedSelector = selector.trim();
817
+ if (!normalizedSelector)
818
+ continue;
819
+ if (!merged[signature]) {
820
+ merged[signature] = [];
821
+ }
822
+ if (!merged[signature].includes(normalizedSelector)) {
823
+ merged[signature].push(normalizedSelector);
824
+ }
825
+ }
826
+ }
827
+ }
828
+ return merged;
829
+ }
830
+ export function applySelectorMemoryUpdates(current, updates) {
831
+ const fresh = {};
832
+ for (const update of updates) {
833
+ if (!update.success)
834
+ continue;
835
+ const signature = update.stepSignature.trim();
836
+ const selector = update.selector.trim();
837
+ if (!signature || !selector)
838
+ continue;
839
+ if (!fresh[signature]) {
840
+ fresh[signature] = [];
841
+ }
842
+ if (!fresh[signature].includes(selector)) {
843
+ fresh[signature].push(selector);
844
+ }
845
+ }
846
+ return mergeSelectorMemory(fresh, current);
847
+ }
848
+ //# sourceMappingURL=capture-run-optimizer.js.map