opensteer 0.4.5 → 0.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,
@@ -4492,33 +4936,36 @@ var OpensteerActionError = class extends Error {
4492
4936
  }
4493
4937
  };
4494
4938
 
4495
- // src/remote/errors.ts
4496
- var OpensteerRemoteError = class extends Error {
4939
+ // src/cloud/contracts.ts
4940
+ var cloudSessionContractVersion = "v3";
4941
+
4942
+ // src/cloud/errors.ts
4943
+ var OpensteerCloudError = class extends Error {
4497
4944
  code;
4498
4945
  status;
4499
4946
  details;
4500
4947
  constructor(code, message, status, details) {
4501
4948
  super(message);
4502
- this.name = "OpensteerRemoteError";
4949
+ this.name = "OpensteerCloudError";
4503
4950
  this.code = code;
4504
4951
  this.status = status;
4505
4952
  this.details = details;
4506
4953
  }
4507
4954
  };
4508
- function remoteUnsupportedMethodError(method, message) {
4509
- return new OpensteerRemoteError(
4510
- "REMOTE_UNSUPPORTED_METHOD",
4511
- message || `${method} is not supported in remote mode.`
4955
+ function cloudUnsupportedMethodError(method, message) {
4956
+ return new OpensteerCloudError(
4957
+ "CLOUD_UNSUPPORTED_METHOD",
4958
+ message || `${method} is not supported in cloud mode.`
4512
4959
  );
4513
4960
  }
4514
- function remoteNotLaunchedError() {
4515
- return new OpensteerRemoteError(
4516
- "REMOTE_SESSION_NOT_FOUND",
4517
- "Remote session is not connected. Call launch() first."
4961
+ function cloudNotLaunchedError() {
4962
+ return new OpensteerCloudError(
4963
+ "CLOUD_SESSION_NOT_FOUND",
4964
+ "Cloud session is not connected. Call launch() first."
4518
4965
  );
4519
4966
  }
4520
4967
 
4521
- // src/remote/action-ws-client.ts
4968
+ // src/cloud/action-ws-client.ts
4522
4969
  import WebSocket from "ws";
4523
4970
  var ActionWsClient = class _ActionWsClient {
4524
4971
  ws;
@@ -4536,18 +4983,18 @@ var ActionWsClient = class _ActionWsClient {
4536
4983
  });
4537
4984
  ws.on("error", (error) => {
4538
4985
  this.rejectAll(
4539
- new OpensteerRemoteError(
4540
- "REMOTE_TRANSPORT_ERROR",
4541
- `Remote action websocket error: ${error.message}`
4986
+ new OpensteerCloudError(
4987
+ "CLOUD_TRANSPORT_ERROR",
4988
+ `Cloud action websocket error: ${error.message}`
4542
4989
  )
4543
4990
  );
4544
4991
  });
4545
4992
  ws.on("close", () => {
4546
4993
  this.closed = true;
4547
4994
  this.rejectAll(
4548
- new OpensteerRemoteError(
4549
- "REMOTE_SESSION_CLOSED",
4550
- "Remote action websocket closed."
4995
+ new OpensteerCloudError(
4996
+ "CLOUD_SESSION_CLOSED",
4997
+ "Cloud action websocket closed."
4551
4998
  )
4552
4999
  );
4553
5000
  });
@@ -4559,8 +5006,8 @@ var ActionWsClient = class _ActionWsClient {
4559
5006
  ws.once("open", () => resolve());
4560
5007
  ws.once("error", (error) => {
4561
5008
  reject(
4562
- new OpensteerRemoteError(
4563
- "REMOTE_TRANSPORT_ERROR",
5009
+ new OpensteerCloudError(
5010
+ "CLOUD_TRANSPORT_ERROR",
4564
5011
  `Failed to connect action websocket: ${error.message}`
4565
5012
  )
4566
5013
  );
@@ -4570,9 +5017,9 @@ var ActionWsClient = class _ActionWsClient {
4570
5017
  }
4571
5018
  async request(method, args) {
4572
5019
  if (this.closed || this.ws.readyState !== WebSocket.OPEN) {
4573
- throw new OpensteerRemoteError(
4574
- "REMOTE_SESSION_CLOSED",
4575
- "Remote action websocket is closed."
5020
+ throw new OpensteerCloudError(
5021
+ "CLOUD_SESSION_CLOSED",
5022
+ "Cloud action websocket is closed."
4576
5023
  );
4577
5024
  }
4578
5025
  const id = this.nextRequestId;
@@ -4591,8 +5038,8 @@ var ActionWsClient = class _ActionWsClient {
4591
5038
  this.ws.send(JSON.stringify(payload));
4592
5039
  } catch (error) {
4593
5040
  this.pending.delete(id);
4594
- const message = error instanceof Error ? error.message : "Failed to send remote action request.";
4595
- throw new OpensteerRemoteError("REMOTE_TRANSPORT_ERROR", message);
5041
+ const message = error instanceof Error ? error.message : "Failed to send cloud action request.";
5042
+ throw new OpensteerCloudError("CLOUD_TRANSPORT_ERROR", message);
4596
5043
  }
4597
5044
  return await resultPromise;
4598
5045
  }
@@ -4610,9 +5057,9 @@ var ActionWsClient = class _ActionWsClient {
4610
5057
  parsed = JSON.parse(rawDataToUtf8(raw));
4611
5058
  } catch {
4612
5059
  this.rejectAll(
4613
- new OpensteerRemoteError(
4614
- "REMOTE_TRANSPORT_ERROR",
4615
- "Invalid remote action response payload."
5060
+ new OpensteerCloudError(
5061
+ "CLOUD_TRANSPORT_ERROR",
5062
+ "Invalid cloud action response payload."
4616
5063
  )
4617
5064
  );
4618
5065
  return;
@@ -4625,7 +5072,7 @@ var ActionWsClient = class _ActionWsClient {
4625
5072
  return;
4626
5073
  }
4627
5074
  pending.reject(
4628
- new OpensteerRemoteError(
5075
+ new OpensteerCloudError(
4629
5076
  parsed.code,
4630
5077
  parsed.error,
4631
5078
  void 0,
@@ -4653,7 +5100,7 @@ function withTokenQuery(wsUrl, token) {
4653
5100
  return url.toString();
4654
5101
  }
4655
5102
 
4656
- // src/remote/local-cache-sync.ts
5103
+ // src/cloud/local-cache-sync.ts
4657
5104
  import fs2 from "fs";
4658
5105
  import path3 from "path";
4659
5106
  function collectLocalSelectorCacheEntries(storage) {
@@ -4777,26 +5224,26 @@ function dedupeNewest(entries) {
4777
5224
  return [...byKey.values()];
4778
5225
  }
4779
5226
 
4780
- // src/remote/cdp-client.ts
5227
+ // src/cloud/cdp-client.ts
4781
5228
  import {
4782
5229
  chromium
4783
5230
  } from "playwright";
4784
- var RemoteCdpClient = class {
5231
+ var CloudCdpClient = class {
4785
5232
  async connect(args) {
4786
5233
  const endpoint = withTokenQuery2(args.wsUrl, args.token);
4787
5234
  let browser;
4788
5235
  try {
4789
5236
  browser = await chromium.connectOverCDP(endpoint);
4790
5237
  } catch (error) {
4791
- const message = error instanceof Error ? error.message : "Failed to connect to remote CDP endpoint.";
4792
- throw new OpensteerRemoteError("REMOTE_TRANSPORT_ERROR", message);
5238
+ const message = error instanceof Error ? error.message : "Failed to connect to cloud CDP endpoint.";
5239
+ throw new OpensteerCloudError("CLOUD_TRANSPORT_ERROR", message);
4793
5240
  }
4794
5241
  const context = browser.contexts()[0];
4795
5242
  if (!context) {
4796
5243
  await browser.close();
4797
- throw new OpensteerRemoteError(
4798
- "REMOTE_INTERNAL",
4799
- "Remote browser returned no context."
5244
+ throw new OpensteerCloudError(
5245
+ "CLOUD_INTERNAL",
5246
+ "Cloud browser returned no context."
4800
5247
  );
4801
5248
  }
4802
5249
  const page = context.pages()[0] || await context.newPage();
@@ -4809,34 +5256,46 @@ function withTokenQuery2(wsUrl, token) {
4809
5256
  return url.toString();
4810
5257
  }
4811
5258
 
4812
- // src/remote/session-client.ts
5259
+ // src/cloud/session-client.ts
4813
5260
  var CACHE_IMPORT_BATCH_SIZE = 200;
4814
- var RemoteSessionClient = class {
5261
+ var CloudSessionClient = class {
4815
5262
  baseUrl;
4816
5263
  key;
4817
- constructor(baseUrl, key) {
5264
+ authScheme;
5265
+ constructor(baseUrl, key, authScheme = "api-key") {
4818
5266
  this.baseUrl = normalizeBaseUrl(baseUrl);
4819
5267
  this.key = key;
5268
+ this.authScheme = authScheme;
4820
5269
  }
4821
5270
  async create(request) {
4822
5271
  const response = await fetch(`${this.baseUrl}/sessions`, {
4823
5272
  method: "POST",
4824
5273
  headers: {
4825
5274
  "content-type": "application/json",
4826
- "x-api-key": this.key
5275
+ ...this.authHeaders()
4827
5276
  },
4828
5277
  body: JSON.stringify(request)
4829
5278
  });
4830
5279
  if (!response.ok) {
4831
5280
  throw await parseHttpError(response);
4832
5281
  }
4833
- return await response.json();
5282
+ let body;
5283
+ try {
5284
+ body = await response.json();
5285
+ } catch {
5286
+ throw new OpensteerCloudError(
5287
+ "CLOUD_CONTRACT_MISMATCH",
5288
+ "Invalid cloud session create response: expected a JSON object.",
5289
+ response.status
5290
+ );
5291
+ }
5292
+ return parseCreateResponse(body, response.status);
4834
5293
  }
4835
5294
  async close(sessionId) {
4836
5295
  const response = await fetch(`${this.baseUrl}/sessions/${sessionId}`, {
4837
5296
  method: "DELETE",
4838
5297
  headers: {
4839
- "x-api-key": this.key
5298
+ ...this.authHeaders()
4840
5299
  }
4841
5300
  });
4842
5301
  if (response.status === 204) {
@@ -4866,7 +5325,7 @@ var RemoteSessionClient = class {
4866
5325
  method: "POST",
4867
5326
  headers: {
4868
5327
  "content-type": "application/json",
4869
- "x-api-key": this.key
5328
+ ...this.authHeaders()
4870
5329
  },
4871
5330
  body: JSON.stringify({ entries })
4872
5331
  });
@@ -4875,10 +5334,148 @@ var RemoteSessionClient = class {
4875
5334
  }
4876
5335
  return await response.json();
4877
5336
  }
5337
+ authHeaders() {
5338
+ if (this.authScheme === "bearer") {
5339
+ return {
5340
+ authorization: `Bearer ${this.key}`
5341
+ };
5342
+ }
5343
+ return {
5344
+ "x-api-key": this.key
5345
+ };
5346
+ }
4878
5347
  };
4879
5348
  function normalizeBaseUrl(baseUrl) {
4880
5349
  return baseUrl.replace(/\/+$/, "");
4881
5350
  }
5351
+ function parseCreateResponse(body, status) {
5352
+ const root = requireObject(
5353
+ body,
5354
+ "Invalid cloud session create response: expected a JSON object.",
5355
+ status
5356
+ );
5357
+ const sessionId = requireString(root, "sessionId", status);
5358
+ const actionWsUrl = requireString(root, "actionWsUrl", status);
5359
+ const cdpWsUrl = requireString(root, "cdpWsUrl", status);
5360
+ const actionToken = requireString(root, "actionToken", status);
5361
+ const cdpToken = requireString(root, "cdpToken", status);
5362
+ const cloudSessionUrl = requireString(root, "cloudSessionUrl", status);
5363
+ const cloudSessionRoot = requireObject(
5364
+ root.cloudSession,
5365
+ "Invalid cloud session create response: cloudSession must be an object.",
5366
+ status
5367
+ );
5368
+ const cloudSession = {
5369
+ sessionId: requireString(cloudSessionRoot, "sessionId", status, "cloudSession"),
5370
+ workspaceId: requireString(
5371
+ cloudSessionRoot,
5372
+ "workspaceId",
5373
+ status,
5374
+ "cloudSession"
5375
+ ),
5376
+ state: requireString(cloudSessionRoot, "state", status, "cloudSession"),
5377
+ createdAt: requireNumber(cloudSessionRoot, "createdAt", status, "cloudSession"),
5378
+ sourceType: requireSourceType(cloudSessionRoot, "sourceType", status, "cloudSession"),
5379
+ sourceRef: optionalString(cloudSessionRoot, "sourceRef", status, "cloudSession"),
5380
+ label: optionalString(cloudSessionRoot, "label", status, "cloudSession")
5381
+ };
5382
+ const expiresAt = optionalNumber(root, "expiresAt", status);
5383
+ return {
5384
+ sessionId,
5385
+ actionWsUrl,
5386
+ cdpWsUrl,
5387
+ actionToken,
5388
+ cdpToken,
5389
+ expiresAt,
5390
+ cloudSessionUrl,
5391
+ cloudSession
5392
+ };
5393
+ }
5394
+ function requireObject(value, message, status) {
5395
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
5396
+ throw new OpensteerCloudError("CLOUD_CONTRACT_MISMATCH", message, status);
5397
+ }
5398
+ return value;
5399
+ }
5400
+ function requireString(source, field, status, parent) {
5401
+ const value = source[field];
5402
+ if (typeof value !== "string" || !value.trim()) {
5403
+ throw new OpensteerCloudError(
5404
+ "CLOUD_CONTRACT_MISMATCH",
5405
+ `Invalid cloud session create response: ${formatFieldPath(
5406
+ field,
5407
+ parent
5408
+ )} must be a non-empty string.`,
5409
+ status
5410
+ );
5411
+ }
5412
+ return value;
5413
+ }
5414
+ function requireNumber(source, field, status, parent) {
5415
+ const value = source[field];
5416
+ if (typeof value !== "number" || !Number.isFinite(value)) {
5417
+ throw new OpensteerCloudError(
5418
+ "CLOUD_CONTRACT_MISMATCH",
5419
+ `Invalid cloud session create response: ${formatFieldPath(
5420
+ field,
5421
+ parent
5422
+ )} must be a finite number.`,
5423
+ status
5424
+ );
5425
+ }
5426
+ return value;
5427
+ }
5428
+ function optionalString(source, field, status, parent) {
5429
+ const value = source[field];
5430
+ if (value == null) {
5431
+ return void 0;
5432
+ }
5433
+ if (typeof value !== "string") {
5434
+ throw new OpensteerCloudError(
5435
+ "CLOUD_CONTRACT_MISMATCH",
5436
+ `Invalid cloud session create response: ${formatFieldPath(
5437
+ field,
5438
+ parent
5439
+ )} must be a string when present.`,
5440
+ status
5441
+ );
5442
+ }
5443
+ return value;
5444
+ }
5445
+ function optionalNumber(source, field, status, parent) {
5446
+ const value = source[field];
5447
+ if (value == null) {
5448
+ return void 0;
5449
+ }
5450
+ if (typeof value !== "number" || !Number.isFinite(value)) {
5451
+ throw new OpensteerCloudError(
5452
+ "CLOUD_CONTRACT_MISMATCH",
5453
+ `Invalid cloud session create response: ${formatFieldPath(
5454
+ field,
5455
+ parent
5456
+ )} must be a finite number when present.`,
5457
+ status
5458
+ );
5459
+ }
5460
+ return value;
5461
+ }
5462
+ function requireSourceType(source, field, status, parent) {
5463
+ const value = source[field];
5464
+ if (value === "agent-thread" || value === "agent-run" || value === "local-cloud" || value === "manual") {
5465
+ return value;
5466
+ }
5467
+ throw new OpensteerCloudError(
5468
+ "CLOUD_CONTRACT_MISMATCH",
5469
+ `Invalid cloud session create response: ${formatFieldPath(
5470
+ field,
5471
+ parent
5472
+ )} must be one of "agent-thread", "agent-run", "local-cloud", or "manual".`,
5473
+ status
5474
+ );
5475
+ }
5476
+ function formatFieldPath(field, parent) {
5477
+ return parent ? `"${parent}.${field}"` : `"${field}"`;
5478
+ }
4882
5479
  function zeroImportResponse() {
4883
5480
  return {
4884
5481
  imported: 0,
@@ -4902,19 +5499,19 @@ async function parseHttpError(response) {
4902
5499
  } catch {
4903
5500
  body = null;
4904
5501
  }
4905
- const code = typeof body?.code === "string" ? toRemoteErrorCode(body.code) : "REMOTE_TRANSPORT_ERROR";
4906
- const message = typeof body?.error === "string" ? body.error : `Remote request failed with status ${response.status}.`;
4907
- return new OpensteerRemoteError(code, message, response.status, body?.details);
5502
+ const code = typeof body?.code === "string" ? toCloudErrorCode(body.code) : "CLOUD_TRANSPORT_ERROR";
5503
+ const message = typeof body?.error === "string" ? body.error : `Cloud request failed with status ${response.status}.`;
5504
+ return new OpensteerCloudError(code, message, response.status, body?.details);
4908
5505
  }
4909
- function toRemoteErrorCode(code) {
4910
- if (code === "REMOTE_AUTH_FAILED" || code === "REMOTE_SESSION_NOT_FOUND" || code === "REMOTE_SESSION_CLOSED" || code === "REMOTE_UNSUPPORTED_METHOD" || code === "REMOTE_INVALID_REQUEST" || code === "REMOTE_MODEL_NOT_ALLOWED" || code === "REMOTE_ACTION_FAILED" || code === "REMOTE_INTERNAL" || code === "REMOTE_CAPACITY_EXHAUSTED" || code === "REMOTE_RUNTIME_UNAVAILABLE" || code === "REMOTE_RUNTIME_MISMATCH" || code === "REMOTE_SESSION_STALE" || code === "REMOTE_CONTROL_PLANE_ERROR") {
5506
+ function toCloudErrorCode(code) {
5507
+ if (code === "CLOUD_AUTH_FAILED" || code === "CLOUD_SESSION_NOT_FOUND" || code === "CLOUD_SESSION_CLOSED" || code === "CLOUD_UNSUPPORTED_METHOD" || code === "CLOUD_INVALID_REQUEST" || code === "CLOUD_MODEL_NOT_ALLOWED" || code === "CLOUD_ACTION_FAILED" || code === "CLOUD_INTERNAL" || code === "CLOUD_CAPACITY_EXHAUSTED" || code === "CLOUD_RUNTIME_UNAVAILABLE" || code === "CLOUD_RUNTIME_MISMATCH" || code === "CLOUD_SESSION_STALE" || code === "CLOUD_CONTRACT_MISMATCH" || code === "CLOUD_CONTROL_PLANE_ERROR") {
4911
5508
  return code;
4912
5509
  }
4913
- return "REMOTE_TRANSPORT_ERROR";
5510
+ return "CLOUD_TRANSPORT_ERROR";
4914
5511
  }
4915
5512
 
4916
5513
  // src/opensteer.ts
4917
- import { createHash } from "crypto";
5514
+ import { createHash, randomUUID as randomUUID2 } from "crypto";
4918
5515
 
4919
5516
  // src/browser/pool.ts
4920
5517
  import {
@@ -5330,7 +5927,7 @@ var BrowserPool = class {
5330
5927
  const context = contexts[0];
5331
5928
  const pages = context.pages();
5332
5929
  const page = pages.length > 0 ? pages[0] : await context.newPage();
5333
- return { browser, context, page, isRemote: true };
5930
+ return { browser, context, page, isExternal: true };
5334
5931
  } catch (error) {
5335
5932
  if (browser) {
5336
5933
  await browser.close().catch(() => void 0);
@@ -5365,7 +5962,7 @@ var BrowserPool = class {
5365
5962
  context = await browser.newContext(options.context || {});
5366
5963
  page = await context.newPage();
5367
5964
  }
5368
- return { browser, context, page, isRemote: false };
5965
+ return { browser, context, page, isExternal: false };
5369
5966
  }
5370
5967
  async launchSandbox(options) {
5371
5968
  const browser = await chromium2.launch({
@@ -5376,7 +5973,7 @@ var BrowserPool = class {
5376
5973
  const context = await browser.newContext(options.context || {});
5377
5974
  const page = await context.newPage();
5378
5975
  this.browser = browser;
5379
- return { browser, context, page, isRemote: false };
5976
+ return { browser, context, page, isExternal: false };
5380
5977
  }
5381
5978
  };
5382
5979
 
@@ -5413,28 +6010,27 @@ function assertNoLegacyAiConfig(source, config) {
5413
6010
  );
5414
6011
  }
5415
6012
  }
5416
- function assertNoLegacyModeConfig(source, config) {
6013
+ function assertNoLegacyRuntimeConfig(source, config) {
5417
6014
  if (!config || typeof config !== "object") return;
5418
6015
  const configRecord = config;
5419
6016
  if (hasOwn(configRecord, "runtime")) {
5420
6017
  throw new Error(
5421
- `Legacy "runtime" config is no longer supported in ${source}. Use top-level "mode" instead.`
6018
+ `Legacy "runtime" config is no longer supported in ${source}. Use top-level "cloud" instead.`
5422
6019
  );
5423
6020
  }
5424
- if (hasOwn(configRecord, "apiKey")) {
6021
+ if (hasOwn(configRecord, "mode")) {
5425
6022
  throw new Error(
5426
- `Top-level "apiKey" config is not supported in ${source}. Use "remote.apiKey" instead.`
6023
+ `Top-level "mode" config is no longer supported in ${source}. Use "cloud: true" to enable cloud mode.`
5427
6024
  );
5428
6025
  }
5429
- const remoteValue = configRecord.remote;
5430
- if (typeof remoteValue === "boolean") {
6026
+ if (hasOwn(configRecord, "remote")) {
5431
6027
  throw new Error(
5432
- `Boolean "remote" config is no longer supported in ${source}. Use "mode: \\"remote\\"" with "remote" options.`
6028
+ `Top-level "remote" config is no longer supported in ${source}. Use "cloud" options instead.`
5433
6029
  );
5434
6030
  }
5435
- if (remoteValue && typeof remoteValue === "object" && !Array.isArray(remoteValue) && hasOwn(remoteValue, "key")) {
6031
+ if (hasOwn(configRecord, "apiKey")) {
5436
6032
  throw new Error(
5437
- `Legacy "remote.key" config is no longer supported in ${source}. Use "remote.apiKey" instead.`
6033
+ `Top-level "apiKey" config is not supported in ${source}. Use "cloud.apiKey" instead.`
5438
6034
  );
5439
6035
  }
5440
6036
  }
@@ -5480,20 +6076,52 @@ function parseNumber(value) {
5480
6076
  if (!Number.isFinite(parsed)) return void 0;
5481
6077
  return parsed;
5482
6078
  }
5483
- function parseMode(value, source) {
6079
+ function parseRuntimeMode(value, source) {
5484
6080
  if (value == null) return void 0;
5485
6081
  if (typeof value !== "string") {
5486
6082
  throw new Error(
5487
- `Invalid ${source} value "${String(value)}". Use "local" or "remote".`
6083
+ `Invalid ${source} value "${String(value)}". Use "local" or "cloud".`
5488
6084
  );
5489
6085
  }
5490
6086
  const normalized = value.trim().toLowerCase();
5491
6087
  if (!normalized) return void 0;
5492
- if (normalized === "local" || normalized === "remote") {
6088
+ if (normalized === "local" || normalized === "cloud") {
5493
6089
  return normalized;
5494
6090
  }
5495
6091
  throw new Error(
5496
- `Invalid ${source} value "${value}". Use "local" or "remote".`
6092
+ `Invalid ${source} value "${value}". Use "local" or "cloud".`
6093
+ );
6094
+ }
6095
+ function parseAuthScheme(value, source) {
6096
+ if (value == null) return void 0;
6097
+ if (typeof value !== "string") {
6098
+ throw new Error(
6099
+ `Invalid ${source} value "${String(value)}". Use "api-key" or "bearer".`
6100
+ );
6101
+ }
6102
+ const normalized = value.trim().toLowerCase();
6103
+ if (!normalized) return void 0;
6104
+ if (normalized === "api-key" || normalized === "bearer") {
6105
+ return normalized;
6106
+ }
6107
+ throw new Error(
6108
+ `Invalid ${source} value "${value}". Use "api-key" or "bearer".`
6109
+ );
6110
+ }
6111
+ function parseCloudAnnounce(value, source) {
6112
+ if (value == null) return void 0;
6113
+ if (typeof value !== "string") {
6114
+ throw new Error(
6115
+ `Invalid ${source} value "${String(value)}". Use "always", "off", or "tty".`
6116
+ );
6117
+ }
6118
+ const normalized = value.trim().toLowerCase();
6119
+ if (!normalized) return void 0;
6120
+ if (normalized === "always" || normalized === "off" || normalized === "tty") {
6121
+ return normalized;
6122
+ }
6123
+ throw new Error(
6124
+ `Invalid ${source} value "${value}". Use "always", "off", or "tty".`
5497
6125
  );
5498
6126
  }
5499
6127
  function resolveOpensteerApiKey() {
@@ -5501,29 +6129,46 @@ function resolveOpensteerApiKey() {
5501
6129
  if (!value) return void 0;
5502
6130
  return value;
5503
6131
  }
5504
- function normalizeRemoteOptions(value) {
6132
+ function resolveOpensteerAuthScheme() {
6133
+ return parseAuthScheme(
6134
+ process.env.OPENSTEER_AUTH_SCHEME,
6135
+ "OPENSTEER_AUTH_SCHEME"
6136
+ );
6137
+ }
6138
+ function normalizeCloudOptions(value) {
5505
6139
  if (!value || typeof value !== "object" || Array.isArray(value)) {
5506
6140
  return void 0;
5507
6141
  }
5508
6142
  return value;
5509
6143
  }
5510
- function resolveModeSelection(config) {
5511
- const configMode = parseMode(config.mode, "mode");
5512
- if (configMode) {
6144
+ function parseCloudEnabled(value, source) {
6145
+ if (value == null) return void 0;
6146
+ if (typeof value === "boolean") return value;
6147
+ if (typeof value === "object" && !Array.isArray(value)) return true;
6148
+ throw new Error(
6149
+ `Invalid ${source} value "${String(value)}". Use true, false, or a cloud options object.`
6150
+ );
6151
+ }
6152
+ function resolveCloudSelection(config) {
6153
+ const configCloud = parseCloudEnabled(config.cloud, "cloud");
6154
+ if (configCloud !== void 0) {
5513
6155
  return {
5514
- mode: configMode,
5515
- source: "config.mode"
6156
+ cloud: configCloud,
6157
+ source: "config.cloud"
5516
6158
  };
5517
6159
  }
5518
- const envMode = parseMode(process.env.OPENSTEER_MODE, "OPENSTEER_MODE");
6160
+ const envMode = parseRuntimeMode(
6161
+ process.env.OPENSTEER_MODE,
6162
+ "OPENSTEER_MODE"
6163
+ );
5519
6164
  if (envMode) {
5520
6165
  return {
5521
- mode: envMode,
6166
+ cloud: envMode === "cloud",
5522
6167
  source: "env.OPENSTEER_MODE"
5523
6168
  };
5524
6169
  }
5525
6170
  return {
5526
- mode: "local",
6171
+ cloud: false,
5527
6172
  source: "default"
5528
6173
  };
5529
6174
  }
@@ -5539,11 +6184,11 @@ function resolveConfig(input = {}) {
5539
6184
  );
5540
6185
  }
5541
6186
  assertNoLegacyAiConfig("Opensteer constructor config", input);
5542
- assertNoLegacyModeConfig("Opensteer constructor config", input);
6187
+ assertNoLegacyRuntimeConfig("Opensteer constructor config", input);
5543
6188
  const rootDir = input.storage?.rootDir ?? DEFAULT_CONFIG.storage.rootDir ?? process.cwd();
5544
6189
  const fileConfig = loadConfigFile(rootDir);
5545
6190
  assertNoLegacyAiConfig(".opensteer/config.json", fileConfig);
5546
- assertNoLegacyModeConfig(".opensteer/config.json", fileConfig);
6191
+ assertNoLegacyRuntimeConfig(".opensteer/config.json", fileConfig);
5547
6192
  const envConfig = {
5548
6193
  browser: {
5549
6194
  headless: parseBool(process.env.OPENSTEER_HEADLESS),
@@ -5560,20 +6205,39 @@ function resolveConfig(input = {}) {
5560
6205
  const mergedWithEnv = mergeDeep(mergedWithFile, envConfig);
5561
6206
  const resolved = mergeDeep(mergedWithEnv, input);
5562
6207
  const envApiKey = resolveOpensteerApiKey();
5563
- const inputRemoteOptions = normalizeRemoteOptions(input.remote);
5564
- const inputHasRemoteApiKey = Boolean(
5565
- inputRemoteOptions && Object.prototype.hasOwnProperty.call(inputRemoteOptions, "apiKey")
6208
+ const envAuthScheme = resolveOpensteerAuthScheme();
6209
+ const envCloudAnnounce = parseCloudAnnounce(
6210
+ process.env.OPENSTEER_REMOTE_ANNOUNCE,
6211
+ "OPENSTEER_REMOTE_ANNOUNCE"
6212
+ );
6213
+ const inputCloudOptions = normalizeCloudOptions(input.cloud);
6214
+ const inputAuthScheme = parseAuthScheme(
6215
+ inputCloudOptions?.authScheme,
6216
+ "cloud.authScheme"
5566
6217
  );
5567
- const modeSelection = resolveModeSelection({
5568
- mode: resolved.mode
6218
+ const inputCloudAnnounce = parseCloudAnnounce(
6219
+ inputCloudOptions?.announce,
6220
+ "cloud.announce"
6221
+ );
6222
+ const inputHasCloudApiKey = Boolean(
6223
+ inputCloudOptions && Object.prototype.hasOwnProperty.call(inputCloudOptions, "apiKey")
6224
+ );
6225
+ const cloudSelection = resolveCloudSelection({
6226
+ cloud: resolved.cloud
5569
6227
  });
5570
- if (modeSelection.mode === "remote") {
5571
- const resolvedRemote = normalizeRemoteOptions(resolved.remote);
5572
- resolved.remote = resolvedRemote ?? {};
6228
+ if (cloudSelection.cloud) {
6229
+ const resolvedCloud = normalizeCloudOptions(resolved.cloud) ?? {};
6230
+ const authScheme = inputAuthScheme ?? envAuthScheme ?? parseAuthScheme(resolvedCloud.authScheme, "cloud.authScheme") ?? "api-key";
6231
+ const announce = inputCloudAnnounce ?? envCloudAnnounce ?? parseCloudAnnounce(resolvedCloud.announce, "cloud.announce") ?? "always";
6232
+ resolved.cloud = {
6233
+ ...resolvedCloud,
6234
+ authScheme,
6235
+ announce
6236
+ };
5573
6237
  }
5574
- if (envApiKey && modeSelection.mode === "remote" && !inputHasRemoteApiKey) {
5575
- resolved.remote = {
5576
- ...normalizeRemoteOptions(resolved.remote) ?? {},
6238
+ if (envApiKey && cloudSelection.cloud && !inputHasCloudApiKey) {
6239
+ resolved.cloud = {
6240
+ ...normalizeCloudOptions(resolved.cloud) ?? {},
5577
6241
  apiKey: envApiKey
5578
6242
  };
5579
6243
  }
@@ -5648,7 +6312,26 @@ var ACTION_WAIT_PROFILES = {
5648
6312
  type: ROBUST_PROFILE
5649
6313
  };
5650
6314
  var NETWORK_POLL_MS = 50;
5651
- var IGNORED_RESOURCE_TYPES = /* @__PURE__ */ new Set(["websocket", "eventsource"]);
6315
+ var NETWORK_RELAX_AFTER_MS = 1800;
6316
+ var RELAXED_ALLOWED_PENDING = 2;
6317
+ var HEAVY_VISUAL_REQUEST_WINDOW_MS = 5e3;
6318
+ var TRACKED_RESOURCE_TYPES = /* @__PURE__ */ new Set([
6319
+ "document",
6320
+ "fetch",
6321
+ "xhr",
6322
+ "stylesheet",
6323
+ "image",
6324
+ "font",
6325
+ "media"
6326
+ ]);
6327
+ var HEAVY_RESOURCE_TYPES = /* @__PURE__ */ new Set(["document", "fetch", "xhr"]);
6328
+ var HEAVY_VISUAL_RESOURCE_TYPES = /* @__PURE__ */ new Set([
6329
+ "stylesheet",
6330
+ "image",
6331
+ "font",
6332
+ "media"
6333
+ ]);
6334
+ var IGNORED_RESOURCE_TYPES = /* @__PURE__ */ new Set(["websocket", "eventsource", "manifest"]);
5652
6335
  var NOOP_SESSION = {
5653
6336
  async wait() {
5654
6337
  },
@@ -5658,7 +6341,7 @@ var NOOP_SESSION = {
5658
6341
  function createPostActionWaitSession(page, action, override) {
5659
6342
  const profile = resolveActionWaitProfile(action, override);
5660
6343
  if (!profile.enabled) return NOOP_SESSION;
5661
- const tracker = profile.includeNetwork ? new ScopedNetworkTracker(page) : null;
6344
+ const tracker = profile.includeNetwork ? new AdaptiveNetworkTracker(page) : null;
5662
6345
  tracker?.start();
5663
6346
  let settled = false;
5664
6347
  return {
@@ -5666,19 +6349,32 @@ function createPostActionWaitSession(page, action, override) {
5666
6349
  if (settled) return;
5667
6350
  settled = true;
5668
6351
  const deadline = Date.now() + profile.timeout;
5669
- const networkWait = tracker ? tracker.waitForQuiet({
5670
- deadline,
5671
- quietMs: profile.networkQuietMs
5672
- }) : Promise.resolve();
6352
+ const visualTimeout = profile.includeNetwork ? Math.min(
6353
+ profile.timeout,
6354
+ resolveNetworkBackedVisualTimeout(profile.settleMs)
6355
+ ) : profile.timeout;
5673
6356
  try {
5674
- await Promise.all([
5675
- waitForVisualStabilityAcrossFrames(page, {
5676
- timeout: profile.timeout,
6357
+ try {
6358
+ await waitForVisualStabilityAcrossFrames(page, {
6359
+ timeout: visualTimeout,
5677
6360
  settleMs: profile.settleMs
5678
- }),
5679
- networkWait
5680
- ]);
5681
- } catch {
6361
+ });
6362
+ } catch (error) {
6363
+ if (isStealthWaitUnavailableError(error)) {
6364
+ throw error;
6365
+ }
6366
+ } finally {
6367
+ tracker?.freezeCollection();
6368
+ }
6369
+ if (tracker) {
6370
+ try {
6371
+ await tracker.waitForQuiet({
6372
+ deadline,
6373
+ quietMs: profile.networkQuietMs
6374
+ });
6375
+ } catch {
6376
+ }
6377
+ }
5682
6378
  } finally {
5683
6379
  tracker?.stop();
5684
6380
  }
@@ -5718,53 +6414,70 @@ function normalizeMs(value, fallback) {
5718
6414
  }
5719
6415
  return Math.max(0, Math.floor(value));
5720
6416
  }
5721
- var ScopedNetworkTracker = class {
6417
+ function resolveNetworkBackedVisualTimeout(settleMs) {
6418
+ const derived = settleMs * 3 + 300;
6419
+ return Math.max(1200, Math.min(2500, derived));
6420
+ }
6421
+ var AdaptiveNetworkTracker = class {
5722
6422
  constructor(page) {
5723
6423
  this.page = page;
5724
6424
  }
5725
- pending = /* @__PURE__ */ new Set();
6425
+ pending = /* @__PURE__ */ new Map();
5726
6426
  started = false;
6427
+ collecting = false;
6428
+ startedAt = 0;
5727
6429
  idleSince = Date.now();
5728
6430
  start() {
5729
6431
  if (this.started) return;
5730
6432
  this.started = true;
6433
+ this.collecting = true;
6434
+ this.startedAt = Date.now();
6435
+ this.idleSince = this.startedAt;
5731
6436
  this.page.on("request", this.handleRequestStarted);
5732
6437
  this.page.on("requestfinished", this.handleRequestFinished);
5733
6438
  this.page.on("requestfailed", this.handleRequestFinished);
5734
6439
  }
6440
+ freezeCollection() {
6441
+ if (!this.started) return;
6442
+ this.collecting = false;
6443
+ }
5735
6444
  stop() {
5736
6445
  if (!this.started) return;
5737
6446
  this.started = false;
6447
+ this.collecting = false;
5738
6448
  this.page.off("request", this.handleRequestStarted);
5739
6449
  this.page.off("requestfinished", this.handleRequestFinished);
5740
6450
  this.page.off("requestfailed", this.handleRequestFinished);
5741
6451
  this.pending.clear();
6452
+ this.startedAt = 0;
5742
6453
  this.idleSince = Date.now();
5743
6454
  }
5744
6455
  async waitForQuiet(options) {
5745
6456
  const quietMs = Math.max(0, options.quietMs);
5746
6457
  if (quietMs === 0) return;
5747
6458
  while (Date.now() < options.deadline) {
5748
- if (this.pending.size === 0) {
6459
+ const now = Date.now();
6460
+ const allowedPending = this.resolveAllowedPending(now);
6461
+ if (this.pending.size <= allowedPending) {
5749
6462
  if (this.idleSince === 0) {
5750
- this.idleSince = Date.now();
6463
+ this.idleSince = now;
5751
6464
  }
5752
- const idleFor = Date.now() - this.idleSince;
6465
+ const idleFor = now - this.idleSince;
5753
6466
  if (idleFor >= quietMs) {
5754
6467
  return;
5755
6468
  }
5756
6469
  } else {
5757
6470
  this.idleSince = 0;
5758
6471
  }
5759
- const remaining = Math.max(1, options.deadline - Date.now());
5760
- await sleep(Math.min(NETWORK_POLL_MS, remaining));
6472
+ const remaining = Math.max(1, options.deadline - now);
6473
+ await sleep2(Math.min(NETWORK_POLL_MS, remaining));
5761
6474
  }
5762
6475
  }
5763
6476
  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);
6477
+ if (!this.started || !this.collecting) return;
6478
+ const trackedRequest = this.classifyRequest(request);
6479
+ if (!trackedRequest) return;
6480
+ this.pending.set(request, trackedRequest);
5768
6481
  this.idleSince = 0;
5769
6482
  };
5770
6483
  handleRequestFinished = (request) => {
@@ -5774,8 +6487,35 @@ var ScopedNetworkTracker = class {
5774
6487
  this.idleSince = Date.now();
5775
6488
  }
5776
6489
  };
6490
+ classifyRequest(request) {
6491
+ const resourceType = request.resourceType().toLowerCase();
6492
+ if (IGNORED_RESOURCE_TYPES.has(resourceType)) return null;
6493
+ if (!TRACKED_RESOURCE_TYPES.has(resourceType)) return null;
6494
+ const frame = request.frame();
6495
+ if (!frame || frame !== this.page.mainFrame()) return null;
6496
+ return {
6497
+ resourceType,
6498
+ startedAt: Date.now()
6499
+ };
6500
+ }
6501
+ resolveAllowedPending(now) {
6502
+ const relaxed = now - this.startedAt >= NETWORK_RELAX_AFTER_MS ? RELAXED_ALLOWED_PENDING : 0;
6503
+ if (this.hasHeavyPending(now)) return 0;
6504
+ return relaxed;
6505
+ }
6506
+ hasHeavyPending(now) {
6507
+ for (const trackedRequest of this.pending.values()) {
6508
+ if (HEAVY_RESOURCE_TYPES.has(trackedRequest.resourceType)) {
6509
+ return true;
6510
+ }
6511
+ if (HEAVY_VISUAL_RESOURCE_TYPES.has(trackedRequest.resourceType) && now - trackedRequest.startedAt < HEAVY_VISUAL_REQUEST_WINDOW_MS) {
6512
+ return true;
6513
+ }
6514
+ }
6515
+ return false;
6516
+ }
5777
6517
  };
5778
- async function sleep(ms) {
6518
+ async function sleep2(ms) {
5779
6519
  await new Promise((resolve) => {
5780
6520
  setTimeout(resolve, ms);
5781
6521
  });
@@ -6893,22 +7633,35 @@ function clonePersistedExtractNode(node) {
6893
7633
  return JSON.parse(JSON.stringify(node));
6894
7634
  }
6895
7635
 
6896
- // src/remote/runtime.ts
6897
- var DEFAULT_REMOTE_BASE_URL = "https://remote.opensteer.com";
6898
- function createRemoteRuntimeState(key, baseUrl = resolveRemoteBaseUrl()) {
7636
+ // src/cloud/runtime.ts
7637
+ var DEFAULT_CLOUD_BASE_URL = "https://remote.opensteer.com";
7638
+ var DEFAULT_CLOUD_APP_URL = "https://opensteer.com";
7639
+ function createCloudRuntimeState(key, baseUrl = resolveCloudBaseUrl(), authScheme = "api-key", appUrl = resolveCloudAppUrl()) {
6899
7640
  return {
6900
- sessionClient: new RemoteSessionClient(baseUrl, key),
6901
- cdpClient: new RemoteCdpClient(),
7641
+ sessionClient: new CloudSessionClient(baseUrl, key, authScheme),
7642
+ cdpClient: new CloudCdpClient(),
7643
+ appUrl: normalizeCloudAppUrl(appUrl),
6902
7644
  actionClient: null,
6903
- sessionId: null
7645
+ sessionId: null,
7646
+ localRunId: null,
7647
+ cloudSessionUrl: null
6904
7648
  };
6905
7649
  }
6906
- function resolveRemoteBaseUrl() {
7650
+ function resolveCloudBaseUrl() {
6907
7651
  const value = process.env.OPENSTEER_BASE_URL?.trim();
6908
- if (!value) return DEFAULT_REMOTE_BASE_URL;
7652
+ if (!value) return DEFAULT_CLOUD_BASE_URL;
7653
+ return value.replace(/\/+$/, "");
7654
+ }
7655
+ function resolveCloudAppUrl() {
7656
+ const value = process.env.OPENSTEER_APP_URL?.trim();
7657
+ if (!value) return DEFAULT_CLOUD_APP_URL;
7658
+ return normalizeCloudAppUrl(value);
7659
+ }
7660
+ function normalizeCloudAppUrl(value) {
7661
+ if (!value) return null;
6909
7662
  return value.replace(/\/+$/, "");
6910
7663
  }
6911
- function readRemoteActionDescription(payload) {
7664
+ function readCloudActionDescription(payload) {
6912
7665
  const description = payload.description;
6913
7666
  if (typeof description !== "string") return void 0;
6914
7667
  const normalized = description.trim();
@@ -6916,7 +7669,7 @@ function readRemoteActionDescription(payload) {
6916
7669
  }
6917
7670
 
6918
7671
  // src/opensteer.ts
6919
- var REMOTE_INTERACTION_METHODS = /* @__PURE__ */ new Set([
7672
+ var CLOUD_INTERACTION_METHODS = /* @__PURE__ */ new Set([
6920
7673
  "click",
6921
7674
  "dblclick",
6922
7675
  "rightclick",
@@ -6933,7 +7686,7 @@ var Opensteer = class _Opensteer {
6933
7686
  namespace;
6934
7687
  storage;
6935
7688
  pool;
6936
- remote;
7689
+ cloud;
6937
7690
  browser = null;
6938
7691
  pageRef = null;
6939
7692
  contextRef = null;
@@ -6941,8 +7694,8 @@ var Opensteer = class _Opensteer {
6941
7694
  snapshotCache = null;
6942
7695
  constructor(config = {}) {
6943
7696
  const resolved = resolveConfig(config);
6944
- const modeSelection = resolveModeSelection({
6945
- mode: resolved.mode
7697
+ const cloudSelection = resolveCloudSelection({
7698
+ cloud: resolved.cloud
6946
7699
  });
6947
7700
  const model = resolved.model;
6948
7701
  this.config = resolved;
@@ -6952,20 +7705,22 @@ var Opensteer = class _Opensteer {
6952
7705
  this.namespace = resolveNamespace(resolved, rootDir);
6953
7706
  this.storage = new LocalSelectorStorage(rootDir, this.namespace);
6954
7707
  this.pool = new BrowserPool(resolved.browser || {});
6955
- if (modeSelection.mode === "remote") {
6956
- const remoteConfig = resolved.remote && typeof resolved.remote === "object" ? resolved.remote : void 0;
6957
- const apiKey = remoteConfig?.apiKey?.trim();
7708
+ if (cloudSelection.cloud) {
7709
+ const cloudConfig = resolved.cloud && typeof resolved.cloud === "object" ? resolved.cloud : void 0;
7710
+ const apiKey = cloudConfig?.apiKey?.trim();
6958
7711
  if (!apiKey) {
6959
7712
  throw new Error(
6960
- "Remote mode requires a non-empty API key via remote.apiKey or OPENSTEER_API_KEY."
7713
+ "Cloud mode requires a non-empty API key via cloud.apiKey or OPENSTEER_API_KEY."
6961
7714
  );
6962
7715
  }
6963
- this.remote = createRemoteRuntimeState(
7716
+ this.cloud = createCloudRuntimeState(
6964
7717
  apiKey,
6965
- remoteConfig?.baseUrl
7718
+ cloudConfig?.baseUrl,
7719
+ cloudConfig?.authScheme,
7720
+ cloudConfig?.appUrl
6966
7721
  );
6967
7722
  } else {
6968
- this.remote = null;
7723
+ this.cloud = null;
6969
7724
  }
6970
7725
  }
6971
7726
  createLazyResolveCallback(model) {
@@ -7003,32 +7758,32 @@ var Opensteer = class _Opensteer {
7003
7758
  };
7004
7759
  return extract;
7005
7760
  }
7006
- async invokeRemoteActionAndResetCache(method, args) {
7007
- const result = await this.invokeRemoteAction(method, args);
7761
+ async invokeCloudActionAndResetCache(method, args) {
7762
+ const result = await this.invokeCloudAction(method, args);
7008
7763
  this.snapshotCache = null;
7009
7764
  return result;
7010
7765
  }
7011
- async invokeRemoteAction(method, args) {
7012
- const actionClient = this.remote?.actionClient;
7013
- const sessionId = this.remote?.sessionId;
7766
+ async invokeCloudAction(method, args) {
7767
+ const actionClient = this.cloud?.actionClient;
7768
+ const sessionId = this.cloud?.sessionId;
7014
7769
  if (!actionClient || !sessionId) {
7015
- throw remoteNotLaunchedError();
7770
+ throw cloudNotLaunchedError();
7016
7771
  }
7017
7772
  const payload = args && typeof args === "object" ? args : {};
7018
7773
  try {
7019
7774
  return await actionClient.request(method, payload);
7020
7775
  } catch (err) {
7021
- if (err instanceof OpensteerRemoteError && err.code === "REMOTE_ACTION_FAILED" && REMOTE_INTERACTION_METHODS.has(method)) {
7776
+ if (err instanceof OpensteerCloudError && err.code === "CLOUD_ACTION_FAILED" && CLOUD_INTERACTION_METHODS.has(method)) {
7022
7777
  const detailsRecord = err.details && typeof err.details === "object" ? err.details : null;
7023
- const remoteFailure = normalizeActionFailure(
7778
+ const cloudFailure = normalizeActionFailure(
7024
7779
  detailsRecord?.actionFailure
7025
7780
  );
7026
- const failure = remoteFailure || classifyActionFailure({
7781
+ const failure = cloudFailure || classifyActionFailure({
7027
7782
  action: method,
7028
7783
  error: err,
7029
7784
  fallbackMessage: defaultActionFailureMessage(method)
7030
7785
  });
7031
- const description = readRemoteActionDescription(payload);
7786
+ const description = readCloudActionDescription(payload);
7032
7787
  throw this.buildActionError(
7033
7788
  method,
7034
7789
  description,
@@ -7069,8 +7824,36 @@ var Opensteer = class _Opensteer {
7069
7824
  }
7070
7825
  return this.contextRef;
7071
7826
  }
7072
- getRemoteSessionId() {
7073
- return this.remote?.sessionId ?? null;
7827
+ getCloudSessionId() {
7828
+ return this.cloud?.sessionId ?? null;
7829
+ }
7830
+ getCloudSessionUrl() {
7831
+ return this.cloud?.cloudSessionUrl ?? null;
7832
+ }
7833
+ announceCloudSession(args) {
7834
+ if (!this.shouldAnnounceCloudSession()) {
7835
+ return;
7836
+ }
7837
+ const fields = [
7838
+ `sessionId=${args.sessionId}`,
7839
+ `workspaceId=${args.workspaceId}`
7840
+ ];
7841
+ if (args.cloudSessionUrl) {
7842
+ fields.push(`url=${args.cloudSessionUrl}`);
7843
+ }
7844
+ process.stderr.write(`[opensteer] cloud session ready ${fields.join(" ")}
7845
+ `);
7846
+ }
7847
+ shouldAnnounceCloudSession() {
7848
+ const cloudConfig = this.config.cloud && typeof this.config.cloud === "object" ? this.config.cloud : null;
7849
+ const announce = cloudConfig?.announce ?? "always";
7850
+ if (announce === "off") {
7851
+ return false;
7852
+ }
7853
+ if (announce === "tty") {
7854
+ return Boolean(process.stderr.isTTY);
7855
+ }
7856
+ return true;
7074
7857
  }
7075
7858
  async launch(options = {}) {
7076
7859
  if (this.pageRef && !this.ownsBrowser) {
@@ -7081,22 +7864,29 @@ var Opensteer = class _Opensteer {
7081
7864
  if (this.pageRef && this.ownsBrowser) {
7082
7865
  return;
7083
7866
  }
7084
- if (this.remote) {
7867
+ if (this.cloud) {
7085
7868
  let actionClient = null;
7086
7869
  let browser = null;
7087
7870
  let sessionId = null;
7871
+ let localRunId = null;
7088
7872
  try {
7089
7873
  try {
7090
- await this.syncLocalSelectorCacheToRemote();
7874
+ await this.syncLocalSelectorCacheToCloud();
7091
7875
  } catch (error) {
7092
7876
  if (this.config.debug) {
7093
7877
  const message = error instanceof Error ? error.message : String(error);
7094
7878
  console.warn(
7095
- `[opensteer] remote selector cache sync failed: ${message}`
7879
+ `[opensteer] cloud selector cache sync failed: ${message}`
7096
7880
  );
7097
7881
  }
7098
7882
  }
7099
- const session2 = await this.remote.sessionClient.create({
7883
+ localRunId = this.cloud.localRunId || buildLocalRunId(this.namespace);
7884
+ this.cloud.localRunId = localRunId;
7885
+ const session2 = await this.cloud.sessionClient.create({
7886
+ cloudSessionContractVersion,
7887
+ sourceType: "local-cloud",
7888
+ clientSessionHint: this.namespace,
7889
+ localRunId,
7100
7890
  name: this.namespace,
7101
7891
  model: this.config.model,
7102
7892
  launchContext: options.context || void 0
@@ -7107,7 +7897,7 @@ var Opensteer = class _Opensteer {
7107
7897
  token: session2.actionToken,
7108
7898
  sessionId: session2.sessionId
7109
7899
  });
7110
- const cdpConnection = await this.remote.cdpClient.connect({
7900
+ const cdpConnection = await this.cloud.cdpClient.connect({
7111
7901
  wsUrl: session2.cdpWsUrl,
7112
7902
  token: session2.cdpToken
7113
7903
  });
@@ -7117,8 +7907,17 @@ var Opensteer = class _Opensteer {
7117
7907
  this.pageRef = cdpConnection.page;
7118
7908
  this.ownsBrowser = true;
7119
7909
  this.snapshotCache = null;
7120
- this.remote.actionClient = actionClient;
7121
- this.remote.sessionId = sessionId;
7910
+ this.cloud.actionClient = actionClient;
7911
+ this.cloud.sessionId = sessionId;
7912
+ this.cloud.cloudSessionUrl = buildCloudSessionUrl(
7913
+ this.cloud.appUrl,
7914
+ session2.cloudSession.sessionId
7915
+ );
7916
+ this.announceCloudSession({
7917
+ sessionId: session2.sessionId,
7918
+ workspaceId: session2.cloudSession.workspaceId,
7919
+ cloudSessionUrl: this.cloud.cloudSessionUrl
7920
+ });
7122
7921
  return;
7123
7922
  } catch (error) {
7124
7923
  if (actionClient) {
@@ -7128,8 +7927,9 @@ var Opensteer = class _Opensteer {
7128
7927
  await browser.close().catch(() => void 0);
7129
7928
  }
7130
7929
  if (sessionId) {
7131
- await this.remote.sessionClient.close(sessionId).catch(() => void 0);
7930
+ await this.cloud.sessionClient.close(sessionId).catch(() => void 0);
7132
7931
  }
7932
+ this.cloud.cloudSessionUrl = null;
7133
7933
  throw error;
7134
7934
  }
7135
7935
  }
@@ -7147,13 +7947,13 @@ var Opensteer = class _Opensteer {
7147
7947
  }
7148
7948
  static from(page, config = {}) {
7149
7949
  const resolvedConfig = resolveConfig(config);
7150
- const modeSelection = resolveModeSelection({
7151
- mode: resolvedConfig.mode
7950
+ const cloudSelection = resolveCloudSelection({
7951
+ cloud: resolvedConfig.cloud
7152
7952
  });
7153
- if (modeSelection.mode === "remote") {
7154
- throw remoteUnsupportedMethodError(
7953
+ if (cloudSelection.cloud) {
7954
+ throw cloudUnsupportedMethodError(
7155
7955
  "Opensteer.from(page)",
7156
- "Opensteer.from(page) is not supported in remote mode."
7956
+ "Opensteer.from(page) is not supported in cloud mode."
7157
7957
  );
7158
7958
  }
7159
7959
  const instance = new _Opensteer(config);
@@ -7166,12 +7966,14 @@ var Opensteer = class _Opensteer {
7166
7966
  }
7167
7967
  async close() {
7168
7968
  this.snapshotCache = null;
7169
- if (this.remote) {
7170
- const actionClient = this.remote.actionClient;
7171
- const sessionId = this.remote.sessionId;
7969
+ if (this.cloud) {
7970
+ const actionClient = this.cloud.actionClient;
7971
+ const sessionId = this.cloud.sessionId;
7172
7972
  const browser = this.browser;
7173
- this.remote.actionClient = null;
7174
- this.remote.sessionId = null;
7973
+ this.cloud.actionClient = null;
7974
+ this.cloud.sessionId = null;
7975
+ this.cloud.localRunId = null;
7976
+ this.cloud.cloudSessionUrl = null;
7175
7977
  this.browser = null;
7176
7978
  this.pageRef = null;
7177
7979
  this.contextRef = null;
@@ -7183,7 +7985,7 @@ var Opensteer = class _Opensteer {
7183
7985
  await browser.close().catch(() => void 0);
7184
7986
  }
7185
7987
  if (sessionId) {
7186
- await this.remote.sessionClient.close(sessionId).catch(() => void 0);
7988
+ await this.cloud.sessionClient.close(sessionId).catch(() => void 0);
7187
7989
  }
7188
7990
  return;
7189
7991
  }
@@ -7195,17 +7997,17 @@ var Opensteer = class _Opensteer {
7195
7997
  this.contextRef = null;
7196
7998
  this.ownsBrowser = false;
7197
7999
  }
7198
- async syncLocalSelectorCacheToRemote() {
7199
- if (!this.remote) return;
8000
+ async syncLocalSelectorCacheToCloud() {
8001
+ if (!this.cloud) return;
7200
8002
  const entries = collectLocalSelectorCacheEntries(this.storage);
7201
8003
  if (!entries.length) return;
7202
- await this.remote.sessionClient.importSelectorCache({
8004
+ await this.cloud.sessionClient.importSelectorCache({
7203
8005
  entries
7204
8006
  });
7205
8007
  }
7206
8008
  async goto(url, options) {
7207
- if (this.remote) {
7208
- await this.invokeRemoteActionAndResetCache("goto", { url, options });
8009
+ if (this.cloud) {
8010
+ await this.invokeCloudActionAndResetCache("goto", { url, options });
7209
8011
  return;
7210
8012
  }
7211
8013
  const { waitUntil = "domcontentloaded", ...rest } = options ?? {};
@@ -7214,8 +8016,8 @@ var Opensteer = class _Opensteer {
7214
8016
  this.snapshotCache = null;
7215
8017
  }
7216
8018
  async snapshot(options = {}) {
7217
- if (this.remote) {
7218
- return await this.invokeRemoteActionAndResetCache("snapshot", {
8019
+ if (this.cloud) {
8020
+ return await this.invokeCloudActionAndResetCache("snapshot", {
7219
8021
  options
7220
8022
  });
7221
8023
  }
@@ -7224,8 +8026,8 @@ var Opensteer = class _Opensteer {
7224
8026
  return prepared.cleanedHtml;
7225
8027
  }
7226
8028
  async state() {
7227
- if (this.remote) {
7228
- return await this.invokeRemoteAction("state", {});
8029
+ if (this.cloud) {
8030
+ return await this.invokeCloudAction("state", {});
7229
8031
  }
7230
8032
  const html = await this.snapshot({ mode: "action" });
7231
8033
  return {
@@ -7235,8 +8037,8 @@ var Opensteer = class _Opensteer {
7235
8037
  };
7236
8038
  }
7237
8039
  async screenshot(options = {}) {
7238
- if (this.remote) {
7239
- const b64 = await this.invokeRemoteAction(
8040
+ if (this.cloud) {
8041
+ const b64 = await this.invokeCloudAction(
7240
8042
  "screenshot",
7241
8043
  options
7242
8044
  );
@@ -7250,8 +8052,8 @@ var Opensteer = class _Opensteer {
7250
8052
  });
7251
8053
  }
7252
8054
  async click(options) {
7253
- if (this.remote) {
7254
- return await this.invokeRemoteActionAndResetCache(
8055
+ if (this.cloud) {
8056
+ return await this.invokeCloudActionAndResetCache(
7255
8057
  "click",
7256
8058
  options
7257
8059
  );
@@ -7263,8 +8065,8 @@ var Opensteer = class _Opensteer {
7263
8065
  });
7264
8066
  }
7265
8067
  async dblclick(options) {
7266
- if (this.remote) {
7267
- return await this.invokeRemoteActionAndResetCache(
8068
+ if (this.cloud) {
8069
+ return await this.invokeCloudActionAndResetCache(
7268
8070
  "dblclick",
7269
8071
  options
7270
8072
  );
@@ -7276,8 +8078,8 @@ var Opensteer = class _Opensteer {
7276
8078
  });
7277
8079
  }
7278
8080
  async rightclick(options) {
7279
- if (this.remote) {
7280
- return await this.invokeRemoteActionAndResetCache(
8081
+ if (this.cloud) {
8082
+ return await this.invokeCloudActionAndResetCache(
7281
8083
  "rightclick",
7282
8084
  options
7283
8085
  );
@@ -7289,8 +8091,8 @@ var Opensteer = class _Opensteer {
7289
8091
  });
7290
8092
  }
7291
8093
  async hover(options) {
7292
- if (this.remote) {
7293
- return await this.invokeRemoteActionAndResetCache(
8094
+ if (this.cloud) {
8095
+ return await this.invokeCloudActionAndResetCache(
7294
8096
  "hover",
7295
8097
  options
7296
8098
  );
@@ -7388,8 +8190,8 @@ var Opensteer = class _Opensteer {
7388
8190
  );
7389
8191
  }
7390
8192
  async input(options) {
7391
- if (this.remote) {
7392
- return await this.invokeRemoteActionAndResetCache(
8193
+ if (this.cloud) {
8194
+ return await this.invokeCloudActionAndResetCache(
7393
8195
  "input",
7394
8196
  options
7395
8197
  );
@@ -7418,7 +8220,7 @@ var Opensteer = class _Opensteer {
7418
8220
  await handle.type(options.text);
7419
8221
  }
7420
8222
  if (options.pressEnter) {
7421
- await handle.press("Enter");
8223
+ await handle.press("Enter", { noWaitAfter: true });
7422
8224
  }
7423
8225
  });
7424
8226
  } catch (err) {
@@ -7491,8 +8293,8 @@ var Opensteer = class _Opensteer {
7491
8293
  );
7492
8294
  }
7493
8295
  async select(options) {
7494
- if (this.remote) {
7495
- return await this.invokeRemoteActionAndResetCache(
8296
+ if (this.cloud) {
8297
+ return await this.invokeCloudActionAndResetCache(
7496
8298
  "select",
7497
8299
  options
7498
8300
  );
@@ -7601,8 +8403,8 @@ var Opensteer = class _Opensteer {
7601
8403
  );
7602
8404
  }
7603
8405
  async scroll(options = {}) {
7604
- if (this.remote) {
7605
- return await this.invokeRemoteActionAndResetCache(
8406
+ if (this.cloud) {
8407
+ return await this.invokeCloudActionAndResetCache(
7606
8408
  "scroll",
7607
8409
  options
7608
8410
  );
@@ -7703,14 +8505,14 @@ var Opensteer = class _Opensteer {
7703
8505
  }
7704
8506
  // --- Tab Management ---
7705
8507
  async tabs() {
7706
- if (this.remote) {
7707
- return await this.invokeRemoteAction("tabs", {});
8508
+ if (this.cloud) {
8509
+ return await this.invokeCloudAction("tabs", {});
7708
8510
  }
7709
8511
  return listTabs(this.context, this.page);
7710
8512
  }
7711
8513
  async newTab(url) {
7712
- if (this.remote) {
7713
- return await this.invokeRemoteActionAndResetCache("newTab", {
8514
+ if (this.cloud) {
8515
+ return await this.invokeCloudActionAndResetCache("newTab", {
7714
8516
  url
7715
8517
  });
7716
8518
  }
@@ -7720,8 +8522,8 @@ var Opensteer = class _Opensteer {
7720
8522
  return info;
7721
8523
  }
7722
8524
  async switchTab(index) {
7723
- if (this.remote) {
7724
- await this.invokeRemoteActionAndResetCache("switchTab", { index });
8525
+ if (this.cloud) {
8526
+ await this.invokeCloudActionAndResetCache("switchTab", { index });
7725
8527
  return;
7726
8528
  }
7727
8529
  const page = await switchTab(this.context, index);
@@ -7729,8 +8531,8 @@ var Opensteer = class _Opensteer {
7729
8531
  this.snapshotCache = null;
7730
8532
  }
7731
8533
  async closeTab(index) {
7732
- if (this.remote) {
7733
- await this.invokeRemoteActionAndResetCache("closeTab", { index });
8534
+ if (this.cloud) {
8535
+ await this.invokeCloudActionAndResetCache("closeTab", { index });
7734
8536
  return;
7735
8537
  }
7736
8538
  const newPage = await closeTab(this.context, this.page, index);
@@ -7741,8 +8543,8 @@ var Opensteer = class _Opensteer {
7741
8543
  }
7742
8544
  // --- Cookie Management ---
7743
8545
  async getCookies(url) {
7744
- if (this.remote) {
7745
- return await this.invokeRemoteAction(
8546
+ if (this.cloud) {
8547
+ return await this.invokeCloudAction(
7746
8548
  "getCookies",
7747
8549
  { url }
7748
8550
  );
@@ -7750,41 +8552,41 @@ var Opensteer = class _Opensteer {
7750
8552
  return getCookies(this.context, url);
7751
8553
  }
7752
8554
  async setCookie(cookie) {
7753
- if (this.remote) {
7754
- await this.invokeRemoteAction("setCookie", cookie);
8555
+ if (this.cloud) {
8556
+ await this.invokeCloudAction("setCookie", cookie);
7755
8557
  return;
7756
8558
  }
7757
8559
  return setCookie(this.context, cookie);
7758
8560
  }
7759
8561
  async clearCookies() {
7760
- if (this.remote) {
7761
- await this.invokeRemoteAction("clearCookies", {});
8562
+ if (this.cloud) {
8563
+ await this.invokeCloudAction("clearCookies", {});
7762
8564
  return;
7763
8565
  }
7764
8566
  return clearCookies(this.context);
7765
8567
  }
7766
8568
  async exportCookies(filePath, url) {
7767
- if (this.remote) {
7768
- throw remoteUnsupportedMethodError(
8569
+ if (this.cloud) {
8570
+ throw cloudUnsupportedMethodError(
7769
8571
  "exportCookies",
7770
- "exportCookies() is not supported in remote mode because it depends on local filesystem paths."
8572
+ "exportCookies() is not supported in cloud mode because it depends on local filesystem paths."
7771
8573
  );
7772
8574
  }
7773
8575
  return exportCookies(this.context, filePath, url);
7774
8576
  }
7775
8577
  async importCookies(filePath) {
7776
- if (this.remote) {
7777
- throw remoteUnsupportedMethodError(
8578
+ if (this.cloud) {
8579
+ throw cloudUnsupportedMethodError(
7778
8580
  "importCookies",
7779
- "importCookies() is not supported in remote mode because it depends on local filesystem paths."
8581
+ "importCookies() is not supported in cloud mode because it depends on local filesystem paths."
7780
8582
  );
7781
8583
  }
7782
8584
  return importCookies(this.context, filePath);
7783
8585
  }
7784
8586
  // --- Keyboard Input ---
7785
8587
  async pressKey(key) {
7786
- if (this.remote) {
7787
- await this.invokeRemoteActionAndResetCache("pressKey", { key });
8588
+ if (this.cloud) {
8589
+ await this.invokeCloudActionAndResetCache("pressKey", { key });
7788
8590
  return;
7789
8591
  }
7790
8592
  await this.runWithPostActionWait("pressKey", void 0, async () => {
@@ -7793,8 +8595,8 @@ var Opensteer = class _Opensteer {
7793
8595
  this.snapshotCache = null;
7794
8596
  }
7795
8597
  async type(text) {
7796
- if (this.remote) {
7797
- await this.invokeRemoteActionAndResetCache("type", { text });
8598
+ if (this.cloud) {
8599
+ await this.invokeCloudActionAndResetCache("type", { text });
7798
8600
  return;
7799
8601
  }
7800
8602
  await this.runWithPostActionWait("type", void 0, async () => {
@@ -7804,8 +8606,8 @@ var Opensteer = class _Opensteer {
7804
8606
  }
7805
8607
  // --- Element Info ---
7806
8608
  async getElementText(options) {
7807
- if (this.remote) {
7808
- return await this.invokeRemoteAction("getElementText", options);
8609
+ if (this.cloud) {
8610
+ return await this.invokeCloudAction("getElementText", options);
7809
8611
  }
7810
8612
  return this.executeElementInfoAction(
7811
8613
  "getElementText",
@@ -7818,8 +8620,8 @@ var Opensteer = class _Opensteer {
7818
8620
  );
7819
8621
  }
7820
8622
  async getElementValue(options) {
7821
- if (this.remote) {
7822
- return await this.invokeRemoteAction(
8623
+ if (this.cloud) {
8624
+ return await this.invokeCloudAction(
7823
8625
  "getElementValue",
7824
8626
  options
7825
8627
  );
@@ -7834,8 +8636,8 @@ var Opensteer = class _Opensteer {
7834
8636
  );
7835
8637
  }
7836
8638
  async getElementAttributes(options) {
7837
- if (this.remote) {
7838
- return await this.invokeRemoteAction(
8639
+ if (this.cloud) {
8640
+ return await this.invokeCloudAction(
7839
8641
  "getElementAttributes",
7840
8642
  options
7841
8643
  );
@@ -7856,8 +8658,8 @@ var Opensteer = class _Opensteer {
7856
8658
  );
7857
8659
  }
7858
8660
  async getElementBoundingBox(options) {
7859
- if (this.remote) {
7860
- return await this.invokeRemoteAction(
8661
+ if (this.cloud) {
8662
+ return await this.invokeCloudAction(
7861
8663
  "getElementBoundingBox",
7862
8664
  options
7863
8665
  );
@@ -7872,14 +8674,14 @@ var Opensteer = class _Opensteer {
7872
8674
  );
7873
8675
  }
7874
8676
  async getHtml(selector) {
7875
- if (this.remote) {
7876
- return await this.invokeRemoteAction("getHtml", { selector });
8677
+ if (this.cloud) {
8678
+ return await this.invokeCloudAction("getHtml", { selector });
7877
8679
  }
7878
8680
  return getPageHtml(this.page, selector);
7879
8681
  }
7880
8682
  async getTitle() {
7881
- if (this.remote) {
7882
- return await this.invokeRemoteAction("getTitle", {});
8683
+ if (this.cloud) {
8684
+ return await this.invokeCloudAction("getTitle", {});
7883
8685
  }
7884
8686
  return getPageTitle(this.page);
7885
8687
  }
@@ -7917,10 +8719,10 @@ var Opensteer = class _Opensteer {
7917
8719
  }
7918
8720
  // --- File Upload ---
7919
8721
  async uploadFile(options) {
7920
- if (this.remote) {
7921
- throw remoteUnsupportedMethodError(
8722
+ if (this.cloud) {
8723
+ throw cloudUnsupportedMethodError(
7922
8724
  "uploadFile",
7923
- "uploadFile() is not supported in remote mode because file paths must be accessible on the remote server."
8725
+ "uploadFile() is not supported in cloud mode because file paths must be accessible on the cloud runtime."
7924
8726
  );
7925
8727
  }
7926
8728
  const storageKey = this.resolveStorageKey(options.description);
@@ -8022,15 +8824,15 @@ var Opensteer = class _Opensteer {
8022
8824
  }
8023
8825
  // --- Wait for Text ---
8024
8826
  async waitForText(text, options) {
8025
- if (this.remote) {
8026
- await this.invokeRemoteAction("waitForText", { text, options });
8827
+ if (this.cloud) {
8828
+ await this.invokeCloudAction("waitForText", { text, options });
8027
8829
  return;
8028
8830
  }
8029
8831
  await this.page.getByText(text).first().waitFor({ timeout: options?.timeout ?? 3e4 });
8030
8832
  }
8031
8833
  async extract(options) {
8032
- if (this.remote) {
8033
- return await this.invokeRemoteAction("extract", options);
8834
+ if (this.cloud) {
8835
+ return await this.invokeCloudAction("extract", options);
8034
8836
  }
8035
8837
  const storageKey = this.resolveStorageKey(options.description);
8036
8838
  const schemaHash = options.schema ? computeSchemaHash(options.schema) : null;
@@ -8082,8 +8884,8 @@ var Opensteer = class _Opensteer {
8082
8884
  return inflateDataPathObject(data);
8083
8885
  }
8084
8886
  async extractFromPlan(options) {
8085
- if (this.remote) {
8086
- return await this.invokeRemoteAction(
8887
+ if (this.cloud) {
8888
+ return await this.invokeCloudAction(
8087
8889
  "extractFromPlan",
8088
8890
  options
8089
8891
  );
@@ -8132,10 +8934,10 @@ var Opensteer = class _Opensteer {
8132
8934
  return this.storage;
8133
8935
  }
8134
8936
  clearCache() {
8135
- if (this.remote) {
8937
+ if (this.cloud) {
8136
8938
  this.snapshotCache = null;
8137
- if (!this.remote.actionClient) return;
8138
- void this.invokeRemoteAction("clearCache", {});
8939
+ if (!this.cloud.actionClient) return;
8940
+ void this.invokeCloudAction("clearCache", {});
8139
8941
  return;
8140
8942
  }
8141
8943
  this.storage.clearNamespace();
@@ -9163,6 +9965,16 @@ function getScrollDelta2(options) {
9163
9965
  return { x: 0, y: absoluteAmount };
9164
9966
  }
9165
9967
  }
9968
+ function buildLocalRunId(namespace) {
9969
+ const normalized = namespace.trim() || "default";
9970
+ return `${normalized}-${Date.now().toString(36)}-${randomUUID2().slice(0, 8)}`;
9971
+ }
9972
+ function buildCloudSessionUrl(appUrl, sessionId) {
9973
+ if (!appUrl) {
9974
+ return null;
9975
+ }
9976
+ return `${appUrl}/browser/${encodeURIComponent(sessionId)}`;
9977
+ }
9166
9978
 
9167
9979
  export {
9168
9980
  normalizeNamespace,
@@ -9225,12 +10037,13 @@ export {
9225
10037
  getPageTitle,
9226
10038
  performFileUpload,
9227
10039
  OpensteerActionError,
9228
- OpensteerRemoteError,
9229
- remoteUnsupportedMethodError,
9230
- remoteNotLaunchedError,
10040
+ cloudSessionContractVersion,
10041
+ OpensteerCloudError,
10042
+ cloudUnsupportedMethodError,
10043
+ cloudNotLaunchedError,
9231
10044
  ActionWsClient,
9232
10045
  collectLocalSelectorCacheEntries,
9233
- RemoteCdpClient,
9234
- RemoteSessionClient,
10046
+ CloudCdpClient,
10047
+ CloudSessionClient,
9235
10048
  Opensteer
9236
10049
  };