opensteer 0.6.6 → 0.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -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,6 +460,7 @@ __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,
464
466
  cloudNotLaunchedError: () => cloudNotLaunchedError,
@@ -471,6 +473,8 @@ __export(index_exports, {
471
473
  createExtractCallback: () => createExtractCallback,
472
474
  createResolveCallback: () => createResolveCallback,
473
475
  createTab: () => createTab,
476
+ detectChromePaths: () => detectChromePaths,
477
+ expandHome: () => expandHome,
474
478
  exportCookies: () => exportCookies,
475
479
  extractArrayRowsWithPaths: () => extractArrayRowsWithPaths,
476
480
  extractArrayWithPaths: () => extractArrayWithPaths,
@@ -481,9 +485,11 @@ __export(index_exports, {
481
485
  getElementText: () => getElementText,
482
486
  getElementValue: () => getElementValue,
483
487
  getModelProvider: () => getModelProvider,
488
+ getOrCreatePersistentProfile: () => getOrCreatePersistentProfile,
484
489
  getPageHtml: () => getPageHtml,
485
490
  getPageTitle: () => getPageTitle,
486
491
  importCookies: () => importCookies,
492
+ listLocalChromeProfiles: () => listLocalChromeProfiles,
487
493
  listTabs: () => listTabs,
488
494
  markInteractiveElements: () => markInteractiveElements,
489
495
  normalizeNamespace: () => normalizeNamespace,
@@ -516,11 +522,8 @@ var import_crypto = require("crypto");
516
522
 
517
523
  // src/browser/pool.ts
518
524
  var import_node_child_process = require("child_process");
519
- var import_node_fs = require("fs");
520
- var import_promises = require("fs/promises");
525
+ var import_promises2 = require("fs/promises");
521
526
  var import_node_net = require("net");
522
- var import_node_os = require("os");
523
- var import_node_path = require("path");
524
527
  var import_playwright = require("playwright");
525
528
 
526
529
  // src/browser/cdp-proxy.ts
@@ -1013,18 +1016,282 @@ function listLocalChromeProfiles(userDataDir = detectChromePaths().defaultUserDa
1013
1016
  }
1014
1017
  }
1015
1018
 
1019
+ // src/browser/persistent-profile.ts
1020
+ var import_node_crypto = require("crypto");
1021
+ var import_node_fs = require("fs");
1022
+ var import_promises = require("fs/promises");
1023
+ var import_node_os = require("os");
1024
+ var import_node_path = require("path");
1025
+ var OPENSTEER_META_FILE = ".opensteer-meta.json";
1026
+ var PROCESS_STARTED_AT_MS = Math.floor(Date.now() - process.uptime() * 1e3);
1027
+ var PROCESS_START_TIME_TOLERANCE_MS = 1e3;
1028
+ var CHROME_SINGLETON_ENTRIES = /* @__PURE__ */ new Set([
1029
+ "SingletonCookie",
1030
+ "SingletonLock",
1031
+ "SingletonSocket",
1032
+ "DevToolsActivePort",
1033
+ "lockfile"
1034
+ ]);
1035
+ var COPY_SKIP_ENTRIES = /* @__PURE__ */ new Set([
1036
+ ...CHROME_SINGLETON_ENTRIES,
1037
+ OPENSTEER_META_FILE
1038
+ ]);
1039
+ var SKIPPED_ROOT_DIRECTORIES = /* @__PURE__ */ new Set([
1040
+ "Crash Reports",
1041
+ "Crashpad",
1042
+ "BrowserMetrics",
1043
+ "GrShaderCache",
1044
+ "ShaderCache",
1045
+ "GraphiteDawnCache",
1046
+ "component_crx_cache",
1047
+ "Crowd Deny",
1048
+ "hyphen-data",
1049
+ "OnDeviceHeadSuggestModel",
1050
+ "OptimizationGuidePredictionModels",
1051
+ "Segmentation Platform",
1052
+ "SmartCardDeviceNames",
1053
+ "WidevineCdm",
1054
+ "pnacl"
1055
+ ]);
1056
+ async function getOrCreatePersistentProfile(sourceUserDataDir, profileDirectory, profilesRootDir = defaultPersistentProfilesRootDir()) {
1057
+ const resolvedSourceUserDataDir = expandHome(sourceUserDataDir);
1058
+ const targetUserDataDir = (0, import_node_path.join)(
1059
+ expandHome(profilesRootDir),
1060
+ buildPersistentProfileKey(resolvedSourceUserDataDir, profileDirectory)
1061
+ );
1062
+ const sourceProfileDir = (0, import_node_path.join)(resolvedSourceUserDataDir, profileDirectory);
1063
+ const metadata = buildPersistentProfileMetadata(
1064
+ resolvedSourceUserDataDir,
1065
+ profileDirectory
1066
+ );
1067
+ await (0, import_promises.mkdir)((0, import_node_path.dirname)(targetUserDataDir), { recursive: true });
1068
+ await cleanOrphanedTempDirs(
1069
+ (0, import_node_path.dirname)(targetUserDataDir),
1070
+ (0, import_node_path.basename)(targetUserDataDir)
1071
+ );
1072
+ if (!(0, import_node_fs.existsSync)(sourceProfileDir)) {
1073
+ throw new Error(
1074
+ `Chrome profile "${profileDirectory}" was not found in "${resolvedSourceUserDataDir}".`
1075
+ );
1076
+ }
1077
+ const created = await createPersistentProfileClone(
1078
+ resolvedSourceUserDataDir,
1079
+ sourceProfileDir,
1080
+ targetUserDataDir,
1081
+ profileDirectory,
1082
+ metadata
1083
+ );
1084
+ await ensurePersistentProfileMetadata(targetUserDataDir, metadata);
1085
+ return {
1086
+ created,
1087
+ userDataDir: targetUserDataDir
1088
+ };
1089
+ }
1090
+ async function clearPersistentProfileSingletons(userDataDir) {
1091
+ await Promise.all(
1092
+ [...CHROME_SINGLETON_ENTRIES].map(
1093
+ (entry) => (0, import_promises.rm)((0, import_node_path.join)(userDataDir, entry), {
1094
+ force: true,
1095
+ recursive: true
1096
+ }).catch(() => void 0)
1097
+ )
1098
+ );
1099
+ }
1100
+ function buildPersistentProfileKey(sourceUserDataDir, profileDirectory) {
1101
+ const hash = (0, import_node_crypto.createHash)("sha256").update(`${sourceUserDataDir}\0${profileDirectory}`).digest("hex").slice(0, 16);
1102
+ const sourceLabel = sanitizePathSegment((0, import_node_path.basename)(sourceUserDataDir) || "user-data");
1103
+ const profileLabel = sanitizePathSegment(profileDirectory || "Default");
1104
+ return `${sourceLabel}-${profileLabel}-${hash}`;
1105
+ }
1106
+ function defaultPersistentProfilesRootDir() {
1107
+ return (0, import_node_path.join)((0, import_node_os.homedir)(), ".opensteer", "real-browser-profiles");
1108
+ }
1109
+ function sanitizePathSegment(value) {
1110
+ const sanitized = value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/-+/g, "-");
1111
+ return sanitized.replace(/^-|-$/g, "") || "profile";
1112
+ }
1113
+ function isProfileDirectory(userDataDir, entry) {
1114
+ return (0, import_node_fs.existsSync)((0, import_node_path.join)(userDataDir, entry, "Preferences"));
1115
+ }
1116
+ async function copyRootLevelEntries(sourceUserDataDir, targetUserDataDir, targetProfileDirectory) {
1117
+ let entries;
1118
+ try {
1119
+ entries = await (0, import_promises.readdir)(sourceUserDataDir);
1120
+ } catch {
1121
+ return;
1122
+ }
1123
+ const copyTasks = [];
1124
+ for (const entry of entries) {
1125
+ if (COPY_SKIP_ENTRIES.has(entry)) continue;
1126
+ if (entry === targetProfileDirectory) continue;
1127
+ const sourcePath = (0, import_node_path.join)(sourceUserDataDir, entry);
1128
+ const targetPath = (0, import_node_path.join)(targetUserDataDir, entry);
1129
+ if ((0, import_node_fs.existsSync)(targetPath)) continue;
1130
+ let entryStat;
1131
+ try {
1132
+ entryStat = await (0, import_promises.stat)(sourcePath);
1133
+ } catch {
1134
+ continue;
1135
+ }
1136
+ if (entryStat.isFile()) {
1137
+ copyTasks.push((0, import_promises.copyFile)(sourcePath, targetPath).catch(() => void 0));
1138
+ } else if (entryStat.isDirectory()) {
1139
+ if (isProfileDirectory(sourceUserDataDir, entry)) continue;
1140
+ if (SKIPPED_ROOT_DIRECTORIES.has(entry)) continue;
1141
+ copyTasks.push(
1142
+ (0, import_promises.cp)(sourcePath, targetPath, { recursive: true }).catch(
1143
+ () => void 0
1144
+ )
1145
+ );
1146
+ }
1147
+ }
1148
+ await Promise.all(copyTasks);
1149
+ }
1150
+ async function writePersistentProfileMetadata(userDataDir, metadata) {
1151
+ await (0, import_promises.writeFile)(
1152
+ (0, import_node_path.join)(userDataDir, OPENSTEER_META_FILE),
1153
+ JSON.stringify(metadata, null, 2)
1154
+ );
1155
+ }
1156
+ function buildPersistentProfileMetadata(sourceUserDataDir, profileDirectory) {
1157
+ return {
1158
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1159
+ profileDirectory,
1160
+ source: sourceUserDataDir
1161
+ };
1162
+ }
1163
+ async function createPersistentProfileClone(sourceUserDataDir, sourceProfileDir, targetUserDataDir, profileDirectory, metadata) {
1164
+ if ((0, import_node_fs.existsSync)(targetUserDataDir)) {
1165
+ return false;
1166
+ }
1167
+ const tempUserDataDir = await (0, import_promises.mkdtemp)(
1168
+ buildPersistentProfileTempDirPrefix(targetUserDataDir)
1169
+ );
1170
+ let published = false;
1171
+ try {
1172
+ await (0, import_promises.cp)(sourceProfileDir, (0, import_node_path.join)(tempUserDataDir, profileDirectory), {
1173
+ recursive: true
1174
+ });
1175
+ await copyRootLevelEntries(
1176
+ sourceUserDataDir,
1177
+ tempUserDataDir,
1178
+ profileDirectory
1179
+ );
1180
+ await writePersistentProfileMetadata(tempUserDataDir, metadata);
1181
+ try {
1182
+ await (0, import_promises.rename)(tempUserDataDir, targetUserDataDir);
1183
+ } catch (error) {
1184
+ if (wasProfilePublishedByAnotherProcess(error, targetUserDataDir)) {
1185
+ return false;
1186
+ }
1187
+ throw error;
1188
+ }
1189
+ published = true;
1190
+ return true;
1191
+ } finally {
1192
+ if (!published) {
1193
+ await (0, import_promises.rm)(tempUserDataDir, {
1194
+ recursive: true,
1195
+ force: true
1196
+ }).catch(() => void 0);
1197
+ }
1198
+ }
1199
+ }
1200
+ async function ensurePersistentProfileMetadata(userDataDir, metadata) {
1201
+ if ((0, import_node_fs.existsSync)((0, import_node_path.join)(userDataDir, OPENSTEER_META_FILE))) {
1202
+ return;
1203
+ }
1204
+ await writePersistentProfileMetadata(userDataDir, metadata);
1205
+ }
1206
+ function wasProfilePublishedByAnotherProcess(error, targetUserDataDir) {
1207
+ const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
1208
+ return (0, import_node_fs.existsSync)(targetUserDataDir) && (code === "EEXIST" || code === "ENOTEMPTY" || code === "EPERM");
1209
+ }
1210
+ function buildPersistentProfileTempDirPrefix(targetUserDataDir) {
1211
+ return (0, import_node_path.join)(
1212
+ (0, import_node_path.dirname)(targetUserDataDir),
1213
+ `${(0, import_node_path.basename)(targetUserDataDir)}-tmp-${process.pid}-${PROCESS_STARTED_AT_MS}-`
1214
+ );
1215
+ }
1216
+ async function cleanOrphanedTempDirs(profilesDir, targetBaseName) {
1217
+ let entries;
1218
+ try {
1219
+ entries = await (0, import_promises.readdir)(profilesDir, {
1220
+ encoding: "utf8",
1221
+ withFileTypes: true
1222
+ });
1223
+ } catch {
1224
+ return;
1225
+ }
1226
+ const tempDirPrefix = `${targetBaseName}-tmp-`;
1227
+ await Promise.all(
1228
+ entries.map(async (entry) => {
1229
+ if (!entry.isDirectory() || !entry.name.startsWith(tempDirPrefix)) {
1230
+ return;
1231
+ }
1232
+ if (isTempDirOwnedByLiveProcess(entry.name, tempDirPrefix)) {
1233
+ return;
1234
+ }
1235
+ await (0, import_promises.rm)((0, import_node_path.join)(profilesDir, entry.name), {
1236
+ recursive: true,
1237
+ force: true
1238
+ }).catch(() => void 0);
1239
+ })
1240
+ );
1241
+ }
1242
+ function isTempDirOwnedByLiveProcess(tempDirName, tempDirPrefix) {
1243
+ const owner = parseTempDirOwner(tempDirName, tempDirPrefix);
1244
+ if (!owner) {
1245
+ return false;
1246
+ }
1247
+ if (owner.pid === process.pid && Math.abs(owner.processStartedAtMs - PROCESS_STARTED_AT_MS) <= PROCESS_START_TIME_TOLERANCE_MS) {
1248
+ return true;
1249
+ }
1250
+ return isProcessRunning(owner.pid);
1251
+ }
1252
+ function parseTempDirOwner(tempDirName, tempDirPrefix) {
1253
+ const remainder = tempDirName.slice(tempDirPrefix.length);
1254
+ const firstDashIndex = remainder.indexOf("-");
1255
+ const secondDashIndex = firstDashIndex === -1 ? -1 : remainder.indexOf("-", firstDashIndex + 1);
1256
+ if (firstDashIndex === -1 || secondDashIndex === -1) {
1257
+ return null;
1258
+ }
1259
+ const pid = Number.parseInt(remainder.slice(0, firstDashIndex), 10);
1260
+ const processStartedAtMs = Number.parseInt(
1261
+ remainder.slice(firstDashIndex + 1, secondDashIndex),
1262
+ 10
1263
+ );
1264
+ if (!Number.isInteger(pid) || pid <= 0) {
1265
+ return null;
1266
+ }
1267
+ if (!Number.isInteger(processStartedAtMs) || processStartedAtMs <= 0) {
1268
+ return null;
1269
+ }
1270
+ return { pid, processStartedAtMs };
1271
+ }
1272
+ function isProcessRunning(pid) {
1273
+ try {
1274
+ process.kill(pid, 0);
1275
+ return true;
1276
+ } catch (error) {
1277
+ const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
1278
+ return code !== "ESRCH";
1279
+ }
1280
+ }
1281
+
1016
1282
  // src/browser/pool.ts
1017
1283
  var BrowserPool = class {
1018
1284
  browser = null;
1019
1285
  cdpProxy = null;
1020
1286
  launchedProcess = null;
1021
- tempUserDataDir = null;
1287
+ managedUserDataDir = null;
1288
+ persistentProfile = false;
1022
1289
  defaults;
1023
1290
  constructor(defaults = {}) {
1024
1291
  this.defaults = defaults;
1025
1292
  }
1026
1293
  async launch(options = {}) {
1027
- if (this.browser || this.cdpProxy || this.launchedProcess || this.tempUserDataDir) {
1294
+ if (this.browser || this.cdpProxy || this.launchedProcess || this.managedUserDataDir) {
1028
1295
  await this.close();
1029
1296
  }
1030
1297
  const mode = options.mode ?? this.defaults.mode ?? "chromium";
@@ -1074,11 +1341,13 @@ var BrowserPool = class {
1074
1341
  const browser = this.browser;
1075
1342
  const cdpProxy = this.cdpProxy;
1076
1343
  const launchedProcess = this.launchedProcess;
1077
- const tempUserDataDir = this.tempUserDataDir;
1344
+ const managedUserDataDir = this.managedUserDataDir;
1345
+ const persistentProfile = this.persistentProfile;
1078
1346
  this.browser = null;
1079
1347
  this.cdpProxy = null;
1080
1348
  this.launchedProcess = null;
1081
- this.tempUserDataDir = null;
1349
+ this.managedUserDataDir = null;
1350
+ this.persistentProfile = false;
1082
1351
  try {
1083
1352
  if (browser) {
1084
1353
  await browser.close().catch(() => void 0);
@@ -1086,8 +1355,8 @@ var BrowserPool = class {
1086
1355
  } finally {
1087
1356
  cdpProxy?.close();
1088
1357
  await killProcessTree(launchedProcess);
1089
- if (tempUserDataDir) {
1090
- await (0, import_promises.rm)(tempUserDataDir, {
1358
+ if (managedUserDataDir && !persistentProfile) {
1359
+ await (0, import_promises2.rm)(managedUserDataDir, {
1091
1360
  recursive: true,
1092
1361
  force: true
1093
1362
  }).catch(() => void 0);
@@ -1136,10 +1405,11 @@ var BrowserPool = class {
1136
1405
  options.userDataDir ?? chromePaths.defaultUserDataDir
1137
1406
  );
1138
1407
  const profileDirectory = options.profileDirectory ?? "Default";
1139
- const tempUserDataDir = await cloneProfileToTempDir(
1408
+ const persistentProfile = await getOrCreatePersistentProfile(
1140
1409
  sourceUserDataDir,
1141
1410
  profileDirectory
1142
1411
  );
1412
+ await clearPersistentProfileSingletons(persistentProfile.userDataDir);
1143
1413
  const debugPort = await reserveDebugPort();
1144
1414
  const headless = resolveLaunchHeadless(
1145
1415
  "real",
@@ -1147,7 +1417,7 @@ var BrowserPool = class {
1147
1417
  this.defaults.headless
1148
1418
  );
1149
1419
  const launchArgs = buildRealBrowserLaunchArgs({
1150
- userDataDir: tempUserDataDir,
1420
+ userDataDir: persistentProfile.userDataDir,
1151
1421
  profileDirectory,
1152
1422
  debugPort,
1153
1423
  headless
@@ -1167,24 +1437,22 @@ var BrowserPool = class {
1167
1437
  timeout: options.timeout ?? 3e4
1168
1438
  });
1169
1439
  const { context, page } = await createOwnedBrowserContextAndPage(
1170
- browser,
1171
- {
1172
- headless,
1173
- initialUrl: options.initialUrl,
1174
- timeoutMs: options.timeout ?? 3e4
1175
- }
1440
+ browser
1176
1441
  );
1442
+ if (options.initialUrl) {
1443
+ await page.goto(options.initialUrl, {
1444
+ waitUntil: "domcontentloaded",
1445
+ timeout: options.timeout ?? 3e4
1446
+ });
1447
+ }
1177
1448
  this.browser = browser;
1178
1449
  this.launchedProcess = processHandle;
1179
- this.tempUserDataDir = tempUserDataDir;
1450
+ this.managedUserDataDir = persistentProfile.userDataDir;
1451
+ this.persistentProfile = true;
1180
1452
  return { browser, context, page, isExternal: false };
1181
1453
  } catch (error) {
1182
1454
  await browser?.close().catch(() => void 0);
1183
1455
  await killProcessTree(processHandle);
1184
- await (0, import_promises.rm)(tempUserDataDir, {
1185
- recursive: true,
1186
- force: true
1187
- }).catch(() => void 0);
1188
1456
  throw error;
1189
1457
  }
1190
1458
  }
@@ -1207,8 +1475,7 @@ var BrowserPool = class {
1207
1475
  };
1208
1476
  async function pickBrowserContextAndPage(browser) {
1209
1477
  const context = getPrimaryBrowserContext(browser);
1210
- const pages = context.pages();
1211
- const page = pages.find((candidate) => isInspectablePageUrl2(candidate.url())) || pages[0] || await context.newPage();
1478
+ const page = await getAttachedPageOrCreate(context);
1212
1479
  return { context, page };
1213
1480
  }
1214
1481
  function resolveLaunchHeadless(mode, requestedHeadless, defaultHeadless) {
@@ -1220,77 +1487,31 @@ function resolveLaunchHeadless(mode, requestedHeadless, defaultHeadless) {
1220
1487
  }
1221
1488
  return mode === "real";
1222
1489
  }
1223
- async function createOwnedBrowserContextAndPage(browser, options) {
1490
+ async function createOwnedBrowserContextAndPage(browser) {
1224
1491
  const context = getPrimaryBrowserContext(browser);
1225
- const page = await createOwnedBrowserPage(browser, context, options);
1492
+ const page = await getExistingPageOrCreate(context);
1226
1493
  return { context, page };
1227
1494
  }
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;
1495
+ async function getAttachedPageOrCreate(context) {
1496
+ const pages = context.pages();
1497
+ const inspectablePage = pages.find(
1498
+ (candidate) => isInspectablePageUrl2(safePageUrl(candidate))
1499
+ );
1500
+ if (inspectablePage) {
1501
+ return inspectablePage;
1258
1502
  }
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);
1503
+ const attachedPage = pages[0];
1504
+ if (attachedPage) {
1505
+ return attachedPage;
1264
1506
  }
1507
+ return await context.newPage();
1265
1508
  }
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;
1509
+ async function getExistingPageOrCreate(context) {
1510
+ const existingPage = context.pages()[0];
1511
+ if (existingPage) {
1512
+ return existingPage;
1290
1513
  }
1291
- throw new Error(
1292
- `Chrome created a target for ${options.targetUrl}, but Playwright did not expose the page in time.`
1293
- );
1514
+ return await context.newPage();
1294
1515
  }
1295
1516
  function getPrimaryBrowserContext(browser) {
1296
1517
  const contexts = browser.contexts();
@@ -1304,19 +1525,11 @@ function getPrimaryBrowserContext(browser) {
1304
1525
  function isInspectablePageUrl2(url) {
1305
1526
  return url === "about:blank" || url.startsWith("http://") || url.startsWith("https://");
1306
1527
  }
1307
- function isDisposableStartupPageUrl(url) {
1308
- return url === "about:blank" || url === "chrome://newtab/" || url === "chrome://new-tab-page/";
1309
- }
1310
- function pageLooselyMatchesUrl(currentUrl, initialUrl) {
1528
+ function safePageUrl(page) {
1311
1529
  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;
1530
+ return page.url();
1318
1531
  } catch {
1319
- return currentUrl === initialUrl;
1532
+ return "";
1320
1533
  }
1321
1534
  }
1322
1535
  function normalizeDiscoveryUrl(cdpUrl) {
@@ -1396,38 +1609,12 @@ async function reserveDebugPort() {
1396
1609
  });
1397
1610
  });
1398
1611
  }
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
1612
  function buildRealBrowserLaunchArgs(options) {
1422
1613
  const args = [
1423
1614
  `--user-data-dir=${options.userDataDir}`,
1424
1615
  `--profile-directory=${options.profileDirectory}`,
1425
1616
  `--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"
1617
+ "--disable-blink-features=AutomationControlled"
1431
1618
  ];
1432
1619
  if (options.headless) {
1433
1620
  args.push("--headless=new");
@@ -7147,7 +7334,7 @@ async function closeTab(context, activePage, index) {
7147
7334
  }
7148
7335
 
7149
7336
  // src/actions/cookies.ts
7150
- var import_promises2 = require("fs/promises");
7337
+ var import_promises3 = require("fs/promises");
7151
7338
  async function getCookies(context, url) {
7152
7339
  return context.cookies(url ? [url] : void 0);
7153
7340
  }
@@ -7159,10 +7346,10 @@ async function clearCookies(context) {
7159
7346
  }
7160
7347
  async function exportCookies(context, filePath, url) {
7161
7348
  const cookies = await context.cookies(url ? [url] : void 0);
7162
- await (0, import_promises2.writeFile)(filePath, JSON.stringify(cookies, null, 2), "utf-8");
7349
+ await (0, import_promises3.writeFile)(filePath, JSON.stringify(cookies, null, 2), "utf-8");
7163
7350
  }
7164
7351
  async function importCookies(context, filePath) {
7165
- const raw = await (0, import_promises2.readFile)(filePath, "utf-8");
7352
+ const raw = await (0, import_promises3.readFile)(filePath, "utf-8");
7166
7353
  const cookies = JSON.parse(raw);
7167
7354
  await context.addCookies(cookies);
7168
7355
  }
@@ -7913,31 +8100,31 @@ function buildVariantDescriptorFromCluster(descriptors) {
7913
8100
  const keyStats = /* @__PURE__ */ new Map();
7914
8101
  for (const descriptor of descriptors) {
7915
8102
  for (const field of descriptor.fields) {
7916
- const stat = keyStats.get(field.path) || {
8103
+ const stat2 = keyStats.get(field.path) || {
7917
8104
  indices: /* @__PURE__ */ new Set(),
7918
8105
  pathNodes: [],
7919
8106
  attributes: [],
7920
8107
  sources: []
7921
8108
  };
7922
- stat.indices.add(descriptor.index);
8109
+ stat2.indices.add(descriptor.index);
7923
8110
  if (isPersistedValueNode(field.node)) {
7924
- stat.pathNodes.push(field.node.$path);
7925
- stat.attributes.push(field.node.attribute);
8111
+ stat2.pathNodes.push(field.node.$path);
8112
+ stat2.attributes.push(field.node.attribute);
7926
8113
  } else if (isPersistedSourceNode(field.node)) {
7927
- stat.sources.push("current_url");
8114
+ stat2.sources.push("current_url");
7928
8115
  }
7929
- keyStats.set(field.path, stat);
8116
+ keyStats.set(field.path, stat2);
7930
8117
  }
7931
8118
  }
7932
8119
  const mergedFields = [];
7933
- for (const [fieldPath, stat] of keyStats) {
7934
- if (stat.indices.size < threshold) continue;
7935
- if (stat.pathNodes.length >= threshold) {
8120
+ for (const [fieldPath, stat2] of keyStats) {
8121
+ if (stat2.indices.size < threshold) continue;
8122
+ if (stat2.pathNodes.length >= threshold) {
7936
8123
  let mergedFieldPath = null;
7937
- if (stat.pathNodes.length === 1) {
7938
- mergedFieldPath = sanitizeElementPath(stat.pathNodes[0]);
8124
+ if (stat2.pathNodes.length === 1) {
8125
+ mergedFieldPath = sanitizeElementPath(stat2.pathNodes[0]);
7939
8126
  } else {
7940
- mergedFieldPath = mergeElementPathsByMajority(stat.pathNodes);
8127
+ mergedFieldPath = mergeElementPathsByMajority(stat2.pathNodes);
7941
8128
  }
7942
8129
  if (!mergedFieldPath) continue;
7943
8130
  if (clusterSize === 1) {
@@ -7950,17 +8137,17 @@ function buildVariantDescriptorFromCluster(descriptors) {
7950
8137
  mergedFieldPath,
7951
8138
  "field"
7952
8139
  );
7953
- const attrThreshold = stat.pathNodes.length === 1 ? 1 : majorityThreshold(stat.pathNodes.length);
8140
+ const attrThreshold = stat2.pathNodes.length === 1 ? 1 : majorityThreshold(stat2.pathNodes.length);
7954
8141
  mergedFields.push({
7955
8142
  path: fieldPath,
7956
8143
  node: createValueNode({
7957
8144
  elementPath: mergedFieldPath,
7958
- attribute: pickModeString(stat.attributes, attrThreshold)
8145
+ attribute: pickModeString(stat2.attributes, attrThreshold)
7959
8146
  })
7960
8147
  });
7961
8148
  continue;
7962
8149
  }
7963
- const dominantSource = pickModeString(stat.sources, threshold);
8150
+ const dominantSource = pickModeString(stat2.sources, threshold);
7964
8151
  if (dominantSource === "current_url") {
7965
8152
  mergedFields.push({
7966
8153
  path: fieldPath,
@@ -9161,7 +9348,7 @@ function selectPreferredContextPage(browser, contexts) {
9161
9348
  let aboutBlankCandidate = null;
9162
9349
  for (const context of contexts) {
9163
9350
  for (const page of context.pages()) {
9164
- const url = safePageUrl(page);
9351
+ const url = safePageUrl2(page);
9165
9352
  if (!isInternalOrEmptyUrl(url)) {
9166
9353
  return { browser, context, page };
9167
9354
  }
@@ -9172,7 +9359,7 @@ function selectPreferredContextPage(browser, contexts) {
9172
9359
  }
9173
9360
  return aboutBlankCandidate;
9174
9361
  }
9175
- function safePageUrl(page) {
9362
+ function safePageUrl2(page) {
9176
9363
  try {
9177
9364
  return page.url();
9178
9365
  } catch {
@@ -14846,6 +15033,7 @@ function sleep7(ms) {
14846
15033
  // Annotate the CommonJS export names for ESM import in node:
14847
15034
  0 && (module.exports = {
14848
15035
  ActionWsClient,
15036
+ BrowserPool,
14849
15037
  BrowserProfileClient,
14850
15038
  CdpOverlayCursorRenderer,
14851
15039
  CloudCdpClient,
@@ -14884,6 +15072,7 @@ function sleep7(ms) {
14884
15072
  cleanForFull,
14885
15073
  cleanForScrollable,
14886
15074
  clearCookies,
15075
+ clearPersistentProfileSingletons,
14887
15076
  cloneElementPath,
14888
15077
  closeTab,
14889
15078
  cloudNotLaunchedError,
@@ -14896,6 +15085,8 @@ function sleep7(ms) {
14896
15085
  createExtractCallback,
14897
15086
  createResolveCallback,
14898
15087
  createTab,
15088
+ detectChromePaths,
15089
+ expandHome,
14899
15090
  exportCookies,
14900
15091
  extractArrayRowsWithPaths,
14901
15092
  extractArrayWithPaths,
@@ -14906,9 +15097,11 @@ function sleep7(ms) {
14906
15097
  getElementText,
14907
15098
  getElementValue,
14908
15099
  getModelProvider,
15100
+ getOrCreatePersistentProfile,
14909
15101
  getPageHtml,
14910
15102
  getPageTitle,
14911
15103
  importCookies,
15104
+ listLocalChromeProfiles,
14912
15105
  listTabs,
14913
15106
  markInteractiveElements,
14914
15107
  normalizeNamespace,