negotium 0.1.41 → 0.1.42

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.
@@ -2571,7 +2571,7 @@ var init_claude_provider = __esm(async () => {
2571
2571
  });
2572
2572
 
2573
2573
  // ../../packages/core/src/version.ts
2574
- var NEGOTIUM_VERSION = "0.1.41";
2574
+ var NEGOTIUM_VERSION = "0.1.42";
2575
2575
 
2576
2576
  // ../../packages/core/src/agents/codex-native-multi-agent.ts
2577
2577
  import { spawn as spawn3 } from "child_process";
@@ -9628,7 +9628,15 @@ var init_self_schedules = __esm(async () => {
9628
9628
  });
9629
9629
 
9630
9630
  // ../../packages/core/src/platform/playwright/manager-utils.ts
9631
- import { resolve as resolve9 } from "path";
9631
+ function isLiveOwnedChildProcess(current2, expected) {
9632
+ return current2?.process === expected && expected.exitCode === null && expected.signalCode === null && !expected.killed;
9633
+ }
9634
+ function matchesSpawnedBrowserHealth(health, expectedSpawnNonce) {
9635
+ if (!health || typeof health !== "object")
9636
+ return false;
9637
+ const candidate = health;
9638
+ return candidate.ok === true && candidate.name === "negotium-browser-gateway" && candidate.spawnNonce === expectedSpawnNonce;
9639
+ }
9632
9640
  function selectIdleEvictionKey(candidates, pinnedKeys, busyKeys, now, maxIdleMs) {
9633
9641
  const pinned = new Set(pinnedKeys);
9634
9642
  const busy = new Set(busyKeys);
@@ -9644,22 +9652,10 @@ function selectIdleEvictionKey(candidates, pinnedKeys, busyKeys, now, maxIdleMs)
9644
9652
  }
9645
9653
  return oldest?.key ?? null;
9646
9654
  }
9647
- function selectReusablePort(minPort, maxPort, reservedPorts, isOccupied) {
9648
- for (let port = minPort;port <= maxPort; port++) {
9649
- if (reservedPorts.has(port) || isOccupied(port))
9650
- continue;
9651
- return port;
9652
- }
9653
- return null;
9654
- }
9655
9655
  function extractUserDataDirArg(cmdline) {
9656
9656
  const match = cmdline.match(/--user-data-dir(?:\s+|=)(\S+)/);
9657
9657
  return match ? match[1] : null;
9658
9658
  }
9659
- function browserProcessMatchesExpectedProfile(cmdline, expectedUserDataDir) {
9660
- const actualUserDataDir = extractUserDataDirArg(cmdline);
9661
- return actualUserDataDir !== null && resolve9(actualUserDataDir) === resolve9(expectedUserDataDir);
9662
- }
9663
9659
  function waitForChildProcessExit(proc, timeoutMs) {
9664
9660
  if (proc.exitCode !== null || proc.signalCode !== null)
9665
9661
  return Promise.resolve(true);
@@ -9683,73 +9679,47 @@ function waitForChildProcessExit(proc, timeoutMs) {
9683
9679
  finish(true);
9684
9680
  });
9685
9681
  }
9686
- function waitForChildProcessSpawnError(proc) {
9687
- return new Promise((_resolve, reject) => {
9688
- proc.once("error", reject);
9689
- });
9690
- }
9691
9682
  var init_manager_utils = () => {};
9692
9683
 
9693
9684
  // ../../packages/core/src/platform/playwright/browser-processes.ts
9694
9685
  import { execFileSync as execFileSync6 } from "child_process";
9695
9686
  import { readdirSync as readdirSync3, unlinkSync as unlinkSync10 } from "fs";
9696
- import { resolve as resolve10, sep } from "path";
9687
+ import { createServer } from "net";
9688
+ import { resolve as resolve9, sep } from "path";
9697
9689
  function isPortInUse(port) {
9698
- try {
9699
- execFileSync6("lsof", ["-i", `:${port}`, "-t"], { stdio: "pipe" });
9700
- return true;
9701
- } catch {
9702
- return false;
9703
- }
9690
+ return new Promise((resolveProbe) => {
9691
+ const server = createServer();
9692
+ let settled = false;
9693
+ const finish = (occupied) => {
9694
+ if (settled)
9695
+ return;
9696
+ settled = true;
9697
+ server.removeAllListeners();
9698
+ resolveProbe(occupied);
9699
+ };
9700
+ server.unref();
9701
+ server.once("error", () => finish(true));
9702
+ server.listen({ host: "127.0.0.1", port, exclusive: true }, () => {
9703
+ server.close((error) => finish(error !== undefined));
9704
+ });
9705
+ });
9704
9706
  }
