opensteer 0.4.5 → 0.4.6

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.
@@ -37,15 +37,52 @@ function sanitizeNamespaceSegment(segment) {
37
37
  // src/navigation.ts
38
38
  var DEFAULT_TIMEOUT = 3e4;
39
39
  var DEFAULT_SETTLE_MS = 750;
40
+ var FRAME_EVALUATE_GRACE_MS = 200;
41
+ var STEALTH_WORLD_NAME = "__opensteer_wait__";
42
+ var StealthWaitUnavailableError = class extends Error {
43
+ constructor(cause) {
44
+ super("Stealth visual wait requires Chromium CDP support.", { cause });
45
+ this.name = "StealthWaitUnavailableError";
46
+ }
47
+ };
48
+ function isStealthWaitUnavailableError(error) {
49
+ return error instanceof StealthWaitUnavailableError;
50
+ }
51
+ var FRAME_OWNER_VISIBILITY_FUNCTION = `function() {
52
+ if (!(this instanceof HTMLElement)) return false;
53
+
54
+ var rect = this.getBoundingClientRect();
55
+ if (rect.width <= 0 || rect.height <= 0) return false;
56
+ if (
57
+ rect.bottom <= 0 ||
58
+ rect.right <= 0 ||
59
+ rect.top >= window.innerHeight ||
60
+ rect.left >= window.innerWidth
61
+ ) {
62
+ return false;
63
+ }
64
+
65
+ var style = window.getComputedStyle(this);
66
+ if (
67
+ style.display === 'none' ||
68
+ style.visibility === 'hidden' ||
69
+ Number(style.opacity) === 0
70
+ ) {
71
+ return false;
72
+ }
73
+
74
+ return true;
75
+ }`;
40
76
  function buildStabilityScript(timeout, settleMs) {
41
77
  return `new Promise(function(resolve) {
42
78
  var deadline = Date.now() + ${timeout};
43
- var timer = null;
44
79
  var resolved = false;
80
+ var timer = null;
45
81
  var observers = [];
46
82
  var observedShadowRoots = [];
47
83
  var fonts = document.fonts;
48
84
  var fontsReady = !fonts || fonts.status === 'loaded';
85
+ var lastRelevantMutationAt = Date.now();
49
86
 
50
87
  function clearObservers() {
51
88
  for (var i = 0; i < observers.length; i++) {
@@ -63,9 +100,87 @@ function buildStabilityScript(timeout, settleMs) {
63
100
  resolve();
64
101
  }
65
102
 
103
+ function isElementVisiblyIntersectingViewport(element) {
104
+ if (!(element instanceof Element)) return false;
105
+
106
+ var rect = element.getBoundingClientRect();
107
+ var inViewport =
108
+ rect.width > 0 &&
109
+ rect.height > 0 &&
110
+ rect.bottom > 0 &&
111
+ rect.right > 0 &&
112
+ rect.top < window.innerHeight &&
113
+ rect.left < window.innerWidth;
114
+
115
+ if (!inViewport) return false;
116
+
117
+ var style = window.getComputedStyle(element);
118
+ if (style.visibility === 'hidden' || style.display === 'none') {
119
+ return false;
120
+ }
121
+ if (Number(style.opacity) === 0) {
122
+ return false;
123
+ }
124
+
125
+ return true;
126
+ }
127
+
128
+ function resolveRelevantElement(node) {
129
+ if (!node) return null;
130
+ if (node instanceof Element) return node;
131
+ if (typeof ShadowRoot !== 'undefined' && node instanceof ShadowRoot) {
132
+ return node.host instanceof Element ? node.host : null;
133
+ }
134
+ var parentElement = node.parentElement;
135
+ return parentElement instanceof Element ? parentElement : null;
136
+ }
137
+
138
+ function isNodeVisiblyRelevant(node) {
139
+ var element = resolveRelevantElement(node);
140
+ if (!element) return false;
141
+ return isElementVisiblyIntersectingViewport(element);
142
+ }
143
+
144
+ function hasRelevantMutation(records) {
145
+ for (var i = 0; i < records.length; i++) {
146
+ var record = records[i];
147
+ if (isNodeVisiblyRelevant(record.target)) return true;
148
+
149
+ var addedNodes = record.addedNodes;
150
+ for (var j = 0; j < addedNodes.length; j++) {
151
+ if (isNodeVisiblyRelevant(addedNodes[j])) return true;
152
+ }
153
+
154
+ var removedNodes = record.removedNodes;
155
+ for (var k = 0; k < removedNodes.length; k++) {
156
+ if (isNodeVisiblyRelevant(removedNodes[k])) return true;
157
+ }
158
+ }
159
+
160
+ return false;
161
+ }
162
+
163
+ function scheduleCheck() {
164
+ if (resolved) return;
165
+ if (timer) clearTimeout(timer);
166
+
167
+ var remaining = deadline - Date.now();
168
+ if (remaining <= 0) {
169
+ done();
170
+ return;
171
+ }
172
+
173
+ var checkDelay = Math.min(120, Math.max(16, ${settleMs}));
174
+ timer = setTimeout(checkNow, checkDelay);
175
+ }
176
+
66
177
  function observeMutations(target) {
67
178
  if (!target) return;
68
- var observer = new MutationObserver(function() { settle(); });
179
+ var observer = new MutationObserver(function(records) {
180
+ if (!hasRelevantMutation(records)) return;
181
+ lastRelevantMutationAt = Date.now();
182
+ scheduleCheck();
183
+ });
69
184
  observer.observe(target, {
70
185
  childList: true,
71
186
  subtree: true,
@@ -103,18 +218,25 @@ function buildStabilityScript(timeout, settleMs) {
103
218
  var images = root.querySelectorAll('img');
104
219
  for (var i = 0; i < images.length; i++) {
105
220
  var img = images[i];
106
- var rect = img.getBoundingClientRect();
107
- var inViewport =
108
- rect.bottom > 0 &&
109
- rect.right > 0 &&
110
- rect.top < window.innerHeight &&
111
- rect.left < window.innerWidth;
112
- if (inViewport && !img.complete) return false;
221
+ if (!isElementVisiblyIntersectingViewport(img)) continue;
222
+ if (!img.complete) return false;
113
223
  }
114
224
  return true;
115
225
  }
116
226
 
117
- function hasRunningFiniteAnimations() {
227
+ function getAnimationTarget(effect) {
228
+ if (!effect) return null;
229
+ var target = effect.target;
230
+ if (target instanceof Element) return target;
231
+
232
+ if (target && target.element instanceof Element) {
233
+ return target.element;
234
+ }
235
+
236
+ return null;
237
+ }
238
+
239
+ function hasRunningVisibleFiniteAnimations() {
118
240
  if (typeof document.getAnimations !== 'function') return false;
119
241
  var animations = document.getAnimations();
120
242
 
@@ -128,6 +250,9 @@ function buildStabilityScript(timeout, settleMs) {
128
250
  ? timing.endTime
129
251
  : Number.POSITIVE_INFINITY;
130
252
  if (Number.isFinite(endTime) && endTime > 0) {
253
+ var target = getAnimationTarget(effect);
254
+ if (!target) continue;
255
+ if (!isElementVisiblyIntersectingViewport(target)) continue;
131
256
  return true;
132
257
  }
133
258
  }
@@ -138,21 +263,29 @@ function buildStabilityScript(timeout, settleMs) {
138
263
  function isVisuallyReady() {
139
264
  if (!fontsReady) return false;
140
265
  if (!checkViewportImages(document)) return false;
141
- if (hasRunningFiniteAnimations()) return false;
266
+ if (hasRunningVisibleFiniteAnimations()) return false;
142
267
  return true;
143
268
  }
144
269
 
145
- function settle() {
146
- if (Date.now() > deadline) { done(); return; }
147
- if (timer) clearTimeout(timer);
270
+ function checkNow() {
271
+ if (Date.now() >= deadline) {
272
+ done();
273
+ return;
274
+ }
275
+
148
276
  observeOpenShadowRoots();
149
- timer = setTimeout(function() {
150
- if (isVisuallyReady()) {
151
- done();
152
- } else {
153
- settle();
154
- }
155
- }, ${settleMs});
277
+
278
+ if (!isVisuallyReady()) {
279
+ scheduleCheck();
280
+ return;
281
+ }
282
+
283
+ if (Date.now() - lastRelevantMutationAt >= ${settleMs}) {
284
+ done();
285
+ return;
286
+ }
287
+
288
+ scheduleCheck();
156
289
  }
157
290
 
158
291
  observeMutations(document.documentElement);
@@ -161,67 +294,266 @@ function buildStabilityScript(timeout, settleMs) {
161
294
  if (fonts && fonts.ready && typeof fonts.ready.then === 'function') {
162
295
  fonts.ready.then(function() {
163
296
  fontsReady = true;
164
- settle();
297
+ scheduleCheck();
298
+ }, function() {
299
+ fontsReady = true;
300
+ scheduleCheck();
165
301
  });
166
302
  }
167
303
 
168
304
  var safetyTimer = setTimeout(done, ${timeout});
169
305
 
170
- settle();
306
+ scheduleCheck();
171
307
  })`;
172
308
  }
309
+ var StealthCdpRuntime = class _StealthCdpRuntime {
310
+ constructor(session) {
311
+ this.session = session;
312
+ }
313
+ contextsByFrame = /* @__PURE__ */ new Map();
314
+ disposed = false;
315
+ static async create(page) {
316
+ let session;
317
+ try {
318
+ session = await page.context().newCDPSession(page);
319
+ } catch (error) {
320
+ throw new StealthWaitUnavailableError(error);
321
+ }
322
+ const runtime = new _StealthCdpRuntime(session);
323
+ try {
324
+ await runtime.initialize();
325
+ return runtime;
326
+ } catch (error) {
327
+ await runtime.dispose();
328
+ throw new StealthWaitUnavailableError(error);
329
+ }
330
+ }
331
+ async dispose() {
332
+ if (this.disposed) return;
333
+ this.disposed = true;
334
+ this.contextsByFrame.clear();
335
+ await this.session.detach().catch(() => void 0);
336
+ }
337
+ async waitForMainFrameVisualStability(options) {
338
+ const timeout = options.timeout ?? DEFAULT_TIMEOUT;
339
+ const settleMs = options.settleMs ?? DEFAULT_SETTLE_MS;
340
+ if (timeout <= 0) return;
341
+ const frameRecords = await this.getFrameRecords();
342
+ const mainFrame = frameRecords[0];
343
+ if (!mainFrame) return;
344
+ await this.waitForFrameVisualStability(mainFrame.frameId, timeout, settleMs);
345
+ }
346
+ async collectVisibleFrameIds() {
347
+ const frameRecords = await this.getFrameRecords();
348
+ if (frameRecords.length === 0) return [];
349
+ const visibleFrameIds = [];
350
+ for (const frameRecord of frameRecords) {
351
+ if (!frameRecord.parentFrameId) {
352
+ visibleFrameIds.push(frameRecord.frameId);
353
+ continue;
354
+ }
355
+ try {
356
+ const parentContextId = await this.ensureFrameContextId(
357
+ frameRecord.parentFrameId
358
+ );
359
+ const visible = await this.isFrameOwnerVisible(
360
+ frameRecord.frameId,
361
+ parentContextId
362
+ );
363
+ if (visible) {
364
+ visibleFrameIds.push(frameRecord.frameId);
365
+ }
366
+ } catch (error) {
367
+ if (isIgnorableFrameError(error)) continue;
368
+ throw error;
369
+ }
370
+ }
371
+ return visibleFrameIds;
372
+ }
373
+ async waitForFrameVisualStability(frameId, timeout, settleMs) {
374
+ if (timeout <= 0) return;
375
+ const script = buildStabilityScript(timeout, settleMs);
376
+ let contextId = await this.ensureFrameContextId(frameId);
377
+ try {
378
+ await this.evaluateWithGuard(contextId, script, timeout);
379
+ } catch (error) {
380
+ if (!isMissingExecutionContextError(error)) {
381
+ throw error;
382
+ }
383
+ this.contextsByFrame.delete(frameId);
384
+ contextId = await this.ensureFrameContextId(frameId);
385
+ await this.evaluateWithGuard(contextId, script, timeout);
386
+ }
387
+ }
388
+ async initialize() {
389
+ await this.session.send("Page.enable");
390
+ await this.session.send("Runtime.enable");
391
+ await this.session.send("DOM.enable");
392
+ }
393
+ async getFrameRecords() {
394
+ const treeResult = await this.session.send("Page.getFrameTree");
395
+ const records = [];
396
+ walkFrameTree(treeResult.frameTree, null, records);
397
+ return records;
398
+ }
399
+ async ensureFrameContextId(frameId) {
400
+ const cached = this.contextsByFrame.get(frameId);
401
+ if (cached != null) {
402
+ return cached;
403
+ }
404
+ const world = await this.session.send("Page.createIsolatedWorld", {
405
+ frameId,
406
+ worldName: STEALTH_WORLD_NAME
407
+ });
408
+ this.contextsByFrame.set(frameId, world.executionContextId);
409
+ return world.executionContextId;
410
+ }
411
+ async evaluateWithGuard(contextId, script, timeout) {
412
+ const evaluationPromise = this.evaluateScript(contextId, script);
413
+ const settledPromise = evaluationPromise.then(
414
+ () => ({ kind: "resolved" }),
415
+ (error) => ({ kind: "rejected", error })
416
+ );
417
+ const timeoutPromise = sleep(
418
+ timeout + FRAME_EVALUATE_GRACE_MS
419
+ ).then(() => ({ kind: "timeout" }));
420
+ const result = await Promise.race([
421
+ settledPromise,
422
+ timeoutPromise
423
+ ]);
424
+ if (result.kind === "rejected") {
425
+ throw result.error;
426
+ }
427
+ }
428
+ async evaluateScript(contextId, expression) {
429
+ const result = await this.session.send("Runtime.evaluate", {
430
+ contextId,
431
+ expression,
432
+ awaitPromise: true,
433
+ returnByValue: true
434
+ });
435
+ if (result.exceptionDetails) {
436
+ throw new Error(formatCdpException(result.exceptionDetails));
437
+ }
438
+ }
439
+ async isFrameOwnerVisible(frameId, parentContextId) {
440
+ const owner = await this.session.send("DOM.getFrameOwner", {
441
+ frameId
442
+ });
443
+ const resolveParams = {
444
+ executionContextId: parentContextId
445
+ };
446
+ if (typeof owner.backendNodeId === "number") {
447
+ resolveParams.backendNodeId = owner.backendNodeId;
448
+ } else if (typeof owner.nodeId === "number") {
449
+ resolveParams.nodeId = owner.nodeId;
450
+ } else {
451
+ return false;
452
+ }
453
+ const resolved = await this.session.send(
454
+ "DOM.resolveNode",
455
+ resolveParams
456
+ );
457
+ const objectId = resolved.object?.objectId;
458
+ if (!objectId) return false;
459
+ try {
460
+ const callResult = await this.session.send("Runtime.callFunctionOn", {
461
+ objectId,
462
+ functionDeclaration: FRAME_OWNER_VISIBILITY_FUNCTION,
463
+ returnByValue: true
464
+ });
465
+ if (callResult.exceptionDetails) {
466
+ throw new Error(formatCdpException(callResult.exceptionDetails));
467
+ }
468
+ return callResult.result.value === true;
469
+ } finally {
470
+ await this.releaseObject(objectId);
471
+ }
472
+ }
473
+ async releaseObject(objectId) {
474
+ await this.session.send("Runtime.releaseObject", {
475
+ objectId
476
+ }).catch(() => void 0);
477
+ }
478
+ };
173
479
  async function waitForVisualStability(page, options = {}) {
174
- const timeout = options.timeout ?? DEFAULT_TIMEOUT;
175
- const settleMs = options.settleMs ?? DEFAULT_SETTLE_MS;
176
- await waitForFrameVisualStability(page.mainFrame(), {
177
- timeout,
178
- settleMs
179
- });
480
+ const runtime = await StealthCdpRuntime.create(page);
481
+ try {
482
+ await runtime.waitForMainFrameVisualStability(options);
483
+ } finally {
484
+ await runtime.dispose();
485
+ }
180
486
  }
181
487
  async function waitForVisualStabilityAcrossFrames(page, options = {}) {
182
488
  const timeout = options.timeout ?? DEFAULT_TIMEOUT;
183
489
  const settleMs = options.settleMs ?? DEFAULT_SETTLE_MS;
490
+ if (timeout <= 0) return;
184
491
  const deadline = Date.now() + timeout;
185
- while (true) {
186
- const remaining = Math.max(0, deadline - Date.now());
187
- if (remaining === 0) return;
188
- const frames = page.frames();
189
- await Promise.all(
190
- frames.map(async (frame) => {
191
- try {
192
- await waitForFrameVisualStability(frame, {
193
- timeout: remaining,
194
- settleMs
195
- });
196
- } catch (error) {
197
- if (isIgnorableFrameError(error)) return;
198
- throw error;
199
- }
200
- })
201
- );
202
- const currentFrames = page.frames();
203
- if (sameFrames(frames, currentFrames)) {
204
- return;
492
+ const runtime = await StealthCdpRuntime.create(page);
493
+ try {
494
+ while (true) {
495
+ const remaining = Math.max(0, deadline - Date.now());
496
+ if (remaining === 0) return;
497
+ const frameIds = await runtime.collectVisibleFrameIds();
498
+ if (frameIds.length === 0) return;
499
+ await Promise.all(
500
+ frameIds.map(async (frameId) => {
501
+ try {
502
+ await runtime.waitForFrameVisualStability(
503
+ frameId,
504
+ remaining,
505
+ settleMs
506
+ );
507
+ } catch (error) {
508
+ if (isIgnorableFrameError(error)) return;
509
+ throw error;
510
+ }
511
+ })
512
+ );
513
+ const currentFrameIds = await runtime.collectVisibleFrameIds();
514
+ if (sameFrameIds(frameIds, currentFrameIds)) {
515
+ return;
516
+ }
205
517
  }
518
+ } finally {
519
+ await runtime.dispose();
206
520
  }
207
521
  }
208
- async function waitForFrameVisualStability(frame, options) {
209
- const timeout = options.timeout ?? DEFAULT_TIMEOUT;
210
- const settleMs = options.settleMs ?? DEFAULT_SETTLE_MS;
211
- if (timeout <= 0) return;
212
- await frame.evaluate(buildStabilityScript(timeout, settleMs));
522
+ function walkFrameTree(node, parentFrameId, records) {
523
+ const frameId = node.frame?.id;
524
+ if (!frameId) return;
525
+ records.push({
526
+ frameId,
527
+ parentFrameId
528
+ });
529
+ for (const child of node.childFrames ?? []) {
530
+ walkFrameTree(child, frameId, records);
531
+ }
213
532
  }
214
- function sameFrames(before, after) {
533
+ function sameFrameIds(before, after) {
215
534
  if (before.length !== after.length) return false;
216
- for (const frame of before) {
217
- if (!after.includes(frame)) return false;
535
+ for (const frameId of before) {
536
+ if (!after.includes(frameId)) return false;
218
537
  }
219
538
  return true;
220
539
  }
540
+ function formatCdpException(details) {
541
+ return details.exception?.description || details.text || "CDP runtime evaluation failed.";
542
+ }
543
+ function isMissingExecutionContextError(error) {
544
+ if (!(error instanceof Error)) return false;
545
+ const message = error.message;
546
+ return message.includes("Cannot find context with specified id") || message.includes("Cannot find execution context");
547
+ }
221
548
  function isIgnorableFrameError(error) {
222
549
  if (!(error instanceof Error)) return false;
223
550
  const message = error.message;
224
- return message.includes("Frame was detached") || message.includes("Execution context was destroyed") || message.includes("Target page, context or browser has been closed");
551
+ return message.includes("Frame was detached") || message.includes("Execution context was destroyed") || message.includes("Target page, context or browser has been closed") || message.includes("Cannot find context with specified id") || message.includes("Cannot find execution context") || message.includes("No frame for given id found");
552
+ }
553
+ function sleep(ms) {
554
+ return new Promise((resolve) => {
555
+ setTimeout(resolve, ms);
556
+ });
225
557
  }
226
558
 
227
559
  // src/storage/registry.ts
@@ -1114,6 +1446,66 @@ var OS_BOUNDARY_ATTR = "data-os-boundary";
1114
1446
  var OS_UNAVAILABLE_ATTR = "data-os-unavailable";
1115
1447
  var OS_IFRAME_BOUNDARY_TAG = "os-iframe-root";
1116
1448
  var OS_SHADOW_BOUNDARY_TAG = "os-shadow-root";
1449
+ function decodeSerializedNodeTableEntry(nodeTable, rawIndex, label) {
1450
+ if (typeof rawIndex !== "number" || !Number.isInteger(rawIndex) || rawIndex < 0 || rawIndex >= nodeTable.length) {
1451
+ throw new Error(
1452
+ `Invalid serialized path node index at "${label}": expected a valid table index.`
1453
+ );
1454
+ }
1455
+ const node = nodeTable[rawIndex];
1456
+ if (!node || typeof node !== "object") {
1457
+ throw new Error(
1458
+ `Invalid serialized path node at "${label}": table entry is missing.`
1459
+ );
1460
+ }
1461
+ return node;
1462
+ }
1463
+ function decodeSerializedDomPath(nodeTable, rawPath, label) {
1464
+ if (!Array.isArray(rawPath)) {
1465
+ throw new Error(
1466
+ `Invalid serialized path at "${label}": expected an array of node indexes.`
1467
+ );
1468
+ }
1469
+ return rawPath.map(
1470
+ (value, index) => decodeSerializedNodeTableEntry(nodeTable, value, `${label}[${index}]`)
1471
+ );
1472
+ }
1473
+ function decodeSerializedElementPath(nodeTable, rawPath, label) {
1474
+ if (!rawPath || typeof rawPath !== "object") {
1475
+ throw new Error(
1476
+ `Invalid serialized element path at "${label}": expected an object.`
1477
+ );
1478
+ }
1479
+ if (rawPath.context !== void 0 && !Array.isArray(rawPath.context)) {
1480
+ throw new Error(
1481
+ `Invalid serialized context at "${label}.context": expected an array.`
1482
+ );
1483
+ }
1484
+ const contextRaw = Array.isArray(rawPath.context) ? rawPath.context : [];
1485
+ const context = contextRaw.map((hop, hopIndex) => {
1486
+ if (!hop || typeof hop !== "object" || hop.kind !== "shadow") {
1487
+ throw new Error(
1488
+ `Invalid serialized context hop at "${label}.context[${hopIndex}]": expected a shadow hop.`
1489
+ );
1490
+ }
1491
+ return {
1492
+ kind: "shadow",
1493
+ host: decodeSerializedDomPath(
1494
+ nodeTable,
1495
+ hop.host,
1496
+ `${label}.context[${hopIndex}].host`
1497
+ )
1498
+ };
1499
+ });
1500
+ return {
1501
+ context,
1502
+ nodes: decodeSerializedDomPath(
1503
+ nodeTable,
1504
+ rawPath.nodes,
1505
+ `${label}.nodes`
1506
+ )
1507
+ };
1508
+ }
1117
1509
  async function serializePageHTML(page, _options = {}) {
1118
1510
  return serializeFrameRecursive(page.mainFrame(), [], "f0");
1119
1511
  }
@@ -1170,6 +1562,8 @@ async function serializeFrameRecursive(frame, baseContext, frameKey) {
1170
1562
  (Array.isArray(deferredMatchAttrKeys) ? deferredMatchAttrKeys : []).map((key) => String(key))
1171
1563
  );
1172
1564
  let counter = 1;
1565
+ const nodeTable = [];
1566
+ const nodeTableIndexByKey = /* @__PURE__ */ new Map();
1173
1567
  const entries = [];
1174
1568
  const helpers = {
1175
1569
  nextToken() {
@@ -1371,6 +1765,47 @@ async function serializeFrameRecursive(frame, baseContext, frameKey) {
1371
1765
  nodes: target
1372
1766
  };
1373
1767
  },
1768
+ buildPathNodeKey(node) {
1769
+ const attrs = Object.entries(node.attrs || {}).sort(
1770
+ ([a], [b]) => a.localeCompare(b)
1771
+ );
1772
+ const match = (node.match || []).map(
1773
+ (clause) => clause.kind === "attr" ? [
1774
+ "attr",
1775
+ clause.key,
1776
+ clause.op || "exact",
1777
+ clause.value ?? null
1778
+ ] : ["position", clause.axis]
1779
+ );
1780
+ return JSON.stringify([
1781
+ node.tag,
1782
+ node.position.nthChild,
1783
+ node.position.nthOfType,
1784
+ attrs,
1785
+ match
1786
+ ]);
1787
+ },
1788
+ internPathNode(node) {
1789
+ const key = helpers.buildPathNodeKey(node);
1790
+ const existing = nodeTableIndexByKey.get(key);
1791
+ if (existing != null) return existing;
1792
+ const index = nodeTable.length;
1793
+ nodeTable.push(node);
1794
+ nodeTableIndexByKey.set(key, index);
1795
+ return index;
1796
+ },
1797
+ packDomPath(path5) {
1798
+ return path5.map((node) => helpers.internPathNode(node));
1799
+ },
1800
+ packElementPath(path5) {
1801
+ return {
1802
+ context: (path5.context || []).map((hop) => ({
1803
+ kind: "shadow",
1804
+ host: helpers.packDomPath(hop.host)
1805
+ })),
1806
+ nodes: helpers.packDomPath(path5.nodes)
1807
+ };
1808
+ },
1374
1809
  ensureNodeId(el) {
1375
1810
  const next = `${frameKey2}_${counter++}`;
1376
1811
  el.setAttribute(nodeAttr, next);
@@ -1400,9 +1835,12 @@ async function serializeFrameRecursive(frame, baseContext, frameKey) {
1400
1835
  serializeElement(el) {
1401
1836
  const nodeId = helpers.ensureNodeId(el);
1402
1837
  const instanceToken = helpers.setInstanceToken(el);
1838
+ const packedPath = helpers.packElementPath(
1839
+ helpers.buildElementPath(el)
1840
+ );
1403
1841
  entries.push({
1404
1842
  nodeId,
1405
- path: helpers.buildElementPath(el),
1843
+ path: packedPath,
1406
1844
  instanceToken
1407
1845
  });
1408
1846
  const tag = el.tagName.toLowerCase();
@@ -1430,11 +1868,12 @@ async function serializeFrameRecursive(frame, baseContext, frameKey) {
1430
1868
  win[frameTokenKey] = frameToken;
1431
1869
  const root = document.documentElement;
1432
1870
  if (!root) {
1433
- return { html: "", frameToken, entries };
1871
+ return { html: "", frameToken, nodeTable, entries };
1434
1872
  }
1435
1873
  return {
1436
1874
  html: helpers.serializeElement(root),
1437
1875
  frameToken,
1876
+ nodeTable,
1438
1877
  entries
1439
1878
  };
1440
1879
  },
@@ -1452,13 +1891,18 @@ async function serializeFrameRecursive(frame, baseContext, frameKey) {
1452
1891
  );
1453
1892
  const nodePaths = /* @__PURE__ */ new Map();
1454
1893
  const nodeMeta = /* @__PURE__ */ new Map();
1455
- for (const entry of frameSnapshot.entries) {
1894
+ for (const [index, entry] of frameSnapshot.entries.entries()) {
1895
+ const path5 = decodeSerializedElementPath(
1896
+ frameSnapshot.nodeTable,
1897
+ entry.path,
1898
+ `entries[${index}].path`
1899
+ );
1456
1900
  nodePaths.set(entry.nodeId, {
1457
1901
  context: [
1458
1902
  ...baseContext,
1459
- ...entry.path.context || []
1903
+ ...path5.context || []
1460
1904
  ],
1461
- nodes: entry.path.nodes
1905
+ nodes: path5.nodes
1462
1906
  });
1463
1907
  nodeMeta.set(entry.nodeId, {
1464
1908
  frameToken: frameSnapshot.frameToken,
@@ -3854,7 +4298,7 @@ async function performInput(page, path5, options) {
3854
4298
  await resolved.element.type(options.text);
3855
4299
  }
3856
4300
  if (options.pressEnter) {
3857
- await resolved.element.press("Enter");
4301
+ await resolved.element.press("Enter", { noWaitAfter: true });
3858
4302
  }
3859
4303
  return {
3860
4304
  ok: true,
@@ -4814,16 +5258,18 @@ var CACHE_IMPORT_BATCH_SIZE = 200;
4814
5258
  var RemoteSessionClient = class {
4815
5259
  baseUrl;
4816
5260
  key;
4817
- constructor(baseUrl, key) {
5261
+ authScheme;
5262
+ constructor(baseUrl, key, authScheme = "api-key") {
4818
5263
  this.baseUrl = normalizeBaseUrl(baseUrl);
4819
5264
  this.key = key;
5265
+ this.authScheme = authScheme;
4820
5266
  }
4821
5267
  async create(request) {
4822
5268
  const response = await fetch(`${this.baseUrl}/sessions`, {
4823
5269
  method: "POST",
4824
5270
  headers: {
4825
5271
  "content-type": "application/json",
4826
- "x-api-key": this.key
5272
+ ...this.authHeaders()
4827
5273
  },
4828
5274
  body: JSON.stringify(request)
4829
5275
  });
@@ -4836,7 +5282,7 @@ var RemoteSessionClient = class {
4836
5282
  const response = await fetch(`${this.baseUrl}/sessions/${sessionId}`, {
4837
5283
  method: "DELETE",
4838
5284
  headers: {
4839
- "x-api-key": this.key
5285
+ ...this.authHeaders()
4840
5286
  }
4841
5287
  });
4842
5288
  if (response.status === 204) {
@@ -4866,7 +5312,7 @@ var RemoteSessionClient = class {
4866
5312
  method: "POST",
4867
5313
  headers: {
4868
5314
  "content-type": "application/json",
4869
- "x-api-key": this.key
5315
+ ...this.authHeaders()
4870
5316
  },
4871
5317
  body: JSON.stringify({ entries })
4872
5318
  });
@@ -4875,6 +5321,16 @@ var RemoteSessionClient = class {
4875
5321
  }
4876
5322
  return await response.json();
4877
5323
  }
5324
+ authHeaders() {
5325
+ if (this.authScheme === "bearer") {
5326
+ return {
5327
+ authorization: `Bearer ${this.key}`
5328
+ };
5329
+ }
5330
+ return {
5331
+ "x-api-key": this.key
5332
+ };
5333
+ }
4878
5334
  };
4879
5335
  function normalizeBaseUrl(baseUrl) {
4880
5336
  return baseUrl.replace(/\/+$/, "");
@@ -5496,11 +5952,33 @@ function parseMode(value, source) {
5496
5952
  `Invalid ${source} value "${value}". Use "local" or "remote".`
5497
5953
  );
5498
5954
  }
5955
+ function parseAuthScheme(value, source) {
5956
+ if (value == null) return void 0;
5957
+ if (typeof value !== "string") {
5958
+ throw new Error(
5959
+ `Invalid ${source} value "${String(value)}". Use "api-key" or "bearer".`
5960
+ );
5961
+ }
5962
+ const normalized = value.trim().toLowerCase();
5963
+ if (!normalized) return void 0;
5964
+ if (normalized === "api-key" || normalized === "bearer") {
5965
+ return normalized;
5966
+ }
5967
+ throw new Error(
5968
+ `Invalid ${source} value "${value}". Use "api-key" or "bearer".`
5969
+ );
5970
+ }
5499
5971
  function resolveOpensteerApiKey() {
5500
5972
  const value = process.env.OPENSTEER_API_KEY?.trim();
5501
5973
  if (!value) return void 0;
5502
5974
  return value;
5503
5975
  }
5976
+ function resolveOpensteerAuthScheme() {
5977
+ return parseAuthScheme(
5978
+ process.env.OPENSTEER_AUTH_SCHEME,
5979
+ "OPENSTEER_AUTH_SCHEME"
5980
+ );
5981
+ }
5504
5982
  function normalizeRemoteOptions(value) {
5505
5983
  if (!value || typeof value !== "object" || Array.isArray(value)) {
5506
5984
  return void 0;
@@ -5560,7 +6038,12 @@ function resolveConfig(input = {}) {
5560
6038
  const mergedWithEnv = mergeDeep(mergedWithFile, envConfig);
5561
6039
  const resolved = mergeDeep(mergedWithEnv, input);
5562
6040
  const envApiKey = resolveOpensteerApiKey();
6041
+ const envAuthScheme = resolveOpensteerAuthScheme();
5563
6042
  const inputRemoteOptions = normalizeRemoteOptions(input.remote);
6043
+ const inputAuthScheme = parseAuthScheme(
6044
+ inputRemoteOptions?.authScheme,
6045
+ "remote.authScheme"
6046
+ );
5564
6047
  const inputHasRemoteApiKey = Boolean(
5565
6048
  inputRemoteOptions && Object.prototype.hasOwnProperty.call(inputRemoteOptions, "apiKey")
5566
6049
  );
@@ -5568,8 +6051,12 @@ function resolveConfig(input = {}) {
5568
6051
  mode: resolved.mode
5569
6052
  });
5570
6053
  if (modeSelection.mode === "remote") {
5571
- const resolvedRemote = normalizeRemoteOptions(resolved.remote);
5572
- resolved.remote = resolvedRemote ?? {};
6054
+ const resolvedRemote = normalizeRemoteOptions(resolved.remote) ?? {};
6055
+ const authScheme = inputAuthScheme ?? envAuthScheme ?? parseAuthScheme(resolvedRemote.authScheme, "remote.authScheme") ?? "api-key";
6056
+ resolved.remote = {
6057
+ ...resolvedRemote,
6058
+ authScheme
6059
+ };
5573
6060
  }
5574
6061
  if (envApiKey && modeSelection.mode === "remote" && !inputHasRemoteApiKey) {
5575
6062
  resolved.remote = {
@@ -5648,7 +6135,26 @@ var ACTION_WAIT_PROFILES = {
5648
6135
  type: ROBUST_PROFILE
5649
6136
  };
5650
6137
  var NETWORK_POLL_MS = 50;
5651
- var IGNORED_RESOURCE_TYPES = /* @__PURE__ */ new Set(["websocket", "eventsource"]);
6138
+ var NETWORK_RELAX_AFTER_MS = 1800;
6139
+ var RELAXED_ALLOWED_PENDING = 2;
6140
+ var HEAVY_VISUAL_REQUEST_WINDOW_MS = 5e3;
6141
+ var TRACKED_RESOURCE_TYPES = /* @__PURE__ */ new Set([
6142
+ "document",
6143
+ "fetch",
6144
+ "xhr",
6145
+ "stylesheet",
6146
+ "image",
6147
+ "font",
6148
+ "media"
6149
+ ]);
6150
+ var HEAVY_RESOURCE_TYPES = /* @__PURE__ */ new Set(["document", "fetch", "xhr"]);
6151
+ var HEAVY_VISUAL_RESOURCE_TYPES = /* @__PURE__ */ new Set([
6152
+ "stylesheet",
6153
+ "image",
6154
+ "font",
6155
+ "media"
6156
+ ]);
6157
+ var IGNORED_RESOURCE_TYPES = /* @__PURE__ */ new Set(["websocket", "eventsource", "manifest"]);
5652
6158
  var NOOP_SESSION = {
5653
6159
  async wait() {
5654
6160
  },
@@ -5658,7 +6164,7 @@ var NOOP_SESSION = {
5658
6164
  function createPostActionWaitSession(page, action, override) {
5659
6165
  const profile = resolveActionWaitProfile(action, override);
5660
6166
  if (!profile.enabled) return NOOP_SESSION;
5661
- const tracker = profile.includeNetwork ? new ScopedNetworkTracker(page) : null;
6167
+ const tracker = profile.includeNetwork ? new AdaptiveNetworkTracker(page) : null;
5662
6168
  tracker?.start();
5663
6169
  let settled = false;
5664
6170
  return {
@@ -5666,19 +6172,32 @@ function createPostActionWaitSession(page, action, override) {
5666
6172
  if (settled) return;
5667
6173
  settled = true;
5668
6174
  const deadline = Date.now() + profile.timeout;
5669
- const networkWait = tracker ? tracker.waitForQuiet({
5670
- deadline,
5671
- quietMs: profile.networkQuietMs
5672
- }) : Promise.resolve();
6175
+ const visualTimeout = profile.includeNetwork ? Math.min(
6176
+ profile.timeout,
6177
+ resolveNetworkBackedVisualTimeout(profile.settleMs)
6178
+ ) : profile.timeout;
5673
6179
  try {
5674
- await Promise.all([
5675
- waitForVisualStabilityAcrossFrames(page, {
5676
- timeout: profile.timeout,
6180
+ try {
6181
+ await waitForVisualStabilityAcrossFrames(page, {
6182
+ timeout: visualTimeout,
5677
6183
  settleMs: profile.settleMs
5678
- }),
5679
- networkWait
5680
- ]);
5681
- } catch {
6184
+ });
6185
+ } catch (error) {
6186
+ if (isStealthWaitUnavailableError(error)) {
6187
+ throw error;
6188
+ }
6189
+ } finally {
6190
+ tracker?.freezeCollection();
6191
+ }
6192
+ if (tracker) {
6193
+ try {
6194
+ await tracker.waitForQuiet({
6195
+ deadline,
6196
+ quietMs: profile.networkQuietMs
6197
+ });
6198
+ } catch {
6199
+ }
6200
+ }
5682
6201
  } finally {
5683
6202
  tracker?.stop();
5684
6203
  }
@@ -5718,53 +6237,70 @@ function normalizeMs(value, fallback) {
5718
6237
  }
5719
6238
  return Math.max(0, Math.floor(value));
5720
6239
  }
5721
- var ScopedNetworkTracker = class {
6240
+ function resolveNetworkBackedVisualTimeout(settleMs) {
6241
+ const derived = settleMs * 3 + 300;
6242
+ return Math.max(1200, Math.min(2500, derived));
6243
+ }
6244
+ var AdaptiveNetworkTracker = class {
5722
6245
  constructor(page) {
5723
6246
  this.page = page;
5724
6247
  }
5725
- pending = /* @__PURE__ */ new Set();
6248
+ pending = /* @__PURE__ */ new Map();
5726
6249
  started = false;
6250
+ collecting = false;
6251
+ startedAt = 0;
5727
6252
  idleSince = Date.now();
5728
6253
  start() {
5729
6254
  if (this.started) return;
5730
6255
  this.started = true;
6256
+ this.collecting = true;
6257
+ this.startedAt = Date.now();
6258
+ this.idleSince = this.startedAt;
5731
6259
  this.page.on("request", this.handleRequestStarted);
5732
6260
  this.page.on("requestfinished", this.handleRequestFinished);
5733
6261
  this.page.on("requestfailed", this.handleRequestFinished);
5734
6262
  }
6263
+ freezeCollection() {
6264
+ if (!this.started) return;
6265
+ this.collecting = false;
6266
+ }
5735
6267
  stop() {
5736
6268
  if (!this.started) return;
5737
6269
  this.started = false;
6270
+ this.collecting = false;
5738
6271
  this.page.off("request", this.handleRequestStarted);
5739
6272
  this.page.off("requestfinished", this.handleRequestFinished);
5740
6273
  this.page.off("requestfailed", this.handleRequestFinished);
5741
6274
  this.pending.clear();
6275
+ this.startedAt = 0;
5742
6276
  this.idleSince = Date.now();
5743
6277
  }
5744
6278
  async waitForQuiet(options) {
5745
6279
  const quietMs = Math.max(0, options.quietMs);
5746
6280
  if (quietMs === 0) return;
5747
6281
  while (Date.now() < options.deadline) {
5748
- if (this.pending.size === 0) {
6282
+ const now = Date.now();
6283
+ const allowedPending = this.resolveAllowedPending(now);
6284
+ if (this.pending.size <= allowedPending) {
5749
6285
  if (this.idleSince === 0) {
5750
- this.idleSince = Date.now();
6286
+ this.idleSince = now;
5751
6287
  }
5752
- const idleFor = Date.now() - this.idleSince;
6288
+ const idleFor = now - this.idleSince;
5753
6289
  if (idleFor >= quietMs) {
5754
6290
  return;
5755
6291
  }
5756
6292
  } else {
5757
6293
  this.idleSince = 0;
5758
6294
  }
5759
- const remaining = Math.max(1, options.deadline - Date.now());
5760
- await sleep(Math.min(NETWORK_POLL_MS, remaining));
6295
+ const remaining = Math.max(1, options.deadline - now);
6296
+ await sleep2(Math.min(NETWORK_POLL_MS, remaining));
5761
6297
  }
5762
6298
  }
5763
6299
  handleRequestStarted = (request) => {
5764
- if (!this.started) return;
5765
- const resourceType = request.resourceType();
5766
- if (IGNORED_RESOURCE_TYPES.has(resourceType)) return;
5767
- this.pending.add(request);
6300
+ if (!this.started || !this.collecting) return;
6301
+ const trackedRequest = this.classifyRequest(request);
6302
+ if (!trackedRequest) return;
6303
+ this.pending.set(request, trackedRequest);
5768
6304
  this.idleSince = 0;
5769
6305
  };
5770
6306
  handleRequestFinished = (request) => {
@@ -5774,8 +6310,35 @@ var ScopedNetworkTracker = class {
5774
6310
  this.idleSince = Date.now();
5775
6311
  }
5776
6312
  };
6313
+ classifyRequest(request) {
6314
+ const resourceType = request.resourceType().toLowerCase();
6315
+ if (IGNORED_RESOURCE_TYPES.has(resourceType)) return null;
6316
+ if (!TRACKED_RESOURCE_TYPES.has(resourceType)) return null;
6317
+ const frame = request.frame();
6318
+ if (!frame || frame !== this.page.mainFrame()) return null;
6319
+ return {
6320
+ resourceType,
6321
+ startedAt: Date.now()
6322
+ };
6323
+ }
6324
+ resolveAllowedPending(now) {
6325
+ const relaxed = now - this.startedAt >= NETWORK_RELAX_AFTER_MS ? RELAXED_ALLOWED_PENDING : 0;
6326
+ if (this.hasHeavyPending(now)) return 0;
6327
+ return relaxed;
6328
+ }
6329
+ hasHeavyPending(now) {
6330
+ for (const trackedRequest of this.pending.values()) {
6331
+ if (HEAVY_RESOURCE_TYPES.has(trackedRequest.resourceType)) {
6332
+ return true;
6333
+ }
6334
+ if (HEAVY_VISUAL_RESOURCE_TYPES.has(trackedRequest.resourceType) && now - trackedRequest.startedAt < HEAVY_VISUAL_REQUEST_WINDOW_MS) {
6335
+ return true;
6336
+ }
6337
+ }
6338
+ return false;
6339
+ }
5777
6340
  };
5778
- async function sleep(ms) {
6341
+ async function sleep2(ms) {
5779
6342
  await new Promise((resolve) => {
5780
6343
  setTimeout(resolve, ms);
5781
6344
  });
@@ -6895,9 +7458,9 @@ function clonePersistedExtractNode(node) {
6895
7458
 
6896
7459
  // src/remote/runtime.ts
6897
7460
  var DEFAULT_REMOTE_BASE_URL = "https://remote.opensteer.com";
6898
- function createRemoteRuntimeState(key, baseUrl = resolveRemoteBaseUrl()) {
7461
+ function createRemoteRuntimeState(key, baseUrl = resolveRemoteBaseUrl(), authScheme = "api-key") {
6899
7462
  return {
6900
- sessionClient: new RemoteSessionClient(baseUrl, key),
7463
+ sessionClient: new RemoteSessionClient(baseUrl, key, authScheme),
6901
7464
  cdpClient: new RemoteCdpClient(),
6902
7465
  actionClient: null,
6903
7466
  sessionId: null
@@ -6962,7 +7525,8 @@ var Opensteer = class _Opensteer {
6962
7525
  }
6963
7526
  this.remote = createRemoteRuntimeState(
6964
7527
  apiKey,
6965
- remoteConfig?.baseUrl
7528
+ remoteConfig?.baseUrl,
7529
+ remoteConfig?.authScheme
6966
7530
  );
6967
7531
  } else {
6968
7532
  this.remote = null;
@@ -7418,7 +7982,7 @@ var Opensteer = class _Opensteer {
7418
7982
  await handle.type(options.text);
7419
7983
  }
7420
7984
  if (options.pressEnter) {
7421
- await handle.press("Enter");
7985
+ await handle.press("Enter", { noWaitAfter: true });
7422
7986
  }
7423
7987
  });
7424
7988
  } catch (err) {