browser-pilot 0.0.16 → 0.0.17

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.
@@ -10,10 +10,13 @@ import {
10
10
  ActionabilityError,
11
11
  BatchExecutor,
12
12
  ElementNotFoundError,
13
+ NetworkResponseTracker,
13
14
  TimeoutError,
15
+ captureStateSignature,
14
16
  ensureActionable,
17
+ evaluateOutcome,
15
18
  generateHints
16
- } from "./chunk-V3VLBQAM.mjs";
19
+ } from "./chunk-ZDODXEBD.mjs";
17
20
 
18
21
  // src/audio/encoding.ts
19
22
  function bufferToBase64(data) {
@@ -2093,6 +2096,114 @@ async function waitForNetworkIdle(cdp, options = {}) {
2093
2096
  });
2094
2097
  }
2095
2098
 
2099
+ // src/browser/delta.ts
2100
+ function extractPageState(url, title, snapshot, forms, pageText) {
2101
+ const headings = [];
2102
+ const buttons = [];
2103
+ const alerts = [];
2104
+ function walkNodes(nodes) {
2105
+ for (const node of nodes) {
2106
+ const role = node.role?.toLowerCase() ?? "";
2107
+ if (role === "heading" && node.name) {
2108
+ headings.push(node.name);
2109
+ }
2110
+ if ((role === "button" || role === "link") && node.name) {
2111
+ const disabled = node.disabled ?? false;
2112
+ buttons.push({ text: node.name, disabled, ref: node.ref });
2113
+ }
2114
+ if (role === "alert" && node.name) {
2115
+ alerts.push(node.name);
2116
+ }
2117
+ if (node.children) {
2118
+ walkNodes(node.children);
2119
+ }
2120
+ }
2121
+ }
2122
+ walkNodes(snapshot.accessibilityTree);
2123
+ const formFields = forms.map((f) => ({
2124
+ label: f.label,
2125
+ name: f.name,
2126
+ id: f.id,
2127
+ value: f.value,
2128
+ type: f.type
2129
+ }));
2130
+ return {
2131
+ url,
2132
+ title,
2133
+ headings,
2134
+ formFields,
2135
+ buttons,
2136
+ alerts,
2137
+ visibleText: pageText.slice(0, 3e3)
2138
+ };
2139
+ }
2140
+ function computeDelta(before, after) {
2141
+ const changes = [];
2142
+ if (before.url !== after.url) {
2143
+ changes.push({ kind: "url", before: before.url, after: after.url });
2144
+ }
2145
+ if (before.title !== after.title) {
2146
+ changes.push({ kind: "title", before: before.title, after: after.title });
2147
+ }
2148
+ const beforeHeadings = new Set(before.headings);
2149
+ const afterHeadings = new Set(after.headings);
2150
+ for (const h of after.headings) {
2151
+ if (!beforeHeadings.has(h)) {
2152
+ changes.push({ kind: "heading_added", after: h });
2153
+ }
2154
+ }
2155
+ for (const h of before.headings) {
2156
+ if (!afterHeadings.has(h)) {
2157
+ changes.push({ kind: "heading_removed", before: h });
2158
+ }
2159
+ }
2160
+ const beforeFieldMap = new Map(
2161
+ before.formFields.map((f) => [f.id ?? f.name ?? f.label ?? "", f])
2162
+ );
2163
+ for (const af of after.formFields) {
2164
+ const key = af.id ?? af.name ?? af.label ?? "";
2165
+ const bf = beforeFieldMap.get(key);
2166
+ if (bf && JSON.stringify(bf.value) !== JSON.stringify(af.value)) {
2167
+ changes.push({
2168
+ kind: "field_changed",
2169
+ before: String(bf.value ?? ""),
2170
+ after: String(af.value ?? ""),
2171
+ detail: af.label ?? af.name ?? af.id ?? key
2172
+ });
2173
+ }
2174
+ }
2175
+ const beforeBtnMap = new Map(before.buttons.map((b) => [b.text, b]));
2176
+ for (const ab of after.buttons) {
2177
+ const bb = beforeBtnMap.get(ab.text);
2178
+ if (bb && bb.disabled !== ab.disabled) {
2179
+ changes.push({
2180
+ kind: "button_changed",
2181
+ detail: ab.text,
2182
+ before: bb.disabled ? "disabled" : "enabled",
2183
+ after: ab.disabled ? "disabled" : "enabled"
2184
+ });
2185
+ }
2186
+ }
2187
+ const beforeAlerts = new Set(before.alerts);
2188
+ const afterAlerts = new Set(after.alerts);
2189
+ for (const a of after.alerts) {
2190
+ if (!beforeAlerts.has(a)) {
2191
+ changes.push({ kind: "alert_added", after: a });
2192
+ }
2193
+ }
2194
+ for (const a of before.alerts) {
2195
+ if (!afterAlerts.has(a)) {
2196
+ changes.push({ kind: "alert_removed", before: a });
2197
+ }
2198
+ }
2199
+ return {
2200
+ changes,
2201
+ before,
2202
+ after,
2203
+ hasChanges: changes.length > 0
2204
+ };
2205
+ }
2206
+
2096
2207
  // src/browser/keyboard.ts