9705
- async function killPlaywrightOnPort(port, expectedUserDataDir) {
9706
- try {
9707
- const pids = execFileSync6("lsof", ["-i", `:${port}`, "-t"], { stdio: "pipe" }).toString().trim();
9708
- if (!pids)
9709
- return;
9710
- for (const pid of pids.split(`
9711
- `)) {
9712
- try {
9713
- const cmdline = execFileSync6("ps", ["-p", pid, "-o", "command="], { stdio: "pipe" }).toString().trim();
9714
- if (!cmdline.includes("mcp-patchright-http.mjs")) {
9715
- logger.warn({ port, pid, cmdline: cmdline.slice(0, 80) }, "Port occupied by non-browser-MCP process, skipping");
9716
- continue;
9717
- }
9718
- if (expectedUserDataDir) {
9719
- const otherDataDir = extractUserDataDirArg(cmdline);
9720
- if (!browserProcessMatchesExpectedProfile(cmdline, expectedUserDataDir)) {
9721
- logger.warn({
9722
- port,
9723
- pid,
9724
- otherDataDir,
9725
- expectedUserDataDir
9726
- }, "Port occupied by another topic's playwright-mcp, skipping");
9727
- continue;
9728
- }
9729
- }
9730
- const pidNum = parseInt(pid, 10);
9731
- if (!Number.isNaN(pidNum)) {
9732
- killProcessTreeChildren(pidNum);
9733
- process.kill(pidNum, "SIGKILL");
9734
- }
9735
- logger.info({ pid, port }, "Killed zombie mcp-patchright");
9736
- } catch (e) {
9737
- logger.warn({ err: e, port }, "Failed to inspect process occupying port");
9738
- }
9707
+ async function reserveAvailableLoopbackPort(minPort, maxPort, reservedPorts, probeOccupied = isPortInUse) {
9708
+ for (let port = minPort;port <= maxPort; port++) {
9709
+ if (reservedPorts.has(port))
9710
+ continue;
9711
+ reservedPorts.add(port);
9712
+ if (await probeOccupied(port)) {
9713
+ reservedPorts.delete(port);
9714
+ continue;
9739
9715
  }
9740
- } catch (e) {
9741
- logger.warn({ err: e, port }, "Failed to check processes on port");
9742
- }
9743
- const start = Date.now();
9744
- while (Date.now() - start < 3000) {
9745
- if (!isPortInUse(port))
9746
- return;
9747
- await delay(200);
9716
+ return port;
9748
9717
  }
9718
+ return null;
9749
9719
  }
9750
9720
  function killBrowserProcsForUserDataDir(userDataDir) {
9751
- const target = resolve10(userDataDir);
9752
- const profileRoot = resolve10(BROWSER_PROFILES_DIR);
9721
+ const target = resolve9(userDataDir);
9722
+ const profileRoot = resolve9(BROWSER_PROFILES_DIR);
9753
9723
  if (target !== profileRoot && !target.startsWith(`${profileRoot}${sep}`))
9754
9724
  return;
9755
9725
  let pids;
@@ -9770,7 +9740,7 @@ function killBrowserProcsForUserDataDir(userDataDir) {
9770
9740
  stdio: "pipe"
9771
9741
  }).toString().trim();
9772
9742
  const argDir = extractUserDataDirArg(cmdline);
9773
- if (!argDir || resolve10(argDir) !== target)
9743
+ if (!argDir || resolve9(argDir) !== target)
9774
9744
  continue;
9775
9745
  killProcessTreeChildren(pidNum);
9776
9746
  process.kill(pidNum, "SIGKILL");
@@ -9781,13 +9751,13 @@ function killBrowserProcsForUserDataDir(userDataDir) {
9781
9751
  }
9782
9752
  }
9783
9753
  function selectOrphanBrowserPids(procs, liveUserDataDirs, profileRoot, selfPid) {
9784
- const root = resolve10(profileRoot);
9785
- const live = new Set([...liveUserDataDirs].map((d) => resolve10(d)));
9754
+ const root = resolve9(profileRoot);
9755
+ const live = new Set([...liveUserDataDirs].map((d) => resolve9(d)));
9786
9756
  const out = [];
9787
9757
  for (const { pid, userDataDir } of procs) {
9788
9758
  if (pid === selfPid || !userDataDir)
9789
9759
  continue;
9790
- const dir = resolve10(userDataDir);
9760
+ const dir = resolve9(userDataDir);
9791
9761
  if (dir !== root && !dir.startsWith(`${root}${sep}`))
9792
9762
  continue;
9793
9763
  if (live.has(dir))
@@ -9803,7 +9773,7 @@ function reapOrphanBrowsers(liveUserDataDirs) {
9803
9773
  const daemonLease = getRuntimeProcessLease("node-daemon", Date.now(), Number.POSITIVE_INFINITY);
9804
9774
  if (!isBrowserJanitorOwner(daemonLease?.pid ?? null, process.pid))
9805
9775
  return;
9806
- const profileRoot = resolve10(BROWSER_PROFILES_DIR);
9776
+ const profileRoot = resolve9(BROWSER_PROFILES_DIR);
9807
9777
  let pids;
9808
9778
  try {
9809
9779
  pids = execFileSync6("pgrep", ["-f", "--", profileRoot], { stdio: "pipe" }).toString().trim();
@@ -9836,26 +9806,13 @@ function reapOrphanBrowsers(liveUserDataDirs) {
9836
9806
  }
9837
9807
  }
9838
9808
  }
9839
- async function isHealthy(port) {
9840
- for (const path of ["/health", "/sse?owner=__negotium_health__"]) {
9841
- try {
9842
- const res = await fetch(`http://127.0.0.1:${port}${path}`, {
9843
- signal: AbortSignal.timeout(2000)
9844
- });
9845
- await res.body?.cancel();
9846
- if (res.ok)
9847
- return true;
9848
- } catch {}
9849
- }
9850
- return false;
9851
- }
9852
9809
  function cleanSingletonFiles(userDataDir) {
9853
9810
  try {
9854
9811
  const files = readdirSync3(userDataDir);
9855
9812
  for (const f of files) {
9856
9813
  if (f.startsWith("Singleton")) {
9857
9814
  try {
9858
- unlinkSync10(resolve10(userDataDir, f));
9815
+ unlinkSync10(resolve9(userDataDir, f));
9859
9816
  logger.info({ file: f, userDataDir }, "Removed stale Singleton file");
9860
9817
  } catch (e) {
9861
9818
  logger.warn({ err: e, file: f }, "Failed to remove stale Chrome Singleton file");
@@ -9895,9 +9852,9 @@ var init_browser_processes = __esm(async () => {
9895
9852
 
9896
9853
  // ../../packages/core/src/platform/playwright/headed-launch.ts
9897
9854
  import { accessSync as accessSync2, constants as constants2 } from "fs";
9898
- import { delimiter, isAbsolute as isAbsolute3, resolve as resolve11 } from "path";
9855
+ import { delimiter, isAbsolute as isAbsolute3, resolve as resolve10 } from "path";
9899
9856
  function findExecutableOnPath(command, environment = process.env) {
9900
- const candidates = isAbsolute3(command) ? [command] : (environment.PATH ?? "").split(delimiter).filter(Boolean).map((directory) => resolve11(directory, command));
9857
+ const candidates = isAbsolute3(command) ? [command] : (environment.PATH ?? "").split(delimiter).filter(Boolean).map((directory) => resolve10(directory, command));
9901
9858
  for (const candidate of candidates) {
9902
9859
  try {
9903
9860
  accessSync2(candidate, constants2.X_OK);
@@ -10058,7 +10015,7 @@ import {
10058
10015
  unlinkSync as unlinkSync11,
10059
10016
  writeFileSync as writeFileSync8
10060
10017
  } from "fs";
10061
- import { dirname as dirname11, join as join18, resolve as resolve12 } from "path";
10018
+ import { dirname as dirname11, join as join18, resolve as resolve11 } from "path";
10062
10019
  function makeInstanceKey(userId, topic) {
10063
10020
  return resolvePlaywrightTopicBinding(userId, topic).instanceKey;
10064
10021
  }
@@ -10097,7 +10054,7 @@ function migrateLegacyTopicProfile(ownerId, topic) {
10097
10054
  const current2 = getTopicBrowserProfile(topic);
10098
10055
  if (current2 !== "default" || !hasBrowserProfileTopic(topic))
10099
10056
  return current2;
10100
- const legacyDir = resolve12(BROWSER_PROFILES_DIR, sanitizeTopicName(topic));
10057
+ const legacyDir = resolve11(BROWSER_PROFILES_DIR, sanitizeTopicName(topic));
10101
10058
  if (!existsSync16(legacyDir))
10102
10059
  return current2;
10103
10060
  const profile = legacyBrowserProfileName(topic);
@@ -10248,29 +10205,19 @@ function evictIdleInstance() {
10248
10205
  }
10249
10206
  return null;
10250
10207
  }
10251
- async function allocatePort(expectedUserDataDir) {
10252
- for (let port = managerHost.basePort;port <= managerHost.maxPort; port++) {
10253
- if (usedPorts.has(port))
10254
- continue;
10255
- usedPorts.add(port);
10256
- if (isPortInUse(port)) {
10257
- logger.warn({ port }, "Port occupied by external process, attempting cleanup");
10258
- await killPlaywrightOnPort(port, expectedUserDataDir);
10259
- if (isPortInUse(port)) {
10260
- usedPorts.delete(port);
10261
- continue;
10262
- }
10263
- }
10208
+ async function allocatePort() {
10209
+ const port = await reserveAvailableLoopbackPort(managerHost.basePort, managerHost.maxPort, usedPorts, async (candidate) => {
10210
+ const occupied = await isPortInUse(candidate);
10211
+ if (occupied)
10212
+ logger.warn({ port: candidate }, "Port occupied by external process, skipping");
10213
+ return occupied;
10214
+ });
10215
+ if (port !== null)
10264
10216
  return port;
10265
- }
10266
10217
  const evictedPort = evictIdleInstance();
10267
10218
  if (evictedPort !== null) {
10268
10219
  await waitForPortRelease(evictedPort);
10269
- const reusablePort = selectReusablePort(managerHost.basePort, managerHost.maxPort, usedPorts, isPortInUse);
10270
- if (reusablePort !== null) {
10271
- usedPorts.add(reusablePort);
10272
- return reusablePort;
10273
- }
10220
+ return allocatePort();
10274
10221
  }
10275
10222
  throw new Error(`No available ports for Playwright MCP (${instances.size} active instances, range ${managerHost.basePort}-${managerHost.maxPort})`);
10276
10223
  }
@@ -10282,7 +10229,7 @@ function ownerDirectory(ownerId) {
10282
10229
  return `${sanitizeTopicName(ownerId).slice(0, 24)}_${digest}`;
10283
10230
  }
10284
10231
  function defaultProfileDir(ownerId, profile) {
10285
- return resolve12(BROWSER_PROFILES_DIR, "profiles", ownerDirectory(ownerId), profile);
10232
+ return resolve11(BROWSER_PROFILES_DIR, "profiles", ownerDirectory(ownerId), profile);
10286
10233
  }
10287
10234
  function resolveUserDataDir(instanceKey) {
10288
10235
  return managerHost.resolveInstanceDataDir(instanceKey);
@@ -10308,9 +10255,53 @@ function killInstance(instanceKey, opts) {
10308
10255
  deletePortFile(instanceKey);
10309
10256
  logger.info({ instanceKey, port: inst.port, keepPort: !!opts?.keepPort }, "Killed Playwright MCP (with cleanup)");
10310
10257
  }
10258
+ function captureBoundedStderr(proc) {
10259
+ let tail = Buffer.alloc(0);
10260
+ proc.stderr?.on("data", (chunk) => {
10261
+ const next = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
10262
+ tail = Buffer.concat([tail, next]);
10263
+ if (tail.byteLength > PLAYWRIGHT_STARTUP_STDERR_LIMIT) {
10264
+ tail = tail.subarray(tail.byteLength - PLAYWRIGHT_STARTUP_STDERR_LIMIT);
10265
+ }
10266
+ });
10267
+ return () => tail.toString("utf8").trim();
10268
+ }
10269
+ function watchChildStartup(proc, stderrTail) {
10270
+ let stopped = false;
10271
+ let rejectFailure = () => {
10272
+ return;
10273
+ };
10274
+ const diagnostics = () => {
10275
+ const stderr = stderrTail();
10276
+ return stderr ? `
10277
+ stderr (last ${PLAYWRIGHT_STARTUP_STDERR_LIMIT} bytes):
10278
+ ${stderr}` : "";
10279
+ };
10280
+ const onError = (error) => {
10281
+ rejectFailure(new Error(`Playwright MCP failed to spawn: ${error.message}${diagnostics()}`, {
10282
+ cause: error
10283
+ }));
10284
+ };
10285
+ const onExit = (code, signal) => {
10286
+ rejectFailure(new Error(`Playwright MCP exited during startup (code=${code ?? "null"}, signal=${signal ?? "null"})${diagnostics()}`));
10287
+ };
10288
+ const failure = new Promise((_resolve, reject) => {
10289
+ rejectFailure = reject;
10290
+ proc.once("error", onError);
10291
+ proc.once("exit", onExit);
10292
+ });
10293
+ const stop = () => {
10294
+ if (stopped)
10295
+ return;
10296
+ stopped = true;
10297
+ proc.off("error", onError);
10298
+ proc.off("exit", onExit);
10299
+ };
10300
+ return { failure, stop };
10301
+ }
10311
10302
  async function spawnPlaywright(instanceKey, ownerId, reservedPort, browserBin = managerHost.browserBin, allowFallback = true) {
10312
10303
  const userDataDir = resolveUserDataDir(instanceKey);
10313
- const port = reservedPort ?? await allocatePort(userDataDir);
10304
+ const port = reservedPort ?? await allocatePort();
10314
10305
  mkdirSync10(userDataDir, { recursive: true });
10315
10306
  const mcpArgs = [
10316
10307
  "--port",
@@ -10323,6 +10314,7 @@ async function spawnPlaywright(instanceKey, ownerId, reservedPort, browserBin =
10323
10314
  ];
10324
10315
  const proxy = managerHost.resolveProxy();
10325
10316
  const capability = randomBytes5(32).toString("hex");
10317
+ const spawnNonce = randomBytes5(32).toString("hex");
10326
10318
  const childEnv = {
10327
10319
  ...managerHost.createChildEnvironment({
10328
10320
  instanceKey,
@@ -10332,7 +10324,8 @@ async function spawnPlaywright(instanceKey, ownerId, reservedPort, browserBin =
10332
10324
  browserRsBin: managerHost.browserRsBin,
10333
10325
  environment: process.env
10334
10326
  }),
10335
- NEGOTIUM_BROWSER_CAPABILITY: capability
10327
+ NEGOTIUM_BROWSER_CAPABILITY: capability,
10328
+ NEGOTIUM_BROWSER_SPAWN_NONCE: spawnNonce
10336
10329
  };
10337
10330
  if (proxy) {
10338
10331
  logger.info({ instanceKey, proxyServer: proxy.server }, "Browser egress proxy enabled");
@@ -10349,7 +10342,7 @@ async function spawnPlaywright(instanceKey, ownerId, reservedPort, browserBin =
10349
10342
  let proc;
10350
10343
  try {
10351
10344
  proc = spawn4(spawnSpec.command, spawnSpec.args, {
10352
- stdio: "ignore",
10345
+ stdio: ["ignore", "ignore", "pipe"],
10353
10346
  detached: false,
10354
10347
  env: childEnv
10355
10348
  });
@@ -10357,14 +10350,15 @@ async function spawnPlaywright(instanceKey, ownerId, reservedPort, browserBin =
10357
10350
  releasePort(port);
10358
10351
  throw err;
10359
10352
  }
10360
- const spawnError = waitForChildProcessSpawnError(proc);
10353
+ const stderrTail = captureBoundedStderr(proc);
10354
+ const startup = watchChildStartup(proc, stderrTail);
10361
10355
  const reapCrashedBrowser = () => {
10362
10356
  const userDataDir2 = resolveUserDataDir(instanceKey);
10363
10357
  managerHost.cleanupBrowserProcessesForDataDir(userDataDir2);
10364
10358
  cleanSingletonFiles(userDataDir2);
10365
10359
  };
10366
10360
  proc.once("error", (err) => {
10367
- logger.error({ err, instanceKey }, "Playwright MCP error");
10361
+ logger.error({ err, instanceKey, stderr: stderrTail() || undefined }, "Playwright MCP error");
10368
10362
  if (instances.get(instanceKey)?.process === proc) {
10369
10363
  releasePort(port);
10370
10364
  instances.delete(instanceKey);
@@ -10376,7 +10370,7 @@ async function spawnPlaywright(instanceKey, ownerId, reservedPort, browserBin =
10376
10370
  }
10377
10371
  });
10378
10372
  proc.once("exit", (code) => {
10379
- logger.info({ instanceKey, code }, "Playwright MCP exited");
10373
+ logger.info({ instanceKey, code, stderr: stderrTail() || undefined }, "Playwright MCP exited");
10380
10374
  const wasOurs = instances.get(instanceKey)?.process === proc;
10381
10375
  if (wasOurs) {
10382
10376
  releasePort(port);
@@ -10394,18 +10388,42 @@ async function spawnPlaywright(instanceKey, ownerId, reservedPort, browserBin =
10394
10388
  lastUsedAt: now,
10395
10389
  capability
10396
10390
  });
10397
- const ready = await Promise.race([
10398
- (async () => await waitForServer(port, 1e4) && await supportsOwnerCleanup(port, capability) && await probePlaywrightMcpTransports(port, capability))(),
10399
- spawnError
10400
- ]);
10391
+ let startupError;
10392
+ let ready = false;
10393
+ try {
10394
+ ready = await Promise.race([
10395
+ (async () => await waitForServer(port, spawnNonce, 1e4) && await supportsOwnerCleanup(port, capability) && await probePlaywrightMcpTransports(port, capability))(),
10396
+ startup.failure
10397
+ ]);
10398
+ } catch (error) {
10399
+ startupError = error instanceof Error ? error : new Error(String(error));
10400
+ } finally {
10401
+ startup.stop();
10402
+ }
10403
+ if (ready && !isLiveOwnedChildProcess(instances.get(instanceKey), proc)) {
10404
+ ready = false;
10405
+ startupError = new Error(`Playwright MCP exited after readiness but before publication on port ${port}` + (stderrTail() ? `
10406
+ stderr:
10407
+ ${stderrTail()}` : ""));
10408
+ }
10401
10409
  if (!ready) {
10402
10410
  const exitCode = proc.exitCode;
10403
10411
  killInstance(instanceKey);
10404
10412
  if (allowFallback && browserBin !== managerHost.fallbackBrowserBin) {
10405
- logger.warn({ instanceKey, browserBin, fallback: managerHost.fallbackBrowserBin }, "Preferred browser MCP unavailable or lacks owner isolation; using Patchright fallback");
10413
+ logger.warn({
10414
+ err: startupError,
10415
+ instanceKey,
10416
+ browserBin,
10417
+ fallback: managerHost.fallbackBrowserBin,
10418
+ stderr: stderrTail() || undefined
10419
+ }, "Preferred browser MCP unavailable or lacks owner isolation; using Patchright fallback");
10406
10420
  return spawnPlaywright(instanceKey, ownerId, undefined, managerHost.fallbackBrowserBin, false);
10407
10421
  }
10408
- throw new Error(`Playwright MCP failed health check after spawn on port ${port}` + (exitCode === null ? "" : ` (exitCode=${exitCode})`));
10422
+ if (startupError)
10423
+ throw startupError;
10424
+ throw new Error(`Playwright MCP failed health check after spawn on port ${port}` + (exitCode === null ? "" : ` (exitCode=${exitCode})`) + (stderrTail() ? `
10425
+ stderr:
10426
+ ${stderrTail()}` : ""));
10409
10427
  }
10410
10428
  writePortFile(instanceKey, port);
10411
10429
  logger.info({ instanceKey, port, pid: proc.pid, ready, virtualDisplay: spawnSpec.virtualDisplay }, "Playwright MCP started");
@@ -10469,7 +10487,7 @@ async function ensurePlaywright(userId, topic) {
10469
10487
  async function waitForPortRelease(port, timeoutMs = 3000) {
10470
10488
  const start = Date.now();
10471
10489
  while (Date.now() - start < timeoutMs) {
10472
- if (!isPortInUse(port))
10490
+ if (!await isPortInUse(port))
10473
10491
  return;
10474
10492
  await delay(200);
10475
10493
  }
@@ -10498,11 +10516,22 @@ async function closeBrowserOwnerTabs(ownerId, rawProfile, owner) {
10498
10516
  const result = await response.json();
10499
10517
  return typeof result.closed === "number" ? result.closed : 0;
10500
10518
  }
10501
- async function waitForServer(port, timeoutMs) {
10519
+ async function waitForServer(port, expectedSpawnNonce, timeoutMs) {
10502
10520
  const start = Date.now();
10503
10521
  while (Date.now() - start < timeoutMs) {
10504
- if (await isHealthy(port))
10505
- return true;
10522
+ try {
10523
+ const response = await fetch(`http://127.0.0.1:${port}/health`, {
10524
+ signal: AbortSignal.timeout(1000)
10525
+ });
10526
+ if (response.ok) {
10527
+ const health = await response.json();
10528
+ if (matchesSpawnedBrowserHealth(health, expectedSpawnNonce)) {
10529
+ return true;
10530
+ }
10531
+ } else {
10532
+ await response.body?.cancel();
10533
+ }
10534
+ } catch {}
10506
10535
  await delay(300);
10507
10536
  }
10508
10537
  logger.warn({ port, timeoutMs }, "Playwright MCP not ready before timeout");
@@ -10576,7 +10605,7 @@ async function cloneProfileForChild(opts) {
10576
10605
  cleanSingletonFiles(dstDir);
10577
10606
  for (const f of ["DevToolsActivePort", "LOCK"]) {
10578
10607
  try {
10579
- unlinkSync11(resolve12(dstDir, f));
10608
+ unlinkSync11(resolve11(dstDir, f));
10580
10609
  } catch {}
10581
10610
  }
10582
10611
  logger.info({ srcKey, dstKey, srcDir, dstDir }, "Cloned Playwright profile for child topic");
@@ -10589,7 +10618,7 @@ function deleteTopicProfileDir(userId, topic) {
10589
10618
  logger.info({ dir, userId, topic }, "Preserved shared browser profile on topic deletion");
10590
10619
  return { deleted: false, dir };
10591
10620
  }
10592
- var defaultManagerHost, managerHost, MAX_IDLE_MS, instances, usedPorts, spawning, pinnedInstances, playwrightFailureHandlers;
10621
+ var defaultManagerHost, managerHost, MAX_IDLE_MS, instances, usedPorts, spawning, pinnedInstances, playwrightFailureHandlers, PLAYWRIGHT_STARTUP_STDERR_LIMIT;
10593
10622
  var init_manager2 = __esm(async () => {
10594
10623
  init_config();
10595
10624
  init_logger();
@@ -10626,6 +10655,7 @@ var init_manager2 = __esm(async () => {
10626
10655
  spawning = new Map;
10627
10656
  pinnedInstances = new Map;
10628
10657
  playwrightFailureHandlers = new Set;
10658
+ PLAYWRIGHT_STARTUP_STDERR_LIMIT = 8 * 1024;
10629
10659
  setInterval(() => {
10630
10660
  while (evictIdleInstance() !== null) {}
10631
10661
  try {
@@ -13188,12 +13218,12 @@ var init_errors = __esm(() => {
13188
13218
 
13189
13219
  // ../../packages/core/src/runtime/event-heartbeat.ts
13190
13220
  function nextOrHeartbeat(pending, intervalMs) {
13191
- return new Promise((resolve13, reject) => {
13192
- const timer = setTimeout(() => resolve13({ kind: "heartbeat" }), intervalMs);
13221
+ return new Promise((resolve12, reject) => {
13222
+ const timer = setTimeout(() => resolve12({ kind: "heartbeat" }), intervalMs);
13193
13223
  timer.unref?.();
13194
13224
  pending.then((result) => {
13195
13225
  clearTimeout(timer);
13196
- resolve13({ kind: "event", result });
13226
+ resolve12({ kind: "event", result });
13197
13227
  }, (error) => {
13198
13228
  clearTimeout(timer);
13199
13229
  reject(error);
@@ -13448,7 +13478,7 @@ var init_visual_html = __esm(() => {
13448
13478
 
13449
13479
  // ../../packages/core/src/runtime/visuals.ts
13450
13480
  import { realpathSync as realpathSync4 } from "fs";
13451
- import { isAbsolute as isAbsolute4, resolve as resolve13 } from "path";
13481
+ import { isAbsolute as isAbsolute4, resolve as resolve12 } from "path";
13452
13482
  function activeVisualHtmlForPrompt(html) {
13453
13483
  if (html.length <= ACTIVE_VISUAL_PROMPT_MAX_CHARS) {
13454
13484
  return { html, omittedChars: 0 };
@@ -13499,8 +13529,8 @@ function topicAllowsVisualFileId(topicId, fileId) {
13499
13529
  return topicHasAttachmentFileId(topicId, fileId) || topicHasVisualFileId(topicId, fileId);
13500
13530
  }
13501
13531
  function isPathInside(baseDir, filePath) {
13502
- const base = resolve13(baseDir);
13503
- const normalized = resolve13(filePath);
13532
+ const base = resolve12(baseDir);
13533
+ const normalized = resolve12(filePath);
13504
13534
  try {
13505
13535
  const realBase = realpathSync4(base);
13506
13536
  const real = realpathSync4(normalized);
@@ -13562,7 +13592,7 @@ function resolveVisualMediaInput(topicId, input) {
13562
13592
  }
13563
13593
  const rawPath = input.file_path.trim();
13564
13594
  const cwd = workspaceCwdFor(topicId);
13565
- const candidate = isAbsolute4(rawPath) ? rawPath : resolve13(cwd, rawPath);
13595
+ const candidate = isAbsolute4(rawPath) ? rawPath : resolve12(cwd, rawPath);
13566
13596
  if (!isPathInside(cwd, candidate)) {
13567
13597
  return { error: "file_path must be inside the topic workspace" };
13568
13598
  }
@@ -13635,7 +13665,7 @@ var init_token_stats = __esm(async () => {
13635
13665
  // ../../packages/core/src/runtime/turn-event-stream.ts
13636
13666
  import { randomUUID as randomUUID15 } from "crypto";
13637
13667
  import { realpathSync as realpathSync5, statSync as statSync8 } from "fs";
13638
- import { isAbsolute as isAbsolute5, resolve as resolve14 } from "path";
13668
+ import { isAbsolute as isAbsolute5, resolve as resolve13 } from "path";
13639
13669
  function sessionEventMatchesCurrentExecution(topicId, queryId, agent, model) {
13640
13670
  if (getRoomQuery(topicId)?.queryId !== queryId)
13641
13671
  return false;
@@ -13942,7 +13972,7 @@ async function runTurnEventStream(topicId, topicTitle, queryId, events, control,
13942
13972
  case "file":
13943
13973
  if (!silent && peerBridge) {
13944
13974
  const cwd = workspaceCwdFor(topicId);
13945
- const path = isAbsolute5(event.path) ? event.path : resolve14(cwd, event.path);
13975
+ const path = isAbsolute5(event.path) ? event.path : resolve13(cwd, event.path);
13946
13976
  if (!isPathInside(cwd, path)) {
13947
13977
  logger.warn({ topicId, path }, "peer output file is outside the topic workspace");
13948
13978
  break;
@@ -17100,4 +17130,4 @@ export {
17100
17130
  DEFAULT_SELF_CONFIG_PRODUCT
17101
17131
  };
17102
17132
 
17103
- //# debugId=819C25A6CBDCEB1764756E2164756E21
17133
+ //# debugId=B58744AF223B7C9664756E2164756E21