opensteer 0.4.14 → 0.5.0

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.
@@ -1192,9 +1192,6 @@ var ENSURE_NAME_SHIM_SCRIPT = `
1192
1192
  `;
1193
1193
  var OS_FRAME_TOKEN_KEY = "__opensteerFrameToken";
1194
1194
  var OS_INSTANCE_TOKEN_KEY = "__opensteerInstanceToken";
1195
- var OS_COUNTER_OWNER_KEY = "__opensteerCounterOwner";
1196
- var OS_COUNTER_VALUE_KEY = "__opensteerCounterValue";
1197
- var OS_COUNTER_NEXT_KEY = "__opensteerCounterNext";
1198
1195
 
1199
1196
  // src/element-path/build.ts
1200
1197
  var MAX_ATTRIBUTE_VALUE_LENGTH = 300;
@@ -2985,567 +2982,179 @@ function cleanForAction(html) {
2985
2982
  return compactHtml(htmlOut);
2986
2983
  }
2987
2984
 
2988
- // src/extract-value-normalization.ts
2989
- var URL_LIST_ATTRIBUTES = /* @__PURE__ */ new Set(["srcset", "imagesrcset", "ping"]);
2990
- function normalizeExtractedValue(raw, attribute) {
2991
- if (raw == null) return null;
2992
- const rawText = String(raw);
2993
- if (!rawText.trim()) return null;
2994
- const normalizedAttribute = String(attribute || "").trim().toLowerCase();
2995
- if (URL_LIST_ATTRIBUTES.has(normalizedAttribute)) {
2996
- const singleValue = pickSingleListAttributeValue(
2997
- normalizedAttribute,
2998
- rawText
2999
- ).trim();
3000
- return singleValue || null;
3001
- }
3002
- const text = rawText.replace(/\s+/g, " ").trim();
3003
- return text || null;
3004
- }
3005
- function pickSingleListAttributeValue(attribute, raw) {
3006
- if (attribute === "ping") {
3007
- const firstUrl = raw.trim().split(/\s+/)[0] || "";
3008
- return firstUrl.trim();
3009
- }
3010
- if (attribute === "srcset" || attribute === "imagesrcset") {
3011
- const picked = pickBestSrcsetCandidate(raw);
3012
- if (picked) return picked;
3013
- return pickFirstSrcsetToken(raw) || "";
3014
- }
3015
- return raw.trim();
3016
- }
3017
- function pickBestSrcsetCandidate(raw) {
3018
- const candidates = parseSrcsetCandidates(raw);
3019
- if (!candidates.length) return null;
3020
- const widthCandidates = candidates.filter(
3021
- (candidate) => typeof candidate.width === "number" && Number.isFinite(candidate.width) && candidate.width > 0
3022
- );
3023
- if (widthCandidates.length) {
3024
- return widthCandidates.reduce(
3025
- (best, candidate) => candidate.width > best.width ? candidate : best
3026
- ).url;
3027
- }
3028
- const densityCandidates = candidates.filter(
3029
- (candidate) => typeof candidate.density === "number" && Number.isFinite(candidate.density) && candidate.density > 0
3030
- );
3031
- if (densityCandidates.length) {
3032
- return densityCandidates.reduce(
3033
- (best, candidate) => candidate.density > best.density ? candidate : best
3034
- ).url;
3035
- }
3036
- return candidates[0]?.url || null;
3037
- }
3038
- function parseSrcsetCandidates(raw) {
3039
- const text = String(raw || "").trim();
3040
- if (!text) return [];
3041
- const out = [];
3042
- let index = 0;
3043
- while (index < text.length) {
3044
- index = skipSeparators(text, index);
3045
- if (index >= text.length) break;
3046
- const urlToken = readUrlToken(text, index);
3047
- index = urlToken.nextIndex;
3048
- const url = urlToken.value.trim();
3049
- if (!url) continue;
3050
- index = skipWhitespace(text, index);
3051
- const descriptors = [];
3052
- while (index < text.length && text[index] !== ",") {
3053
- const descriptorToken = readDescriptorToken(text, index);
3054
- if (!descriptorToken.value) {
3055
- index = descriptorToken.nextIndex;
3056
- continue;
3057
- }
3058
- descriptors.push(descriptorToken.value);
3059
- index = descriptorToken.nextIndex;
3060
- index = skipWhitespace(text, index);
3061
- }
3062
- if (index < text.length && text[index] === ",") {
3063
- index += 1;
3064
- }
3065
- let width = null;
3066
- let density = null;
3067
- for (const descriptor of descriptors) {
3068
- const token = descriptor.trim().toLowerCase();
3069
- if (!token) continue;
3070
- const widthMatch = token.match(/^(\d+)w$/);
3071
- if (widthMatch) {
3072
- const parsed = Number.parseInt(widthMatch[1], 10);
3073
- if (Number.isFinite(parsed)) {
3074
- width = parsed;
3075
- }
3076
- continue;
3077
- }
3078
- const densityMatch = token.match(/^(\d*\.?\d+)x$/);
3079
- if (densityMatch) {
3080
- const parsed = Number.parseFloat(densityMatch[1]);
3081
- if (Number.isFinite(parsed)) {
3082
- density = parsed;
3083
- }
3084
- }
3085
- }
3086
- out.push({
3087
- url,
3088
- width,
3089
- density
3090
- });
3091
- }
3092
- return out;
3093
- }
3094
- function pickFirstSrcsetToken(raw) {
3095
- const candidate = parseSrcsetCandidates(raw)[0];
3096
- if (candidate?.url) {
3097
- return candidate.url;
3098
- }
3099
- const text = String(raw || "");
3100
- const start = skipSeparators(text, 0);
3101
- if (start >= text.length) return null;
3102
- const firstToken = readUrlToken(text, start).value.trim();
3103
- return firstToken || null;
3104
- }
3105
- function skipWhitespace(value, index) {
3106
- let cursor = index;
3107
- while (cursor < value.length && /\s/.test(value[cursor])) {
3108
- cursor += 1;
3109
- }
3110
- return cursor;
3111
- }
3112
- function skipSeparators(value, index) {
3113
- let cursor = skipWhitespace(value, index);
3114
- while (cursor < value.length && value[cursor] === ",") {
3115
- cursor += 1;
3116
- cursor = skipWhitespace(value, cursor);
3117
- }
3118
- return cursor;
3119
- }
3120
- function readUrlToken(value, index) {
3121
- let cursor = index;
3122
- let out = "";
3123
- const isDataUrl = value.slice(index, index + 5).toLowerCase().startsWith("data:");
3124
- while (cursor < value.length) {
3125
- const char = value[cursor];
3126
- if (/\s/.test(char)) {
3127
- break;
3128
- }
3129
- if (char === "," && !isDataUrl) {
3130
- break;
3131
- }
3132
- out += char;
3133
- cursor += 1;
3134
- }
3135
- if (isDataUrl && out.endsWith(",") && cursor < value.length) {
3136
- out = out.slice(0, -1);
2985
+ // src/html/pipeline.ts
2986
+ import * as cheerio3 from "cheerio";
2987
+ function applyCleaner(mode, html) {
2988
+ switch (mode) {
2989
+ case "clickable":
2990
+ return cleanForClickable(html);
2991
+ case "scrollable":
2992
+ return cleanForScrollable(html);
2993
+ case "extraction":
2994
+ return cleanForExtraction(html);
2995
+ case "full":
2996
+ return cleanForFull(html);
2997
+ case "action":
2998
+ default:
2999
+ return cleanForAction(html);
3137
3000
  }
3138
- return {
3139
- value: out,
3140
- nextIndex: cursor
3141
- };
3142
3001
  }
3143
- function readDescriptorToken(value, index) {
3144
- let cursor = skipWhitespace(value, index);
3145
- let out = "";
3146
- while (cursor < value.length) {
3147
- const char = value[cursor];
3148
- if (char === "," || /\s/.test(char)) {
3149
- break;
3002
+ async function assignCounters(page, html, nodePaths, nodeMeta) {
3003
+ const $ = cheerio3.load(html, { xmlMode: false });
3004
+ const counterIndex = /* @__PURE__ */ new Map();
3005
+ let nextCounter = 1;
3006
+ const assignedByNodeId = /* @__PURE__ */ new Map();
3007
+ $("*").each(function() {
3008
+ const el = $(this);
3009
+ const nodeId = el.attr(OS_NODE_ID_ATTR);
3010
+ if (!nodeId) return;
3011
+ const counter = nextCounter++;
3012
+ assignedByNodeId.set(nodeId, counter);
3013
+ const path5 = nodePaths.get(nodeId);
3014
+ el.attr("c", String(counter));
3015
+ el.removeAttr(OS_NODE_ID_ATTR);
3016
+ if (path5) {
3017
+ counterIndex.set(counter, cloneElementPath(path5));
3150
3018
  }
3151
- out += char;
3152
- cursor += 1;
3019
+ });
3020
+ try {
3021
+ await syncLiveCounters(page, nodeMeta, assignedByNodeId);
3022
+ } catch (error) {
3023
+ await clearLiveCounters(page);
3024
+ throw error;
3153
3025
  }
3026
+ $(`[${OS_NODE_ID_ATTR}]`).removeAttr(OS_NODE_ID_ATTR);
3154
3027
  return {
3155
- value: out.trim(),
3156
- nextIndex: cursor
3028
+ html: $.html(),
3029
+ counterIndex
3157
3030
  };
3158
3031
  }
3159
-
3160
- // src/html/counter-runtime.ts
3161
- var CounterResolutionError = class extends Error {
3162
- code;
3163
- constructor(code, message) {
3164
- super(message);
3165
- this.name = "CounterResolutionError";
3166
- this.code = code;
3167
- }
3168
- };
3169
- async function ensureLiveCounters(page, nodeMeta, nodeIds) {
3170
- const out = /* @__PURE__ */ new Map();
3171
- if (!nodeIds.length) return out;
3172
- const grouped = /* @__PURE__ */ new Map();
3173
- for (const nodeId of nodeIds) {
3032
+ async function syncLiveCounters(page, nodeMeta, assignedByNodeId) {
3033
+ await clearLiveCounters(page);
3034
+ if (!assignedByNodeId.size) return;
3035
+ const groupedByFrame = /* @__PURE__ */ new Map();
3036
+ for (const [nodeId, counter] of assignedByNodeId.entries()) {
3174
3037
  const meta = nodeMeta.get(nodeId);
3175
- if (!meta) {
3176
- throw new CounterResolutionError(
3177
- "ERR_COUNTER_STALE_OR_NOT_FOUND",
3178
- `Missing metadata for node ${nodeId}. Run snapshot() again.`
3179
- );
3180
- }
3181
- const list = grouped.get(meta.frameToken) || [];
3038
+ if (!meta?.frameToken) continue;
3039
+ const list = groupedByFrame.get(meta.frameToken) || [];
3182
3040
  list.push({
3183
3041
  nodeId,
3184
- instanceToken: meta.instanceToken
3042
+ counter
3185
3043
  });
3186
- grouped.set(meta.frameToken, list);
3044
+ groupedByFrame.set(meta.frameToken, list);
3187
3045
  }
3046
+ if (!groupedByFrame.size) return;
3047
+ const failures = [];
3188
3048
  const framesByToken = await mapFramesByToken(page);
3189
- let nextCounter = await readGlobalNextCounter(page);
3190
- const usedCounters = /* @__PURE__ */ new Map();
3191
- for (const [frameToken, entries] of grouped.entries()) {
3049
+ for (const [frameToken, entries] of groupedByFrame.entries()) {
3192
3050
  const frame = framesByToken.get(frameToken);
3193
3051
  if (!frame) {
3194
- throw new CounterResolutionError(
3195
- "ERR_COUNTER_FRAME_UNAVAILABLE",
3196
- `Counter frame ${frameToken} is unavailable. Run snapshot() again.`
3197
- );
3052
+ for (const entry of entries) {
3053
+ failures.push({
3054
+ nodeId: entry.nodeId,
3055
+ counter: entry.counter,
3056
+ frameToken,
3057
+ reason: "frame_missing"
3058
+ });
3059
+ }
3060
+ continue;
3198
3061
  }
3199
- const result = await frame.evaluate(
3200
- ({
3201
- entries: entries2,
3202
- nodeAttr,
3203
- instanceTokenKey,
3204
- counterOwnerKey,
3205
- counterValueKey,
3206
- startCounter
3207
- }) => {
3208
- const helpers = {
3209
- pushNode(map, node) {
3210
- const nodeId = node.getAttribute(nodeAttr);
3211
- if (!nodeId) return;
3212
- const list = map.get(nodeId) || [];
3213
- list.push(node);
3214
- map.set(nodeId, list);
3215
- },
3216
- walk(map, root) {
3062
+ try {
3063
+ const unresolved = await frame.evaluate(
3064
+ ({ entries: entries2, nodeAttr }) => {
3065
+ const index = /* @__PURE__ */ new Map();
3066
+ const unresolved2 = [];
3067
+ const walk = (root) => {
3217
3068
  const children = Array.from(root.children);
3218
3069
  for (const child of children) {
3219
- helpers.pushNode(map, child);
3220
- helpers.walk(map, child);
3070
+ const nodeId = child.getAttribute(nodeAttr);
3071
+ if (nodeId) {
3072
+ const list = index.get(nodeId) || [];
3073
+ list.push(child);
3074
+ index.set(nodeId, list);
3075
+ }
3076
+ walk(child);
3221
3077
  if (child.shadowRoot) {
3222
- helpers.walk(map, child.shadowRoot);
3078
+ walk(child.shadowRoot);
3223
3079
  }
3224
3080
  }
3225
- },
3226
- buildNodeIndex() {
3227
- const map = /* @__PURE__ */ new Map();
3228
- helpers.walk(map, document);
3229
- return map;
3230
- }
3231
- };
3232
- const index = helpers.buildNodeIndex();
3233
- const assigned = [];
3234
- const failures = [];
3235
- let next = Math.max(1, Number(startCounter || 1));
3236
- for (const entry of entries2) {
3237
- const matches = index.get(entry.nodeId) || [];
3238
- if (!matches.length) {
3239
- failures.push({
3240
- nodeId: entry.nodeId,
3241
- reason: "missing"
3242
- });
3243
- continue;
3244
- }
3245
- if (matches.length !== 1) {
3246
- failures.push({
3247
- nodeId: entry.nodeId,
3248
- reason: "ambiguous"
3249
- });
3250
- continue;
3251
- }
3252
- const target = matches[0];
3253
- if (target[instanceTokenKey] !== entry.instanceToken) {
3254
- failures.push({
3255
- nodeId: entry.nodeId,
3256
- reason: "instance_mismatch"
3257
- });
3258
- continue;
3259
- }
3260
- const owned = target[counterOwnerKey] === true;
3261
- const runtimeCounter = Number(target[counterValueKey] || 0);
3262
- if (owned && Number.isFinite(runtimeCounter) && runtimeCounter > 0) {
3263
- target.setAttribute("c", String(runtimeCounter));
3264
- assigned.push({
3265
- nodeId: entry.nodeId,
3266
- counter: runtimeCounter
3267
- });
3268
- continue;
3081
+ };
3082
+ walk(document);
3083
+ for (const entry of entries2) {
3084
+ const matches = index.get(entry.nodeId) || [];
3085
+ if (matches.length !== 1) {
3086
+ unresolved2.push({
3087
+ nodeId: entry.nodeId,
3088
+ counter: entry.counter,
3089
+ matches: matches.length
3090
+ });
3091
+ continue;
3092
+ }
3093
+ matches[0].setAttribute("c", String(entry.counter));
3269
3094
  }
3270
- const counter = next++;
3271
- target.setAttribute("c", String(counter));
3272
- Object.defineProperty(target, counterOwnerKey, {
3273
- value: true,
3274
- writable: true,
3275
- configurable: true
3276
- });
3277
- Object.defineProperty(target, counterValueKey, {
3278
- value: counter,
3279
- writable: true,
3280
- configurable: true
3281
- });
3282
- assigned.push({ nodeId: entry.nodeId, counter });
3095
+ return unresolved2;
3096
+ },
3097
+ {
3098
+ entries,
3099
+ nodeAttr: OS_NODE_ID_ATTR
3283
3100
  }
3284
- return {
3285
- assigned,
3286
- failures,
3287
- nextCounter: next
3288
- };
3289
- },
3290
- {
3291
- entries,
3292
- nodeAttr: OS_NODE_ID_ATTR,
3293
- instanceTokenKey: OS_INSTANCE_TOKEN_KEY,
3294
- counterOwnerKey: OS_COUNTER_OWNER_KEY,
3295
- counterValueKey: OS_COUNTER_VALUE_KEY,
3296
- startCounter: nextCounter
3297
- }
3298
- );
3299
- if (result.failures.length) {
3300
- const first = result.failures[0];
3301
- throw buildCounterFailureError(first.nodeId, first.reason);
3302
- }
3303
- nextCounter = result.nextCounter;
3304
- for (const item of result.assigned) {
3305
- const existingNode = usedCounters.get(item.counter);
3306
- if (existingNode && existingNode !== item.nodeId) {
3307
- throw new CounterResolutionError(
3308
- "ERR_COUNTER_AMBIGUOUS",
3309
- `Counter ${item.counter} is assigned to multiple nodes (${existingNode}, ${item.nodeId}). Run snapshot() again.`
3310
- );
3101
+ );
3102
+ for (const entry of unresolved) {
3103
+ failures.push({
3104
+ nodeId: entry.nodeId,
3105
+ counter: entry.counter,
3106
+ frameToken,
3107
+ reason: "match_count",
3108
+ matches: entry.matches
3109
+ });
3110
+ }
3111
+ } catch {
3112
+ for (const entry of entries) {
3113
+ failures.push({
3114
+ nodeId: entry.nodeId,
3115
+ counter: entry.counter,
3116
+ frameToken,
3117
+ reason: "frame_unavailable"
3118
+ });
3311
3119
  }
3312
- usedCounters.set(item.counter, item.nodeId);
3313
- out.set(item.nodeId, item.counter);
3314
3120
  }
3315
3121
  }
3316
- await writeGlobalNextCounter(page, nextCounter);
3317
- return out;
3318
- }
3319
- async function resolveCounterElement(page, snapshot, counter) {
3320
- const binding = readBinding(snapshot, counter);
3321
- const framesByToken = await mapFramesByToken(page);
3322
- const frame = framesByToken.get(binding.frameToken);
3323
- if (!frame) {
3324
- throw new CounterResolutionError(
3325
- "ERR_COUNTER_FRAME_UNAVAILABLE",
3326
- `Counter ${counter} frame is unavailable. Run snapshot() again.`
3327
- );
3328
- }
3329
- const status = await frame.evaluate(
3330
- ({
3331
- nodeId,
3332
- instanceToken,
3333
- counter: counter2,
3334
- nodeAttr,
3335
- instanceTokenKey,
3336
- counterOwnerKey,
3337
- counterValueKey
3338
- }) => {
3339
- const helpers = {
3340
- walk(map, root) {
3341
- const children = Array.from(root.children);
3342
- for (const child of children) {
3343
- const id = child.getAttribute(nodeAttr);
3344
- if (id) {
3345
- const list = map.get(id) || [];
3346
- list.push(child);
3347
- map.set(id, list);
3348
- }
3349
- helpers.walk(map, child);
3350
- if (child.shadowRoot) {
3351
- helpers.walk(map, child.shadowRoot);
3352
- }
3353
- }
3354
- },
3355
- buildNodeIndex() {
3356
- const map = /* @__PURE__ */ new Map();
3357
- helpers.walk(map, document);
3358
- return map;
3359
- }
3360
- };
3361
- const matches = helpers.buildNodeIndex().get(nodeId) || [];
3362
- if (!matches.length) return "missing";
3363
- if (matches.length !== 1) return "ambiguous";
3364
- const target = matches[0];
3365
- if (target[instanceTokenKey] !== instanceToken) {
3366
- return "instance_mismatch";
3367
- }
3368
- if (target[counterOwnerKey] !== true) {
3369
- return "instance_mismatch";
3122
+ if (failures.length) {
3123
+ const preview = failures.slice(0, 3).map((failure) => {
3124
+ const base = `counter ${failure.counter} (nodeId "${failure.nodeId}") in frame "${failure.frameToken}"`;
3125
+ if (failure.reason === "frame_missing") {
3126
+ return `${base} could not be synchronized because the frame is missing.`;
3370
3127
  }
3371
- if (Number(target[counterValueKey] || 0) !== counter2) {
3372
- return "instance_mismatch";
3128
+ if (failure.reason === "frame_unavailable") {
3129
+ return `${base} could not be synchronized because frame evaluation failed.`;
3373
3130
  }
3374
- if (target.getAttribute("c") !== String(counter2)) {
3375
- return "instance_mismatch";
3376
- }
3377
- return "ok";
3378
- },
3379
- {
3380
- nodeId: binding.nodeId,
3381
- instanceToken: binding.instanceToken,
3382
- counter,
3383
- nodeAttr: OS_NODE_ID_ATTR,
3384
- instanceTokenKey: OS_INSTANCE_TOKEN_KEY,
3385
- counterOwnerKey: OS_COUNTER_OWNER_KEY,
3386
- counterValueKey: OS_COUNTER_VALUE_KEY
3387
- }
3388
- );
3389
- if (status !== "ok") {
3390
- throw buildCounterFailureError(binding.nodeId, status);
3131
+ return `${base} expected exactly one live node but found ${failure.matches ?? 0}.`;
3132
+ });
3133
+ const remaining = failures.length > 3 ? ` (+${failures.length - 3} more)` : "";
3134
+ throw new Error(
3135
+ `Failed to synchronize snapshot counters with the live DOM: ${preview.join(" ")}${remaining}`
3136
+ );
3391
3137
  }
3392
- const handle = await frame.evaluateHandle(
3393
- ({ nodeId, nodeAttr }) => {
3394
- const helpers = {
3395
- walk(matches, root) {
3138
+ }
3139
+ async function clearLiveCounters(page) {
3140
+ for (const frame of page.frames()) {
3141
+ try {
3142
+ await frame.evaluate(() => {
3143
+ const walk = (root) => {
3396
3144
  const children = Array.from(root.children);
3397
3145
  for (const child of children) {
3398
- if (child.getAttribute(nodeAttr) === nodeId) {
3399
- matches.push(child);
3400
- }
3401
- helpers.walk(matches, child);
3146
+ child.removeAttribute("c");
3147
+ walk(child);
3402
3148
  if (child.shadowRoot) {
3403
- helpers.walk(matches, child.shadowRoot);
3149
+ walk(child.shadowRoot);
3404
3150
  }
3405
3151
  }
3406
- },
3407
- findUniqueNode() {
3408
- const matches = [];
3409
- helpers.walk(matches, document);
3410
- if (matches.length !== 1) return null;
3411
- return matches[0];
3412
- }
3413
- };
3414
- return helpers.findUniqueNode();
3415
- },
3416
- {
3417
- nodeId: binding.nodeId,
3418
- nodeAttr: OS_NODE_ID_ATTR
3419
- }
3420
- );
3421
- const element = handle.asElement();
3422
- if (!element) {
3423
- await handle.dispose();
3424
- throw new CounterResolutionError(
3425
- "ERR_COUNTER_STALE_OR_NOT_FOUND",
3426
- `Counter ${counter} became stale. Run snapshot() again.`
3427
- );
3428
- }
3429
- return element;
3430
- }
3431
- async function resolveCountersBatch(page, snapshot, requests) {
3432
- const out = {};
3433
- if (!requests.length) return out;
3434
- const grouped = /* @__PURE__ */ new Map();
3435
- for (const request of requests) {
3436
- const binding = readBinding(snapshot, request.counter);
3437
- const list = grouped.get(binding.frameToken) || [];
3438
- list.push({
3439
- ...request,
3440
- ...binding
3441
- });
3442
- grouped.set(binding.frameToken, list);
3443
- }
3444
- const framesByToken = await mapFramesByToken(page);
3445
- for (const [frameToken, entries] of grouped.entries()) {
3446
- const frame = framesByToken.get(frameToken);
3447
- if (!frame) {
3448
- throw new CounterResolutionError(
3449
- "ERR_COUNTER_FRAME_UNAVAILABLE",
3450
- `Counter frame ${frameToken} is unavailable. Run snapshot() again.`
3451
- );
3452
- }
3453
- const result = await frame.evaluate(
3454
- ({
3455
- entries: entries2,
3456
- nodeAttr,
3457
- instanceTokenKey,
3458
- counterOwnerKey,
3459
- counterValueKey
3460
- }) => {
3461
- const values = [];
3462
- const failures = [];
3463
- const helpers = {
3464
- walk(map, root) {
3465
- const children = Array.from(root.children);
3466
- for (const child of children) {
3467
- const id = child.getAttribute(nodeAttr);
3468
- if (id) {
3469
- const list = map.get(id) || [];
3470
- list.push(child);
3471
- map.set(id, list);
3472
- }
3473
- helpers.walk(map, child);
3474
- if (child.shadowRoot) {
3475
- helpers.walk(map, child.shadowRoot);
3476
- }
3477
- }
3478
- },
3479
- buildNodeIndex() {
3480
- const map = /* @__PURE__ */ new Map();
3481
- helpers.walk(map, document);
3482
- return map;
3483
- },
3484
- readRawValue(element, attribute) {
3485
- if (attribute) {
3486
- return element.getAttribute(attribute);
3487
- }
3488
- return element.textContent;
3489
- }
3490
3152
  };
3491
- const index = helpers.buildNodeIndex();
3492
- for (const entry of entries2) {
3493
- const matches = index.get(entry.nodeId) || [];
3494
- if (!matches.length) {
3495
- failures.push({
3496
- nodeId: entry.nodeId,
3497
- reason: "missing"
3498
- });
3499
- continue;
3500
- }
3501
- if (matches.length !== 1) {
3502
- failures.push({
3503
- nodeId: entry.nodeId,
3504
- reason: "ambiguous"
3505
- });
3506
- continue;
3507
- }
3508
- const target = matches[0];
3509
- if (target[instanceTokenKey] !== entry.instanceToken || target[counterOwnerKey] !== true || Number(target[counterValueKey] || 0) !== entry.counter || target.getAttribute("c") !== String(entry.counter)) {
3510
- failures.push({
3511
- nodeId: entry.nodeId,
3512
- reason: "instance_mismatch"
3513
- });
3514
- continue;
3515
- }
3516
- values.push({
3517
- key: entry.key,
3518
- value: helpers.readRawValue(target, entry.attribute)
3519
- });
3520
- }
3521
- return {
3522
- values,
3523
- failures
3524
- };
3525
- },
3526
- {
3527
- entries,
3528
- nodeAttr: OS_NODE_ID_ATTR,
3529
- instanceTokenKey: OS_INSTANCE_TOKEN_KEY,
3530
- counterOwnerKey: OS_COUNTER_OWNER_KEY,
3531
- counterValueKey: OS_COUNTER_VALUE_KEY
3532
- }
3533
- );
3534
- if (result.failures.length) {
3535
- const first = result.failures[0];
3536
- throw buildCounterFailureError(first.nodeId, first.reason);
3537
- }
3538
- const attributeByKey = new Map(
3539
- entries.map((entry) => [entry.key, entry.attribute])
3540
- );
3541
- for (const item of result.values) {
3542
- out[item.key] = normalizeExtractedValue(
3543
- item.value,
3544
- attributeByKey.get(item.key)
3545
- );
3153
+ walk(document);
3154
+ });
3155
+ } catch {
3546
3156
  }
3547
3157
  }
3548
- return out;
3549
3158
  }
3550
3159
  async function mapFramesByToken(page) {
3551
3160
  const out = /* @__PURE__ */ new Map();
@@ -3567,182 +3176,6 @@ async function readFrameToken(frame) {
3567
3176
  return null;
3568
3177
  }
3569
3178
  }
3570
- async function readGlobalNextCounter(page) {
3571
- const current = await page.mainFrame().evaluate((counterNextKey) => {
3572
- const win = window;
3573
- return Number(win[counterNextKey] || 0);
3574
- }, OS_COUNTER_NEXT_KEY).catch(() => 0);
3575
- if (Number.isFinite(current) && current > 0) {
3576
- return current;
3577
- }
3578
- let max = 0;
3579
- for (const frame of page.frames()) {
3580
- try {
3581
- const frameMax = await frame.evaluate(
3582
- ({ nodeAttr, counterOwnerKey, counterValueKey }) => {
3583
- let localMax = 0;
3584
- const helpers = {
3585
- walk(root) {
3586
- const children = Array.from(
3587
- root.children
3588
- );
3589
- for (const child of children) {
3590
- const candidate = child;
3591
- const hasNodeId = child.hasAttribute(nodeAttr);
3592
- const owned = candidate[counterOwnerKey] === true;
3593
- if (hasNodeId && owned) {
3594
- const value = Number(
3595
- candidate[counterValueKey] || 0
3596
- );
3597
- if (Number.isFinite(value) && value > localMax) {
3598
- localMax = value;
3599
- }
3600
- }
3601
- helpers.walk(child);
3602
- if (child.shadowRoot) {
3603
- helpers.walk(child.shadowRoot);
3604
- }
3605
- }
3606
- }
3607
- };
3608
- helpers.walk(document);
3609
- return localMax;
3610
- },
3611
- {
3612
- nodeAttr: OS_NODE_ID_ATTR,
3613
- counterOwnerKey: OS_COUNTER_OWNER_KEY,
3614
- counterValueKey: OS_COUNTER_VALUE_KEY
3615
- }
3616
- );
3617
- if (frameMax > max) {
3618
- max = frameMax;
3619
- }
3620
- } catch {
3621
- }
3622
- }
3623
- const next = max + 1;
3624
- await writeGlobalNextCounter(page, next);
3625
- return next;
3626
- }
3627
- async function writeGlobalNextCounter(page, nextCounter) {
3628
- await page.mainFrame().evaluate(
3629
- ({ counterNextKey, nextCounter: nextCounter2 }) => {
3630
- const win = window;
3631
- win[counterNextKey] = nextCounter2;
3632
- },
3633
- {
3634
- counterNextKey: OS_COUNTER_NEXT_KEY,
3635
- nextCounter
3636
- }
3637
- ).catch(() => void 0);
3638
- }
3639
- function readBinding(snapshot, counter) {
3640
- if (!snapshot.counterBindings) {
3641
- throw new CounterResolutionError(
3642
- "ERR_COUNTER_NOT_FOUND",
3643
- `Counter ${counter} is unavailable because this snapshot has no counter bindings. Run snapshot() with counters first.`
3644
- );
3645
- }
3646
- const binding = snapshot.counterBindings.get(counter);
3647
- if (!binding) {
3648
- throw new CounterResolutionError(
3649
- "ERR_COUNTER_NOT_FOUND",
3650
- `Counter ${counter} was not found in the current snapshot. Run snapshot() again.`
3651
- );
3652
- }
3653
- if (binding.sessionId !== snapshot.snapshotSessionId) {
3654
- throw new CounterResolutionError(
3655
- "ERR_COUNTER_STALE_OR_NOT_FOUND",
3656
- `Counter ${counter} is stale for this snapshot session. Run snapshot() again.`
3657
- );
3658
- }
3659
- return binding;
3660
- }
3661
- function buildCounterFailureError(nodeId, reason) {
3662
- if (reason === "ambiguous") {
3663
- return new CounterResolutionError(
3664
- "ERR_COUNTER_AMBIGUOUS",
3665
- `Counter target is ambiguous for node ${nodeId}. Run snapshot() again.`
3666
- );
3667
- }
3668
- return new CounterResolutionError(
3669
- "ERR_COUNTER_STALE_OR_NOT_FOUND",
3670
- `Counter target is stale or missing for node ${nodeId}. Run snapshot() again.`
3671
- );
3672
- }
3673
-
3674
- // src/html/pipeline.ts
3675
- import * as cheerio3 from "cheerio";
3676
- import { randomUUID } from "crypto";
3677
- function applyCleaner(mode, html) {
3678
- switch (mode) {
3679
- case "clickable":
3680
- return cleanForClickable(html);
3681
- case "scrollable":
3682
- return cleanForScrollable(html);
3683
- case "extraction":
3684
- return cleanForExtraction(html);
3685
- case "full":
3686
- return cleanForFull(html);
3687
- case "action":
3688
- default:
3689
- return cleanForAction(html);
3690
- }
3691
- }
3692
- async function assignCounters(page, html, nodePaths, nodeMeta, snapshotSessionId) {
3693
- const $ = cheerio3.load(html, { xmlMode: false });
3694
- const counterIndex = /* @__PURE__ */ new Map();
3695
- const counterBindings = /* @__PURE__ */ new Map();
3696
- const orderedNodeIds = [];
3697
- $("*").each(function() {
3698
- const el = $(this);
3699
- const nodeId = el.attr(OS_NODE_ID_ATTR);
3700
- if (!nodeId) return;
3701
- orderedNodeIds.push(nodeId);
3702
- });
3703
- const countersByNodeId = await ensureLiveCounters(
3704
- page,
3705
- nodeMeta,
3706
- orderedNodeIds
3707
- );
3708
- $("*").each(function() {
3709
- const el = $(this);
3710
- const nodeId = el.attr(OS_NODE_ID_ATTR);
3711
- if (!nodeId) return;
3712
- const path5 = nodePaths.get(nodeId);
3713
- const meta = nodeMeta.get(nodeId);
3714
- const counter = countersByNodeId.get(nodeId);
3715
- if (counter == null || !Number.isFinite(counter)) {
3716
- throw new Error(
3717
- `Counter assignment failed for node ${nodeId}. Run snapshot() again.`
3718
- );
3719
- }
3720
- if (counterBindings.has(counter) && counterBindings.get(counter)?.nodeId !== nodeId) {
3721
- throw new Error(
3722
- `Counter ${counter} was assigned to multiple nodes. Run snapshot() again.`
3723
- );
3724
- }
3725
- el.attr("c", String(counter));
3726
- el.removeAttr(OS_NODE_ID_ATTR);
3727
- if (path5) {
3728
- counterIndex.set(counter, cloneElementPath(path5));
3729
- }
3730
- if (meta) {
3731
- counterBindings.set(counter, {
3732
- sessionId: snapshotSessionId,
3733
- frameToken: meta.frameToken,
3734
- nodeId,
3735
- instanceToken: meta.instanceToken
3736
- });
3737
- }
3738
- });
3739
- $(`[${OS_NODE_ID_ATTR}]`).removeAttr(OS_NODE_ID_ATTR);
3740
- return {
3741
- html: $.html(),
3742
- counterIndex,
3743
- counterBindings
3744
- };
3745
- }
3746
3179
  function stripNodeIds(html) {
3747
3180
  if (!html.includes(OS_NODE_ID_ATTR)) return html;
3748
3181
  const $ = cheerio3.load(html, { xmlMode: false });
@@ -3750,7 +3183,6 @@ function stripNodeIds(html) {
3750
3183
  return $.html();
3751
3184
  }
3752
3185
  async function prepareSnapshot(page, options = {}) {
3753
- const snapshotSessionId = randomUUID();
3754
3186
  const mode = options.mode ?? "action";
3755
3187
  const withCounters = options.withCounters ?? true;
3756
3188
  const shouldMarkInteractive = options.markInteractive ?? true;
@@ -3763,18 +3195,15 @@ async function prepareSnapshot(page, options = {}) {
3763
3195
  const reducedHtml = applyCleaner(mode, processedHtml);
3764
3196
  let cleanedHtml = reducedHtml;
3765
3197
  let counterIndex = null;
3766
- let counterBindings = null;
3767
3198
  if (withCounters) {
3768
3199
  const counted = await assignCounters(
3769
3200
  page,
3770
3201
  reducedHtml,
3771
3202
  serialized.nodePaths,
3772
- serialized.nodeMeta,
3773
- snapshotSessionId
3203
+ serialized.nodeMeta
3774
3204
  );
3775
3205
  cleanedHtml = counted.html;
3776
3206
  counterIndex = counted.counterIndex;
3777
- counterBindings = counted.counterBindings;
3778
3207
  } else {
3779
3208
  cleanedHtml = stripNodeIds(cleanedHtml);
3780
3209
  }
@@ -3783,15 +3212,13 @@ async function prepareSnapshot(page, options = {}) {
3783
3212
  cleanedHtml = $unwrap("body").html()?.trim() || cleanedHtml;
3784
3213
  }
3785
3214
  return {
3786
- snapshotSessionId,
3787
3215
  mode,
3788
3216
  url: page.url(),
3789
3217
  rawHtml,
3790
3218
  processedHtml,
3791
3219
  reducedHtml,
3792
3220
  cleanedHtml,
3793
- counterIndex,
3794
- counterBindings
3221
+ counterIndex
3795
3222
  };
3796
3223
  }
3797
3224
 
@@ -3920,111 +3347,512 @@ function buildTargetNotFoundMessage(domPath, diagnostics) {
3920
3347
  return `${base} Tried ${Math.max(diagnostics.length, 0)} candidates.`;
3921
3348
  return `${base} Target depth ${depth}. Candidate counts: ${sample}.`;
3922
3349
  }
3923
- function selectInDocument(selectors) {
3924
- let fallback = null;
3925
- for (const selector of selectors) {
3926
- if (!selector) continue;
3927
- let count = 0;
3928
- try {
3929
- count = document.querySelectorAll(selector).length;
3930
- } catch {
3931
- count = 0;
3350
+ function selectInDocument(selectors) {
3351
+ let fallback = null;
3352
+ for (const selector of selectors) {
3353
+ if (!selector) continue;
3354
+ let count = 0;
3355
+ try {
3356
+ count = document.querySelectorAll(selector).length;
3357
+ } catch {
3358
+ count = 0;
3359
+ }
3360
+ if (count === 1) {
3361
+ return {
3362
+ selector,
3363
+ count,
3364
+ mode: "unique"
3365
+ };
3366
+ }
3367
+ if (count > 1 && !fallback) {
3368
+ fallback = {
3369
+ selector,
3370
+ count,
3371
+ mode: "fallback"
3372
+ };
3373
+ }
3374
+ }
3375
+ return fallback;
3376
+ }
3377
+ function selectInRoot(root, selectors) {
3378
+ if (!(root instanceof ShadowRoot)) return null;
3379
+ let fallback = null;
3380
+ for (const selector of selectors) {
3381
+ if (!selector) continue;
3382
+ let count = 0;
3383
+ try {
3384
+ count = root.querySelectorAll(selector).length;
3385
+ } catch {
3386
+ count = 0;
3387
+ }
3388
+ if (count === 1) {
3389
+ return {
3390
+ selector,
3391
+ count,
3392
+ mode: "unique"
3393
+ };
3394
+ }
3395
+ if (count > 1 && !fallback) {
3396
+ fallback = {
3397
+ selector,
3398
+ count,
3399
+ mode: "fallback"
3400
+ };
3401
+ }
3402
+ }
3403
+ return fallback;
3404
+ }
3405
+ function countInDocument(selectors) {
3406
+ const out = [];
3407
+ for (const selector of selectors) {
3408
+ if (!selector) continue;
3409
+ let count = 0;
3410
+ try {
3411
+ count = document.querySelectorAll(selector).length;
3412
+ } catch {
3413
+ count = 0;
3414
+ }
3415
+ out.push({ selector, count });
3416
+ }
3417
+ return out;
3418
+ }
3419
+ function countInRoot(root, selectors) {
3420
+ if (!(root instanceof ShadowRoot)) return [];
3421
+ const out = [];
3422
+ for (const selector of selectors) {
3423
+ if (!selector) continue;
3424
+ let count = 0;
3425
+ try {
3426
+ count = root.querySelectorAll(selector).length;
3427
+ } catch {
3428
+ count = 0;
3429
+ }
3430
+ out.push({ selector, count });
3431
+ }
3432
+ return out;
3433
+ }
3434
+ function isPathDebugEnabled() {
3435
+ const value = process.env.OPENSTEER_DEBUG_PATH || process.env.OPENSTEER_DEBUG || process.env.DEBUG_SELECTORS;
3436
+ if (!value) return false;
3437
+ const normalized = value.trim().toLowerCase();
3438
+ return normalized === "1" || normalized === "true";
3439
+ }
3440
+ function debugPath(message, data) {
3441
+ if (!isPathDebugEnabled()) return;
3442
+ if (data !== void 0) {
3443
+ console.log(`[opensteer:path] ${message}`, data);
3444
+ } else {
3445
+ console.log(`[opensteer:path] ${message}`);
3446
+ }
3447
+ }
3448
+ async function disposeHandle(handle) {
3449
+ if (!handle) return;
3450
+ try {
3451
+ await handle.dispose();
3452
+ } catch {
3453
+ }
3454
+ }
3455
+
3456
+ // src/extract-value-normalization.ts
3457
+ var URL_LIST_ATTRIBUTES = /* @__PURE__ */ new Set(["srcset", "imagesrcset", "ping"]);
3458
+ function normalizeExtractedValue(raw, attribute) {
3459
+ if (raw == null) return null;
3460
+ const rawText = String(raw);
3461
+ if (!rawText.trim()) return null;
3462
+ const normalizedAttribute = String(attribute || "").trim().toLowerCase();
3463
+ if (URL_LIST_ATTRIBUTES.has(normalizedAttribute)) {
3464
+ const singleValue = pickSingleListAttributeValue(
3465
+ normalizedAttribute,
3466
+ rawText
3467
+ ).trim();
3468
+ return singleValue || null;
3469
+ }
3470
+ const text = rawText.replace(/\s+/g, " ").trim();
3471
+ return text || null;
3472
+ }
3473
+ function pickSingleListAttributeValue(attribute, raw) {
3474
+ if (attribute === "ping") {
3475
+ const firstUrl = raw.trim().split(/\s+/)[0] || "";
3476
+ return firstUrl.trim();
3477
+ }
3478
+ if (attribute === "srcset" || attribute === "imagesrcset") {
3479
+ const picked = pickBestSrcsetCandidate(raw);
3480
+ if (picked) return picked;
3481
+ return pickFirstSrcsetToken(raw) || "";
3482
+ }
3483
+ return raw.trim();
3484
+ }
3485
+ function pickBestSrcsetCandidate(raw) {
3486
+ const candidates = parseSrcsetCandidates(raw);
3487
+ if (!candidates.length) return null;
3488
+ const widthCandidates = candidates.filter(
3489
+ (candidate) => typeof candidate.width === "number" && Number.isFinite(candidate.width) && candidate.width > 0
3490
+ );
3491
+ if (widthCandidates.length) {
3492
+ return widthCandidates.reduce(
3493
+ (best, candidate) => candidate.width > best.width ? candidate : best
3494
+ ).url;
3495
+ }
3496
+ const densityCandidates = candidates.filter(
3497
+ (candidate) => typeof candidate.density === "number" && Number.isFinite(candidate.density) && candidate.density > 0
3498
+ );
3499
+ if (densityCandidates.length) {
3500
+ return densityCandidates.reduce(
3501
+ (best, candidate) => candidate.density > best.density ? candidate : best
3502
+ ).url;
3503
+ }
3504
+ return candidates[0]?.url || null;
3505
+ }
3506
+ function parseSrcsetCandidates(raw) {
3507
+ const text = String(raw || "").trim();
3508
+ if (!text) return [];
3509
+ const out = [];
3510
+ let index = 0;
3511
+ while (index < text.length) {
3512
+ index = skipSeparators(text, index);
3513
+ if (index >= text.length) break;
3514
+ const urlToken = readUrlToken(text, index);
3515
+ index = urlToken.nextIndex;
3516
+ const url = urlToken.value.trim();
3517
+ if (!url) continue;
3518
+ index = skipWhitespace(text, index);
3519
+ const descriptors = [];
3520
+ while (index < text.length && text[index] !== ",") {
3521
+ const descriptorToken = readDescriptorToken(text, index);
3522
+ if (!descriptorToken.value) {
3523
+ index = descriptorToken.nextIndex;
3524
+ continue;
3525
+ }
3526
+ descriptors.push(descriptorToken.value);
3527
+ index = descriptorToken.nextIndex;
3528
+ index = skipWhitespace(text, index);
3529
+ }
3530
+ if (index < text.length && text[index] === ",") {
3531
+ index += 1;
3532
+ }
3533
+ let width = null;
3534
+ let density = null;
3535
+ for (const descriptor of descriptors) {
3536
+ const token = descriptor.trim().toLowerCase();
3537
+ if (!token) continue;
3538
+ const widthMatch = token.match(/^(\d+)w$/);
3539
+ if (widthMatch) {
3540
+ const parsed = Number.parseInt(widthMatch[1], 10);
3541
+ if (Number.isFinite(parsed)) {
3542
+ width = parsed;
3543
+ }
3544
+ continue;
3545
+ }
3546
+ const densityMatch = token.match(/^(\d*\.?\d+)x$/);
3547
+ if (densityMatch) {
3548
+ const parsed = Number.parseFloat(densityMatch[1]);
3549
+ if (Number.isFinite(parsed)) {
3550
+ density = parsed;
3551
+ }
3552
+ }
3553
+ }
3554
+ out.push({
3555
+ url,
3556
+ width,
3557
+ density
3558
+ });
3559
+ }
3560
+ return out;
3561
+ }
3562
+ function pickFirstSrcsetToken(raw) {
3563
+ const candidate = parseSrcsetCandidates(raw)[0];
3564
+ if (candidate?.url) {
3565
+ return candidate.url;
3566
+ }
3567
+ const text = String(raw || "");
3568
+ const start = skipSeparators(text, 0);
3569
+ if (start >= text.length) return null;
3570
+ const firstToken = readUrlToken(text, start).value.trim();
3571
+ return firstToken || null;
3572
+ }
3573
+ function skipWhitespace(value, index) {
3574
+ let cursor = index;
3575
+ while (cursor < value.length && /\s/.test(value[cursor])) {
3576
+ cursor += 1;
3577
+ }
3578
+ return cursor;
3579
+ }
3580
+ function skipSeparators(value, index) {
3581
+ let cursor = skipWhitespace(value, index);
3582
+ while (cursor < value.length && value[cursor] === ",") {
3583
+ cursor += 1;
3584
+ cursor = skipWhitespace(value, cursor);
3585
+ }
3586
+ return cursor;
3587
+ }
3588
+ function readUrlToken(value, index) {
3589
+ let cursor = index;
3590
+ let out = "";
3591
+ const isDataUrl = value.slice(index, index + 5).toLowerCase().startsWith("data:");
3592
+ while (cursor < value.length) {
3593
+ const char = value[cursor];
3594
+ if (/\s/.test(char)) {
3595
+ break;
3596
+ }
3597
+ if (char === "," && !isDataUrl) {
3598
+ break;
3599
+ }
3600
+ out += char;
3601
+ cursor += 1;
3602
+ }
3603
+ if (isDataUrl && out.endsWith(",") && cursor < value.length) {
3604
+ out = out.slice(0, -1);
3605
+ }
3606
+ return {
3607
+ value: out,
3608
+ nextIndex: cursor
3609
+ };
3610
+ }
3611
+ function readDescriptorToken(value, index) {
3612
+ let cursor = skipWhitespace(value, index);
3613
+ let out = "";
3614
+ while (cursor < value.length) {
3615
+ const char = value[cursor];
3616
+ if (char === "," || /\s/.test(char)) {
3617
+ break;
3618
+ }
3619
+ out += char;
3620
+ cursor += 1;
3621
+ }
3622
+ return {
3623
+ value: out.trim(),
3624
+ nextIndex: cursor
3625
+ };
3626
+ }
3627
+
3628
+ // src/html/counter-runtime.ts
3629
+ var CounterResolutionError = class extends Error {
3630
+ code;
3631
+ constructor(code, message) {
3632
+ super(message);
3633
+ this.name = "CounterResolutionError";
3634
+ this.code = code;
3635
+ }
3636
+ };
3637
+ async function resolveCounterElement(page, counter) {
3638
+ const normalized = normalizeCounter(counter);
3639
+ if (normalized == null) {
3640
+ throw buildCounterNotFoundError(counter);
3641
+ }
3642
+ const scan = await scanCounterOccurrences(page, [normalized]);
3643
+ const entry = scan.get(normalized);
3644
+ if (!entry || entry.count <= 0 || !entry.frame) {
3645
+ throw buildCounterNotFoundError(counter);
3646
+ }
3647
+ if (entry.count > 1) {
3648
+ throw buildCounterAmbiguousError(counter);
3649
+ }
3650
+ const handle = await resolveUniqueHandleInFrame(entry.frame, normalized);
3651
+ const element = handle.asElement();
3652
+ if (!element) {
3653
+ await handle.dispose();
3654
+ throw buildCounterNotFoundError(counter);
3655
+ }
3656
+ return element;
3657
+ }
3658
+ async function resolveCountersBatch(page, requests) {
3659
+ const out = {};
3660
+ if (!requests.length) return out;
3661
+ const counters = dedupeCounters(requests);
3662
+ const scan = await scanCounterOccurrences(page, counters);
3663
+ for (const counter of counters) {
3664
+ const entry = scan.get(counter);
3665
+ if (entry.count > 1) {
3666
+ throw buildCounterAmbiguousError(counter);
3932
3667
  }
3933
- if (count === 1) {
3934
- return {
3935
- selector,
3936
- count,
3937
- mode: "unique"
3938
- };
3668
+ }
3669
+ const valueCache = /* @__PURE__ */ new Map();
3670
+ for (const request of requests) {
3671
+ const normalized = normalizeCounter(request.counter);
3672
+ if (normalized == null) {
3673
+ out[request.key] = null;
3674
+ continue;
3939
3675
  }
3940
- if (count > 1 && !fallback) {
3941
- fallback = {
3942
- selector,
3943
- count,
3944
- mode: "fallback"
3945
- };
3676
+ const entry = scan.get(normalized);
3677
+ if (!entry || entry.count <= 0 || !entry.frame) {
3678
+ out[request.key] = null;
3679
+ continue;
3946
3680
  }
3947
- }
3948
- return fallback;
3949
- }
3950
- function selectInRoot(root, selectors) {
3951
- if (!(root instanceof ShadowRoot)) return null;
3952
- let fallback = null;
3953
- for (const selector of selectors) {
3954
- if (!selector) continue;
3955
- let count = 0;
3956
- try {
3957
- count = root.querySelectorAll(selector).length;
3958
- } catch {
3959
- count = 0;
3681
+ const cacheKey = `${normalized}:${request.attribute || ""}`;
3682
+ if (valueCache.has(cacheKey)) {
3683
+ out[request.key] = valueCache.get(cacheKey);
3684
+ continue;
3960
3685
  }
3961
- if (count === 1) {
3962
- return {
3963
- selector,
3964
- count,
3965
- mode: "unique"
3966
- };
3686
+ const read = await readCounterValueInFrame(
3687
+ entry.frame,
3688
+ normalized,
3689
+ request.attribute
3690
+ );
3691
+ if (read.status === "ambiguous") {
3692
+ throw buildCounterAmbiguousError(normalized);
3967
3693
  }
3968
- if (count > 1 && !fallback) {
3969
- fallback = {
3970
- selector,
3971
- count,
3972
- mode: "fallback"
3973
- };
3694
+ if (read.status === "missing") {
3695
+ valueCache.set(cacheKey, null);
3696
+ out[request.key] = null;
3697
+ continue;
3974
3698
  }
3699
+ const normalizedValue = normalizeExtractedValue(
3700
+ read.value ?? null,
3701
+ request.attribute
3702
+ );
3703
+ valueCache.set(cacheKey, normalizedValue);
3704
+ out[request.key] = normalizedValue;
3975
3705
  }
3976
- return fallback;
3706
+ return out;
3977
3707
  }
3978
- function countInDocument(selectors) {
3708
+ function dedupeCounters(requests) {
3709
+ const seen = /* @__PURE__ */ new Set();
3979
3710
  const out = [];
3980
- for (const selector of selectors) {
3981
- if (!selector) continue;
3982
- let count = 0;
3983
- try {
3984
- count = document.querySelectorAll(selector).length;
3985
- } catch {
3986
- count = 0;
3987
- }
3988
- out.push({ selector, count });
3711
+ for (const request of requests) {
3712
+ const normalized = normalizeCounter(request.counter);
3713
+ if (normalized == null || seen.has(normalized)) continue;
3714
+ seen.add(normalized);
3715
+ out.push(normalized);
3989
3716
  }
3990
3717
  return out;
3991
3718
  }
3992
- function countInRoot(root, selectors) {
3993
- if (!(root instanceof ShadowRoot)) return [];
3994
- const out = [];
3995
- for (const selector of selectors) {
3996
- if (!selector) continue;
3997
- let count = 0;
3719
+ function normalizeCounter(counter) {
3720
+ if (!Number.isFinite(counter)) return null;
3721
+ if (!Number.isInteger(counter)) return null;
3722
+ if (counter <= 0) return null;
3723
+ return counter;
3724
+ }
3725
+ async function scanCounterOccurrences(page, counters) {
3726
+ const out = /* @__PURE__ */ new Map();
3727
+ for (const counter of counters) {
3728
+ out.set(counter, {
3729
+ count: 0,
3730
+ frame: null
3731
+ });
3732
+ }
3733
+ if (!counters.length) return out;
3734
+ for (const frame of page.frames()) {
3735
+ let frameCounts;
3998
3736
  try {
3999
- count = root.querySelectorAll(selector).length;
3737
+ frameCounts = await frame.evaluate((candidates) => {
3738
+ const keys = new Set(candidates.map((value) => String(value)));
3739
+ const counts = {};
3740
+ for (const key of keys) {
3741
+ counts[key] = 0;
3742
+ }
3743
+ const walk = (root) => {
3744
+ const children = Array.from(root.children);
3745
+ for (const child of children) {
3746
+ const value = child.getAttribute("c");
3747
+ if (value && keys.has(value)) {
3748
+ counts[value] = (counts[value] || 0) + 1;
3749
+ }
3750
+ walk(child);
3751
+ if (child.shadowRoot) {
3752
+ walk(child.shadowRoot);
3753
+ }
3754
+ }
3755
+ };
3756
+ walk(document);
3757
+ return counts;
3758
+ }, counters);
4000
3759
  } catch {
4001
- count = 0;
3760
+ continue;
3761
+ }
3762
+ for (const [rawCounter, rawCount] of Object.entries(frameCounts)) {
3763
+ const counter = Number.parseInt(rawCounter, 10);
3764
+ if (!Number.isFinite(counter)) continue;
3765
+ const count = Number(rawCount || 0);
3766
+ if (!Number.isFinite(count) || count <= 0) continue;
3767
+ const entry = out.get(counter);
3768
+ entry.count += count;
3769
+ if (!entry.frame) {
3770
+ entry.frame = frame;
3771
+ }
4002
3772
  }
4003
- out.push({ selector, count });
4004
3773
  }
4005
3774
  return out;
4006
3775
  }
4007
- function isPathDebugEnabled() {
4008
- const value = process.env.OPENSTEER_DEBUG_PATH || process.env.OPENSTEER_DEBUG || process.env.DEBUG_SELECTORS;
4009
- if (!value) return false;
4010
- const normalized = value.trim().toLowerCase();
4011
- return normalized === "1" || normalized === "true";
4012
- }
4013
- function debugPath(message, data) {
4014
- if (!isPathDebugEnabled()) return;
4015
- if (data !== void 0) {
4016
- console.log(`[opensteer:path] ${message}`, data);
4017
- } else {
4018
- console.log(`[opensteer:path] ${message}`);
4019
- }
3776
+ async function resolveUniqueHandleInFrame(frame, counter) {
3777
+ return frame.evaluateHandle((targetCounter) => {
3778
+ const matches = [];
3779
+ const walk = (root) => {
3780
+ const children = Array.from(root.children);
3781
+ for (const child of children) {
3782
+ if (child.getAttribute("c") === targetCounter) {
3783
+ matches.push(child);
3784
+ }
3785
+ walk(child);
3786
+ if (child.shadowRoot) {
3787
+ walk(child.shadowRoot);
3788
+ }
3789
+ }
3790
+ };
3791
+ walk(document);
3792
+ if (matches.length !== 1) {
3793
+ return null;
3794
+ }
3795
+ return matches[0];
3796
+ }, String(counter));
4020
3797
  }
4021
- async function disposeHandle(handle) {
4022
- if (!handle) return;
3798
+ async function readCounterValueInFrame(frame, counter, attribute) {
4023
3799
  try {
4024
- await handle.dispose();
3800
+ return await frame.evaluate(
3801
+ ({ targetCounter, attribute: attribute2 }) => {
3802
+ const matches = [];
3803
+ const walk = (root) => {
3804
+ const children = Array.from(root.children);
3805
+ for (const child of children) {
3806
+ if (child.getAttribute("c") === targetCounter) {
3807
+ matches.push(child);
3808
+ }
3809
+ walk(child);
3810
+ if (child.shadowRoot) {
3811
+ walk(child.shadowRoot);
3812
+ }
3813
+ }
3814
+ };
3815
+ walk(document);
3816
+ if (!matches.length) {
3817
+ return {
3818
+ status: "missing"
3819
+ };
3820
+ }
3821
+ if (matches.length > 1) {
3822
+ return {
3823
+ status: "ambiguous"
3824
+ };
3825
+ }
3826
+ const target = matches[0];
3827
+ const value = attribute2 ? target.getAttribute(attribute2) : target.textContent;
3828
+ return {
3829
+ status: "ok",
3830
+ value
3831
+ };
3832
+ },
3833
+ {
3834
+ targetCounter: String(counter),
3835
+ attribute
3836
+ }
3837
+ );
4025
3838
  } catch {
3839
+ return {
3840
+ status: "missing"
3841
+ };
4026
3842
  }
4027
3843
  }
3844
+ function buildCounterNotFoundError(counter) {
3845
+ return new CounterResolutionError(
3846
+ "ERR_COUNTER_NOT_FOUND",
3847
+ `Counter ${counter} was not found in the live DOM.`
3848
+ );
3849
+ }
3850
+ function buildCounterAmbiguousError(counter) {
3851
+ return new CounterResolutionError(
3852
+ "ERR_COUNTER_AMBIGUOUS",
3853
+ `Counter ${counter} matches multiple live elements.`
3854
+ );
3855
+ }
4028
3856
 
4029
3857
  // src/actions/actionability-probe.ts
4030
3858
  async function probeActionabilityState(element) {
@@ -4217,13 +4045,6 @@ function classifyTypedError(error) {
4217
4045
  classificationSource: "typed_error"
4218
4046
  });
4219
4047
  }
4220
- if (error.code === "ERR_COUNTER_FRAME_UNAVAILABLE") {
4221
- return buildFailure({
4222
- code: "TARGET_UNAVAILABLE",
4223
- message: error.message,
4224
- classificationSource: "typed_error"
4225
- });
4226
- }
4227
4048
  if (error.code === "ERR_COUNTER_AMBIGUOUS") {
4228
4049
  return buildFailure({
4229
4050
  code: "TARGET_AMBIGUOUS",
@@ -4231,13 +4052,6 @@ function classifyTypedError(error) {
4231
4052
  classificationSource: "typed_error"
4232
4053
  });
4233
4054
  }
4234
- if (error.code === "ERR_COUNTER_STALE_OR_NOT_FOUND") {
4235
- return buildFailure({
4236
- code: "TARGET_STALE",
4237
- message: error.message,
4238
- classificationSource: "typed_error"
4239
- });
4240
- }
4241
4055
  }
4242
4056
  return null;
4243
4057
  }
@@ -7588,7 +7402,7 @@ function sleep3(ms) {
7588
7402
  }
7589
7403
 
7590
7404
  // src/opensteer.ts
7591
- import { createHash, randomUUID as randomUUID2 } from "crypto";
7405
+ import { createHash, randomUUID } from "crypto";
7592
7406
 
7593
7407
  // src/browser/pool.ts
7594
7408
  import {
@@ -10368,7 +10182,7 @@ var Opensteer = class _Opensteer {
10368
10182
  let persistPath = null;
10369
10183
  try {
10370
10184
  if (storageKey && resolution.shouldPersist) {
10371
- persistPath = await this.buildPathFromResolvedHandle(
10185
+ persistPath = await this.tryBuildPathFromResolvedHandle(
10372
10186
  handle,
10373
10187
  "hover",
10374
10188
  resolution.counter
@@ -10467,7 +10281,7 @@ var Opensteer = class _Opensteer {
10467
10281
  let persistPath = null;
10468
10282
  try {
10469
10283
  if (storageKey && resolution.shouldPersist) {
10470
- persistPath = await this.buildPathFromResolvedHandle(
10284
+ persistPath = await this.tryBuildPathFromResolvedHandle(
10471
10285
  handle,
10472
10286
  "input",
10473
10287
  resolution.counter
@@ -10570,7 +10384,7 @@ var Opensteer = class _Opensteer {
10570
10384
  let persistPath = null;
10571
10385
  try {
10572
10386
  if (storageKey && resolution.shouldPersist) {
10573
- persistPath = await this.buildPathFromResolvedHandle(
10387
+ persistPath = await this.tryBuildPathFromResolvedHandle(
10574
10388
  handle,
10575
10389
  "select",
10576
10390
  resolution.counter
@@ -10680,7 +10494,7 @@ var Opensteer = class _Opensteer {
10680
10494
  let persistPath = null;
10681
10495
  try {
10682
10496
  if (storageKey && resolution.shouldPersist) {
10683
- persistPath = await this.buildPathFromResolvedHandle(
10497
+ persistPath = await this.tryBuildPathFromResolvedHandle(
10684
10498
  handle,
10685
10499
  "scroll",
10686
10500
  resolution.counter
@@ -10976,17 +10790,19 @@ var Opensteer = class _Opensteer {
10976
10790
  const handle = await this.resolveCounterHandle(resolution.counter);
10977
10791
  try {
10978
10792
  if (storageKey && resolution.shouldPersist) {
10979
- const persistPath = await this.buildPathFromResolvedHandle(
10793
+ const persistPath = await this.tryBuildPathFromResolvedHandle(
10980
10794
  handle,
10981
10795
  method,
10982
10796
  resolution.counter
10983
10797
  );
10984
- this.persistPath(
10985
- storageKey,
10986
- method,
10987
- options.description,
10988
- persistPath
10989
- );
10798
+ if (persistPath) {
10799
+ this.persistPath(
10800
+ storageKey,
10801
+ method,
10802
+ options.description,
10803
+ persistPath
10804
+ );
10805
+ }
10990
10806
  }
10991
10807
  return await counterFn(handle);
10992
10808
  } catch (err) {
@@ -11024,7 +10840,7 @@ var Opensteer = class _Opensteer {
11024
10840
  let persistPath = null;
11025
10841
  try {
11026
10842
  if (storageKey && resolution.shouldPersist) {
11027
- persistPath = await this.buildPathFromResolvedHandle(
10843
+ persistPath = await this.tryBuildPathFromResolvedHandle(
11028
10844
  handle,
11029
10845
  "uploadFile",
11030
10846
  resolution.counter
@@ -11300,7 +11116,7 @@ var Opensteer = class _Opensteer {
11300
11116
  let persistPath = null;
11301
11117
  try {
11302
11118
  if (storageKey && resolution.shouldPersist) {
11303
- persistPath = await this.buildPathFromResolvedHandle(
11119
+ persistPath = await this.tryBuildPathFromResolvedHandle(
11304
11120
  handle,
11305
11121
  "click",
11306
11122
  resolution.counter
@@ -11396,17 +11212,6 @@ var Opensteer = class _Opensteer {
11396
11212
  }
11397
11213
  }
11398
11214
  if (options.element != null) {
11399
- const pathFromElement = await this.tryBuildPathFromCounter(
11400
- options.element
11401
- );
11402
- if (pathFromElement) {
11403
- return {
11404
- path: pathFromElement,
11405
- counter: null,
11406
- shouldPersist: Boolean(storageKey),
11407
- source: "element"
11408
- };
11409
- }
11410
11215
  return {
11411
11216
  path: null,
11412
11217
  counter: options.element,
@@ -11434,17 +11239,6 @@ var Opensteer = class _Opensteer {
11434
11239
  options.description
11435
11240
  );
11436
11241
  if (resolved?.counter != null) {
11437
- const pathFromAiCounter = await this.tryBuildPathFromCounter(
11438
- resolved.counter
11439
- );
11440
- if (pathFromAiCounter) {
11441
- return {
11442
- path: pathFromAiCounter,
11443
- counter: null,
11444
- shouldPersist: Boolean(storageKey),
11445
- source: "ai"
11446
- };
11447
- }
11448
11242
  return {
11449
11243
  path: null,
11450
11244
  counter: resolved.counter,
@@ -11526,23 +11320,22 @@ var Opensteer = class _Opensteer {
11526
11320
  try {
11527
11321
  const builtPath = await buildElementPathFromHandle(handle);
11528
11322
  if (builtPath) {
11529
- return this.withIndexedIframeContext(builtPath, indexedPath);
11323
+ const withFrameContext = await this.withHandleIframeContext(
11324
+ handle,
11325
+ builtPath
11326
+ );
11327
+ return this.withIndexedIframeContext(
11328
+ withFrameContext,
11329
+ indexedPath
11330
+ );
11530
11331
  }
11531
11332
  return indexedPath;
11532
11333
  } finally {
11533
11334
  await handle.dispose();
11534
11335
  }
11535
11336
  }
11536
- async tryBuildPathFromCounter(counter) {
11537
- try {
11538
- return await this.buildPathFromElement(counter);
11539
- } catch {
11540
- return null;
11541
- }
11542
- }
11543
11337
  async resolveCounterHandle(element) {
11544
- const snapshot = await this.ensureSnapshotWithCounters();
11545
- return resolveCounterElement(this.page, snapshot, element);
11338
+ return resolveCounterElement(this.page, element);
11546
11339
  }
11547
11340
  async resolveCounterHandleForAction(action, description, element) {
11548
11341
  try {
@@ -11566,8 +11359,12 @@ var Opensteer = class _Opensteer {
11566
11359
  const indexedPath = await this.readPathFromCounterIndex(counter);
11567
11360
  const builtPath = await buildElementPathFromHandle(handle);
11568
11361
  if (builtPath) {
11362
+ const withFrameContext = await this.withHandleIframeContext(
11363
+ handle,
11364
+ builtPath
11365
+ );
11569
11366
  const normalized = this.withIndexedIframeContext(
11570
- builtPath,
11367
+ withFrameContext,
11571
11368
  indexedPath
11572
11369
  );
11573
11370
  if (normalized.nodes.length) return normalized;
@@ -11577,15 +11374,34 @@ var Opensteer = class _Opensteer {
11577
11374
  `Unable to build element path from counter ${counter} during ${action}.`
11578
11375
  );
11579
11376
  }
11377
+ async tryBuildPathFromResolvedHandle(handle, action, counter) {
11378
+ try {
11379
+ return await this.buildPathFromResolvedHandle(handle, action, counter);
11380
+ } catch (error) {
11381
+ this.logDebugError(
11382
+ `path persistence skipped for ${action} counter ${counter}`,
11383
+ error
11384
+ );
11385
+ return null;
11386
+ }
11387
+ }
11580
11388
  withIndexedIframeContext(builtPath, indexedPath) {
11581
11389
  const normalizedBuilt = this.normalizePath(builtPath);
11582
11390
  if (!indexedPath) return normalizedBuilt;
11583
11391
  const iframePrefix = collectIframeContextPrefix(indexedPath);
11584
11392
  if (!iframePrefix.length) return normalizedBuilt;
11393
+ const builtContext = cloneContextHops(normalizedBuilt.context);
11394
+ const overlap = measureContextOverlap(iframePrefix, builtContext);
11395
+ const missingPrefix = cloneContextHops(
11396
+ iframePrefix.slice(0, iframePrefix.length - overlap)
11397
+ );
11398
+ if (!missingPrefix.length) {
11399
+ return normalizedBuilt;
11400
+ }
11585
11401
  const merged = {
11586
11402
  context: [
11587
- ...cloneContextHops(iframePrefix),
11588
- ...cloneContextHops(normalizedBuilt.context)
11403
+ ...missingPrefix,
11404
+ ...builtContext
11589
11405
  ],
11590
11406
  nodes: cloneElementPath(normalizedBuilt).nodes
11591
11407
  };
@@ -11595,9 +11411,48 @@ var Opensteer = class _Opensteer {
11595
11411
  if (fallback.nodes.length) return fallback;
11596
11412
  return normalizedBuilt;
11597
11413
  }
11414
+ async withHandleIframeContext(handle, path5) {
11415
+ const ownFrame = await handle.ownerFrame();
11416
+ if (!ownFrame) {
11417
+ return this.normalizePath(path5);
11418
+ }
11419
+ let frame = ownFrame;
11420
+ let prefix = [];
11421
+ while (frame && frame !== this.page.mainFrame()) {
11422
+ const parent = frame.parentFrame();
11423
+ if (!parent) break;
11424
+ const frameElement = await frame.frameElement().catch(() => null);
11425
+ if (!frameElement) break;
11426
+ try {
11427
+ const frameElementPath = await buildElementPathFromHandle(frameElement);
11428
+ if (frameElementPath?.nodes.length) {
11429
+ const segment = [
11430
+ ...cloneContextHops(frameElementPath.context),
11431
+ {
11432
+ kind: "iframe",
11433
+ host: cloneElementPath(frameElementPath).nodes
11434
+ }
11435
+ ];
11436
+ prefix = [...segment, ...prefix];
11437
+ }
11438
+ } finally {
11439
+ await frameElement.dispose().catch(() => void 0);
11440
+ }
11441
+ frame = parent;
11442
+ }
11443
+ if (!prefix.length) {
11444
+ return this.normalizePath(path5);
11445
+ }
11446
+ return this.normalizePath({
11447
+ context: [...prefix, ...cloneContextHops(path5.context)],
11448
+ nodes: cloneElementPath(path5).nodes
11449
+ });
11450
+ }
11598
11451
  async readPathFromCounterIndex(counter) {
11599
- const snapshot = await this.ensureSnapshotWithCounters();
11600
- const indexed = snapshot.counterIndex?.get(counter);
11452
+ if (!this.snapshotCache || this.snapshotCache.url !== this.page.url() || !this.snapshotCache.counterIndex) {
11453
+ return null;
11454
+ }
11455
+ const indexed = this.snapshotCache.counterIndex.get(counter);
11601
11456
  if (!indexed) return null;
11602
11457
  const normalized = this.normalizePath(indexed);
11603
11458
  if (!normalized.nodes.length) return null;
@@ -11608,15 +11463,6 @@ var Opensteer = class _Opensteer {
11608
11463
  if (!path5) return null;
11609
11464
  return this.normalizePath(path5);
11610
11465
  }
11611
- async ensureSnapshotWithCounters() {
11612
- if (!this.snapshotCache || !this.snapshotCache.counterBindings || this.snapshotCache.url !== this.page.url()) {
11613
- await this.snapshot({
11614
- mode: "full",
11615
- withCounters: true
11616
- });
11617
- }
11618
- return this.snapshotCache;
11619
- }
11620
11466
  persistPath(id, method, description, path5) {
11621
11467
  const now = Date.now();
11622
11468
  const safeFile = this.storage.getSelectorFileName(id);
@@ -11866,17 +11712,6 @@ var Opensteer = class _Opensteer {
11866
11712
  return;
11867
11713
  }
11868
11714
  if (normalized.element != null) {
11869
- const path5 = await this.tryBuildPathFromCounter(
11870
- normalized.element
11871
- );
11872
- if (path5) {
11873
- fields.push({
11874
- key: fieldKey,
11875
- path: path5,
11876
- attribute: normalized.attribute
11877
- });
11878
- return;
11879
- }
11880
11715
  fields.push({
11881
11716
  key: fieldKey,
11882
11717
  counter: normalized.element,
@@ -11921,15 +11756,6 @@ var Opensteer = class _Opensteer {
11921
11756
  continue;
11922
11757
  }
11923
11758
  if (fieldPlan.element != null) {
11924
- const path6 = await this.tryBuildPathFromCounter(fieldPlan.element);
11925
- if (path6) {
11926
- fields.push({
11927
- key,
11928
- path: path6,
11929
- attribute: fieldPlan.attribute
11930
- });
11931
- continue;
11932
- }
11933
11759
  fields.push({
11934
11760
  key,
11935
11761
  counter: fieldPlan.element,
@@ -11984,12 +11810,7 @@ var Opensteer = class _Opensteer {
11984
11810
  }
11985
11811
  }
11986
11812
  if (counterRequests.length) {
11987
- const snapshot = await this.ensureSnapshotWithCounters();
11988
- const counterValues = await resolveCountersBatch(
11989
- this.page,
11990
- snapshot,
11991
- counterRequests
11992
- );
11813
+ const counterValues = await resolveCountersBatch(this.page, counterRequests);
11993
11814
  Object.assign(result, counterValues);
11994
11815
  }
11995
11816
  if (pathFields.length) {
@@ -12019,7 +11840,7 @@ var Opensteer = class _Opensteer {
12019
11840
  const path5 = await this.buildPathFromElement(field.counter);
12020
11841
  if (!path5) {
12021
11842
  throw new Error(
12022
- `Unable to build element path from counter ${field.counter} for extraction field "${field.key}".`
11843
+ `Unable to persist extraction schema field "${field.key}": counter ${field.counter} could not be converted into a stable element path.`
12023
11844
  );
12024
11845
  }
12025
11846
  resolved.push({
@@ -12065,6 +11886,33 @@ function collectIframeContextPrefix(path5) {
12065
11886
  if (lastIframeIndex < 0) return [];
12066
11887
  return cloneContextHops(context.slice(0, lastIframeIndex + 1));
12067
11888
  }
11889
+ function measureContextOverlap(indexedPrefix, builtContext) {
11890
+ const maxOverlap = Math.min(indexedPrefix.length, builtContext.length);
11891
+ for (let size = maxOverlap; size > 0; size -= 1) {
11892
+ if (matchesContextPrefix(indexedPrefix, builtContext, size, true)) {
11893
+ return size;
11894
+ }
11895
+ }
11896
+ for (let size = maxOverlap; size > 0; size -= 1) {
11897
+ if (matchesContextPrefix(indexedPrefix, builtContext, size, false)) {
11898
+ return size;
11899
+ }
11900
+ }
11901
+ return 0;
11902
+ }
11903
+ function matchesContextPrefix(indexedPrefix, builtContext, size, strictHost) {
11904
+ for (let idx = 0; idx < size; idx += 1) {
11905
+ const left = indexedPrefix[indexedPrefix.length - size + idx];
11906
+ const right = builtContext[idx];
11907
+ if (left.kind !== right.kind) {
11908
+ return false;
11909
+ }
11910
+ if (strictHost && JSON.stringify(left.host) !== JSON.stringify(right.host)) {
11911
+ return false;
11912
+ }
11913
+ }
11914
+ return true;
11915
+ }
12068
11916
  function normalizeSchemaValue(value) {
12069
11917
  if (!value) return null;
12070
11918
  if (typeof value !== "object" || Array.isArray(value)) {
@@ -12322,7 +12170,7 @@ function isInternalOrBlankPageUrl(url) {
12322
12170
  }
12323
12171
  function buildLocalRunId(namespace) {
12324
12172
  const normalized = namespace.trim() || "default";
12325
- return `${normalized}-${Date.now().toString(36)}-${randomUUID2().slice(0, 8)}`;
12173
+ return `${normalized}-${Date.now().toString(36)}-${randomUUID().slice(0, 8)}`;
12326
12174
  }
12327
12175
 
12328
12176
  export {
@@ -12352,13 +12200,12 @@ export {
12352
12200
  cleanForClickable,
12353
12201
  cleanForScrollable,
12354
12202
  cleanForAction,
12355
- CounterResolutionError,
12356
- ensureLiveCounters,
12357
- resolveCounterElement,
12358
- resolveCountersBatch,
12359
12203
  prepareSnapshot,
12360
12204
  ElementPathError,
12361
12205
  resolveElementPath,
12206
+ CounterResolutionError,
12207
+ resolveCounterElement,
12208
+ resolveCountersBatch,
12362
12209
  performClick,
12363
12210
  performHover,
12364
12211
  performInput,