2097
2208
  var US_KEYBOARD = {
2098
2209
  // Letters (lowercase)
@@ -2252,8 +2363,118 @@ function parseShortcut(combo) {
2252
2363
  return { modifiers, key };
2253
2364
  }
2254
2365
 
2366
+ // src/browser/review.ts
2367
+ function extractReview(url, title, snapshot, forms, pageText) {
2368
+ const headings = [];
2369
+ const alerts = [];
2370
+ const statusLabels = [];
2371
+ const keyValues = [];
2372
+ const tables = [];
2373
+ const summaryCards = [];
2374
+ function walkNodes(nodes, parentHeading) {
2375
+ let currentHeading = parentHeading;
2376
+ for (const node of nodes) {
2377
+ const role = node.role?.toLowerCase() ?? "";
2378
+ if (role === "heading" && node.name) {
2379
+ headings.push(node.name);
2380
+ currentHeading = node.name;
2381
+ }
2382
+ if (role === "alert" && node.name) {
2383
+ alerts.push(node.name);
2384
+ }
2385
+ if (role === "status" && node.name) {
2386
+ statusLabels.push(node.name);
2387
+ }
2388
+ if (role === "table" || role === "grid") {
2389
+ const table = extractTableFromNode(node);
2390
+ if (table) tables.push(table);
2391
+ }
2392
+ if ((role === "definition" || role === "term") && node.name) {
2393
+ if (role === "term") {
2394
+ keyValues.push({ key: node.name, value: "" });
2395
+ } else if (role === "definition" && keyValues.length > 0) {
2396
+ const last = keyValues[keyValues.length - 1];
2397
+ if (!last.value) last.value = node.name;
2398
+ }
2399
+ }
2400
+ if (node.children) {
2401
+ walkNodes(node.children, currentHeading);
2402
+ }
2403
+ }
2404
+ }
2405
+ walkNodes(snapshot.accessibilityTree);
2406
+ const textKvPairs = extractKeyValueFromText(pageText);
2407
+ keyValues.push(...textKvPairs);
2408
+ const formEntries = forms.map((f) => ({
2409
+ label: f.label,
2410
+ value: f.value,
2411
+ type: f.type,
2412
+ disabled: f.disabled
2413
+ }));
2414
+ return {
2415
+ url,
2416
+ title,
2417
+ headings,
2418
+ forms: formEntries,
2419
+ alerts,
2420
+ summaryCards,
2421
+ tables,
2422
+ keyValues,
2423
+ statusLabels
2424
+ };
2425
+ }
2426
+ function extractTableFromNode(node) {
2427
+ const headers = [];
2428
+ const rows = [];
2429
+ function findRows(n) {
2430
+ const role = n.role?.toLowerCase() ?? "";
2431
+ if (role === "columnheader" && n.name) {
2432
+ headers.push(n.name);
2433
+ }
2434
+ if (role === "row") {
2435
+ const cells = [];
2436
+ if (n.children) {
2437
+ for (const child of n.children) {
2438
+ const childRole = child.role?.toLowerCase() ?? "";
2439
+ if ((childRole === "cell" || childRole === "gridcell") && child.name) {
2440
+ cells.push(child.name);
2441
+ }
2442
+ }
2443
+ }
2444
+ if (cells.length > 0) rows.push(cells);
2445
+ }
2446
+ if (n.children) {
2447
+ for (const child of n.children) findRows(child);
2448
+ }
2449
+ }
2450
+ findRows(node);
2451
+ if (rows.length === 0) return null;
2452
+ return { headers, rows };
2453
+ }
2454
+ function extractKeyValueFromText(text) {
2455
+ const pairs = [];
2456
+ const lines = text.split("\n").map((l) => l.trim()).filter(Boolean);
2457
+ for (const line of lines) {
2458
+ const match = line.match(/^([A-Z][A-Za-z0-9 ]{1,30})[:—]\s+(.+)$/);
2459
+ if (match) {
2460
+ pairs.push({ key: match[1].trim(), value: match[2].trim() });
2461
+ }
2462
+ }
2463
+ return pairs.slice(0, 20);
2464
+ }
2465
+
2255
2466
  // src/browser/page.ts
2256
2467
  var DEFAULT_TIMEOUT = 3e4;
2468
+ function normalizeAXCheckedValue(value) {
2469
+ if (typeof value === "boolean") {
2470
+ return value;
2471
+ }
2472
+ if (typeof value === "string") {
2473
+ if (value === "true") return true;
2474
+ if (value === "false") return false;
2475
+ }
2476
+ return void 0;
2477
+ }
2257
2478
  var EVENT_LISTENER_TRACKER_SCRIPT = `(() => {
2258
2479
  if (globalThis.__bpEventListenerTrackerInstalled) return;
2259
2480
  Object.defineProperty(globalThis, '__bpEventListenerTrackerInstalled', {
@@ -4039,7 +4260,9 @@ var Page = class {
4039
4260
  }
4040
4261
  }
4041
4262
  const disabled = node.properties?.find((p) => p.name === "disabled")?.value.value;
4042
- const checked = node.properties?.find((p) => p.name === "checked")?.value.value;
4263
+ const checked = normalizeAXCheckedValue(
4264
+ node.properties?.find((p) => p.name === "checked")?.value.value
4265
+ );
4043
4266
  return {
4044
4267
  role,
4045
4268
  name,
@@ -4095,7 +4318,9 @@ var Page = class {
4095
4318
  const ref = nodeRefs.get(node.nodeId);
4096
4319
  const name = node.name?.value ?? "";
4097
4320
  const disabled = node.properties?.find((p) => p.name === "disabled")?.value.value;
4098
- const checked = node.properties?.find((p) => p.name === "checked")?.value.value;
4321
+ const checked = normalizeAXCheckedValue(
4322
+ node.properties?.find((p) => p.name === "checked")?.value.value
4323
+ );
4099
4324
  const value = node.value?.value;
4100
4325
  const selector = node.backendDOMNodeId ? `[data-backend-node-id="${node.backendDOMNodeId}"]` : `[aria-label="${name}"]`;
4101
4326
  interactiveElements.push({
@@ -4162,6 +4387,45 @@ var Page = class {
4162
4387
  }
4163
4388
  }
4164
4389
  }
4390
+ // ============ Delta & Review ============
4391
+ /**
4392
+ * Capture current page state for delta comparison.
4393
+ * Call before an action, then call delta() again after and use computeDelta().
4394
+ */
4395
+ async captureState() {
4396
+ const [url, title, snapshot, forms, text] = await Promise.all([
4397
+ this.url(),
4398
+ this.title(),
4399
+ this.snapshot(),
4400
+ this.forms(),
4401
+ this.text()
4402
+ ]);
4403
+ return extractPageState(url, title, snapshot, forms, text);
4404
+ }
4405
+ /**
4406
+ * Compute what changed between two page states.
4407
+ * If no arguments: captures current state and returns it (for use as "before").
4408
+ * If one argument (before state): captures current state and computes delta.
4409
+ */
4410
+ async delta(before) {
4411
+ const currentState = await this.captureState();
4412
+ if (!before) return currentState;
4413
+ return computeDelta(before, currentState);
4414
+ }
4415
+ /**
4416
+ * Extract structured review surface from the current page.
4417
+ * Returns headings, form values, alerts, key-value pairs, tables, and status labels.
4418
+ */
4419
+ async review() {
4420
+ const [url, title, snapshot, forms, text] = await Promise.all([
4421
+ this.url(),
4422
+ this.title(),
4423
+ this.snapshot(),
4424
+ this.forms(),
4425
+ this.text()
4426
+ ]);
4427
+ return extractReview(url, title, snapshot, forms, text);
4428
+ }
4165
4429
  // ============ Batch Execution ============
4166
4430
  /**
4167
4431
  * Execute a batch of steps
@@ -5587,6 +5851,310 @@ function connect(options) {
5587
5851
  return Browser.connect(options);
5588
5852
  }
5589
5853
 
5854
+ // src/browser/fingerprint.ts
5855
+ function createFingerprint(node, context) {
5856
+ const role = node.role?.toLowerCase() ?? "";
5857
+ const name = node.name ?? "";
5858
+ let valueShape = "";
5859
+ if (node.value !== void 0) {
5860
+ valueShape = typeof node.value === "string" ? "text" : typeof node.value === "number" ? "number" : typeof node.value === "boolean" ? "boolean" : "other";
5861
+ }
5862
+ const stableAttrs = {};
5863
+ if (node.properties) {
5864
+ for (const key of ["id", "name", "type", "aria-label"]) {
5865
+ const val = node.properties[key];
5866
+ if (val !== void 0 && val !== null) {
5867
+ stableAttrs[key] = String(val);
5868
+ }
5869
+ }
5870
+ }
5871
+ return {
5872
+ role,
5873
+ name,
5874
+ valueShape,
5875
+ label: name,
5876
+ // label is typically the accessible name
5877
+ stableAttrs,
5878
+ nearestHeading: context.nearestHeading,
5879
+ siblingIndex: context.siblingIndex,
5880
+ sectionPath: [...context.headingTrail]
5881
+ };
5882
+ }
5883
+ function fingerprintKey(fp) {
5884
+ const parts = [fp.role, fp.name, fp.sectionPath.join(">")];
5885
+ if (fp.stableAttrs["id"]) parts.push(`id=${fp.stableAttrs["id"]}`);
5886
+ if (fp.stableAttrs["name"]) parts.push(`name=${fp.stableAttrs["name"]}`);
5887
+ return parts.join("|");
5888
+ }
5889
+ function fingerprintSimilarity(a, b) {
5890
+ let score = 0;
5891
+ let weight = 0;
5892
+ weight += 3;
5893
+ if (a.role === b.role) score += 3;
5894
+ else return 0;
5895
+ weight += 5;
5896
+ if (a.name && b.name && a.name === b.name) score += 5;
5897
+ else if (a.name && b.name && a.name.toLowerCase() === b.name.toLowerCase()) score += 4;
5898
+ weight += 3;
5899
+ const pathA = a.sectionPath.join(">");
5900
+ const pathB = b.sectionPath.join(">");
5901
+ if (pathA === pathB) score += 3;
5902
+ else if (pathA && pathB && (pathA.includes(pathB) || pathB.includes(pathA))) score += 1;
5903
+ const attrKeys = /* @__PURE__ */ new Set([...Object.keys(a.stableAttrs), ...Object.keys(b.stableAttrs)]);
5904
+ for (const key of attrKeys) {
5905
+ weight += 2;
5906
+ if (a.stableAttrs[key] && b.stableAttrs[key] && a.stableAttrs[key] === b.stableAttrs[key]) {
5907
+ score += 2;
5908
+ }
5909
+ }
5910
+ weight += 1;
5911
+ if (a.siblingIndex === b.siblingIndex) score += 1;
5912
+ return score / weight;
5913
+ }
5914
+ var INTERACTIVE_ROLES = /* @__PURE__ */ new Set([
5915
+ "button",
5916
+ "link",
5917
+ "textbox",
5918
+ "checkbox",
5919
+ "radio",
5920
+ "combobox",
5921
+ "listbox",
5922
+ "menuitem",
5923
+ "tab",
5924
+ "switch",
5925
+ "searchbox",
5926
+ "spinbutton",
5927
+ "slider"
5928
+ ]);
5929
+ function buildFingerprintMap(nodes) {
5930
+ const map = /* @__PURE__ */ new Map();
5931
+ function walk(nodeList, headingTrail, nearestHeading) {
5932
+ const roleCounts = /* @__PURE__ */ new Map();
5933
+ for (const node of nodeList) {
5934
+ const role = node.role?.toLowerCase() ?? "";
5935
+ let currentHeadingTrail = headingTrail;
5936
+ let currentNearestHeading = nearestHeading;
5937
+ if (role === "heading" && node.name) {
5938
+ currentHeadingTrail = [...headingTrail, node.name];
5939
+ currentNearestHeading = node.name;
5940
+ }
5941
+ if (INTERACTIVE_ROLES.has(role) && node.ref) {
5942
+ const siblingCount = roleCounts.get(role) ?? 0;
5943
+ roleCounts.set(role, siblingCount + 1);
5944
+ const fp = createFingerprint(node, {
5945
+ headingTrail: currentHeadingTrail,
5946
+ siblingIndex: siblingCount,
5947
+ nearestHeading: currentNearestHeading
5948
+ });
5949
+ map.set(node.ref, fp);
5950
+ }
5951
+ if (node.children) {
5952
+ walk(node.children, currentHeadingTrail, currentNearestHeading);
5953
+ }
5954
+ }
5955
+ }
5956
+ walk(nodes, [], "");
5957
+ return map;
5958
+ }
5959
+ function recoverStaleRef(staleFingerprint, currentFingerprints, threshold = 0.7) {
5960
+ let bestRef = null;
5961
+ let bestScore = 0;
5962
+ let secondBestScore = 0;
5963
+ for (const [ref, fp] of currentFingerprints) {
5964
+ const similarity = fingerprintSimilarity(staleFingerprint, fp);
5965
+ if (similarity > bestScore) {
5966
+ secondBestScore = bestScore;
5967
+ bestScore = similarity;
5968
+ bestRef = ref;
5969
+ } else if (similarity > secondBestScore) {
5970
+ secondBestScore = similarity;
5971
+ }
5972
+ }
5973
+ if (!bestRef || bestScore < threshold) return null;
5974
+ if (secondBestScore > 0 && bestScore - secondBestScore < 0.15) return null;
5975
+ return { ref: bestRef, confidence: bestScore };
5976
+ }
5977
+
5978
+ // src/browser/overlay-detect.ts
5979
+ async function detectOverlay(page) {
5980
+ const result = await page.evaluate(`(() => {
5981
+ // Check for role="dialog" or role="alertdialog"
5982
+ const dialogs = document.querySelectorAll('[role="dialog"], [role="alertdialog"], dialog[open]');
5983
+ for (const d of dialogs) {
5984
+ if (d.offsetParent !== null || getComputedStyle(d).display !== 'none') {
5985
+ return {
5986
+ hasOverlay: true,
5987
+ overlaySelector: d.id ? '#' + d.id : (d.getAttribute('role') ? '[role="' + d.getAttribute('role') + '"]' : 'dialog'),
5988
+ overlayText: (d.textContent || '').trim().slice(0, 200),
5989
+ };
5990
+ }
5991
+ }
5992
+
5993
+ // Check for fixed/absolute positioned elements with high z-index that look like modals
5994
+ const allElements = document.querySelectorAll('*');
5995
+ for (const el of allElements) {
5996
+ const style = getComputedStyle(el);
5997
+ if (
5998
+ (style.position === 'fixed' || style.position === 'absolute') &&
5999
+ parseInt(style.zIndex || '0', 10) > 999 &&
6000
+ el.offsetWidth > 100 &&
6001
+ el.offsetHeight > 100 &&
6002
+ style.display !== 'none' &&
6003
+ style.visibility !== 'hidden'
6004
+ ) {
6005
+ const text = (el.textContent || '').trim();
6006
+ if (text.length > 10) {
6007
+ return {
6008
+ hasOverlay: true,
6009
+ overlaySelector: el.id ? '#' + el.id : null,
6010
+ overlayText: text.slice(0, 200),
6011
+ };
6012
+ }
6013
+ }
6014
+ }
6015
+
6016
+ return { hasOverlay: false };
6017
+ })()`);
6018
+ return result ?? { hasOverlay: false };
6019
+ }
6020
+
6021
+ // src/browser/safe-submit.ts
6022
+ async function submitAndVerify(page, options) {
6023
+ const {
6024
+ selector,
6025
+ method = "enter+click",
6026
+ expectAny,
6027
+ expectAll,
6028
+ failIf,
6029
+ dangerous = false,
6030
+ timeout = 3e4,
6031
+ waitForNavigation: waitForNavigation2 = "auto"
6032
+ } = options;
6033
+ const startTime = Date.now();
6034
+ const allConditions = [...expectAny ?? [], ...expectAll ?? [], ...failIf ?? []];
6035
+ const needsNetwork = allConditions.some((c) => c.kind === "networkResponse");
6036
+ const needsSignature = allConditions.some((c) => c.kind === "stateSignatureChanges");
6037
+ let networkTracker;
6038
+ let beforeSignature;
6039
+ if (needsNetwork) {
6040
+ networkTracker = new NetworkResponseTracker();
6041
+ networkTracker.start(page.cdpClient);
6042
+ }
6043
+ if (needsSignature) {
6044
+ beforeSignature = await captureStateSignature(page);
6045
+ }
6046
+ try {
6047
+ await page.submit(selector, {
6048
+ timeout,
6049
+ method,
6050
+ waitForNavigation: waitForNavigation2
6051
+ });
6052
+ if (networkTracker) networkTracker.stop(page.cdpClient);
6053
+ if (allConditions.length === 0) {
6054
+ return {
6055
+ submitted: true,
6056
+ outcomeStatus: "success",
6057
+ matchedConditions: [],
6058
+ retrySafe: !dangerous,
6059
+ durationMs: Date.now() - startTime
6060
+ };
6061
+ }
6062
+ const outcome = await evaluateOutcome(page, {
6063
+ expectAny,
6064
+ expectAll,
6065
+ failIf,
6066
+ dangerous,
6067
+ networkTracker,
6068
+ beforeSignature
6069
+ });
6070
+ return {
6071
+ submitted: true,
6072
+ outcomeStatus: outcome.outcomeStatus,
6073
+ matchedConditions: outcome.matchedConditions,
6074
+ retrySafe: outcome.retrySafe,
6075
+ durationMs: Date.now() - startTime
6076
+ };
6077
+ } catch (error) {
6078
+ if (networkTracker) networkTracker.stop(page.cdpClient);
6079
+ return {
6080
+ submitted: false,
6081
+ outcomeStatus: "failed",
6082
+ matchedConditions: [],
6083
+ retrySafe: !dangerous,
6084
+ durationMs: Date.now() - startTime,
6085
+ error: error instanceof Error ? error.message : String(error)
6086
+ };
6087
+ }
6088
+ }
6089
+
6090
+ // src/runtime/clock.ts
6091
+ function now() {
6092
+ return Date.now();
6093
+ }
6094
+
6095
+ // src/browser/target-pin.ts
6096
+ function createTargetFingerprint(targetId, url, title) {
6097
+ return {
6098
+ url,
6099
+ title,
6100
+ originalTargetId: targetId,
6101
+ pinnedAt: now()
6102
+ };
6103
+ }
6104
+ function scoreCandidate(candidate, pin) {
6105
+ if (candidate.targetId === pin.originalTargetId) return 1;
6106
+ let score = 0;
6107
+ if (candidate.url && pin.url) {
6108
+ if (candidate.url === pin.url) {
6109
+ score += 0.6;
6110
+ } else {
6111
+ try {
6112
+ const candidateOrigin = new URL(candidate.url).origin;
6113
+ const pinOrigin = new URL(pin.url).origin;
6114
+ if (candidateOrigin === pinOrigin) score += 0.3;
6115
+ } catch {
6116
+ }
6117
+ }
6118
+ }
6119
+ if (candidate.title && pin.title) {
6120
+ if (candidate.title === pin.title) {
6121
+ score += 0.3;
6122
+ } else if (candidate.title.includes(pin.title) || pin.title.includes(candidate.title)) {
6123
+ score += 0.15;
6124
+ }
6125
+ }
6126
+ if (candidate.type !== "page") score *= 0.5;
6127
+ return Math.min(score, 0.95);
6128
+ }
6129
+ function recoverPinnedTarget(pin, targets, threshold = 0.4) {
6130
+ if (targets.length === 0) return null;
6131
+ let bestTarget = null;
6132
+ let bestScore = 0;
6133
+ for (const target of targets) {
6134
+ const score = scoreCandidate(target, pin);
6135
+ if (score > bestScore) {
6136
+ bestScore = score;
6137
+ bestTarget = target;
6138
+ }
6139
+ }
6140
+ if (!bestTarget || bestScore < threshold) return null;
6141
+ let method;
6142
+ if (bestTarget.targetId === pin.originalTargetId) {
6143
+ method = "exact";
6144
+ } else if (bestTarget.url === pin.url) {
6145
+ method = "url_match";
6146
+ } else if (bestTarget.title === pin.title) {
6147
+ method = "title_match";
6148
+ } else {
6149
+ method = "best_guess";
6150
+ }
6151
+ return {
6152
+ targetId: bestTarget.targetId,
6153
+ method,
6154
+ confidence: bestScore
6155
+ };
6156
+ }
6157
+
5590
6158
  export {
5591
6159
  bufferToBase64,
5592
6160
  calculateRMS,
@@ -5602,7 +6170,19 @@ export {
5602
6170
  waitForAnyElement,
5603
6171
  waitForNavigation,
5604
6172
  waitForNetworkIdle,
6173
+ extractPageState,
6174
+ computeDelta,
6175
+ extractReview,
5605
6176
  Page,
5606
6177
  Browser,
5607
- connect
6178
+ connect,
6179
+ createFingerprint,
6180
+ fingerprintKey,
6181
+ fingerprintSimilarity,
6182
+ buildFingerprintMap,
6183
+ recoverStaleRef,
6184
+ detectOverlay,
6185
+ submitAndVerify,
6186
+ createTargetFingerprint,
6187
+ recoverPinnedTarget
5608
6188
  };
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-LCNFBXB5.mjs";
4
4
  import {
5
5
  Page
6
- } from "./chunk-6GBYX7C2.mjs";
6
+ } from "./chunk-MRY3HRFJ.mjs";
7
7
 
8
8
  // src/providers/browserbase.ts
9
9
  var BrowserBaseProvider = class {