browser-pilot-cli 0.3.0-rc.2 → 0.3.0-rc.4

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/daemon.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import http2 from "http";
3
3
  import { unlinkSync as unlinkSync2, chmodSync as chmodSync2 } from "fs";
4
4
  import { createHash as createHash5, randomUUID as randomUUID14 } from "crypto";
5
- import { join as join6 } from "path";
5
+ import { join as join5 } from "path";
6
6
 
7
7
  // src/paths.ts
8
8
  import { createHash } from "crypto";
@@ -2344,10 +2344,10 @@ var MemoryCommandRuntime = class {
2344
2344
  throw new BrowserPilotError("internal_error", "Invalid or duplicate Command ID");
2345
2345
  }
2346
2346
  const acceptedAt = this.now();
2347
- let resolve3;
2347
+ let resolve2;
2348
2348
  let reject;
2349
2349
  const completion = new Promise((resolvePromise, rejectPromise) => {
2350
- resolve3 = resolvePromise;
2350
+ resolve2 = resolvePromise;
2351
2351
  reject = rejectPromise;
2352
2352
  });
2353
2353
  const record = {
@@ -2368,7 +2368,7 @@ var MemoryCommandRuntime = class {
2368
2368
  scopeKey,
2369
2369
  cancellation: input.cancellation,
2370
2370
  abortController: new AbortController(),
2371
- resolve: resolve3,
2371
+ resolve: resolve2,
2372
2372
  reject,
2373
2373
  completion,
2374
2374
  settled: false
@@ -2988,8 +2988,8 @@ var MemoryBrokerRuntime = class {
2988
2988
  if (connection.notificationWaiter) {
2989
2989
  throw invalidArgument("Only one notification poll may be active per bridge Connection");
2990
2990
  }
2991
- return new Promise((resolve3) => {
2992
- const waiter = { resolve: resolve3, signal: options.signal };
2991
+ return new Promise((resolve2) => {
2992
+ const waiter = { resolve: resolve2, signal: options.signal };
2993
2993
  const finish = () => this.resolveNotificationWaiter(connection, void 0);
2994
2994
  waiter.timer = setTimeout(finish, waitMs);
2995
2995
  waiter.timer.unref();
@@ -5458,8 +5458,8 @@ async function waitForLoad(transport, sessionId, timeout = 3e4) {
5458
5458
  let interactiveSince = null;
5459
5459
  const deadlineReached = /* @__PURE__ */ Symbol("page-load-deadline");
5460
5460
  let deadlineTimer;
5461
- const deadline = new Promise((resolve3) => {
5462
- deadlineTimer = setTimeout(() => resolve3(deadlineReached), timeout);
5461
+ const deadline = new Promise((resolve2) => {
5462
+ deadlineTimer = setTimeout(() => resolve2(deadlineReached), timeout);
5463
5463
  });
5464
5464
  const injectBorder = async () => {
5465
5465
  await Promise.race([
@@ -5491,7 +5491,7 @@ async function waitForLoad(transport, sessionId, timeout = 3e4) {
5491
5491
  }
5492
5492
  }
5493
5493
  const sleep = await Promise.race([
5494
- new Promise((resolve3) => setTimeout(() => resolve3("poll"), 200)),
5494
+ new Promise((resolve2) => setTimeout(() => resolve2("poll"), 200)),
5495
5495
  deadline
5496
5496
  ]);
5497
5497
  if (sleep === deadlineReached) throw new PageLoadTimeoutError(timeout);
@@ -5675,7 +5675,7 @@ var InputDispatcher = class {
5675
5675
  }
5676
5676
  hooks.afterCharacter?.({ character: char, index });
5677
5677
  index += 1;
5678
- if (delayMs > 0) await new Promise((resolve3) => setTimeout(resolve3, delayMs));
5678
+ if (delayMs > 0) await new Promise((resolve2) => setTimeout(resolve2, delayMs));
5679
5679
  }
5680
5680
  }
5681
5681
  async insertText(text) {
@@ -5719,7 +5719,7 @@ var ObservationService = class {
5719
5719
  }
5720
5720
  async observeAfterAction(limit = OBSERVATION_V1_LIMITS.defaultElements) {
5721
5721
  if (this.settleDelayMs > 0) {
5722
- await new Promise((resolve3) => setTimeout(resolve3, this.settleDelayMs));
5722
+ await new Promise((resolve2) => setTimeout(resolve2, this.settleDelayMs));
5723
5723
  }
5724
5724
  await this.loadWaiter(this.transport, this.sessionId, this.loadTimeoutMs);
5725
5725
  return this.observe(limit);
@@ -6076,7 +6076,7 @@ var ActionService = class {
6076
6076
  await this.input.click(point.x, point.y, { button, clickCount });
6077
6077
  this.markDispatched(run);
6078
6078
  if (this.readbackDelayMs > 0) {
6079
- await new Promise((resolve3) => setTimeout(resolve3, this.readbackDelayMs));
6079
+ await new Promise((resolve2) => setTimeout(resolve2, this.readbackDelayMs));
6080
6080
  }
6081
6081
  let after;
6082
6082
  try {
@@ -6128,7 +6128,7 @@ var ActionService = class {
6128
6128
  await this.input.press(key);
6129
6129
  this.markDispatched(run);
6130
6130
  if (this.readbackDelayMs > 0) {
6131
- await new Promise((resolve3) => setTimeout(resolve3, this.readbackDelayMs));
6131
+ await new Promise((resolve2) => setTimeout(resolve2, this.readbackDelayMs));
6132
6132
  }
6133
6133
  let after;
6134
6134
  try {
@@ -6194,7 +6194,7 @@ var ActionService = class {
6194
6194
  this.markDispatched(run);
6195
6195
  }
6196
6196
  if (this.readbackDelayMs > 0) {
6197
- await new Promise((resolve3) => setTimeout(resolve3, this.readbackDelayMs));
6197
+ await new Promise((resolve2) => setTimeout(resolve2, this.readbackDelayMs));
6198
6198
  }
6199
6199
  const after = await this.readElementState(objectId);
6200
6200
  evidence = inputEvidence("type", before, after, text, !!options.clear);
@@ -6226,7 +6226,7 @@ var ActionService = class {
6226
6226
  this.willDispatch();
6227
6227
  await this.input.click(location.x, location.y);
6228
6228
  this.markDispatched(run);
6229
- if (this.focusDelayMs > 0) await new Promise((resolve3) => setTimeout(resolve3, this.focusDelayMs));
6229
+ if (this.focusDelayMs > 0) await new Promise((resolve2) => setTimeout(resolve2, this.focusDelayMs));
6230
6230
  }
6231
6231
  await this.captureFocus(run, "capture_input_focus");
6232
6232
  const before = await this.readActiveState();
@@ -6248,7 +6248,7 @@ var ActionService = class {
6248
6248
  },
6249
6249
  afterCharacter: () => this.markDispatched(run)
6250
6250
  });
6251
- if (this.readbackDelayMs > 0) await new Promise((resolve3) => setTimeout(resolve3, this.readbackDelayMs));
6251
+ if (this.readbackDelayMs > 0) await new Promise((resolve2) => setTimeout(resolve2, this.readbackDelayMs));
6252
6252
  const after = await this.readActiveState();
6253
6253
  const evidence = inputEvidence("keyboard", before, after, text, !!options.clear);
6254
6254
  this.requireVerification(evidence, options.verification);
@@ -6608,8 +6608,8 @@ var CdpActionContinuityGuard = class _CdpActionContinuityGuard {
6608
6608
 
6609
6609
  // src/services/download-controller.ts
6610
6610
  import { randomUUID as randomUUID6 } from "crypto";
6611
- import { chmod, lstat, mkdir, readdir, rm, stat, unlink } from "fs/promises";
6612
- import { basename as basename2, join as join4, resolve } from "path";
6611
+ import { lstat } from "fs/promises";
6612
+ import { isAbsolute as isAbsolute3 } from "path";
6613
6613
 
6614
6614
  // src/services/agent-hint-service.ts
6615
6615
  var MAX_HINT_REFS = 32;
@@ -6710,6 +6710,15 @@ function repeatedActionAgentHint(streak, value) {
6710
6710
  // src/services/download-controller.ts
6711
6711
  var MAX_EVENT_URL_LENGTH = 16384;
6712
6712
  var MAX_FILE_NAME_LENGTH = 4096;
6713
+ var MAX_FILE_PATH_LENGTH = 32768;
6714
+ var DownloadFinalizeError = class extends Error {
6715
+ code;
6716
+ constructor(code) {
6717
+ super(code);
6718
+ this.name = "DownloadFinalizeError";
6719
+ this.code = code;
6720
+ }
6721
+ };
6713
6722
  function boundedString(value, maxLength) {
6714
6723
  return typeof value === "string" ? value.slice(0, maxLength) : "";
6715
6724
  }
@@ -6718,14 +6727,13 @@ function finiteBytes(value) {
6718
6727
  }
6719
6728
  function safeFileName(value) {
6720
6729
  const candidate = boundedString(value, MAX_FILE_NAME_LENGTH).replace(/[\u0000-\u001f\u007f]/g, "").trim();
6721
- const leaf = basename2(candidate);
6730
+ const leaf = candidate.split(/[\\/]/).at(-1) ?? "";
6722
6731
  return leaf && leaf !== "." && leaf !== ".." ? leaf : "download";
6723
6732
  }
6724
6733
  var DownloadController = class {
6725
6734
  constructor(transport, artifacts, options = {}) {
6726
6735
  this.transport = transport;
6727
6736
  this.artifacts = artifacts;
6728
- this.directory = resolve(options.directory ?? DOWNLOAD_DIR);
6729
6737
  this.maxDownloadBytes = options.maxDownloadBytes ?? artifacts.maxItemBytes;
6730
6738
  this.maxActivePerSession = options.maxActivePerSession ?? 8;
6731
6739
  this.maxActivePerWorkspace = options.maxActivePerWorkspace ?? 32;
@@ -6737,102 +6745,87 @@ var DownloadController = class {
6737
6745
  this.maxActivePerWorkspace,
6738
6746
  this.maxActiveTotal
6739
6747
  ].some((value) => !Number.isSafeInteger(value) || value <= 0) || this.maxDownloadBytes > artifacts.maxItemBytes) {
6740
- throw new Error("Invalid download size limit");
6748
+ throw new Error("Invalid DownloadController limit");
6741
6749
  }
6742
6750
  this.installHandlers();
6743
6751
  }
6744
6752
  sessions = /* @__PURE__ */ new Map();
6745
6753
  attachments = /* @__PURE__ */ new Map();
6746
- attachmentContexts = /* @__PURE__ */ new Map();
6747
- cancelledAttachments = /* @__PURE__ */ new Set();
6748
- directory;
6754
+ downloadsByGuid = /* @__PURE__ */ new Map();
6755
+ configurations = /* @__PURE__ */ new Map();
6749
6756
  maxDownloadBytes;
6750
6757
  maxActivePerSession;
6751
6758
  maxActivePerWorkspace;
6752
6759
  maxActiveTotal;
6753
6760
  publishEvent;
6754
- initializePromise;
6761
+ latestConfigurationGeneration = 0;
6755
6762
  attachSession(context) {
6756
- if (this.sessions.has(context.sessionId)) {
6757
- return Promise.resolve(this.sessions.get(context.sessionId).available);
6758
- }
6759
6763
  const pending = this.attachments.get(context.sessionId);
6760
6764
  if (pending) return pending;
6765
+ const existing = this.sessions.get(context.sessionId);
6766
+ if (existing) return Promise.resolve(existing.available);
6761
6767
  const attachment = this.configureSession(context).finally(() => {
6762
6768
  if (this.attachments.get(context.sessionId) === attachment) {
6763
6769
  this.attachments.delete(context.sessionId);
6764
6770
  }
6765
- this.attachmentContexts.delete(context.sessionId);
6766
- this.cancelledAttachments.delete(context.sessionId);
6767
6771
  });
6768
6772
  this.attachments.set(context.sessionId, attachment);
6769
- this.attachmentContexts.set(context.sessionId, { ...context });
6770
6773
  return attachment;
6771
6774
  }
6772
6775
  async configureSession(context) {
6773
- const directory = join4(this.directory, randomUUID6());
6774
6776
  const session = {
6775
6777
  context: { ...context },
6776
- directory,
6777
6778
  available: false,
6778
6779
  closed: false,
6779
6780
  downloads: /* @__PURE__ */ new Map()
6780
6781
  };
6781
- let stagingReady = false;
6782
- try {
6783
- await this.initialize();
6784
- await mkdir(directory, { recursive: true, mode: 448 });
6785
- await chmod(directory, 448).catch(() => {
6786
- });
6787
- stagingReady = true;
6788
- if (this.cancelledAttachments.has(context.sessionId)) {
6789
- await rm(directory, { recursive: true, force: true }).catch(() => {
6790
- });
6791
- return false;
6792
- }
6793
- this.sessions.set(context.sessionId, session);
6794
- await this.transport.send("Page.setDownloadBehavior", {
6795
- behavior: "allowAndName",
6796
- downloadPath: directory,
6797
- eventsEnabled: true
6798
- }, context.sessionId);
6799
- if (session.closed || this.sessions.get(context.sessionId) !== session) {
6800
- await rm(directory, { recursive: true, force: true }).catch(() => {
6801
- });
6802
- return false;
6803
- }
6804
- session.available = true;
6805
- return true;
6806
- } catch {
6807
- this.sessions.delete(context.sessionId);
6808
- session.closed = true;
6809
- await rm(directory, { recursive: true, force: true }).catch(() => {
6810
- });
6782
+ this.sessions.set(context.sessionId, session);
6783
+ const available = await this.enableBrowserDownloadEvents(context);
6784
+ if (session.closed || this.sessions.get(context.sessionId) !== session) return false;
6785
+ session.available = available;
6786
+ if (!available) {
6811
6787
  this.publish(session, {
6812
6788
  state: "capture_unavailable",
6813
- reason: stagingReady ? "target_session_api_unavailable" : "staging_unavailable"
6789
+ reason: "browser_download_events_unavailable"
6814
6790
  });
6815
- return false;
6816
6791
  }
6792
+ return available;
6793
+ }
6794
+ enableBrowserDownloadEvents(context) {
6795
+ if (context.browserConnectionGeneration > this.latestConfigurationGeneration) {
6796
+ this.latestConfigurationGeneration = context.browserConnectionGeneration;
6797
+ for (const generation of this.configurations.keys()) {
6798
+ if (generation < context.browserConnectionGeneration) this.configurations.delete(generation);
6799
+ }
6800
+ }
6801
+ let contexts = this.configurations.get(context.browserConnectionGeneration);
6802
+ if (!contexts) {
6803
+ contexts = /* @__PURE__ */ new Map();
6804
+ this.configurations.set(context.browserConnectionGeneration, contexts);
6805
+ }
6806
+ const key = context.cdpBrowserContextId ?? "";
6807
+ const existing = contexts.get(key);
6808
+ if (existing) return existing;
6809
+ const configured = this.transport.send("Browser.setDownloadBehavior", {
6810
+ behavior: "default",
6811
+ eventsEnabled: true,
6812
+ ...context.cdpBrowserContextId ? { browserContextId: context.cdpBrowserContextId } : {}
6813
+ }).then(() => true, () => {
6814
+ if (contexts?.get(key) === configured) contexts.delete(key);
6815
+ return false;
6816
+ });
6817
+ contexts.set(key, configured);
6818
+ return configured;
6817
6819
  }
6818
6820
  detachSession(sessionId, reason2) {
6819
- if (this.attachments.has(sessionId)) this.cancelledAttachments.add(sessionId);
6820
6821
  const session = this.sessions.get(sessionId);
6821
6822
  if (!session) return;
6822
6823
  this.sessions.delete(sessionId);
6823
6824
  session.closed = true;
6824
- if (session.available) {
6825
- void this.transport.send("Page.setDownloadBehavior", {
6826
- behavior: "default",
6827
- eventsEnabled: false
6828
- }, sessionId).catch(() => {
6829
- });
6830
- }
6831
- for (const download of session.downloads.values()) {
6825
+ for (const download of [...session.downloads.values()]) {
6832
6826
  if (download.state === "terminal") continue;
6833
6827
  download.state = "terminal";
6834
- void this.transport.send("Browser.cancelDownload", { guid: download.guid }).catch(() => {
6835
- });
6828
+ this.removeBinding(session, download);
6836
6829
  this.publish(session, {
6837
6830
  downloadId: download.id,
6838
6831
  state: "cancelled",
@@ -6841,21 +6834,13 @@ var DownloadController = class {
6841
6834
  ...download.totalBytes !== void 0 ? { totalBytes: download.totalBytes } : {}
6842
6835
  });
6843
6836
  }
6844
- void rm(session.directory, { recursive: true, force: true }).catch(() => {
6845
- });
6846
6837
  }
6847
6838
  releaseLease(leaseId) {
6848
- for (const context of [...this.attachmentContexts.values()]) {
6849
- if (context.leaseId === leaseId) this.detachSession(context.sessionId, "lease_released");
6850
- }
6851
6839
  for (const session of [...this.sessions.values()]) {
6852
6840
  if (session.context.leaseId === leaseId) this.detachSession(session.context.sessionId, "lease_released");
6853
6841
  }
6854
6842
  }
6855
6843
  releaseWorkspace(workspaceId) {
6856
- for (const context of [...this.attachmentContexts.values()]) {
6857
- if (context.workspaceId === workspaceId) this.detachSession(context.sessionId, "workspace_released");
6858
- }
6859
6844
  for (const session of [...this.sessions.values()]) {
6860
6845
  if (session.context.workspaceId === workspaceId) {
6861
6846
  this.detachSession(session.context.sessionId, "workspace_released");
@@ -6868,13 +6853,20 @@ var DownloadController = class {
6868
6853
  const session = this.sessions.get(sessionId);
6869
6854
  const guid = boundedString(params?.guid, 512);
6870
6855
  if (!session || !session.available || session.closed || !/^[A-Za-z0-9._-]{1,512}$/.test(guid)) return;
6871
- if (session.downloads.has(guid)) return;
6872
- if (!this.hasDownloadCapacity(session)) {
6873
- const downloadId = `download:${randomUUID6()}`;
6874
- void this.transport.send("Browser.cancelDownload", { guid }).catch(() => {
6856
+ const collision = this.downloadsByGuid.get(guid);
6857
+ if (collision) {
6858
+ if (collision.session === session) return;
6859
+ this.failDownload(collision.session, collision.download, "download_identity_collision");
6860
+ this.publish(session, {
6861
+ downloadId: `download:${randomUUID6()}`,
6862
+ state: "failed",
6863
+ reason: "download_identity_collision"
6875
6864
  });
6865
+ return;
6866
+ }
6867
+ if (!this.hasDownloadCapacity(session)) {
6876
6868
  this.publish(session, {
6877
- downloadId,
6869
+ downloadId: `download:${randomUUID6()}`,
6878
6870
  state: "failed",
6879
6871
  reason: "concurrency_limit_exceeded"
6880
6872
  });
@@ -6883,13 +6875,13 @@ var DownloadController = class {
6883
6875
  const download = {
6884
6876
  id: `download:${randomUUID6()}`,
6885
6877
  guid,
6886
- path: join4(session.directory, guid),
6887
6878
  url: boundedString(params?.url, MAX_EVENT_URL_LENGTH),
6888
6879
  fileName: safeFileName(params?.suggestedFilename),
6889
6880
  receivedBytes: 0,
6890
6881
  state: "active"
6891
6882
  };
6892
6883
  session.downloads.set(guid, download);
6884
+ this.downloadsByGuid.set(guid, { session, download });
6893
6885
  this.publish(session, {
6894
6886
  downloadId: download.id,
6895
6887
  state: "started",
@@ -6899,63 +6891,49 @@ var DownloadController = class {
6899
6891
  });
6900
6892
  this.transport.on?.("Page.downloadProgress", (params, sessionId) => {
6901
6893
  if (!sessionId) return;
6902
- const session = this.sessions.get(sessionId);
6903
- const guid = boundedString(params?.guid, 512);
6904
- const download = session?.downloads.get(guid);
6905
- if (!session || !download) return;
6906
- const receivedBytes = finiteBytes(params?.receivedBytes);
6907
- const totalBytes = finiteBytes(params?.totalBytes);
6908
- if (receivedBytes !== void 0) download.receivedBytes = receivedBytes;
6909
- if (totalBytes !== void 0) download.totalBytes = totalBytes;
6910
- if (download.state === "terminal") {
6911
- if (params?.state === "completed" || params?.state === "canceled") {
6912
- session.downloads.delete(download.guid);
6913
- void unlink(download.path).catch(() => {
6914
- });
6915
- }
6916
- return;
6917
- }
6918
- if (download.receivedBytes > this.maxDownloadBytes || download.totalBytes !== void 0 && download.totalBytes > this.maxDownloadBytes) {
6919
- this.failOversized(session, download);
6920
- return;
6894
+ const binding = this.downloadsByGuid.get(boundedString(params?.guid, 512));
6895
+ if (!binding || binding.session.context.sessionId !== sessionId) return;
6896
+ this.updateBytes(binding.download, params);
6897
+ if (this.isOversized(binding.download)) {
6898
+ this.failOversized(binding.session, binding.download);
6899
+ } else if (params?.state === "canceled") {
6900
+ this.cancelDownload(binding.session, binding.download, "browser_cancelled");
6921
6901
  }
6922
- if (this.exceedsStagingQuota(session)) {
6923
- this.failDownload(session, download, "staging_quota_exceeded", {
6924
- maxWorkspaceBytes: this.artifacts.maxWorkspaceCapacityBytes,
6925
- maxTotalBytes: this.artifacts.maxTotalCapacityBytes
6926
- });
6902
+ });
6903
+ this.transport.on?.("Browser.downloadProgress", (params) => {
6904
+ const binding = this.downloadsByGuid.get(boundedString(params?.guid, 512));
6905
+ if (!binding) return;
6906
+ this.updateBytes(binding.download, params);
6907
+ if (this.isOversized(binding.download)) {
6908
+ this.failOversized(binding.session, binding.download);
6927
6909
  return;
6928
6910
  }
6929
- if (params?.state === "completed" && download.state === "active") {
6930
- download.state = "finalizing";
6931
- void this.finalize(session, download);
6911
+ if (params?.state === "completed" && binding.download.state === "active") {
6912
+ binding.download.state = "finalizing";
6913
+ void this.finalize(binding.session, binding.download, params?.filePath);
6932
6914
  } else if (params?.state === "canceled") {
6933
- download.state = "terminal";
6934
- this.publish(session, {
6935
- downloadId: download.id,
6936
- state: "cancelled",
6937
- reason: "browser_cancelled",
6938
- receivedBytes: download.receivedBytes,
6939
- ...download.totalBytes !== void 0 ? { totalBytes: download.totalBytes } : {}
6940
- });
6941
- void unlink(download.path).catch(() => {
6942
- });
6943
- session.downloads.delete(download.guid);
6915
+ this.cancelDownload(binding.session, binding.download, "browser_cancelled");
6944
6916
  }
6945
6917
  });
6946
6918
  }
6919
+ updateBytes(download, params) {
6920
+ const receivedBytes = finiteBytes(params?.receivedBytes);
6921
+ const totalBytes = finiteBytes(params?.totalBytes);
6922
+ if (receivedBytes !== void 0) download.receivedBytes = receivedBytes;
6923
+ if (totalBytes !== void 0) download.totalBytes = totalBytes;
6924
+ }
6925
+ isOversized(download) {
6926
+ return download.receivedBytes > this.maxDownloadBytes || download.totalBytes !== void 0 && download.totalBytes > this.maxDownloadBytes;
6927
+ }
6947
6928
  failOversized(session, download) {
6948
6929
  this.failDownload(session, download, "size_limit_exceeded", {
6949
6930
  maxDownloadBytes: this.maxDownloadBytes
6950
6931
  });
6951
6932
  }
6952
- failDownload(session, download, reason2, limits) {
6933
+ failDownload(session, download, reason2, limits = {}) {
6953
6934
  if (download.state === "terminal") return;
6954
6935
  download.state = "terminal";
6955
- void this.transport.send("Browser.cancelDownload", { guid: download.guid }).catch(() => {
6956
- });
6957
- void unlink(download.path).catch(() => {
6958
- });
6936
+ this.removeBinding(session, download);
6959
6937
  this.publish(session, {
6960
6938
  downloadId: download.id,
6961
6939
  state: "failed",
@@ -6965,48 +6943,77 @@ var DownloadController = class {
6965
6943
  ...limits
6966
6944
  });
6967
6945
  }
6968
- async finalize(session, download) {
6946
+ cancelDownload(session, download, reason2) {
6947
+ if (download.state === "terminal") return;
6948
+ download.state = "terminal";
6949
+ this.removeBinding(session, download);
6950
+ this.publish(session, {
6951
+ downloadId: download.id,
6952
+ state: "cancelled",
6953
+ reason: reason2,
6954
+ receivedBytes: download.receivedBytes,
6955
+ ...download.totalBytes !== void 0 ? { totalBytes: download.totalBytes } : {}
6956
+ });
6957
+ }
6958
+ async finalize(session, download, rawFilePath) {
6969
6959
  try {
6970
- const linkInfo = await lstat(download.path);
6971
- if (linkInfo.isSymbolicLink()) throw new Error("download_not_regular_file");
6972
- const info = await stat(download.path);
6973
- if (!info.isFile()) throw new Error("download_not_regular_file");
6974
- if (info.size > this.maxDownloadBytes) {
6975
- download.receivedBytes = info.size;
6960
+ const completedFile = await this.validateCompletedFile(rawFilePath);
6961
+ if (completedFile.size > this.maxDownloadBytes) {
6962
+ download.receivedBytes = completedFile.size;
6976
6963
  this.failOversized(session, download);
6977
6964
  return;
6978
6965
  }
6979
- const record = await this.artifacts.ingestDownload(
6966
+ if (!this.isTracked(session, download)) return;
6967
+ const record = await this.artifacts.ingestDownloadCopy(
6980
6968
  session.context.workspaceId,
6981
- download.path,
6969
+ completedFile.path,
6982
6970
  download.fileName
6983
6971
  );
6984
- if (session.closed || download.state === "terminal") {
6972
+ if (!this.isTracked(session, download)) {
6985
6973
  await this.artifacts.release(session.context.workspaceId, record.descriptor.id);
6986
6974
  return;
6987
6975
  }
6988
6976
  download.state = "terminal";
6977
+ this.removeBinding(session, download);
6989
6978
  this.publish(session, {
6990
6979
  downloadId: download.id,
6991
6980
  state: "completed",
6992
6981
  artifact: record.descriptor
6993
6982
  });
6994
- session.downloads.delete(download.guid);
6995
6983
  } catch (error) {
6996
6984
  if (download.state === "terminal") return;
6997
- download.state = "terminal";
6998
- await unlink(download.path).catch(() => {
6999
- });
7000
- this.publish(session, {
7001
- downloadId: download.id,
7002
- state: "failed",
7003
- reason: error instanceof Error && "code" in error && typeof error.code === "string" ? error.code.slice(0, 128) : "artifact_ingest_failed",
7004
- receivedBytes: download.receivedBytes,
7005
- ...download.totalBytes !== void 0 ? { totalBytes: download.totalBytes } : {}
7006
- });
7007
- session.downloads.delete(download.guid);
6985
+ const code = error instanceof DownloadFinalizeError ? error.code : error instanceof Error && "code" in error && error.code === "result_too_large" ? "artifact_quota_exceeded" : error instanceof Error && "code" in error && error.code === "invalid_argument" ? "download_file_unavailable" : "artifact_ingest_failed";
6986
+ this.failDownload(session, download, code);
6987
+ }
6988
+ }
6989
+ async validateCompletedFile(rawFilePath) {
6990
+ if (typeof rawFilePath !== "string" || rawFilePath.length === 0) {
6991
+ throw new DownloadFinalizeError("download_file_path_unavailable");
6992
+ }
6993
+ if (rawFilePath.length > MAX_FILE_PATH_LENGTH || rawFilePath.includes("\0") || !isAbsolute3(rawFilePath)) {
6994
+ throw new DownloadFinalizeError("download_file_path_invalid");
6995
+ }
6996
+ try {
6997
+ const info = await lstat(rawFilePath);
6998
+ if (info.isSymbolicLink() || !info.isFile()) {
6999
+ throw new DownloadFinalizeError("download_not_regular_file");
7000
+ }
7001
+ return { path: rawFilePath, size: info.size };
7002
+ } catch (error) {
7003
+ if (error instanceof DownloadFinalizeError) throw error;
7004
+ throw new DownloadFinalizeError("download_file_unavailable");
7005
+ }
7006
+ }
7007
+ removeBinding(session, download) {
7008
+ if (session.downloads.get(download.guid) === download) session.downloads.delete(download.guid);
7009
+ const binding = this.downloadsByGuid.get(download.guid);
7010
+ if (binding?.session === session && binding.download === download) {
7011
+ this.downloadsByGuid.delete(download.guid);
7008
7012
  }
7009
7013
  }
7014
+ isTracked(session, download) {
7015
+ return !session.closed && session.downloads.get(download.guid) === download && this.downloadsByGuid.get(download.guid)?.download === download;
7016
+ }
7010
7017
  publish(session, payload) {
7011
7018
  if (!this.publishEvent) return;
7012
7019
  const hint = this.agentHint(payload);
@@ -7044,44 +7051,12 @@ var DownloadController = class {
7044
7051
  return void 0;
7045
7052
  }
7046
7053
  hasDownloadCapacity(session) {
7047
- const active = (candidate) => candidate.state !== "terminal";
7048
- const sessionCount = [...session.downloads.values()].filter(active).length;
7054
+ const sessionCount = session.downloads.size;
7049
7055
  let workspaceCount = 0;
7050
- let totalCount = 0;
7051
- for (const candidate of this.sessions.values()) {
7052
- for (const download of candidate.downloads.values()) {
7053
- if (!active(download)) continue;
7054
- totalCount += 1;
7055
- if (candidate.context.workspaceId === session.context.workspaceId) workspaceCount += 1;
7056
- }
7057
- }
7058
- return sessionCount < this.maxActivePerSession && workspaceCount < this.maxActivePerWorkspace && totalCount < this.maxActiveTotal;
7059
- }
7060
- exceedsStagingQuota(currentSession) {
7061
- let workspaceBytes = 0;
7062
- let totalBytes = 0;
7063
- for (const session of this.sessions.values()) {
7064
- for (const download of session.downloads.values()) {
7065
- if (download.state === "terminal") continue;
7066
- const bytes = Math.max(download.receivedBytes, download.totalBytes ?? 0);
7067
- totalBytes += bytes;
7068
- if (session.context.workspaceId === currentSession.context.workspaceId) workspaceBytes += bytes;
7069
- }
7056
+ for (const binding of this.downloadsByGuid.values()) {
7057
+ if (binding.session.context.workspaceId === session.context.workspaceId) workspaceCount += 1;
7070
7058
  }
7071
- return workspaceBytes > this.artifacts.maxWorkspaceCapacityBytes || totalBytes > this.artifacts.maxTotalCapacityBytes;
7072
- }
7073
- initialize() {
7074
- if (!this.initializePromise) {
7075
- this.initializePromise = (async () => {
7076
- await mkdir(this.directory, { recursive: true, mode: 448 });
7077
- await chmod(this.directory, 448).catch(() => {
7078
- });
7079
- const entries = await readdir(this.directory, { withFileTypes: true });
7080
- await Promise.all(entries.map((entry) => rm(join4(this.directory, entry.name), { recursive: true, force: true }).catch(() => {
7081
- })));
7082
- })();
7083
- }
7084
- return this.initializePromise;
7059
+ return sessionCount < this.maxActivePerSession && workspaceCount < this.maxActivePerWorkspace && this.downloadsByGuid.size < this.maxActiveTotal;
7085
7060
  }
7086
7061
  };
7087
7062
 
@@ -9511,7 +9486,7 @@ var TargetInventoryService = class {
9511
9486
  };
9512
9487
 
9513
9488
  // src/services/upload-service.ts
9514
- import { basename as basename3 } from "path";
9489
+ import { basename as basename2 } from "path";
9515
9490
  var FILE_INPUT_BLOCK_REASONS = /* @__PURE__ */ new Set([
9516
9491
  "detached",
9517
9492
  "disabled",
@@ -9634,7 +9609,7 @@ var UploadService = class {
9634
9609
  backendNodeId
9635
9610
  }, this.sessionId);
9636
9611
  if (this.readbackDelayMs > 0) {
9637
- await new Promise((resolve3) => setTimeout(resolve3, this.readbackDelayMs));
9612
+ await new Promise((resolve2) => setTimeout(resolve2, this.readbackDelayMs));
9638
9613
  }
9639
9614
  let after;
9640
9615
  try {
@@ -9656,7 +9631,7 @@ var UploadService = class {
9656
9631
  reason: "target_unavailable"
9657
9632
  };
9658
9633
  }
9659
- const nameMatched = after.firstFileName === basename3(filePath);
9634
+ const nameMatched = after.firstFileName === basename2(filePath);
9660
9635
  const status = after.fileCount === 1 && nameMatched ? "verified" : "mismatch";
9661
9636
  const reason2 = after.fileCount !== 1 ? "file_count_mismatch" : !nameMatched ? "file_name_mismatch" : void 0;
9662
9637
  return {
@@ -11759,7 +11734,7 @@ var BrowserToolService = class {
11759
11734
  if (Array.isArray(current?.targetInfos) && !current.targetInfos.some((target) => target?.targetId === targetId)) return true;
11760
11735
  } catch {
11761
11736
  }
11762
- if (attempt < 19) await new Promise((resolve3) => setTimeout(resolve3, 50));
11737
+ if (attempt < 19) await new Promise((resolve2) => setTimeout(resolve2, 50));
11763
11738
  }
11764
11739
  return false;
11765
11740
  }
@@ -11907,7 +11882,7 @@ var BrowserToolService = class {
11907
11882
  candidateTargetId = candidate.targetId;
11908
11883
  break;
11909
11884
  }
11910
- await new Promise((resolve3) => setTimeout(resolve3, 50));
11885
+ await new Promise((resolve2) => setTimeout(resolve2, 50));
11911
11886
  }
11912
11887
  if (!candidateTargetId) {
11913
11888
  throw this.managedTargetUnknownOutcome(profile);
@@ -11946,7 +11921,7 @@ var BrowserToolService = class {
11946
11921
  if (result?.result?.value !== void 0) return false;
11947
11922
  } catch {
11948
11923
  }
11949
- if (attempt < 9) await new Promise((resolve3) => setTimeout(resolve3, 25));
11924
+ if (attempt < 9) await new Promise((resolve2) => setTimeout(resolve2, 25));
11950
11925
  }
11951
11926
  return false;
11952
11927
  } catch {
@@ -11994,13 +11969,15 @@ var BrowserToolService = class {
11994
11969
  if (typeof attached?.sessionId !== "string") {
11995
11970
  throw new BrowserPilotError("internal_error", "Chrome returned an invalid CDP session");
11996
11971
  }
11972
+ const profile = this.profileContexts.forTarget(cdpTargetId, context.browserConnectionGeneration);
11997
11973
  const session = {
11998
11974
  workspaceId: context.workspaceId,
11999
11975
  leaseId: context.leaseId,
12000
11976
  targetId,
12001
11977
  browserConnectionGeneration: context.browserConnectionGeneration,
12002
11978
  cdpTargetId,
12003
- sessionId: attached.sessionId
11979
+ sessionId: attached.sessionId,
11980
+ ...profile?.cdpBrowserContextId ? { cdpBrowserContextId: profile.cdpBrowserContextId } : {}
12004
11981
  };
12005
11982
  this.sessions.set(key, session);
12006
11983
  this.ownedSessionIds.add(attached.sessionId);
@@ -13183,19 +13160,19 @@ var BrowserToolRouter = class {
13183
13160
  // src/services/artifact-store.ts
13184
13161
  import { randomUUID as randomUUID12 } from "crypto";
13185
13162
  import {
13186
- chmod as chmod2,
13163
+ chmod,
13187
13164
  copyFile,
13188
13165
  lstat as lstat2,
13189
- mkdir as mkdir2,
13166
+ mkdir,
13190
13167
  realpath,
13191
- readdir as readdir2,
13192
- rm as rm2,
13193
- stat as stat2,
13194
- unlink as unlink2,
13168
+ readdir,
13169
+ rm,
13170
+ stat,
13171
+ unlink,
13195
13172
  writeFile
13196
13173
  } from "fs/promises";
13197
13174
  import { constants as fsConstants } from "fs";
13198
- import { basename as basename4, dirname as dirname2, extname, isAbsolute as isAbsolute3, join as join5, relative, resolve as resolve2, sep } from "path";
13175
+ import { basename as basename3, dirname as dirname2, extname, isAbsolute as isAbsolute4, join as join4, relative, resolve, sep } from "path";
13199
13176
  var ARTIFACT_ID_PATTERN2 = /^artifact:[A-Za-z0-9][A-Za-z0-9._:-]{0,118}$/;
13200
13177
  function clone2(record) {
13201
13178
  return { descriptor: { ...record.descriptor }, path: record.path };
@@ -13233,7 +13210,7 @@ var ArtifactStore = class {
13233
13210
  initialized = false;
13234
13211
  mutationTail = Promise.resolve();
13235
13212
  constructor(options = {}) {
13236
- this.directory = resolve2(options.directory ?? ARTIFACT_DIR);
13213
+ this.directory = resolve(options.directory ?? ARTIFACT_DIR);
13237
13214
  this.ttlMs = options.ttlMs ?? 15 * 6e4;
13238
13215
  this.retainedTtlMs = options.retainedTtlMs ?? 24 * 60 * 6e4;
13239
13216
  this.maxArtifactBytes = options.maxArtifactBytes ?? 100 * 1024 * 1024;
@@ -13269,10 +13246,10 @@ var ArtifactStore = class {
13269
13246
  kind: "upload_input",
13270
13247
  sensitivity: "user_file",
13271
13248
  mimeType,
13272
- removeSource: false
13249
+ rejectSymbolicLink: false
13273
13250
  });
13274
13251
  }
13275
- async ingestDownload(workspaceId, sourcePath, fileName, mimeType) {
13252
+ async ingestDownloadCopy(workspaceId, sourcePath, fileName, mimeType) {
13276
13253
  return this.ingestFile({
13277
13254
  workspaceId,
13278
13255
  sourcePath,
@@ -13280,7 +13257,7 @@ var ArtifactStore = class {
13280
13257
  sensitivity: "user_file",
13281
13258
  fileName,
13282
13259
  mimeType,
13283
- removeSource: true
13260
+ rejectSymbolicLink: true
13284
13261
  });
13285
13262
  }
13286
13263
  async initialize() {
@@ -13290,8 +13267,8 @@ var ArtifactStore = class {
13290
13267
  return this.withLock(async () => clone2(await this.requireRecordUnlocked(workspaceId, artifactId)));
13291
13268
  }
13292
13269
  async export(workspaceId, artifactId, destination, overwrite = false) {
13293
- if (!isAbsolute3(destination)) throw invalidArgument("Artifact export path must be absolute", "path");
13294
- const target = resolve2(destination);
13270
+ if (!isAbsolute4(destination)) throw invalidArgument("Artifact export path must be absolute", "path");
13271
+ const target = resolve(destination);
13295
13272
  if (this.isInsideStore(target)) {
13296
13273
  throw invalidArgument("Artifact export path must be outside Broker storage", "path");
13297
13274
  }
@@ -13308,7 +13285,7 @@ var ArtifactStore = class {
13308
13285
  });
13309
13286
  }
13310
13287
  const canonicalStore = await realpath(this.directory);
13311
- if (this.isWithin(canonicalStore, join5(canonicalParent, basename4(target)))) {
13288
+ if (this.isWithin(canonicalStore, join4(canonicalParent, basename3(target)))) {
13312
13289
  throw invalidArgument("Artifact export path must be outside Broker storage", "path");
13313
13290
  }
13314
13291
  if (overwrite) {
@@ -13328,7 +13305,7 @@ var ArtifactStore = class {
13328
13305
  cause
13329
13306
  });
13330
13307
  });
13331
- await chmod2(target, 384).catch(() => {
13308
+ await chmod(target, 384).catch(() => {
13332
13309
  });
13333
13310
  return { artifact: { ...record.descriptor }, path: target };
13334
13311
  });
@@ -13385,8 +13362,8 @@ var ArtifactStore = class {
13385
13362
  return this.maxTotalBytes;
13386
13363
  }
13387
13364
  async ingestFile(input) {
13388
- if (!isAbsolute3(input.sourcePath)) throw invalidArgument("Artifact import path must be absolute", "path");
13389
- const source = resolve2(input.sourcePath);
13365
+ if (!isAbsolute4(input.sourcePath)) throw invalidArgument("Artifact import path must be absolute", "path");
13366
+ const source = resolve(input.sourcePath);
13390
13367
  if (this.isInsideStore(source)) {
13391
13368
  throw invalidArgument("Artifact import path must be outside Broker storage", "path");
13392
13369
  }
@@ -13394,6 +13371,21 @@ var ArtifactStore = class {
13394
13371
  await this.sweepUnlocked();
13395
13372
  await this.ensureDirectory();
13396
13373
  const canonicalStore = await realpath(this.directory);
13374
+ if (input.rejectSymbolicLink) {
13375
+ let sourceLinkInfo;
13376
+ try {
13377
+ sourceLinkInfo = await lstat2(source);
13378
+ } catch (cause) {
13379
+ throw new BrowserPilotError("invalid_argument", "Artifact import file is not accessible", {
13380
+ context: { field: "path" },
13381
+ rpcCode: -32602,
13382
+ cause
13383
+ });
13384
+ }
13385
+ if (sourceLinkInfo.isSymbolicLink() || !sourceLinkInfo.isFile()) {
13386
+ throw invalidArgument("Artifact import path must identify a regular file", "path");
13387
+ }
13388
+ }
13397
13389
  let canonicalSource;
13398
13390
  try {
13399
13391
  canonicalSource = await realpath(source);
@@ -13409,7 +13401,7 @@ var ArtifactStore = class {
13409
13401
  }
13410
13402
  let sourceInfo;
13411
13403
  try {
13412
- sourceInfo = await stat2(canonicalSource);
13404
+ sourceInfo = await stat(canonicalSource);
13413
13405
  } catch (cause) {
13414
13406
  throw new BrowserPilotError("invalid_argument", "Artifact import file is not accessible", {
13415
13407
  context: { field: "path" },
@@ -13419,8 +13411,8 @@ var ArtifactStore = class {
13419
13411
  }
13420
13412
  if (!sourceInfo.isFile()) throw invalidArgument("Artifact import path must identify a regular file", "path");
13421
13413
  this.assertQuota(input.workspaceId, sourceInfo.size);
13422
- const fileName = input.fileName ?? basename4(source);
13423
- if (!fileName || fileName === "." || fileName === ".." || basename4(fileName) !== fileName || fileName.length > 4096 || /[\u0000-\u001f\u007f]/.test(fileName)) {
13414
+ const fileName = input.fileName ?? basename3(source);
13415
+ if (!fileName || fileName === "." || fileName === ".." || basename3(fileName) !== fileName || fileName.length > 4096 || /[\u0000-\u001f\u007f]/.test(fileName)) {
13424
13416
  throw invalidArgument("Artifact import filename is invalid or too long", "path");
13425
13417
  }
13426
13418
  const resolvedMimeType = input.mimeType ?? inferredMimeType(fileName);
@@ -13428,14 +13420,14 @@ var ArtifactStore = class {
13428
13420
  throw invalidArgument("Artifact MIME type is invalid or too long", "mimeType");
13429
13421
  }
13430
13422
  const id = this.nextArtifactId();
13431
- const storageDirectory = join5(this.directory, randomUUID12());
13432
- const destination = join5(storageDirectory, fileName);
13433
- await mkdir2(storageDirectory, { mode: 448 });
13423
+ const storageDirectory = join4(this.directory, randomUUID12());
13424
+ const destination = join4(storageDirectory, fileName);
13425
+ await mkdir(storageDirectory, { mode: 448 });
13434
13426
  try {
13435
13427
  await copyFile(canonicalSource, destination, fsConstants.COPYFILE_EXCL);
13436
- await chmod2(destination, 384).catch(() => {
13428
+ await chmod(destination, 384).catch(() => {
13437
13429
  });
13438
- const copied = await stat2(destination);
13430
+ const copied = await stat(destination);
13439
13431
  if (!copied.isFile() || copied.size !== sourceInfo.size) {
13440
13432
  throw new BrowserPilotError("invalid_argument", "Artifact import file changed while it was being copied", {
13441
13433
  context: { field: "path" },
@@ -13457,11 +13449,9 @@ var ArtifactStore = class {
13457
13449
  };
13458
13450
  const record = { descriptor: descriptor2, path: destination };
13459
13451
  this.records.set(id, record);
13460
- if (input.removeSource) await unlink2(canonicalSource).catch(() => {
13461
- });
13462
13452
  return clone2(record);
13463
13453
  } catch (error) {
13464
- await rm2(storageDirectory, { recursive: true, force: true }).catch(() => {
13454
+ await rm(storageDirectory, { recursive: true, force: true }).catch(() => {
13465
13455
  });
13466
13456
  throw error;
13467
13457
  }
@@ -13473,10 +13463,10 @@ var ArtifactStore = class {
13473
13463
  this.assertQuota(input.workspaceId, byteSize);
13474
13464
  await this.ensureDirectory();
13475
13465
  const id = this.nextArtifactId();
13476
- const file = join5(this.directory, `${randomUUID12()}.bin`);
13466
+ const file = join4(this.directory, `${randomUUID12()}.bin`);
13477
13467
  const createdAt = this.now();
13478
13468
  await writeFile(file, input.bytes, { mode: 384, flag: "wx" });
13479
- await chmod2(file, 384).catch(() => {
13469
+ await chmod(file, 384).catch(() => {
13480
13470
  });
13481
13471
  const descriptor2 = {
13482
13472
  id,
@@ -13506,7 +13496,7 @@ var ArtifactStore = class {
13506
13496
  throw this.notFound(artifactId);
13507
13497
  }
13508
13498
  try {
13509
- const info = await stat2(record.path);
13499
+ const info = await stat(record.path);
13510
13500
  if (!info.isFile() || info.size !== record.descriptor.byteSize) throw new Error("Artifact file mismatch");
13511
13501
  } catch {
13512
13502
  this.records.delete(artifactId);
@@ -13530,15 +13520,15 @@ var ArtifactStore = class {
13530
13520
  }
13531
13521
  async ensureDirectory() {
13532
13522
  if (!this.initialized) {
13533
- await mkdir2(this.directory, { recursive: true, mode: 448 });
13523
+ await mkdir(this.directory, { recursive: true, mode: 448 });
13534
13524
  if (this.purgeOrphansOnInitialize) {
13535
- const entries = await readdir2(this.directory, { withFileTypes: true });
13536
- await Promise.all(entries.filter((entry) => entry.isFile() && entry.name.endsWith(".bin") || entry.isDirectory()).map((entry) => rm2(join5(this.directory, entry.name), { recursive: true, force: true }).catch(() => {
13525
+ const entries = await readdir(this.directory, { withFileTypes: true });
13526
+ await Promise.all(entries.filter((entry) => entry.isFile() && entry.name.endsWith(".bin") || entry.isDirectory()).map((entry) => rm(join4(this.directory, entry.name), { recursive: true, force: true }).catch(() => {
13537
13527
  })));
13538
13528
  }
13539
13529
  this.initialized = true;
13540
13530
  }
13541
- await chmod2(this.directory, 448).catch(() => {
13531
+ await chmod(this.directory, 448).catch(() => {
13542
13532
  });
13543
13533
  }
13544
13534
  notFound(artifactId) {
@@ -13553,11 +13543,11 @@ var ArtifactStore = class {
13553
13543
  }
13554
13544
  isInsideStore(path) {
13555
13545
  const child = relative(this.directory, path);
13556
- return child === "" || child !== ".." && !child.startsWith(`..${sep}`) && !isAbsolute3(child);
13546
+ return child === "" || child !== ".." && !child.startsWith(`..${sep}`) && !isAbsolute4(child);
13557
13547
  }
13558
13548
  isWithin(parent, path) {
13559
13549
  const child = relative(parent, path);
13560
- return child === "" || child !== ".." && !child.startsWith(`..${sep}`) && !isAbsolute3(child);
13550
+ return child === "" || child !== ".." && !child.startsWith(`..${sep}`) && !isAbsolute4(child);
13561
13551
  }
13562
13552
  nextArtifactId() {
13563
13553
  const id = this.idFactory();
@@ -13585,11 +13575,11 @@ var ArtifactStore = class {
13585
13575
  }
13586
13576
  }
13587
13577
  async removeStoredFile(path) {
13588
- await unlink2(path).catch(() => {
13578
+ await unlink(path).catch(() => {
13589
13579
  });
13590
13580
  const parent = dirname2(path);
13591
13581
  if (parent !== this.directory && this.isInsideStore(parent)) {
13592
- await rm2(parent, { recursive: true, force: true }).catch(() => {
13582
+ await rm(parent, { recursive: true, force: true }).catch(() => {
13593
13583
  });
13594
13584
  }
13595
13585
  }
@@ -13801,8 +13791,8 @@ var ManagedTargetJanitorClient = class {
13801
13791
  });
13802
13792
  this.worker = worker;
13803
13793
  this.ready = false;
13804
- const ready = new Promise((resolve3, reject) => {
13805
- this.readyResolve = resolve3;
13794
+ const ready = new Promise((resolve2, reject) => {
13795
+ this.readyResolve = resolve2;
13806
13796
  this.readyReject = reject;
13807
13797
  });
13808
13798
  worker.on("message", (value) => this.handleWorkerMessage(worker, value));
@@ -13907,7 +13897,7 @@ var ManagedTargetJanitorClient = class {
13907
13897
  }
13908
13898
  const id = this.nextRequestId++;
13909
13899
  const request = { id, method, params };
13910
- return new Promise((resolve3, reject) => {
13900
+ return new Promise((resolve2, reject) => {
13911
13901
  const timer = setTimeout(() => {
13912
13902
  this.pendingRequests.delete(id);
13913
13903
  reject(method === "create" ? new BrowserPilotError(
@@ -13917,7 +13907,7 @@ var ManagedTargetJanitorClient = class {
13917
13907
  ) : new Error(`Managed browser connection timeout: ${method}`));
13918
13908
  }, REQUEST_TIMEOUT_MS);
13919
13909
  timer.unref();
13920
- this.pendingRequests.set(id, { resolve: resolve3, reject, timer, method });
13910
+ this.pendingRequests.set(id, { resolve: resolve2, reject, timer, method });
13921
13911
  worker.send?.(request, (error) => {
13922
13912
  if (!error) return;
13923
13913
  const pending = this.pendingRequests.get(id);
@@ -13947,19 +13937,19 @@ var ManagedTargetJanitorClient = class {
13947
13937
  this.expectedExits.add(worker);
13948
13938
  if (cleanup2 && worker.stdin && !worker.stdin.destroyed) worker.stdin.end();
13949
13939
  else worker.kill("SIGTERM");
13950
- await new Promise((resolve3) => {
13940
+ await new Promise((resolve2) => {
13951
13941
  if (worker.exitCode !== null || worker.signalCode !== null) {
13952
- resolve3();
13942
+ resolve2();
13953
13943
  return;
13954
13944
  }
13955
13945
  const timer = setTimeout(() => {
13956
13946
  worker.kill("SIGKILL");
13957
- resolve3();
13947
+ resolve2();
13958
13948
  }, STOP_TIMEOUT_MS);
13959
13949
  timer.unref();
13960
13950
  worker.once("exit", () => {
13961
13951
  clearTimeout(timer);
13962
- resolve3();
13952
+ resolve2();
13963
13953
  });
13964
13954
  });
13965
13955
  if (this.worker === worker) this.failWorker(worker, this.unavailableError());
@@ -13989,7 +13979,7 @@ var ManagedTargetJanitorClient = class {
13989
13979
  };
13990
13980
 
13991
13981
  // src/version.ts
13992
- var BROWSER_PILOT_VERSION = "0.3.0-rc.2";
13982
+ var BROWSER_PILOT_VERSION = "0.3.0-rc.4";
13993
13983
 
13994
13984
  // src/daemon.ts
13995
13985
  var CLI_EXECUTABLE_PATH = publicExecutablePath(import.meta.url);
@@ -14003,7 +13993,7 @@ var browserProfile = process.argv[4] || "";
14003
13993
  ensureBrokerDirectoriesSync();
14004
13994
  for (const legacyFile of ["state.json", "refs.json"]) {
14005
13995
  try {
14006
- unlinkSync2(join6(STATE_DIR, legacyFile));
13996
+ unlinkSync2(join5(STATE_DIR, legacyFile));
14007
13997
  } catch {
14008
13998
  }
14009
13999
  }
@@ -14023,7 +14013,7 @@ function cleanup(brokerProcessIdentity) {
14023
14013
  removeBrokerLocatorSync(brokerProcessIdentity);
14024
14014
  }
14025
14015
  function readBody(req, maxBytes = 5 * 1024 * 1024) {
14026
- return new Promise((resolve3, reject) => {
14016
+ return new Promise((resolve2, reject) => {
14027
14017
  const chunks = [];
14028
14018
  let byteLength = 0;
14029
14019
  req.on("data", (value) => {
@@ -14036,7 +14026,7 @@ function readBody(req, maxBytes = 5 * 1024 * 1024) {
14036
14026
  }
14037
14027
  chunks.push(chunk);
14038
14028
  });
14039
- req.on("end", () => resolve3(Buffer.concat(chunks).toString("utf8")));
14029
+ req.on("end", () => resolve2(Buffer.concat(chunks).toString("utf8")));
14040
14030
  req.on("error", reject);
14041
14031
  });
14042
14032
  }
@@ -14103,8 +14093,6 @@ async function syncFetchAll(cdp, currentSessionId) {
14103
14093
  async function main() {
14104
14094
  const startedAt = Date.now();
14105
14095
  const brokerProcessIdentity = `${process.pid}:${startedAt}`;
14106
- writeBrokerStartingSync({ pid: process.pid, startedAt, brokerProcessIdentity });
14107
- process.once("exit", () => cleanup(brokerProcessIdentity));
14108
14096
  const cdp = new ManagedTargetJanitorClient({
14109
14097
  onLog: (message) => process.stderr.write(`Managed browser connection: ${message}
14110
14098
  `)
@@ -14121,8 +14109,10 @@ async function main() {
14121
14109
  const requestTermination = () => {
14122
14110
  void terminate();
14123
14111
  };
14112
+ process.once("exit", () => cleanup(brokerProcessIdentity));
14124
14113
  process.on("SIGTERM", requestTermination);
14125
14114
  process.on("SIGINT", requestTermination);
14115
+ writeBrokerStartingSync({ pid: process.pid, startedAt, brokerProcessIdentity });
14126
14116
  if (initialWsUrl) await cdp.connect(initialWsUrl);
14127
14117
  let activeSessionId;
14128
14118
  let currentWsUrl = initialWsUrl;