@orkestrel/browser 0.0.24 → 0.0.25

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.
@@ -1,4 +1,4 @@
1
- import { BROWSER_DEFAULT_TIMEOUT_MS, BROWSER_JOURNEY_FORMAT_VERSION, BROWSER_JOURNEY_NAME_PATTERN, BROWSER_JOURNEY_TOOL_NAMES, BROWSER_RUN_ID_PATTERN, BROWSER_TOOL_COPY, BROWSER_TOOL_NAMES, BrowserConnectionError, BrowserContext, BrowserError, BrowserTransition, CDPClient, createBrowserToolset, createCDPClient, generateBrowserRunId, isBrowserConnectionError, isBrowserError, isCDPTimeoutError, normalizeBrowserJourneyReason, validateBrowserContextOptions, validateBrowserJourney, validateBrowserRun, validateBrowserStorePage } from "../core/index.js";
1
+ import { BROWSER_DEFAULT_TIMEOUT_MS, BROWSER_JOURNEY_FORMAT_VERSION, BROWSER_JOURNEY_NAME_PATTERN, BROWSER_JOURNEY_TOOL_NAMES, BROWSER_RUN_ID_PATTERN, BROWSER_TOOL_COPY, BROWSER_TOOL_NAMES, BrowserConnectionError, BrowserContext, BrowserError, BrowserTransition, CDPClient, createBrowserToolset, createCDPClient, generateBrowserRunId, isBrowserConnectionError, isBrowserError, isCDPConnectionError, isCDPTimeoutError, normalizeBrowserJourneyReason, validateBrowserContextOptions, validateBrowserJourney, validateBrowserRun, validateBrowserStorePage } from "../core/index.js";
2
2
  import { attempt, isArray, isError, isInstance, isInteger, isNumber, isRecord, isString, parseInteger, parseJSON } from "@orkestrel/contract";
3
3
  import { existsSync, globSync, realpathSync } from "node:fs";
4
4
  import { lstat, mkdir, mkdtemp, open, readFile, readdir, rename, rm, rmdir, unlink, writeFile } from "node:fs/promises";
@@ -37,8 +37,18 @@ var BROWSER_CDP_VERSION_PATH = "/json/version";
37
37
  * other target category Chromium reports — `'/json/list'`.
38
38
  */
39
39
  var BROWSER_CDP_LIST_PATH = "/json/list";
40
- /** Lists the flags always passed to a launched browser process, alongside the caller's own. */
41
- var BROWSER_LAUNCH_ARGS = Object.freeze(["--no-first-run", "--no-default-browser-check"]);
40
+ /**
41
+ * Lists the flags always passed to a launched browser process, alongside the caller's own.
42
+ *
43
+ * @remarks
44
+ * Disables account sync so extension installs cannot reset URL loader factories during page imports.
45
+ * Edge can still sign a fresh Windows profile in to the operating-system account.
46
+ */
47
+ var BROWSER_LAUNCH_ARGS = Object.freeze([
48
+ "--no-first-run",
49
+ "--no-default-browser-check",
50
+ "--disable-sync"
51
+ ]);
42
52
  /**
43
53
  * Names the flag that enables headless mode on a launched browser process, `'--headless=new'`.
44
54
  */
@@ -218,6 +228,10 @@ var BROWSER_SERVER_UNAVAILABLE = "BROWSER_SERVER_UNAVAILABLE";
218
228
  var BROWSER_SERVER_LAUNCH = "BROWSER_SERVER_LAUNCH";
219
229
  /** Sets the default number of warm browsers to 1. */
220
230
  var BROWSER_SERVER_POOL_SIZE = 1;
231
+ /** Sets the default context capacity per browser, including the shared holder. */
232
+ var BROWSER_SERVER_CONTEXTS = 2;
233
+ /** Limits context capacity to the largest measured per-browser count. */
234
+ var BROWSER_SERVER_CONTEXTS_LIMIT = 4;
221
235
  /** Limits the number of warm browsers to 3. */
222
236
  var BROWSER_SERVER_POOL_LIMIT = 3;
223
237
  /** Permits one failed refill before the next failure spends the bound. */
@@ -1041,7 +1055,7 @@ var FileBrowserWriter = class {
1041
1055
  };
1042
1056
  //#endregion
1043
1057
  //#region package.json
1044
- var version = "0.0.24";
1058
+ var version = "0.0.25";
1045
1059
  //#endregion
1046
1060
  //#region src/server/BrowserMCPServer.ts
1047
1061
  /**
@@ -1057,17 +1071,18 @@ var version = "0.0.24";
1057
1071
  * tool it adopts, is mirrored as a dispatcher with that tool's definition, and the mirror is
1058
1072
  * removed when the toolset withdraws the tool, so the server's tool list changes and a subscribed
1059
1073
  * client is notified. The server also advertises `acquire`, `execute`, `tools`, and `destroy` with
1060
- * `BROWSER_SERVER_COPY`. Startup warms the pool and leases the shared browser. The legacy
1074
+ * `BROWSER_SERVER_COPY`. Startup warms the pool and assigns a prepared context to the shared holder. The legacy
1061
1075
  * handshake and browser calls await that setup. Named holders each own a lease, a pending grant,
1062
1076
  * and loss notices; concurrent calls on a holder share its replacement grant. Ending a holder
1063
- * destroys its token, and admission counts that holder until disposal settles.
1077
+ * closes its context and releases its lease, and admission counts that holder until disposal settles.
1064
1078
  * A launch creates `ROOT/.profiles/PID-UUID/` exclusively, connects a browser that never attaches to an
1065
1079
  * existing endpoint, opens one page in an isolated context, and constructs the page toolset with
1066
1080
  * that context and the file journey and run stores under the root, on a manager the toolset owns.
1067
1081
  * A dispatcher forwards its arguments and the call's context, signal included, to the toolset's
1068
1082
  * manager and returns that manager's value, or rejects with its failure message; a text value
1069
1083
  * reaches the client as one text block rather than as its JSON text.
1070
- * A disconnect or current-page crash retires its slot. A failed call receives a liveness ping;
1084
+ * A disconnect retires its browser and every hosted generation. A current-page crash retires only
1085
+ * that holder's context. A failed call receives a liveness ping;
1071
1086
  * loss makes its outcome unresolved, while a known success remains successful. Pending losses
1072
1087
  * prefix the next outcome on a successor, or a refusal, and calls are never repeated.
1073
1088
  * Concurrent checks share one ping per slot. Cancelling a call ends only its wait; the ping keeps
@@ -1075,7 +1090,8 @@ var version = "0.0.24";
1075
1090
  *
1076
1091
  * `start()` serves stdio and destroys the server at the end of its input, on `SIGINT`, and on
1077
1092
  * `SIGTERM`. `destroy()` stops reading requests and removes every dispatcher, then destroys every
1078
- * slot through the pool — its toolset, which aborts the active replay, then its browser, then its
1093
+ * slot through the pool — each toolset, which aborts its active replay, then its context and downloads,
1094
+ * then its browser and its
1079
1095
  * profile — and rechecks the profile folders this server made or kept.
1080
1096
  *
1081
1097
  * Chromium on Linux refuses to start as the root user unless its sandbox is disabled, so a server
@@ -1110,8 +1126,16 @@ var BrowserMCPServer = class {
1110
1126
  #folders = /* @__PURE__ */ new Set();
