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.
@@ -752,7 +752,7 @@ var BrowserPool = class {
752
752
  const context = contexts[0];
753
753
  const pages = context.pages();
754
754
  const page = pages.length > 0 ? pages[0] : await context.newPage();
755
- return { browser, context, page, isRemote: true };
755
+ return { browser, context, page, isExternal: true };
756
756
  } catch (error) {
757
757
  if (browser) {
758
758
  await browser.close().catch(() => void 0);
@@ -787,7 +787,7 @@ var BrowserPool = class {
787
787
  context = await browser.newContext(options.context || {});
788
788
  page = await context.newPage();
789
789
  }
790
- return { browser, context, page, isRemote: false };
790
+ return { browser, context, page, isExternal: false };
791
791
  }
792
792
  async launchSandbox(options) {
793
793
  const browser = await import_playwright.chromium.launch({
@@ -798,7 +798,7 @@ var BrowserPool = class {
798
798
  const context = await browser.newContext(options.context || {});
799
799
  const page = await context.newPage();
800
800
  this.browser = browser;
801
- return { browser, context, page, isRemote: false };
801
+ return { browser, context, page, isExternal: false };
802
802
  }
803
803
  };
804
804
 
@@ -869,28 +869,27 @@ function assertNoLegacyAiConfig(source, config) {
869
869
  );
870
870
  }
871
871
  }
872
- function assertNoLegacyModeConfig(source, config) {
872
+ function assertNoLegacyRuntimeConfig(source, config) {
873
873
  if (!config || typeof config !== "object") return;
874
874
  const configRecord = config;
875
875
  if (hasOwn(configRecord, "runtime")) {
876
876
  throw new Error(
877
- `Legacy "runtime" config is no longer supported in ${source}. Use top-level "mode" instead.`
877
+ `Legacy "runtime" config is no longer supported in ${source}. Use top-level "cloud" instead.`
878
878
  );
879
879
  }
880
- if (hasOwn(configRecord, "apiKey")) {
880
+ if (hasOwn(configRecord, "mode")) {
881
881
  throw new Error(
882
- `Top-level "apiKey" config is not supported in ${source}. Use "remote.apiKey" instead.`
882
+ `Top-level "mode" config is no longer supported in ${source}. Use "cloud: true" to enable cloud mode.`
883
883
  );
884
884
  }
885
- const remoteValue = configRecord.remote;
886
- if (typeof remoteValue === "boolean") {
885
+ if (hasOwn(configRecord, "remote")) {
887
886
  throw new Error(
888
- `Boolean "remote" config is no longer supported in ${source}. Use "mode: \\"remote\\"" with "remote" options.`
887
+ `Top-level "remote" config is no longer supported in ${source}. Use "cloud" options instead.`
889
888
  );
890
889
  }
891
- if (remoteValue && typeof remoteValue === "object" && !Array.isArray(remoteValue) && hasOwn(remoteValue, "key")) {
890
+ if (hasOwn(configRecord, "apiKey")) {
892
891
  throw new Error(
893
- `Legacy "remote.key" config is no longer supported in ${source}. Use "remote.apiKey" instead.`
892
+ `Top-level "apiKey" config is not supported in ${source}. Use "cloud.apiKey" instead.`
894
893
  );
895
894
  }
896
895
  }
@@ -936,20 +935,52 @@ function parseNumber(value) {
936
935
  if (!Number.isFinite(parsed)) return void 0;
937
936
  return parsed;
938
937
  }
939
- function parseMode(value, source) {
938
+ function parseRuntimeMode(value, source) {
940
939
  if (value == null) return void 0;
941
940
  if (typeof value !== "string") {
942
941
  throw new Error(
943
- `Invalid ${source} value "${String(value)}". Use "local" or "remote".`
942
+ `Invalid ${source} value "${String(value)}". Use "local" or "cloud".`
944
943
  );
945
944
  }
946
945
  const normalized = value.trim().toLowerCase();
947
946
  if (!normalized) return void 0;
948
- if (normalized === "local" || normalized === "remote") {
947
+ if (normalized === "local" || normalized === "cloud") {
949
948
  return normalized;
950
949
  }
951
950
  throw new Error(
952
- `Invalid ${source} value "${value}". Use "local" or "remote".`
951
+ `Invalid ${source} value "${value}". Use "local" or "cloud".`
952
+ );
953
+ }
954
+ function parseAuthScheme(value, source) {
955
+ if (value == null) return void 0;
956
+ if (typeof value !== "string") {
957
+ throw new Error(
958
+ `Invalid ${source} value "${String(value)}". Use "api-key" or "bearer".`
959
+ );
960
+ }
961
+ const normalized = value.trim().toLowerCase();
962
+ if (!normalized) return void 0;
963
+ if (normalized === "api-key" || normalized === "bearer") {
964
+ return normalized;
965
+ }
966
+ throw new Error(
967
+ `Invalid ${source} value "${value}". Use "api-key" or "bearer".`
968
+ );
969
+ }
970
+ function parseCloudAnnounce(value, source) {
971
+ if (value == null) return void 0;
972
+ if (typeof value !== "string") {
973
+ throw new Error(
974
+ `Invalid ${source} value "${String(value)}". Use "always", "off", or "tty".`
975
+ );
976
+ }
977
+ const normalized = value.trim().toLowerCase();
978
+ if (!normalized) return void 0;
979
+ if (normalized === "always" || normalized === "off" || normalized === "tty") {
980
+ return normalized;
981
+ }
982
+ throw new Error(
983
+ `Invalid ${source} value "${value}". Use "always", "off", or "tty".`
953
984
  );
954
985
  }
955
986
  function resolveOpensteerApiKey() {
@@ -957,29 +988,46 @@ function resolveOpensteerApiKey() {
957
988
  if (!value) return void 0;
958
989
  return value;
959
990
  }
960
- function normalizeRemoteOptions(value) {
991
+ function resolveOpensteerAuthScheme() {
992
+ return parseAuthScheme(
993
+ process.env.OPENSTEER_AUTH_SCHEME,
994
+ "OPENSTEER_AUTH_SCHEME"
995
+ );
996
+ }
997
+ function normalizeCloudOptions(value) {
961
998
  if (!value || typeof value !== "object" || Array.isArray(value)) {
962
999
  return void 0;
963
1000
  }
964
1001
  return value;
965
1002
  }
966
- function resolveModeSelection(config) {
967
- const configMode = parseMode(config.mode, "mode");
968
- if (configMode) {
1003
+ function parseCloudEnabled(value, source) {
1004
+ if (value == null) return void 0;
1005
+ if (typeof value === "boolean") return value;
1006
+ if (typeof value === "object" && !Array.isArray(value)) return true;
1007
+ throw new Error(
1008
+ `Invalid ${source} value "${String(value)}". Use true, false, or a cloud options object.`
1009
+ );
1010
+ }
1011
+ function resolveCloudSelection(config) {
1012
+ const configCloud = parseCloudEnabled(config.cloud, "cloud");
1013
+ if (configCloud !== void 0) {
969
1014
  return {
970
- mode: configMode,
971
- source: "config.mode"
1015
+ cloud: configCloud,
1016
+ source: "config.cloud"
972
1017
  };
973
1018
  }
974
- const envMode = parseMode(process.env.OPENSTEER_MODE, "OPENSTEER_MODE");
1019
+ const envMode = parseRuntimeMode(
1020
+ process.env.OPENSTEER_MODE,
1021
+ "OPENSTEER_MODE"
1022
+ );
975
1023
  if (envMode) {
976
1024
  return {
977
- mode: envMode,
1025
+ cloud: envMode === "cloud",
978
1026
  source: "env.OPENSTEER_MODE"
979
1027
  };
980
1028
  }
981
1029
  return {
982
- mode: "local",
1030
+ cloud: false,
983
1031
  source: "default"
984
1032
  };
985
1033
  }
@@ -995,11 +1043,11 @@ function resolveConfig(input = {}) {
995
1043
  );
996
1044
  }
997
1045
  assertNoLegacyAiConfig("Opensteer constructor config", input);
998
- assertNoLegacyModeConfig("Opensteer constructor config", input);
1046
+ assertNoLegacyRuntimeConfig("Opensteer constructor config", input);
999
1047
  const rootDir = input.storage?.rootDir ?? DEFAULT_CONFIG.storage.rootDir ?? process.cwd();
1000
1048
  const fileConfig = loadConfigFile(rootDir);
1001
1049
  assertNoLegacyAiConfig(".opensteer/config.json", fileConfig);
1002
- assertNoLegacyModeConfig(".opensteer/config.json", fileConfig);
1050
+ assertNoLegacyRuntimeConfig(".opensteer/config.json", fileConfig);
1003
1051
  const envConfig = {
1004
1052
  browser: {
1005
1053
  headless: parseBool(process.env.OPENSTEER_HEADLESS),
@@ -1016,20 +1064,39 @@ function resolveConfig(input = {}) {
1016
1064
  const mergedWithEnv = mergeDeep(mergedWithFile, envConfig);
1017
1065
  const resolved = mergeDeep(mergedWithEnv, input);
1018
1066
  const envApiKey = resolveOpensteerApiKey();
1019
- const inputRemoteOptions = normalizeRemoteOptions(input.remote);
1020
- const inputHasRemoteApiKey = Boolean(
1021
- inputRemoteOptions && Object.prototype.hasOwnProperty.call(inputRemoteOptions, "apiKey")
1067
+ const envAuthScheme = resolveOpensteerAuthScheme();
1068
+ const envCloudAnnounce = parseCloudAnnounce(
1069
+ process.env.OPENSTEER_REMOTE_ANNOUNCE,
1070
+ "OPENSTEER_REMOTE_ANNOUNCE"
1071
+ );
1072
+ const inputCloudOptions = normalizeCloudOptions(input.cloud);
1073
+ const inputAuthScheme = parseAuthScheme(
1074
+ inputCloudOptions?.authScheme,
1075
+ "cloud.authScheme"
1076
+ );
1077
+ const inputCloudAnnounce = parseCloudAnnounce(
1078
+ inputCloudOptions?.announce,
1079
+ "cloud.announce"
1080
+ );
1081
+ const inputHasCloudApiKey = Boolean(
1082
+ inputCloudOptions && Object.prototype.hasOwnProperty.call(inputCloudOptions, "apiKey")
1022
1083
  );
1023
- const modeSelection = resolveModeSelection({
1024
- mode: resolved.mode
1084
+ const cloudSelection = resolveCloudSelection({
1085
+ cloud: resolved.cloud
1025
1086
  });
1026
- if (modeSelection.mode === "remote") {
1027
- const resolvedRemote = normalizeRemoteOptions(resolved.remote);
1028
- resolved.remote = resolvedRemote ?? {};
1087
+ if (cloudSelection.cloud) {
1088
+ const resolvedCloud = normalizeCloudOptions(resolved.cloud) ?? {};
1089
+ const authScheme = inputAuthScheme ?? envAuthScheme ?? parseAuthScheme(resolvedCloud.authScheme, "cloud.authScheme") ?? "api-key";
1090
+ const announce = inputCloudAnnounce ?? envCloudAnnounce ?? parseCloudAnnounce(resolvedCloud.announce, "cloud.announce") ?? "always";
1091
+ resolved.cloud = {
1092
+ ...resolvedCloud,
1093
+ authScheme,
1094
+ announce
1095
+ };
1029
1096
  }
1030
- if (envApiKey && modeSelection.mode === "remote" && !inputHasRemoteApiKey) {
1031
- resolved.remote = {
1032
- ...normalizeRemoteOptions(resolved.remote) ?? {},
1097
+ if (envApiKey && cloudSelection.cloud && !inputHasCloudApiKey) {
1098
+ resolved.cloud = {
1099
+ ...normalizeCloudOptions(resolved.cloud) ?? {},
1033
1100
  apiKey: envApiKey
1034
1101
  };
1035
1102
  }
@@ -1072,15 +1139,52 @@ function getCallerFilePath() {
1072
1139
  // src/navigation.ts
1073
1140
  var DEFAULT_TIMEOUT = 3e4;
1074
1141
  var DEFAULT_SETTLE_MS = 750;
1142
+ var FRAME_EVALUATE_GRACE_MS = 200;
1143
+ var STEALTH_WORLD_NAME = "__opensteer_wait__";
1144
+ var StealthWaitUnavailableError = class extends Error {
1145
+ constructor(cause) {
1146
+ super("Stealth visual wait requires Chromium CDP support.", { cause });
1147
+ this.name = "StealthWaitUnavailableError";
1148
+ }
1149
+ };
1150
+ function isStealthWaitUnavailableError(error) {
1151
+ return error instanceof StealthWaitUnavailableError;
1152
+ }
1153
+ var FRAME_OWNER_VISIBILITY_FUNCTION = `function() {
1154
+ if (!(this instanceof HTMLElement)) return false;
1155
+
1156
+ var rect = this.getBoundingClientRect();
1157
+ if (rect.width <= 0 || rect.height <= 0) return false;
1158
+ if (
1159
+ rect.bottom <= 0 ||
1160
+ rect.right <= 0 ||
1161
+ rect.top >= window.innerHeight ||
1162
+ rect.left >= window.innerWidth
1163
+ ) {
1164
+ return false;
1165
+ }
1166
+
1167
+ var style = window.getComputedStyle(this);
1168
+ if (
1169
+ style.display === 'none' ||
1170
+ style.visibility === 'hidden' ||
1171
+ Number(style.opacity) === 0
1172
+ ) {
1173
+ return false;
1174
+ }
1175
+
1176
+ return true;
1177
+ }`;
1075
1178
  function buildStabilityScript(timeout, settleMs) {
1076
1179
  return `new Promise(function(resolve) {
1077
1180
  var deadline = Date.now() + ${timeout};
1078
- var timer = null;
1079
1181
  var resolved = false;
1182
+ var timer = null;
1080
1183
  var observers = [];
1081
1184
  var observedShadowRoots = [];
1082
1185
  var fonts = document.fonts;
1083
1186
  var fontsReady = !fonts || fonts.status === 'loaded';
1187
+ var lastRelevantMutationAt = Date.now();
1084
1188
 
1085
1189
  function clearObservers() {
1086
1190
  for (var i = 0; i < observers.length; i++) {
@@ -1098,9 +1202,87 @@ function buildStabilityScript(timeout, settleMs) {
1098
1202
  resolve();
1099
1203
  }
1100
1204
 
1205
+ function isElementVisiblyIntersectingViewport(element) {
1206
+ if (!(element instanceof Element)) return false;
1207
+
1208
+ var rect = element.getBoundingClientRect();
1209
+ var inViewport =
1210
+ rect.width > 0 &&
1211
+ rect.height > 0 &&
1212
+ rect.bottom > 0 &&
1213
+ rect.right > 0 &&
1214
+ rect.top < window.innerHeight &&
1215
+ rect.left < window.innerWidth;
1216
+
1217
+ if (!inViewport) return false;
1218
+
1219
+ var style = window.getComputedStyle(element);
1220
+ if (style.visibility === 'hidden' || style.display === 'none') {
1221
+ return false;
1222
+ }
1223
+ if (Number(style.opacity) === 0) {
1224
+ return false;
1225
+ }
1226
+
1227
+ return true;
1228
+ }
1229
+
1230
+ function resolveRelevantElement(node) {
1231
+ if (!node) return null;
1232
+ if (node instanceof Element) return node;
1233
+ if (typeof ShadowRoot !== 'undefined' && node instanceof ShadowRoot) {
1234
+ return node.host instanceof Element ? node.host : null;
1235
+ }
1236
+ var parentElement = node.parentElement;
1237
+ return parentElement instanceof Element ? parentElement : null;
1238
+ }
1239
+
1240
+ function isNodeVisiblyRelevant(node) {
1241
+ var element = resolveRelevantElement(node);
1242
+ if (!element) return false;
1243
+ return isElementVisiblyIntersectingViewport(element);
1244
+ }
1245
+
1246
+ function hasRelevantMutation(records) {
1247
+ for (var i = 0; i < records.length; i++) {
1248
+ var record = records[i];
1249
+ if (isNodeVisiblyRelevant(record.target)) return true;
1250
+
1251
+ var addedNodes = record.addedNodes;
1252
+ for (var j = 0; j < addedNodes.length; j++) {
1253
+ if (isNodeVisiblyRelevant(addedNodes[j])) return true;
1254
+ }
1255
+
1256
+ var removedNodes = record.removedNodes;
1257
+ for (var k = 0; k < removedNodes.length; k++) {
1258
+ if (isNodeVisiblyRelevant(removedNodes[k])) return true;
1259
+ }
1260
+ }
1261
+
1262
+ return false;
1263
+ }
1264
+
1265
+ function scheduleCheck() {
1266
+ if (resolved) return;
1267
+ if (timer) clearTimeout(timer);
1268
+
1269
+ var remaining = deadline - Date.now();
1270
+ if (remaining <= 0) {
1271
+ done();
1272
+ return;
1273
+ }
1274
+
1275
+ var checkDelay = Math.min(120, Math.max(16, ${settleMs}));
1276
+ timer = setTimeout(checkNow, checkDelay);
1277
+ }
1278
+
1101
1279
  function observeMutations(target) {
1102
1280
  if (!target) return;
1103
- var observer = new MutationObserver(function() { settle(); });
1281
+ var observer = new MutationObserver(function(records) {
1282
+ if (!hasRelevantMutation(records)) return;
1283
+ lastRelevantMutationAt = Date.now();
1284
+ scheduleCheck();
1285
+ });
1104
1286
  observer.observe(target, {
1105
1287
  childList: true,
1106
1288
  subtree: true,
@@ -1138,18 +1320,25 @@ function buildStabilityScript(timeout, settleMs) {
1138
1320
  var images = root.querySelectorAll('img');
1139
1321
  for (var i = 0; i < images.length; i++) {
1140
1322
  var img = images[i];
1141
- var rect = img.getBoundingClientRect();
1142
- var inViewport =
1143
- rect.bottom > 0 &&
1144
- rect.right > 0 &&
1145
- rect.top < window.innerHeight &&
1146
- rect.left < window.innerWidth;
1147
- if (inViewport && !img.complete) return false;
1323
+ if (!isElementVisiblyIntersectingViewport(img)) continue;
1324
+ if (!img.complete) return false;
1148
1325
  }
1149
1326
  return true;
1150
1327
  }
1151
1328
 
1152
- function hasRunningFiniteAnimations() {
1329
+ function getAnimationTarget(effect) {
1330
+ if (!effect) return null;
1331
+ var target = effect.target;
1332
+ if (target instanceof Element) return target;
1333
+
1334
+ if (target && target.element instanceof Element) {
1335
+ return target.element;
1336
+ }
1337
+
1338
+ return null;
1339
+ }
1340
+
1341
+ function hasRunningVisibleFiniteAnimations() {
1153
1342
  if (typeof document.getAnimations !== 'function') return false;
1154
1343
  var animations = document.getAnimations();
1155
1344
 
@@ -1163,6 +1352,9 @@ function buildStabilityScript(timeout, settleMs) {
1163
1352
  ? timing.endTime
1164
1353
  : Number.POSITIVE_INFINITY;
1165
1354
  if (Number.isFinite(endTime) && endTime > 0) {
1355
+ var target = getAnimationTarget(effect);
1356
+ if (!target) continue;
1357
+ if (!isElementVisiblyIntersectingViewport(target)) continue;
1166
1358
  return true;
1167
1359
  }
1168
1360
  }
@@ -1173,21 +1365,29 @@ function buildStabilityScript(timeout, settleMs) {
1173
1365
  function isVisuallyReady() {
1174
1366
  if (!fontsReady) return false;
1175
1367
  if (!checkViewportImages(document)) return false;
1176
- if (hasRunningFiniteAnimations()) return false;
1368
+ if (hasRunningVisibleFiniteAnimations()) return false;
1177
1369
  return true;
1178
1370
  }
1179
1371
 
1180
- function settle() {
1181
- if (Date.now() > deadline) { done(); return; }
1182
- if (timer) clearTimeout(timer);
1372
+ function checkNow() {
1373
+ if (Date.now() >= deadline) {
1374
+ done();
1375
+ return;
1376
+ }
1377
+
1183
1378
  observeOpenShadowRoots();
1184
- timer = setTimeout(function() {
1185
- if (isVisuallyReady()) {
1186
- done();
1187
- } else {
1188
- settle();
1189
- }
1190
- }, ${settleMs});
1379
+
1380
+ if (!isVisuallyReady()) {
1381
+ scheduleCheck();
1382
+ return;
1383
+ }
1384
+
1385
+ if (Date.now() - lastRelevantMutationAt >= ${settleMs}) {
1386
+ done();
1387
+ return;
1388
+ }
1389
+
1390
+ scheduleCheck();
1191
1391
  }
1192
1392
 
1193
1393
  observeMutations(document.documentElement);
@@ -1196,67 +1396,266 @@ function buildStabilityScript(timeout, settleMs) {
1196
1396
  if (fonts && fonts.ready && typeof fonts.ready.then === 'function') {
1197
1397
  fonts.ready.then(function() {
1198
1398
  fontsReady = true;
1199
- settle();
1399
+ scheduleCheck();
1400
+ }, function() {
1401
+ fontsReady = true;
1402
+ scheduleCheck();
1200
1403
  });
1201
1404
  }
1202
1405
 
1203
1406
  var safetyTimer = setTimeout(done, ${timeout});
1204
1407
 
1205
- settle();
1408
+ scheduleCheck();
1206
1409
  })`;
1207
1410
  }
1411
+ var StealthCdpRuntime = class _StealthCdpRuntime {
1412
+ constructor(session2) {
1413
+ this.session = session2;
1414
+ }
1415
+ contextsByFrame = /* @__PURE__ */ new Map();
1416
+ disposed = false;
1417
+ static async create(page) {
1418
+ let session2;
1419
+ try {
1420
+ session2 = await page.context().newCDPSession(page);
1421
+ } catch (error) {
1422
+ throw new StealthWaitUnavailableError(error);
1423
+ }
1424
+ const runtime = new _StealthCdpRuntime(session2);
1425
+ try {
1426
+ await runtime.initialize();
1427
+ return runtime;
1428
+ } catch (error) {
1429
+ await runtime.dispose();
1430
+ throw new StealthWaitUnavailableError(error);
1431
+ }
1432
+ }
1433
+ async dispose() {
1434
+ if (this.disposed) return;
1435
+ this.disposed = true;
1436
+ this.contextsByFrame.clear();
1437
+ await this.session.detach().catch(() => void 0);
1438
+ }
1439
+ async waitForMainFrameVisualStability(options) {
1440
+ const timeout = options.timeout ?? DEFAULT_TIMEOUT;
1441
+ const settleMs = options.settleMs ?? DEFAULT_SETTLE_MS;
1442
+ if (timeout <= 0) return;
1443
+ const frameRecords = await this.getFrameRecords();
1444
+ const mainFrame = frameRecords[0];
1445
+ if (!mainFrame) return;
1446
+ await this.waitForFrameVisualStability(mainFrame.frameId, timeout, settleMs);
1447
+ }
1448
+ async collectVisibleFrameIds() {
1449
+ const frameRecords = await this.getFrameRecords();
1450
+ if (frameRecords.length === 0) return [];
1451
+ const visibleFrameIds = [];
1452
+ for (const frameRecord of frameRecords) {
1453
+ if (!frameRecord.parentFrameId) {
1454
+ visibleFrameIds.push(frameRecord.frameId);
1455
+ continue;
1456
+ }
1457
+ try {
1458
+ const parentContextId = await this.ensureFrameContextId(
1459
+ frameRecord.parentFrameId
1460
+ );
1461
+ const visible = await this.isFrameOwnerVisible(
1462
+ frameRecord.frameId,
1463
+ parentContextId
1464
+ );
1465
+ if (visible) {
1466
+ visibleFrameIds.push(frameRecord.frameId);
1467
+ }
1468
+ } catch (error) {
1469
+ if (isIgnorableFrameError(error)) continue;
1470
+ throw error;
1471
+ }
1472
+ }
1473
+ return visibleFrameIds;
1474
+ }
1475
+ async waitForFrameVisualStability(frameId, timeout, settleMs) {
1476
+ if (timeout <= 0) return;
1477
+ const script = buildStabilityScript(timeout, settleMs);
1478
+ let contextId = await this.ensureFrameContextId(frameId);
1479
+ try {
1480
+ await this.evaluateWithGuard(contextId, script, timeout);
1481
+ } catch (error) {
1482
+ if (!isMissingExecutionContextError(error)) {
1483
+ throw error;
1484
+ }
1485
+ this.contextsByFrame.delete(frameId);
1486
+ contextId = await this.ensureFrameContextId(frameId);
1487
+ await this.evaluateWithGuard(contextId, script, timeout);
1488
+ }
1489
+ }
1490
+ async initialize() {
1491
+ await this.session.send("Page.enable");
1492
+ await this.session.send("Runtime.enable");
1493
+ await this.session.send("DOM.enable");
1494
+ }
1495
+ async getFrameRecords() {
1496
+ const treeResult = await this.session.send("Page.getFrameTree");
1497
+ const records = [];
1498
+ walkFrameTree(treeResult.frameTree, null, records);
1499
+ return records;
1500
+ }
1501
+ async ensureFrameContextId(frameId) {
1502
+ const cached = this.contextsByFrame.get(frameId);
1503
+ if (cached != null) {
1504
+ return cached;
1505
+ }
1506
+ const world = await this.session.send("Page.createIsolatedWorld", {
1507
+ frameId,
1508
+ worldName: STEALTH_WORLD_NAME
1509
+ });
1510
+ this.contextsByFrame.set(frameId, world.executionContextId);
1511
+ return world.executionContextId;
1512
+ }
1513
+ async evaluateWithGuard(contextId, script, timeout) {
1514
+ const evaluationPromise = this.evaluateScript(contextId, script);
1515
+ const settledPromise = evaluationPromise.then(
1516
+ () => ({ kind: "resolved" }),
1517
+ (error) => ({ kind: "rejected", error })
1518
+ );
1519
+ const timeoutPromise = sleep(
1520
+ timeout + FRAME_EVALUATE_GRACE_MS
1521
+ ).then(() => ({ kind: "timeout" }));
1522
+ const result = await Promise.race([
1523
+ settledPromise,
1524
+ timeoutPromise
1525
+ ]);
1526
+ if (result.kind === "rejected") {
1527
+ throw result.error;
1528
+ }
1529
+ }
1530
+ async evaluateScript(contextId, expression) {
1531
+ const result = await this.session.send("Runtime.evaluate", {
1532
+ contextId,
1533
+ expression,
1534
+ awaitPromise: true,
1535
+ returnByValue: true
1536
+ });
1537
+ if (result.exceptionDetails) {
1538
+ throw new Error(formatCdpException(result.exceptionDetails));
1539
+ }
1540
+ }
1541
+ async isFrameOwnerVisible(frameId, parentContextId) {
1542
+ const owner = await this.session.send("DOM.getFrameOwner", {
1543
+ frameId
1544
+ });
1545
+ const resolveParams = {
1546
+ executionContextId: parentContextId
1547
+ };
1548
+ if (typeof owner.backendNodeId === "number") {
1549
+ resolveParams.backendNodeId = owner.backendNodeId;
1550
+ } else if (typeof owner.nodeId === "number") {
1551
+ resolveParams.nodeId = owner.nodeId;
1552
+ } else {
1553
+ return false;
1554
+ }
1555
+ const resolved = await this.session.send(
1556
+ "DOM.resolveNode",
1557
+ resolveParams
1558
+ );
1559
+ const objectId = resolved.object?.objectId;
1560
+ if (!objectId) return false;
1561
+ try {
1562
+ const callResult = await this.session.send("Runtime.callFunctionOn", {
1563
+ objectId,
1564
+ functionDeclaration: FRAME_OWNER_VISIBILITY_FUNCTION,
1565
+ returnByValue: true
1566
+ });
1567
+ if (callResult.exceptionDetails) {
1568
+ throw new Error(formatCdpException(callResult.exceptionDetails));
1569
+ }
1570
+ return callResult.result.value === true;
1571
+ } finally {
1572
+ await this.releaseObject(objectId);
1573
+ }
1574
+ }
1575
+ async releaseObject(objectId) {
1576
+ await this.session.send("Runtime.releaseObject", {
1577
+ objectId
1578
+ }).catch(() => void 0);
1579
+ }
1580
+ };
1208
1581
  async function waitForVisualStability(page, options = {}) {
1209
- const timeout = options.timeout ?? DEFAULT_TIMEOUT;
1210
- const settleMs = options.settleMs ?? DEFAULT_SETTLE_MS;
1211
- await waitForFrameVisualStability(page.mainFrame(), {
1212
- timeout,
1213
- settleMs
1214
- });
1582
+ const runtime = await StealthCdpRuntime.create(page);
1583
+ try {
1584
+ await runtime.waitForMainFrameVisualStability(options);
1585
+ } finally {
1586
+ await runtime.dispose();
1587
+ }
1215
1588
  }
1216
1589
  async function waitForVisualStabilityAcrossFrames(page, options = {}) {
1217
1590
  const timeout = options.timeout ?? DEFAULT_TIMEOUT;
1218
1591
  const settleMs = options.settleMs ?? DEFAULT_SETTLE_MS;
1592
+ if (timeout <= 0) return;
1219
1593
  const deadline = Date.now() + timeout;
1220
- while (true) {
1221
- const remaining = Math.max(0, deadline - Date.now());
1222
- if (remaining === 0) return;
1223
- const frames = page.frames();
1224
- await Promise.all(
1225
- frames.map(async (frame) => {
1226
- try {
1227
- await waitForFrameVisualStability(frame, {
1228
- timeout: remaining,
1229
- settleMs
1230
- });
1231
- } catch (error) {
1232
- if (isIgnorableFrameError(error)) return;
1233
- throw error;
1234
- }
1235
- })
1236
- );
1237
- const currentFrames = page.frames();
1238
- if (sameFrames(frames, currentFrames)) {
1239
- return;
1594
+ const runtime = await StealthCdpRuntime.create(page);
1595
+ try {
1596
+ while (true) {
1597
+ const remaining = Math.max(0, deadline - Date.now());
1598
+ if (remaining === 0) return;
1599
+ const frameIds = await runtime.collectVisibleFrameIds();
1600
+ if (frameIds.length === 0) return;
1601
+ await Promise.all(
1602
+ frameIds.map(async (frameId) => {
1603
+ try {
1604
+ await runtime.waitForFrameVisualStability(
1605
+ frameId,
1606
+ remaining,
1607
+ settleMs
1608
+ );
1609
+ } catch (error) {
1610
+ if (isIgnorableFrameError(error)) return;
1611
+ throw error;
1612
+ }
1613
+ })
1614
+ );
1615
+ const currentFrameIds = await runtime.collectVisibleFrameIds();
1616
+ if (sameFrameIds(frameIds, currentFrameIds)) {
1617
+ return;
1618
+ }
1240
1619
  }
1620
+ } finally {
1621
+ await runtime.dispose();
1241
1622
  }
1242
1623
  }
1243
- async function waitForFrameVisualStability(frame, options) {
1244
- const timeout = options.timeout ?? DEFAULT_TIMEOUT;
1245
- const settleMs = options.settleMs ?? DEFAULT_SETTLE_MS;
1246
- if (timeout <= 0) return;
1247
- await frame.evaluate(buildStabilityScript(timeout, settleMs));
1624
+ function walkFrameTree(node, parentFrameId, records) {
1625
+ const frameId = node.frame?.id;
1626
+ if (!frameId) return;
1627
+ records.push({
1628
+ frameId,
1629
+ parentFrameId
1630
+ });
1631
+ for (const child of node.childFrames ?? []) {
1632
+ walkFrameTree(child, frameId, records);
1633
+ }
1248
1634
  }
1249
- function sameFrames(before, after) {
1635
+ function sameFrameIds(before, after) {
1250
1636
  if (before.length !== after.length) return false;
1251
- for (const frame of before) {
1252
- if (!after.includes(frame)) return false;
1637
+ for (const frameId of before) {
1638
+ if (!after.includes(frameId)) return false;
1253
1639
  }
1254
1640
  return true;
1255
1641
  }
1642
+ function formatCdpException(details) {
1643
+ return details.exception?.description || details.text || "CDP runtime evaluation failed.";
1644
+ }
1645
+ function isMissingExecutionContextError(error) {
1646
+ if (!(error instanceof Error)) return false;
1647
+ const message = error.message;
1648
+ return message.includes("Cannot find context with specified id") || message.includes("Cannot find execution context");
1649
+ }
1256
1650
  function isIgnorableFrameError(error) {
1257
1651
  if (!(error instanceof Error)) return false;
1258
1652
  const message = error.message;
1259
- return message.includes("Frame was detached") || message.includes("Execution context was destroyed") || message.includes("Target page, context or browser has been closed");
1653
+ 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");
1654
+ }
1655
+ function sleep(ms) {
1656
+ return new Promise((resolve) => {
1657
+ setTimeout(resolve, ms);
1658
+ });
1260
1659
  }
1261
1660
 
1262
1661
  // src/storage/local.ts
@@ -2157,6 +2556,66 @@ var OS_BOUNDARY_ATTR = "data-os-boundary";
2157
2556
  var OS_UNAVAILABLE_ATTR = "data-os-unavailable";
2158
2557
  var OS_IFRAME_BOUNDARY_TAG = "os-iframe-root";
2159
2558
  var OS_SHADOW_BOUNDARY_TAG = "os-shadow-root";
2559
+ function decodeSerializedNodeTableEntry(nodeTable, rawIndex, label) {
2560
+ if (typeof rawIndex !== "number" || !Number.isInteger(rawIndex) || rawIndex < 0 || rawIndex >= nodeTable.length) {
2561
+ throw new Error(
2562
+ `Invalid serialized path node index at "${label}": expected a valid table index.`
2563
+ );
2564
+ }
2565
+ const node = nodeTable[rawIndex];
2566
+ if (!node || typeof node !== "object") {
2567
+ throw new Error(
2568
+ `Invalid serialized path node at "${label}": table entry is missing.`
2569
+ );
2570
+ }
2571
+ return node;
2572
+ }
2573
+ function decodeSerializedDomPath(nodeTable, rawPath, label) {
2574
+ if (!Array.isArray(rawPath)) {
2575
+ throw new Error(
2576
+ `Invalid serialized path at "${label}": expected an array of node indexes.`
2577
+ );
2578
+ }
2579
+ return rawPath.map(
2580
+ (value, index) => decodeSerializedNodeTableEntry(nodeTable, value, `${label}[${index}]`)
2581
+ );
2582
+ }
2583
+ function decodeSerializedElementPath(nodeTable, rawPath, label) {
2584
+ if (!rawPath || typeof rawPath !== "object") {
2585
+ throw new Error(
2586
+ `Invalid serialized element path at "${label}": expected an object.`
2587
+ );
2588
+ }
2589
+ if (rawPath.context !== void 0 && !Array.isArray(rawPath.context)) {
2590
+ throw new Error(
2591
+ `Invalid serialized context at "${label}.context": expected an array.`
2592
+ );
2593
+ }
2594
+ const contextRaw = Array.isArray(rawPath.context) ? rawPath.context : [];
2595
+ const context = contextRaw.map((hop, hopIndex) => {
2596
+ if (!hop || typeof hop !== "object" || hop.kind !== "shadow") {
2597
+ throw new Error(
2598
+ `Invalid serialized context hop at "${label}.context[${hopIndex}]": expected a shadow hop.`
2599
+ );
2600
+ }
2601
+ return {
2602
+ kind: "shadow",
2603
+ host: decodeSerializedDomPath(
2604
+ nodeTable,
2605
+ hop.host,
2606
+ `${label}.context[${hopIndex}].host`
2607
+ )
2608
+ };
2609
+ });
2610
+ return {
2611
+ context,
2612
+ nodes: decodeSerializedDomPath(
2613
+ nodeTable,
2614
+ rawPath.nodes,
2615
+ `${label}.nodes`
2616
+ )
2617
+ };
2618
+ }
2160
2619
  async function serializePageHTML(page, _options = {}) {
2161
2620
  return serializeFrameRecursive(page.mainFrame(), [], "f0");
2162
2621
  }
@@ -2213,6 +2672,8 @@ async function serializeFrameRecursive(frame, baseContext, frameKey) {
2213
2672
  (Array.isArray(deferredMatchAttrKeys) ? deferredMatchAttrKeys : []).map((key) => String(key))
2214
2673
  );
2215
2674
  let counter = 1;
2675
+ const nodeTable = [];
2676
+ const nodeTableIndexByKey = /* @__PURE__ */ new Map();
2216
2677
  const entries = [];
2217
2678
  const helpers = {
2218
2679
  nextToken() {
@@ -2414,6 +2875,47 @@ async function serializeFrameRecursive(frame, baseContext, frameKey) {
2414
2875
  nodes: target
2415
2876
  };
2416
2877
  },
2878
+ buildPathNodeKey(node) {
2879
+ const attrs = Object.entries(node.attrs || {}).sort(
2880
+ ([a], [b]) => a.localeCompare(b)
2881
+ );
2882
+ const match = (node.match || []).map(
2883
+ (clause) => clause.kind === "attr" ? [
2884
+ "attr",
2885
+ clause.key,
2886
+ clause.op || "exact",
2887
+ clause.value ?? null
2888
+ ] : ["position", clause.axis]
2889
+ );
2890
+ return JSON.stringify([
2891
+ node.tag,
2892
+ node.position.nthChild,
2893
+ node.position.nthOfType,
2894
+ attrs,
2895
+ match
2896
+ ]);
2897
+ },
2898
+ internPathNode(node) {
2899
+ const key = helpers.buildPathNodeKey(node);
2900
+ const existing = nodeTableIndexByKey.get(key);
2901
+ if (existing != null) return existing;
2902
+ const index = nodeTable.length;
2903
+ nodeTable.push(node);
2904
+ nodeTableIndexByKey.set(key, index);
2905
+ return index;
2906
+ },
2907
+ packDomPath(path5) {
2908
+ return path5.map((node) => helpers.internPathNode(node));
2909
+ },
2910
+ packElementPath(path5) {
2911
+ return {
2912
+ context: (path5.context || []).map((hop) => ({
2913
+ kind: "shadow",
2914
+ host: helpers.packDomPath(hop.host)
2915
+ })),
2916
+ nodes: helpers.packDomPath(path5.nodes)
2917
+ };
2918
+ },
2417
2919
  ensureNodeId(el) {
2418
2920
  const next = `${frameKey2}_${counter++}`;
2419
2921
  el.setAttribute(nodeAttr, next);
@@ -2443,9 +2945,12 @@ async function serializeFrameRecursive(frame, baseContext, frameKey) {
2443
2945
  serializeElement(el) {
2444
2946
  const nodeId = helpers.ensureNodeId(el);
2445
2947
  const instanceToken = helpers.setInstanceToken(el);
2948
+ const packedPath = helpers.packElementPath(
2949
+ helpers.buildElementPath(el)
2950
+ );
2446
2951
  entries.push({
2447
2952
  nodeId,
2448
- path: helpers.buildElementPath(el),
2953
+ path: packedPath,
2449
2954
  instanceToken
2450
2955
  });
2451
2956
  const tag = el.tagName.toLowerCase();
@@ -2473,11 +2978,12 @@ async function serializeFrameRecursive(frame, baseContext, frameKey) {
2473
2978
  win[frameTokenKey] = frameToken;
2474
2979
  const root = document.documentElement;
2475
2980
  if (!root) {
2476
- return { html: "", frameToken, entries };
2981
+ return { html: "", frameToken, nodeTable, entries };
2477
2982
  }
2478
2983
  return {
2479
2984
  html: helpers.serializeElement(root),
2480
2985
  frameToken,
2986
+ nodeTable,
2481
2987
  entries
2482
2988
  };
2483
2989
  },
@@ -2495,13 +3001,18 @@ async function serializeFrameRecursive(frame, baseContext, frameKey) {
2495
3001
  );
2496
3002
  const nodePaths = /* @__PURE__ */ new Map();
2497
3003
  const nodeMeta = /* @__PURE__ */ new Map();
2498
- for (const entry of frameSnapshot.entries) {
3004
+ for (const [index, entry] of frameSnapshot.entries.entries()) {
3005
+ const path5 = decodeSerializedElementPath(
3006
+ frameSnapshot.nodeTable,
3007
+ entry.path,
3008
+ `entries[${index}].path`
3009
+ );
2499
3010
  nodePaths.set(entry.nodeId, {
2500
3011
  context: [
2501
3012
  ...baseContext,
2502
- ...entry.path.context || []
3013
+ ...path5.context || []
2503
3014
  ],
2504
- nodes: entry.path.nodes
3015
+ nodes: path5.nodes
2505
3016
  });
2506
3017
  nodeMeta.set(entry.nodeId, {
2507
3018
  frameToken: frameSnapshot.frameToken,
@@ -4895,7 +5406,7 @@ async function performInput(page, path5, options) {
4895
5406
  await resolved.element.type(options.text);
4896
5407
  }
4897
5408
  if (options.pressEnter) {
4898
- await resolved.element.press("Enter");
5409
+ await resolved.element.press("Enter", { noWaitAfter: true });
4899
5410
  }
4900
5411
  return {
4901
5412
  ok: true,
@@ -5561,7 +6072,26 @@ var ACTION_WAIT_PROFILES = {
5561
6072
  type: ROBUST_PROFILE
5562
6073
  };
5563
6074
  var NETWORK_POLL_MS = 50;
5564
- var IGNORED_RESOURCE_TYPES = /* @__PURE__ */ new Set(["websocket", "eventsource"]);
6075
+ var NETWORK_RELAX_AFTER_MS = 1800;
6076
+ var RELAXED_ALLOWED_PENDING = 2;
6077
+ var HEAVY_VISUAL_REQUEST_WINDOW_MS = 5e3;
6078
+ var TRACKED_RESOURCE_TYPES = /* @__PURE__ */ new Set([
6079
+ "document",
6080
+ "fetch",
6081
+ "xhr",
6082
+ "stylesheet",
6083
+ "image",
6084
+ "font",
6085
+ "media"
6086
+ ]);
6087
+ var HEAVY_RESOURCE_TYPES = /* @__PURE__ */ new Set(["document", "fetch", "xhr"]);
6088
+ var HEAVY_VISUAL_RESOURCE_TYPES = /* @__PURE__ */ new Set([
6089
+ "stylesheet",
6090
+ "image",
6091
+ "font",
6092
+ "media"
6093
+ ]);
6094
+ var IGNORED_RESOURCE_TYPES = /* @__PURE__ */ new Set(["websocket", "eventsource", "manifest"]);
5565
6095
  var NOOP_SESSION = {
5566
6096
  async wait() {
5567
6097
  },
@@ -5571,7 +6101,7 @@ var NOOP_SESSION = {
5571
6101
  function createPostActionWaitSession(page, action, override) {
5572
6102
  const profile = resolveActionWaitProfile(action, override);
5573
6103
  if (!profile.enabled) return NOOP_SESSION;
5574
- const tracker = profile.includeNetwork ? new ScopedNetworkTracker(page) : null;
6104
+ const tracker = profile.includeNetwork ? new AdaptiveNetworkTracker(page) : null;
5575
6105
  tracker?.start();
5576
6106
  let settled = false;
5577
6107
  return {
@@ -5579,19 +6109,32 @@ function createPostActionWaitSession(page, action, override) {
5579
6109
  if (settled) return;
5580
6110
  settled = true;
5581
6111
  const deadline = Date.now() + profile.timeout;
5582
- const networkWait = tracker ? tracker.waitForQuiet({
5583
- deadline,
5584
- quietMs: profile.networkQuietMs
5585
- }) : Promise.resolve();
6112
+ const visualTimeout = profile.includeNetwork ? Math.min(
6113
+ profile.timeout,
6114
+ resolveNetworkBackedVisualTimeout(profile.settleMs)
6115
+ ) : profile.timeout;
5586
6116
  try {
5587
- await Promise.all([
5588
- waitForVisualStabilityAcrossFrames(page, {
5589
- timeout: profile.timeout,
6117
+ try {
6118
+ await waitForVisualStabilityAcrossFrames(page, {
6119
+ timeout: visualTimeout,
5590
6120
  settleMs: profile.settleMs
5591
- }),
5592
- networkWait
5593
- ]);
5594
- } catch {
6121
+ });
6122
+ } catch (error) {
6123
+ if (isStealthWaitUnavailableError(error)) {
6124
+ throw error;
6125
+ }
6126
+ } finally {
6127
+ tracker?.freezeCollection();
6128
+ }
6129
+ if (tracker) {
6130
+ try {
6131
+ await tracker.waitForQuiet({
6132
+ deadline,
6133
+ quietMs: profile.networkQuietMs
6134
+ });
6135
+ } catch {
6136
+ }
6137
+ }
5595
6138
  } finally {
5596
6139
  tracker?.stop();
5597
6140
  }
@@ -5631,53 +6174,70 @@ function normalizeMs(value, fallback) {
5631
6174
  }
5632
6175
  return Math.max(0, Math.floor(value));
5633
6176
  }
5634
- var ScopedNetworkTracker = class {
6177
+ function resolveNetworkBackedVisualTimeout(settleMs) {
6178
+ const derived = settleMs * 3 + 300;
6179
+ return Math.max(1200, Math.min(2500, derived));
6180
+ }
6181
+ var AdaptiveNetworkTracker = class {
5635
6182
  constructor(page) {
5636
6183
  this.page = page;
5637
6184
  }
5638
- pending = /* @__PURE__ */ new Set();
6185
+ pending = /* @__PURE__ */ new Map();
5639
6186
  started = false;
6187
+ collecting = false;
6188
+ startedAt = 0;
5640
6189
  idleSince = Date.now();
5641
6190
  start() {
5642
6191
  if (this.started) return;
5643
6192
  this.started = true;
6193
+ this.collecting = true;
6194
+ this.startedAt = Date.now();
6195
+ this.idleSince = this.startedAt;
5644
6196
  this.page.on("request", this.handleRequestStarted);
5645
6197
  this.page.on("requestfinished", this.handleRequestFinished);
5646
6198
  this.page.on("requestfailed", this.handleRequestFinished);
5647
6199
  }
6200
+ freezeCollection() {
6201
+ if (!this.started) return;
6202
+ this.collecting = false;
6203
+ }
5648
6204
  stop() {
5649
6205
  if (!this.started) return;
5650
6206
  this.started = false;
6207
+ this.collecting = false;
5651
6208
  this.page.off("request", this.handleRequestStarted);
5652
6209
  this.page.off("requestfinished", this.handleRequestFinished);
5653
6210
  this.page.off("requestfailed", this.handleRequestFinished);
5654
6211
  this.pending.clear();
6212
+ this.startedAt = 0;
5655
6213
  this.idleSince = Date.now();
5656
6214
  }
5657
6215
  async waitForQuiet(options) {
5658
6216
  const quietMs = Math.max(0, options.quietMs);
5659
6217
  if (quietMs === 0) return;
5660
6218
  while (Date.now() < options.deadline) {
5661
- if (this.pending.size === 0) {
6219
+ const now = Date.now();
6220
+ const allowedPending = this.resolveAllowedPending(now);
6221
+ if (this.pending.size <= allowedPending) {
5662
6222
  if (this.idleSince === 0) {
5663
- this.idleSince = Date.now();
6223
+ this.idleSince = now;
5664
6224
  }
5665
- const idleFor = Date.now() - this.idleSince;
6225
+ const idleFor = now - this.idleSince;
5666
6226
  if (idleFor >= quietMs) {
5667
6227
  return;
5668
6228
  }
5669
6229
  } else {
5670
6230
  this.idleSince = 0;
5671
6231
  }
5672
- const remaining = Math.max(1, options.deadline - Date.now());
5673
- await sleep(Math.min(NETWORK_POLL_MS, remaining));
6232
+ const remaining = Math.max(1, options.deadline - now);
6233
+ await sleep2(Math.min(NETWORK_POLL_MS, remaining));
5674
6234
  }
5675
6235
  }
5676
6236
  handleRequestStarted = (request) => {
5677
- if (!this.started) return;
5678
- const resourceType = request.resourceType();
5679
- if (IGNORED_RESOURCE_TYPES.has(resourceType)) return;
5680
- this.pending.add(request);
6237
+ if (!this.started || !this.collecting) return;
6238
+ const trackedRequest = this.classifyRequest(request);
6239
+ if (!trackedRequest) return;
6240
+ this.pending.set(request, trackedRequest);
5681
6241
  this.idleSince = 0;
5682
6242
  };
5683
6243
  handleRequestFinished = (request) => {
@@ -5687,8 +6247,35 @@ var ScopedNetworkTracker = class {
5687
6247
  this.idleSince = Date.now();
5688
6248
  }
5689
6249
  };
6250
+ classifyRequest(request) {
6251
+ const resourceType = request.resourceType().toLowerCase();
6252
+ if (IGNORED_RESOURCE_TYPES.has(resourceType)) return null;
6253
+ if (!TRACKED_RESOURCE_TYPES.has(resourceType)) return null;
6254
+ const frame = request.frame();
6255
+ if (!frame || frame !== this.page.mainFrame()) return null;
6256
+ return {
6257
+ resourceType,
6258
+ startedAt: Date.now()
6259
+ };
6260
+ }
6261
+ resolveAllowedPending(now) {
6262
+ const relaxed = now - this.startedAt >= NETWORK_RELAX_AFTER_MS ? RELAXED_ALLOWED_PENDING : 0;
6263
+ if (this.hasHeavyPending(now)) return 0;
6264
+ return relaxed;
6265
+ }
6266
+ hasHeavyPending(now) {
6267
+ for (const trackedRequest of this.pending.values()) {
6268
+ if (HEAVY_RESOURCE_TYPES.has(trackedRequest.resourceType)) {
6269
+ return true;
6270
+ }
6271
+ if (HEAVY_VISUAL_RESOURCE_TYPES.has(trackedRequest.resourceType) && now - trackedRequest.startedAt < HEAVY_VISUAL_REQUEST_WINDOW_MS) {
6272
+ return true;
6273
+ }
6274
+ }
6275
+ return false;
6276
+ }
5690
6277
  };
5691
- async function sleep(ms) {
6278
+ async function sleep2(ms) {
5692
6279
  await new Promise((resolve) => {
5693
6280
  setTimeout(resolve, ms);
5694
6281
  });
@@ -6806,36 +7393,39 @@ function clonePersistedExtractNode(node) {
6806
7393
  return JSON.parse(JSON.stringify(node));
6807
7394
  }
6808
7395
 
6809
- // src/remote/action-ws-client.ts
7396
+ // src/cloud/contracts.ts
7397
+ var cloudSessionContractVersion = "v3";
7398
+
7399
+ // src/cloud/action-ws-client.ts
6810
7400
  var import_ws2 = __toESM(require("ws"), 1);
6811
7401
 
6812
- // src/remote/errors.ts
6813
- var OpensteerRemoteError = class extends Error {
7402
+ // src/cloud/errors.ts
7403
+ var OpensteerCloudError = class extends Error {
6814
7404
  code;
6815
7405
  status;
6816
7406
  details;
6817
7407
  constructor(code, message, status, details) {
6818
7408
  super(message);
6819
- this.name = "OpensteerRemoteError";
7409
+ this.name = "OpensteerCloudError";
6820
7410
  this.code = code;
6821
7411
  this.status = status;
6822
7412
  this.details = details;
6823
7413
  }
6824
7414
  };
6825
- function remoteUnsupportedMethodError(method, message) {
6826
- return new OpensteerRemoteError(
6827
- "REMOTE_UNSUPPORTED_METHOD",
6828
- message || `${method} is not supported in remote mode.`
7415
+ function cloudUnsupportedMethodError(method, message) {
7416
+ return new OpensteerCloudError(
7417
+ "CLOUD_UNSUPPORTED_METHOD",
7418
+ message || `${method} is not supported in cloud mode.`
6829
7419
  );
6830
7420
  }
6831
- function remoteNotLaunchedError() {
6832
- return new OpensteerRemoteError(
6833
- "REMOTE_SESSION_NOT_FOUND",
6834
- "Remote session is not connected. Call launch() first."
7421
+ function cloudNotLaunchedError() {
7422
+ return new OpensteerCloudError(
7423
+ "CLOUD_SESSION_NOT_FOUND",
7424
+ "Cloud session is not connected. Call launch() first."
6835
7425
  );
6836
7426
  }
6837
7427
 
6838
- // src/remote/action-ws-client.ts
7428
+ // src/cloud/action-ws-client.ts
6839
7429
  var ActionWsClient = class _ActionWsClient {
6840
7430
  ws;
6841
7431
  sessionId;
@@ -6852,18 +7442,18 @@ var ActionWsClient = class _ActionWsClient {
6852
7442
  });
6853
7443
  ws.on("error", (error) => {
6854
7444
  this.rejectAll(
6855
- new OpensteerRemoteError(
6856
- "REMOTE_TRANSPORT_ERROR",
6857
- `Remote action websocket error: ${error.message}`
7445
+ new OpensteerCloudError(
7446
+ "CLOUD_TRANSPORT_ERROR",
7447
+ `Cloud action websocket error: ${error.message}`
6858
7448
  )
6859
7449
  );
6860
7450
  });
6861
7451
  ws.on("close", () => {
6862
7452
  this.closed = true;
6863
7453
  this.rejectAll(
6864
- new OpensteerRemoteError(
6865
- "REMOTE_SESSION_CLOSED",
6866
- "Remote action websocket closed."
7454
+ new OpensteerCloudError(
7455
+ "CLOUD_SESSION_CLOSED",
7456
+ "Cloud action websocket closed."
6867
7457
  )
6868
7458
  );
6869
7459
  });
@@ -6875,8 +7465,8 @@ var ActionWsClient = class _ActionWsClient {
6875
7465
  ws.once("open", () => resolve());
6876
7466
  ws.once("error", (error) => {
6877
7467
  reject(
6878
- new OpensteerRemoteError(
6879
- "REMOTE_TRANSPORT_ERROR",
7468
+ new OpensteerCloudError(
7469
+ "CLOUD_TRANSPORT_ERROR",
6880
7470
  `Failed to connect action websocket: ${error.message}`
6881
7471
  )
6882
7472
  );
@@ -6886,9 +7476,9 @@ var ActionWsClient = class _ActionWsClient {
6886
7476
  }
6887
7477
  async request(method, args) {
6888
7478
  if (this.closed || this.ws.readyState !== import_ws2.default.OPEN) {
6889
- throw new OpensteerRemoteError(
6890
- "REMOTE_SESSION_CLOSED",
6891
- "Remote action websocket is closed."
7479
+ throw new OpensteerCloudError(
7480
+ "CLOUD_SESSION_CLOSED",
7481
+ "Cloud action websocket is closed."
6892
7482
  );
6893
7483
  }
6894
7484
  const id = this.nextRequestId;
@@ -6907,8 +7497,8 @@ var ActionWsClient = class _ActionWsClient {
6907
7497
  this.ws.send(JSON.stringify(payload));
6908
7498
  } catch (error) {
6909
7499
  this.pending.delete(id);
6910
- const message = error instanceof Error ? error.message : "Failed to send remote action request.";
6911
- throw new OpensteerRemoteError("REMOTE_TRANSPORT_ERROR", message);
7500
+ const message = error instanceof Error ? error.message : "Failed to send cloud action request.";
7501
+ throw new OpensteerCloudError("CLOUD_TRANSPORT_ERROR", message);
6912
7502
  }
6913
7503
  return await resultPromise;
6914
7504
  }
@@ -6926,9 +7516,9 @@ var ActionWsClient = class _ActionWsClient {
6926
7516
  parsed = JSON.parse(rawDataToUtf8(raw));
6927
7517
  } catch {
6928
7518
  this.rejectAll(
6929
- new OpensteerRemoteError(
6930
- "REMOTE_TRANSPORT_ERROR",
6931
- "Invalid remote action response payload."
7519
+ new OpensteerCloudError(
7520
+ "CLOUD_TRANSPORT_ERROR",
7521
+ "Invalid cloud action response payload."
6932
7522
  )
6933
7523
  );
6934
7524
  return;
@@ -6941,7 +7531,7 @@ var ActionWsClient = class _ActionWsClient {
6941
7531
  return;
6942
7532
  }
6943
7533
  pending.reject(
6944
- new OpensteerRemoteError(
7534
+ new OpensteerCloudError(
6945
7535
  parsed.code,
6946
7536
  parsed.error,
6947
7537
  void 0,
@@ -6969,7 +7559,7 @@ function withTokenQuery(wsUrl, token) {
6969
7559
  return url.toString();
6970
7560
  }
6971
7561
 
6972
- // src/remote/local-cache-sync.ts
7562
+ // src/cloud/local-cache-sync.ts
6973
7563
  var import_fs3 = __toESM(require("fs"), 1);
6974
7564
  var import_path5 = __toESM(require("path"), 1);
6975
7565
  function collectLocalSelectorCacheEntries(storage) {
@@ -7093,24 +7683,24 @@ function dedupeNewest(entries) {
7093
7683
  return [...byKey.values()];
7094
7684
  }
7095
7685
 
7096
- // src/remote/cdp-client.ts
7686
+ // src/cloud/cdp-client.ts
7097
7687
  var import_playwright2 = require("playwright");
7098
- var RemoteCdpClient = class {
7688
+ var CloudCdpClient = class {
7099
7689
  async connect(args) {
7100
7690
  const endpoint = withTokenQuery2(args.wsUrl, args.token);
7101
7691
  let browser;
7102
7692
  try {
7103
7693
  browser = await import_playwright2.chromium.connectOverCDP(endpoint);
7104
7694
  } catch (error) {
7105
- const message = error instanceof Error ? error.message : "Failed to connect to remote CDP endpoint.";
7106
- throw new OpensteerRemoteError("REMOTE_TRANSPORT_ERROR", message);
7695
+ const message = error instanceof Error ? error.message : "Failed to connect to cloud CDP endpoint.";
7696
+ throw new OpensteerCloudError("CLOUD_TRANSPORT_ERROR", message);
7107
7697
  }
7108
7698
  const context = browser.contexts()[0];
7109
7699
  if (!context) {
7110
7700
  await browser.close();
7111
- throw new OpensteerRemoteError(
7112
- "REMOTE_INTERNAL",
7113
- "Remote browser returned no context."
7701
+ throw new OpensteerCloudError(
7702
+ "CLOUD_INTERNAL",
7703
+ "Cloud browser returned no context."
7114
7704
  );
7115
7705
  }
7116
7706
  const page = context.pages()[0] || await context.newPage();
@@ -7123,34 +7713,46 @@ function withTokenQuery2(wsUrl, token) {
7123
7713
  return url.toString();
7124
7714
  }
7125
7715
 
7126
- // src/remote/session-client.ts
7716
+ // src/cloud/session-client.ts
7127
7717
  var CACHE_IMPORT_BATCH_SIZE = 200;
7128
- var RemoteSessionClient = class {
7718
+ var CloudSessionClient = class {
7129
7719
  baseUrl;
7130
7720
  key;
7131
- constructor(baseUrl, key) {
7721
+ authScheme;
7722
+ constructor(baseUrl, key, authScheme = "api-key") {
7132
7723
  this.baseUrl = normalizeBaseUrl(baseUrl);
7133
7724
  this.key = key;
7725
+ this.authScheme = authScheme;
7134
7726
  }
7135
7727
  async create(request) {
7136
7728
  const response = await fetch(`${this.baseUrl}/sessions`, {
7137
7729
  method: "POST",
7138
7730
  headers: {
7139
7731
  "content-type": "application/json",
7140
- "x-api-key": this.key
7732
+ ...this.authHeaders()
7141
7733
  },
7142
7734
  body: JSON.stringify(request)
7143
7735
  });
7144
7736
  if (!response.ok) {
7145
7737
  throw await parseHttpError(response);
7146
7738
  }
7147
- return await response.json();
7739
+ let body;
7740
+ try {
7741
+ body = await response.json();
7742
+ } catch {
7743
+ throw new OpensteerCloudError(
7744
+ "CLOUD_CONTRACT_MISMATCH",
7745
+ "Invalid cloud session create response: expected a JSON object.",
7746
+ response.status
7747
+ );
7748
+ }
7749
+ return parseCreateResponse(body, response.status);
7148
7750
  }
7149
7751
  async close(sessionId) {
7150
7752
  const response = await fetch(`${this.baseUrl}/sessions/${sessionId}`, {
7151
7753
  method: "DELETE",
7152
7754
  headers: {
7153
- "x-api-key": this.key
7755
+ ...this.authHeaders()
7154
7756
  }
7155
7757
  });
7156
7758
  if (response.status === 204) {
@@ -7180,7 +7782,7 @@ var RemoteSessionClient = class {
7180
7782
  method: "POST",
7181
7783
  headers: {
7182
7784
  "content-type": "application/json",
7183
- "x-api-key": this.key
7785
+ ...this.authHeaders()
7184
7786
  },
7185
7787
  body: JSON.stringify({ entries })
7186
7788
  });
@@ -7189,10 +7791,148 @@ var RemoteSessionClient = class {
7189
7791
  }
7190
7792
  return await response.json();
7191
7793
  }
7794
+ authHeaders() {
7795
+ if (this.authScheme === "bearer") {
7796
+ return {
7797
+ authorization: `Bearer ${this.key}`
7798
+ };
7799
+ }
7800
+ return {
7801
+ "x-api-key": this.key
7802
+ };
7803
+ }
7192
7804
  };
7193
7805
  function normalizeBaseUrl(baseUrl) {
7194
7806
  return baseUrl.replace(/\/+$/, "");
7195
7807
  }
7808
+ function parseCreateResponse(body, status) {
7809
+ const root = requireObject(
7810
+ body,
7811
+ "Invalid cloud session create response: expected a JSON object.",
7812
+ status
7813
+ );
7814
+ const sessionId = requireString(root, "sessionId", status);
7815
+ const actionWsUrl = requireString(root, "actionWsUrl", status);
7816
+ const cdpWsUrl = requireString(root, "cdpWsUrl", status);
7817
+ const actionToken = requireString(root, "actionToken", status);
7818
+ const cdpToken = requireString(root, "cdpToken", status);
7819
+ const cloudSessionUrl = requireString(root, "cloudSessionUrl", status);
7820
+ const cloudSessionRoot = requireObject(
7821
+ root.cloudSession,
7822
+ "Invalid cloud session create response: cloudSession must be an object.",
7823
+ status
7824
+ );
7825
+ const cloudSession = {
7826
+ sessionId: requireString(cloudSessionRoot, "sessionId", status, "cloudSession"),
7827
+ workspaceId: requireString(
7828
+ cloudSessionRoot,
7829
+ "workspaceId",
7830
+ status,
7831
+ "cloudSession"
7832
+ ),
7833
+ state: requireString(cloudSessionRoot, "state", status, "cloudSession"),
7834
+ createdAt: requireNumber(cloudSessionRoot, "createdAt", status, "cloudSession"),
7835
+ sourceType: requireSourceType(cloudSessionRoot, "sourceType", status, "cloudSession"),
7836
+ sourceRef: optionalString(cloudSessionRoot, "sourceRef", status, "cloudSession"),
7837
+ label: optionalString(cloudSessionRoot, "label", status, "cloudSession")
7838
+ };
7839
+ const expiresAt = optionalNumber(root, "expiresAt", status);
7840
+ return {
7841
+ sessionId,
7842
+ actionWsUrl,
7843
+ cdpWsUrl,
7844
+ actionToken,
7845
+ cdpToken,
7846
+ expiresAt,
7847
+ cloudSessionUrl,
7848
+ cloudSession
7849
+ };
7850
+ }
7851
+ function requireObject(value, message, status) {
7852
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
7853
+ throw new OpensteerCloudError("CLOUD_CONTRACT_MISMATCH", message, status);
7854
+ }
7855
+ return value;
7856
+ }
7857
+ function requireString(source, field, status, parent) {
7858
+ const value = source[field];
7859
+ if (typeof value !== "string" || !value.trim()) {
7860
+ throw new OpensteerCloudError(
7861
+ "CLOUD_CONTRACT_MISMATCH",
7862
+ `Invalid cloud session create response: ${formatFieldPath(
7863
+ field,
7864
+ parent
7865
+ )} must be a non-empty string.`,
7866
+ status
7867
+ );
7868
+ }
7869
+ return value;
7870
+ }
7871
+ function requireNumber(source, field, status, parent) {
7872
+ const value = source[field];
7873
+ if (typeof value !== "number" || !Number.isFinite(value)) {
7874
+ throw new OpensteerCloudError(
7875
+ "CLOUD_CONTRACT_MISMATCH",
7876
+ `Invalid cloud session create response: ${formatFieldPath(
7877
+ field,
7878
+ parent
7879
+ )} must be a finite number.`,
7880
+ status
7881
+ );
7882
+ }
7883
+ return value;
7884
+ }
7885
+ function optionalString(source, field, status, parent) {
7886
+ const value = source[field];
7887
+ if (value == null) {
7888
+ return void 0;
7889
+ }
7890
+ if (typeof value !== "string") {
7891
+ throw new OpensteerCloudError(
7892
+ "CLOUD_CONTRACT_MISMATCH",
7893
+ `Invalid cloud session create response: ${formatFieldPath(
7894
+ field,
7895
+ parent
7896
+ )} must be a string when present.`,
7897
+ status
7898
+ );
7899
+ }
7900
+ return value;
7901
+ }
7902
+ function optionalNumber(source, field, status, parent) {
7903
+ const value = source[field];
7904
+ if (value == null) {
7905
+ return void 0;
7906
+ }
7907
+ if (typeof value !== "number" || !Number.isFinite(value)) {
7908
+ throw new OpensteerCloudError(
7909
+ "CLOUD_CONTRACT_MISMATCH",
7910
+ `Invalid cloud session create response: ${formatFieldPath(
7911
+ field,
7912
+ parent
7913
+ )} must be a finite number when present.`,
7914
+ status
7915
+ );
7916
+ }
7917
+ return value;
7918
+ }
7919
+ function requireSourceType(source, field, status, parent) {
7920
+ const value = source[field];
7921
+ if (value === "agent-thread" || value === "agent-run" || value === "local-cloud" || value === "manual") {
7922
+ return value;
7923
+ }
7924
+ throw new OpensteerCloudError(
7925
+ "CLOUD_CONTRACT_MISMATCH",
7926
+ `Invalid cloud session create response: ${formatFieldPath(
7927
+ field,
7928
+ parent
7929
+ )} must be one of "agent-thread", "agent-run", "local-cloud", or "manual".`,
7930
+ status
7931
+ );
7932
+ }
7933
+ function formatFieldPath(field, parent) {
7934
+ return parent ? `"${parent}.${field}"` : `"${field}"`;
7935
+ }
7196
7936
  function zeroImportResponse() {
7197
7937
  return {
7198
7938
  imported: 0,
@@ -7216,33 +7956,46 @@ async function parseHttpError(response) {
7216
7956
  } catch {
7217
7957
  body = null;
7218
7958
  }
7219
- const code = typeof body?.code === "string" ? toRemoteErrorCode(body.code) : "REMOTE_TRANSPORT_ERROR";
7220
- const message = typeof body?.error === "string" ? body.error : `Remote request failed with status ${response.status}.`;
7221
- return new OpensteerRemoteError(code, message, response.status, body?.details);
7959
+ const code = typeof body?.code === "string" ? toCloudErrorCode(body.code) : "CLOUD_TRANSPORT_ERROR";
7960
+ const message = typeof body?.error === "string" ? body.error : `Cloud request failed with status ${response.status}.`;
7961
+ return new OpensteerCloudError(code, message, response.status, body?.details);
7222
7962
  }
7223
- function toRemoteErrorCode(code) {
7224
- 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") {
7963
+ function toCloudErrorCode(code) {
7964
+ 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") {
7225
7965
  return code;
7226
7966
  }
7227
- return "REMOTE_TRANSPORT_ERROR";
7967
+ return "CLOUD_TRANSPORT_ERROR";
7228
7968
  }
7229
7969
 
7230
- // src/remote/runtime.ts
7231
- var DEFAULT_REMOTE_BASE_URL = "https://remote.opensteer.com";
7232
- function createRemoteRuntimeState(key, baseUrl = resolveRemoteBaseUrl()) {
7970
+ // src/cloud/runtime.ts
7971
+ var DEFAULT_CLOUD_BASE_URL = "https://remote.opensteer.com";
7972
+ var DEFAULT_CLOUD_APP_URL = "https://opensteer.com";
7973
+ function createCloudRuntimeState(key, baseUrl = resolveCloudBaseUrl(), authScheme = "api-key", appUrl = resolveCloudAppUrl()) {
7233
7974
  return {
7234
- sessionClient: new RemoteSessionClient(baseUrl, key),
7235
- cdpClient: new RemoteCdpClient(),
7975
+ sessionClient: new CloudSessionClient(baseUrl, key, authScheme),
7976
+ cdpClient: new CloudCdpClient(),
7977
+ appUrl: normalizeCloudAppUrl(appUrl),
7236
7978
  actionClient: null,
7237
- sessionId: null
7979
+ sessionId: null,
7980
+ localRunId: null,
7981
+ cloudSessionUrl: null
7238
7982
  };
7239
7983
  }
7240
- function resolveRemoteBaseUrl() {
7984
+ function resolveCloudBaseUrl() {
7241
7985
  const value = process.env.OPENSTEER_BASE_URL?.trim();
7242
- if (!value) return DEFAULT_REMOTE_BASE_URL;
7986
+ if (!value) return DEFAULT_CLOUD_BASE_URL;
7987
+ return value.replace(/\/+$/, "");
7988
+ }
7989
+ function resolveCloudAppUrl() {
7990
+ const value = process.env.OPENSTEER_APP_URL?.trim();
7991
+ if (!value) return DEFAULT_CLOUD_APP_URL;
7992
+ return normalizeCloudAppUrl(value);
7993
+ }
7994
+ function normalizeCloudAppUrl(value) {
7995
+ if (!value) return null;
7243
7996
  return value.replace(/\/+$/, "");
7244
7997
  }
7245
- function readRemoteActionDescription(payload) {
7998
+ function readCloudActionDescription(payload) {
7246
7999
  const description = payload.description;
7247
8000
  if (typeof description !== "string") return void 0;
7248
8001
  const normalized = description.trim();
@@ -7250,7 +8003,7 @@ function readRemoteActionDescription(payload) {
7250
8003
  }
7251
8004
 
7252
8005
  // src/opensteer.ts
7253
- var REMOTE_INTERACTION_METHODS = /* @__PURE__ */ new Set([
8006
+ var CLOUD_INTERACTION_METHODS = /* @__PURE__ */ new Set([
7254
8007
  "click",
7255
8008
  "dblclick",
7256
8009
  "rightclick",
@@ -7267,7 +8020,7 @@ var Opensteer = class _Opensteer {
7267
8020
  namespace;
7268
8021
  storage;
7269
8022
  pool;
7270
- remote;
8023
+ cloud;
7271
8024
  browser = null;
7272
8025
  pageRef = null;
7273
8026
  contextRef = null;
@@ -7275,8 +8028,8 @@ var Opensteer = class _Opensteer {
7275
8028
  snapshotCache = null;
7276
8029
  constructor(config = {}) {
7277
8030
  const resolved = resolveConfig(config);
7278
- const modeSelection = resolveModeSelection({
7279
- mode: resolved.mode
8031
+ const cloudSelection = resolveCloudSelection({
8032
+ cloud: resolved.cloud
7280
8033
  });
7281
8034
  const model = resolved.model;
7282
8035
  this.config = resolved;
@@ -7286,20 +8039,22 @@ var Opensteer = class _Opensteer {
7286
8039
  this.namespace = resolveNamespace(resolved, rootDir);
7287
8040
  this.storage = new LocalSelectorStorage(rootDir, this.namespace);
7288
8041
  this.pool = new BrowserPool(resolved.browser || {});
7289
- if (modeSelection.mode === "remote") {
7290
- const remoteConfig = resolved.remote && typeof resolved.remote === "object" ? resolved.remote : void 0;
7291
- const apiKey = remoteConfig?.apiKey?.trim();
8042
+ if (cloudSelection.cloud) {
8043
+ const cloudConfig = resolved.cloud && typeof resolved.cloud === "object" ? resolved.cloud : void 0;
8044
+ const apiKey = cloudConfig?.apiKey?.trim();
7292
8045
  if (!apiKey) {
7293
8046
  throw new Error(
7294
- "Remote mode requires a non-empty API key via remote.apiKey or OPENSTEER_API_KEY."
8047
+ "Cloud mode requires a non-empty API key via cloud.apiKey or OPENSTEER_API_KEY."
7295
8048
  );
7296
8049
  }
7297
- this.remote = createRemoteRuntimeState(
8050
+ this.cloud = createCloudRuntimeState(
7298
8051
  apiKey,
7299
- remoteConfig?.baseUrl
8052
+ cloudConfig?.baseUrl,
8053
+ cloudConfig?.authScheme,
8054
+ cloudConfig?.appUrl
7300
8055
  );
7301
8056
  } else {
7302
- this.remote = null;
8057
+ this.cloud = null;
7303
8058
  }
7304
8059
  }
7305
8060
  createLazyResolveCallback(model) {
@@ -7337,32 +8092,32 @@ var Opensteer = class _Opensteer {
7337
8092
  };
7338
8093
  return extract;
7339
8094
  }
7340
- async invokeRemoteActionAndResetCache(method, args) {
7341
- const result = await this.invokeRemoteAction(method, args);
8095
+ async invokeCloudActionAndResetCache(method, args) {
8096
+ const result = await this.invokeCloudAction(method, args);
7342
8097
  this.snapshotCache = null;
7343
8098
  return result;
7344
8099
  }
7345
- async invokeRemoteAction(method, args) {
7346
- const actionClient = this.remote?.actionClient;
7347
- const sessionId = this.remote?.sessionId;
8100
+ async invokeCloudAction(method, args) {
8101
+ const actionClient = this.cloud?.actionClient;
8102
+ const sessionId = this.cloud?.sessionId;
7348
8103
  if (!actionClient || !sessionId) {
7349
- throw remoteNotLaunchedError();
8104
+ throw cloudNotLaunchedError();
7350
8105
  }
7351
8106
  const payload = args && typeof args === "object" ? args : {};
7352
8107
  try {
7353
8108
  return await actionClient.request(method, payload);
7354
8109
  } catch (err) {
7355
- if (err instanceof OpensteerRemoteError && err.code === "REMOTE_ACTION_FAILED" && REMOTE_INTERACTION_METHODS.has(method)) {
8110
+ if (err instanceof OpensteerCloudError && err.code === "CLOUD_ACTION_FAILED" && CLOUD_INTERACTION_METHODS.has(method)) {
7356
8111
  const detailsRecord = err.details && typeof err.details === "object" ? err.details : null;
7357
- const remoteFailure = normalizeActionFailure(
8112
+ const cloudFailure = normalizeActionFailure(
7358
8113
  detailsRecord?.actionFailure
7359
8114
  );
7360
- const failure = remoteFailure || classifyActionFailure({
8115
+ const failure = cloudFailure || classifyActionFailure({
7361
8116
  action: method,
7362
8117
  error: err,
7363
8118
  fallbackMessage: defaultActionFailureMessage(method)
7364
8119
  });
7365
- const description = readRemoteActionDescription(payload);
8120
+ const description = readCloudActionDescription(payload);
7366
8121
  throw this.buildActionError(
7367
8122
  method,
7368
8123
  description,
@@ -7403,8 +8158,36 @@ var Opensteer = class _Opensteer {
7403
8158
  }
7404
8159
  return this.contextRef;
7405
8160
  }
7406
- getRemoteSessionId() {
7407
- return this.remote?.sessionId ?? null;
8161
+ getCloudSessionId() {
8162
+ return this.cloud?.sessionId ?? null;
8163
+ }
8164
+ getCloudSessionUrl() {
8165
+ return this.cloud?.cloudSessionUrl ?? null;
8166
+ }
8167
+ announceCloudSession(args) {
8168
+ if (!this.shouldAnnounceCloudSession()) {
8169
+ return;
8170
+ }
8171
+ const fields = [
8172
+ `sessionId=${args.sessionId}`,
8173
+ `workspaceId=${args.workspaceId}`
8174
+ ];
8175
+ if (args.cloudSessionUrl) {
8176
+ fields.push(`url=${args.cloudSessionUrl}`);
8177
+ }
8178
+ process.stderr.write(`[opensteer] cloud session ready ${fields.join(" ")}
8179
+ `);
8180
+ }
8181
+ shouldAnnounceCloudSession() {
8182
+ const cloudConfig = this.config.cloud && typeof this.config.cloud === "object" ? this.config.cloud : null;
8183
+ const announce = cloudConfig?.announce ?? "always";
8184
+ if (announce === "off") {
8185
+ return false;
8186
+ }
8187
+ if (announce === "tty") {
8188
+ return Boolean(process.stderr.isTTY);
8189
+ }
8190
+ return true;
7408
8191
  }
7409
8192
  async launch(options = {}) {
7410
8193
  if (this.pageRef && !this.ownsBrowser) {
@@ -7415,22 +8198,29 @@ var Opensteer = class _Opensteer {
7415
8198
  if (this.pageRef && this.ownsBrowser) {
7416
8199
  return;
7417
8200
  }
7418
- if (this.remote) {
8201
+ if (this.cloud) {
7419
8202
  let actionClient = null;
7420
8203
  let browser = null;
7421
8204
  let sessionId = null;
8205
+ let localRunId = null;
7422
8206
  try {
7423
8207
  try {
7424
- await this.syncLocalSelectorCacheToRemote();
8208
+ await this.syncLocalSelectorCacheToCloud();
7425
8209
  } catch (error) {
7426
8210
  if (this.config.debug) {
7427
8211
  const message = error instanceof Error ? error.message : String(error);
7428
8212
  console.warn(
7429
- `[opensteer] remote selector cache sync failed: ${message}`
8213
+ `[opensteer] cloud selector cache sync failed: ${message}`
7430
8214
  );
7431
8215
  }
7432
8216
  }
7433
- const session3 = await this.remote.sessionClient.create({
8217
+ localRunId = this.cloud.localRunId || buildLocalRunId(this.namespace);
8218
+ this.cloud.localRunId = localRunId;
8219
+ const session3 = await this.cloud.sessionClient.create({
8220
+ cloudSessionContractVersion,
8221
+ sourceType: "local-cloud",
8222
+ clientSessionHint: this.namespace,
8223
+ localRunId,
7434
8224
  name: this.namespace,
7435
8225
  model: this.config.model,
7436
8226
  launchContext: options.context || void 0
@@ -7441,7 +8231,7 @@ var Opensteer = class _Opensteer {
7441
8231
  token: session3.actionToken,
7442
8232
  sessionId: session3.sessionId
7443
8233
  });
7444
- const cdpConnection = await this.remote.cdpClient.connect({
8234
+ const cdpConnection = await this.cloud.cdpClient.connect({
7445
8235
  wsUrl: session3.cdpWsUrl,
7446
8236
  token: session3.cdpToken
7447
8237
  });
@@ -7451,8 +8241,17 @@ var Opensteer = class _Opensteer {
7451
8241
  this.pageRef = cdpConnection.page;
7452
8242
  this.ownsBrowser = true;
7453
8243
  this.snapshotCache = null;
7454
- this.remote.actionClient = actionClient;
7455
- this.remote.sessionId = sessionId;
8244
+ this.cloud.actionClient = actionClient;
8245
+ this.cloud.sessionId = sessionId;
8246
+ this.cloud.cloudSessionUrl = buildCloudSessionUrl(
8247
+ this.cloud.appUrl,
8248
+ session3.cloudSession.sessionId
8249
+ );
8250
+ this.announceCloudSession({
8251
+ sessionId: session3.sessionId,
8252
+ workspaceId: session3.cloudSession.workspaceId,
8253
+ cloudSessionUrl: this.cloud.cloudSessionUrl
8254
+ });
7456
8255
  return;
7457
8256
  } catch (error) {
7458
8257
  if (actionClient) {
@@ -7462,8 +8261,9 @@ var Opensteer = class _Opensteer {
7462
8261
  await browser.close().catch(() => void 0);
7463
8262
  }
7464
8263
  if (sessionId) {
7465
- await this.remote.sessionClient.close(sessionId).catch(() => void 0);
8264
+ await this.cloud.sessionClient.close(sessionId).catch(() => void 0);
7466
8265
  }
8266
+ this.cloud.cloudSessionUrl = null;
7467
8267
  throw error;
7468
8268
  }
7469
8269
  }
@@ -7481,13 +8281,13 @@ var Opensteer = class _Opensteer {
7481
8281
  }
7482
8282
  static from(page, config = {}) {
7483
8283
  const resolvedConfig = resolveConfig(config);
7484
- const modeSelection = resolveModeSelection({
7485
- mode: resolvedConfig.mode
8284
+ const cloudSelection = resolveCloudSelection({
8285
+ cloud: resolvedConfig.cloud
7486
8286
  });
7487
- if (modeSelection.mode === "remote") {
7488
- throw remoteUnsupportedMethodError(
8287
+ if (cloudSelection.cloud) {
8288
+ throw cloudUnsupportedMethodError(
7489
8289
  "Opensteer.from(page)",
7490
- "Opensteer.from(page) is not supported in remote mode."
8290
+ "Opensteer.from(page) is not supported in cloud mode."
7491
8291
  );
7492
8292
  }
7493
8293
  const instance2 = new _Opensteer(config);
@@ -7500,12 +8300,14 @@ var Opensteer = class _Opensteer {
7500
8300
  }
7501
8301
  async close() {
7502
8302
  this.snapshotCache = null;
7503
- if (this.remote) {
7504
- const actionClient = this.remote.actionClient;
7505
- const sessionId = this.remote.sessionId;
8303
+ if (this.cloud) {
8304
+ const actionClient = this.cloud.actionClient;
8305
+ const sessionId = this.cloud.sessionId;
7506
8306
  const browser = this.browser;
7507
- this.remote.actionClient = null;
7508
- this.remote.sessionId = null;
8307
+ this.cloud.actionClient = null;
8308
+ this.cloud.sessionId = null;
8309
+ this.cloud.localRunId = null;
8310
+ this.cloud.cloudSessionUrl = null;
7509
8311
  this.browser = null;
7510
8312
  this.pageRef = null;
7511
8313
  this.contextRef = null;
@@ -7517,7 +8319,7 @@ var Opensteer = class _Opensteer {
7517
8319
  await browser.close().catch(() => void 0);
7518
8320
  }
7519
8321
  if (sessionId) {
7520
- await this.remote.sessionClient.close(sessionId).catch(() => void 0);
8322
+ await this.cloud.sessionClient.close(sessionId).catch(() => void 0);
7521
8323
  }
7522
8324
  return;
7523
8325
  }
@@ -7529,17 +8331,17 @@ var Opensteer = class _Opensteer {
7529
8331
  this.contextRef = null;
7530
8332
  this.ownsBrowser = false;
7531
8333
  }
7532
- async syncLocalSelectorCacheToRemote() {
7533
- if (!this.remote) return;
8334
+ async syncLocalSelectorCacheToCloud() {
8335
+ if (!this.cloud) return;
7534
8336
  const entries = collectLocalSelectorCacheEntries(this.storage);
7535
8337
  if (!entries.length) return;
7536
- await this.remote.sessionClient.importSelectorCache({
8338
+ await this.cloud.sessionClient.importSelectorCache({
7537
8339
  entries
7538
8340
  });
7539
8341
  }
7540
8342
  async goto(url, options) {
7541
- if (this.remote) {
7542
- await this.invokeRemoteActionAndResetCache("goto", { url, options });
8343
+ if (this.cloud) {
8344
+ await this.invokeCloudActionAndResetCache("goto", { url, options });
7543
8345
  return;
7544
8346
  }
7545
8347
  const { waitUntil = "domcontentloaded", ...rest } = options ?? {};
@@ -7548,8 +8350,8 @@ var Opensteer = class _Opensteer {
7548
8350
  this.snapshotCache = null;
7549
8351
  }
7550
8352
  async snapshot(options = {}) {
7551
- if (this.remote) {
7552
- return await this.invokeRemoteActionAndResetCache("snapshot", {
8353
+ if (this.cloud) {
8354
+ return await this.invokeCloudActionAndResetCache("snapshot", {
7553
8355
  options
7554
8356
  });
7555
8357
  }
@@ -7558,8 +8360,8 @@ var Opensteer = class _Opensteer {
7558
8360
  return prepared.cleanedHtml;
7559
8361
  }
7560
8362
  async state() {
7561
- if (this.remote) {
7562
- return await this.invokeRemoteAction("state", {});
8363
+ if (this.cloud) {
8364
+ return await this.invokeCloudAction("state", {});
7563
8365
  }
7564
8366
  const html = await this.snapshot({ mode: "action" });
7565
8367
  return {
@@ -7569,8 +8371,8 @@ var Opensteer = class _Opensteer {
7569
8371
  };
7570
8372
  }
7571
8373
  async screenshot(options = {}) {
7572
- if (this.remote) {
7573
- const b64 = await this.invokeRemoteAction(
8374
+ if (this.cloud) {
8375
+ const b64 = await this.invokeCloudAction(
7574
8376
  "screenshot",
7575
8377
  options
7576
8378
  );
@@ -7584,8 +8386,8 @@ var Opensteer = class _Opensteer {
7584
8386
  });
7585
8387
  }
7586
8388
  async click(options) {
7587
- if (this.remote) {
7588
- return await this.invokeRemoteActionAndResetCache(
8389
+ if (this.cloud) {
8390
+ return await this.invokeCloudActionAndResetCache(
7589
8391
  "click",
7590
8392
  options
7591
8393
  );
@@ -7597,8 +8399,8 @@ var Opensteer = class _Opensteer {
7597
8399
  });
7598
8400
  }
7599
8401
  async dblclick(options) {
7600
- if (this.remote) {
7601
- return await this.invokeRemoteActionAndResetCache(
8402
+ if (this.cloud) {
8403
+ return await this.invokeCloudActionAndResetCache(
7602
8404
  "dblclick",
7603
8405
  options
7604
8406
  );
@@ -7610,8 +8412,8 @@ var Opensteer = class _Opensteer {
7610
8412
  });
7611
8413
  }
7612
8414
  async rightclick(options) {
7613
- if (this.remote) {
7614
- return await this.invokeRemoteActionAndResetCache(
8415
+ if (this.cloud) {
8416
+ return await this.invokeCloudActionAndResetCache(
7615
8417
  "rightclick",
7616
8418
  options
7617
8419
  );
@@ -7623,8 +8425,8 @@ var Opensteer = class _Opensteer {
7623
8425
  });
7624
8426
  }
7625
8427
  async hover(options) {
7626
- if (this.remote) {
7627
- return await this.invokeRemoteActionAndResetCache(
8428
+ if (this.cloud) {
8429
+ return await this.invokeCloudActionAndResetCache(
7628
8430
  "hover",
7629
8431
  options
7630
8432
  );
@@ -7722,8 +8524,8 @@ var Opensteer = class _Opensteer {
7722
8524
  );
7723
8525
  }
7724
8526
  async input(options) {
7725
- if (this.remote) {
7726
- return await this.invokeRemoteActionAndResetCache(
8527
+ if (this.cloud) {
8528
+ return await this.invokeCloudActionAndResetCache(
7727
8529
  "input",
7728
8530
  options
7729
8531
  );
@@ -7752,7 +8554,7 @@ var Opensteer = class _Opensteer {
7752
8554
  await handle.type(options.text);
7753
8555
  }
7754
8556
  if (options.pressEnter) {
7755
- await handle.press("Enter");
8557
+ await handle.press("Enter", { noWaitAfter: true });
7756
8558
  }
7757
8559
  });
7758
8560
  } catch (err) {
@@ -7825,8 +8627,8 @@ var Opensteer = class _Opensteer {
7825
8627
  );
7826
8628
  }
7827
8629
  async select(options) {
7828
- if (this.remote) {
7829
- return await this.invokeRemoteActionAndResetCache(
8630
+ if (this.cloud) {
8631
+ return await this.invokeCloudActionAndResetCache(
7830
8632
  "select",
7831
8633
  options
7832
8634
  );
@@ -7935,8 +8737,8 @@ var Opensteer = class _Opensteer {
7935
8737
  );
7936
8738
  }
7937
8739
  async scroll(options = {}) {
7938
- if (this.remote) {
7939
- return await this.invokeRemoteActionAndResetCache(
8740
+ if (this.cloud) {
8741
+ return await this.invokeCloudActionAndResetCache(
7940
8742
  "scroll",
7941
8743
  options
7942
8744
  );
@@ -8037,14 +8839,14 @@ var Opensteer = class _Opensteer {
8037
8839
  }
8038
8840
  // --- Tab Management ---
8039
8841
  async tabs() {
8040
- if (this.remote) {
8041
- return await this.invokeRemoteAction("tabs", {});
8842
+ if (this.cloud) {
8843
+ return await this.invokeCloudAction("tabs", {});
8042
8844
  }
8043
8845
  return listTabs(this.context, this.page);
8044
8846
  }
8045
8847
  async newTab(url) {
8046
- if (this.remote) {
8047
- return await this.invokeRemoteActionAndResetCache("newTab", {
8848
+ if (this.cloud) {
8849
+ return await this.invokeCloudActionAndResetCache("newTab", {
8048
8850
  url
8049
8851
  });
8050
8852
  }
@@ -8054,8 +8856,8 @@ var Opensteer = class _Opensteer {
8054
8856
  return info;
8055
8857
  }
8056
8858
  async switchTab(index) {
8057
- if (this.remote) {
8058
- await this.invokeRemoteActionAndResetCache("switchTab", { index });
8859
+ if (this.cloud) {
8860
+ await this.invokeCloudActionAndResetCache("switchTab", { index });
8059
8861
  return;
8060
8862
  }
8061
8863
  const page = await switchTab(this.context, index);
@@ -8063,8 +8865,8 @@ var Opensteer = class _Opensteer {
8063
8865
  this.snapshotCache = null;
8064
8866
  }
8065
8867
  async closeTab(index) {
8066
- if (this.remote) {
8067
- await this.invokeRemoteActionAndResetCache("closeTab", { index });
8868
+ if (this.cloud) {
8869
+ await this.invokeCloudActionAndResetCache("closeTab", { index });
8068
8870
  return;
8069
8871
  }
8070
8872
  const newPage = await closeTab(this.context, this.page, index);
@@ -8075,8 +8877,8 @@ var Opensteer = class _Opensteer {
8075
8877
  }
8076
8878
  // --- Cookie Management ---
8077
8879
  async getCookies(url) {
8078
- if (this.remote) {
8079
- return await this.invokeRemoteAction(
8880
+ if (this.cloud) {
8881
+ return await this.invokeCloudAction(
8080
8882
  "getCookies",
8081
8883
  { url }
8082
8884
  );
@@ -8084,41 +8886,41 @@ var Opensteer = class _Opensteer {
8084
8886
  return getCookies(this.context, url);
8085
8887
  }
8086
8888
  async setCookie(cookie) {
8087
- if (this.remote) {
8088
- await this.invokeRemoteAction("setCookie", cookie);
8889
+ if (this.cloud) {
8890
+ await this.invokeCloudAction("setCookie", cookie);
8089
8891
  return;
8090
8892
  }
8091
8893
  return setCookie(this.context, cookie);
8092
8894
  }
8093
8895
  async clearCookies() {
8094
- if (this.remote) {
8095
- await this.invokeRemoteAction("clearCookies", {});
8896
+ if (this.cloud) {
8897
+ await this.invokeCloudAction("clearCookies", {});
8096
8898
  return;
8097
8899
  }
8098
8900
  return clearCookies(this.context);
8099
8901
  }
8100
8902
  async exportCookies(filePath, url) {
8101
- if (this.remote) {
8102
- throw remoteUnsupportedMethodError(
8903
+ if (this.cloud) {
8904
+ throw cloudUnsupportedMethodError(
8103
8905
  "exportCookies",
8104
- "exportCookies() is not supported in remote mode because it depends on local filesystem paths."
8906
+ "exportCookies() is not supported in cloud mode because it depends on local filesystem paths."
8105
8907
  );
8106
8908
  }
8107
8909
  return exportCookies(this.context, filePath, url);
8108
8910
  }
8109
8911
  async importCookies(filePath) {
8110
- if (this.remote) {
8111
- throw remoteUnsupportedMethodError(
8912
+ if (this.cloud) {
8913
+ throw cloudUnsupportedMethodError(
8112
8914
  "importCookies",
8113
- "importCookies() is not supported in remote mode because it depends on local filesystem paths."
8915
+ "importCookies() is not supported in cloud mode because it depends on local filesystem paths."
8114
8916
  );
8115
8917
  }
8116
8918
  return importCookies(this.context, filePath);
8117
8919
  }
8118
8920
  // --- Keyboard Input ---
8119
8921
  async pressKey(key) {
8120
- if (this.remote) {
8121
- await this.invokeRemoteActionAndResetCache("pressKey", { key });
8922
+ if (this.cloud) {
8923
+ await this.invokeCloudActionAndResetCache("pressKey", { key });
8122
8924
  return;
8123
8925
  }
8124
8926
  await this.runWithPostActionWait("pressKey", void 0, async () => {
@@ -8127,8 +8929,8 @@ var Opensteer = class _Opensteer {
8127
8929
  this.snapshotCache = null;
8128
8930
  }
8129
8931
  async type(text) {
8130
- if (this.remote) {
8131
- await this.invokeRemoteActionAndResetCache("type", { text });
8932
+ if (this.cloud) {
8933
+ await this.invokeCloudActionAndResetCache("type", { text });
8132
8934
  return;
8133
8935
  }
8134
8936
  await this.runWithPostActionWait("type", void 0, async () => {
@@ -8138,8 +8940,8 @@ var Opensteer = class _Opensteer {
8138
8940
  }
8139
8941
  // --- Element Info ---
8140
8942
  async getElementText(options) {
8141
- if (this.remote) {
8142
- return await this.invokeRemoteAction("getElementText", options);
8943
+ if (this.cloud) {
8944
+ return await this.invokeCloudAction("getElementText", options);
8143
8945
  }
8144
8946
  return this.executeElementInfoAction(
8145
8947
  "getElementText",
@@ -8152,8 +8954,8 @@ var Opensteer = class _Opensteer {
8152
8954
  );
8153
8955
  }
8154
8956
  async getElementValue(options) {
8155
- if (this.remote) {
8156
- return await this.invokeRemoteAction(
8957
+ if (this.cloud) {
8958
+ return await this.invokeCloudAction(
8157
8959
  "getElementValue",
8158
8960
  options
8159
8961
  );
@@ -8168,8 +8970,8 @@ var Opensteer = class _Opensteer {
8168
8970
  );
8169
8971
  }
8170
8972
  async getElementAttributes(options) {
8171
- if (this.remote) {
8172
- return await this.invokeRemoteAction(
8973
+ if (this.cloud) {
8974
+ return await this.invokeCloudAction(
8173
8975
  "getElementAttributes",
8174
8976
  options
8175
8977
  );
@@ -8190,8 +8992,8 @@ var Opensteer = class _Opensteer {
8190
8992
  );
8191
8993
  }
8192
8994
  async getElementBoundingBox(options) {
8193
- if (this.remote) {
8194
- return await this.invokeRemoteAction(
8995
+ if (this.cloud) {
8996
+ return await this.invokeCloudAction(
8195
8997
  "getElementBoundingBox",
8196
8998
  options
8197
8999
  );
@@ -8206,14 +9008,14 @@ var Opensteer = class _Opensteer {
8206
9008
  );
8207
9009
  }
8208
9010
  async getHtml(selector) {
8209
- if (this.remote) {
8210
- return await this.invokeRemoteAction("getHtml", { selector });
9011
+ if (this.cloud) {
9012
+ return await this.invokeCloudAction("getHtml", { selector });
8211
9013
  }
8212
9014
  return getPageHtml(this.page, selector);
8213
9015
  }
8214
9016
  async getTitle() {
8215
- if (this.remote) {
8216
- return await this.invokeRemoteAction("getTitle", {});
9017
+ if (this.cloud) {
9018
+ return await this.invokeCloudAction("getTitle", {});
8217
9019
  }
8218
9020
  return getPageTitle(this.page);
8219
9021
  }
@@ -8251,10 +9053,10 @@ var Opensteer = class _Opensteer {
8251
9053
  }
8252
9054
  // --- File Upload ---
8253
9055
  async uploadFile(options) {
8254
- if (this.remote) {
8255
- throw remoteUnsupportedMethodError(
9056
+ if (this.cloud) {
9057
+ throw cloudUnsupportedMethodError(
8256
9058
  "uploadFile",
8257
- "uploadFile() is not supported in remote mode because file paths must be accessible on the remote server."
9059
+ "uploadFile() is not supported in cloud mode because file paths must be accessible on the cloud runtime."
8258
9060
  );
8259
9061
  }
8260
9062
  const storageKey = this.resolveStorageKey(options.description);
@@ -8356,15 +9158,15 @@ var Opensteer = class _Opensteer {
8356
9158
  }
8357
9159
  // --- Wait for Text ---
8358
9160
  async waitForText(text, options) {
8359
- if (this.remote) {
8360
- await this.invokeRemoteAction("waitForText", { text, options });
9161
+ if (this.cloud) {
9162
+ await this.invokeCloudAction("waitForText", { text, options });
8361
9163
  return;
8362
9164
  }
8363
9165
  await this.page.getByText(text).first().waitFor({ timeout: options?.timeout ?? 3e4 });
8364
9166
  }
8365
9167
  async extract(options) {
8366
- if (this.remote) {
8367
- return await this.invokeRemoteAction("extract", options);
9168
+ if (this.cloud) {
9169
+ return await this.invokeCloudAction("extract", options);
8368
9170
  }
8369
9171
  const storageKey = this.resolveStorageKey(options.description);
8370
9172
  const schemaHash = options.schema ? computeSchemaHash(options.schema) : null;
@@ -8416,8 +9218,8 @@ var Opensteer = class _Opensteer {
8416
9218
  return inflateDataPathObject(data);
8417
9219
  }
8418
9220
  async extractFromPlan(options) {
8419
- if (this.remote) {
8420
- return await this.invokeRemoteAction(
9221
+ if (this.cloud) {
9222
+ return await this.invokeCloudAction(
8421
9223
  "extractFromPlan",
8422
9224
  options
8423
9225
  );
@@ -8466,10 +9268,10 @@ var Opensteer = class _Opensteer {
8466
9268
  return this.storage;
8467
9269
  }
8468
9270
  clearCache() {
8469
- if (this.remote) {
9271
+ if (this.cloud) {
8470
9272
  this.snapshotCache = null;
8471
- if (!this.remote.actionClient) return;
8472
- void this.invokeRemoteAction("clearCache", {});
9273
+ if (!this.cloud.actionClient) return;
9274
+ void this.invokeCloudAction("clearCache", {});
8473
9275
  return;
8474
9276
  }
8475
9277
  this.storage.clearNamespace();
@@ -9497,6 +10299,16 @@ function getScrollDelta2(options) {
9497
10299
  return { x: 0, y: absoluteAmount };
9498
10300
  }
9499
10301
  }
10302
+ function buildLocalRunId(namespace) {
10303
+ const normalized = namespace.trim() || "default";
10304
+ return `${normalized}-${Date.now().toString(36)}-${(0, import_crypto2.randomUUID)().slice(0, 8)}`;
10305
+ }
10306
+ function buildCloudSessionUrl(appUrl, sessionId) {
10307
+ if (!appUrl) {
10308
+ return null;
10309
+ }
10310
+ return `${appUrl}/browser/${encodeURIComponent(sessionId)}`;
10311
+ }
9500
10312
 
9501
10313
  // src/cli/paths.ts
9502
10314
  var import_os2 = require("os");
@@ -9898,7 +10710,8 @@ async function handleRequest(request, socket) {
9898
10710
  url: instance.page.url(),
9899
10711
  session,
9900
10712
  name: activeNamespace,
9901
- remoteSessionId: instance.getRemoteSessionId() ?? void 0
10713
+ cloudSessionId: instance.getCloudSessionId() ?? void 0,
10714
+ cloudSessionUrl: instance.getCloudSessionUrl() ?? void 0
9902
10715
  }
9903
10716
  });
9904
10717
  } catch (err) {