@buda-ai/connector 0.1.32 → 0.1.34

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/cli.js CHANGED
@@ -25,8 +25,8 @@ async function acquireExclusiveFileLock(targetPath, options = {}) {
25
25
  } catch (error) {
26
26
  if (!isAlreadyExists(error)) throw error;
27
27
  if (await shouldRecoverLock(lockPath, staleMs)) {
28
- await unlink(lockPath).catch(() => void 0);
29
- continue;
28
+ const recovered = await unlink(lockPath).then(() => true).catch(() => false);
29
+ if (recovered) continue;
30
30
  }
31
31
  if (Date.now() >= deadline) {
32
32
  throw new Error(`Timed out waiting for exclusive access to ${targetPath}`);
@@ -35,17 +35,23 @@ async function acquireExclusiveFileLock(targetPath, options = {}) {
35
35
  continue;
36
36
  }
37
37
  const ownerToken = randomUUID();
38
- await handle.writeFile(
39
- `${JSON.stringify({ pid: process.pid, ownerToken, createdAt: Date.now() })}
38
+ try {
39
+ await handle.writeFile(
40
+ `${JSON.stringify({ pid: process.pid, ownerToken, createdAt: Date.now() })}
40
41
  `
41
- );
42
- await handle.sync();
42
+ );
43
+ await handle.sync();
44
+ } catch (error) {
45
+ await handle.close().catch(() => void 0);
46
+ await unlink(lockPath).catch(() => void 0);
47
+ throw error;
48
+ }
49
+ await handle.close();
43
50
  let released = false;
44
51
  return {
45
52
  release: async () => {
46
53
  if (released) return;
47
54
  released = true;
48
- await handle.close();
49
55
  const currentOwner = await readFile(lockPath, "utf8").then((raw) => JSON.parse(raw).ownerToken).catch(() => void 0);
50
56
  if (currentOwner === ownerToken) await unlink(lockPath).catch(() => void 0);
51
57
  }
@@ -98,8 +104,8 @@ var defaultDependencies = {
98
104
  };
99
105
  var readRawSystemId = (dependencies) => {
100
106
  if (dependencies.platform === "darwin") {
101
- const output = dependencies.execFile("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"]);
102
- return output.match(/"IOPlatformUUID"\s*=\s*"([^"]+)"/)?.[1];
107
+ const output2 = dependencies.execFile("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"]);
108
+ return output2.match(/"IOPlatformUUID"\s*=\s*"([^"]+)"/)?.[1];
103
109
  }
104
110
  if (dependencies.platform === "linux") {
105
111
  for (const filePath of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
@@ -112,13 +118,13 @@ var readRawSystemId = (dependencies) => {
112
118
  return void 0;
113
119
  }
114
120
  if (dependencies.platform === "win32") {
115
- const output = dependencies.execFile("reg", [
121
+ const output2 = dependencies.execFile("reg", [
116
122
  "query",
117
123
  String.raw`HKLM\SOFTWARE\Microsoft\Cryptography`,
118
124
  "/v",
119
125
  "MachineGuid"
120
126
  ]);
121
- return output.match(/MachineGuid\s+REG_SZ\s+([^\r\n]+)/i)?.[1];
127
+ return output2.match(/MachineGuid\s+REG_SZ\s+([^\r\n]+)/i)?.[1];
122
128
  }
123
129
  return void 0;
124
130
  };
@@ -517,7 +523,7 @@ import path2 from "path";
517
523
  // package.json
518
524
  var package_default = {
519
525
  name: "@buda-ai/connector",
520
- version: "0.1.32",
526
+ version: "0.1.34",
521
527
  private: false,
522
528
  type: "module",
523
529
  publishConfig: {
@@ -535,6 +541,7 @@ var package_default = {
535
541
  dev: "tsx src/cli.ts dev",
536
542
  daemon: "tsx src/cli.ts daemon",
537
543
  new: "tsx src/cli.ts new",
544
+ prepack: "tsup --silent",
538
545
  start: "node dist/cli.js daemon",
539
546
  build: "tsup",
540
547
  test: "vitest run",
@@ -543,7 +550,7 @@ var package_default = {
543
550
  "lint:err": "biome format . --write && biome check . --write --diagnostic-level=error"
544
551
  },
545
552
  dependencies: {
546
- "@bunny-agent/daemon": "0.9.51",
553
+ "@bunny-agent/daemon": "0.9.60-beta.1",
547
554
  "@clack/prompts": "^1.0.0",
548
555
  "playwright-core": "^1.52.0",
549
556
  ws: "^8.21.0"
@@ -847,14 +854,14 @@ var openControllingTerminal = () => {
847
854
  try {
848
855
  inputFd = openSync(inputPath, "r");
849
856
  outputFd = openSync(outputPath, "w");
850
- const input = new ReadStream(inputFd);
851
- const output = new WriteStream(outputFd);
857
+ const input2 = new ReadStream(inputFd);
858
+ const output2 = new WriteStream(outputFd);
852
859
  return {
853
- input,
854
- output,
860
+ input: input2,
861
+ output: output2,
855
862
  close: () => {
856
- input.destroy();
857
- output.destroy();
863
+ input2.destroy();
864
+ output2.destroy();
858
865
  }
859
866
  };
860
867
  } catch {
@@ -1005,8 +1012,8 @@ var getConnectorSectionHeight = (state, height) => state.connectors.length ? Mat
1005
1012
  Math.max(4, state.connectors.length + 2),
1006
1013
  Math.max(4, Math.min(Math.floor(height / 3), height - 13))
1007
1014
  ) : 0;
1008
- var parseDashboardMouseWheel = (input, state, size) => {
1009
- const event = input.split("\x1B[<").at(-1);
1015
+ var parseDashboardMouseWheel = (input2, state, size) => {
1016
+ const event = input2.split("\x1B[<").at(-1);
1010
1017
  const match = event?.match(/^(64|65);\d+;(\d+)[Mm]/);
1011
1018
  if (!match) return void 0;
1012
1019
  const row = Number(match[2]);
@@ -1117,13 +1124,13 @@ var renderConnectors = (state, height, width) => {
1117
1124
  const range = state.connectors.length ? `${start + 1}-${start + visible.length}/${state.connectors.length}` : "0/0";
1118
1125
  const title = ` Connectors ${state.activeSection === "connectors" ? "[active]" : ""}`;
1119
1126
  const headerFill = Math.max(1, innerWidth - title.length - range.length);
1120
- const output = [
1127
+ const output2 = [
1121
1128
  `${state.activeSection === "connectors" ? colors.reverse : ""}+${title}${"-".repeat(headerFill)}${range}+${colors.reset}`
1122
1129
  ];
1123
1130
  for (let index = 0; index < contentHeight; index += 1) {
1124
1131
  const connector = visible[index];
1125
1132
  if (!connector) {
1126
- output.push(`|${" ".repeat(innerWidth)}|`);
1133
+ output2.push(`|${" ".repeat(innerWidth)}|`);
1127
1134
  continue;
1128
1135
  }
1129
1136
  const marker = connector.connectorId === state.selectedConnectorId ? ">" : " ";
@@ -1134,10 +1141,10 @@ var renderConnectors = (state, height, width) => {
1134
1141
  const serverWidth = Math.max(8, availableFieldWidth - connectorWidth - spaceWidth);
1135
1142
  const content = `${marker} ${statusText} ${truncate(connector.label ?? connector.connectorId, connectorWidth)} | ${truncate(connector.spaceId, spaceWidth)} | ${truncate(connector.serverUrl, serverWidth)}`;
1136
1143
  const color = connector.status === "Stopped" ? colors.dim : connector.status === "Online" ? colors.cyan : connector.status === "Connecting" || connector.status === "Stopping" ? colors.yellow : connector.status === "Error" || connector.status === "Unresponsive" ? colors.red : "";
1137
- output.push(`|${color}${truncate(content, innerWidth)}${colors.reset}|`);
1144
+ output2.push(`|${color}${truncate(content, innerWidth)}${colors.reset}|`);
1138
1145
  }
1139
- output.push(`+${"-".repeat(innerWidth)}+`);
1140
- return output;
1146
+ output2.push(`+${"-".repeat(innerWidth)}+`);
1147
+ return output2;
1141
1148
  };
1142
1149
  var renderPane = (title, lines, height, width, active, scroll) => {
1143
1150
  const innerWidth = width - 2;
@@ -1147,22 +1154,22 @@ var renderPane = (title, lines, height, width, active, scroll) => {
1147
1154
  const headerSuffix = scroll === 0 ? "following" : `${scroll} lines behind`;
1148
1155
  const titleText = ` ${title} ${active ? "[active]" : ""}`;
1149
1156
  const headerFill = Math.max(1, innerWidth - titleText.length - headerSuffix.length);
1150
- const output = [
1157
+ const output2 = [
1151
1158
  `${active ? colors.reverse : ""}+${titleText}${"-".repeat(headerFill)}${headerSuffix}+${colors.reset}`
1152
1159
  ];
1153
1160
  for (let index = 0; index < contentHeight; index += 1) {
1154
1161
  const line = visible[index];
1155
1162
  if (!line) {
1156
- output.push(`|${" ".repeat(innerWidth)}|`);
1163
+ output2.push(`|${" ".repeat(innerWidth)}|`);
1157
1164
  continue;
1158
1165
  }
1159
1166
  const levelColor = line.level === "error" ? colors.red : line.level === "warn" ? colors.yellow : colors.dim;
1160
1167
  const count = line.count > 1 ? ` x${line.count}` : "";
1161
1168
  const content = `${line.time} ${line.level.toUpperCase().padEnd(5)} ${line.message}${count}`;
1162
- output.push(`|${levelColor}${truncate(content, innerWidth)}${colors.reset}|`);
1169
+ output2.push(`|${levelColor}${truncate(content, innerWidth)}${colors.reset}|`);
1163
1170
  }
1164
- output.push(`+${"-".repeat(innerWidth)}+`);
1165
- return output;
1171
+ output2.push(`+${"-".repeat(innerWidth)}+`);
1172
+ return output2;
1166
1173
  };
1167
1174
  var renderFooter = (state, width) => {
1168
1175
  const managerKeys = state.connectors.length ? "Up/Down select | S stop | Esc exit | " : "";
@@ -1524,7 +1531,7 @@ var installConnectorConsoleCapture = () => {
1524
1531
  log: console.log,
1525
1532
  warn: console.warn
1526
1533
  };
1527
- const capture = (level, output) => (...args) => {
1534
+ const capture = (level, output2) => (...args) => {
1528
1535
  const message = format2(...args);
1529
1536
  if (!message.startsWith("[buda-connector]")) {
1530
1537
  writes.push(
@@ -1539,7 +1546,7 @@ var installConnectorConsoleCapture = () => {
1539
1546
  })
1540
1547
  );
1541
1548
  }
1542
- output(...args);
1549
+ output2(...args);
1543
1550
  };
1544
1551
  console.log = capture("info", original.log);
1545
1552
  console.warn = capture("warn", original.warn);
@@ -1817,7 +1824,7 @@ async function handleBrowserInput(sessionIdValue, body) {
1817
1824
  if (!sessionId) throw new Error("A valid browser session id is required.");
1818
1825
  const ev = body ?? {};
1819
1826
  const s = await ensureSession(sessionId);
1820
- const input = s.inputQueue.catch(() => {
1827
+ const input2 = s.inputQueue.catch(() => {
1821
1828
  }).then(async () => {
1822
1829
  switch (ev.type) {
1823
1830
  case "mouseMove":
@@ -1871,8 +1878,8 @@ async function handleBrowserInput(sessionIdValue, body) {
1871
1878
  throw new Error(`Unknown browser input event: ${String(ev.type)}`);
1872
1879
  }
1873
1880
  });
1874
- s.inputQueue = input;
1875
- await input;
1881
+ s.inputQueue = input2;
1882
+ await input2;
1876
1883
  }
1877
1884
  var getPageResult = async (s) => ({
1878
1885
  url: s.page.url(),
@@ -1902,16 +1909,16 @@ async function handleBrowserRequest(requestPath, method, body, context = {}) {
1902
1909
  if (!sessionId)
1903
1910
  return jsonResponse(400, { ok: false, error: "valid browser session id required" });
1904
1911
  if (route === "start" && method === "POST") {
1905
- const input = body ?? {};
1912
+ const input2 = body ?? {};
1906
1913
  const viewport = {
1907
- width: normalizeViewportDimension(input.width, 320, 2560, DEFAULT_VIEWPORT.width),
1908
- height: normalizeViewportDimension(input.height, 240, 2560, DEFAULT_VIEWPORT.height)
1914
+ width: normalizeViewportDimension(input2.width, 320, 2560, DEFAULT_VIEWPORT.width),
1915
+ height: normalizeViewportDimension(input2.height, 240, 2560, DEFAULT_VIEWPORT.height)
1909
1916
  };
1910
1917
  const s = await ensureSession(sessionId, viewport);
1911
- const clientId = normalizeSessionId(input.clientId);
1918
+ const clientId = normalizeSessionId(input2.clientId);
1912
1919
  if (clientId) s.clientIds.add(clientId);
1913
1920
  await resizeBrowser(s, viewport);
1914
- const url = input.url?.trim() || "about:blank";
1921
+ const url = input2.url?.trim() || "about:blank";
1915
1922
  await writeLog("info", `browser viewport ${viewport.width}x${viewport.height}`);
1916
1923
  await navigateBrowser(s, url);
1917
1924
  await writeLog("info", `browser ready at ${s.page.url()}`);
@@ -1922,8 +1929,8 @@ async function handleBrowserRequest(requestPath, method, body, context = {}) {
1922
1929
  return jsonResponse(200, { ok: true });
1923
1930
  }
1924
1931
  if (route === "release" && method === "POST") {
1925
- const input = body ?? {};
1926
- const clientId = normalizeSessionId(input.clientId);
1932
+ const input2 = body ?? {};
1933
+ const clientId = normalizeSessionId(input2.clientId);
1927
1934
  if (!clientId) return jsonResponse(400, { ok: false, error: "valid client id required" });
1928
1935
  const s = sessions.get(sessionId);
1929
1936
  if (!s) return jsonResponse(200, { ok: true, closed: false });
@@ -1933,13 +1940,13 @@ async function handleBrowserRequest(requestPath, method, body, context = {}) {
1933
1940
  return jsonResponse(200, { ok: true, closed: true });
1934
1941
  }
1935
1942
  if (route === "navigate" && method === "POST") {
1936
- const input = body ?? {};
1937
- const url = input.url?.trim();
1943
+ const input2 = body ?? {};
1944
+ const url = input2.url?.trim();
1938
1945
  if (!url) return jsonResponse(400, { ok: false, error: "url required" });
1939
1946
  const s = await ensureSession(sessionId);
1940
1947
  const response = await navigateBrowser(s, url, {
1941
- waitUntil: input.waitUntil ?? "domcontentloaded",
1942
- timeout: normalizeTimeout(input.timeoutMs, 3e4, 6e4)
1948
+ waitUntil: input2.waitUntil ?? "domcontentloaded",
1949
+ timeout: normalizeTimeout(input2.timeoutMs, 3e4, 6e4)
1943
1950
  });
1944
1951
  return jsonResponse(200, {
1945
1952
  ok: true,
@@ -1949,14 +1956,14 @@ async function handleBrowserRequest(requestPath, method, body, context = {}) {
1949
1956
  });
1950
1957
  }
1951
1958
  if (route === "click" && method === "POST") {
1952
- const input = body ?? {};
1953
- const selector = input.selector?.trim();
1959
+ const input2 = body ?? {};
1960
+ const selector = input2.selector?.trim();
1954
1961
  if (!selector) return jsonResponse(400, { ok: false, error: "selector required" });
1955
1962
  const s = await ensureSession(sessionId);
1956
- const button = input.button === "right" || input.button === "middle" ? input.button : "left";
1963
+ const button = input2.button === "right" || input2.button === "middle" ? input2.button : "left";
1957
1964
  await s.page.locator(selector).click({
1958
1965
  button,
1959
- timeout: normalizeTimeout(input.timeoutMs, 1e4, 3e4)
1966
+ timeout: normalizeTimeout(input2.timeoutMs, 1e4, 3e4)
1960
1967
  });
1961
1968
  await s.page.waitForLoadState("domcontentloaded", { timeout: 5e3 }).catch(() => {
1962
1969
  });
@@ -1968,14 +1975,14 @@ async function handleBrowserRequest(requestPath, method, body, context = {}) {
1968
1975
  });
1969
1976
  }
1970
1977
  if (route === "type" && method === "POST") {
1971
- const input = body ?? {};
1972
- const selector = input.selector?.trim();
1978
+ const input2 = body ?? {};
1979
+ const selector = input2.selector?.trim();
1973
1980
  if (!selector) return jsonResponse(400, { ok: false, error: "selector required" });
1974
1981
  const s = await ensureSession(sessionId);
1975
1982
  const locator = s.page.locator(selector);
1976
- const text2 = typeof input.text === "string" ? input.text : String(input.text ?? "");
1977
- const timeout = normalizeTimeout(input.timeoutMs, 1e4, 3e4);
1978
- if (input.clear !== false) {
1983
+ const text2 = typeof input2.text === "string" ? input2.text : String(input2.text ?? "");
1984
+ const timeout = normalizeTimeout(input2.timeoutMs, 1e4, 3e4);
1985
+ if (input2.clear !== false) {
1979
1986
  await locator.fill("", { timeout });
1980
1987
  await locator.fill(text2, { timeout });
1981
1988
  } else {
@@ -1989,12 +1996,12 @@ async function handleBrowserRequest(requestPath, method, body, context = {}) {
1989
1996
  });
1990
1997
  }
1991
1998
  if (route === "extract" && method === "POST") {
1992
- const input = body ?? {};
1993
- const selector = input.selector?.trim() || "body";
1999
+ const input2 = body ?? {};
2000
+ const selector = input2.selector?.trim() || "body";
1994
2001
  const s = await ensureSession(sessionId);
1995
2002
  const locator = s.page.locator(selector).first();
1996
2003
  const text2 = (await locator.innerText({ timeout: 1e4 }).catch(async () => s.page.locator("body").innerText({ timeout: 1e4 }))).trim();
1997
- const maxChars = typeof input.maxChars === "number" && Number.isFinite(input.maxChars) ? Math.min(2e4, Math.max(500, Math.round(input.maxChars))) : 8e3;
2004
+ const maxChars = typeof input2.maxChars === "number" && Number.isFinite(input2.maxChars) ? Math.min(2e4, Math.max(500, Math.round(input2.maxChars))) : 8e3;
1998
2005
  return jsonResponse(200, {
1999
2006
  ok: true,
2000
2007
  action: "extract",
@@ -2003,13 +2010,13 @@ async function handleBrowserRequest(requestPath, method, body, context = {}) {
2003
2010
  });
2004
2011
  }
2005
2012
  if (route === "screenshot" && method === "POST") {
2006
- const input = body ?? {};
2013
+ const input2 = body ?? {};
2007
2014
  const s = await ensureSession(sessionId);
2008
- const screenshotPath = resolveScreenshotPath(context.workdirRoot, input.file_path);
2015
+ const screenshotPath = resolveScreenshotPath(context.workdirRoot, input2.file_path);
2009
2016
  await mkdir4(path4.dirname(screenshotPath.absolutePath), { recursive: true });
2010
2017
  await s.page.screenshot({
2011
2018
  path: screenshotPath.absolutePath,
2012
- fullPage: input.fullPage !== false
2019
+ fullPage: input2.fullPage !== false
2013
2020
  });
2014
2021
  return jsonResponse(200, {
2015
2022
  ok: true,
@@ -2020,10 +2027,10 @@ async function handleBrowserRequest(requestPath, method, body, context = {}) {
2020
2027
  });
2021
2028
  }
2022
2029
  if (route === "resize" && method === "POST") {
2023
- const input = body ?? {};
2030
+ const input2 = body ?? {};
2024
2031
  const viewport = {
2025
- width: normalizeViewportDimension(input.width, 320, 2560, DEFAULT_VIEWPORT.width),
2026
- height: normalizeViewportDimension(input.height, 240, 2560, DEFAULT_VIEWPORT.height)
2032
+ width: normalizeViewportDimension(input2.width, 320, 2560, DEFAULT_VIEWPORT.width),
2033
+ height: normalizeViewportDimension(input2.height, 240, 2560, DEFAULT_VIEWPORT.height)
2027
2034
  };
2028
2035
  const s = await ensureSession(sessionId);
2029
2036
  await resizeBrowser(s, viewport);
@@ -2208,6 +2215,13 @@ var ConnectorDeletedError = class extends Error {
2208
2215
  this.name = "ConnectorDeletedError";
2209
2216
  }
2210
2217
  };
2218
+ var ConnectorUnauthorizedError = class extends Error {
2219
+ constructor(message) {
2220
+ super(message);
2221
+ this.name = "ConnectorUnauthorizedError";
2222
+ }
2223
+ };
2224
+ var UNREGISTER_TIMEOUT_MS = 3e3;
2211
2225
  async function resolveConnectorScope(deviceId, launchConfig) {
2212
2226
  const serverUrl = normalizeServerOrigin(getServerUrl(launchConfig));
2213
2227
  const headers = { "Content-Type": "application/json" };
@@ -2294,14 +2308,15 @@ function buildAuthHeaders(config, launchConfig) {
2294
2308
  if (config.deviceToken) headers["X-Buda-Connector-Device-Token"] = config.deviceToken;
2295
2309
  return headers;
2296
2310
  }
2297
- async function postJson(serverUrl, pathname, body, config, launchConfig) {
2311
+ async function postJson(serverUrl, pathname, body, config, launchConfig, options = {}) {
2298
2312
  const response = await fetch(`${serverUrl}${pathname}`, {
2299
2313
  method: "POST",
2300
2314
  headers: {
2301
2315
  "Content-Type": "application/json",
2302
2316
  ...buildAuthHeaders(config, launchConfig)
2303
2317
  },
2304
- body: JSON.stringify(body)
2318
+ body: JSON.stringify(body),
2319
+ ...options.timeoutMs ? { signal: AbortSignal.timeout(options.timeoutMs) } : {}
2305
2320
  });
2306
2321
  if (!response.ok) {
2307
2322
  const text2 = await response.text().catch(() => "");
@@ -2321,6 +2336,9 @@ async function postJson(serverUrl, pathname, body, config, launchConfig) {
2321
2336
  if (errorCode === "connector_deleted") {
2322
2337
  throw new ConnectorDeletedError(errorMessage3 || "Connector no longer exists");
2323
2338
  }
2339
+ if (response.status === 401) {
2340
+ throw new ConnectorUnauthorizedError(errorMessage3 || "Connector authentication failed");
2341
+ }
2324
2342
  throw new Error(`${pathname} failed: ${response.status} ${errorMessage3}`);
2325
2343
  }
2326
2344
  return await response.json();
@@ -2396,7 +2414,8 @@ async function unregisterConnector(config, launchConfig) {
2396
2414
  connectorId: config.connectorId
2397
2415
  },
2398
2416
  config,
2399
- launchConfig
2417
+ launchConfig,
2418
+ { timeoutMs: UNREGISTER_TIMEOUT_MS }
2400
2419
  ).catch((error) => {
2401
2420
  void writeLog("warn", `unregister failed: ${error instanceof Error ? error.message : error}`);
2402
2421
  });
@@ -2421,11 +2440,19 @@ import path6 from "path";
2421
2440
  import { promisify } from "util";
2422
2441
  var execFileAsync = promisify(execFile);
2423
2442
  var RUNTIME_INSTALL_TIMEOUT_MS = 5 * 6e4;
2443
+ var WINDOWS_RENAME_RETRY_DELAYS_MS = [50, 100, 200, 400, 500, 500, 500, 500, 500];
2424
2444
  var MACOS_NODE_LIBRARY_PATTERN = /^libnode(?:\.\d+)*\.dylib$/;
2425
2445
  var getLoadedSharedObjects = () => {
2426
2446
  const report = process.report?.getReport();
2427
2447
  return Array.isArray(report?.sharedObjects) ? report.sharedObjects.filter((value) => typeof value === "string") : [];
2428
2448
  };
2449
+ var resolveBundledNodePlacement = (sourceRoot, sourceNode, pathApi = path6) => {
2450
+ const relativeNode = pathApi.relative(sourceRoot, sourceNode);
2451
+ return {
2452
+ copyNode: relativeNode.startsWith("..") || pathApi.isAbsolute(relativeNode),
2453
+ relativeNode
2454
+ };
2455
+ };
2429
2456
  var copyNodeRuntime = async (sourceNode, destinationNode, options = {}) => {
2430
2457
  const resolvedSourceNode = await realpath(sourceNode);
2431
2458
  const sourceDirectory = path6.dirname(resolvedSourceNode);
@@ -2452,8 +2479,7 @@ var copyNodeRuntime = async (sourceNode, destinationNode, options = {}) => {
2452
2479
  }))
2453
2480
  )).flatMap(({ directory, entries }) => entries.map((entry) => ({ directory, entry })));
2454
2481
  const companionFiles = /* @__PURE__ */ new Map();
2455
- const loadedLibraries = options.loadedLibraries ?? getLoadedSharedObjects();
2456
- for (const library of loadedLibraries) {
2482
+ for (const library of options.loadedLibraries ?? getLoadedSharedObjects()) {
2457
2483
  const name = path6.basename(library);
2458
2484
  if (MACOS_NODE_LIBRARY_PATTERN.test(name)) companionFiles.set(name, library);
2459
2485
  }
@@ -2471,7 +2497,44 @@ var copyNodeRuntime = async (sourceNode, destinationNode, options = {}) => {
2471
2497
  )
2472
2498
  );
2473
2499
  };
2500
+ var isWindowsRenameContention = (error) => error instanceof Error && "code" in error && ["EACCES", "EBUSY", "EPERM"].includes(String(error.code));
2501
+ var renameStagedRuntime = async (source, destination, options = {}) => {
2502
+ const platform4 = options.platform ?? process.platform;
2503
+ const renamePath = options.renamePath ?? rename4;
2504
+ const wait4 = options.wait ?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
2505
+ const delays = platform4 === "win32" ? WINDOWS_RENAME_RETRY_DELAYS_MS : [];
2506
+ for (let attempt = 0; ; attempt += 1) {
2507
+ try {
2508
+ await renamePath(source, destination);
2509
+ return;
2510
+ } catch (error) {
2511
+ const delay = delays[attempt];
2512
+ if (delay === void 0 || !isWindowsRenameContention(error)) throw error;
2513
+ await wait4(delay);
2514
+ }
2515
+ }
2516
+ };
2474
2517
  var exists = async (filePath) => access(filePath).then(() => true).catch(() => false);
2518
+ var resolveNpmInvocation = async (options = {}) => {
2519
+ const platform4 = options.platform ?? process.platform;
2520
+ if (platform4 !== "win32") return { argsPrefix: [], executable: "npm" };
2521
+ const nodeExecutable = options.nodeExecutable ?? process.execPath;
2522
+ const pathApi = options.pathApi ?? path6;
2523
+ const pathExists = options.pathExists ?? exists;
2524
+ const candidates = [
2525
+ options.npmExecPath ?? process.env.npm_execpath,
2526
+ pathApi.join(pathApi.dirname(nodeExecutable), "node_modules", "npm", "bin", "npm-cli.js")
2527
+ ];
2528
+ for (const candidate of candidates) {
2529
+ if (!candidate || pathApi.basename(candidate).toLowerCase() !== "npm-cli.js") continue;
2530
+ if (await pathExists(candidate)) {
2531
+ return { argsPrefix: [candidate], executable: nodeExecutable };
2532
+ }
2533
+ }
2534
+ throw new Error(
2535
+ `Unable to locate npm-cli.js for the stable Connector runtime installation (node=${JSON.stringify(nodeExecutable)}, npm_execpath=${JSON.stringify(options.npmExecPath ?? process.env.npm_execpath ?? "")})`
2536
+ );
2537
+ };
2475
2538
  var runtimeRoot = () => process.env.BUDA_CONNECTOR_RUNTIME_ROOT || path6.join(homedir3(), ".buda", "runtime");
2476
2539
  var runtimePlatformKey = `${process.platform}-${process.arch}-node${process.versions.modules}`;
2477
2540
  var versionDirectory = () => path6.join(runtimeRoot(), runtimePlatformKey, CONNECTOR_RUNTIME_VERSION);
@@ -2505,9 +2568,11 @@ var validateRuntime = async (runtime) => {
2505
2568
  };
2506
2569
  var installFromNpm = async (directory) => {
2507
2570
  await mkdir5(directory, { recursive: true, mode: 448 });
2571
+ const npm = await resolveNpmInvocation();
2508
2572
  await execFileAsync(
2509
- process.platform === "win32" ? "npm.cmd" : "npm",
2573
+ npm.executable,
2510
2574
  [
2575
+ ...npm.argsPrefix,
2511
2576
  "install",
2512
2577
  "--ignore-scripts=false",
2513
2578
  "--no-audit",
@@ -2541,10 +2606,10 @@ var installFromBundledRuntime = async (directory, sourceRoot) => {
2541
2606
  if (relativeEntrypoint.startsWith("..") || path6.isAbsolute(relativeEntrypoint)) {
2542
2607
  throw new Error("Bundled Connector entrypoint must be inside its runtime root");
2543
2608
  }
2544
- const relativeNode = path6.relative(sourceRoot, sourceNode);
2609
+ const { copyNode, relativeNode } = resolveBundledNodePlacement(sourceRoot, sourceNode);
2545
2610
  const entrypoint = path6.join(directory, relativeEntrypoint);
2546
- const nodeExecutable = relativeNode.startsWith("..") ? path6.join(directory, "node", path6.basename(sourceNode)) : path6.join(directory, relativeNode);
2547
- if (relativeNode.startsWith("..")) {
2611
+ const nodeExecutable = copyNode ? path6.join(directory, "node", path6.basename(sourceNode)) : path6.join(directory, relativeNode);
2612
+ if (copyNode) {
2548
2613
  await copyNodeRuntime(sourceNode, nodeExecutable);
2549
2614
  }
2550
2615
  if (!await exists(entrypoint))
@@ -2591,7 +2656,7 @@ async function ensureStableConnectorRuntime() {
2591
2656
  };
2592
2657
  await writeManifest(temporary, rebased);
2593
2658
  await rm(finalDirectory, { recursive: true, force: true });
2594
- await rename4(temporary, finalDirectory);
2659
+ await renameStagedRuntime(temporary, finalDirectory);
2595
2660
  return rebased;
2596
2661
  } catch (error) {
2597
2662
  await rm(temporary, { recursive: true, force: true });
@@ -2605,6 +2670,8 @@ async function ensureStableConnectorRuntime() {
2605
2670
  // src/daemon-client.ts
2606
2671
  var START_TIMEOUT_MS = 45e3;
2607
2672
  var BOOTSTRAP_TIMEOUT_MS = 5 * 6e4;
2673
+ var GRACEFUL_STOP_TIMEOUT_MS = 3e3;
2674
+ var LOCK_ONLY_RECOVERY_MAX_AGE_MS = 5 * 6e4;
2608
2675
  var POLL_INTERVAL_MS = 250;
2609
2676
  var wait2 = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
2610
2677
  var getConfigRoot = () => process.env.BUDA_CONNECTOR_CONFIG_ROOT;
@@ -2637,14 +2704,78 @@ var compareVersions = (left, right) => {
2637
2704
  return 0;
2638
2705
  };
2639
2706
  var shouldReplaceDaemonVersion = (runningVersion, candidateVersion) => compareVersions(candidateVersion, runningVersion) > 0;
2707
+ var shouldDisplayConnectorLogEntry = (createdAt, startedAt) => {
2708
+ const timestamp = Date.parse(createdAt);
2709
+ return Number.isNaN(timestamp) || timestamp >= startedAt;
2710
+ };
2711
+ var getErrorCode = (error) => typeof error === "object" && error !== null && "code" in error ? String(error.code) : "UNKNOWN";
2712
+ var waitForChildSpawn = (child) => new Promise((resolve, reject) => {
2713
+ const onError = (error) => {
2714
+ child.off("spawn", onSpawn);
2715
+ reject(error);
2716
+ };
2717
+ const onSpawn = () => {
2718
+ child.off("error", onError);
2719
+ resolve();
2720
+ };
2721
+ child.once("error", onError);
2722
+ child.once("spawn", onSpawn);
2723
+ });
2724
+ async function spawnDetachedDaemonProcess(command, args, options, dependencies = {}) {
2725
+ const platform4 = dependencies.platform ?? process.platform;
2726
+ const spawnProcess = dependencies.spawnProcess ?? ((file, fileArgs, spawnOptions) => spawn(file, fileArgs, spawnOptions));
2727
+ const strategies = platform4 === "win32" ? [
2728
+ { name: "detached-hidden", windowsHide: true },
2729
+ { name: "detached-unhidden", windowsHide: false }
2730
+ ] : [{ name: "detached-hidden", windowsHide: true }];
2731
+ const attempted = [];
2732
+ for (const [index, strategy] of strategies.entries()) {
2733
+ attempted.push(strategy.name);
2734
+ const child = spawnProcess(command, args, {
2735
+ detached: true,
2736
+ env: options.env,
2737
+ stdio: options.stdio,
2738
+ windowsHide: strategy.windowsHide
2739
+ });
2740
+ try {
2741
+ await waitForChildSpawn(child);
2742
+ if (!child.pid) {
2743
+ throw Object.assign(new Error("spawned Connector daemon did not expose a PID"), {
2744
+ code: "NO_PID"
2745
+ });
2746
+ }
2747
+ child.unref();
2748
+ return { pid: child.pid, strategy: strategy.name };
2749
+ } catch (error) {
2750
+ const code = getErrorCode(error);
2751
+ const shouldRetry = platform4 === "win32" && index === 0 && code === "EINVAL";
2752
+ if (shouldRetry) continue;
2753
+ const diagnostics = [
2754
+ `code=${code}`,
2755
+ `strategies=${attempted.join(",")}`,
2756
+ `platform=${platform4}`,
2757
+ `node=${dependencies.nodeVersion ?? process.version}`,
2758
+ `executable=${JSON.stringify(command)}`,
2759
+ `entrypoint=${JSON.stringify(args[0] ?? "")}`,
2760
+ `cwd=${JSON.stringify(dependencies.cwd ?? process.cwd())}`
2761
+ ].join(" ");
2762
+ throw new Error(`Connector daemon process creation failed: ${diagnostics}`, { cause: error });
2763
+ }
2764
+ }
2765
+ throw new Error("Connector daemon process creation failed without a spawn attempt");
2766
+ }
2767
+ var readReadyDaemon = async (launchConfig) => {
2768
+ const config = await readDeviceConfig(launchConfig, getConfigRoot());
2769
+ if (!config) return null;
2770
+ const status = await readLiveStatus(config);
2771
+ return status ? { config, status } : null;
2772
+ };
2640
2773
  var waitForDaemon = async (launchConfig, deadline) => {
2641
2774
  let lastConfig = null;
2642
2775
  while (Date.now() < deadline) {
2776
+ const ready = await readReadyDaemon(launchConfig);
2777
+ if (ready) return ready;
2643
2778
  lastConfig = await readDeviceConfig(launchConfig, getConfigRoot());
2644
- if (lastConfig) {
2645
- const status = await readLiveStatus(lastConfig);
2646
- if (status) return { config: lastConfig, status };
2647
- }
2648
2779
  await wait2(POLL_INTERVAL_MS);
2649
2780
  }
2650
2781
  throw new Error(
@@ -2653,17 +2784,126 @@ var waitForDaemon = async (launchConfig, deadline) => {
2653
2784
  };
2654
2785
  var spawnInternalDaemon = async (launchConfig, mode, preparedRuntime, options = {}) => {
2655
2786
  const runtime = preparedRuntime ?? await ensureStableConnectorRuntime();
2656
- const child = spawn(runtime.nodeExecutable, getRuntimeCommandArgs(runtime, mode, options), {
2657
- detached: true,
2658
- env: {
2659
- ...process.env,
2660
- BUDA_CONNECTOR_INTERNAL_LAUNCH_CONFIG: JSON.stringify(launchConfig ?? {})
2661
- },
2662
- stdio: "ignore",
2663
- windowsHide: true
2664
- });
2665
- child.unref();
2787
+ const debugDaemonStdio = process.env.BUDA_CONNECTOR_DEBUG_DAEMON_STDIO === "1";
2788
+ return spawnDetachedDaemonProcess(
2789
+ runtime.nodeExecutable,
2790
+ getRuntimeCommandArgs(runtime, mode, options),
2791
+ {
2792
+ env: {
2793
+ ...process.env,
2794
+ BUDA_CONNECTOR_INTERNAL_LAUNCH_CONFIG: JSON.stringify(launchConfig ?? {})
2795
+ },
2796
+ stdio: debugDaemonStdio ? ["ignore", "inherit", "inherit"] : "ignore"
2797
+ }
2798
+ );
2799
+ };
2800
+ var waitForProcessExit = async (pid, timeoutMs) => {
2801
+ const deadline = Date.now() + timeoutMs;
2802
+ while (Date.now() < deadline) {
2803
+ if (!isProcessAlive2(pid)) return true;
2804
+ await wait2(50);
2805
+ }
2806
+ return !isProcessAlive2(pid);
2807
+ };
2808
+ var signalProcess = (pid, signal) => {
2809
+ try {
2810
+ process.kill(pid, signal);
2811
+ return true;
2812
+ } catch (error) {
2813
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ESRCH") {
2814
+ return false;
2815
+ }
2816
+ throw error;
2817
+ }
2818
+ };
2819
+ var stopUnresponsiveDaemonProcess = async (pid) => {
2820
+ if (!isProcessAlive2(pid)) return;
2821
+ if (!signalProcess(pid, "SIGTERM")) return;
2822
+ if (await waitForProcessExit(pid, 2e3)) return;
2823
+ if (!signalProcess(pid, "SIGKILL")) return;
2824
+ if (await waitForProcessExit(pid, 3e3)) return;
2825
+ throw new Error(`Timed-out Connector daemon PID ${pid} could not be stopped safely`);
2826
+ };
2827
+ var waitForDaemonRelease = async (config, status, configRoot = getConfigRoot()) => {
2828
+ const deadline = Date.now() + GRACEFUL_STOP_TIMEOUT_MS;
2829
+ while (Date.now() < deadline) {
2830
+ if (!isProcessAlive2(status.pid)) return true;
2831
+ const current = await readPersistedDaemonStatus(config, configRoot).catch(() => null);
2832
+ if (!current || current.pid !== status.pid || current.instanceId !== status.instanceId) {
2833
+ return true;
2834
+ }
2835
+ await wait2(50);
2836
+ }
2837
+ return !isProcessAlive2(status.pid);
2666
2838
  };
2839
+ var stopLiveDaemon = async (config, status, configRoot = getConfigRoot(), command = "stop") => {
2840
+ const stopStatus = await requestDaemonControl(config, command, configRoot);
2841
+ if (command === "stop-if-idle" && !stopStatus.idle) return false;
2842
+ if (await waitForDaemonRelease(config, status, configRoot)) return true;
2843
+ const current = await readPersistedDaemonStatus(config, configRoot).catch(() => null);
2844
+ if (!current || current.pid !== status.pid || current.instanceId !== status.instanceId) {
2845
+ return true;
2846
+ }
2847
+ await stopUnresponsiveDaemonProcess(status.pid);
2848
+ return true;
2849
+ };
2850
+ var matchesDaemonIdentity = (config, status) => status.connectorId === config.connectorId && normalizeServerOrigin(status.serverUrl) === normalizeServerOrigin(config.serverUrl) && Number.isSafeInteger(status.pid) && status.pid > 0 && typeof status.instanceId === "string" && status.instanceId.length > 0;
2851
+ var readMatchingPersistedDaemonStatus = async (config, configRoot = getConfigRoot()) => {
2852
+ const status = await readPersistedDaemonStatus(config, configRoot).catch(() => null);
2853
+ return status && matchesDaemonIdentity(config, status) ? status : null;
2854
+ };
2855
+ var stopDaemonForIdentity = async (config, configRoot = getConfigRoot()) => {
2856
+ const liveStatus = await readLiveStatus(config, configRoot);
2857
+ if (liveStatus) {
2858
+ await stopLiveDaemon(config, liveStatus, configRoot);
2859
+ return;
2860
+ }
2861
+ const persistedStatus = await readMatchingPersistedDaemonStatus(config, configRoot);
2862
+ if (!persistedStatus || !isProcessAlive2(persistedStatus.pid)) return;
2863
+ const currentStatus = await readMatchingPersistedDaemonStatus(config, configRoot);
2864
+ if (!currentStatus || currentStatus.pid !== persistedStatus.pid || currentStatus.instanceId !== persistedStatus.instanceId || !isProcessAlive2(currentStatus.pid)) {
2865
+ return;
2866
+ }
2867
+ await stopUnresponsiveDaemonProcess(currentStatus.pid);
2868
+ };
2869
+ var shouldRestartUnhealthyDaemonForEnrollment = (status, explicitEnrollment) => explicitEnrollment && status.idle && status.heartbeatHealthy === false;
2870
+ var readDaemonLockOwnerPid = async (config) => {
2871
+ const lockPath = `${getConnectorDaemonLockTarget(
2872
+ config.serverUrl,
2873
+ config.connectorId,
2874
+ getConfigRoot()
2875
+ )}.lock`;
2876
+ const raw = await readFile6(lockPath, "utf8").catch(() => "");
2877
+ try {
2878
+ const metadata = JSON.parse(raw);
2879
+ const age = typeof metadata.createdAt === "number" && Number.isFinite(metadata.createdAt) ? Date.now() - metadata.createdAt : Number.POSITIVE_INFINITY;
2880
+ return typeof metadata.pid === "number" && Number.isSafeInteger(metadata.pid) && metadata.pid > 0 && age >= 0 && age <= LOCK_ONLY_RECOVERY_MAX_AGE_MS ? metadata.pid : null;
2881
+ } catch {
2882
+ return null;
2883
+ }
2884
+ };
2885
+ async function startDaemonWithRecovery(dependencies, maxAttempts = 2) {
2886
+ let lastError;
2887
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
2888
+ const spawned = await dependencies.spawnDaemon();
2889
+ try {
2890
+ return await dependencies.waitForReady();
2891
+ } catch (error) {
2892
+ lastError = error;
2893
+ const lateReady = await dependencies.readReady().catch(() => null);
2894
+ if (lateReady) return lateReady;
2895
+ await dependencies.stopDaemon(spawned.pid);
2896
+ if (attempt === maxAttempts) throw error;
2897
+ }
2898
+ }
2899
+ throw lastError;
2900
+ }
2901
+ var startInternalDaemonWithRecovery = (launchConfig, mode, preparedRuntime, options = {}) => startDaemonWithRecovery({
2902
+ readReady: () => readReadyDaemon(launchConfig),
2903
+ spawnDaemon: () => spawnInternalDaemon(launchConfig, mode, preparedRuntime, options),
2904
+ stopDaemon: stopUnresponsiveDaemonProcess,
2905
+ waitForReady: () => waitForDaemon(launchConfig, Date.now() + START_TIMEOUT_MS)
2906
+ });
2667
2907
  var stopOlderIdleDaemon = async (config, status) => {
2668
2908
  if (!shouldReplaceDaemonVersion(status.version, CONNECTOR_RUNTIME_VERSION)) return false;
2669
2909
  if (!status.idle) return false;
@@ -2690,14 +2930,19 @@ async function ensureConnectorDaemon(launchConfig, mode, options = {}) {
2690
2930
  if (existingConfig) {
2691
2931
  const existingStatus = await readLiveStatus(existingConfig);
2692
2932
  if (existingStatus) {
2933
+ if (shouldRestartUnhealthyDaemonForEnrollment(
2934
+ existingStatus,
2935
+ Boolean(options.explicitEnrollment)
2936
+ ) && await stopLiveDaemon(existingConfig, existingStatus, getConfigRoot(), "stop-if-idle")) {
2937
+ return await startInternalDaemonWithRecovery(launchConfig, mode, void 0, options);
2938
+ }
2693
2939
  const preparedRuntime = !shouldReplaceDaemonVersion(existingStatus.version, CONNECTOR_RUNTIME_VERSION) || !existingStatus.idle ? void 0 : await ensureStableConnectorRuntime();
2694
2940
  const replaced = await stopOlderIdleDaemon(existingConfig, existingStatus);
2695
2941
  if (!replaced) {
2696
2942
  const status = mode === "persistent" ? await requestDaemonControl(existingConfig, "persist", getConfigRoot()) : existingStatus;
2697
2943
  return { config: existingConfig, status };
2698
2944
  }
2699
- await spawnInternalDaemon(launchConfig, mode, preparedRuntime, options);
2700
- return await waitForDaemon(launchConfig, Date.now() + START_TIMEOUT_MS);
2945
+ return await startInternalDaemonWithRecovery(launchConfig, mode, preparedRuntime, options);
2701
2946
  }
2702
2947
  const persistedStatus = await readPersistedDaemonStatus(
2703
2948
  existingConfig,
@@ -2708,9 +2953,12 @@ async function ensureConnectorDaemon(launchConfig, mode, options = {}) {
2708
2953
  `Connector daemon PID ${persistedStatus.pid} is running but its local control channel is not responding. Stop that process and retry.`
2709
2954
  );
2710
2955
  }
2956
+ const lockOwnerPid = await readDaemonLockOwnerPid(existingConfig);
2957
+ if (lockOwnerPid && isProcessAlive2(lockOwnerPid)) {
2958
+ await stopUnresponsiveDaemonProcess(lockOwnerPid);
2959
+ }
2711
2960
  }
2712
- await spawnInternalDaemon(launchConfig, mode, void 0, options);
2713
- return await waitForDaemon(launchConfig, Date.now() + START_TIMEOUT_MS);
2961
+ return await startInternalDaemonWithRecovery(launchConfig, mode, void 0, options);
2714
2962
  } finally {
2715
2963
  await bootstrapLock.release();
2716
2964
  }
@@ -2730,17 +2978,18 @@ var streamDaemonLogs = (options) => {
2730
2978
  for (const line of lines.slice(seenLines)) {
2731
2979
  try {
2732
2980
  const entry = JSON.parse(line);
2981
+ if (!shouldDisplayConnectorLogEntry(entry.createdAt, options.startedAt)) continue;
2733
2982
  const connectorId = entry.metadata?.connectorId;
2734
2983
  if (options.dashboardActive && typeof connectorId === "string") {
2735
- const output2 = entry.metadata?.source === "daemon-console" ? entry.message : `[buda-connector] ${entry.message}`;
2736
- appendConnectorDashboardLog(connectorId, entry.level, output2);
2984
+ const output3 = entry.metadata?.source === "daemon-console" ? entry.message : `[buda-connector] ${entry.message}`;
2985
+ appendConnectorDashboardLog(connectorId, entry.level, output3);
2737
2986
  continue;
2738
2987
  }
2739
2988
  if (connectorId !== options.connectorId) continue;
2740
- const output = entry.metadata?.source === "daemon-console" ? entry.message : `[buda-connector] ${entry.message}`;
2741
- if (entry.level === "error") console.error(output);
2742
- else if (entry.level === "warn") console.warn(output);
2743
- else console.log(output);
2989
+ const output2 = entry.metadata?.source === "daemon-console" ? entry.message : `[buda-connector] ${entry.message}`;
2990
+ if (entry.level === "error") console.error(output2);
2991
+ else if (entry.level === "warn") console.warn(output2);
2992
+ else console.log(output2);
2744
2993
  } catch {
2745
2994
  if (options.plain) console.log(line);
2746
2995
  }
@@ -2805,9 +3054,7 @@ async function stopLocalConnectorDaemon(connectorId, serverUrl, configRoot = get
2805
3054
  (candidate) => candidate.connectorId === connectorId && normalizeServerOrigin(candidate.serverUrl) === serverOrigin
2806
3055
  );
2807
3056
  if (!config) throw new Error(`Local Connector ${connectorId} was not found`);
2808
- const status = await readLiveStatus(config, configRoot);
2809
- if (!status) return;
2810
- await requestDaemonControl(config, "stop", configRoot);
3057
+ await stopDaemonForIdentity(config, configRoot);
2811
3058
  }
2812
3059
  async function stopAllLocalConnectorDaemons(configRoot = getConfigRoot()) {
2813
3060
  const configs = await listDeviceConfigs(configRoot);
@@ -2818,10 +3065,7 @@ async function stopAllLocalConnectorDaemons(configRoot = getConfigRoot()) {
2818
3065
  ${config.connectorId}`, config);
2819
3066
  }
2820
3067
  const results = await Promise.allSettled(
2821
- [...identities.values()].map(async (config) => {
2822
- const status = await readLiveStatus(config, configRoot);
2823
- if (status) await requestDaemonControl(config, "stop", configRoot);
2824
- })
3068
+ [...identities.values()].map((config) => stopDaemonForIdentity(config, configRoot))
2825
3069
  );
2826
3070
  const failures = results.filter((result) => result.status === "rejected");
2827
3071
  if (failures.length) {
@@ -2840,6 +3084,7 @@ var waitForConnectorViewerExit = async (dashboardClosed) => {
2840
3084
  process.off("SIGTERM", finishSignal);
2841
3085
  };
2842
3086
  async function runConnectorDashboard(launchConfig, options = {}) {
3087
+ const startedAt = Date.now();
2843
3088
  let finishDashboard = () => void 0;
2844
3089
  const dashboardClosed = new Promise((resolve) => {
2845
3090
  finishDashboard = resolve;
@@ -2860,7 +3105,8 @@ async function runConnectorDashboard(launchConfig, options = {}) {
2860
3105
  connectorId: config.connectorId,
2861
3106
  dashboardActive: false,
2862
3107
  plain: Boolean(options.plain),
2863
- serverUrl: config.serverUrl
3108
+ serverUrl: config.serverUrl,
3109
+ startedAt
2864
3110
  });
2865
3111
  if (!process.stdin.isTTY) {
2866
3112
  stopLogs2();
@@ -2887,7 +3133,8 @@ async function runConnectorDashboard(launchConfig, options = {}) {
2887
3133
  const stopLogs = streamDaemonLogs({
2888
3134
  dashboardActive: true,
2889
3135
  plain: false,
2890
- serverUrl: getServerUrl(launchConfig)
3136
+ serverUrl: getServerUrl(launchConfig),
3137
+ startedAt
2891
3138
  });
2892
3139
  let refreshing = false;
2893
3140
  const refresh = async () => {
@@ -2983,9 +3230,6 @@ var getDaemonModeFlag = () => {
2983
3230
  return process.argv[index + 1] === "transient" ? "transient" : "persistent";
2984
3231
  };
2985
3232
 
2986
- // src/onboard.ts
2987
- import { cancel, intro, isCancel, log, note, outro, spinner, text } from "@clack/prompts";
2988
-
2989
3233
  // src/bunny-daemon.ts
2990
3234
  import { mkdir as mkdir7 } from "fs/promises";
2991
3235
  import { createDaemon } from "@bunny-agent/daemon";
@@ -3224,17 +3468,17 @@ var installConnectorTerminalEndpoint = (server, options = {}) => {
3224
3468
  sessions2.clear();
3225
3469
  });
3226
3470
  };
3227
- var registerLocalTerminal = (input) => {
3228
- if (!isSafeId(input.workspaceKey) || !isSafeId(input.tabId)) {
3471
+ var registerLocalTerminal = (input2) => {
3472
+ if (!isSafeId(input2.workspaceKey) || !isSafeId(input2.tabId)) {
3229
3473
  throw new Error("Invalid local terminal workspace session.");
3230
3474
  }
3231
3475
  const token = randomBytes3(32).toString("base64url");
3232
3476
  localGrants.set(token, {
3233
- sessionKey: `${input.workspaceKey}:${input.tabId}`,
3234
- workspaceKey: input.workspaceKey,
3235
- cwd: input.cwd,
3236
- cols: input.cols,
3237
- rows: input.rows,
3477
+ sessionKey: `${input2.workspaceKey}:${input2.tabId}`,
3478
+ workspaceKey: input2.workspaceKey,
3479
+ cwd: input2.cwd,
3480
+ cols: input2.cols,
3481
+ rows: input2.rows,
3238
3482
  expiresAt: Date.now() + LOCAL_TERMINAL_GRANT_TTL_MS
3239
3483
  });
3240
3484
  const cleanupTimer = setTimeout(() => localGrants.delete(token), LOCAL_TERMINAL_GRANT_TTL_MS);
@@ -3258,16 +3502,16 @@ var connectSocket = async (socket) => new Promise((resolve, reject) => {
3258
3502
  socket.once("open", onOpen);
3259
3503
  socket.once("error", onError);
3260
3504
  });
3261
- var proxyConnectorTerminalTunnel = async (input) => {
3505
+ var proxyConnectorTerminalTunnel = async (input2) => {
3262
3506
  const cloud = new WebSocket(
3263
- getConnectorTerminalUrl(input.serverUrl, input.connectorId, input.sessionId),
3507
+ getConnectorTerminalUrl(input2.serverUrl, input2.connectorId, input2.sessionId),
3264
3508
  {
3265
- headers: { "X-Buda-Terminal-Token": input.cloudToken },
3509
+ headers: { "X-Buda-Terminal-Token": input2.cloudToken },
3266
3510
  handshakeTimeout: TERMINAL_HANDSHAKE_TIMEOUT_MS
3267
3511
  }
3268
3512
  );
3269
- const local = new WebSocket(toLocalTerminalUrl(input.localBaseUrl), {
3270
- headers: { "X-Buda-Local-Terminal-Token": input.localToken },
3513
+ const local = new WebSocket(toLocalTerminalUrl(input2.localBaseUrl), {
3514
+ headers: { "X-Buda-Local-Terminal-Token": input2.localToken },
3271
3515
  handshakeTimeout: TERMINAL_HANDSHAKE_TIMEOUT_MS
3272
3516
  });
3273
3517
  let bridgeAccepted = false;
@@ -3278,7 +3522,7 @@ var proxyConnectorTerminalTunnel = async (input) => {
3278
3522
  const endActivity = () => {
3279
3523
  if (!activityStarted || activityEnded) return;
3280
3524
  activityEnded = true;
3281
- input.onActivityEnd?.();
3525
+ input2.onActivityEnd?.();
3282
3526
  };
3283
3527
  let pendingBytes = 0;
3284
3528
  const pendingCloudFrames = [];
@@ -3334,7 +3578,7 @@ var proxyConnectorTerminalTunnel = async (input) => {
3334
3578
  if (!bridgeAccepted) {
3335
3579
  void writeLog(
3336
3580
  "warn",
3337
- `terminal cloud tunnel closed before readiness session=${input.sessionId} code=${code} reason=${normalizedReason}`
3581
+ `terminal cloud tunnel closed before readiness session=${input2.sessionId} code=${code} reason=${normalizedReason}`
3338
3582
  );
3339
3583
  }
3340
3584
  if (local.readyState === WebSocket.OPEN) {
@@ -3372,12 +3616,12 @@ var proxyConnectorTerminalTunnel = async (input) => {
3372
3616
  if (cloud.readyState !== WebSocket.OPEN) return;
3373
3617
  if (local.readyState === WebSocket.OPEN) {
3374
3618
  activityStarted = true;
3375
- input.onActivityStart?.();
3619
+ input2.onActivityStart?.();
3376
3620
  }
3377
3621
  cloud.send(JSON.stringify({ type: "bridge-ready" }));
3378
3622
  await writeLog(
3379
3623
  "info",
3380
- `terminal proxy connected session=${input.sessionId} workspace=${input.workspaceKey}`
3624
+ `terminal proxy connected session=${input2.sessionId} workspace=${input2.workspaceKey}`
3381
3625
  );
3382
3626
  };
3383
3627
 
@@ -3783,6 +4027,7 @@ var writeJournal = async (journalPath, journal) => {
3783
4027
  }
3784
4028
  };
3785
4029
  var syncDirectory2 = async (directory) => {
4030
+ if (process.platform === "win32") return;
3786
4031
  const handle = await open2(directory, constants2.O_RDONLY);
3787
4032
  try {
3788
4033
  await handle.sync();
@@ -3960,6 +4205,86 @@ var verifyFailure = (detail) => new LegacyWorkspaceMigrationError(
3960
4205
  var corrupt = (message) => new LegacyWorkspaceMigrationError("MIGRATION_CORRUPT", message);
3961
4206
  var errorMessage = (error) => error instanceof Error ? error.message : String(error);
3962
4207
 
4208
+ // src/prompts.ts
4209
+ import { stdin as input, stdout as output } from "process";
4210
+ import { createInterface } from "readline/promises";
4211
+ var CANCELLED = /* @__PURE__ */ Symbol("prompt-cancelled");
4212
+ function printLine(prefix, message) {
4213
+ output.write(`${prefix}${message}
4214
+ `);
4215
+ }
4216
+ function intro(message) {
4217
+ printLine("\n", message);
4218
+ }
4219
+ function outro(message) {
4220
+ printLine("", message);
4221
+ }
4222
+ function cancel(message) {
4223
+ printLine("", message);
4224
+ }
4225
+ function note(message, title) {
4226
+ if (title) {
4227
+ printLine("", `${title}
4228
+ ${message}`);
4229
+ return;
4230
+ }
4231
+ printLine("", message);
4232
+ }
4233
+ var log = {
4234
+ info(message) {
4235
+ printLine("", message);
4236
+ },
4237
+ error(message) {
4238
+ printLine("", message);
4239
+ },
4240
+ step(message) {
4241
+ printLine("", message);
4242
+ }
4243
+ };
4244
+ function isCancel(value) {
4245
+ return value === CANCELLED;
4246
+ }
4247
+ function spinner() {
4248
+ let activeMessage = null;
4249
+ return {
4250
+ start(message) {
4251
+ activeMessage = message;
4252
+ printLine("", message);
4253
+ },
4254
+ stop(message) {
4255
+ if (message) {
4256
+ printLine("", message);
4257
+ } else if (activeMessage) {
4258
+ printLine("", activeMessage);
4259
+ }
4260
+ activeMessage = null;
4261
+ }
4262
+ };
4263
+ }
4264
+ async function text(options) {
4265
+ if (!input.isTTY || !output.isTTY) {
4266
+ const fallback = options.defaultValue ?? "";
4267
+ const validationError = options.validate?.(fallback);
4268
+ return validationError ? CANCELLED : fallback;
4269
+ }
4270
+ const rl = createInterface({ input, output });
4271
+ const hint = options.defaultValue ?? options.placeholder;
4272
+ const suffix = hint ? ` (${hint})` : "";
4273
+ try {
4274
+ while (true) {
4275
+ const answer = await rl.question(`${options.message}${suffix}: `);
4276
+ const value = answer.trim() || options.defaultValue || "";
4277
+ const validationError = options.validate?.(value);
4278
+ if (!validationError) return value;
4279
+ printLine("", validationError);
4280
+ }
4281
+ } catch {
4282
+ return CANCELLED;
4283
+ } finally {
4284
+ rl.close();
4285
+ }
4286
+ }
4287
+
3963
4288
  // src/relay.ts
3964
4289
  import { realpathSync } from "fs";
3965
4290
  import path12 from "path";
@@ -4050,7 +4375,10 @@ function verifyConnectorExecutionIdentity(options) {
4050
4375
  const signature = decodeSignature(identity.signature);
4051
4376
  let verified = false;
4052
4377
  try {
4053
- const publicKey = createPublicKey({ key: protocol.publicKeyJwk, format: "jwk" });
4378
+ const publicKey = createPublicKey({
4379
+ key: protocol.publicKeyJwk,
4380
+ format: "jwk"
4381
+ });
4054
4382
  if (publicKey.asymmetricKeyType !== "ed25519") {
4055
4383
  throw new ConnectorRelayProtocolError("PROTOCOL_UNSUPPORTED");
4056
4384
  }
@@ -4157,8 +4485,8 @@ var MIME_TYPES = {
4157
4485
  };
4158
4486
  var HtmlPreviewServerRegistry = class {
4159
4487
  servers = /* @__PURE__ */ new Map();
4160
- async open(input) {
4161
- const htmlFile = await resolveWorkspaceFile(input);
4488
+ async open(input2) {
4489
+ const htmlFile = await resolveWorkspaceFile(input2);
4162
4490
  const folder = path10.dirname(htmlFile);
4163
4491
  let pending = this.servers.get(folder);
4164
4492
  if (!pending) {
@@ -4216,10 +4544,10 @@ var handleHtmlPreviewRequest = async (request, mounts, registry) => {
4216
4544
  );
4217
4545
  }
4218
4546
  };
4219
- var resolveWorkspaceFile = async (input) => {
4220
- const virtualPath = input.filePath.replaceAll("\\", "/");
4547
+ var resolveWorkspaceFile = async (input2) => {
4548
+ const virtualPath = input2.filePath.replaceAll("\\", "/");
4221
4549
  if (virtualPath.includes("\0")) throw new Error("HTML preview path contains invalid data.");
4222
- const mountPath = path10.resolve(input.mounts[input.rootVolume]);
4550
+ const mountPath = path10.resolve(input2.mounts[input2.rootVolume]);
4223
4551
  const root = await realpath3(mountPath);
4224
4552
  let relativePath = virtualPath;
4225
4553
  let explicitVolume;
@@ -4231,7 +4559,7 @@ var resolveWorkspaceFile = async (input) => {
4231
4559
  break;
4232
4560
  }
4233
4561
  }
4234
- if (explicitVolume && explicitVolume !== input.rootVolume) {
4562
+ if (explicitVolume && explicitVolume !== input2.rootVolume) {
4235
4563
  throw new Error("HTML preview path does not match the requested root volume.");
4236
4564
  }
4237
4565
  if (!explicitVolume && (path10.isAbsolute(virtualPath) || /^[A-Za-z]:\//.test(virtualPath))) {
@@ -4356,6 +4684,11 @@ import {
4356
4684
  writeFile as writeFile4
4357
4685
  } from "fs/promises";
4358
4686
  import path11 from "path";
4687
+
4688
+ // src/directory-link.ts
4689
+ var directoryLinkType = (platform4 = process.platform) => platform4 === "win32" ? "junction" : "dir";
4690
+
4691
+ // src/workspace-volume-lifecycle.ts
4359
4692
  var MOUNT_DIRECTORY = "mount";
4360
4693
  var ARCHIVE_MARKER = "archived.json";
4361
4694
  var AGENT_CONTEXT_TEMPLATE_FILES = [
@@ -4507,7 +4840,7 @@ var ensureVolumeLink = async (target, linkPath) => {
4507
4840
  } catch (error) {
4508
4841
  if (!isMissingFile(error)) throw error;
4509
4842
  try {
4510
- await symlink2(target, linkPath, "dir");
4843
+ await symlink2(target, linkPath, directoryLinkType());
4511
4844
  } catch (createError) {
4512
4845
  if (!isAlreadyExists3(createError)) throw createError;
4513
4846
  const info = await lstat2(linkPath);
@@ -4522,6 +4855,7 @@ var isAlreadyExists3 = (error) => error instanceof Error && "code" in error && e
4522
4855
  // src/relay.ts
4523
4856
  var RELAY_PING_INTERVAL_MS = 2e4;
4524
4857
  var RELAY_PONG_TIMEOUT_MS = 1e4;
4858
+ var RELAY_ACTIVE_REQUEST_PONG_TIMEOUT_LIMIT = 2;
4525
4859
  var RELAY_HANDSHAKE_TIMEOUT_MS = 1e4;
4526
4860
  var RELAY_RECONNECT_MAX_MS = 3e4;
4527
4861
  var RELAY_RECONNECT_JITTER = 0.2;
@@ -4627,6 +4961,7 @@ function startConnectorRelay(config, bunnyDaemon, relayProtocol, workspaceRegist
4627
4961
  socket = ws;
4628
4962
  const inflight = /* @__PURE__ */ new Map();
4629
4963
  currentInflight = inflight;
4964
+ let consecutivePongTimeouts = 0;
4630
4965
  let detached = false;
4631
4966
  const detach = () => {
4632
4967
  if (detached) return;
@@ -4639,6 +4974,7 @@ function startConnectorRelay(config, bunnyDaemon, relayProtocol, workspaceRegist
4639
4974
  void writeLog("info", `relay connected ${relayUrl}`);
4640
4975
  hadOpenConnection = true;
4641
4976
  reconnectDelayMs = RELAY_RECONNECT_INTERVAL_MS;
4977
+ consecutivePongTimeouts = 0;
4642
4978
  pingTimer = setInterval(() => {
4643
4979
  if (ws.readyState !== WebSocket2.OPEN) return;
4644
4980
  try {
@@ -4650,18 +4986,33 @@ function startConnectorRelay(config, bunnyDaemon, relayProtocol, workspaceRegist
4650
4986
  }
4651
4987
  if (pongTimer) clearTimeout(pongTimer);
4652
4988
  pongTimer = setTimeout(() => {
4989
+ pongTimer = null;
4990
+ consecutivePongTimeouts += 1;
4991
+ if (inflight.size > 0 && consecutivePongTimeouts < RELAY_ACTIVE_REQUEST_PONG_TIMEOUT_LIMIT) {
4992
+ void writeLog(
4993
+ "warn",
4994
+ `relay pong timeout deferred while ${inflight.size} request(s) are active`
4995
+ );
4996
+ return;
4997
+ }
4653
4998
  void writeLog("warn", "relay pong timeout, terminating");
4654
4999
  ws.terminate();
4655
5000
  }, RELAY_PONG_TIMEOUT_MS);
4656
5001
  }, RELAY_PING_INTERVAL_MS);
4657
5002
  });
4658
5003
  ws.on("pong", () => {
5004
+ consecutivePongTimeouts = 0;
4659
5005
  if (pongTimer) {
4660
5006
  clearTimeout(pongTimer);
4661
5007
  pongTimer = null;
4662
5008
  }
4663
5009
  });
4664
5010
  ws.on("message", (raw) => {
5011
+ consecutivePongTimeouts = 0;
5012
+ if (pongTimer) {
5013
+ clearTimeout(pongTimer);
5014
+ pongTimer = null;
5015
+ }
4665
5016
  void handleRelayMessage(
4666
5017
  ws,
4667
5018
  bunnyDaemon,
@@ -4837,7 +5188,7 @@ async function handleRelayMessage(socket, bunnyDaemon, raw, inflight, expectedCo
4837
5188
  });
4838
5189
  executionIdentity = message.executionIdentity;
4839
5190
  } catch (error) {
4840
- sendRelayError(socket, message.requestId, error);
5191
+ await sendRelayError(socket, message.requestId, error).catch(() => void 0);
4841
5192
  return;
4842
5193
  }
4843
5194
  }
@@ -4923,7 +5274,7 @@ async function handleRelayMessage(socket, bunnyDaemon, raw, inflight, expectedCo
4923
5274
  durationMs: Date.now() - codingRunStartedAt
4924
5275
  });
4925
5276
  }
4926
- sendRelayMessage(socket, {
5277
+ await sendRelayMessage(socket, {
4927
5278
  type: "start",
4928
5279
  requestId: message.requestId,
4929
5280
  status: response.status,
@@ -4943,7 +5294,7 @@ async function handleRelayMessage(socket, bunnyDaemon, raw, inflight, expectedCo
4943
5294
  while (true) {
4944
5295
  const { value, done } = await reader.read();
4945
5296
  if (done) break;
4946
- sendRelayMessage(socket, {
5297
+ await sendRelayMessage(socket, {
4947
5298
  type: "chunk",
4948
5299
  requestId: message.requestId,
4949
5300
  data: binaryResponse ? Buffer.from(value).toString("base64") : decoder?.decode(value, { stream: true }) ?? "",
@@ -4952,7 +5303,7 @@ async function handleRelayMessage(socket, bunnyDaemon, raw, inflight, expectedCo
4952
5303
  }
4953
5304
  const tail = decoder?.decode() ?? "";
4954
5305
  if (tail) {
4955
- sendRelayMessage(socket, {
5306
+ await sendRelayMessage(socket, {
4956
5307
  type: "chunk",
4957
5308
  requestId: message.requestId,
4958
5309
  data: tail
@@ -4963,7 +5314,7 @@ async function handleRelayMessage(socket, bunnyDaemon, raw, inflight, expectedCo
4963
5314
  reader.releaseLock();
4964
5315
  }
4965
5316
  }
4966
- sendRelayMessage(socket, {
5317
+ await sendRelayMessage(socket, {
4967
5318
  type: "end",
4968
5319
  requestId: message.requestId
4969
5320
  });
@@ -4975,14 +5326,16 @@ async function handleRelayMessage(socket, bunnyDaemon, raw, inflight, expectedCo
4975
5326
  });
4976
5327
  }
4977
5328
  } catch (error) {
5329
+ controller.abort();
4978
5330
  if (codingRunTrace && codingRunStartedAt !== void 0) {
4979
5331
  await writeLog("error", "relay request coding run error", {
4980
5332
  ...codingRunTrace,
4981
5333
  status: codingRunStatus,
4982
- durationMs: Date.now() - codingRunStartedAt
5334
+ durationMs: Date.now() - codingRunStartedAt,
5335
+ error: formatConnectorRelayError(error)
4983
5336
  });
4984
5337
  }
4985
- sendRelayError(socket, message.requestId, error);
5338
+ await sendRelayError(socket, message.requestId, error).catch(() => void 0);
4986
5339
  } finally {
4987
5340
  inflight.delete(message.requestId);
4988
5341
  }
@@ -5036,17 +5389,20 @@ var normalizeWorkspaceRequest = (request, mounts) => {
5036
5389
  if (!mount || relative === void 0) {
5037
5390
  throw new ConnectorRelayProtocolError("WORKSPACE_MISMATCH");
5038
5391
  }
5039
- const env = body.env && typeof body.env === "object" && !Array.isArray(body.env) ? body.env : {};
5392
+ const systemEnv = body.systemEnv && typeof body.systemEnv === "object" && !Array.isArray(body.systemEnv) ? body.systemEnv : {};
5393
+ const workspaceEnv = {
5394
+ BUDA_AGENT_ROOT: mounts.agent,
5395
+ BUDA_SPACE_ROOT: mounts.space,
5396
+ BUDA_SYSTEM_ROOT: mounts.system
5397
+ };
5040
5398
  return {
5041
5399
  ...request,
5042
5400
  body: {
5043
5401
  ...body,
5044
5402
  cwd: path12.join(mount, relative),
5045
- env: {
5046
- ...env,
5047
- BUDA_AGENT_ROOT: mounts.agent,
5048
- BUDA_SPACE_ROOT: mounts.space,
5049
- BUDA_SYSTEM_ROOT: mounts.system
5403
+ systemEnv: {
5404
+ ...systemEnv,
5405
+ ...workspaceEnv
5050
5406
  }
5051
5407
  }
5052
5408
  };
@@ -5078,17 +5434,24 @@ var normalizeConnectorFilesystemRequest = (request, mounts) => {
5078
5434
  }
5079
5435
  return request;
5080
5436
  };
5081
- function sendRelayError(socket, requestId, error) {
5082
- sendRelayMessage(socket, {
5437
+ async function sendRelayError(socket, requestId, error) {
5438
+ await sendRelayMessage(socket, {
5083
5439
  type: "error",
5084
5440
  requestId,
5085
5441
  error: error instanceof Error ? error.message : String(error),
5086
5442
  ...error instanceof ConnectorRelayProtocolError ? { code: error.code } : {}
5087
5443
  });
5088
5444
  }
5089
- function sendRelayMessage(socket, message) {
5090
- if (socket.readyState !== WebSocket2.OPEN) return;
5091
- socket.send(JSON.stringify(message));
5445
+ async function sendRelayMessage(socket, message) {
5446
+ if (socket.readyState !== WebSocket2.OPEN) {
5447
+ throw new Error(`Connector relay socket closed before ${message.type} could be sent.`);
5448
+ }
5449
+ await new Promise((resolve, reject) => {
5450
+ socket.send(JSON.stringify(message), (error) => {
5451
+ if (error) reject(error);
5452
+ else resolve();
5453
+ });
5454
+ });
5092
5455
  }
5093
5456
  function headersToRecord(headers) {
5094
5457
  const result = {};
@@ -5147,7 +5510,7 @@ async function ensureSymlink(target, linkPath) {
5147
5510
  } catch {
5148
5511
  }
5149
5512
  await mkdir10(path13.dirname(linkPath), { recursive: true });
5150
- await symlink3(target, linkPath, "dir");
5513
+ await symlink3(target, linkPath, directoryLinkType());
5151
5514
  }
5152
5515
 
5153
5516
  // src/workspace-registry.ts
@@ -5604,7 +5967,30 @@ import { mkdir as mkdir12 } from "fs/promises";
5604
5967
  import path15 from "path";
5605
5968
  var RECOVERED_DAEMON_STOP_TIMEOUT_MS = 1e4;
5606
5969
  var RECOVERED_DAEMON_STOP_POLL_MS = 100;
5970
+ var SHUTDOWN_FORCE_EXIT_MS = 1e4;
5971
+ var SHUTDOWN_STEP_TIMEOUT_MS = 2e3;
5607
5972
  var wait3 = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
5973
+ var runShutdownStep = async (label, operation) => {
5974
+ let timeout;
5975
+ try {
5976
+ await Promise.race([
5977
+ Promise.resolve().then(operation),
5978
+ new Promise((_, reject) => {
5979
+ timeout = setTimeout(
5980
+ () => reject(new Error(`${label} timed out after ${SHUTDOWN_STEP_TIMEOUT_MS}ms`)),
5981
+ SHUTDOWN_STEP_TIMEOUT_MS
5982
+ );
5983
+ })
5984
+ ]);
5985
+ } catch (error) {
5986
+ await writeLog(
5987
+ "warn",
5988
+ `${label} failed during shutdown: ${error instanceof Error ? error.message : String(error)}`
5989
+ ).catch(() => void 0);
5990
+ } finally {
5991
+ if (timeout) clearTimeout(timeout);
5992
+ }
5993
+ };
5608
5994
  async function stopRecoveredIdleDaemon(config, configRoot) {
5609
5995
  const stopStatus = await requestDaemonControl(config, "stop-if-idle", configRoot).catch(
5610
5996
  () => null
@@ -5724,18 +6110,30 @@ async function runConnectorDaemon(launchConfig, options = {}) {
5724
6110
  const shutdown = async (shutdownOptions = {}) => {
5725
6111
  if (shuttingDown) return;
5726
6112
  shuttingDown = true;
6113
+ const forceExit = options.exitOnShutdown === false ? void 0 : setTimeout(() => process.exit(0), SHUTDOWN_FORCE_EXIT_MS);
5727
6114
  if (interval) clearInterval(interval);
5728
6115
  relay.stop();
5729
6116
  try {
5730
- await closeAllBrowserSessions();
5731
- if (shutdownOptions.unregister !== false) {
5732
- await unregisterConnector(config, launchConfig);
5733
- }
5734
- await stopEmbeddedBunnyDaemon(activeBunnyDaemon);
6117
+ await Promise.all([
6118
+ runShutdownStep("browser session cleanup", closeAllBrowserSessions),
6119
+ ...shutdownOptions.unregister === false ? [] : [
6120
+ runShutdownStep(
6121
+ "connector unregister",
6122
+ () => unregisterConnector(config, launchConfig)
6123
+ )
6124
+ ],
6125
+ runShutdownStep(
6126
+ "embedded Bunny daemon shutdown",
6127
+ () => stopEmbeddedBunnyDaemon(activeBunnyDaemon)
6128
+ )
6129
+ ]);
5735
6130
  } finally {
5736
- await control?.close();
5737
- await daemonLock.release();
5738
- await restoreConsole?.();
6131
+ await Promise.all([
6132
+ runShutdownStep("control channel shutdown", async () => control?.close()),
6133
+ runShutdownStep("daemon lock release", () => daemonLock.release()),
6134
+ runShutdownStep("console capture restore", async () => restoreConsole?.())
6135
+ ]);
6136
+ if (forceExit) clearTimeout(forceExit);
5739
6137
  if (options.exitOnShutdown !== false) process.exit(0);
5740
6138
  }
5741
6139
  };
@@ -5750,8 +6148,11 @@ async function runConnectorDaemon(launchConfig, options = {}) {
5750
6148
  });
5751
6149
  }).catch((error) => {
5752
6150
  heartbeatHealthy = false;
5753
- if (error instanceof ConnectorDeletedError) {
5754
- void writeLog("warn", "connector deleted remotely; stopping daemon");
6151
+ if (error instanceof ConnectorDeletedError || error instanceof ConnectorUnauthorizedError) {
6152
+ void writeLog(
6153
+ "warn",
6154
+ error instanceof ConnectorDeletedError ? "connector deleted remotely; stopping daemon" : "connector authentication rejected remotely; stopping daemon"
6155
+ );
5755
6156
  void shutdown({ unregister: false });
5756
6157
  return;
5757
6158
  }
@@ -5775,6 +6176,10 @@ async function runConnectorDaemon(launchConfig, options = {}) {
5775
6176
  process.once("SIGINT", () => void shutdown());
5776
6177
  process.once("SIGTERM", () => void shutdown());
5777
6178
  } catch (error) {
6179
+ await writeLog(
6180
+ "error",
6181
+ `daemon startup failed: ${error instanceof Error ? error.stack || error.message : String(error)}`
6182
+ ).catch(() => void 0);
5778
6183
  if (bunnyDaemon) await stopEmbeddedBunnyDaemon(bunnyDaemon);
5779
6184
  await daemonLock.release();
5780
6185
  await restoreConsole?.();