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.
package/dist/index.cjs CHANGED
@@ -338,6 +338,8 @@ var init_extractor = __esm({
338
338
  var index_exports = {};
339
339
  __export(index_exports, {
340
340
  ActionWsClient: () => ActionWsClient,
341
+ CloudCdpClient: () => CloudCdpClient,
342
+ CloudSessionClient: () => CloudSessionClient,
341
343
  CounterResolutionError: () => CounterResolutionError,
342
344
  ElementPathError: () => ElementPathError,
343
345
  LocalSelectorStorage: () => LocalSelectorStorage,
@@ -351,9 +353,7 @@ __export(index_exports, {
351
353
  OS_UNAVAILABLE_ATTR: () => OS_UNAVAILABLE_ATTR,
352
354
  Opensteer: () => Opensteer,
353
355
  OpensteerActionError: () => OpensteerActionError,
354
- OpensteerRemoteError: () => OpensteerRemoteError,
355
- RemoteCdpClient: () => RemoteCdpClient,
356
- RemoteSessionClient: () => RemoteSessionClient,
356
+ OpensteerCloudError: () => OpensteerCloudError,
357
357
  buildElementPathFromHandle: () => buildElementPathFromHandle,
358
358
  buildElementPathFromSelector: () => buildElementPathFromSelector,
359
359
  buildPathSelectorHint: () => buildPathSelectorHint,
@@ -365,6 +365,9 @@ __export(index_exports, {
365
365
  clearCookies: () => clearCookies,
366
366
  cloneElementPath: () => cloneElementPath,
367
367
  closeTab: () => closeTab,
368
+ cloudNotLaunchedError: () => cloudNotLaunchedError,
369
+ cloudSessionContractVersion: () => cloudSessionContractVersion,
370
+ cloudUnsupportedMethodError: () => cloudUnsupportedMethodError,
368
371
  collectLocalSelectorCacheEntries: () => collectLocalSelectorCacheEntries,
369
372
  countArrayItemsWithPath: () => countArrayItemsWithPath,
370
373
  createEmptyRegistry: () => createEmptyRegistry,
@@ -396,8 +399,6 @@ __export(index_exports, {
396
399
  performSelect: () => performSelect,
397
400
  prepareSnapshot: () => prepareSnapshot,
398
401
  pressKey: () => pressKey,
399
- remoteNotLaunchedError: () => remoteNotLaunchedError,
400
- remoteUnsupportedMethodError: () => remoteUnsupportedMethodError,
401
402
  resolveCounterElement: () => resolveCounterElement,
402
403
  resolveCountersBatch: () => resolveCountersBatch,
403
404
  resolveElementPath: () => resolveElementPath,
@@ -826,7 +827,7 @@ var BrowserPool = class {
826
827
  const context = contexts[0];
827
828
  const pages = context.pages();
828
829
  const page = pages.length > 0 ? pages[0] : await context.newPage();
829
- return { browser, context, page, isRemote: true };
830
+ return { browser, context, page, isExternal: true };
830
831
  } catch (error) {
831
832
  if (browser) {
832
833
  await browser.close().catch(() => void 0);
@@ -861,7 +862,7 @@ var BrowserPool = class {
861
862
  context = await browser.newContext(options.context || {});
862
863
  page = await context.newPage();
863
864
  }
864
- return { browser, context, page, isRemote: false };
865
+ return { browser, context, page, isExternal: false };
865
866
  }
866
867
  async launchSandbox(options) {
867
868
  const browser = await import_playwright.chromium.launch({
@@ -872,7 +873,7 @@ var BrowserPool = class {
872
873
  const context = await browser.newContext(options.context || {});
873
874
  const page = await context.newPage();
874
875
  this.browser = browser;
875
- return { browser, context, page, isRemote: false };
876
+ return { browser, context, page, isExternal: false };
876
877
  }
877
878
  };
878
879
 
@@ -943,28 +944,27 @@ function assertNoLegacyAiConfig(source, config) {
943
944
  );
944
945
  }
945
946
  }
946
- function assertNoLegacyModeConfig(source, config) {
947
+ function assertNoLegacyRuntimeConfig(source, config) {
947
948
  if (!config || typeof config !== "object") return;
948
949
  const configRecord = config;
949
950
  if (hasOwn(configRecord, "runtime")) {
950
951
  throw new Error(
951
- `Legacy "runtime" config is no longer supported in ${source}. Use top-level "mode" instead.`
952
+ `Legacy "runtime" config is no longer supported in ${source}. Use top-level "cloud" instead.`
952
953
  );
953
954
  }
954
- if (hasOwn(configRecord, "apiKey")) {
955
+ if (hasOwn(configRecord, "mode")) {
955
956
  throw new Error(
956
- `Top-level "apiKey" config is not supported in ${source}. Use "remote.apiKey" instead.`
957
+ `Top-level "mode" config is no longer supported in ${source}. Use "cloud: true" to enable cloud mode.`
957
958
  );
958
959
  }
959
- const remoteValue = configRecord.remote;
960
- if (typeof remoteValue === "boolean") {
960
+ if (hasOwn(configRecord, "remote")) {
961
961
  throw new Error(
962
- `Boolean "remote" config is no longer supported in ${source}. Use "mode: \\"remote\\"" with "remote" options.`
962
+ `Top-level "remote" config is no longer supported in ${source}. Use "cloud" options instead.`
963
963
  );
964
964
  }
965
- if (remoteValue && typeof remoteValue === "object" && !Array.isArray(remoteValue) && hasOwn(remoteValue, "key")) {
965
+ if (hasOwn(configRecord, "apiKey")) {
966
966
  throw new Error(
967
- `Legacy "remote.key" config is no longer supported in ${source}. Use "remote.apiKey" instead.`
967
+ `Top-level "apiKey" config is not supported in ${source}. Use "cloud.apiKey" instead.`
968
968
  );
969
969
  }
970
970
  }
@@ -1010,20 +1010,52 @@ function parseNumber(value) {
1010
1010
  if (!Number.isFinite(parsed)) return void 0;
1011
1011
  return parsed;
1012
1012
  }
1013
- function parseMode(value, source) {
1013
+ function parseRuntimeMode(value, source) {
1014
1014
  if (value == null) return void 0;
1015
1015
  if (typeof value !== "string") {
1016
1016
  throw new Error(
1017
- `Invalid ${source} value "${String(value)}". Use "local" or "remote".`
1017
+ `Invalid ${source} value "${String(value)}". Use "local" or "cloud".`
1018
1018
  );
1019
1019
  }
1020
1020
  const normalized = value.trim().toLowerCase();
1021
1021
  if (!normalized) return void 0;
1022
- if (normalized === "local" || normalized === "remote") {
1022
+ if (normalized === "local" || normalized === "cloud") {
1023
1023
  return normalized;
1024
1024
  }
1025
1025
  throw new Error(
1026
- `Invalid ${source} value "${value}". Use "local" or "remote".`
1026
+ `Invalid ${source} value "${value}". Use "local" or "cloud".`
1027
+ );
1028
+ }
1029
+ function parseAuthScheme(value, source) {
1030
+ if (value == null) return void 0;
1031
+ if (typeof value !== "string") {
1032
+ throw new Error(
1033
+ `Invalid ${source} value "${String(value)}". Use "api-key" or "bearer".`
1034
+ );
1035
+ }
1036
+ const normalized = value.trim().toLowerCase();
1037
+ if (!normalized) return void 0;
1038
+ if (normalized === "api-key" || normalized === "bearer") {
1039
+ return normalized;
1040
+ }
1041
+ throw new Error(
1042
+ `Invalid ${source} value "${value}". Use "api-key" or "bearer".`
1043
+ );
1044
+ }
1045
+ function parseCloudAnnounce(value, source) {
1046
+ if (value == null) return void 0;
1047
+ if (typeof value !== "string") {
1048
+ throw new Error(
1049
+ `Invalid ${source} value "${String(value)}". Use "always", "off", or "tty".`
1050
+ );
1051
+ }
1052
+ const normalized = value.trim().toLowerCase();
1053
+ if (!normalized) return void 0;
1054
+ if (normalized === "always" || normalized === "off" || normalized === "tty") {
1055
+ return normalized;
1056
+ }
1057
+ throw new Error(
1058
+ `Invalid ${source} value "${value}". Use "always", "off", or "tty".`
1027
1059
  );
1028
1060
  }
1029
1061
  function resolveOpensteerApiKey() {
@@ -1031,29 +1063,46 @@ function resolveOpensteerApiKey() {
1031
1063
  if (!value) return void 0;
1032
1064
  return value;
1033
1065
  }
1034
- function normalizeRemoteOptions(value) {
1066
+ function resolveOpensteerAuthScheme() {
1067
+ return parseAuthScheme(
1068
+ process.env.OPENSTEER_AUTH_SCHEME,
1069
+ "OPENSTEER_AUTH_SCHEME"
1070
+ );
1071
+ }
1072
+ function normalizeCloudOptions(value) {
1035
1073
  if (!value || typeof value !== "object" || Array.isArray(value)) {
1036
1074
  return void 0;
1037
1075
  }
1038
1076
  return value;
1039
1077
  }
1040
- function resolveModeSelection(config) {
1041
- const configMode = parseMode(config.mode, "mode");
1042
- if (configMode) {
1078
+ function parseCloudEnabled(value, source) {
1079
+ if (value == null) return void 0;
1080
+ if (typeof value === "boolean") return value;
1081
+ if (typeof value === "object" && !Array.isArray(value)) return true;
1082
+ throw new Error(
1083
+ `Invalid ${source} value "${String(value)}". Use true, false, or a cloud options object.`
1084
+ );
1085
+ }
1086
+ function resolveCloudSelection(config) {
1087
+ const configCloud = parseCloudEnabled(config.cloud, "cloud");
1088
+ if (configCloud !== void 0) {
1043
1089
  return {
1044
- mode: configMode,
1045
- source: "config.mode"
1090
+ cloud: configCloud,
1091
+ source: "config.cloud"
1046
1092
  };
1047
1093
  }
1048
- const envMode = parseMode(process.env.OPENSTEER_MODE, "OPENSTEER_MODE");
1094
+ const envMode = parseRuntimeMode(
1095
+ process.env.OPENSTEER_MODE,
1096
+ "OPENSTEER_MODE"
1097
+ );
1049
1098
  if (envMode) {
1050
1099
  return {
1051
- mode: envMode,
1100
+ cloud: envMode === "cloud",
1052
1101
  source: "env.OPENSTEER_MODE"
1053
1102
  };
1054
1103
  }
1055
1104
  return {
1056
- mode: "local",
1105
+ cloud: false,
1057
1106
  source: "default"
1058
1107
  };
1059
1108
  }
@@ -1069,11 +1118,11 @@ function resolveConfig(input = {}) {
1069
1118
  );
1070
1119
  }
1071
1120
  assertNoLegacyAiConfig("Opensteer constructor config", input);
1072
- assertNoLegacyModeConfig("Opensteer constructor config", input);
1121
+ assertNoLegacyRuntimeConfig("Opensteer constructor config", input);
1073
1122
  const rootDir = input.storage?.rootDir ?? DEFAULT_CONFIG.storage.rootDir ?? process.cwd();
1074
1123
  const fileConfig = loadConfigFile(rootDir);
1075
1124
  assertNoLegacyAiConfig(".opensteer/config.json", fileConfig);
1076
- assertNoLegacyModeConfig(".opensteer/config.json", fileConfig);
1125
+ assertNoLegacyRuntimeConfig(".opensteer/config.json", fileConfig);
1077
1126
  const envConfig = {
1078
1127
  browser: {
1079
1128
  headless: parseBool(process.env.OPENSTEER_HEADLESS),
@@ -1090,20 +1139,39 @@ function resolveConfig(input = {}) {
1090
1139
  const mergedWithEnv = mergeDeep(mergedWithFile, envConfig);
1091
1140
  const resolved = mergeDeep(mergedWithEnv, input);
1092
1141
  const envApiKey = resolveOpensteerApiKey();
1093
- const inputRemoteOptions = normalizeRemoteOptions(input.remote);
1094
- const inputHasRemoteApiKey = Boolean(
1095
- inputRemoteOptions && Object.prototype.hasOwnProperty.call(inputRemoteOptions, "apiKey")
1142
+ const envAuthScheme = resolveOpensteerAuthScheme();
1143
+ const envCloudAnnounce = parseCloudAnnounce(
1144
+ process.env.OPENSTEER_REMOTE_ANNOUNCE,
1145
+ "OPENSTEER_REMOTE_ANNOUNCE"
1146
+ );
1147
+ const inputCloudOptions = normalizeCloudOptions(input.cloud);
1148
+ const inputAuthScheme = parseAuthScheme(
1149
+ inputCloudOptions?.authScheme,
1150
+ "cloud.authScheme"
1151
+ );
1152
+ const inputCloudAnnounce = parseCloudAnnounce(
1153
+ inputCloudOptions?.announce,
1154
+ "cloud.announce"
1155
+ );
1156
+ const inputHasCloudApiKey = Boolean(
1157
+ inputCloudOptions && Object.prototype.hasOwnProperty.call(inputCloudOptions, "apiKey")
1096
1158
  );
1097
- const modeSelection = resolveModeSelection({
1098
- mode: resolved.mode
1159
+ const cloudSelection = resolveCloudSelection({
1160
+ cloud: resolved.cloud
1099
1161
  });
1100
- if (modeSelection.mode === "remote") {
1101
- const resolvedRemote = normalizeRemoteOptions(resolved.remote);
1102
- resolved.remote = resolvedRemote ?? {};
1162
+ if (cloudSelection.cloud) {
1163
+ const resolvedCloud = normalizeCloudOptions(resolved.cloud) ?? {};
1164
+ const authScheme = inputAuthScheme ?? envAuthScheme ?? parseAuthScheme(resolvedCloud.authScheme, "cloud.authScheme") ?? "api-key";
1165
+ const announce = inputCloudAnnounce ?? envCloudAnnounce ?? parseCloudAnnounce(resolvedCloud.announce, "cloud.announce") ?? "always";
1166
+ resolved.cloud = {
1167
+ ...resolvedCloud,
1168
+ authScheme,
1169
+ announce
1170
+ };
1103
1171
  }
1104
- if (envApiKey && modeSelection.mode === "remote" && !inputHasRemoteApiKey) {
1105
- resolved.remote = {
1106
- ...normalizeRemoteOptions(resolved.remote) ?? {},
1172
+ if (envApiKey && cloudSelection.cloud && !inputHasCloudApiKey) {
1173
+ resolved.cloud = {
1174
+ ...normalizeCloudOptions(resolved.cloud) ?? {},
1107
1175
  apiKey: envApiKey
1108
1176
  };
1109
1177
  }
@@ -1146,15 +1214,52 @@ function getCallerFilePath() {
1146
1214
  // src/navigation.ts
1147
1215
  var DEFAULT_TIMEOUT = 3e4;
1148
1216
  var DEFAULT_SETTLE_MS = 750;
1217
+ var FRAME_EVALUATE_GRACE_MS = 200;
1218
+ var STEALTH_WORLD_NAME = "__opensteer_wait__";
1219
+ var StealthWaitUnavailableError = class extends Error {
1220
+ constructor(cause) {
1221
+ super("Stealth visual wait requires Chromium CDP support.", { cause });
1222
+ this.name = "StealthWaitUnavailableError";
1223
+ }
1224
+ };
1225
+ function isStealthWaitUnavailableError(error) {
1226
+ return error instanceof StealthWaitUnavailableError;
1227
+ }
1228
+ var FRAME_OWNER_VISIBILITY_FUNCTION = `function() {
1229
+ if (!(this instanceof HTMLElement)) return false;
1230
+
1231
+ var rect = this.getBoundingClientRect();
1232
+ if (rect.width <= 0 || rect.height <= 0) return false;
1233
+ if (
1234
+ rect.bottom <= 0 ||
1235
+ rect.right <= 0 ||
1236
+ rect.top >= window.innerHeight ||
1237
+ rect.left >= window.innerWidth
1238
+ ) {
1239
+ return false;
1240
+ }
1241
+
1242
+ var style = window.getComputedStyle(this);
1243
+ if (
1244
+ style.display === 'none' ||
1245
+ style.visibility === 'hidden' ||
1246
+ Number(style.opacity) === 0
1247
+ ) {
1248
+ return false;
1249
+ }
1250
+
1251
+ return true;
1252
+ }`;
1149
1253
  function buildStabilityScript(timeout, settleMs) {
1150
1254
  return `new Promise(function(resolve) {
1151
1255
  var deadline = Date.now() + ${timeout};
1152
- var timer = null;
1153
1256
  var resolved = false;
1257
+ var timer = null;
1154
1258
  var observers = [];
1155
1259
  var observedShadowRoots = [];
1156
1260
  var fonts = document.fonts;
1157
1261
  var fontsReady = !fonts || fonts.status === 'loaded';
1262
+ var lastRelevantMutationAt = Date.now();
1158
1263
 
1159
1264
  function clearObservers() {
1160
1265
  for (var i = 0; i < observers.length; i++) {
@@ -1172,9 +1277,87 @@ function buildStabilityScript(timeout, settleMs) {
1172
1277
  resolve();
1173
1278
  }
1174
1279
 
1280
+ function isElementVisiblyIntersectingViewport(element) {
1281
+ if (!(element instanceof Element)) return false;
1282
+
1283
+ var rect = element.getBoundingClientRect();
1284
+ var inViewport =
1285
+ rect.width > 0 &&
1286
+ rect.height > 0 &&
1287
+ rect.bottom > 0 &&
1288
+ rect.right > 0 &&
1289
+ rect.top < window.innerHeight &&
1290
+ rect.left < window.innerWidth;
1291
+
1292
+ if (!inViewport) return false;
1293
+
1294
+ var style = window.getComputedStyle(element);
1295
+ if (style.visibility === 'hidden' || style.display === 'none') {
1296
+ return false;
1297
+ }
1298
+ if (Number(style.opacity) === 0) {
1299
+ return false;
1300
+ }
1301
+
1302
+ return true;
1303
+ }
1304
+
1305
+ function resolveRelevantElement(node) {
1306
+ if (!node) return null;
1307
+ if (node instanceof Element) return node;
1308
+ if (typeof ShadowRoot !== 'undefined' && node instanceof ShadowRoot) {
1309
+ return node.host instanceof Element ? node.host : null;
1310
+ }
1311
+ var parentElement = node.parentElement;
1312
+ return parentElement instanceof Element ? parentElement : null;
1313
+ }
1314
+
1315
+ function isNodeVisiblyRelevant(node) {
1316
+ var element = resolveRelevantElement(node);
1317
+ if (!element) return false;
1318
+ return isElementVisiblyIntersectingViewport(element);
1319
+ }
1320
+
1321
+ function hasRelevantMutation(records) {
1322
+ for (var i = 0; i < records.length; i++) {
1323
+ var record = records[i];
1324
+ if (isNodeVisiblyRelevant(record.target)) return true;
1325
+
1326
+ var addedNodes = record.addedNodes;
1327
+ for (var j = 0; j < addedNodes.length; j++) {
1328
+ if (isNodeVisiblyRelevant(addedNodes[j])) return true;
1329
+ }
1330
+
1331
+ var removedNodes = record.removedNodes;
1332
+ for (var k = 0; k < removedNodes.length; k++) {
1333
+ if (isNodeVisiblyRelevant(removedNodes[k])) return true;
1334
+ }
1335
+ }
1336
+
1337
+ return false;
1338
+ }
1339
+
1340
+ function scheduleCheck() {
1341
+ if (resolved) return;
1342
+ if (timer) clearTimeout(timer);
1343
+
1344
+ var remaining = deadline - Date.now();
1345
+ if (remaining <= 0) {
1346
+ done();
1347
+ return;
1348
+ }
1349
+
1350
+ var checkDelay = Math.min(120, Math.max(16, ${settleMs}));
1351
+ timer = setTimeout(checkNow, checkDelay);
1352
+ }
1353
+
1175
1354
  function observeMutations(target) {
1176
1355
  if (!target) return;
1177
- var observer = new MutationObserver(function() { settle(); });
1356
+ var observer = new MutationObserver(function(records) {
1357
+ if (!hasRelevantMutation(records)) return;
1358
+ lastRelevantMutationAt = Date.now();
1359
+ scheduleCheck();
1360
+ });
1178
1361
  observer.observe(target, {
1179
1362
  childList: true,
1180
1363
  subtree: true,
@@ -1212,18 +1395,25 @@ function buildStabilityScript(timeout, settleMs) {
1212
1395
  var images = root.querySelectorAll('img');
1213
1396
  for (var i = 0; i < images.length; i++) {
1214
1397
  var img = images[i];
1215
- var rect = img.getBoundingClientRect();
1216
- var inViewport =
1217
- rect.bottom > 0 &&
1218
- rect.right > 0 &&
1219
- rect.top < window.innerHeight &&
1220
- rect.left < window.innerWidth;
1221
- if (inViewport && !img.complete) return false;
1398
+ if (!isElementVisiblyIntersectingViewport(img)) continue;
1399
+ if (!img.complete) return false;
1222
1400
  }
1223
1401
  return true;
1224
1402
  }
1225
1403
 
1226
- function hasRunningFiniteAnimations() {
1404
+ function getAnimationTarget(effect) {
1405
+ if (!effect) return null;
1406
+ var target = effect.target;
1407
+ if (target instanceof Element) return target;
1408
+
1409
+ if (target && target.element instanceof Element) {
1410
+ return target.element;
1411
+ }
1412
+
1413
+ return null;
1414
+ }
1415
+
1416
+ function hasRunningVisibleFiniteAnimations() {
1227
1417
  if (typeof document.getAnimations !== 'function') return false;
1228
1418
  var animations = document.getAnimations();
1229
1419
 
@@ -1237,6 +1427,9 @@ function buildStabilityScript(timeout, settleMs) {
1237
1427
  ? timing.endTime
1238
1428
  : Number.POSITIVE_INFINITY;
1239
1429
  if (Number.isFinite(endTime) && endTime > 0) {
1430
+ var target = getAnimationTarget(effect);
1431
+ if (!target) continue;
1432
+ if (!isElementVisiblyIntersectingViewport(target)) continue;
1240
1433
  return true;
1241
1434
  }
1242
1435
  }
@@ -1247,21 +1440,29 @@ function buildStabilityScript(timeout, settleMs) {
1247
1440
  function isVisuallyReady() {
1248
1441
  if (!fontsReady) return false;
1249
1442
  if (!checkViewportImages(document)) return false;
1250
- if (hasRunningFiniteAnimations()) return false;
1443
+ if (hasRunningVisibleFiniteAnimations()) return false;
1251
1444
  return true;
1252
1445
  }
1253
1446
 
1254
- function settle() {
1255
- if (Date.now() > deadline) { done(); return; }
1256
- if (timer) clearTimeout(timer);
1447
+ function checkNow() {
1448
+ if (Date.now() >= deadline) {
1449
+ done();
1450
+ return;
1451
+ }
1452
+
1257
1453
  observeOpenShadowRoots();
1258
- timer = setTimeout(function() {
1259
- if (isVisuallyReady()) {
1260
- done();
1261
- } else {
1262
- settle();
1263
- }
1264
- }, ${settleMs});
1454
+
1455
+ if (!isVisuallyReady()) {
1456
+ scheduleCheck();
1457
+ return;
1458
+ }
1459
+
1460
+ if (Date.now() - lastRelevantMutationAt >= ${settleMs}) {
1461
+ done();
1462
+ return;
1463
+ }
1464
+
1465
+ scheduleCheck();
1265
1466
  }
1266
1467
 
1267
1468
  observeMutations(document.documentElement);
@@ -1270,67 +1471,266 @@ function buildStabilityScript(timeout, settleMs) {
1270
1471
  if (fonts && fonts.ready && typeof fonts.ready.then === 'function') {
1271
1472
  fonts.ready.then(function() {
1272
1473
  fontsReady = true;
1273
- settle();
1474
+ scheduleCheck();
1475
+ }, function() {
1476
+ fontsReady = true;
1477
+ scheduleCheck();
1274
1478
  });
1275
1479
  }
1276
1480
 
1277
1481
  var safetyTimer = setTimeout(done, ${timeout});
1278
1482
 
1279
- settle();
1483
+ scheduleCheck();
1280
1484
  })`;
1281
1485
  }
1486
+ var StealthCdpRuntime = class _StealthCdpRuntime {
1487
+ constructor(session) {
1488
+ this.session = session;
1489
+ }
1490
+ contextsByFrame = /* @__PURE__ */ new Map();
1491
+ disposed = false;
1492
+ static async create(page) {
1493
+ let session;
1494
+ try {
1495
+ session = await page.context().newCDPSession(page);
1496
+ } catch (error) {
1497
+ throw new StealthWaitUnavailableError(error);
1498
+ }
1499
+ const runtime = new _StealthCdpRuntime(session);
1500
+ try {
1501
+ await runtime.initialize();
1502
+ return runtime;
1503
+ } catch (error) {
1504
+ await runtime.dispose();
1505
+ throw new StealthWaitUnavailableError(error);
1506
+ }
1507
+ }
1508
+ async dispose() {
1509
+ if (this.disposed) return;
1510
+ this.disposed = true;
1511
+ this.contextsByFrame.clear();
1512
+ await this.session.detach().catch(() => void 0);
1513
+ }
1514
+ async waitForMainFrameVisualStability(options) {
1515
+ const timeout = options.timeout ?? DEFAULT_TIMEOUT;
1516
+ const settleMs = options.settleMs ?? DEFAULT_SETTLE_MS;
1517
+ if (timeout <= 0) return;
1518
+ const frameRecords = await this.getFrameRecords();
1519
+ const mainFrame = frameRecords[0];
1520
+ if (!mainFrame) return;
1521
+ await this.waitForFrameVisualStability(mainFrame.frameId, timeout, settleMs);
1522
+ }
1523
+ async collectVisibleFrameIds() {
1524
+ const frameRecords = await this.getFrameRecords();
1525
+ if (frameRecords.length === 0) return [];
1526
+ const visibleFrameIds = [];
1527
+ for (const frameRecord of frameRecords) {
1528
+ if (!frameRecord.parentFrameId) {
1529
+ visibleFrameIds.push(frameRecord.frameId);
1530
+ continue;
1531
+ }
1532
+ try {
1533
+ const parentContextId = await this.ensureFrameContextId(
1534
+ frameRecord.parentFrameId
1535
+ );
1536
+ const visible = await this.isFrameOwnerVisible(
1537
+ frameRecord.frameId,
1538
+ parentContextId
1539
+ );
1540
+ if (visible) {
1541
+ visibleFrameIds.push(frameRecord.frameId);
1542
+ }
1543
+ } catch (error) {
1544
+ if (isIgnorableFrameError(error)) continue;
1545
+ throw error;
1546
+ }
1547
+ }
1548
+ return visibleFrameIds;
1549
+ }
1550
+ async waitForFrameVisualStability(frameId, timeout, settleMs) {
1551
+ if (timeout <= 0) return;
1552
+ const script = buildStabilityScript(timeout, settleMs);
1553
+ let contextId = await this.ensureFrameContextId(frameId);
1554
+ try {
1555
+ await this.evaluateWithGuard(contextId, script, timeout);
1556
+ } catch (error) {
1557
+ if (!isMissingExecutionContextError(error)) {
1558
+ throw error;
1559
+ }
1560
+ this.contextsByFrame.delete(frameId);
1561
+ contextId = await this.ensureFrameContextId(frameId);
1562
+ await this.evaluateWithGuard(contextId, script, timeout);
1563
+ }
1564
+ }
1565
+ async initialize() {
1566
+ await this.session.send("Page.enable");
1567
+ await this.session.send("Runtime.enable");
1568
+ await this.session.send("DOM.enable");
1569
+ }
1570
+ async getFrameRecords() {
1571
+ const treeResult = await this.session.send("Page.getFrameTree");
1572
+ const records = [];
1573
+ walkFrameTree(treeResult.frameTree, null, records);
1574
+ return records;
1575
+ }
1576
+ async ensureFrameContextId(frameId) {
1577
+ const cached = this.contextsByFrame.get(frameId);
1578
+ if (cached != null) {
1579
+ return cached;
1580
+ }
1581
+ const world = await this.session.send("Page.createIsolatedWorld", {
1582
+ frameId,
1583
+ worldName: STEALTH_WORLD_NAME
1584
+ });
1585
+ this.contextsByFrame.set(frameId, world.executionContextId);
1586
+ return world.executionContextId;
1587
+ }
1588
+ async evaluateWithGuard(contextId, script, timeout) {
1589
+ const evaluationPromise = this.evaluateScript(contextId, script);
1590
+ const settledPromise = evaluationPromise.then(
1591
+ () => ({ kind: "resolved" }),
1592
+ (error) => ({ kind: "rejected", error })
1593
+ );
1594
+ const timeoutPromise = sleep(
1595
+ timeout + FRAME_EVALUATE_GRACE_MS
1596
+ ).then(() => ({ kind: "timeout" }));
1597
+ const result = await Promise.race([
1598
+ settledPromise,
1599
+ timeoutPromise
1600
+ ]);
1601
+ if (result.kind === "rejected") {
1602
+ throw result.error;
1603
+ }
1604
+ }
1605
+ async evaluateScript(contextId, expression) {
1606
+ const result = await this.session.send("Runtime.evaluate", {
1607
+ contextId,
1608
+ expression,
1609
+ awaitPromise: true,
1610
+ returnByValue: true
1611
+ });
1612
+ if (result.exceptionDetails) {
1613
+ throw new Error(formatCdpException(result.exceptionDetails));
1614
+ }
1615
+ }
1616
+ async isFrameOwnerVisible(frameId, parentContextId) {
1617
+ const owner = await this.session.send("DOM.getFrameOwner", {
1618
+ frameId
1619
+ });
1620
+ const resolveParams = {
1621
+ executionContextId: parentContextId
1622
+ };
1623
+ if (typeof owner.backendNodeId === "number") {
1624
+ resolveParams.backendNodeId = owner.backendNodeId;
1625
+ } else if (typeof owner.nodeId === "number") {
1626
+ resolveParams.nodeId = owner.nodeId;
1627
+ } else {
1628
+ return false;
1629
+ }
1630
+ const resolved = await this.session.send(
1631
+ "DOM.resolveNode",
1632
+ resolveParams
1633
+ );
1634
+ const objectId = resolved.object?.objectId;
1635
+ if (!objectId) return false;
1636
+ try {
1637
+ const callResult = await this.session.send("Runtime.callFunctionOn", {
1638
+ objectId,
1639
+ functionDeclaration: FRAME_OWNER_VISIBILITY_FUNCTION,
1640
+ returnByValue: true
1641
+ });
1642
+ if (callResult.exceptionDetails) {
1643
+ throw new Error(formatCdpException(callResult.exceptionDetails));
1644
+ }
1645
+ return callResult.result.value === true;
1646
+ } finally {
1647
+ await this.releaseObject(objectId);
1648
+ }
1649
+ }
1650
+ async releaseObject(objectId) {
1651
+ await this.session.send("Runtime.releaseObject", {
1652
+ objectId
1653
+ }).catch(() => void 0);
1654
+ }
1655
+ };
1282
1656
  async function waitForVisualStability(page, options = {}) {
1283
- const timeout = options.timeout ?? DEFAULT_TIMEOUT;
1284
- const settleMs = options.settleMs ?? DEFAULT_SETTLE_MS;
1285
- await waitForFrameVisualStability(page.mainFrame(), {
1286
- timeout,
1287
- settleMs
1288
- });
1657
+ const runtime = await StealthCdpRuntime.create(page);
1658
+ try {
1659
+ await runtime.waitForMainFrameVisualStability(options);
1660
+ } finally {
1661
+ await runtime.dispose();
1662
+ }
1289
1663
  }
1290
1664
  async function waitForVisualStabilityAcrossFrames(page, options = {}) {
1291
1665
  const timeout = options.timeout ?? DEFAULT_TIMEOUT;
1292
1666
  const settleMs = options.settleMs ?? DEFAULT_SETTLE_MS;
1667
+ if (timeout <= 0) return;
1293
1668
  const deadline = Date.now() + timeout;
1294
- while (true) {
1295
- const remaining = Math.max(0, deadline - Date.now());
1296
- if (remaining === 0) return;
1297
- const frames = page.frames();
1298
- await Promise.all(
1299
- frames.map(async (frame) => {
1300
- try {
1301
- await waitForFrameVisualStability(frame, {
1302
- timeout: remaining,
1303
- settleMs
1304
- });
1305
- } catch (error) {
1306
- if (isIgnorableFrameError(error)) return;
1307
- throw error;
1308
- }
1309
- })
1310
- );
1311
- const currentFrames = page.frames();
1312
- if (sameFrames(frames, currentFrames)) {
1313
- return;
1669
+ const runtime = await StealthCdpRuntime.create(page);
1670
+ try {
1671
+ while (true) {
1672
+ const remaining = Math.max(0, deadline - Date.now());
1673
+ if (remaining === 0) return;
1674
+ const frameIds = await runtime.collectVisibleFrameIds();
1675
+ if (frameIds.length === 0) return;
1676
+ await Promise.all(
1677
+ frameIds.map(async (frameId) => {
1678
+ try {
1679
+ await runtime.waitForFrameVisualStability(
1680
+ frameId,
1681
+ remaining,
1682
+ settleMs
1683
+ );
1684
+ } catch (error) {
1685
+ if (isIgnorableFrameError(error)) return;
1686
+ throw error;
1687
+ }
1688
+ })
1689
+ );
1690
+ const currentFrameIds = await runtime.collectVisibleFrameIds();
1691
+ if (sameFrameIds(frameIds, currentFrameIds)) {
1692
+ return;
1693
+ }
1314
1694
  }
1695
+ } finally {
1696
+ await runtime.dispose();
1315
1697
  }
1316
1698
  }
1317
- async function waitForFrameVisualStability(frame, options) {
1318
- const timeout = options.timeout ?? DEFAULT_TIMEOUT;
1319
- const settleMs = options.settleMs ?? DEFAULT_SETTLE_MS;
1320
- if (timeout <= 0) return;
1321
- await frame.evaluate(buildStabilityScript(timeout, settleMs));
1699
+ function walkFrameTree(node, parentFrameId, records) {
1700
+ const frameId = node.frame?.id;
1701
+ if (!frameId) return;
1702
+ records.push({
1703
+ frameId,
1704
+ parentFrameId
1705
+ });
1706
+ for (const child of node.childFrames ?? []) {
1707
+ walkFrameTree(child, frameId, records);
1708
+ }
1322
1709
  }
1323
- function sameFrames(before, after) {
1710
+ function sameFrameIds(before, after) {
1324
1711
  if (before.length !== after.length) return false;
1325
- for (const frame of before) {
1326
- if (!after.includes(frame)) return false;
1712
+ for (const frameId of before) {
1713
+ if (!after.includes(frameId)) return false;
1327
1714
  }
1328
1715
  return true;
1329
1716
  }
1717
+ function formatCdpException(details) {
1718
+ return details.exception?.description || details.text || "CDP runtime evaluation failed.";
1719
+ }
1720
+ function isMissingExecutionContextError(error) {
1721
+ if (!(error instanceof Error)) return false;
1722
+ const message = error.message;
1723
+ return message.includes("Cannot find context with specified id") || message.includes("Cannot find execution context");
1724
+ }
1330
1725
  function isIgnorableFrameError(error) {
1331
1726
  if (!(error instanceof Error)) return false;
1332
1727
  const message = error.message;
1333
- return message.includes("Frame was detached") || message.includes("Execution context was destroyed") || message.includes("Target page, context or browser has been closed");
1728
+ 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");
1729
+ }
1730
+ function sleep(ms) {
1731
+ return new Promise((resolve) => {
1732
+ setTimeout(resolve, ms);
1733
+ });
1334
1734
  }
1335
1735
 
1336
1736
  // src/storage/local.ts
@@ -2231,6 +2631,66 @@ var OS_BOUNDARY_ATTR = "data-os-boundary";
2231
2631
  var OS_UNAVAILABLE_ATTR = "data-os-unavailable";
2232
2632
  var OS_IFRAME_BOUNDARY_TAG = "os-iframe-root";
2233
2633
  var OS_SHADOW_BOUNDARY_TAG = "os-shadow-root";
2634
+ function decodeSerializedNodeTableEntry(nodeTable, rawIndex, label) {
2635
+ if (typeof rawIndex !== "number" || !Number.isInteger(rawIndex) || rawIndex < 0 || rawIndex >= nodeTable.length) {
2636
+ throw new Error(
2637
+ `Invalid serialized path node index at "${label}": expected a valid table index.`
2638
+ );
2639
+ }
2640
+ const node = nodeTable[rawIndex];
2641
+ if (!node || typeof node !== "object") {
2642
+ throw new Error(
2643
+ `Invalid serialized path node at "${label}": table entry is missing.`
2644
+ );
2645
+ }
2646
+ return node;
2647
+ }
2648
+ function decodeSerializedDomPath(nodeTable, rawPath, label) {
2649
+ if (!Array.isArray(rawPath)) {
2650
+ throw new Error(
2651
+ `Invalid serialized path at "${label}": expected an array of node indexes.`
2652
+ );
2653
+ }
2654
+ return rawPath.map(
2655
+ (value, index) => decodeSerializedNodeTableEntry(nodeTable, value, `${label}[${index}]`)
2656
+ );
2657
+ }
2658
+ function decodeSerializedElementPath(nodeTable, rawPath, label) {
2659
+ if (!rawPath || typeof rawPath !== "object") {
2660
+ throw new Error(
2661
+ `Invalid serialized element path at "${label}": expected an object.`
2662
+ );
2663
+ }
2664
+ if (rawPath.context !== void 0 && !Array.isArray(rawPath.context)) {
2665
+ throw new Error(
2666
+ `Invalid serialized context at "${label}.context": expected an array.`
2667
+ );
2668
+ }
2669
+ const contextRaw = Array.isArray(rawPath.context) ? rawPath.context : [];
2670
+ const context = contextRaw.map((hop, hopIndex) => {
2671
+ if (!hop || typeof hop !== "object" || hop.kind !== "shadow") {
2672
+ throw new Error(
2673
+ `Invalid serialized context hop at "${label}.context[${hopIndex}]": expected a shadow hop.`
2674
+ );
2675
+ }
2676
+ return {
2677
+ kind: "shadow",
2678
+ host: decodeSerializedDomPath(
2679
+ nodeTable,
2680
+ hop.host,
2681
+ `${label}.context[${hopIndex}].host`
2682
+ )
2683
+ };
2684
+ });
2685
+ return {
2686
+ context,
2687
+ nodes: decodeSerializedDomPath(
2688
+ nodeTable,
2689
+ rawPath.nodes,
2690
+ `${label}.nodes`
2691
+ )
2692
+ };
2693
+ }
2234
2694
  async function serializePageHTML(page, _options = {}) {
2235
2695
  return serializeFrameRecursive(page.mainFrame(), [], "f0");
2236
2696
  }
@@ -2287,6 +2747,8 @@ async function serializeFrameRecursive(frame, baseContext, frameKey) {
2287
2747
  (Array.isArray(deferredMatchAttrKeys) ? deferredMatchAttrKeys : []).map((key) => String(key))
2288
2748
  );
2289
2749
  let counter = 1;
2750
+ const nodeTable = [];
2751
+ const nodeTableIndexByKey = /* @__PURE__ */ new Map();
2290
2752
  const entries = [];
2291
2753
  const helpers = {
2292
2754
  nextToken() {
@@ -2488,6 +2950,47 @@ async function serializeFrameRecursive(frame, baseContext, frameKey) {
2488
2950
  nodes: target
2489
2951
  };
2490
2952
  },
2953
+ buildPathNodeKey(node) {
2954
+ const attrs = Object.entries(node.attrs || {}).sort(
2955
+ ([a], [b]) => a.localeCompare(b)
2956
+ );
2957
+ const match = (node.match || []).map(
2958
+ (clause) => clause.kind === "attr" ? [
2959
+ "attr",
2960
+ clause.key,
2961
+ clause.op || "exact",
2962
+ clause.value ?? null
2963
+ ] : ["position", clause.axis]
2964
+ );
2965
+ return JSON.stringify([
2966
+ node.tag,
2967
+ node.position.nthChild,
2968
+ node.position.nthOfType,
2969
+ attrs,
2970
+ match
2971
+ ]);
2972
+ },
2973
+ internPathNode(node) {
2974
+ const key = helpers.buildPathNodeKey(node);
2975
+ const existing = nodeTableIndexByKey.get(key);
2976
+ if (existing != null) return existing;
2977
+ const index = nodeTable.length;
2978
+ nodeTable.push(node);
2979
+ nodeTableIndexByKey.set(key, index);
2980
+ return index;
2981
+ },
2982
+ packDomPath(path5) {
2983
+ return path5.map((node) => helpers.internPathNode(node));
2984
+ },
2985
+ packElementPath(path5) {
2986
+ return {
2987
+ context: (path5.context || []).map((hop) => ({
2988
+ kind: "shadow",
2989
+ host: helpers.packDomPath(hop.host)
2990
+ })),
2991
+ nodes: helpers.packDomPath(path5.nodes)
2992
+ };
2993
+ },
2491
2994
  ensureNodeId(el) {
2492
2995
  const next = `${frameKey2}_${counter++}`;
2493
2996
  el.setAttribute(nodeAttr, next);
@@ -2517,9 +3020,12 @@ async function serializeFrameRecursive(frame, baseContext, frameKey) {
2517
3020
  serializeElement(el) {
2518
3021
  const nodeId = helpers.ensureNodeId(el);
2519
3022
  const instanceToken = helpers.setInstanceToken(el);
3023
+ const packedPath = helpers.packElementPath(
3024
+ helpers.buildElementPath(el)
3025
+ );
2520
3026
  entries.push({
2521
3027
  nodeId,
2522
- path: helpers.buildElementPath(el),
3028
+ path: packedPath,
2523
3029
  instanceToken
2524
3030
  });
2525
3031
  const tag = el.tagName.toLowerCase();
@@ -2547,11 +3053,12 @@ async function serializeFrameRecursive(frame, baseContext, frameKey) {
2547
3053
  win[frameTokenKey] = frameToken;
2548
3054
  const root = document.documentElement;
2549
3055
  if (!root) {
2550
- return { html: "", frameToken, entries };
3056
+ return { html: "", frameToken, nodeTable, entries };
2551
3057
  }
2552
3058
  return {
2553
3059
  html: helpers.serializeElement(root),
2554
3060
  frameToken,
3061
+ nodeTable,
2555
3062
  entries
2556
3063
  };
2557
3064
  },
@@ -2569,13 +3076,18 @@ async function serializeFrameRecursive(frame, baseContext, frameKey) {
2569
3076
  );
2570
3077
  const nodePaths = /* @__PURE__ */ new Map();
2571
3078
  const nodeMeta = /* @__PURE__ */ new Map();
2572
- for (const entry of frameSnapshot.entries) {
3079
+ for (const [index, entry] of frameSnapshot.entries.entries()) {
3080
+ const path5 = decodeSerializedElementPath(
3081
+ frameSnapshot.nodeTable,
3082
+ entry.path,
3083
+ `entries[${index}].path`
3084
+ );
2573
3085
  nodePaths.set(entry.nodeId, {
2574
3086
  context: [
2575
3087
  ...baseContext,
2576
- ...entry.path.context || []
3088
+ ...path5.context || []
2577
3089
  ],
2578
- nodes: entry.path.nodes
3090
+ nodes: path5.nodes
2579
3091
  });
2580
3092
  nodeMeta.set(entry.nodeId, {
2581
3093
  frameToken: frameSnapshot.frameToken,
@@ -4969,7 +5481,7 @@ async function performInput(page, path5, options) {
4969
5481
  await resolved.element.type(options.text);
4970
5482
  }
4971
5483
  if (options.pressEnter) {
4972
- await resolved.element.press("Enter");
5484
+ await resolved.element.press("Enter", { noWaitAfter: true });
4973
5485
  }
4974
5486
  return {
4975
5487
  ok: true,
@@ -5645,7 +6157,26 @@ var ACTION_WAIT_PROFILES = {
5645
6157
  type: ROBUST_PROFILE
5646
6158
  };
5647
6159
  var NETWORK_POLL_MS = 50;
5648
- var IGNORED_RESOURCE_TYPES = /* @__PURE__ */ new Set(["websocket", "eventsource"]);
6160
+ var NETWORK_RELAX_AFTER_MS = 1800;
6161
+ var RELAXED_ALLOWED_PENDING = 2;
6162
+ var HEAVY_VISUAL_REQUEST_WINDOW_MS = 5e3;
6163
+ var TRACKED_RESOURCE_TYPES = /* @__PURE__ */ new Set([
6164
+ "document",
6165
+ "fetch",
6166
+ "xhr",
6167
+ "stylesheet",
6168
+ "image",
6169
+ "font",
6170
+ "media"
6171
+ ]);
6172
+ var HEAVY_RESOURCE_TYPES = /* @__PURE__ */ new Set(["document", "fetch", "xhr"]);
6173
+ var HEAVY_VISUAL_RESOURCE_TYPES = /* @__PURE__ */ new Set([
6174
+ "stylesheet",
6175
+ "image",
6176
+ "font",
6177
+ "media"
6178
+ ]);
6179
+ var IGNORED_RESOURCE_TYPES = /* @__PURE__ */ new Set(["websocket", "eventsource", "manifest"]);
5649
6180
  var NOOP_SESSION = {
5650
6181
  async wait() {
5651
6182
  },
@@ -5655,7 +6186,7 @@ var NOOP_SESSION = {
5655
6186
  function createPostActionWaitSession(page, action, override) {
5656
6187
  const profile = resolveActionWaitProfile(action, override);
5657
6188
  if (!profile.enabled) return NOOP_SESSION;
5658
- const tracker = profile.includeNetwork ? new ScopedNetworkTracker(page) : null;
6189
+ const tracker = profile.includeNetwork ? new AdaptiveNetworkTracker(page) : null;
5659
6190
  tracker?.start();
5660
6191
  let settled = false;
5661
6192
  return {
@@ -5663,19 +6194,32 @@ function createPostActionWaitSession(page, action, override) {
5663
6194
  if (settled) return;
5664
6195
  settled = true;
5665
6196
  const deadline = Date.now() + profile.timeout;
5666
- const networkWait = tracker ? tracker.waitForQuiet({
5667
- deadline,
5668
- quietMs: profile.networkQuietMs
5669
- }) : Promise.resolve();
6197
+ const visualTimeout = profile.includeNetwork ? Math.min(
6198
+ profile.timeout,
6199
+ resolveNetworkBackedVisualTimeout(profile.settleMs)
6200
+ ) : profile.timeout;
5670
6201
  try {
5671
- await Promise.all([
5672
- waitForVisualStabilityAcrossFrames(page, {
5673
- timeout: profile.timeout,
6202
+ try {
6203
+ await waitForVisualStabilityAcrossFrames(page, {
6204
+ timeout: visualTimeout,
5674
6205
  settleMs: profile.settleMs
5675
- }),
5676
- networkWait
5677
- ]);
5678
- } catch {
6206
+ });
6207
+ } catch (error) {
6208
+ if (isStealthWaitUnavailableError(error)) {
6209
+ throw error;
6210
+ }
6211
+ } finally {
6212
+ tracker?.freezeCollection();
6213
+ }
6214
+ if (tracker) {
6215
+ try {
6216
+ await tracker.waitForQuiet({
6217
+ deadline,
6218
+ quietMs: profile.networkQuietMs
6219
+ });
6220
+ } catch {
6221
+ }
6222
+ }
5679
6223
  } finally {
5680
6224
  tracker?.stop();
5681
6225
  }
@@ -5715,53 +6259,70 @@ function normalizeMs(value, fallback) {
5715
6259
  }
5716
6260
  return Math.max(0, Math.floor(value));
5717
6261
  }
5718
- var ScopedNetworkTracker = class {
6262
+ function resolveNetworkBackedVisualTimeout(settleMs) {
6263
+ const derived = settleMs * 3 + 300;
6264
+ return Math.max(1200, Math.min(2500, derived));
6265
+ }
6266
+ var AdaptiveNetworkTracker = class {
5719
6267
  constructor(page) {
5720
6268
  this.page = page;
5721
6269
  }
5722
- pending = /* @__PURE__ */ new Set();
6270
+ pending = /* @__PURE__ */ new Map();
5723
6271
  started = false;
6272
+ collecting = false;
6273
+ startedAt = 0;
5724
6274
  idleSince = Date.now();
5725
6275
  start() {
5726
6276
  if (this.started) return;
5727
6277
  this.started = true;
6278
+ this.collecting = true;
6279
+ this.startedAt = Date.now();
6280
+ this.idleSince = this.startedAt;
5728
6281
  this.page.on("request", this.handleRequestStarted);
5729
6282
  this.page.on("requestfinished", this.handleRequestFinished);
5730
6283
  this.page.on("requestfailed", this.handleRequestFinished);
5731
6284
  }
6285
+ freezeCollection() {
6286
+ if (!this.started) return;
6287
+ this.collecting = false;
6288
+ }
5732
6289
  stop() {
5733
6290
  if (!this.started) return;
5734
6291
  this.started = false;
6292
+ this.collecting = false;
5735
6293
  this.page.off("request", this.handleRequestStarted);
5736
6294
  this.page.off("requestfinished", this.handleRequestFinished);
5737
6295
  this.page.off("requestfailed", this.handleRequestFinished);
5738
6296
  this.pending.clear();
6297
+ this.startedAt = 0;
5739
6298
  this.idleSince = Date.now();
5740
6299
  }
5741
6300
  async waitForQuiet(options) {
5742
6301
  const quietMs = Math.max(0, options.quietMs);
5743
6302
  if (quietMs === 0) return;
5744
6303
  while (Date.now() < options.deadline) {
5745
- if (this.pending.size === 0) {
6304
+ const now = Date.now();
6305
+ const allowedPending = this.resolveAllowedPending(now);
6306
+ if (this.pending.size <= allowedPending) {
5746
6307
  if (this.idleSince === 0) {
5747
- this.idleSince = Date.now();
6308
+ this.idleSince = now;
5748
6309
  }
5749
- const idleFor = Date.now() - this.idleSince;
6310
+ const idleFor = now - this.idleSince;
5750
6311
  if (idleFor >= quietMs) {
5751
6312
  return;
5752
6313
  }
5753
6314
  } else {
5754
6315
  this.idleSince = 0;
5755
6316
  }
5756
- const remaining = Math.max(1, options.deadline - Date.now());
5757
- await sleep(Math.min(NETWORK_POLL_MS, remaining));
6317
+ const remaining = Math.max(1, options.deadline - now);
6318
+ await sleep2(Math.min(NETWORK_POLL_MS, remaining));
5758
6319
  }
5759
6320
  }
5760
6321
  handleRequestStarted = (request) => {
5761
- if (!this.started) return;
5762
- const resourceType = request.resourceType();
5763
- if (IGNORED_RESOURCE_TYPES.has(resourceType)) return;
5764
- this.pending.add(request);
6322
+ if (!this.started || !this.collecting) return;
6323
+ const trackedRequest = this.classifyRequest(request);
6324
+ if (!trackedRequest) return;
6325
+ this.pending.set(request, trackedRequest);
5765
6326
  this.idleSince = 0;
5766
6327
  };
5767
6328
  handleRequestFinished = (request) => {
@@ -5771,8 +6332,35 @@ var ScopedNetworkTracker = class {
5771
6332
  this.idleSince = Date.now();
5772
6333
  }
5773
6334
  };
6335
+ classifyRequest(request) {
6336
+ const resourceType = request.resourceType().toLowerCase();
6337
+ if (IGNORED_RESOURCE_TYPES.has(resourceType)) return null;
6338
+ if (!TRACKED_RESOURCE_TYPES.has(resourceType)) return null;
6339
+ const frame = request.frame();
6340
+ if (!frame || frame !== this.page.mainFrame()) return null;
6341
+ return {
6342
+ resourceType,
6343
+ startedAt: Date.now()
6344
+ };
6345
+ }
6346
+ resolveAllowedPending(now) {
6347
+ const relaxed = now - this.startedAt >= NETWORK_RELAX_AFTER_MS ? RELAXED_ALLOWED_PENDING : 0;
6348
+ if (this.hasHeavyPending(now)) return 0;
6349
+ return relaxed;
6350
+ }
6351
+ hasHeavyPending(now) {
6352
+ for (const trackedRequest of this.pending.values()) {
6353
+ if (HEAVY_RESOURCE_TYPES.has(trackedRequest.resourceType)) {
6354
+ return true;
6355
+ }
6356
+ if (HEAVY_VISUAL_RESOURCE_TYPES.has(trackedRequest.resourceType) && now - trackedRequest.startedAt < HEAVY_VISUAL_REQUEST_WINDOW_MS) {
6357
+ return true;
6358
+ }
6359
+ }
6360
+ return false;
6361
+ }
5774
6362
  };
5775
- async function sleep(ms) {
6363
+ async function sleep2(ms) {
5776
6364
  await new Promise((resolve) => {
5777
6365
  setTimeout(resolve, ms);
5778
6366
  });
@@ -6890,36 +7478,39 @@ function clonePersistedExtractNode(node) {
6890
7478
  return JSON.parse(JSON.stringify(node));
6891
7479
  }
6892
7480
 
6893
- // src/remote/action-ws-client.ts
7481
+ // src/cloud/contracts.ts
7482
+ var cloudSessionContractVersion = "v3";
7483
+
7484
+ // src/cloud/action-ws-client.ts
6894
7485
  var import_ws2 = __toESM(require("ws"), 1);
6895
7486
 
6896
- // src/remote/errors.ts
6897
- var OpensteerRemoteError = class extends Error {
7487
+ // src/cloud/errors.ts
7488
+ var OpensteerCloudError = class extends Error {
6898
7489
  code;
6899
7490
  status;
6900
7491
  details;
6901
7492
  constructor(code, message, status, details) {
6902
7493
  super(message);
6903
- this.name = "OpensteerRemoteError";
7494
+ this.name = "OpensteerCloudError";
6904
7495
  this.code = code;
6905
7496
  this.status = status;
6906
7497
  this.details = details;
6907
7498
  }
6908
7499
  };
6909
- function remoteUnsupportedMethodError(method, message) {
6910
- return new OpensteerRemoteError(
6911
- "REMOTE_UNSUPPORTED_METHOD",
6912
- message || `${method} is not supported in remote mode.`
7500
+ function cloudUnsupportedMethodError(method, message) {
7501
+ return new OpensteerCloudError(
7502
+ "CLOUD_UNSUPPORTED_METHOD",
7503
+ message || `${method} is not supported in cloud mode.`
6913
7504
  );
6914
7505
  }
6915
- function remoteNotLaunchedError() {
6916
- return new OpensteerRemoteError(
6917
- "REMOTE_SESSION_NOT_FOUND",
6918
- "Remote session is not connected. Call launch() first."
7506
+ function cloudNotLaunchedError() {
7507
+ return new OpensteerCloudError(
7508
+ "CLOUD_SESSION_NOT_FOUND",
7509
+ "Cloud session is not connected. Call launch() first."
6919
7510
  );
6920
7511
  }
6921
7512
 
6922
- // src/remote/action-ws-client.ts
7513
+ // src/cloud/action-ws-client.ts
6923
7514
  var ActionWsClient = class _ActionWsClient {
6924
7515
  ws;
6925
7516
  sessionId;
@@ -6936,18 +7527,18 @@ var ActionWsClient = class _ActionWsClient {
6936
7527
  });
6937
7528
  ws.on("error", (error) => {
6938
7529
  this.rejectAll(
6939
- new OpensteerRemoteError(
6940
- "REMOTE_TRANSPORT_ERROR",
6941
- `Remote action websocket error: ${error.message}`
7530
+ new OpensteerCloudError(
7531
+ "CLOUD_TRANSPORT_ERROR",
7532
+ `Cloud action websocket error: ${error.message}`
6942
7533
  )
6943
7534
  );
6944
7535
  });
6945
7536
  ws.on("close", () => {
6946
7537
  this.closed = true;
6947
7538
  this.rejectAll(
6948
- new OpensteerRemoteError(
6949
- "REMOTE_SESSION_CLOSED",
6950
- "Remote action websocket closed."
7539
+ new OpensteerCloudError(
7540
+ "CLOUD_SESSION_CLOSED",
7541
+ "Cloud action websocket closed."
6951
7542
  )
6952
7543
  );
6953
7544
  });
@@ -6959,8 +7550,8 @@ var ActionWsClient = class _ActionWsClient {
6959
7550
  ws.once("open", () => resolve());
6960
7551
  ws.once("error", (error) => {
6961
7552
  reject(
6962
- new OpensteerRemoteError(
6963
- "REMOTE_TRANSPORT_ERROR",
7553
+ new OpensteerCloudError(
7554
+ "CLOUD_TRANSPORT_ERROR",
6964
7555
  `Failed to connect action websocket: ${error.message}`
6965
7556
  )
6966
7557
  );
@@ -6970,9 +7561,9 @@ var ActionWsClient = class _ActionWsClient {
6970
7561
  }
6971
7562
  async request(method, args) {
6972
7563
  if (this.closed || this.ws.readyState !== import_ws2.default.OPEN) {
6973
- throw new OpensteerRemoteError(
6974
- "REMOTE_SESSION_CLOSED",
6975
- "Remote action websocket is closed."
7564
+ throw new OpensteerCloudError(
7565
+ "CLOUD_SESSION_CLOSED",
7566
+ "Cloud action websocket is closed."
6976
7567
  );
6977
7568
  }
6978
7569
  const id = this.nextRequestId;
@@ -6991,8 +7582,8 @@ var ActionWsClient = class _ActionWsClient {
6991
7582
  this.ws.send(JSON.stringify(payload));
6992
7583
  } catch (error) {
6993
7584
  this.pending.delete(id);
6994
- const message = error instanceof Error ? error.message : "Failed to send remote action request.";
6995
- throw new OpensteerRemoteError("REMOTE_TRANSPORT_ERROR", message);
7585
+ const message = error instanceof Error ? error.message : "Failed to send cloud action request.";
7586
+ throw new OpensteerCloudError("CLOUD_TRANSPORT_ERROR", message);
6996
7587
  }
6997
7588
  return await resultPromise;
6998
7589
  }
@@ -7010,9 +7601,9 @@ var ActionWsClient = class _ActionWsClient {
7010
7601
  parsed = JSON.parse(rawDataToUtf8(raw));
7011
7602
  } catch {
7012
7603
  this.rejectAll(
7013
- new OpensteerRemoteError(
7014
- "REMOTE_TRANSPORT_ERROR",
7015
- "Invalid remote action response payload."
7604
+ new OpensteerCloudError(
7605
+ "CLOUD_TRANSPORT_ERROR",
7606
+ "Invalid cloud action response payload."
7016
7607
  )
7017
7608
  );
7018
7609
  return;
@@ -7025,7 +7616,7 @@ var ActionWsClient = class _ActionWsClient {
7025
7616
  return;
7026
7617
  }
7027
7618
  pending.reject(
7028
- new OpensteerRemoteError(
7619
+ new OpensteerCloudError(
7029
7620
  parsed.code,
7030
7621
  parsed.error,
7031
7622
  void 0,
@@ -7053,7 +7644,7 @@ function withTokenQuery(wsUrl, token) {
7053
7644
  return url.toString();
7054
7645
  }
7055
7646
 
7056
- // src/remote/local-cache-sync.ts
7647
+ // src/cloud/local-cache-sync.ts
7057
7648
  var import_fs3 = __toESM(require("fs"), 1);
7058
7649
  var import_path5 = __toESM(require("path"), 1);
7059
7650
  function collectLocalSelectorCacheEntries(storage) {
@@ -7177,24 +7768,24 @@ function dedupeNewest(entries) {
7177
7768
  return [...byKey.values()];
7178
7769
  }
7179
7770
 
7180
- // src/remote/cdp-client.ts
7771
+ // src/cloud/cdp-client.ts
7181
7772
  var import_playwright2 = require("playwright");
7182
- var RemoteCdpClient = class {
7773
+ var CloudCdpClient = class {
7183
7774
  async connect(args) {
7184
7775
  const endpoint = withTokenQuery2(args.wsUrl, args.token);
7185
7776
  let browser;
7186
7777
  try {
7187
7778
  browser = await import_playwright2.chromium.connectOverCDP(endpoint);
7188
7779
  } catch (error) {
7189
- const message = error instanceof Error ? error.message : "Failed to connect to remote CDP endpoint.";
7190
- throw new OpensteerRemoteError("REMOTE_TRANSPORT_ERROR", message);
7780
+ const message = error instanceof Error ? error.message : "Failed to connect to cloud CDP endpoint.";
7781
+ throw new OpensteerCloudError("CLOUD_TRANSPORT_ERROR", message);
7191
7782
  }
7192
7783
  const context = browser.contexts()[0];
7193
7784
  if (!context) {
7194
7785
  await browser.close();
7195
- throw new OpensteerRemoteError(
7196
- "REMOTE_INTERNAL",
7197
- "Remote browser returned no context."
7786
+ throw new OpensteerCloudError(
7787
+ "CLOUD_INTERNAL",
7788
+ "Cloud browser returned no context."
7198
7789
  );
7199
7790
  }
7200
7791
  const page = context.pages()[0] || await context.newPage();
@@ -7207,34 +7798,46 @@ function withTokenQuery2(wsUrl, token) {
7207
7798
  return url.toString();
7208
7799
  }
7209
7800
 
7210
- // src/remote/session-client.ts
7801
+ // src/cloud/session-client.ts
7211
7802
  var CACHE_IMPORT_BATCH_SIZE = 200;
7212
- var RemoteSessionClient = class {
7803
+ var CloudSessionClient = class {
7213
7804
  baseUrl;
7214
7805
  key;
7215
- constructor(baseUrl, key) {
7806
+ authScheme;
7807
+ constructor(baseUrl, key, authScheme = "api-key") {
7216
7808
  this.baseUrl = normalizeBaseUrl(baseUrl);
7217
7809
  this.key = key;
7810
+ this.authScheme = authScheme;
7218
7811
  }
7219
7812
  async create(request) {
7220
7813
  const response = await fetch(`${this.baseUrl}/sessions`, {
7221
7814
  method: "POST",
7222
7815
  headers: {
7223
7816
  "content-type": "application/json",
7224
- "x-api-key": this.key
7817
+ ...this.authHeaders()
7225
7818
  },
7226
7819
  body: JSON.stringify(request)
7227
7820
  });
7228
7821
  if (!response.ok) {
7229
7822
  throw await parseHttpError(response);
7230
7823
  }
7231
- return await response.json();
7824
+ let body;
7825
+ try {
7826
+ body = await response.json();
7827
+ } catch {
7828
+ throw new OpensteerCloudError(
7829
+ "CLOUD_CONTRACT_MISMATCH",
7830
+ "Invalid cloud session create response: expected a JSON object.",
7831
+ response.status
7832
+ );
7833
+ }
7834
+ return parseCreateResponse(body, response.status);
7232
7835
  }
7233
7836
  async close(sessionId) {
7234
7837
  const response = await fetch(`${this.baseUrl}/sessions/${sessionId}`, {
7235
7838
  method: "DELETE",
7236
7839
  headers: {
7237
- "x-api-key": this.key
7840
+ ...this.authHeaders()
7238
7841
  }
7239
7842
  });
7240
7843
  if (response.status === 204) {
@@ -7264,7 +7867,7 @@ var RemoteSessionClient = class {
7264
7867
  method: "POST",
7265
7868
  headers: {
7266
7869
  "content-type": "application/json",
7267
- "x-api-key": this.key
7870
+ ...this.authHeaders()
7268
7871
  },
7269
7872
  body: JSON.stringify({ entries })
7270
7873
  });
@@ -7273,10 +7876,148 @@ var RemoteSessionClient = class {
7273
7876
  }
7274
7877
  return await response.json();
7275
7878
  }
7879
+ authHeaders() {
7880
+ if (this.authScheme === "bearer") {
7881
+ return {
7882
+ authorization: `Bearer ${this.key}`
7883
+ };
7884
+ }
7885
+ return {
7886
+ "x-api-key": this.key
7887
+ };
7888
+ }
7276
7889
  };
7277
7890
  function normalizeBaseUrl(baseUrl) {
7278
7891
  return baseUrl.replace(/\/+$/, "");
7279
7892
  }
7893
+ function parseCreateResponse(body, status) {
7894
+ const root = requireObject(
7895
+ body,
7896
+ "Invalid cloud session create response: expected a JSON object.",
7897
+ status
7898
+ );
7899
+ const sessionId = requireString(root, "sessionId", status);
7900
+ const actionWsUrl = requireString(root, "actionWsUrl", status);
7901
+ const cdpWsUrl = requireString(root, "cdpWsUrl", status);
7902
+ const actionToken = requireString(root, "actionToken", status);
7903
+ const cdpToken = requireString(root, "cdpToken", status);
7904
+ const cloudSessionUrl = requireString(root, "cloudSessionUrl", status);
7905
+ const cloudSessionRoot = requireObject(
7906
+ root.cloudSession,
7907
+ "Invalid cloud session create response: cloudSession must be an object.",
7908
+ status
7909
+ );
7910
+ const cloudSession = {
7911
+ sessionId: requireString(cloudSessionRoot, "sessionId", status, "cloudSession"),
7912
+ workspaceId: requireString(
7913
+ cloudSessionRoot,
7914
+ "workspaceId",
7915
+ status,
7916
+ "cloudSession"
7917
+ ),
7918
+ state: requireString(cloudSessionRoot, "state", status, "cloudSession"),
7919
+ createdAt: requireNumber(cloudSessionRoot, "createdAt", status, "cloudSession"),
7920
+ sourceType: requireSourceType(cloudSessionRoot, "sourceType", status, "cloudSession"),
7921
+ sourceRef: optionalString(cloudSessionRoot, "sourceRef", status, "cloudSession"),
7922
+ label: optionalString(cloudSessionRoot, "label", status, "cloudSession")
7923
+ };
7924
+ const expiresAt = optionalNumber(root, "expiresAt", status);
7925
+ return {
7926
+ sessionId,
7927
+ actionWsUrl,
7928
+ cdpWsUrl,
7929
+ actionToken,
7930
+ cdpToken,
7931
+ expiresAt,
7932
+ cloudSessionUrl,
7933
+ cloudSession
7934
+ };
7935
+ }
7936
+ function requireObject(value, message, status) {
7937
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
7938
+ throw new OpensteerCloudError("CLOUD_CONTRACT_MISMATCH", message, status);
7939
+ }
7940
+ return value;
7941
+ }
7942
+ function requireString(source, field, status, parent) {
7943
+ const value = source[field];
7944
+ if (typeof value !== "string" || !value.trim()) {
7945
+ throw new OpensteerCloudError(
7946
+ "CLOUD_CONTRACT_MISMATCH",
7947
+ `Invalid cloud session create response: ${formatFieldPath(
7948
+ field,
7949
+ parent
7950
+ )} must be a non-empty string.`,
7951
+ status
7952
+ );
7953
+ }
7954
+ return value;
7955
+ }
7956
+ function requireNumber(source, field, status, parent) {
7957
+ const value = source[field];
7958
+ if (typeof value !== "number" || !Number.isFinite(value)) {
7959
+ throw new OpensteerCloudError(
7960
+ "CLOUD_CONTRACT_MISMATCH",
7961
+ `Invalid cloud session create response: ${formatFieldPath(
7962
+ field,
7963
+ parent
7964
+ )} must be a finite number.`,
7965
+ status
7966
+ );
7967
+ }
7968
+ return value;
7969
+ }
7970
+ function optionalString(source, field, status, parent) {
7971
+ const value = source[field];
7972
+ if (value == null) {
7973
+ return void 0;
7974
+ }
7975
+ if (typeof value !== "string") {
7976
+ throw new OpensteerCloudError(
7977
+ "CLOUD_CONTRACT_MISMATCH",
7978
+ `Invalid cloud session create response: ${formatFieldPath(
7979
+ field,
7980
+ parent
7981
+ )} must be a string when present.`,
7982
+ status
7983
+ );
7984
+ }
7985
+ return value;
7986
+ }
7987
+ function optionalNumber(source, field, status, parent) {
7988
+ const value = source[field];
7989
+ if (value == null) {
7990
+ return void 0;
7991
+ }
7992
+ if (typeof value !== "number" || !Number.isFinite(value)) {
7993
+ throw new OpensteerCloudError(
7994
+ "CLOUD_CONTRACT_MISMATCH",
7995
+ `Invalid cloud session create response: ${formatFieldPath(
7996
+ field,
7997
+ parent
7998
+ )} must be a finite number when present.`,
7999
+ status
8000
+ );
8001
+ }
8002
+ return value;
8003
+ }
8004
+ function requireSourceType(source, field, status, parent) {
8005
+ const value = source[field];
8006
+ if (value === "agent-thread" || value === "agent-run" || value === "local-cloud" || value === "manual") {
8007
+ return value;
8008
+ }
8009
+ throw new OpensteerCloudError(
8010
+ "CLOUD_CONTRACT_MISMATCH",
8011
+ `Invalid cloud session create response: ${formatFieldPath(
8012
+ field,
8013
+ parent
8014
+ )} must be one of "agent-thread", "agent-run", "local-cloud", or "manual".`,
8015
+ status
8016
+ );
8017
+ }
8018
+ function formatFieldPath(field, parent) {
8019
+ return parent ? `"${parent}.${field}"` : `"${field}"`;
8020
+ }
7280
8021
  function zeroImportResponse() {
7281
8022
  return {
7282
8023
  imported: 0,
@@ -7300,33 +8041,46 @@ async function parseHttpError(response) {
7300
8041
  } catch {
7301
8042
  body = null;
7302
8043
  }
7303
- const code = typeof body?.code === "string" ? toRemoteErrorCode(body.code) : "REMOTE_TRANSPORT_ERROR";
7304
- const message = typeof body?.error === "string" ? body.error : `Remote request failed with status ${response.status}.`;
7305
- return new OpensteerRemoteError(code, message, response.status, body?.details);
8044
+ const code = typeof body?.code === "string" ? toCloudErrorCode(body.code) : "CLOUD_TRANSPORT_ERROR";
8045
+ const message = typeof body?.error === "string" ? body.error : `Cloud request failed with status ${response.status}.`;
8046
+ return new OpensteerCloudError(code, message, response.status, body?.details);
7306
8047
  }
7307
- function toRemoteErrorCode(code) {
7308
- 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") {
8048
+ function toCloudErrorCode(code) {
8049
+ 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") {
7309
8050
  return code;
7310
8051
  }
7311
- return "REMOTE_TRANSPORT_ERROR";
8052
+ return "CLOUD_TRANSPORT_ERROR";
7312
8053
  }
7313
8054
 
7314
- // src/remote/runtime.ts
7315
- var DEFAULT_REMOTE_BASE_URL = "https://remote.opensteer.com";
7316
- function createRemoteRuntimeState(key, baseUrl = resolveRemoteBaseUrl()) {
8055
+ // src/cloud/runtime.ts
8056
+ var DEFAULT_CLOUD_BASE_URL = "https://remote.opensteer.com";
8057
+ var DEFAULT_CLOUD_APP_URL = "https://opensteer.com";
8058
+ function createCloudRuntimeState(key, baseUrl = resolveCloudBaseUrl(), authScheme = "api-key", appUrl = resolveCloudAppUrl()) {
7317
8059
  return {
7318
- sessionClient: new RemoteSessionClient(baseUrl, key),
7319
- cdpClient: new RemoteCdpClient(),
8060
+ sessionClient: new CloudSessionClient(baseUrl, key, authScheme),
8061
+ cdpClient: new CloudCdpClient(),
8062
+ appUrl: normalizeCloudAppUrl(appUrl),
7320
8063
  actionClient: null,
7321
- sessionId: null
8064
+ sessionId: null,
8065
+ localRunId: null,
8066
+ cloudSessionUrl: null
7322
8067
  };
7323
8068
  }
7324
- function resolveRemoteBaseUrl() {
8069
+ function resolveCloudBaseUrl() {
7325
8070
  const value = process.env.OPENSTEER_BASE_URL?.trim();
7326
- if (!value) return DEFAULT_REMOTE_BASE_URL;
8071
+ if (!value) return DEFAULT_CLOUD_BASE_URL;
8072
+ return value.replace(/\/+$/, "");
8073
+ }
8074
+ function resolveCloudAppUrl() {
8075
+ const value = process.env.OPENSTEER_APP_URL?.trim();
8076
+ if (!value) return DEFAULT_CLOUD_APP_URL;
8077
+ return normalizeCloudAppUrl(value);
8078
+ }
8079
+ function normalizeCloudAppUrl(value) {
8080
+ if (!value) return null;
7327
8081
  return value.replace(/\/+$/, "");
7328
8082
  }
7329
- function readRemoteActionDescription(payload) {
8083
+ function readCloudActionDescription(payload) {
7330
8084
  const description = payload.description;
7331
8085
  if (typeof description !== "string") return void 0;
7332
8086
  const normalized = description.trim();
@@ -7334,7 +8088,7 @@ function readRemoteActionDescription(payload) {
7334
8088
  }
7335
8089
 
7336
8090
  // src/opensteer.ts
7337
- var REMOTE_INTERACTION_METHODS = /* @__PURE__ */ new Set([
8091
+ var CLOUD_INTERACTION_METHODS = /* @__PURE__ */ new Set([
7338
8092
  "click",
7339
8093
  "dblclick",
7340
8094
  "rightclick",
@@ -7351,7 +8105,7 @@ var Opensteer = class _Opensteer {
7351
8105
  namespace;
7352
8106
  storage;
7353
8107
  pool;
7354
- remote;
8108
+ cloud;
7355
8109
  browser = null;
7356
8110
  pageRef = null;
7357
8111
  contextRef = null;
@@ -7359,8 +8113,8 @@ var Opensteer = class _Opensteer {
7359
8113
  snapshotCache = null;
7360
8114
  constructor(config = {}) {
7361
8115
  const resolved = resolveConfig(config);
7362
- const modeSelection = resolveModeSelection({
7363
- mode: resolved.mode
8116
+ const cloudSelection = resolveCloudSelection({
8117
+ cloud: resolved.cloud
7364
8118
  });
7365
8119
  const model = resolved.model;
7366
8120
  this.config = resolved;
@@ -7370,20 +8124,22 @@ var Opensteer = class _Opensteer {
7370
8124
  this.namespace = resolveNamespace(resolved, rootDir);
7371
8125
  this.storage = new LocalSelectorStorage(rootDir, this.namespace);
7372
8126
  this.pool = new BrowserPool(resolved.browser || {});
7373
- if (modeSelection.mode === "remote") {
7374
- const remoteConfig = resolved.remote && typeof resolved.remote === "object" ? resolved.remote : void 0;
7375
- const apiKey = remoteConfig?.apiKey?.trim();
8127
+ if (cloudSelection.cloud) {
8128
+ const cloudConfig = resolved.cloud && typeof resolved.cloud === "object" ? resolved.cloud : void 0;
8129
+ const apiKey = cloudConfig?.apiKey?.trim();
7376
8130
  if (!apiKey) {
7377
8131
  throw new Error(
7378
- "Remote mode requires a non-empty API key via remote.apiKey or OPENSTEER_API_KEY."
8132
+ "Cloud mode requires a non-empty API key via cloud.apiKey or OPENSTEER_API_KEY."
7379
8133
  );
7380
8134
  }
7381
- this.remote = createRemoteRuntimeState(
8135
+ this.cloud = createCloudRuntimeState(
7382
8136
  apiKey,
7383
- remoteConfig?.baseUrl
8137
+ cloudConfig?.baseUrl,
8138
+ cloudConfig?.authScheme,
8139
+ cloudConfig?.appUrl
7384
8140
  );
7385
8141
  } else {
7386
- this.remote = null;
8142
+ this.cloud = null;
7387
8143
  }
7388
8144
  }
7389
8145
  createLazyResolveCallback(model) {
@@ -7421,32 +8177,32 @@ var Opensteer = class _Opensteer {
7421
8177
  };
7422
8178
  return extract;
7423
8179
  }
7424
- async invokeRemoteActionAndResetCache(method, args) {
7425
- const result = await this.invokeRemoteAction(method, args);
8180
+ async invokeCloudActionAndResetCache(method, args) {
8181
+ const result = await this.invokeCloudAction(method, args);
7426
8182
  this.snapshotCache = null;
7427
8183
  return result;
7428
8184
  }
7429
- async invokeRemoteAction(method, args) {
7430
- const actionClient = this.remote?.actionClient;
7431
- const sessionId = this.remote?.sessionId;
8185
+ async invokeCloudAction(method, args) {
8186
+ const actionClient = this.cloud?.actionClient;
8187
+ const sessionId = this.cloud?.sessionId;
7432
8188
  if (!actionClient || !sessionId) {
7433
- throw remoteNotLaunchedError();
8189
+ throw cloudNotLaunchedError();
7434
8190
  }
7435
8191
  const payload = args && typeof args === "object" ? args : {};
7436
8192
  try {
7437
8193
  return await actionClient.request(method, payload);
7438
8194
  } catch (err) {
7439
- if (err instanceof OpensteerRemoteError && err.code === "REMOTE_ACTION_FAILED" && REMOTE_INTERACTION_METHODS.has(method)) {
8195
+ if (err instanceof OpensteerCloudError && err.code === "CLOUD_ACTION_FAILED" && CLOUD_INTERACTION_METHODS.has(method)) {
7440
8196
  const detailsRecord = err.details && typeof err.details === "object" ? err.details : null;
7441
- const remoteFailure = normalizeActionFailure(
8197
+ const cloudFailure = normalizeActionFailure(
7442
8198
  detailsRecord?.actionFailure
7443
8199
  );
7444
- const failure = remoteFailure || classifyActionFailure({
8200
+ const failure = cloudFailure || classifyActionFailure({
7445
8201
  action: method,
7446
8202
  error: err,
7447
8203
  fallbackMessage: defaultActionFailureMessage(method)
7448
8204
  });
7449
- const description = readRemoteActionDescription(payload);
8205
+ const description = readCloudActionDescription(payload);
7450
8206
  throw this.buildActionError(
7451
8207
  method,
7452
8208
  description,
@@ -7487,8 +8243,36 @@ var Opensteer = class _Opensteer {
7487
8243
  }
7488
8244
  return this.contextRef;
7489
8245
  }
7490
- getRemoteSessionId() {
7491
- return this.remote?.sessionId ?? null;
8246
+ getCloudSessionId() {
8247
+ return this.cloud?.sessionId ?? null;
8248
+ }
8249
+ getCloudSessionUrl() {
8250
+ return this.cloud?.cloudSessionUrl ?? null;
8251
+ }
8252
+ announceCloudSession(args) {
8253
+ if (!this.shouldAnnounceCloudSession()) {
8254
+ return;
8255
+ }
8256
+ const fields = [
8257
+ `sessionId=${args.sessionId}`,
8258
+ `workspaceId=${args.workspaceId}`
8259
+ ];
8260
+ if (args.cloudSessionUrl) {
8261
+ fields.push(`url=${args.cloudSessionUrl}`);
8262
+ }
8263
+ process.stderr.write(`[opensteer] cloud session ready ${fields.join(" ")}
8264
+ `);
8265
+ }
8266
+ shouldAnnounceCloudSession() {
8267
+ const cloudConfig = this.config.cloud && typeof this.config.cloud === "object" ? this.config.cloud : null;
8268
+ const announce = cloudConfig?.announce ?? "always";
8269
+ if (announce === "off") {
8270
+ return false;
8271
+ }
8272
+ if (announce === "tty") {
8273
+ return Boolean(process.stderr.isTTY);
8274
+ }
8275
+ return true;
7492
8276
  }
7493
8277
  async launch(options = {}) {
7494
8278
  if (this.pageRef && !this.ownsBrowser) {
@@ -7499,22 +8283,29 @@ var Opensteer = class _Opensteer {
7499
8283
  if (this.pageRef && this.ownsBrowser) {
7500
8284
  return;
7501
8285
  }
7502
- if (this.remote) {
8286
+ if (this.cloud) {
7503
8287
  let actionClient = null;
7504
8288
  let browser = null;
7505
8289
  let sessionId = null;
8290
+ let localRunId = null;
7506
8291
  try {
7507
8292
  try {
7508
- await this.syncLocalSelectorCacheToRemote();
8293
+ await this.syncLocalSelectorCacheToCloud();
7509
8294
  } catch (error) {
7510
8295
  if (this.config.debug) {
7511
8296
  const message = error instanceof Error ? error.message : String(error);
7512
8297
  console.warn(
7513
- `[opensteer] remote selector cache sync failed: ${message}`
8298
+ `[opensteer] cloud selector cache sync failed: ${message}`
7514
8299
  );
7515
8300
  }
7516
8301
  }
7517
- const session2 = await this.remote.sessionClient.create({
8302
+ localRunId = this.cloud.localRunId || buildLocalRunId(this.namespace);
8303
+ this.cloud.localRunId = localRunId;
8304
+ const session2 = await this.cloud.sessionClient.create({
8305
+ cloudSessionContractVersion,
8306
+ sourceType: "local-cloud",
8307
+ clientSessionHint: this.namespace,
8308
+ localRunId,
7518
8309
  name: this.namespace,
7519
8310
  model: this.config.model,
7520
8311
  launchContext: options.context || void 0
@@ -7525,7 +8316,7 @@ var Opensteer = class _Opensteer {
7525
8316
  token: session2.actionToken,
7526
8317
  sessionId: session2.sessionId
7527
8318
  });
7528
- const cdpConnection = await this.remote.cdpClient.connect({
8319
+ const cdpConnection = await this.cloud.cdpClient.connect({
7529
8320
  wsUrl: session2.cdpWsUrl,
7530
8321
  token: session2.cdpToken
7531
8322
  });
@@ -7535,8 +8326,17 @@ var Opensteer = class _Opensteer {
7535
8326
  this.pageRef = cdpConnection.page;
7536
8327
  this.ownsBrowser = true;
7537
8328
  this.snapshotCache = null;
7538
- this.remote.actionClient = actionClient;
7539
- this.remote.sessionId = sessionId;
8329
+ this.cloud.actionClient = actionClient;
8330
+ this.cloud.sessionId = sessionId;
8331
+ this.cloud.cloudSessionUrl = buildCloudSessionUrl(
8332
+ this.cloud.appUrl,
8333
+ session2.cloudSession.sessionId
8334
+ );
8335
+ this.announceCloudSession({
8336
+ sessionId: session2.sessionId,
8337
+ workspaceId: session2.cloudSession.workspaceId,
8338
+ cloudSessionUrl: this.cloud.cloudSessionUrl
8339
+ });
7540
8340
  return;
7541
8341
  } catch (error) {
7542
8342
  if (actionClient) {
@@ -7546,8 +8346,9 @@ var Opensteer = class _Opensteer {
7546
8346
  await browser.close().catch(() => void 0);
7547
8347
  }
7548
8348
  if (sessionId) {
7549
- await this.remote.sessionClient.close(sessionId).catch(() => void 0);
8349
+ await this.cloud.sessionClient.close(sessionId).catch(() => void 0);
7550
8350
  }
8351
+ this.cloud.cloudSessionUrl = null;
7551
8352
  throw error;
7552
8353
  }
7553
8354
  }
@@ -7565,13 +8366,13 @@ var Opensteer = class _Opensteer {
7565
8366
  }
7566
8367
  static from(page, config = {}) {
7567
8368
  const resolvedConfig = resolveConfig(config);
7568
- const modeSelection = resolveModeSelection({
7569
- mode: resolvedConfig.mode
8369
+ const cloudSelection = resolveCloudSelection({
8370
+ cloud: resolvedConfig.cloud
7570
8371
  });
7571
- if (modeSelection.mode === "remote") {
7572
- throw remoteUnsupportedMethodError(
8372
+ if (cloudSelection.cloud) {
8373
+ throw cloudUnsupportedMethodError(
7573
8374
  "Opensteer.from(page)",
7574
- "Opensteer.from(page) is not supported in remote mode."
8375
+ "Opensteer.from(page) is not supported in cloud mode."
7575
8376
  );
7576
8377
  }
7577
8378
  const instance = new _Opensteer(config);
@@ -7584,12 +8385,14 @@ var Opensteer = class _Opensteer {
7584
8385
  }
7585
8386
  async close() {
7586
8387
  this.snapshotCache = null;
7587
- if (this.remote) {
7588
- const actionClient = this.remote.actionClient;
7589
- const sessionId = this.remote.sessionId;
8388
+ if (this.cloud) {
8389
+ const actionClient = this.cloud.actionClient;
8390
+ const sessionId = this.cloud.sessionId;
7590
8391
  const browser = this.browser;
7591
- this.remote.actionClient = null;
7592
- this.remote.sessionId = null;
8392
+ this.cloud.actionClient = null;
8393
+ this.cloud.sessionId = null;
8394
+ this.cloud.localRunId = null;
8395
+ this.cloud.cloudSessionUrl = null;
7593
8396
  this.browser = null;
7594
8397
  this.pageRef = null;
7595
8398
  this.contextRef = null;
@@ -7601,7 +8404,7 @@ var Opensteer = class _Opensteer {
7601
8404
  await browser.close().catch(() => void 0);
7602
8405
  }
7603
8406
  if (sessionId) {
7604
- await this.remote.sessionClient.close(sessionId).catch(() => void 0);
8407
+ await this.cloud.sessionClient.close(sessionId).catch(() => void 0);
7605
8408
  }
7606
8409
  return;
7607
8410
  }
@@ -7613,17 +8416,17 @@ var Opensteer = class _Opensteer {
7613
8416
  this.contextRef = null;
7614
8417
  this.ownsBrowser = false;
7615
8418
  }
7616
- async syncLocalSelectorCacheToRemote() {
7617
- if (!this.remote) return;
8419
+ async syncLocalSelectorCacheToCloud() {
8420
+ if (!this.cloud) return;
7618
8421
  const entries = collectLocalSelectorCacheEntries(this.storage);
7619
8422
  if (!entries.length) return;
7620
- await this.remote.sessionClient.importSelectorCache({
8423
+ await this.cloud.sessionClient.importSelectorCache({
7621
8424
  entries
7622
8425
  });
7623
8426
  }
7624
8427
  async goto(url, options) {
7625
- if (this.remote) {
7626
- await this.invokeRemoteActionAndResetCache("goto", { url, options });
8428
+ if (this.cloud) {
8429
+ await this.invokeCloudActionAndResetCache("goto", { url, options });
7627
8430
  return;
7628
8431
  }
7629
8432
  const { waitUntil = "domcontentloaded", ...rest } = options ?? {};
@@ -7632,8 +8435,8 @@ var Opensteer = class _Opensteer {
7632
8435
  this.snapshotCache = null;
7633
8436
  }
7634
8437
  async snapshot(options = {}) {
7635
- if (this.remote) {
7636
- return await this.invokeRemoteActionAndResetCache("snapshot", {
8438
+ if (this.cloud) {
8439
+ return await this.invokeCloudActionAndResetCache("snapshot", {
7637
8440
  options
7638
8441
  });
7639
8442
  }
@@ -7642,8 +8445,8 @@ var Opensteer = class _Opensteer {
7642
8445
  return prepared.cleanedHtml;
7643
8446
  }
7644
8447
  async state() {
7645
- if (this.remote) {
7646
- return await this.invokeRemoteAction("state", {});
8448
+ if (this.cloud) {
8449
+ return await this.invokeCloudAction("state", {});
7647
8450
  }
7648
8451
  const html = await this.snapshot({ mode: "action" });
7649
8452
  return {
@@ -7653,8 +8456,8 @@ var Opensteer = class _Opensteer {
7653
8456
  };
7654
8457
  }
7655
8458
  async screenshot(options = {}) {
7656
- if (this.remote) {
7657
- const b64 = await this.invokeRemoteAction(
8459
+ if (this.cloud) {
8460
+ const b64 = await this.invokeCloudAction(
7658
8461
  "screenshot",
7659
8462
  options
7660
8463
  );
@@ -7668,8 +8471,8 @@ var Opensteer = class _Opensteer {
7668
8471
  });
7669
8472
  }
7670
8473
  async click(options) {
7671
- if (this.remote) {
7672
- return await this.invokeRemoteActionAndResetCache(
8474
+ if (this.cloud) {
8475
+ return await this.invokeCloudActionAndResetCache(
7673
8476
  "click",
7674
8477
  options
7675
8478
  );
@@ -7681,8 +8484,8 @@ var Opensteer = class _Opensteer {
7681
8484
  });
7682
8485
  }
7683
8486
  async dblclick(options) {
7684
- if (this.remote) {
7685
- return await this.invokeRemoteActionAndResetCache(
8487
+ if (this.cloud) {
8488
+ return await this.invokeCloudActionAndResetCache(
7686
8489
  "dblclick",
7687
8490
  options
7688
8491
  );
@@ -7694,8 +8497,8 @@ var Opensteer = class _Opensteer {
7694
8497
  });
7695
8498
  }
7696
8499
  async rightclick(options) {
7697
- if (this.remote) {
7698
- return await this.invokeRemoteActionAndResetCache(
8500
+ if (this.cloud) {
8501
+ return await this.invokeCloudActionAndResetCache(
7699
8502
  "rightclick",
7700
8503
  options
7701
8504
  );
@@ -7707,8 +8510,8 @@ var Opensteer = class _Opensteer {
7707
8510
  });
7708
8511
  }
7709
8512
  async hover(options) {
7710
- if (this.remote) {
7711
- return await this.invokeRemoteActionAndResetCache(
8513
+ if (this.cloud) {
8514
+ return await this.invokeCloudActionAndResetCache(
7712
8515
  "hover",
7713
8516
  options
7714
8517
  );
@@ -7806,8 +8609,8 @@ var Opensteer = class _Opensteer {
7806
8609
  );
7807
8610
  }
7808
8611
  async input(options) {
7809
- if (this.remote) {
7810
- return await this.invokeRemoteActionAndResetCache(
8612
+ if (this.cloud) {
8613
+ return await this.invokeCloudActionAndResetCache(
7811
8614
  "input",
7812
8615
  options
7813
8616
  );
@@ -7836,7 +8639,7 @@ var Opensteer = class _Opensteer {
7836
8639
  await handle.type(options.text);
7837
8640
  }
7838
8641
  if (options.pressEnter) {
7839
- await handle.press("Enter");
8642
+ await handle.press("Enter", { noWaitAfter: true });
7840
8643
  }
7841
8644
  });
7842
8645
  } catch (err) {
@@ -7909,8 +8712,8 @@ var Opensteer = class _Opensteer {
7909
8712
  );
7910
8713
  }
7911
8714
  async select(options) {
7912
- if (this.remote) {
7913
- return await this.invokeRemoteActionAndResetCache(
8715
+ if (this.cloud) {
8716
+ return await this.invokeCloudActionAndResetCache(
7914
8717
  "select",
7915
8718
  options
7916
8719
  );
@@ -8019,8 +8822,8 @@ var Opensteer = class _Opensteer {
8019
8822
  );
8020
8823
  }
8021
8824
  async scroll(options = {}) {
8022
- if (this.remote) {
8023
- return await this.invokeRemoteActionAndResetCache(
8825
+ if (this.cloud) {
8826
+ return await this.invokeCloudActionAndResetCache(
8024
8827
  "scroll",
8025
8828
  options
8026
8829
  );
@@ -8121,14 +8924,14 @@ var Opensteer = class _Opensteer {
8121
8924
  }
8122
8925
  // --- Tab Management ---
8123
8926
  async tabs() {
8124
- if (this.remote) {
8125
- return await this.invokeRemoteAction("tabs", {});
8927
+ if (this.cloud) {
8928
+ return await this.invokeCloudAction("tabs", {});
8126
8929
  }
8127
8930
  return listTabs(this.context, this.page);
8128
8931
  }
8129
8932
  async newTab(url) {
8130
- if (this.remote) {
8131
- return await this.invokeRemoteActionAndResetCache("newTab", {
8933
+ if (this.cloud) {
8934
+ return await this.invokeCloudActionAndResetCache("newTab", {
8132
8935
  url
8133
8936
  });
8134
8937
  }
@@ -8138,8 +8941,8 @@ var Opensteer = class _Opensteer {
8138
8941
  return info;
8139
8942
  }
8140
8943
  async switchTab(index) {
8141
- if (this.remote) {
8142
- await this.invokeRemoteActionAndResetCache("switchTab", { index });
8944
+ if (this.cloud) {
8945
+ await this.invokeCloudActionAndResetCache("switchTab", { index });
8143
8946
  return;
8144
8947
  }
8145
8948
  const page = await switchTab(this.context, index);
@@ -8147,8 +8950,8 @@ var Opensteer = class _Opensteer {
8147
8950
  this.snapshotCache = null;
8148
8951
  }
8149
8952
  async closeTab(index) {
8150
- if (this.remote) {
8151
- await this.invokeRemoteActionAndResetCache("closeTab", { index });
8953
+ if (this.cloud) {
8954
+ await this.invokeCloudActionAndResetCache("closeTab", { index });
8152
8955
  return;
8153
8956
  }
8154
8957
  const newPage = await closeTab(this.context, this.page, index);
@@ -8159,8 +8962,8 @@ var Opensteer = class _Opensteer {
8159
8962
  }
8160
8963
  // --- Cookie Management ---
8161
8964
  async getCookies(url) {
8162
- if (this.remote) {
8163
- return await this.invokeRemoteAction(
8965
+ if (this.cloud) {
8966
+ return await this.invokeCloudAction(
8164
8967
  "getCookies",
8165
8968
  { url }
8166
8969
  );
@@ -8168,41 +8971,41 @@ var Opensteer = class _Opensteer {
8168
8971
  return getCookies(this.context, url);
8169
8972
  }
8170
8973
  async setCookie(cookie) {
8171
- if (this.remote) {
8172
- await this.invokeRemoteAction("setCookie", cookie);
8974
+ if (this.cloud) {
8975
+ await this.invokeCloudAction("setCookie", cookie);
8173
8976
  return;
8174
8977
  }
8175
8978
  return setCookie(this.context, cookie);
8176
8979
  }
8177
8980
  async clearCookies() {
8178
- if (this.remote) {
8179
- await this.invokeRemoteAction("clearCookies", {});
8981
+ if (this.cloud) {
8982
+ await this.invokeCloudAction("clearCookies", {});
8180
8983
  return;
8181
8984
  }
8182
8985
  return clearCookies(this.context);
8183
8986
  }
8184
8987
  async exportCookies(filePath, url) {
8185
- if (this.remote) {
8186
- throw remoteUnsupportedMethodError(
8988
+ if (this.cloud) {
8989
+ throw cloudUnsupportedMethodError(
8187
8990
  "exportCookies",
8188
- "exportCookies() is not supported in remote mode because it depends on local filesystem paths."
8991
+ "exportCookies() is not supported in cloud mode because it depends on local filesystem paths."
8189
8992
  );
8190
8993
  }
8191
8994
  return exportCookies(this.context, filePath, url);
8192
8995
  }
8193
8996
  async importCookies(filePath) {
8194
- if (this.remote) {
8195
- throw remoteUnsupportedMethodError(
8997
+ if (this.cloud) {
8998
+ throw cloudUnsupportedMethodError(
8196
8999
  "importCookies",
8197
- "importCookies() is not supported in remote mode because it depends on local filesystem paths."
9000
+ "importCookies() is not supported in cloud mode because it depends on local filesystem paths."
8198
9001
  );
8199
9002
  }
8200
9003
  return importCookies(this.context, filePath);
8201
9004
  }
8202
9005
  // --- Keyboard Input ---
8203
9006
  async pressKey(key) {
8204
- if (this.remote) {
8205
- await this.invokeRemoteActionAndResetCache("pressKey", { key });
9007
+ if (this.cloud) {
9008
+ await this.invokeCloudActionAndResetCache("pressKey", { key });
8206
9009
  return;
8207
9010
  }
8208
9011
  await this.runWithPostActionWait("pressKey", void 0, async () => {
@@ -8211,8 +9014,8 @@ var Opensteer = class _Opensteer {
8211
9014
  this.snapshotCache = null;
8212
9015
  }
8213
9016
  async type(text) {
8214
- if (this.remote) {
8215
- await this.invokeRemoteActionAndResetCache("type", { text });
9017
+ if (this.cloud) {
9018
+ await this.invokeCloudActionAndResetCache("type", { text });
8216
9019
  return;
8217
9020
  }
8218
9021
  await this.runWithPostActionWait("type", void 0, async () => {
@@ -8222,8 +9025,8 @@ var Opensteer = class _Opensteer {
8222
9025
  }
8223
9026
  // --- Element Info ---
8224
9027
  async getElementText(options) {
8225
- if (this.remote) {
8226
- return await this.invokeRemoteAction("getElementText", options);
9028
+ if (this.cloud) {
9029
+ return await this.invokeCloudAction("getElementText", options);
8227
9030
  }
8228
9031
  return this.executeElementInfoAction(
8229
9032
  "getElementText",
@@ -8236,8 +9039,8 @@ var Opensteer = class _Opensteer {
8236
9039
  );
8237
9040
  }
8238
9041
  async getElementValue(options) {
8239
- if (this.remote) {
8240
- return await this.invokeRemoteAction(
9042
+ if (this.cloud) {
9043
+ return await this.invokeCloudAction(
8241
9044
  "getElementValue",
8242
9045
  options
8243
9046
  );
@@ -8252,8 +9055,8 @@ var Opensteer = class _Opensteer {
8252
9055
  );
8253
9056
  }
8254
9057
  async getElementAttributes(options) {
8255
- if (this.remote) {
8256
- return await this.invokeRemoteAction(
9058
+ if (this.cloud) {
9059
+ return await this.invokeCloudAction(
8257
9060
  "getElementAttributes",
8258
9061
  options
8259
9062
  );
@@ -8274,8 +9077,8 @@ var Opensteer = class _Opensteer {
8274
9077
  );
8275
9078
  }
8276
9079
  async getElementBoundingBox(options) {
8277
- if (this.remote) {
8278
- return await this.invokeRemoteAction(
9080
+ if (this.cloud) {
9081
+ return await this.invokeCloudAction(
8279
9082
  "getElementBoundingBox",
8280
9083
  options
8281
9084
  );
@@ -8290,14 +9093,14 @@ var Opensteer = class _Opensteer {
8290
9093
  );
8291
9094
  }
8292
9095
  async getHtml(selector) {
8293
- if (this.remote) {
8294
- return await this.invokeRemoteAction("getHtml", { selector });
9096
+ if (this.cloud) {
9097
+ return await this.invokeCloudAction("getHtml", { selector });
8295
9098
  }
8296
9099
  return getPageHtml(this.page, selector);
8297
9100
  }
8298
9101
  async getTitle() {
8299
- if (this.remote) {
8300
- return await this.invokeRemoteAction("getTitle", {});
9102
+ if (this.cloud) {
9103
+ return await this.invokeCloudAction("getTitle", {});
8301
9104
  }
8302
9105
  return getPageTitle(this.page);
8303
9106
  }
@@ -8335,10 +9138,10 @@ var Opensteer = class _Opensteer {
8335
9138
  }
8336
9139
  // --- File Upload ---
8337
9140
  async uploadFile(options) {
8338
- if (this.remote) {
8339
- throw remoteUnsupportedMethodError(
9141
+ if (this.cloud) {
9142
+ throw cloudUnsupportedMethodError(
8340
9143
  "uploadFile",
8341
- "uploadFile() is not supported in remote mode because file paths must be accessible on the remote server."
9144
+ "uploadFile() is not supported in cloud mode because file paths must be accessible on the cloud runtime."
8342
9145
  );
8343
9146
  }
8344
9147
  const storageKey = this.resolveStorageKey(options.description);
@@ -8440,15 +9243,15 @@ var Opensteer = class _Opensteer {
8440
9243
  }
8441
9244
  // --- Wait for Text ---
8442
9245
  async waitForText(text, options) {
8443
- if (this.remote) {
8444
- await this.invokeRemoteAction("waitForText", { text, options });
9246
+ if (this.cloud) {
9247
+ await this.invokeCloudAction("waitForText", { text, options });
8445
9248
  return;
8446
9249
  }
8447
9250
  await this.page.getByText(text).first().waitFor({ timeout: options?.timeout ?? 3e4 });
8448
9251
  }
8449
9252
  async extract(options) {
8450
- if (this.remote) {
8451
- return await this.invokeRemoteAction("extract", options);
9253
+ if (this.cloud) {
9254
+ return await this.invokeCloudAction("extract", options);
8452
9255
  }
8453
9256
  const storageKey = this.resolveStorageKey(options.description);
8454
9257
  const schemaHash = options.schema ? computeSchemaHash(options.schema) : null;
@@ -8500,8 +9303,8 @@ var Opensteer = class _Opensteer {
8500
9303
  return inflateDataPathObject(data);
8501
9304
  }
8502
9305
  async extractFromPlan(options) {
8503
- if (this.remote) {
8504
- return await this.invokeRemoteAction(
9306
+ if (this.cloud) {
9307
+ return await this.invokeCloudAction(
8505
9308
  "extractFromPlan",
8506
9309
  options
8507
9310
  );
@@ -8550,10 +9353,10 @@ var Opensteer = class _Opensteer {
8550
9353
  return this.storage;
8551
9354
  }
8552
9355
  clearCache() {
8553
- if (this.remote) {
9356
+ if (this.cloud) {
8554
9357
  this.snapshotCache = null;
8555
- if (!this.remote.actionClient) return;
8556
- void this.invokeRemoteAction("clearCache", {});
9358
+ if (!this.cloud.actionClient) return;
9359
+ void this.invokeCloudAction("clearCache", {});
8557
9360
  return;
8558
9361
  }
8559
9362
  this.storage.clearNamespace();
@@ -9581,6 +10384,16 @@ function getScrollDelta2(options) {
9581
10384
  return { x: 0, y: absoluteAmount };
9582
10385
  }
9583
10386
  }
10387
+ function buildLocalRunId(namespace) {
10388
+ const normalized = namespace.trim() || "default";
10389
+ return `${normalized}-${Date.now().toString(36)}-${(0, import_crypto2.randomUUID)().slice(0, 8)}`;
10390
+ }
10391
+ function buildCloudSessionUrl(appUrl, sessionId) {
10392
+ if (!appUrl) {
10393
+ return null;
10394
+ }
10395
+ return `${appUrl}/browser/${encodeURIComponent(sessionId)}`;
10396
+ }
9584
10397
 
9585
10398
  // src/ai/index.ts
9586
10399
  init_resolver();
@@ -9589,6 +10402,8 @@ init_model();
9589
10402
  // Annotate the CommonJS export names for ESM import in node:
9590
10403
  0 && (module.exports = {
9591
10404
  ActionWsClient,
10405
+ CloudCdpClient,
10406
+ CloudSessionClient,
9592
10407
  CounterResolutionError,
9593
10408
  ElementPathError,
9594
10409
  LocalSelectorStorage,
@@ -9602,9 +10417,7 @@ init_model();
9602
10417
  OS_UNAVAILABLE_ATTR,
9603
10418
  Opensteer,
9604
10419
  OpensteerActionError,
9605
- OpensteerRemoteError,
9606
- RemoteCdpClient,
9607
- RemoteSessionClient,
10420
+ OpensteerCloudError,
9608
10421
  buildElementPathFromHandle,
9609
10422
  buildElementPathFromSelector,
9610
10423
  buildPathSelectorHint,
@@ -9616,6 +10429,9 @@ init_model();
9616
10429
  clearCookies,
9617
10430
  cloneElementPath,
9618
10431
  closeTab,
10432
+ cloudNotLaunchedError,
10433
+ cloudSessionContractVersion,
10434
+ cloudUnsupportedMethodError,
9619
10435
  collectLocalSelectorCacheEntries,
9620
10436
  countArrayItemsWithPath,
9621
10437
  createEmptyRegistry,
@@ -9647,8 +10463,6 @@ init_model();
9647
10463
  performSelect,
9648
10464
  prepareSnapshot,
9649
10465
  pressKey,
9650
- remoteNotLaunchedError,
9651
- remoteUnsupportedMethodError,
9652
10466
  resolveCounterElement,
9653
10467
  resolveCountersBatch,
9654
10468
  resolveElementPath,