opensteer 0.6.6 → 0.6.8

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
@@ -421,6 +421,7 @@ var init_extractor = __esm({
421
421
  var index_exports = {};
422
422
  __export(index_exports, {
423
423
  ActionWsClient: () => ActionWsClient,
424
+ BrowserPool: () => BrowserPool,
424
425
  BrowserProfileClient: () => BrowserProfileClient,
425
426
  CdpOverlayCursorRenderer: () => CdpOverlayCursorRenderer,
426
427
  CloudCdpClient: () => CloudCdpClient,
@@ -459,10 +460,15 @@ __export(index_exports, {
459
460
  cleanForFull: () => cleanForFull,
460
461
  cleanForScrollable: () => cleanForScrollable,
461
462
  clearCookies: () => clearCookies,
463
+ clearPersistentProfileSingletons: () => clearPersistentProfileSingletons,
462
464
  cloneElementPath: () => cloneElementPath,
463
465
  closeTab: () => closeTab,
466
+ cloudActionMethods: () => cloudActionMethods,
467
+ cloudErrorCodes: () => cloudErrorCodes,
464
468
  cloudNotLaunchedError: () => cloudNotLaunchedError,
465
469
  cloudSessionContractVersion: () => cloudSessionContractVersion,
470
+ cloudSessionSourceTypes: () => cloudSessionSourceTypes,
471
+ cloudSessionStatuses: () => cloudSessionStatuses,
466
472
  cloudUnsupportedMethodError: () => cloudUnsupportedMethodError,
467
473
  collectLocalSelectorCacheEntries: () => collectLocalSelectorCacheEntries,
468
474
  countArrayItemsWithPath: () => countArrayItemsWithPath,
@@ -471,6 +477,8 @@ __export(index_exports, {
471
477
  createExtractCallback: () => createExtractCallback,
472
478
  createResolveCallback: () => createResolveCallback,
473
479
  createTab: () => createTab,
480
+ detectChromePaths: () => detectChromePaths,
481
+ expandHome: () => expandHome,
474
482
  exportCookies: () => exportCookies,
475
483
  extractArrayRowsWithPaths: () => extractArrayRowsWithPaths,
476
484
  extractArrayWithPaths: () => extractArrayWithPaths,
@@ -481,9 +489,15 @@ __export(index_exports, {
481
489
  getElementText: () => getElementText,
482
490
  getElementValue: () => getElementValue,
483
491
  getModelProvider: () => getModelProvider,
492
+ getOrCreatePersistentProfile: () => getOrCreatePersistentProfile,
484
493
  getPageHtml: () => getPageHtml,
485
494
  getPageTitle: () => getPageTitle,
486
495
  importCookies: () => importCookies,
496
+ isCloudActionMethod: () => isCloudActionMethod,
497
+ isCloudErrorCode: () => isCloudErrorCode,
498
+ isCloudSessionSourceType: () => isCloudSessionSourceType,
499
+ isCloudSessionStatus: () => isCloudSessionStatus,
500
+ listLocalChromeProfiles: () => listLocalChromeProfiles,
487
501
  listTabs: () => listTabs,
488
502
  markInteractiveElements: () => markInteractiveElements,
489
503
  normalizeNamespace: () => normalizeNamespace,
@@ -516,11 +530,8 @@ var import_crypto = require("crypto");
516
530
 
517
531
  // src/browser/pool.ts
518
532
  var import_node_child_process = require("child_process");
519
- var import_node_fs = require("fs");
520
- var import_promises = require("fs/promises");
533
+ var import_promises2 = require("fs/promises");
521
534
  var import_node_net = require("net");
522
- var import_node_os = require("os");
523
- var import_node_path = require("path");
524
535
  var import_playwright = require("playwright");
525
536
 
526
537
  // src/browser/cdp-proxy.ts
@@ -1013,18 +1024,282 @@ function listLocalChromeProfiles(userDataDir = detectChromePaths().defaultUserDa
1013
1024
  }
1014
1025
  }
1015
1026
 
1027
+ // src/browser/persistent-profile.ts
1028
+ var import_node_crypto = require("crypto");
1029
+ var import_node_fs = require("fs");
1030
+ var import_promises = require("fs/promises");
1031
+ var import_node_os = require("os");
1032
+ var import_node_path = require("path");
1033
+ var OPENSTEER_META_FILE = ".opensteer-meta.json";
1034
+ var PROCESS_STARTED_AT_MS = Math.floor(Date.now() - process.uptime() * 1e3);
1035
+ var PROCESS_START_TIME_TOLERANCE_MS = 1e3;
1036
+ var CHROME_SINGLETON_ENTRIES = /* @__PURE__ */ new Set([
1037
+ "SingletonCookie",
1038
+ "SingletonLock",
1039
+ "SingletonSocket",
1040
+ "DevToolsActivePort",
1041
+ "lockfile"
1042
+ ]);
1043
+ var COPY_SKIP_ENTRIES = /* @__PURE__ */ new Set([
1044
+ ...CHROME_SINGLETON_ENTRIES,
1045
+ OPENSTEER_META_FILE
1046
+ ]);
1047
+ var SKIPPED_ROOT_DIRECTORIES = /* @__PURE__ */ new Set([
1048
+ "Crash Reports",
1049
+ "Crashpad",
1050
+ "BrowserMetrics",
1051
+ "GrShaderCache",
1052
+ "ShaderCache",
1053
+ "GraphiteDawnCache",
1054
+ "component_crx_cache",
1055
+ "Crowd Deny",
1056
+ "hyphen-data",
1057
+ "OnDeviceHeadSuggestModel",
1058
+ "OptimizationGuidePredictionModels",
1059
+ "Segmentation Platform",
1060
+ "SmartCardDeviceNames",
1061
+ "WidevineCdm",
1062
+ "pnacl"
1063
+ ]);
1064
+ async function getOrCreatePersistentProfile(sourceUserDataDir, profileDirectory, profilesRootDir = defaultPersistentProfilesRootDir()) {
1065
+ const resolvedSourceUserDataDir = expandHome(sourceUserDataDir);
1066
+ const targetUserDataDir = (0, import_node_path.join)(
1067
+ expandHome(profilesRootDir),
1068
+ buildPersistentProfileKey(resolvedSourceUserDataDir, profileDirectory)
1069
+ );
1070
+ const sourceProfileDir = (0, import_node_path.join)(resolvedSourceUserDataDir, profileDirectory);
1071
+ const metadata = buildPersistentProfileMetadata(
1072
+ resolvedSourceUserDataDir,
1073
+ profileDirectory
1074
+ );
1075
+ await (0, import_promises.mkdir)((0, import_node_path.dirname)(targetUserDataDir), { recursive: true });
1076
+ await cleanOrphanedTempDirs(
1077
+ (0, import_node_path.dirname)(targetUserDataDir),
1078
+ (0, import_node_path.basename)(targetUserDataDir)
1079
+ );
1080
+ if (!(0, import_node_fs.existsSync)(sourceProfileDir)) {
1081
+ throw new Error(
1082
+ `Chrome profile "${profileDirectory}" was not found in "${resolvedSourceUserDataDir}".`
1083
+ );
1084
+ }
1085
+ const created = await createPersistentProfileClone(
1086
+ resolvedSourceUserDataDir,
1087
+ sourceProfileDir,
1088
+ targetUserDataDir,
1089
+ profileDirectory,
1090
+ metadata
1091
+ );
1092
+ await ensurePersistentProfileMetadata(targetUserDataDir, metadata);
1093
+ return {
1094
+ created,
1095
+ userDataDir: targetUserDataDir
1096
+ };
1097
+ }
1098
+ async function clearPersistentProfileSingletons(userDataDir) {
1099
+ await Promise.all(
1100
+ [...CHROME_SINGLETON_ENTRIES].map(
1101
+ (entry) => (0, import_promises.rm)((0, import_node_path.join)(userDataDir, entry), {
1102
+ force: true,
1103
+ recursive: true
1104
+ }).catch(() => void 0)
1105
+ )
1106
+ );
1107
+ }
1108
+ function buildPersistentProfileKey(sourceUserDataDir, profileDirectory) {
1109
+ const hash = (0, import_node_crypto.createHash)("sha256").update(`${sourceUserDataDir}\0${profileDirectory}`).digest("hex").slice(0, 16);
1110
+ const sourceLabel = sanitizePathSegment((0, import_node_path.basename)(sourceUserDataDir) || "user-data");
1111
+ const profileLabel = sanitizePathSegment(profileDirectory || "Default");
1112
+ return `${sourceLabel}-${profileLabel}-${hash}`;
1113
+ }
1114
+ function defaultPersistentProfilesRootDir() {
1115
+ return (0, import_node_path.join)((0, import_node_os.homedir)(), ".opensteer", "real-browser-profiles");
1116
+ }
1117
+ function sanitizePathSegment(value) {
1118
+ const sanitized = value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/-+/g, "-");
1119
+ return sanitized.replace(/^-|-$/g, "") || "profile";
1120
+ }
1121
+ function isProfileDirectory(userDataDir, entry) {
1122
+ return (0, import_node_fs.existsSync)((0, import_node_path.join)(userDataDir, entry, "Preferences"));
1123
+ }
1124
+ async function copyRootLevelEntries(sourceUserDataDir, targetUserDataDir, targetProfileDirectory) {
1125
+ let entries;
1126
+ try {
1127
+ entries = await (0, import_promises.readdir)(sourceUserDataDir);
1128
+ } catch {
1129
+ return;
1130
+ }
1131
+ const copyTasks = [];
1132
+ for (const entry of entries) {
1133
+ if (COPY_SKIP_ENTRIES.has(entry)) continue;
1134
+ if (entry === targetProfileDirectory) continue;
1135
+ const sourcePath = (0, import_node_path.join)(sourceUserDataDir, entry);
1136
+ const targetPath = (0, import_node_path.join)(targetUserDataDir, entry);
1137
+ if ((0, import_node_fs.existsSync)(targetPath)) continue;
1138
+ let entryStat;
1139
+ try {
1140
+ entryStat = await (0, import_promises.stat)(sourcePath);
1141
+ } catch {
1142
+ continue;
1143
+ }
1144
+ if (entryStat.isFile()) {
1145
+ copyTasks.push((0, import_promises.copyFile)(sourcePath, targetPath).catch(() => void 0));
1146
+ } else if (entryStat.isDirectory()) {
1147
+ if (isProfileDirectory(sourceUserDataDir, entry)) continue;
1148
+ if (SKIPPED_ROOT_DIRECTORIES.has(entry)) continue;
1149
+ copyTasks.push(
1150
+ (0, import_promises.cp)(sourcePath, targetPath, { recursive: true }).catch(
1151
+ () => void 0
1152
+ )
1153
+ );
1154
+ }
1155
+ }
1156
+ await Promise.all(copyTasks);
1157
+ }
1158
+ async function writePersistentProfileMetadata(userDataDir, metadata) {
1159
+ await (0, import_promises.writeFile)(
1160
+ (0, import_node_path.join)(userDataDir, OPENSTEER_META_FILE),
1161
+ JSON.stringify(metadata, null, 2)
1162
+ );
1163
+ }
1164
+ function buildPersistentProfileMetadata(sourceUserDataDir, profileDirectory) {
1165
+ return {
1166
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1167
+ profileDirectory,
1168
+ source: sourceUserDataDir
1169
+ };
1170
+ }
1171
+ async function createPersistentProfileClone(sourceUserDataDir, sourceProfileDir, targetUserDataDir, profileDirectory, metadata) {
1172
+ if ((0, import_node_fs.existsSync)(targetUserDataDir)) {
1173
+ return false;
1174
+ }
1175
+ const tempUserDataDir = await (0, import_promises.mkdtemp)(
1176
+ buildPersistentProfileTempDirPrefix(targetUserDataDir)
1177
+ );
1178
+ let published = false;
1179
+ try {
1180
+ await (0, import_promises.cp)(sourceProfileDir, (0, import_node_path.join)(tempUserDataDir, profileDirectory), {
1181
+ recursive: true
1182
+ });
1183
+ await copyRootLevelEntries(
1184
+ sourceUserDataDir,
1185
+ tempUserDataDir,
1186
+ profileDirectory
1187
+ );
1188
+ await writePersistentProfileMetadata(tempUserDataDir, metadata);
1189
+ try {
1190
+ await (0, import_promises.rename)(tempUserDataDir, targetUserDataDir);
1191
+ } catch (error) {
1192
+ if (wasProfilePublishedByAnotherProcess(error, targetUserDataDir)) {
1193
+ return false;
1194
+ }
1195
+ throw error;
1196
+ }
1197
+ published = true;
1198
+ return true;
1199
+ } finally {
1200
+ if (!published) {
1201
+ await (0, import_promises.rm)(tempUserDataDir, {
1202
+ recursive: true,
1203
+ force: true
1204
+ }).catch(() => void 0);
1205
+ }
1206
+ }
1207
+ }
1208
+ async function ensurePersistentProfileMetadata(userDataDir, metadata) {
1209
+ if ((0, import_node_fs.existsSync)((0, import_node_path.join)(userDataDir, OPENSTEER_META_FILE))) {
1210
+ return;
1211
+ }
1212
+ await writePersistentProfileMetadata(userDataDir, metadata);
1213
+ }
1214
+ function wasProfilePublishedByAnotherProcess(error, targetUserDataDir) {
1215
+ const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
1216
+ return (0, import_node_fs.existsSync)(targetUserDataDir) && (code === "EEXIST" || code === "ENOTEMPTY" || code === "EPERM");
1217
+ }
1218
+ function buildPersistentProfileTempDirPrefix(targetUserDataDir) {
1219
+ return (0, import_node_path.join)(
1220
+ (0, import_node_path.dirname)(targetUserDataDir),
1221
+ `${(0, import_node_path.basename)(targetUserDataDir)}-tmp-${process.pid}-${PROCESS_STARTED_AT_MS}-`
1222
+ );
1223
+ }
1224
+ async function cleanOrphanedTempDirs(profilesDir, targetBaseName) {
1225
+ let entries;
1226
+ try {
1227
+ entries = await (0, import_promises.readdir)(profilesDir, {
1228
+ encoding: "utf8",
1229
+ withFileTypes: true
1230
+ });
1231
+ } catch {
1232
+ return;
1233
+ }
1234
+ const tempDirPrefix = `${targetBaseName}-tmp-`;
1235
+ await Promise.all(
1236
+ entries.map(async (entry) => {
1237
+ if (!entry.isDirectory() || !entry.name.startsWith(tempDirPrefix)) {
1238
+ return;
1239
+ }
1240
+ if (isTempDirOwnedByLiveProcess(entry.name, tempDirPrefix)) {
1241
+ return;
1242
+ }
1243
+ await (0, import_promises.rm)((0, import_node_path.join)(profilesDir, entry.name), {
1244
+ recursive: true,
1245
+ force: true
1246
+ }).catch(() => void 0);
1247
+ })
1248
+ );
1249
+ }
1250
+ function isTempDirOwnedByLiveProcess(tempDirName, tempDirPrefix) {
1251
+ const owner = parseTempDirOwner(tempDirName, tempDirPrefix);
1252
+ if (!owner) {
1253
+ return false;
1254
+ }
1255
+ if (owner.pid === process.pid && Math.abs(owner.processStartedAtMs - PROCESS_STARTED_AT_MS) <= PROCESS_START_TIME_TOLERANCE_MS) {
1256
+ return true;
1257
+ }
1258
+ return isProcessRunning(owner.pid);
1259
+ }
1260
+ function parseTempDirOwner(tempDirName, tempDirPrefix) {
1261
+ const remainder = tempDirName.slice(tempDirPrefix.length);
1262
+ const firstDashIndex = remainder.indexOf("-");
1263
+ const secondDashIndex = firstDashIndex === -1 ? -1 : remainder.indexOf("-", firstDashIndex + 1);
1264
+ if (firstDashIndex === -1 || secondDashIndex === -1) {
1265
+ return null;
1266
+ }
1267
+ const pid = Number.parseInt(remainder.slice(0, firstDashIndex), 10);
1268
+ const processStartedAtMs = Number.parseInt(
1269
+ remainder.slice(firstDashIndex + 1, secondDashIndex),
1270
+ 10
1271
+ );
1272
+ if (!Number.isInteger(pid) || pid <= 0) {
1273
+ return null;
1274
+ }
1275
+ if (!Number.isInteger(processStartedAtMs) || processStartedAtMs <= 0) {
1276
+ return null;
1277
+ }
1278
+ return { pid, processStartedAtMs };
1279
+ }
1280
+ function isProcessRunning(pid) {
1281
+ try {
1282
+ process.kill(pid, 0);
1283
+ return true;
1284
+ } catch (error) {
1285
+ const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
1286
+ return code !== "ESRCH";
1287
+ }
1288
+ }
1289
+
1016
1290
  // src/browser/pool.ts
1017
1291
  var BrowserPool = class {
1018
1292
  browser = null;
1019
1293
  cdpProxy = null;
1020
1294
  launchedProcess = null;
1021
- tempUserDataDir = null;
1295
+ managedUserDataDir = null;
1296
+ persistentProfile = false;
1022
1297
  defaults;
1023
1298
  constructor(defaults = {}) {
1024
1299
  this.defaults = defaults;
1025
1300
  }
1026
1301
  async launch(options = {}) {
1027
- if (this.browser || this.cdpProxy || this.launchedProcess || this.tempUserDataDir) {
1302
+ if (this.browser || this.cdpProxy || this.launchedProcess || this.managedUserDataDir) {
1028
1303
  await this.close();
1029
1304
  }
1030
1305
  const mode = options.mode ?? this.defaults.mode ?? "chromium";
@@ -1074,11 +1349,13 @@ var BrowserPool = class {
1074
1349
  const browser = this.browser;
1075
1350
  const cdpProxy = this.cdpProxy;
1076
1351
  const launchedProcess = this.launchedProcess;
1077
- const tempUserDataDir = this.tempUserDataDir;
1352
+ const managedUserDataDir = this.managedUserDataDir;
1353
+ const persistentProfile = this.persistentProfile;
1078
1354
  this.browser = null;
1079
1355
  this.cdpProxy = null;
1080
1356
  this.launchedProcess = null;
1081
- this.tempUserDataDir = null;
1357
+ this.managedUserDataDir = null;
1358
+ this.persistentProfile = false;
1082
1359
  try {
1083
1360
  if (browser) {
1084
1361
  await browser.close().catch(() => void 0);
@@ -1086,8 +1363,8 @@ var BrowserPool = class {
1086
1363
  } finally {
1087
1364
  cdpProxy?.close();
1088
1365
  await killProcessTree(launchedProcess);
1089
- if (tempUserDataDir) {
1090
- await (0, import_promises.rm)(tempUserDataDir, {
1366
+ if (managedUserDataDir && !persistentProfile) {
1367
+ await (0, import_promises2.rm)(managedUserDataDir, {
1091
1368
  recursive: true,
1092
1369
  force: true
1093
1370
  }).catch(() => void 0);
@@ -1136,10 +1413,11 @@ var BrowserPool = class {
1136
1413
  options.userDataDir ?? chromePaths.defaultUserDataDir
1137
1414
  );
1138
1415
  const profileDirectory = options.profileDirectory ?? "Default";
1139
- const tempUserDataDir = await cloneProfileToTempDir(
1416
+ const persistentProfile = await getOrCreatePersistentProfile(
1140
1417
  sourceUserDataDir,
1141
1418
  profileDirectory
1142
1419
  );
1420
+ await clearPersistentProfileSingletons(persistentProfile.userDataDir);
1143
1421
  const debugPort = await reserveDebugPort();
1144
1422
  const headless = resolveLaunchHeadless(
1145
1423
  "real",
@@ -1147,7 +1425,7 @@ var BrowserPool = class {
1147
1425
  this.defaults.headless
1148
1426
  );
1149
1427
  const launchArgs = buildRealBrowserLaunchArgs({
1150
- userDataDir: tempUserDataDir,
1428
+ userDataDir: persistentProfile.userDataDir,
1151
1429
  profileDirectory,
1152
1430
  debugPort,
1153
1431
  headless
@@ -1167,24 +1445,22 @@ var BrowserPool = class {
1167
1445
  timeout: options.timeout ?? 3e4
1168
1446
  });
1169
1447
  const { context, page } = await createOwnedBrowserContextAndPage(
1170
- browser,
1171
- {
1172
- headless,
1173
- initialUrl: options.initialUrl,
1174
- timeoutMs: options.timeout ?? 3e4
1175
- }
1448
+ browser
1176
1449
  );
1450
+ if (options.initialUrl) {
1451
+ await page.goto(options.initialUrl, {
1452
+ waitUntil: "domcontentloaded",
1453
+ timeout: options.timeout ?? 3e4
1454
+ });
1455
+ }
1177
1456
  this.browser = browser;
1178
1457
  this.launchedProcess = processHandle;
1179
- this.tempUserDataDir = tempUserDataDir;
1458
+ this.managedUserDataDir = persistentProfile.userDataDir;
1459
+ this.persistentProfile = true;
1180
1460
  return { browser, context, page, isExternal: false };
1181
1461
  } catch (error) {
1182
1462
  await browser?.close().catch(() => void 0);
1183
1463
  await killProcessTree(processHandle);
1184
- await (0, import_promises.rm)(tempUserDataDir, {
1185
- recursive: true,
1186
- force: true
1187
- }).catch(() => void 0);
1188
1464
  throw error;
1189
1465
  }
1190
1466
  }
@@ -1207,8 +1483,7 @@ var BrowserPool = class {
1207
1483
  };
1208
1484
  async function pickBrowserContextAndPage(browser) {
1209
1485
  const context = getPrimaryBrowserContext(browser);
1210
- const pages = context.pages();
1211
- const page = pages.find((candidate) => isInspectablePageUrl2(candidate.url())) || pages[0] || await context.newPage();
1486
+ const page = await getAttachedPageOrCreate(context);
1212
1487
  return { context, page };
1213
1488
  }
1214
1489
  function resolveLaunchHeadless(mode, requestedHeadless, defaultHeadless) {
@@ -1220,77 +1495,31 @@ function resolveLaunchHeadless(mode, requestedHeadless, defaultHeadless) {
1220
1495
  }
1221
1496
  return mode === "real";
1222
1497
  }
1223
- async function createOwnedBrowserContextAndPage(browser, options) {
1498
+ async function createOwnedBrowserContextAndPage(browser) {
1224
1499
  const context = getPrimaryBrowserContext(browser);
1225
- const page = await createOwnedBrowserPage(browser, context, options);
1500
+ const page = await getExistingPageOrCreate(context);
1226
1501
  return { context, page };
1227
1502
  }
1228
- async function createOwnedBrowserPage(browser, context, options) {
1229
- const targetUrl = options.initialUrl ?? "about:blank";
1230
- const existingPages = new Set(context.pages());
1231
- const browserSession = await browser.newBrowserCDPSession();
1232
- try {
1233
- const { targetId } = await browserSession.send("Target.createTarget", {
1234
- url: targetUrl,
1235
- newWindow: !options.headless
1236
- });
1237
- await browserSession.send("Target.activateTarget", { targetId }).catch(() => void 0);
1238
- const page = await waitForOwnedBrowserPage(context, {
1239
- existingPages,
1240
- targetUrl,
1241
- timeoutMs: options.timeoutMs
1242
- });
1243
- if (targetUrl !== "about:blank") {
1244
- await page.waitForLoadState("domcontentloaded", {
1245
- timeout: options.timeoutMs
1246
- });
1247
- }
1248
- await closeDisposableStartupTargets(browserSession, targetId);
1249
- return page;
1250
- } finally {
1251
- await browserSession.detach().catch(() => void 0);
1252
- }
1253
- }
1254
- async function closeDisposableStartupTargets(browserSession, preservedTargetId) {
1255
- const response = await browserSession.send("Target.getTargets").catch(() => null);
1256
- if (!response) {
1257
- return;
1503
+ async function getAttachedPageOrCreate(context) {
1504
+ const pages = context.pages();
1505
+ const inspectablePage = pages.find(
1506
+ (candidate) => isInspectablePageUrl2(safePageUrl(candidate))
1507
+ );
1508
+ if (inspectablePage) {
1509
+ return inspectablePage;
1258
1510
  }
1259
- for (const targetInfo of response.targetInfos) {
1260
- if (targetInfo.targetId === preservedTargetId || targetInfo.type !== "page" || !isDisposableStartupPageUrl(targetInfo.url)) {
1261
- continue;
1262
- }
1263
- await browserSession.send("Target.closeTarget", { targetId: targetInfo.targetId }).catch(() => void 0);
1511
+ const attachedPage = pages[0];
1512
+ if (attachedPage) {
1513
+ return attachedPage;
1264
1514
  }
1515
+ return await context.newPage();
1265
1516
  }
1266
- async function waitForOwnedBrowserPage(context, options) {
1267
- const deadline = Date.now() + options.timeoutMs;
1268
- let fallbackPage = null;
1269
- while (Date.now() < deadline) {
1270
- for (const candidate of context.pages()) {
1271
- if (options.existingPages.has(candidate)) {
1272
- continue;
1273
- }
1274
- const url = candidate.url();
1275
- if (!isInspectablePageUrl2(url)) {
1276
- continue;
1277
- }
1278
- fallbackPage ??= candidate;
1279
- if (options.targetUrl === "about:blank") {
1280
- return candidate;
1281
- }
1282
- if (pageLooselyMatchesUrl(url, options.targetUrl)) {
1283
- return candidate;
1284
- }
1285
- }
1286
- await sleep(100);
1287
- }
1288
- if (fallbackPage) {
1289
- return fallbackPage;
1517
+ async function getExistingPageOrCreate(context) {
1518
+ const existingPage = context.pages()[0];
1519
+ if (existingPage) {
1520
+ return existingPage;
1290
1521
  }
1291
- throw new Error(
1292
- `Chrome created a target for ${options.targetUrl}, but Playwright did not expose the page in time.`
1293
- );
1522
+ return await context.newPage();
1294
1523
  }
1295
1524
  function getPrimaryBrowserContext(browser) {
1296
1525
  const contexts = browser.contexts();
@@ -1304,19 +1533,11 @@ function getPrimaryBrowserContext(browser) {
1304
1533
  function isInspectablePageUrl2(url) {
1305
1534
  return url === "about:blank" || url.startsWith("http://") || url.startsWith("https://");
1306
1535
  }
1307
- function isDisposableStartupPageUrl(url) {
1308
- return url === "about:blank" || url === "chrome://newtab/" || url === "chrome://new-tab-page/";
1309
- }
1310
- function pageLooselyMatchesUrl(currentUrl, initialUrl) {
1536
+ function safePageUrl(page) {
1311
1537
  try {
1312
- const current = new URL(currentUrl);
1313
- const requested = new URL(initialUrl);
1314
- if (current.href === requested.href) {
1315
- return true;
1316
- }
1317
- return current.hostname === requested.hostname && current.pathname === requested.pathname;
1538
+ return page.url();
1318
1539
  } catch {
1319
- return currentUrl === initialUrl;
1540
+ return "";
1320
1541
  }
1321
1542
  }
1322
1543
  function normalizeDiscoveryUrl(cdpUrl) {
@@ -1396,38 +1617,12 @@ async function reserveDebugPort() {
1396
1617
  });
1397
1618
  });
1398
1619
  }
1399
- async function cloneProfileToTempDir(userDataDir, profileDirectory) {
1400
- const resolvedUserDataDir = expandHome(userDataDir);
1401
- const tempUserDataDir = await (0, import_promises.mkdtemp)(
1402
- (0, import_node_path.join)((0, import_node_os.tmpdir)(), "opensteer-real-browser-")
1403
- );
1404
- const sourceProfileDir = (0, import_node_path.join)(resolvedUserDataDir, profileDirectory);
1405
- const targetProfileDir = (0, import_node_path.join)(tempUserDataDir, profileDirectory);
1406
- if ((0, import_node_fs.existsSync)(sourceProfileDir)) {
1407
- await (0, import_promises.cp)(sourceProfileDir, targetProfileDir, {
1408
- recursive: true
1409
- });
1410
- } else {
1411
- await (0, import_promises.mkdir)(targetProfileDir, {
1412
- recursive: true
1413
- });
1414
- }
1415
- const localStatePath = (0, import_node_path.join)(resolvedUserDataDir, "Local State");
1416
- if ((0, import_node_fs.existsSync)(localStatePath)) {
1417
- await (0, import_promises.copyFile)(localStatePath, (0, import_node_path.join)(tempUserDataDir, "Local State"));
1418
- }
1419
- return tempUserDataDir;
1420
- }
1421
1620
  function buildRealBrowserLaunchArgs(options) {
1422
1621
  const args = [
1423
1622
  `--user-data-dir=${options.userDataDir}`,
1424
1623
  `--profile-directory=${options.profileDirectory}`,
1425
1624
  `--remote-debugging-port=${options.debugPort}`,
1426
- "--no-first-run",
1427
- "--no-default-browser-check",
1428
- "--disable-background-networking",
1429
- "--disable-sync",
1430
- "--disable-popup-blocking"
1625
+ "--disable-blink-features=AutomationControlled"
1431
1626
  ];
1432
1627
  if (options.headless) {
1433
1628
  args.push("--headless=new");
@@ -7147,7 +7342,7 @@ async function closeTab(context, activePage, index) {
7147
7342
  }
7148
7343
 
7149
7344
  // src/actions/cookies.ts
7150
- var import_promises2 = require("fs/promises");
7345
+ var import_promises3 = require("fs/promises");
7151
7346
  async function getCookies(context, url) {
7152
7347
  return context.cookies(url ? [url] : void 0);
7153
7348
  }
@@ -7159,10 +7354,10 @@ async function clearCookies(context) {
7159
7354
  }
7160
7355
  async function exportCookies(context, filePath, url) {
7161
7356
  const cookies = await context.cookies(url ? [url] : void 0);
7162
- await (0, import_promises2.writeFile)(filePath, JSON.stringify(cookies, null, 2), "utf-8");
7357
+ await (0, import_promises3.writeFile)(filePath, JSON.stringify(cookies, null, 2), "utf-8");
7163
7358
  }
7164
7359
  async function importCookies(context, filePath) {
7165
- const raw = await (0, import_promises2.readFile)(filePath, "utf-8");
7360
+ const raw = await (0, import_promises3.readFile)(filePath, "utf-8");
7166
7361
  const cookies = JSON.parse(raw);
7167
7362
  await context.addCookies(cookies);
7168
7363
  }
@@ -7913,31 +8108,31 @@ function buildVariantDescriptorFromCluster(descriptors) {
7913
8108
  const keyStats = /* @__PURE__ */ new Map();
7914
8109
  for (const descriptor of descriptors) {
7915
8110
  for (const field of descriptor.fields) {
7916
- const stat = keyStats.get(field.path) || {
8111
+ const stat2 = keyStats.get(field.path) || {
7917
8112
  indices: /* @__PURE__ */ new Set(),
7918
8113
  pathNodes: [],
7919
8114
  attributes: [],
7920
8115
  sources: []
7921
8116
  };
7922
- stat.indices.add(descriptor.index);
8117
+ stat2.indices.add(descriptor.index);
7923
8118
  if (isPersistedValueNode(field.node)) {
7924
- stat.pathNodes.push(field.node.$path);
7925
- stat.attributes.push(field.node.attribute);
8119
+ stat2.pathNodes.push(field.node.$path);
8120
+ stat2.attributes.push(field.node.attribute);
7926
8121
  } else if (isPersistedSourceNode(field.node)) {
7927
- stat.sources.push("current_url");
8122
+ stat2.sources.push("current_url");
7928
8123
  }
7929
- keyStats.set(field.path, stat);
8124
+ keyStats.set(field.path, stat2);
7930
8125
  }
7931
8126
  }
7932
8127
  const mergedFields = [];
7933
- for (const [fieldPath, stat] of keyStats) {
7934
- if (stat.indices.size < threshold) continue;
7935
- if (stat.pathNodes.length >= threshold) {
8128
+ for (const [fieldPath, stat2] of keyStats) {
8129
+ if (stat2.indices.size < threshold) continue;
8130
+ if (stat2.pathNodes.length >= threshold) {
7936
8131
  let mergedFieldPath = null;
7937
- if (stat.pathNodes.length === 1) {
7938
- mergedFieldPath = sanitizeElementPath(stat.pathNodes[0]);
8132
+ if (stat2.pathNodes.length === 1) {
8133
+ mergedFieldPath = sanitizeElementPath(stat2.pathNodes[0]);
7939
8134
  } else {
7940
- mergedFieldPath = mergeElementPathsByMajority(stat.pathNodes);
8135
+ mergedFieldPath = mergeElementPathsByMajority(stat2.pathNodes);
7941
8136
  }
7942
8137
  if (!mergedFieldPath) continue;
7943
8138
  if (clusterSize === 1) {
@@ -7950,17 +8145,17 @@ function buildVariantDescriptorFromCluster(descriptors) {
7950
8145
  mergedFieldPath,
7951
8146
  "field"
7952
8147
  );
7953
- const attrThreshold = stat.pathNodes.length === 1 ? 1 : majorityThreshold(stat.pathNodes.length);
8148
+ const attrThreshold = stat2.pathNodes.length === 1 ? 1 : majorityThreshold(stat2.pathNodes.length);
7954
8149
  mergedFields.push({
7955
8150
  path: fieldPath,
7956
8151
  node: createValueNode({
7957
8152
  elementPath: mergedFieldPath,
7958
- attribute: pickModeString(stat.attributes, attrThreshold)
8153
+ attribute: pickModeString(stat2.attributes, attrThreshold)
7959
8154
  })
7960
8155
  });
7961
8156
  continue;
7962
8157
  }
7963
- const dominantSource = pickModeString(stat.sources, threshold);
8158
+ const dominantSource = pickModeString(stat2.sources, threshold);
7964
8159
  if (dominantSource === "current_url") {
7965
8160
  mergedFields.push({
7966
8161
  path: fieldPath,
@@ -8829,7 +9024,99 @@ function stripPositionClauses2(path5) {
8829
9024
  }
8830
9025
 
8831
9026
  // src/cloud/contracts.ts
9027
+ var cloudActionMethods = [
9028
+ "goto",
9029
+ "snapshot",
9030
+ "screenshot",
9031
+ "state",
9032
+ "click",
9033
+ "dblclick",
9034
+ "rightclick",
9035
+ "hover",
9036
+ "input",
9037
+ "select",
9038
+ "scroll",
9039
+ "tabs",
9040
+ "newTab",
9041
+ "switchTab",
9042
+ "closeTab",
9043
+ "getCookies",
9044
+ "setCookie",
9045
+ "clearCookies",
9046
+ "pressKey",
9047
+ "type",
9048
+ "getElementText",
9049
+ "getElementValue",
9050
+ "getElementAttributes",
9051
+ "getElementBoundingBox",
9052
+ "getHtml",
9053
+ "getTitle",
9054
+ "uploadFile",
9055
+ "exportCookies",
9056
+ "importCookies",
9057
+ "waitForText",
9058
+ "extract",
9059
+ "extractFromPlan",
9060
+ "clearCache"
9061
+ ];
9062
+ var cloudErrorCodes = [
9063
+ "CLOUD_AUTH_FAILED",
9064
+ "CLOUD_SESSION_NOT_FOUND",
9065
+ "CLOUD_SESSION_CLOSED",
9066
+ "CLOUD_UNSUPPORTED_METHOD",
9067
+ "CLOUD_INVALID_REQUEST",
9068
+ "CLOUD_MODEL_NOT_ALLOWED",
9069
+ "CLOUD_ACTION_FAILED",
9070
+ "CLOUD_CAPACITY_EXHAUSTED",
9071
+ "CLOUD_RUNTIME_UNAVAILABLE",
9072
+ "CLOUD_RUNTIME_MISMATCH",
9073
+ "CLOUD_SESSION_STALE",
9074
+ "CLOUD_CONTROL_PLANE_ERROR",
9075
+ "CLOUD_CONTRACT_MISMATCH",
9076
+ "CLOUD_PROXY_UNAVAILABLE",
9077
+ "CLOUD_PROXY_REQUIRED",
9078
+ "CLOUD_BILLING_LIMIT_REACHED",
9079
+ "CLOUD_RATE_LIMITED",
9080
+ "CLOUD_BROWSER_PROFILE_NOT_FOUND",
9081
+ "CLOUD_BROWSER_PROFILE_BUSY",
9082
+ "CLOUD_BROWSER_PROFILE_DISABLED",
9083
+ "CLOUD_BROWSER_PROFILE_PROXY_UNAVAILABLE",
9084
+ "CLOUD_BROWSER_PROFILE_SYNC_FAILED",
9085
+ "CLOUD_INTERNAL"
9086
+ ];
9087
+ var cloudSessionStatuses = [
9088
+ "provisioning",
9089
+ "active",
9090
+ "closing",
9091
+ "closed",
9092
+ "failed"
9093
+ ];
8832
9094
  var cloudSessionContractVersion = "v3";
9095
+ var cloudSessionSourceTypes = [
9096
+ "agent-thread",
9097
+ "agent-run",
9098
+ "project-agent-run",
9099
+ "local-cloud",
9100
+ "manual"
9101
+ ];
9102
+ var cloudActionMethodSet = new Set(cloudActionMethods);
9103
+ var cloudErrorCodeSet = new Set(cloudErrorCodes);
9104
+ var cloudSessionSourceTypeSet = new Set(
9105
+ cloudSessionSourceTypes
9106
+ );
9107
+ var cloudSessionStatusSet = new Set(cloudSessionStatuses);
9108
+ function isCloudActionMethod(value) {
9109
+ return typeof value === "string" && cloudActionMethodSet.has(value);
9110
+ }
9111
+ function isCloudErrorCode(value) {
9112
+ return typeof value === "string" && cloudErrorCodeSet.has(value);
9113
+ }
9114
+ function isCloudSessionSourceType(value) {
9115
+ return typeof value === "string" && cloudSessionSourceTypeSet.has(value);
9116
+ }
9117
+ function isCloudSessionStatus(value) {
9118
+ return typeof value === "string" && cloudSessionStatusSet.has(value);
9119
+ }
8833
9120
 
8834
9121
  // src/cloud/action-ws-client.ts
8835
9122
  var import_ws2 = __toESM(require("ws"), 1);
@@ -8860,6 +9147,13 @@ function cloudNotLaunchedError() {
8860
9147
  );
8861
9148
  }
8862
9149
 
9150
+ // src/cloud/ws-url.ts
9151
+ function withTokenQuery(wsUrl, token) {
9152
+ const url = new URL(wsUrl);
9153
+ url.searchParams.set("token", token);
9154
+ return url.toString();
9155
+ }
9156
+
8863
9157
  // src/cloud/action-ws-client.ts
8864
9158
  var ActionWsClient = class _ActionWsClient {
8865
9159
  ws;
@@ -8988,11 +9282,6 @@ function rawDataToUtf8(raw) {
8988
9282
  if (Array.isArray(raw)) return Buffer.concat(raw).toString("utf8");
8989
9283
  return raw.toString("utf8");
8990
9284
  }
8991
- function withTokenQuery(wsUrl, token) {
8992
- const url = new URL(wsUrl);
8993
- url.searchParams.set("token", token);
8994
- return url.toString();
8995
- }
8996
9285
 
8997
9286
  // src/cloud/local-cache-sync.ts
8998
9287
  var import_fs4 = __toESM(require("fs"), 1);
@@ -9132,7 +9421,7 @@ function dedupeNewest(entries) {
9132
9421
  var import_playwright2 = require("playwright");
9133
9422
  var CloudCdpClient = class {
9134
9423
  async connect(args) {
9135
- const endpoint = withTokenQuery2(args.wsUrl, args.token);
9424
+ const endpoint = withTokenQuery(args.wsUrl, args.token);
9136
9425
  let browser;
9137
9426
  try {
9138
9427
  browser = await import_playwright2.chromium.connectOverCDP(endpoint);
@@ -9161,7 +9450,7 @@ function selectPreferredContextPage(browser, contexts) {
9161
9450
  let aboutBlankCandidate = null;
9162
9451
  for (const context of contexts) {
9163
9452
  for (const page of context.pages()) {
9164
- const url = safePageUrl(page);
9453
+ const url = safePageUrl2(page);
9165
9454
  if (!isInternalOrEmptyUrl(url)) {
9166
9455
  return { browser, context, page };
9167
9456
  }
@@ -9172,7 +9461,7 @@ function selectPreferredContextPage(browser, contexts) {
9172
9461
  }
9173
9462
  return aboutBlankCandidate;
9174
9463
  }
9175
- function safePageUrl(page) {
9464
+ function safePageUrl2(page) {
9176
9465
  try {
9177
9466
  return page.url();
9178
9467
  } catch {
@@ -9184,11 +9473,6 @@ function isInternalOrEmptyUrl(url) {
9184
9473
  if (url === "about:blank") return true;
9185
9474
  return url.startsWith("chrome://") || url.startsWith("devtools://") || url.startsWith("edge://");
9186
9475
  }
9187
- function withTokenQuery2(wsUrl, token) {
9188
- const url = new URL(wsUrl);
9189
- url.searchParams.set("token", token);
9190
- return url.toString();
9191
- }
9192
9476
 
9193
9477
  // src/utils/strip-trailing-slashes.ts
9194
9478
  function stripTrailingSlashes(value) {
@@ -9225,10 +9509,7 @@ async function parseCloudHttpError(response) {
9225
9509
  return new OpensteerCloudError(code, message, response.status, body?.details);
9226
9510
  }
9227
9511
  function toCloudErrorCode(code) {
9228
- 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" || code === "CLOUD_PROXY_UNAVAILABLE" || code === "CLOUD_PROXY_REQUIRED" || code === "CLOUD_BILLING_LIMIT_REACHED" || code === "CLOUD_RATE_LIMITED" || code === "CLOUD_BROWSER_PROFILE_NOT_FOUND" || code === "CLOUD_BROWSER_PROFILE_BUSY" || code === "CLOUD_BROWSER_PROFILE_DISABLED" || code === "CLOUD_BROWSER_PROFILE_PROXY_UNAVAILABLE" || code === "CLOUD_BROWSER_PROFILE_SYNC_FAILED") {
9229
- return code;
9230
- }
9231
- return "CLOUD_TRANSPORT_ERROR";
9512
+ return isCloudErrorCode(code) ? code : "CLOUD_TRANSPORT_ERROR";
9232
9513
  }
9233
9514
 
9234
9515
  // src/cloud/session-client.ts
@@ -9335,7 +9616,12 @@ function parseCreateResponse(body, status) {
9335
9616
  status,
9336
9617
  "cloudSession"
9337
9618
  ),
9338
- state: requireString(cloudSessionRoot, "state", status, "cloudSession"),
9619
+ state: requireSessionStatus(
9620
+ cloudSessionRoot,
9621
+ "state",
9622
+ status,
9623
+ "cloudSession"
9624
+ ),
9339
9625
  createdAt: requireNumber(cloudSessionRoot, "createdAt", status, "cloudSession"),
9340
9626
  sourceType: requireSourceType(cloudSessionRoot, "sourceType", status, "cloudSession"),
9341
9627
  sourceRef: optionalString(cloudSessionRoot, "sourceRef", status, "cloudSession"),
@@ -9423,7 +9709,7 @@ function optionalNumber(source, field, status, parent) {
9423
9709
  }
9424
9710
  function requireSourceType(source, field, status, parent) {
9425
9711
  const value = source[field];
9426
- if (value === "agent-thread" || value === "agent-run" || value === "local-cloud" || value === "manual") {
9712
+ if (isCloudSessionSourceType(value)) {
9427
9713
  return value;
9428
9714
  }
9429
9715
  throw new OpensteerCloudError(
@@ -9431,13 +9717,30 @@ function requireSourceType(source, field, status, parent) {
9431
9717
  `Invalid cloud session create response: ${formatFieldPath(
9432
9718
  field,
9433
9719
  parent
9434
- )} must be one of "agent-thread", "agent-run", "local-cloud", or "manual".`,
9720
+ )} must be one of ${formatAllowedValues(cloudSessionSourceTypes)}.`,
9721
+ status
9722
+ );
9723
+ }
9724
+ function requireSessionStatus(source, field, status, parent) {
9725
+ const value = source[field];
9726
+ if (isCloudSessionStatus(value)) {
9727
+ return value;
9728
+ }
9729
+ throw new OpensteerCloudError(
9730
+ "CLOUD_CONTRACT_MISMATCH",
9731
+ `Invalid cloud session create response: ${formatFieldPath(
9732
+ field,
9733
+ parent
9734
+ )} must be one of ${formatAllowedValues(cloudSessionStatuses)}.`,
9435
9735
  status
9436
9736
  );
9437
9737
  }
9438
9738
  function formatFieldPath(field, parent) {
9439
9739
  return parent ? `"${parent}.${field}"` : `"${field}"`;
9440
9740
  }
9741
+ function formatAllowedValues(values) {
9742
+ return values.map((value) => `"${value}"`).join(", ");
9743
+ }
9441
9744
  function zeroImportResponse() {
9442
9745
  return {
9443
9746
  imported: 0,
@@ -14846,6 +15149,7 @@ function sleep7(ms) {
14846
15149
  // Annotate the CommonJS export names for ESM import in node:
14847
15150
  0 && (module.exports = {
14848
15151
  ActionWsClient,
15152
+ BrowserPool,
14849
15153
  BrowserProfileClient,
14850
15154
  CdpOverlayCursorRenderer,
14851
15155
  CloudCdpClient,
@@ -14884,10 +15188,15 @@ function sleep7(ms) {
14884
15188
  cleanForFull,
14885
15189
  cleanForScrollable,
14886
15190
  clearCookies,
15191
+ clearPersistentProfileSingletons,
14887
15192
  cloneElementPath,
14888
15193
  closeTab,
15194
+ cloudActionMethods,
15195
+ cloudErrorCodes,
14889
15196
  cloudNotLaunchedError,
14890
15197
  cloudSessionContractVersion,
15198
+ cloudSessionSourceTypes,
15199
+ cloudSessionStatuses,
14891
15200
  cloudUnsupportedMethodError,
14892
15201
  collectLocalSelectorCacheEntries,
14893
15202
  countArrayItemsWithPath,
@@ -14896,6 +15205,8 @@ function sleep7(ms) {
14896
15205
  createExtractCallback,
14897
15206
  createResolveCallback,
14898
15207
  createTab,
15208
+ detectChromePaths,
15209
+ expandHome,
14899
15210
  exportCookies,
14900
15211
  extractArrayRowsWithPaths,
14901
15212
  extractArrayWithPaths,
@@ -14906,9 +15217,15 @@ function sleep7(ms) {
14906
15217
  getElementText,
14907
15218
  getElementValue,
14908
15219
  getModelProvider,
15220
+ getOrCreatePersistentProfile,
14909
15221
  getPageHtml,
14910
15222
  getPageTitle,
14911
15223
  importCookies,
15224
+ isCloudActionMethod,
15225
+ isCloudErrorCode,
15226
+ isCloudSessionSourceType,
15227
+ isCloudSessionStatus,
15228
+ listLocalChromeProfiles,
14912
15229
  listTabs,
14913
15230
  markInteractiveElements,
14914
15231
  normalizeNamespace,