1111
1127
  #faults = /* @__PURE__ */ new Set();
1112
1128
  #losses = /* @__PURE__ */ new WeakMap();
1129
+ #departures = /* @__PURE__ */ new WeakMap();
1113
1130
  #pings = /* @__PURE__ */ new WeakMap();
1114
1131
  #watches = /* @__PURE__ */ new Map();
1132
+ #observed = /* @__PURE__ */ new Map();
1133
+ #prepared = /* @__PURE__ */ new Map();
1134
+ #owned = /* @__PURE__ */ new Map();
1135
+ #building = /* @__PURE__ */ new Map();
1136
+ #cleaning = /* @__PURE__ */ new WeakMap();
1137
+ #endings = /* @__PURE__ */ new WeakMap();
1138
+ #retentions = /* @__PURE__ */ new Map();
1115
1139
  #shared = {
1116
1140
  id: "shared",
1117
1141
  purpose: "Shared browser",
@@ -1125,7 +1149,7 @@ var BrowserMCPServer = class {
1125
1149
  #drains = /* @__PURE__ */ new Map();
1126
1150
  #readers = /* @__PURE__ */ new Map();
1127
1151
  #writers = /* @__PURE__ */ new Set();
1128
- #size;
1152
+ #admission;
1129
1153
  #reference;
1130
1154
  #references = 0;
1131
1155
  #failure;
@@ -1140,17 +1164,20 @@ var BrowserMCPServer = class {
1140
1164
  */
1141
1165
  constructor(options) {
1142
1166
  const size = options?.pool?.size ?? 1;
1167
+ const contexts = options?.pool?.contexts ?? 2;
1143
1168
  if (!isInteger(size) || size < 1 || size > 3) throw new BrowserError(`pool.size must be an integer from 1 through 3`, BROWSER_SERVER_OPTIONS);
1144
- this.#size = size;
1169
+ if (!isInteger(contexts) || contexts < 1 || contexts > 4) throw new BrowserError(`pool.contexts must be an integer from 1 through 4`, BROWSER_SERVER_OPTIONS);
1170
+ this.#admission = size * contexts;
1145
1171
  this.#log = options?.log ?? process.stderr;
1146
1172
  this.#reference = this.#issue.bind(this);
1147
1173
  this.#pool = createPool({
1148
1174
  create: this.#warm.bind(this),
1149
- destroy: (slot) => this.#destroySlot(slot.profile, slot.browser, slot.toolset, this.#losses.get(slot)?.cause),
1175
+ destroy: this.#destroyRecord.bind(this),
1150
1176
  validate: this.#validate.bind(this),
1151
1177
  watch: this.#watch.bind(this),
1152
1178
  error: this.#fault.bind(this),
1153
1179
  min: size,
1180
+ capacity: contexts,
1154
1181
  restarts: 1
1155
1182
  });
1156
1183
  this.#root = resolve(options?.root ?? "tmp/browsers");
@@ -1233,6 +1260,7 @@ var BrowserMCPServer = class {
1233
1260
  this.#transport.stop();
1234
1261
  for (const tool of this.#tools.tools()) this.#tools.remove(tool.name);
1235
1262
  this.#unmirror();
1263
+ for (const generation of this.#observed.keys()) this.#untrack(generation);
1236
1264
  this.#leases.clear();
1237
1265
  for (const holder of this.#holders.values()) holder.abort.abort();
1238
1266
  this.#abort.abort();
@@ -1241,6 +1269,7 @@ var BrowserMCPServer = class {
1241
1269
  });
1242
1270
  await this.#starting?.catch(() => void 0);
1243
1271
  await barrier;
1272
+ await Promise.allSettled(this.#grants.values());
1244
1273
  await Promise.allSettled(this.#disposals.values());
1245
1274
  this.#holders.clear();
1246
1275
  await this.#sweeping;
@@ -1292,7 +1321,7 @@ var BrowserMCPServer = class {
1292
1321
  if (this.#closing !== void 0) throw this.#ended();
1293
1322
  const purpose = args["purpose"];
1294
1323
  if (!isString(purpose) || purpose.trim().length === 0) throw new BrowserError("purpose must describe the work", "BROWSER_TOOLSET_ARGUMENT");
1295
- if (this.#holders.size >= this.#size) throw new BrowserError(`${BROWSER_SERVER_BUSY}: ${[...this.#holders.values()].map((holder) => `${holder.id} (${holder.purpose})`).join(", ")}. Call destroy for a holder no longer needed or call the named tools to share the shared browser.`, BROWSER_SERVER_BUSY);
1324
+ if (this.#holders.size >= this.#admission) throw new BrowserError(`${BROWSER_SERVER_BUSY}: ${[...this.#holders.values()].map((holder) => `${holder.id} (${holder.purpose})`).join(", ")}. Call destroy for a holder no longer needed or call the named tools to share the shared browser.`, BROWSER_SERVER_BUSY);
1296
1325
  const holder = {
1297
1326
  id: randomUUID(),
1298
1327
  purpose,
@@ -1301,13 +1330,13 @@ var BrowserMCPServer = class {
1301
1330
  this.#holders.set(holder.id, holder);
1302
1331
  const listener = addAbortListener(context.signal, () => holder.abort.abort(context.signal.reason));
1303
1332
  try {
1304
- const slot = await this.#serve(holder, context.signal);
1333
+ const lease = await this.#serve(holder, context.signal);
1305
1334
  context.signal.throwIfAborted();
1306
1335
  if (holder.abort.signal.aborted || this.#holders.get(holder.id) !== holder) throw this.#ended();
1307
- if (this.#leases.get(holder)?.value !== slot) throw this.#unavailable(this.#losses.get(slot)?.cause);
1336
+ if (this.#leases.get(holder) !== lease) throw this.#unavailable(this.#departures.get(lease)?.cause);
1308
1337
  const catalog = {
1309
1338
  holder: holder.id,
1310
- tools: slot.toolset.tools.definitions()
1339
+ tools: lease.generation.toolset.tools.definitions()
1311
1340
  };
1312
1341
  return JSON.stringify(catalog);
1313
1342
  } catch (error) {
@@ -1333,12 +1362,12 @@ var BrowserMCPServer = class {
1333
1362
  const holder = this.#holder(args["holder"]);
1334
1363
  const signal = AbortSignal.any([context.signal, holder.abort.signal]);
1335
1364
  try {
1336
- const slot = await this.#serve(holder, signal);
1365
+ const lease = await this.#serve(holder, signal);
1337
1366
  signal.throwIfAborted();
1338
- if (this.#leases.get(holder)?.value !== slot) throw this.#unavailable(this.#losses.get(slot)?.cause);
1367
+ if (this.#leases.get(holder) !== lease) throw this.#unavailable(this.#departures.get(lease)?.cause);
1339
1368
  const catalog = {
1340
1369
  holder: holder.id,
1341
- tools: slot.toolset.tools.definitions()
1370
+ tools: lease.generation.toolset.tools.definitions()
1342
1371
  };
1343
1372
  return this.#annotate(holder, JSON.stringify(catalog));
1344
1373
  } catch (error) {
@@ -1357,17 +1386,19 @@ var BrowserMCPServer = class {
1357
1386
  const pending = this.#disposals.get(holder.id);
1358
1387
  if (pending !== void 0) return pending;
1359
1388
  holder.abort.abort();
1360
- const token = this.#leases.get(holder);
1389
+ const lease = this.#leases.get(holder);
1361
1390
  this.#leases.delete(holder);
1391
+ if (lease !== void 0) this.#untrack(lease.generation);
1362
1392
  const disposal = Promise.resolve().then(async () => {
1363
1393
  try {
1364
1394
  await this.#grants.get(holder)?.catch(() => void 0);
1365
- await token?.destroy();
1395
+ if (lease !== void 0) await this.#release(holder, lease);
1366
1396
  await Promise.all(this.#drains.get(holder) ?? []);
1397
+ await Promise.all([...this.#notices.get(holder) ?? []].filter((lost) => this.#losses.has(lost.token.value)).map((lost) => this.#endings.get(lost.token.value)?.promise));
1367
1398
  } catch (error) {
1368
1399
  this.#faults.add(error);
1369
1400
  } finally {
1370
- if (this.#holders.get(holder.id) === holder) this.#holders.delete(holder.id);
1401
+ if (this.#holders.get(holder.id) === holder && !this.#retentions.has(holder)) this.#holders.delete(holder.id);
1371
1402
  this.#notices.delete(holder);
1372
1403
  this.#drains.delete(holder);
1373
1404
  }
@@ -1400,9 +1431,9 @@ var BrowserMCPServer = class {
1400
1431
  }
1401
1432
  async #perform(holder, name, args, context) {
1402
1433
  const signal = holder === this.#shared ? context.signal : AbortSignal.any([context.signal, holder.abort.signal]);
1403
- let slot;
1434
+ let lease;
1404
1435
  try {
1405
- slot = await this.#serve(holder, signal);
1436
+ lease = await this.#serve(holder, signal);
1406
1437
  } catch (error) {
1407
1438
  context.signal.throwIfAborted();
1408
1439
  if (holder !== this.#shared && holder.abort.signal.aborted) throw new BrowserError(`${BROWSER_SERVER_UNRESOLVED}: the holder ended during the call`, BROWSER_SERVER_UNRESOLVED);
@@ -1411,7 +1442,7 @@ var BrowserMCPServer = class {
1411
1442
  }
1412
1443
  let result;
1413
1444
  try {
1414
- const execution = slot.toolset.tools.execute({
1445
+ const execution = lease.generation.toolset.tools.execute({
1415
1446
  id: name,
1416
1447
  name,
1417
1448
  arguments: args
@@ -1433,23 +1464,23 @@ var BrowserMCPServer = class {
1433
1464
  context.signal.throwIfAborted();
1434
1465
  if (holder !== this.#shared && holder.abort.signal.aborted) throw new BrowserError(BROWSER_SERVER_UNRESOLVED + ": the holder ended during the call", BROWSER_SERVER_UNRESOLVED);
1435
1466
  if (!result.success) {
1436
- if (this.#leases.get(holder)?.value === slot && !context.signal.aborted) try {
1437
- await this.#race(this.#ping(slot), context.signal);
1467
+ if (this.#leases.get(holder) === lease && !context.signal.aborted) try {
1468
+ await this.#race(this.#ping(lease.token.value), context.signal);
1438
1469
  } catch {}
1439
1470
  context.signal.throwIfAborted();
1440
- if (this.#leases.get(holder)?.value !== slot) {
1441
- const loss = this.#losses.get(slot);
1442
- throw new BrowserError(this.#annotate(holder, describeBrowserServerLoss(BROWSER_SERVER_UNRESOLVED, loss?.cause, loss?.url), slot), BROWSER_SERVER_UNRESOLVED);
1471
+ if (this.#leases.get(holder) !== lease) {
1472
+ const loss = this.#departures.get(lease);
1473
+ throw new BrowserError(this.#annotate(holder, describeBrowserServerLoss(BROWSER_SERVER_UNRESOLVED, loss?.cause, loss?.url), lease), BROWSER_SERVER_UNRESOLVED);
1443
1474
  }
1444
- throw new BrowserError(this.#annotate(holder, result.error, slot));
1475
+ throw new BrowserError(this.#annotate(holder, result.error, lease));
1445
1476
  }
1446
- return typeof result.value === "string" ? this.#annotate(holder, result.value, slot) : result.value;
1477
+ return typeof result.value === "string" ? this.#annotate(holder, result.value, lease) : result.value;
1447
1478
  }
1448
- #annotate(holder, value, slot) {
1479
+ #annotate(holder, value, lease) {
1449
1480
  const notes = [];
1450
1481
  for (const lost of this.#notices.get(holder) ?? []) {
1451
- if (lost === slot) continue;
1452
- const loss = this.#losses.get(lost);
1482
+ if (lost === lease) continue;
1483
+ const loss = this.#departures.get(lost);
1453
1484
  notes.push(describeBrowserServerLoss(BROWSER_SERVER_CRASH, loss?.cause, loss?.url));
1454
1485
  this.#notices.get(holder)?.delete(lost);
1455
1486
  }
@@ -1480,41 +1511,70 @@ var BrowserMCPServer = class {
1480
1511
  if (held !== void 0) return held;
1481
1512
  let granting = this.#grants.get(holder);
1482
1513
  if (granting === void 0) {
1483
- granting = this.#pool.acquire(holder.abort.signal).then(this.#hold.bind(this, holder));
1514
+ granting = Promise.resolve().then(async () => {
1515
+ await Promise.allSettled(this.#drains.get(holder) ?? []);
1516
+ if (this.#retentions.has(holder)) throw this.#unavailable(this.#retentions.get(holder));
1517
+ const token = await this.#pool.acquire(holder.abort.signal);
1518
+ return this.#hold(holder, token);
1519
+ });
1484
1520
  this.#grants.set(holder, granting);
1485
1521
  granting.finally(() => {
1486
1522
  if (this.#grants.get(holder) === granting) this.#grants.delete(holder);
1487
1523
  }).catch(() => void 0);
1488
1524
  }
1489
- const token = await this.#race(granting, signal);
1490
- if (token === void 0) throw this.#ended();
1491
- return token;
1525
+ const lease = await this.#race(granting, signal);
1526
+ if (lease === void 0) throw this.#ended();
1527
+ return lease;
1492
1528
  }
1493
1529
  async #hold(holder, token) {
1530
+ const slot = token.value;
1494
1531
  if (this.#closing !== void 0 || this.#holders.get(holder.id) !== holder || holder.abort.signal.aborted) {
1495
- await token.destroy();
1532
+ token.release();
1533
+ throw this.#ended();
1534
+ }
1535
+ if (!this.#prepared.has(slot)) await this.#ping(slot);
1536
+ if (this.#closing !== void 0 || holder.abort.signal.aborted) {
1537
+ token.release();
1496
1538
  throw this.#ended();
1497
1539
  }
1498
- if (this.#losses.has(token.value)) {
1499
- await token.destroy().catch(() => void 0);
1500
- throw this.#unavailable(this.#losses.get(token.value)?.cause);
1540
+ if (this.#losses.has(slot)) {
1541
+ token.release();
1542
+ throw this.#unavailable(this.#losses.get(slot)?.cause);
1501
1543
  }
1502
- this.#leases.set(holder, token);
1544
+ let generation = this.#prepared.get(slot);
1545
+ this.#prepared.delete(slot);
1546
+ try {
1547
+ generation ??= await this.#build(slot, token);
1548
+ } catch (error) {
1549
+ if (this.#losses.has(slot)) await this.#endings.get(slot)?.promise;
1550
+ else token.release();
1551
+ throw this.#unavailable(error);
1552
+ }
1553
+ const lease = {
1554
+ token,
1555
+ generation
1556
+ };
1557
+ if (this.#closing !== void 0 || this.#holders.get(holder.id) !== holder || holder.abort.signal.aborted || this.#losses.has(slot)) {
1558
+ await this.#release(holder, lease);
1559
+ throw this.#ended();
1560
+ }
1561
+ this.#leases.set(holder, lease);
1562
+ this.#observe(slot, generation);
1503
1563
  if (holder === this.#shared) {
1504
- const tools = token.value.toolset.tools;
1564
+ const tools = generation.toolset.tools;
1505
1565
  const mirrored = {
1506
- token,
1507
- added: this.#mirror.bind(this, token),
1508
- removed: this.#withdraw.bind(this, token),
1509
- cleared: this.#clear.bind(this, token)
1566
+ lease,
1567
+ added: this.#mirror.bind(this, lease),
1568
+ removed: this.#withdraw.bind(this, lease),
1569
+ cleared: this.#clear.bind(this, lease)
1510
1570
  };
1511
1571
  this.#mirrored = mirrored;
1512
- for (const tool of tools.tools()) this.#mirror(token, tool);
1572
+ for (const tool of tools.tools()) this.#mirror(lease, tool);
1513
1573
  tools.emitter.on("add", mirrored.added);
1514
1574
  tools.emitter.on("remove", mirrored.removed);
1515
1575
  tools.emitter.on("clear", mirrored.cleared);
1516
1576
  }
1517
- return token;
1577
+ return lease;
1518
1578
  }
1519
1579
  async #serve(holder, signal) {
1520
1580
  if (this.#closing !== void 0 || holder.abort.signal.aborted) throw this.#ended();
@@ -1522,16 +1582,16 @@ var BrowserMCPServer = class {
1522
1582
  if (this.#closing !== void 0 || holder.abort.signal.aborted) throw this.#ended();
1523
1583
  const held = this.#leases.get(holder);
1524
1584
  if (held !== void 0) try {
1525
- await this.#race(this.#ping(held.value), signal);
1526
- if (this.#leases.get(holder) === held) return held.value;
1585
+ await this.#race(this.#ping(held.token.value), signal);
1586
+ if (this.#leases.get(holder) === held) return held;
1527
1587
  } catch {
1528
1588
  signal.throwIfAborted();
1529
1589
  if (holder.abort.signal.aborted) throw this.#ended();
1530
1590
  }
1531
1591
  try {
1532
- const token = await this.#grant(holder, signal);
1533
- if (this.#leases.get(holder) !== token) throw this.#unavailable(this.#losses.get(token.value)?.cause);
1534
- return token.value;
1592
+ const lease = await this.#grant(holder, signal);
1593
+ if (this.#leases.get(holder) !== lease) throw this.#unavailable(this.#departures.get(lease)?.cause);
1594
+ return lease;
1535
1595
  } catch (error) {
1536
1596
  signal.throwIfAborted();
1537
1597
  if (holder.abort.signal.aborted || isPoolError(error) && error.code === "destroyed") throw this.#ended();
@@ -1562,78 +1622,211 @@ var BrowserMCPServer = class {
1562
1622
  }
1563
1623
  #lose(slot, cause) {
1564
1624
  if (this.#closing !== void 0 || this.#losses.has(slot)) return;
1565
- this.#losses.set(slot, {
1566
- cause,
1567
- url: slot.toolset.view.url
1568
- });
1625
+ this.#losses.set(slot, { cause });
1569
1626
  this.#failure = cause;
1570
- for (const [holder, token] of this.#leases) {
1571
- if (token.value !== slot || this.#holders.get(holder.id) !== holder) continue;
1572
- if (holder === this.#shared) this.#unmirror();
1573
- this.#leases.delete(holder);
1574
- const notices = this.#notices.get(holder) ?? /* @__PURE__ */ new Set();
1575
- notices.add(slot);
1576
- this.#notices.set(holder, notices);
1577
- const drain = token.destroy();
1578
- const drains = this.#drains.get(holder) ?? /* @__PURE__ */ new Set();
1579
- drains.add(drain);
1580
- this.#drains.set(holder, drains);
1581
- drain.finally(() => drains.delete(drain)).catch(() => void 0);
1627
+ for (const [holder, lease] of this.#leases) {
1628
+ if (lease.token.value !== slot) continue;
1629
+ this.#detach(holder, lease, cause);
1582
1630
  }
1631
+ const watch = this.#watches.get(slot);
1632
+ this.#unwatch(slot);
1633
+ watch?.resolve(cause);
1634
+ }
1635
+ #detach(holder, lease, cause) {
1636
+ if (this.#leases.get(holder) !== lease || this.#holders.get(holder.id) !== holder) return;
1637
+ this.#departures.set(lease, {
1638
+ cause,
1639
+ url: lease.generation.toolset.view.url
1640
+ });
1641
+ if (holder === this.#shared) this.#unmirror();
1642
+ this.#leases.delete(holder);
1643
+ this.#untrack(lease.generation);
1644
+ const notices = this.#notices.get(holder) ?? /* @__PURE__ */ new Set();
1645
+ notices.add(lease);
1646
+ this.#notices.set(holder, notices);
1583
1647
  }
1584
1648
  #watch(slot, signal) {
1585
1649
  const loss = Promise.withResolvers();
1586
1650
  const disconnect = this.#disconnect.bind(this, slot);
1587
- const page = this.#track.bind(this, slot);
1588
1651
  const subscription = addAbortListener(signal, this.#unwatch.bind(this, slot));
1589
1652
  this.#watches.set(slot, {
1590
1653
  resolve: loss.resolve,
1591
1654
  disconnect,
1592
- page,
1593
- subscription,
1594
- crashes: /* @__PURE__ */ new Map()
1655
+ subscription
1595
1656
  });
1596
1657
  slot.browser.emitter.on("disconnect", disconnect);
1597
- slot.context.emitter.on("page", page);
1598
- for (const current of slot.context.pages()) this.#track(slot, current);
1599
1658
  if (signal.aborted) this.#unwatch(slot);
1600
1659
  else if (slot.browser.status !== "connected") this.#disconnect(slot);
1601
1660
  return loss.promise;
1602
1661
  }
1603
- #track(slot, page) {
1604
- const watch = this.#watches.get(slot);
1662
+ #observe(slot, generation) {
1663
+ if (this.#observed.has(generation)) return;
1664
+ const page = this.#track.bind(this, slot, generation);
1665
+ this.#observed.set(generation, {
1666
+ page,
1667
+ crashes: /* @__PURE__ */ new Map()
1668
+ });
1669
+ generation.context.emitter.on("page", page);
1670
+ for (const current of generation.context.pages()) this.#track(slot, generation, current);
1671
+ }
1672
+ #track(slot, generation, page) {
1673
+ const watch = this.#observed.get(generation);
1605
1674
  if (watch === void 0 || watch.crashes.has(page)) return;
1606
- const crash = this.#createCrash(slot, page);
1675
+ const crash = this.#crash.bind(this, slot, generation, page);
1607
1676
  page.emitter.on("crash", crash);
1608
- this.#watches.set(slot, {
1677
+ this.#observed.set(generation, {
1609
1678
  ...watch,
1610
1679
  crashes: new Map([...watch.crashes, [page, crash]])
1611
1680
  });
1612
1681
  }
1613
- #createCrash(slot, page) {
1614
- return () => {
1615
- if (page === slot.toolset.view) this.#report(slot, /* @__PURE__ */ new Error("The current page renderer crashed"));
1616
- };
1682
+ #crash(slot, generation, page) {
1683
+ if (this.#prepared.get(slot) === generation) {
1684
+ this.#prepared.delete(slot);
1685
+ this.#untrack(generation);
1686
+ this.#clean(slot, generation).catch(this.#fault.bind(this));
1687
+ return;
1688
+ }
1689
+ const held = [...this.#leases].find(([, lease]) => lease.generation === generation);
1690
+ if (held === void 0 || page !== generation.toolset.view) return;
1691
+ const [holder, lease] = held;
1692
+ this.#detach(holder, lease, /* @__PURE__ */ new Error("The current page renderer crashed"));
1693
+ const drain = this.#release(holder, lease);
1694
+ const drains = this.#drains.get(holder) ?? /* @__PURE__ */ new Set();
1695
+ drains.add(drain);
1696
+ this.#drains.set(holder, drains);
1697
+ drain.finally(() => drains.delete(drain)).catch(this.#fault.bind(this));
1698
+ }
1699
+ #untrack(generation) {
1700
+ const watch = this.#observed.get(generation);
1701
+ if (watch === void 0) return;
1702
+ this.#observed.delete(generation);
1703
+ generation.context.emitter.off("page", watch.page);
1704
+ for (const [page, crash] of watch.crashes) page.emitter.off("crash", crash);
1617
1705
  }
1618
1706
  #disconnect(slot) {
1619
- this.#report(slot, /* @__PURE__ */ new Error(slot.browser.pid === void 0 ? "The browser process exited" : "The browser transport disconnected"));
1620
- }
1621
- #report(slot, cause) {
1622
- const watch = this.#watches.get(slot);
1623
- if (watch === void 0) return;
1624
- this.#unwatch(slot);
1625
- this.#lose(slot, cause);
1626
- watch.resolve(cause);
1707
+ this.#lose(slot, /* @__PURE__ */ new Error(slot.browser.pid === void 0 ? "The browser process exited" : "The browser transport disconnected"));
1627
1708
  }
1628
1709
  #unwatch(slot) {
1629
1710
  const watch = this.#watches.get(slot);
1630
1711
  if (watch === void 0) return;
1631
1712
  this.#watches.delete(slot);
1632
1713
  slot.browser.emitter.off("disconnect", watch.disconnect);
1633
- slot.context.emitter.off("page", watch.page);
1634
- for (const [page, crash] of watch.crashes) page.emitter.off("crash", crash);
1635
1714
  watch.subscription[Symbol.dispose]();
1636
1715
  }
1716
+ async #release(holder, lease) {
1717
+ const slot = lease.token.value;
1718
+ const removed = await this.#clean(slot, lease.generation);
1719
+ const disposal = lease.generation.context.disposal;
1720
+ if (!removed) this.#retentions.set(holder, /* @__PURE__ */ new Error("The context downloads directory remains owned"));
1721
+ if (disposal?.confirmed !== true && !this.#losses.has(slot) && this.#closing === void 0) {
1722
+ if (![...this.#leases.values()].some((held) => held.token.value === slot && held !== lease) && (this.#building.get(slot)?.size ?? 0) === 0) lease.token.release();
1723
+ this.#lose(slot, disposal?.error ?? /* @__PURE__ */ new Error("The context disposal is unconfirmed"));
1724
+ await this.#endings.get(slot)?.promise;
1725
+ } else if (removed) lease.token.release();
1726
+ }
1727
+ #clean(slot, generation) {
1728
+ let cleaning = this.#cleaning.get(generation);
1729
+ if (cleaning !== void 0) return cleaning;
1730
+ cleaning = Promise.resolve().then(async () => {
1731
+ await generation.toolset.destroy().catch(this.#fault.bind(this));
1732
+ await generation.context.close().catch((error) => {
1733
+ if (!this.#losses.has(slot) && slot.browser.status === "connected") this.#fault(error);
1734
+ });
1735
+ try {
1736
+ await rm(generation.directory, {
1737
+ recursive: true,
1738
+ force: true
1739
+ });
1740
+ this.#owned.get(slot)?.delete(generation);
1741
+ return true;
1742
+ } catch (error) {
1743
+ this.#fault(error);
1744
+ return false;
1745
+ }
1746
+ });
1747
+ this.#cleaning.set(generation, cleaning);
1748
+ return cleaning;
1749
+ }
1750
+ #build(slot, token) {
1751
+ const construction = this.#construct(slot, token);
1752
+ const building = this.#building.get(slot) ?? /* @__PURE__ */ new Set();
1753
+ building.add(construction);
1754
+ this.#building.set(slot, building);
1755
+ construction.finally(() => building.delete(construction)).catch(() => void 0);
1756
+ return construction;
1757
+ }
1758
+ async #construct(slot, token) {
1759
+ const directory = join(slot.profile, "contexts", randomUUID(), "downloads");
1760
+ let context;
1761
+ let toolset;
1762
+ try {
1763
+ await mkdir(directory, { recursive: true });
1764
+ let page;
1765
+ try {
1766
+ context = await slot.browser.isolate({
1767
+ reference: this.#reference,
1768
+ downloads: { path: directory },
1769
+ ...this.#viewport === void 0 ? {} : { emulation: { viewport: this.#viewport } }
1770
+ });
1771
+ page = await context.create();
1772
+ } catch (error) {
1773
+ if (token !== void 0) {
1774
+ if (![...this.#leases.values()].some((lease) => lease.token.value === slot) && (this.#building.get(slot)?.size ?? 0) === 1) token.release();
1775
+ this.#lose(slot, error);
1776
+ }
1777
+ throw error;
1778
+ }
1779
+ toolset = createBrowserToolset(page, {
1780
+ context,
1781
+ journeys: {
1782
+ store: createFileBrowserJourneyStore({ root: this.#root }),
1783
+ runs: createFileBrowserRunStore({ root: this.#root }),
1784
+ readonly: this.#readonly
1785
+ }
1786
+ });
1787
+ await toolset.start();
1788
+ const generation = {
1789
+ context,
1790
+ toolset,
1791
+ directory
1792
+ };
1793
+ const owned = this.#owned.get(slot) ?? /* @__PURE__ */ new Set();
1794
+ owned.add(generation);
1795
+ this.#owned.set(slot, owned);
1796
+ return generation;
1797
+ } catch (error) {
1798
+ await toolset?.destroy().catch(this.#fault.bind(this));
1799
+ await context?.close().catch((failure) => {
1800
+ if (!this.#losses.has(slot) && slot.browser.status === "connected" && this.#closing === void 0 && !isCDPConnectionError(failure)) this.#fault(failure);
1801
+ });
1802
+ await rm(directory, {
1803
+ recursive: true,
1804
+ force: true
1805
+ }).catch(this.#fault.bind(this));
1806
+ throw error;
1807
+ }
1808
+ }
1809
+ async #destroyRecord(slot) {
1810
+ if (isCDPTimeoutError(this.#losses.get(slot)?.cause) && slot.browser.pid !== void 0) try {
1811
+ process.kill(slot.browser.pid, "SIGKILL");
1812
+ } catch (error) {
1813
+ if (!isError(error) || !("code" in error) || error.code !== "ESRCH") this.#faults.add(error);
1814
+ }
1815
+ try {
1816
+ await Promise.allSettled(this.#building.get(slot) ?? []);
1817
+ const prepared = this.#prepared.get(slot);
1818
+ if (prepared !== void 0) this.#untrack(prepared);
1819
+ this.#prepared.delete(slot);
1820
+ await Promise.all([...this.#owned.get(slot) ?? []].map((generation) => this.#clean(slot, generation)));
1821
+ await this.#destroySlot(slot.profile, slot.browser);
1822
+ this.#owned.delete(slot);
1823
+ this.#building.delete(slot);
1824
+ this.#endings.get(slot)?.resolve();
1825
+ } catch (error) {
1826
+ this.#endings.get(slot)?.reject(error);
1827
+ throw error;
1828
+ }
1829
+ }
1637
1830
  #fault(error) {
1638
1831
  this.#faults.add(error);
1639
1832
  }
@@ -1642,7 +1835,7 @@ var BrowserMCPServer = class {
1642
1835
  if (this.#stranded !== void 0) throw new BrowserError(isError(this.#stranded) ? this.#stranded.message : String(this.#stranded), BROWSER_SERVER_TEARDOWN, isBrowserError(this.#stranded) ? this.#stranded.context : void 0);
1643
1836
  const profile = join(this.#root, ".profiles", formatBrowserLockEntry(process.pid, randomUUID()));
1644
1837
  let browser;
1645
- let toolset;
1838
+ let slot;
1646
1839
  try {
1647
1840
  await mkdir(profile);
1648
1841
  this.#folders.add(profile);
@@ -1664,42 +1857,26 @@ var BrowserMCPServer = class {
1664
1857
  }));
1665
1858
  await rename(`${record}.tmp`, record);
1666
1859
  }
1667
- const context = await browser.isolate({
1668
- reference: this.#reference,
1669
- downloads: { path: join(profile, "downloads") },
1670
- ...this.#viewport === void 0 ? {} : { emulation: { viewport: this.#viewport } }
1671
- });
1672
- const page = await context.create();
1673
- toolset = createBrowserToolset(page, {
1674
- context,
1675
- journeys: {
1676
- store: createFileBrowserJourneyStore({ root: this.#root }),
1677
- runs: createFileBrowserRunStore({ root: this.#root }),
1678
- readonly: this.#readonly
1679
- }
1680
- });
1681
- await toolset.start();
1682
- return {
1860
+ slot = {
1683
1861
  browser,
1684
- profile,
1685
- context,
1686
- toolset
1862
+ profile
1687
1863
  };
1864
+ const ending = Promise.withResolvers();
1865
+ ending.promise.catch(() => void 0);
1866
+ this.#endings.set(slot, ending);
1867
+ const generation = await this.#build(slot);
1868
+ this.#prepared.set(slot, generation);
1869
+ this.#observe(slot, generation);
1870
+ return slot;
1688
1871
  } catch (error) {
1689
- if (this.#folders.has(profile)) await this.#destroySlot(profile, browser, toolset).catch((failure) => {
1872
+ if (this.#folders.has(profile)) await (slot === void 0 ? this.#destroySlot(profile, browser) : this.#destroyRecord(slot)).catch((failure) => {
1690
1873
  this.#stranded = failure;
1691
1874
  });
1692
1875
  if (this.#closing === void 0) this.#write(`browse: ${describeBrowserServerLoss(BROWSER_SERVER_LAUNCH, error)}`);
1693
1876
  throw error;
1694
1877
  }
1695
1878
  }
1696
- async #destroySlot(profile, browser, toolset, cause) {
1697
- if (isCDPTimeoutError(cause) && browser?.pid !== void 0) try {
1698
- process.kill(browser.pid, "SIGKILL");
1699
- } catch (error) {
1700
- if (!isError(error) || !("code" in error) || error.code !== "ESRCH") this.#faults.add(error);
1701
- }
1702
- await toolset?.destroy().catch((error) => this.#faults.add(error));
1879
+ async #destroySlot(profile, browser) {
1703
1880
  try {
1704
1881
  await browser?.destroy();
1705
1882
  } catch (error) {
@@ -1775,27 +1952,27 @@ var BrowserMCPServer = class {
1775
1952
  #issue() {
1776
1953
  return `e${++this.#references}`;
1777
1954
  }
1778
- #mirror(token, tool) {
1779
- if (this.#leases.get(this.#shared) !== token || this.#vocabulary.has(tool.name)) return;
1955
+ #mirror(lease, tool) {
1956
+ if (this.#leases.get(this.#shared) !== lease || this.#vocabulary.has(tool.name)) return;
1780
1957
  this.#tools.add(createTool({
1781
1958
  ...toolToDefinition(tool),
1782
1959
  execute: this.#forward.bind(this, this.#shared, tool.name)
1783
1960
  }));
1784
1961
  }
1785
- #withdraw(token, tool) {
1786
- if (this.#leases.get(this.#shared) !== token || this.#vocabulary.has(tool.name)) return;
1787
- const current = token.value.toolset.tools.tool(tool.name);
1962
+ #withdraw(lease, tool) {
1963
+ if (this.#leases.get(this.#shared) !== lease || this.#vocabulary.has(tool.name)) return;
1964
+ const current = lease.generation.toolset.tools.tool(tool.name);
1788
1965
  if (current === void 0) this.#tools.remove(tool.name);
1789
- else this.#mirror(token, current);
1966
+ else this.#mirror(lease, current);
1790
1967
  }
1791
- #clear(token, tools) {
1792
- for (const tool of tools) this.#withdraw(token, tool);
1968
+ #clear(lease, tools) {
1969
+ for (const tool of tools) this.#withdraw(lease, tool);
1793
1970
  }
1794
1971
  #unmirror() {
1795
1972
  const mirrored = this.#mirrored;
1796
1973
  if (mirrored === void 0) return;
1797
1974
  this.#mirrored = void 0;
1798
- const tools = mirrored.token.value.toolset.tools;
1975
+ const tools = mirrored.lease.generation.toolset.tools;
1799
1976
  for (const tool of tools.tools()) if (!this.#vocabulary.has(tool.name)) this.#tools.remove(tool.name);
1800
1977
  tools.emitter.off("add", mirrored.added);
1801
1978
  tools.emitter.off("remove", mirrored.removed);
@@ -2226,11 +2403,14 @@ var FileBrowserJourneyStore = class {
2226
2403
  /**
2227
2404
  * Persists runs and captures only in directories allocated by this instance.
2228
2405
  * @remarks Requires an existing root. Reopened stores can read and delete saved runs.
2406
+ * Allocations of one journey share a process-local queue across instances; the filesystem lock
2407
+ * still refuses conflicting mutations and allocations from another process.
2229
2408
  * @example
2230
2409
  * const store = new FileBrowserRunStore({ root: directory })
2231
2410
  * const slot = await store.open('check-ready')
2232
2411
  */
2233
- var FileBrowserRunStore = class {
2412
+ var FileBrowserRunStore = class FileBrowserRunStore {
2413
+ static #allocations = /* @__PURE__ */ new Map();
2234
2414
  #files;
2235
2415
  #slots = /* @__PURE__ */ new WeakMap();
2236
2416
  #directories = /* @__PURE__ */ new Set();
@@ -2258,16 +2438,27 @@ var FileBrowserRunStore = class {
2258
2438
  async open(name, options) {
2259
2439
  options?.signal?.throwIfAborted();
2260
2440
  this.#files.validateName(name);
2261
- return this.#files.lock(this.#files.resolvePath(name, BROWSER_JOURNEY_LOCK_DIRECTORY), async () => {
2262
- const directory = await this.#files.allocate(this.#files.resolvePath(name, BROWSER_RUN_DIRECTORY), generateBrowserRunId, options);
2263
- const slot = Object.freeze({
2264
- id: directory.id,
2265
- directory: directory.path
2266
- });
2267
- this.#directories.add(directory.path);
2268
- this.#slots.set(slot, directory.path);
2269
- return slot;
2270
- }, options);
2441
+ const path = this.#files.resolvePath(name, BROWSER_JOURNEY_LOCK_DIRECTORY);
2442
+ const previous = FileBrowserRunStore.#allocations.get(path);
2443
+ const allocation = Promise.withResolvers();
2444
+ FileBrowserRunStore.#allocations.set(path, allocation.promise);
2445
+ try {
2446
+ await previous;
2447
+ options?.signal?.throwIfAborted();
2448
+ return await this.#files.lock(path, async () => {
2449
+ const directory = await this.#files.allocate(this.#files.resolvePath(name, BROWSER_RUN_DIRECTORY), generateBrowserRunId, options);
2450
+ const slot = Object.freeze({
2451
+ id: directory.id,
2452
+ directory: directory.path
2453
+ });
2454
+ this.#directories.add(directory.path);
2455
+ this.#slots.set(slot, directory.path);
2456
+ return slot;
2457
+ }, options);
2458
+ } finally {
2459
+ allocation.resolve();
2460
+ if (FileBrowserRunStore.#allocations.get(path) === allocation.promise) FileBrowserRunStore.#allocations.delete(path);
2461
+ }
2271
2462
  }
2272
2463
  async get(name, id, options) {
2273
2464
  options?.signal?.throwIfAborted();
@@ -2477,6 +2668,7 @@ var Browser = class {
2477
2668
  #servingPid;
2478
2669
  #profile;
2479
2670
  #contexts = [];
2671
+ #isolating = /* @__PURE__ */ new Set();
2480
2672
  #destroyed = false;
2481
2673
  #connecting = new BrowserTransition();
2482
2674
  #disconnecting = new BrowserTransition();
@@ -2563,33 +2755,13 @@ var Browser = class {
2563
2755
  return [...this.#contexts];
2564
2756
  }
2565
2757
  async isolate(options) {
2566
- if (this.#destroyed) throw new BrowserDestroyedError();
2567
- const client = this.#client;
2568
- if (this.#status !== "connected" || client === void 0) throw new BrowserNotConnectedError();
2569
- validateBrowserContextOptions(options);
2570
- const params = { disposeOnDetach: false };
2571
- if (options?.proxy !== void 0) {
2572
- params["proxyServer"] = options.proxy.server;
2573
- if (options.proxy.bypass !== void 0) params["proxyBypassList"] = options.proxy.bypass.join(",");
2574
- }
2575
- if (options?.origins !== void 0) params["originsWithUniversalNetworkAccess"] = [...options.origins];
2576
- const result = await client.send("Target.createBrowserContext", params);
2577
- if (!isRecord(result) || !isString(result["browserContextId"])) throw new BrowserConnectionError("Failed to create isolated browser context");
2578
- const id = result["browserContextId"];
2579
- const context = new BrowserContext(client, id, options?.emulation?.viewport ?? this.#options.viewport, createBrowserWriter(), options?.emulation, options?.downloads, options);
2758
+ const attempt = this.#isolate(options);
2759
+ this.#isolating.add(attempt);
2580
2760
  try {
2581
- if (options?.downloads !== void 0) await client.send("Browser.setDownloadBehavior", {
2582
- behavior: options.downloads.named === true ? "allowAndName" : "allow",
2583
- browserContextId: id,
2584
- downloadPath: options.downloads.path,
2585
- eventsEnabled: true
2586
- });
2587
- } catch (error) {
2588
- await context.close().catch(() => void 0);
2589
- throw error;
2761
+ return await attempt;
2762
+ } finally {
2763
+ this.#isolating.delete(attempt);
2590
2764
  }
2591
- this.#registerContext(context);
2592
- return context;
2593
2765
  }
2594
2766
  async create(options) {
2595
2767
  if (this.#destroyed) throw new BrowserDestroyedError();
@@ -2624,6 +2796,37 @@ var Browser = class {
2624
2796
  this.#shutdown = shutdown;
2625
2797
  return shutdown;
2626
2798
  }
2799
+ async #isolate(options) {
2800
+ if (this.#destroyed) throw new BrowserDestroyedError();
2801
+ const client = this.#client;
2802
+ if (this.#status !== "connected" || client === void 0) throw new BrowserNotConnectedError();
2803
+ validateBrowserContextOptions(options);
2804
+ const params = { disposeOnDetach: false };
2805
+ if (options?.proxy !== void 0) {
2806
+ params["proxyServer"] = options.proxy.server;
2807
+ if (options.proxy.bypass !== void 0) params["proxyBypassList"] = options.proxy.bypass.join(",");
2808
+ }
2809
+ if (options?.origins !== void 0) params["originsWithUniversalNetworkAccess"] = [...options.origins];
2810
+ const result = await client.send("Target.createBrowserContext", params);
2811
+ if (!isRecord(result) || !isString(result["browserContextId"])) throw new BrowserConnectionError("Failed to create isolated browser context");
2812
+ const id = result["browserContextId"];
2813
+ const context = new BrowserContext(client, id, options?.emulation?.viewport ?? this.#options.viewport, createBrowserWriter(), options?.emulation, options?.downloads, options);
2814
+ try {
2815
+ if (this.#destroyed || this.#client !== client) throw new BrowserDestroyedError();
2816
+ if (options?.downloads !== void 0) await client.send("Browser.setDownloadBehavior", {
2817
+ behavior: options.downloads.named === true ? "allowAndName" : "allow",
2818
+ browserContextId: id,
2819
+ downloadPath: options.downloads.path,
2820
+ eventsEnabled: true
2821
+ });
2822
+ if (this.#destroyed || this.#client !== client) throw new BrowserDestroyedError();
2823
+ } catch (error) {
2824
+ await context.close().catch(() => void 0);
2825
+ throw error;
2826
+ }
2827
+ this.#registerContext(context);
2828
+ return context;
2829
+ }
2627
2830
  async #establish() {
2628
2831
  const disconnecting = this.#disconnecting.pending;
2629
2832
  if (disconnecting !== void 0) await disconnecting;
@@ -3097,6 +3300,7 @@ var Browser = class {
3097
3300
  }
3098
3301
  }
3099
3302
  async #settle() {
3303
+ await Promise.allSettled(this.#isolating);
3100
3304
  await this.#connecting.pending?.catch(() => void 0);
3101
3305
  await this.#disconnecting.pending?.catch(() => void 0);
3102
3306
  await this.#settleExit();
@@ -3285,6 +3489,6 @@ var Browser = class {
3285
3489
  }
3286
3490
  };
3287
3491
  //#endregion
3288
- export { BROWSER_CDP_LIST_PATH, BROWSER_CDP_PROTOCOL, BROWSER_CDP_VERSION_PATH, BROWSER_DEFAULT_CDP_PORT, BROWSER_DEFAULT_HOST, BROWSER_DEVTOOLS_PATTERN, BROWSER_DRAIN_INTERVAL_MS, BROWSER_ENGINE_HINTS, BROWSER_ENV_PATH_KEYS, BROWSER_EXECUTABLE_NAMES, BROWSER_EXECUTABLE_PATHS, BROWSER_FILE_STORE_LIMIT, BROWSER_HEADLESS_ARG, BROWSER_JOURNEY_LOCK_ATTEMPTS, BROWSER_JOURNEY_LOCK_DIRECTORY, BROWSER_JOURNEY_REVISION_FILE, BROWSER_JOURNEY_SNAPSHOT_FILE, BROWSER_KILL_GRACE_MS, BROWSER_LAUNCH_ARGS, BROWSER_PORT_PROBE_TIMEOUT_MS, BROWSER_PROCESS_EXIT_CAUSE, BROWSER_PROFILE_PREFIX, BROWSER_RUN_DIRECTORY, BROWSER_RUN_FILE, BROWSER_SERVER_BUSY, BROWSER_SERVER_COPY, BROWSER_SERVER_CRASH, BROWSER_SERVER_EXHAUSTED, BROWSER_SERVER_HOLDER, BROWSER_SERVER_LAUNCH, BROWSER_SERVER_OPTIONS, BROWSER_SERVER_POOL_LIMIT, BROWSER_SERVER_POOL_SIZE, BROWSER_SERVER_RECORD, BROWSER_SERVER_RESTARTS, BROWSER_SERVER_SWEEP, BROWSER_SERVER_TEARDOWN, BROWSER_SERVER_UNAVAILABLE, BROWSER_SERVER_UNRESOLVED, BROWSER_STORE_CACHE_DIRS, BROWSER_STORE_DEFAULT_DIRS, BROWSER_STORE_ENV_KEY, BROWSER_STORE_GLOBS, BROWSER_STORE_LINK_NAME, BROWSER_TRANSPORT_LOSS_CAUSE, BROWSER_TRANSPORT_LOSS_DEFER_MS, BROWSER_WINDOWS_ROOT_FALLBACKS, BROWSER_WINDOWS_SUFFIXES, Browser, BrowserDestroyedError, BrowserMCPServer, BrowserNotConnectedError, FileBrowserJourneyStore, FileBrowserRunStore, FileBrowserWriter, WebSocketCDPTransport, browserToEngine, buildInstallPaths, buildStoreBases, buildWindowsRoots, createBrowser, createBrowserMCPServer, createBrowserProfile, createBrowserWriter, createCDPTransport, createFileBrowserJourneyStore, createFileBrowserRunStore, describeBrowserServerLoss, fetchCDPTargets, findEnvOverrides, findInstallPaths, findStorePaths, findSystemBrowser, findSystemBrowsers, formatBrowserLockEntry, isBrowserDestroyedError, isBrowserNotConnectedError, launchBrowserProcess, normalizeExecutablePath, parseBrowserEngine, parseBrowserLockEntry, parseBrowserProfileRecord, parseBrowserViewport, probePathNames, probeProcess, readBrowserEndpoint, readFirstLine, removeBrowserProfile };
3492
+ export { BROWSER_CDP_LIST_PATH, BROWSER_CDP_PROTOCOL, BROWSER_CDP_VERSION_PATH, BROWSER_DEFAULT_CDP_PORT, BROWSER_DEFAULT_HOST, BROWSER_DEVTOOLS_PATTERN, BROWSER_DRAIN_INTERVAL_MS, BROWSER_ENGINE_HINTS, BROWSER_ENV_PATH_KEYS, BROWSER_EXECUTABLE_NAMES, BROWSER_EXECUTABLE_PATHS, BROWSER_FILE_STORE_LIMIT, BROWSER_HEADLESS_ARG, BROWSER_JOURNEY_LOCK_ATTEMPTS, BROWSER_JOURNEY_LOCK_DIRECTORY, BROWSER_JOURNEY_REVISION_FILE, BROWSER_JOURNEY_SNAPSHOT_FILE, BROWSER_KILL_GRACE_MS, BROWSER_LAUNCH_ARGS, BROWSER_PORT_PROBE_TIMEOUT_MS, BROWSER_PROCESS_EXIT_CAUSE, BROWSER_PROFILE_PREFIX, BROWSER_RUN_DIRECTORY, BROWSER_RUN_FILE, BROWSER_SERVER_BUSY, BROWSER_SERVER_CONTEXTS, BROWSER_SERVER_CONTEXTS_LIMIT, BROWSER_SERVER_COPY, BROWSER_SERVER_CRASH, BROWSER_SERVER_EXHAUSTED, BROWSER_SERVER_HOLDER, BROWSER_SERVER_LAUNCH, BROWSER_SERVER_OPTIONS, BROWSER_SERVER_POOL_LIMIT, BROWSER_SERVER_POOL_SIZE, BROWSER_SERVER_RECORD, BROWSER_SERVER_RESTARTS, BROWSER_SERVER_SWEEP, BROWSER_SERVER_TEARDOWN, BROWSER_SERVER_UNAVAILABLE, BROWSER_SERVER_UNRESOLVED, BROWSER_STORE_CACHE_DIRS, BROWSER_STORE_DEFAULT_DIRS, BROWSER_STORE_ENV_KEY, BROWSER_STORE_GLOBS, BROWSER_STORE_LINK_NAME, BROWSER_TRANSPORT_LOSS_CAUSE, BROWSER_TRANSPORT_LOSS_DEFER_MS, BROWSER_WINDOWS_ROOT_FALLBACKS, BROWSER_WINDOWS_SUFFIXES, Browser, BrowserDestroyedError, BrowserMCPServer, BrowserNotConnectedError, FileBrowserJourneyStore, FileBrowserRunStore, FileBrowserWriter, WebSocketCDPTransport, browserToEngine, buildInstallPaths, buildStoreBases, buildWindowsRoots, createBrowser, createBrowserMCPServer, createBrowserProfile, createBrowserWriter, createCDPTransport, createFileBrowserJourneyStore, createFileBrowserRunStore, describeBrowserServerLoss, fetchCDPTargets, findEnvOverrides, findInstallPaths, findStorePaths, findSystemBrowser, findSystemBrowsers, formatBrowserLockEntry, isBrowserDestroyedError, isBrowserNotConnectedError, launchBrowserProcess, normalizeExecutablePath, parseBrowserEngine, parseBrowserLockEntry, parseBrowserProfileRecord, parseBrowserViewport, probePathNames, probeProcess, readBrowserEndpoint, readFirstLine, removeBrowserProfile };
3289
3493
 
3290
3494
  //# sourceMappingURL=index.js.map