@buda-ai/connector 0.1.30 → 0.1.32

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.
@@ -47,7 +47,7 @@ Capture what matters. Decisions, context, things to remember. Skip the secrets u
47
47
 
48
48
  - **Memory is limited** — if you want to remember something, you MUST write it to a file using your tools (like `bash` or `write`).
49
49
  - "Mental notes" do not survive session restarts or context limits. Only files do.
50
- - When the user gives you a **new rule, a new preference, changes your identity/role, or tells you to "remember this"** → You **MUST IMMEDIATELY** use your tools to update `SOUL.md`, `AGENTS.md`, `MEMORY.md` or `memory/YYYY-MM-DD.md`.
50
+ - When the user gives you a **new rule, a new preference, changes your identity/role, or tells you to "remember this"** → You **MUST IMMEDIATELY** use your tools to update `SOUL.md`, `AGENTS.md`, `USER.md` or `memory/YYYY-MM-DD.md`.
51
51
  - DO NOT just reply "Okay, I will remember this." You must actually execute a file write operation to make it permanent.
52
52
  - When you learn a lesson → update `AGENTS.md`, `TOOLS.md`, or the relevant skill.
53
53
  - When you make a mistake → document it so future-you doesn't repeat it.
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
- const recovered = await unlink(lockPath).then(() => true).catch(() => false);
29
- if (recovered) continue;
28
+ await unlink(lockPath).catch(() => void 0);
29
+ continue;
30
30
  }
31
31
  if (Date.now() >= deadline) {
32
32
  throw new Error(`Timed out waiting for exclusive access to ${targetPath}`);
@@ -35,23 +35,17 @@ async function acquireExclusiveFileLock(targetPath, options = {}) {
35
35
  continue;
36
36
  }
37
37
  const ownerToken = randomUUID();
38
- try {
39
- await handle.writeFile(
40
- `${JSON.stringify({ pid: process.pid, ownerToken, createdAt: Date.now() })}
38
+ await handle.writeFile(
39
+ `${JSON.stringify({ pid: process.pid, ownerToken, createdAt: Date.now() })}
41
40
  `
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();
41
+ );
42
+ await handle.sync();
50
43
  let released = false;
51
44
  return {
52
45
  release: async () => {
53
46
  if (released) return;
54
47
  released = true;
48
+ await handle.close();
55
49
  const currentOwner = await readFile(lockPath, "utf8").then((raw) => JSON.parse(raw).ownerToken).catch(() => void 0);
56
50
  if (currentOwner === ownerToken) await unlink(lockPath).catch(() => void 0);
57
51
  }
@@ -523,7 +517,7 @@ import path2 from "path";
523
517
  // package.json
524
518
  var package_default = {
525
519
  name: "@buda-ai/connector",
526
- version: "0.1.30",
520
+ version: "0.1.32",
527
521
  private: false,
528
522
  type: "module",
529
523
  publishConfig: {
@@ -541,7 +535,6 @@ var package_default = {
541
535
  dev: "tsx src/cli.ts dev",
542
536
  daemon: "tsx src/cli.ts daemon",
543
537
  new: "tsx src/cli.ts new",
544
- prepack: "tsup --silent",
545
538
  start: "node dist/cli.js daemon",
546
539
  build: "tsup",
547
540
  test: "vitest run",
@@ -550,7 +543,7 @@ var package_default = {
550
543
  "lint:err": "biome format . --write && biome check . --write --diagnostic-level=error"
551
544
  },
552
545
  dependencies: {
553
- "@bunny-agent/daemon": "0.9.57",
546
+ "@bunny-agent/daemon": "0.9.51",
554
547
  "@clack/prompts": "^1.0.0",
555
548
  "playwright-core": "^1.52.0",
556
549
  ws: "^8.21.0"
@@ -2215,13 +2208,6 @@ var ConnectorDeletedError = class extends Error {
2215
2208
  this.name = "ConnectorDeletedError";
2216
2209
  }
2217
2210
  };
2218
- var ConnectorUnauthorizedError = class extends Error {
2219
- constructor(message) {
2220
- super(message);
2221
- this.name = "ConnectorUnauthorizedError";
2222
- }
2223
- };
2224
- var UNREGISTER_TIMEOUT_MS = 3e3;
2225
2211
  async function resolveConnectorScope(deviceId, launchConfig) {
2226
2212
  const serverUrl = normalizeServerOrigin(getServerUrl(launchConfig));
2227
2213
  const headers = { "Content-Type": "application/json" };
@@ -2308,15 +2294,14 @@ function buildAuthHeaders(config, launchConfig) {
2308
2294
  if (config.deviceToken) headers["X-Buda-Connector-Device-Token"] = config.deviceToken;
2309
2295
  return headers;
2310
2296
  }
2311
- async function postJson(serverUrl, pathname, body, config, launchConfig, options = {}) {
2297
+ async function postJson(serverUrl, pathname, body, config, launchConfig) {
2312
2298
  const response = await fetch(`${serverUrl}${pathname}`, {
2313
2299
  method: "POST",
2314
2300
  headers: {
2315
2301
  "Content-Type": "application/json",
2316
2302
  ...buildAuthHeaders(config, launchConfig)
2317
2303
  },
2318
- body: JSON.stringify(body),
2319
- ...options.timeoutMs ? { signal: AbortSignal.timeout(options.timeoutMs) } : {}
2304
+ body: JSON.stringify(body)
2320
2305
  });
2321
2306
  if (!response.ok) {
2322
2307
  const text2 = await response.text().catch(() => "");
@@ -2336,9 +2321,6 @@ async function postJson(serverUrl, pathname, body, config, launchConfig, options
2336
2321
  if (errorCode === "connector_deleted") {
2337
2322
  throw new ConnectorDeletedError(errorMessage3 || "Connector no longer exists");
2338
2323
  }
2339
- if (response.status === 401) {
2340
- throw new ConnectorUnauthorizedError(errorMessage3 || "Connector authentication failed");
2341
- }
2342
2324
  throw new Error(`${pathname} failed: ${response.status} ${errorMessage3}`);
2343
2325
  }
2344
2326
  return await response.json();
@@ -2414,8 +2396,7 @@ async function unregisterConnector(config, launchConfig) {
2414
2396
  connectorId: config.connectorId
2415
2397
  },
2416
2398
  config,
2417
- launchConfig,
2418
- { timeoutMs: UNREGISTER_TIMEOUT_MS }
2399
+ launchConfig
2419
2400
  ).catch((error) => {
2420
2401
  void writeLog("warn", `unregister failed: ${error instanceof Error ? error.message : error}`);
2421
2402
  });
@@ -2424,58 +2405,73 @@ async function unregisterConnector(config, launchConfig) {
2424
2405
  // src/runtime-install.ts
2425
2406
  import { execFile } from "child_process";
2426
2407
  import { randomUUID as randomUUID4 } from "crypto";
2427
- import { access, cp, mkdir as mkdir5, readFile as readFile5, rename as rename4, rm, writeFile as writeFile3 } from "fs/promises";
2408
+ import {
2409
+ access,
2410
+ cp,
2411
+ mkdir as mkdir5,
2412
+ readdir as readdir4,
2413
+ readFile as readFile5,
2414
+ realpath,
2415
+ rename as rename4,
2416
+ rm,
2417
+ writeFile as writeFile3
2418
+ } from "fs/promises";
2428
2419
  import { homedir as homedir3 } from "os";
2429
2420
  import path6 from "path";
2430
2421
  import { promisify } from "util";
2431
2422
  var execFileAsync = promisify(execFile);
2432
2423
  var RUNTIME_INSTALL_TIMEOUT_MS = 5 * 6e4;
2433
- var WINDOWS_RENAME_RETRY_DELAYS_MS = [50, 100, 200, 400, 500, 500, 500, 500, 500];
2434
- var resolveBundledNodePlacement = (sourceRoot, sourceNode, pathApi = path6) => {
2435
- const relativeNode = pathApi.relative(sourceRoot, sourceNode);
2436
- return {
2437
- copyNode: relativeNode.startsWith("..") || pathApi.isAbsolute(relativeNode),
2438
- relativeNode
2439
- };
2440
- };
2441
- var isWindowsRenameContention = (error) => error instanceof Error && "code" in error && ["EACCES", "EBUSY", "EPERM"].includes(String(error.code));
2442
- var renameStagedRuntime = async (source, destination, options = {}) => {
2443
- const platform4 = options.platform ?? process.platform;
2444
- const renamePath = options.renamePath ?? rename4;
2445
- const wait4 = options.wait ?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
2446
- const delays = platform4 === "win32" ? WINDOWS_RENAME_RETRY_DELAYS_MS : [];
2447
- for (let attempt = 0; ; attempt += 1) {
2448
- try {
2449
- await renamePath(source, destination);
2450
- return;
2451
- } catch (error) {
2452
- const delay = delays[attempt];
2453
- if (delay === void 0 || !isWindowsRenameContention(error)) throw error;
2454
- await wait4(delay);
2455
- }
2456
- }
2457
- };
2458
- var exists = async (filePath) => access(filePath).then(() => true).catch(() => false);
2459
- var resolveNpmInvocation = async (options = {}) => {
2460
- const platform4 = options.platform ?? process.platform;
2461
- if (platform4 !== "win32") return { argsPrefix: [], executable: "npm" };
2462
- const nodeExecutable = options.nodeExecutable ?? process.execPath;
2463
- const pathApi = options.pathApi ?? path6;
2464
- const pathExists = options.pathExists ?? exists;
2465
- const candidates = [
2466
- options.npmExecPath ?? process.env.npm_execpath,
2467
- pathApi.join(pathApi.dirname(nodeExecutable), "node_modules", "npm", "bin", "npm-cli.js")
2424
+ var MACOS_NODE_LIBRARY_PATTERN = /^libnode(?:\.\d+)*\.dylib$/;
2425
+ var getLoadedSharedObjects = () => {
2426
+ const report = process.report?.getReport();
2427
+ return Array.isArray(report?.sharedObjects) ? report.sharedObjects.filter((value) => typeof value === "string") : [];
2428
+ };
2429
+ var copyNodeRuntime = async (sourceNode, destinationNode, options = {}) => {
2430
+ const resolvedSourceNode = await realpath(sourceNode);
2431
+ const sourceDirectory = path6.dirname(resolvedSourceNode);
2432
+ const destinationDirectory = path6.dirname(destinationNode);
2433
+ await mkdir5(destinationDirectory, { recursive: true });
2434
+ await cp(resolvedSourceNode, destinationNode, { dereference: true, force: true });
2435
+ if ((options.platform ?? process.platform) !== "darwin") return;
2436
+ const launcherDirectory = path6.resolve(path6.dirname(sourceNode));
2437
+ const companionDirectories = [
2438
+ .../* @__PURE__ */ new Set([
2439
+ launcherDirectory,
2440
+ path6.resolve(launcherDirectory, "..", "lib"),
2441
+ sourceDirectory,
2442
+ path6.resolve(sourceDirectory, "..", "lib")
2443
+ ])
2468
2444
  ];
2469
- for (const candidate of candidates) {
2470
- if (!candidate || pathApi.basename(candidate).toLowerCase() !== "npm-cli.js") continue;
2471
- if (await pathExists(candidate)) {
2472
- return { argsPrefix: [candidate], executable: nodeExecutable };
2445
+ const companions = (await Promise.all(
2446
+ companionDirectories.map(async (directory) => ({
2447
+ directory,
2448
+ entries: await readdir4(directory, { withFileTypes: true }).catch((error) => {
2449
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return [];
2450
+ throw error;
2451
+ })
2452
+ }))
2453
+ )).flatMap(({ directory, entries }) => entries.map((entry) => ({ directory, entry })));
2454
+ const companionFiles = /* @__PURE__ */ new Map();
2455
+ const loadedLibraries = options.loadedLibraries ?? getLoadedSharedObjects();
2456
+ for (const library of loadedLibraries) {
2457
+ const name = path6.basename(library);
2458
+ if (MACOS_NODE_LIBRARY_PATTERN.test(name)) companionFiles.set(name, library);
2459
+ }
2460
+ for (const { directory, entry } of companions) {
2461
+ if ((entry.isFile() || entry.isSymbolicLink()) && MACOS_NODE_LIBRARY_PATTERN.test(entry.name) && !companionFiles.has(entry.name)) {
2462
+ companionFiles.set(entry.name, path6.join(directory, entry.name));
2473
2463
  }
2474
2464
  }
2475
- throw new Error(
2476
- `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 ?? "")})`
2465
+ await Promise.all(
2466
+ [...companionFiles].map(
2467
+ ([name, source]) => cp(source, path6.join(destinationDirectory, name), {
2468
+ dereference: true,
2469
+ force: true
2470
+ })
2471
+ )
2477
2472
  );
2478
2473
  };
2474
+ var exists = async (filePath) => access(filePath).then(() => true).catch(() => false);
2479
2475
  var runtimeRoot = () => process.env.BUDA_CONNECTOR_RUNTIME_ROOT || path6.join(homedir3(), ".buda", "runtime");
2480
2476
  var runtimePlatformKey = `${process.platform}-${process.arch}-node${process.versions.modules}`;
2481
2477
  var versionDirectory = () => path6.join(runtimeRoot(), runtimePlatformKey, CONNECTOR_RUNTIME_VERSION);
@@ -2509,11 +2505,9 @@ var validateRuntime = async (runtime) => {
2509
2505
  };
2510
2506
  var installFromNpm = async (directory) => {
2511
2507
  await mkdir5(directory, { recursive: true, mode: 448 });
2512
- const npm = await resolveNpmInvocation();
2513
2508
  await execFileAsync(
2514
- npm.executable,
2509
+ process.platform === "win32" ? "npm.cmd" : "npm",
2515
2510
  [
2516
- ...npm.argsPrefix,
2517
2511
  "install",
2518
2512
  "--ignore-scripts=false",
2519
2513
  "--no-audit",
@@ -2534,8 +2528,7 @@ var installFromNpm = async (directory) => {
2534
2528
  "cli.js"
2535
2529
  );
2536
2530
  const nodeExecutable = path6.join(directory, "node", path6.basename(process.execPath));
2537
- await mkdir5(path6.dirname(nodeExecutable), { recursive: true });
2538
- await cp(process.execPath, nodeExecutable, { force: true });
2531
+ await copyNodeRuntime(process.execPath, nodeExecutable);
2539
2532
  if (!await exists(entrypoint)) throw new Error("Installed Connector runtime is incomplete");
2540
2533
  return { entrypoint, nodeExecutable, version: CONNECTOR_RUNTIME_VERSION };
2541
2534
  };
@@ -2548,12 +2541,11 @@ var installFromBundledRuntime = async (directory, sourceRoot) => {
2548
2541
  if (relativeEntrypoint.startsWith("..") || path6.isAbsolute(relativeEntrypoint)) {
2549
2542
  throw new Error("Bundled Connector entrypoint must be inside its runtime root");
2550
2543
  }
2551
- const { copyNode, relativeNode } = resolveBundledNodePlacement(sourceRoot, sourceNode);
2544
+ const relativeNode = path6.relative(sourceRoot, sourceNode);
2552
2545
  const entrypoint = path6.join(directory, relativeEntrypoint);
2553
- const nodeExecutable = copyNode ? path6.join(directory, "node", path6.basename(sourceNode)) : path6.join(directory, relativeNode);
2554
- if (copyNode) {
2555
- await mkdir5(path6.dirname(nodeExecutable), { recursive: true });
2556
- await cp(sourceNode, nodeExecutable, { force: true });
2546
+ const nodeExecutable = relativeNode.startsWith("..") ? path6.join(directory, "node", path6.basename(sourceNode)) : path6.join(directory, relativeNode);
2547
+ if (relativeNode.startsWith("..")) {
2548
+ await copyNodeRuntime(sourceNode, nodeExecutable);
2557
2549
  }
2558
2550
  if (!await exists(entrypoint))
2559
2551
  throw new Error("Copied Connector runtime entrypoint is missing");
@@ -2599,7 +2591,7 @@ async function ensureStableConnectorRuntime() {
2599
2591
  };
2600
2592
  await writeManifest(temporary, rebased);
2601
2593
  await rm(finalDirectory, { recursive: true, force: true });
2602
- await renameStagedRuntime(temporary, finalDirectory);
2594
+ await rename4(temporary, finalDirectory);
2603
2595
  return rebased;
2604
2596
  } catch (error) {
2605
2597
  await rm(temporary, { recursive: true, force: true });
@@ -2613,8 +2605,6 @@ async function ensureStableConnectorRuntime() {
2613
2605
  // src/daemon-client.ts
2614
2606
  var START_TIMEOUT_MS = 45e3;
2615
2607
  var BOOTSTRAP_TIMEOUT_MS = 5 * 6e4;
2616
- var GRACEFUL_STOP_TIMEOUT_MS = 3e3;
2617
- var LOCK_ONLY_RECOVERY_MAX_AGE_MS = 5 * 6e4;
2618
2608
  var POLL_INTERVAL_MS = 250;
2619
2609
  var wait2 = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
2620
2610
  var getConfigRoot = () => process.env.BUDA_CONNECTOR_CONFIG_ROOT;
@@ -2647,78 +2637,14 @@ var compareVersions = (left, right) => {
2647
2637
  return 0;
2648
2638
  };
2649
2639
  var shouldReplaceDaemonVersion = (runningVersion, candidateVersion) => compareVersions(candidateVersion, runningVersion) > 0;
2650
- var shouldDisplayConnectorLogEntry = (createdAt, startedAt) => {
2651
- const timestamp = Date.parse(createdAt);
2652
- return Number.isNaN(timestamp) || timestamp >= startedAt;
2653
- };
2654
- var getErrorCode = (error) => typeof error === "object" && error !== null && "code" in error ? String(error.code) : "UNKNOWN";
2655
- var waitForChildSpawn = (child) => new Promise((resolve, reject) => {
2656
- const onError = (error) => {
2657
- child.off("spawn", onSpawn);
2658
- reject(error);
2659
- };
2660
- const onSpawn = () => {
2661
- child.off("error", onError);
2662
- resolve();
2663
- };
2664
- child.once("error", onError);
2665
- child.once("spawn", onSpawn);
2666
- });
2667
- async function spawnDetachedDaemonProcess(command, args, options, dependencies = {}) {
2668
- const platform4 = dependencies.platform ?? process.platform;
2669
- const spawnProcess = dependencies.spawnProcess ?? ((file, fileArgs, spawnOptions) => spawn(file, fileArgs, spawnOptions));
2670
- const strategies = platform4 === "win32" ? [
2671
- { name: "detached-hidden", windowsHide: true },
2672
- { name: "detached-unhidden", windowsHide: false }
2673
- ] : [{ name: "detached-hidden", windowsHide: true }];
2674
- const attempted = [];
2675
- for (const [index, strategy] of strategies.entries()) {
2676
- attempted.push(strategy.name);
2677
- const child = spawnProcess(command, args, {
2678
- detached: true,
2679
- env: options.env,
2680
- stdio: options.stdio,
2681
- windowsHide: strategy.windowsHide
2682
- });
2683
- try {
2684
- await waitForChildSpawn(child);
2685
- if (!child.pid) {
2686
- throw Object.assign(new Error("spawned Connector daemon did not expose a PID"), {
2687
- code: "NO_PID"
2688
- });
2689
- }
2690
- child.unref();
2691
- return { pid: child.pid, strategy: strategy.name };
2692
- } catch (error) {
2693
- const code = getErrorCode(error);
2694
- const shouldRetry = platform4 === "win32" && index === 0 && code === "EINVAL";
2695
- if (shouldRetry) continue;
2696
- const diagnostics = [
2697
- `code=${code}`,
2698
- `strategies=${attempted.join(",")}`,
2699
- `platform=${platform4}`,
2700
- `node=${dependencies.nodeVersion ?? process.version}`,
2701
- `executable=${JSON.stringify(command)}`,
2702
- `entrypoint=${JSON.stringify(args[0] ?? "")}`,
2703
- `cwd=${JSON.stringify(dependencies.cwd ?? process.cwd())}`
2704
- ].join(" ");
2705
- throw new Error(`Connector daemon process creation failed: ${diagnostics}`, { cause: error });
2706
- }
2707
- }
2708
- throw new Error("Connector daemon process creation failed without a spawn attempt");
2709
- }
2710
- var readReadyDaemon = async (launchConfig) => {
2711
- const config = await readDeviceConfig(launchConfig, getConfigRoot());
2712
- if (!config) return null;
2713
- const status = await readLiveStatus(config);
2714
- return status ? { config, status } : null;
2715
- };
2716
2640
  var waitForDaemon = async (launchConfig, deadline) => {
2717
2641
  let lastConfig = null;
2718
2642
  while (Date.now() < deadline) {
2719
- const ready = await readReadyDaemon(launchConfig);
2720
- if (ready) return ready;
2721
2643
  lastConfig = await readDeviceConfig(launchConfig, getConfigRoot());
2644
+ if (lastConfig) {
2645
+ const status = await readLiveStatus(lastConfig);
2646
+ if (status) return { config: lastConfig, status };
2647
+ }
2722
2648
  await wait2(POLL_INTERVAL_MS);
2723
2649
  }
2724
2650
  throw new Error(
@@ -2727,126 +2653,17 @@ var waitForDaemon = async (launchConfig, deadline) => {
2727
2653
  };
2728
2654
  var spawnInternalDaemon = async (launchConfig, mode, preparedRuntime, options = {}) => {
2729
2655
  const runtime = preparedRuntime ?? await ensureStableConnectorRuntime();
2730
- const debugDaemonStdio = process.env.BUDA_CONNECTOR_DEBUG_DAEMON_STDIO === "1";
2731
- return spawnDetachedDaemonProcess(
2732
- runtime.nodeExecutable,
2733
- getRuntimeCommandArgs(runtime, mode, options),
2734
- {
2735
- env: {
2736
- ...process.env,
2737
- BUDA_CONNECTOR_INTERNAL_LAUNCH_CONFIG: JSON.stringify(launchConfig ?? {})
2738
- },
2739
- stdio: debugDaemonStdio ? ["ignore", "inherit", "inherit"] : "ignore"
2740
- }
2741
- );
2742
- };
2743
- var waitForProcessExit = async (pid, timeoutMs) => {
2744
- const deadline = Date.now() + timeoutMs;
2745
- while (Date.now() < deadline) {
2746
- if (!isProcessAlive2(pid)) return true;
2747
- await wait2(50);
2748
- }
2749
- return !isProcessAlive2(pid);
2750
- };
2751
- var signalProcess = (pid, signal) => {
2752
- try {
2753
- process.kill(pid, signal);
2754
- return true;
2755
- } catch (error) {
2756
- if (typeof error === "object" && error !== null && "code" in error && error.code === "ESRCH") {
2757
- return false;
2758
- }
2759
- throw error;
2760
- }
2761
- };
2762
- var stopUnresponsiveDaemonProcess = async (pid) => {
2763
- if (!isProcessAlive2(pid)) return;
2764
- if (!signalProcess(pid, "SIGTERM")) return;
2765
- if (await waitForProcessExit(pid, 2e3)) return;
2766
- if (!signalProcess(pid, "SIGKILL")) return;
2767
- if (await waitForProcessExit(pid, 3e3)) return;
2768
- throw new Error(`Timed-out Connector daemon PID ${pid} could not be stopped safely`);
2769
- };
2770
- var waitForDaemonRelease = async (config, status, configRoot = getConfigRoot()) => {
2771
- const deadline = Date.now() + GRACEFUL_STOP_TIMEOUT_MS;
2772
- while (Date.now() < deadline) {
2773
- if (!isProcessAlive2(status.pid)) return true;
2774
- const current = await readPersistedDaemonStatus(config, configRoot).catch(() => null);
2775
- if (!current || current.pid !== status.pid || current.instanceId !== status.instanceId) {
2776
- return true;
2777
- }
2778
- await wait2(50);
2779
- }
2780
- return !isProcessAlive2(status.pid);
2781
- };
2782
- var stopLiveDaemon = async (config, status, configRoot = getConfigRoot(), command = "stop") => {
2783
- const stopStatus = await requestDaemonControl(config, command, configRoot);
2784
- if (command === "stop-if-idle" && !stopStatus.idle) return false;
2785
- if (await waitForDaemonRelease(config, status, configRoot)) return true;
2786
- const current = await readPersistedDaemonStatus(config, configRoot).catch(() => null);
2787
- if (!current || current.pid !== status.pid || current.instanceId !== status.instanceId) {
2788
- return true;
2789
- }
2790
- await stopUnresponsiveDaemonProcess(status.pid);
2791
- return true;
2792
- };
2793
- 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;
2794
- var readMatchingPersistedDaemonStatus = async (config, configRoot = getConfigRoot()) => {
2795
- const status = await readPersistedDaemonStatus(config, configRoot).catch(() => null);
2796
- return status && matchesDaemonIdentity(config, status) ? status : null;
2797
- };
2798
- var stopDaemonForIdentity = async (config, configRoot = getConfigRoot()) => {
2799
- const liveStatus = await readLiveStatus(config, configRoot);
2800
- if (liveStatus) {
2801
- await stopLiveDaemon(config, liveStatus, configRoot);
2802
- return;
2803
- }
2804
- const persistedStatus = await readMatchingPersistedDaemonStatus(config, configRoot);
2805
- if (!persistedStatus || !isProcessAlive2(persistedStatus.pid)) return;
2806
- const currentStatus = await readMatchingPersistedDaemonStatus(config, configRoot);
2807
- if (!currentStatus || currentStatus.pid !== persistedStatus.pid || currentStatus.instanceId !== persistedStatus.instanceId || !isProcessAlive2(currentStatus.pid)) {
2808
- return;
2809
- }
2810
- await stopUnresponsiveDaemonProcess(currentStatus.pid);
2811
- };
2812
- var shouldRestartUnhealthyDaemonForEnrollment = (status, explicitEnrollment) => explicitEnrollment && status.idle && status.heartbeatHealthy === false;
2813
- var readDaemonLockOwnerPid = async (config) => {
2814
- const lockPath = `${getConnectorDaemonLockTarget(
2815
- config.serverUrl,
2816
- config.connectorId,
2817
- getConfigRoot()
2818
- )}.lock`;
2819
- const raw = await readFile6(lockPath, "utf8").catch(() => "");
2820
- try {
2821
- const metadata = JSON.parse(raw);
2822
- const age = typeof metadata.createdAt === "number" && Number.isFinite(metadata.createdAt) ? Date.now() - metadata.createdAt : Number.POSITIVE_INFINITY;
2823
- return typeof metadata.pid === "number" && Number.isSafeInteger(metadata.pid) && metadata.pid > 0 && age >= 0 && age <= LOCK_ONLY_RECOVERY_MAX_AGE_MS ? metadata.pid : null;
2824
- } catch {
2825
- return null;
2826
- }
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();
2827
2666
  };
2828
- async function startDaemonWithRecovery(dependencies, maxAttempts = 2) {
2829
- let lastError;
2830
- for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
2831
- const spawned = await dependencies.spawnDaemon();
2832
- try {
2833
- return await dependencies.waitForReady();
2834
- } catch (error) {
2835
- lastError = error;
2836
- const lateReady = await dependencies.readReady().catch(() => null);
2837
- if (lateReady) return lateReady;
2838
- await dependencies.stopDaemon(spawned.pid);
2839
- if (attempt === maxAttempts) throw error;
2840
- }
2841
- }
2842
- throw lastError;
2843
- }
2844
- var startInternalDaemonWithRecovery = (launchConfig, mode, preparedRuntime, options = {}) => startDaemonWithRecovery({
2845
- readReady: () => readReadyDaemon(launchConfig),
2846
- spawnDaemon: () => spawnInternalDaemon(launchConfig, mode, preparedRuntime, options),
2847
- stopDaemon: stopUnresponsiveDaemonProcess,
2848
- waitForReady: () => waitForDaemon(launchConfig, Date.now() + START_TIMEOUT_MS)
2849
- });
2850
2667
  var stopOlderIdleDaemon = async (config, status) => {
2851
2668
  if (!shouldReplaceDaemonVersion(status.version, CONNECTOR_RUNTIME_VERSION)) return false;
2852
2669
  if (!status.idle) return false;
@@ -2873,19 +2690,14 @@ async function ensureConnectorDaemon(launchConfig, mode, options = {}) {
2873
2690
  if (existingConfig) {
2874
2691
  const existingStatus = await readLiveStatus(existingConfig);
2875
2692
  if (existingStatus) {
2876
- if (shouldRestartUnhealthyDaemonForEnrollment(
2877
- existingStatus,
2878
- Boolean(options.explicitEnrollment)
2879
- ) && await stopLiveDaemon(existingConfig, existingStatus, getConfigRoot(), "stop-if-idle")) {
2880
- return await startInternalDaemonWithRecovery(launchConfig, mode, void 0, options);
2881
- }
2882
2693
  const preparedRuntime = !shouldReplaceDaemonVersion(existingStatus.version, CONNECTOR_RUNTIME_VERSION) || !existingStatus.idle ? void 0 : await ensureStableConnectorRuntime();
2883
2694
  const replaced = await stopOlderIdleDaemon(existingConfig, existingStatus);
2884
2695
  if (!replaced) {
2885
2696
  const status = mode === "persistent" ? await requestDaemonControl(existingConfig, "persist", getConfigRoot()) : existingStatus;
2886
2697
  return { config: existingConfig, status };
2887
2698
  }
2888
- return await startInternalDaemonWithRecovery(launchConfig, mode, preparedRuntime, options);
2699
+ await spawnInternalDaemon(launchConfig, mode, preparedRuntime, options);
2700
+ return await waitForDaemon(launchConfig, Date.now() + START_TIMEOUT_MS);
2889
2701
  }
2890
2702
  const persistedStatus = await readPersistedDaemonStatus(
2891
2703
  existingConfig,
@@ -2896,12 +2708,9 @@ async function ensureConnectorDaemon(launchConfig, mode, options = {}) {
2896
2708
  `Connector daemon PID ${persistedStatus.pid} is running but its local control channel is not responding. Stop that process and retry.`
2897
2709
  );
2898
2710
  }
2899
- const lockOwnerPid = await readDaemonLockOwnerPid(existingConfig);
2900
- if (lockOwnerPid && isProcessAlive2(lockOwnerPid)) {
2901
- await stopUnresponsiveDaemonProcess(lockOwnerPid);
2902
- }
2903
2711
  }
2904
- return await startInternalDaemonWithRecovery(launchConfig, mode, void 0, options);
2712
+ await spawnInternalDaemon(launchConfig, mode, void 0, options);
2713
+ return await waitForDaemon(launchConfig, Date.now() + START_TIMEOUT_MS);
2905
2714
  } finally {
2906
2715
  await bootstrapLock.release();
2907
2716
  }
@@ -2921,7 +2730,6 @@ var streamDaemonLogs = (options) => {
2921
2730
  for (const line of lines.slice(seenLines)) {
2922
2731
  try {
2923
2732
  const entry = JSON.parse(line);
2924
- if (!shouldDisplayConnectorLogEntry(entry.createdAt, options.startedAt)) continue;
2925
2733
  const connectorId = entry.metadata?.connectorId;
2926
2734
  if (options.dashboardActive && typeof connectorId === "string") {
2927
2735
  const output2 = entry.metadata?.source === "daemon-console" ? entry.message : `[buda-connector] ${entry.message}`;
@@ -2997,7 +2805,9 @@ async function stopLocalConnectorDaemon(connectorId, serverUrl, configRoot = get
2997
2805
  (candidate) => candidate.connectorId === connectorId && normalizeServerOrigin(candidate.serverUrl) === serverOrigin
2998
2806
  );
2999
2807
  if (!config) throw new Error(`Local Connector ${connectorId} was not found`);
3000
- await stopDaemonForIdentity(config, configRoot);
2808
+ const status = await readLiveStatus(config, configRoot);
2809
+ if (!status) return;
2810
+ await requestDaemonControl(config, "stop", configRoot);
3001
2811
  }
3002
2812
  async function stopAllLocalConnectorDaemons(configRoot = getConfigRoot()) {
3003
2813
  const configs = await listDeviceConfigs(configRoot);
@@ -3008,7 +2818,10 @@ async function stopAllLocalConnectorDaemons(configRoot = getConfigRoot()) {
3008
2818
  ${config.connectorId}`, config);
3009
2819
  }
3010
2820
  const results = await Promise.allSettled(
3011
- [...identities.values()].map((config) => stopDaemonForIdentity(config, configRoot))
2821
+ [...identities.values()].map(async (config) => {
2822
+ const status = await readLiveStatus(config, configRoot);
2823
+ if (status) await requestDaemonControl(config, "stop", configRoot);
2824
+ })
3012
2825
  );
3013
2826
  const failures = results.filter((result) => result.status === "rejected");
3014
2827
  if (failures.length) {
@@ -3027,7 +2840,6 @@ var waitForConnectorViewerExit = async (dashboardClosed) => {
3027
2840
  process.off("SIGTERM", finishSignal);
3028
2841
  };
3029
2842
  async function runConnectorDashboard(launchConfig, options = {}) {
3030
- const startedAt = Date.now();
3031
2843
  let finishDashboard = () => void 0;
3032
2844
  const dashboardClosed = new Promise((resolve) => {
3033
2845
  finishDashboard = resolve;
@@ -3048,8 +2860,7 @@ async function runConnectorDashboard(launchConfig, options = {}) {
3048
2860
  connectorId: config.connectorId,
3049
2861
  dashboardActive: false,
3050
2862
  plain: Boolean(options.plain),
3051
- serverUrl: config.serverUrl,
3052
- startedAt
2863
+ serverUrl: config.serverUrl
3053
2864
  });
3054
2865
  if (!process.stdin.isTTY) {
3055
2866
  stopLogs2();
@@ -3076,8 +2887,7 @@ async function runConnectorDashboard(launchConfig, options = {}) {
3076
2887
  const stopLogs = streamDaemonLogs({
3077
2888
  dashboardActive: true,
3078
2889
  plain: false,
3079
- serverUrl: getServerUrl(launchConfig),
3080
- startedAt
2890
+ serverUrl: getServerUrl(launchConfig)
3081
2891
  });
3082
2892
  let refreshing = false;
3083
2893
  const refresh = async () => {
@@ -3622,10 +3432,10 @@ import {
3622
3432
  lstat,
3623
3433
  mkdir as mkdir8,
3624
3434
  open as open2,
3625
- readdir as readdir4,
3435
+ readdir as readdir5,
3626
3436
  readFile as readFile7,
3627
3437
  readlink,
3628
- realpath,
3438
+ realpath as realpath2,
3629
3439
  rename as rename5,
3630
3440
  rm as rm2,
3631
3441
  symlink,
@@ -3677,7 +3487,7 @@ var migrateLegacyWorkspaceRoot = async (storageRoot, options = {}) => {
3677
3487
  "Legacy migration storage root must be a real directory"
3678
3488
  );
3679
3489
  }
3680
- const canonicalRoot = await realpath(storageRoot);
3490
+ const canonicalRoot = await realpath2(storageRoot);
3681
3491
  const migrationDirectory = path9.join(canonicalRoot, MIGRATION_DIRECTORY);
3682
3492
  await ensurePrivateDirectoryWithin(canonicalRoot, migrationDirectory);
3683
3493
  const journalPath = path9.join(migrationDirectory, JOURNAL_FILENAME);
@@ -3807,7 +3617,7 @@ var createJournal = async (storageRoot, migrationDirectory, owner) => {
3807
3617
  };
3808
3618
  };
3809
3619
  var inventoryLegacySources = async (storageRoot) => {
3810
- const rootEntries = await readdir4(storageRoot, { withFileTypes: true });
3620
+ const rootEntries = await readdir5(storageRoot, { withFileTypes: true });
3811
3621
  const templateNames = new Set(LEGACY_TEMPLATE_FILES);
3812
3622
  for (const entry of rootEntries) {
3813
3623
  if (entry.name.endsWith(".md")) templateNames.add(entry.name);
@@ -3837,7 +3647,7 @@ var inventoryLegacySources = async (storageRoot) => {
3837
3647
  `Legacy migration source ${candidate.id} must not be a symbolic link`
3838
3648
  );
3839
3649
  }
3840
- if (info.isDirectory() && (await readdir4(candidate.source)).length === 0) continue;
3650
+ if (info.isDirectory() && (await readdir5(candidate.source)).length === 0) continue;
3841
3651
  found.push(candidate);
3842
3652
  }
3843
3653
  return found;
@@ -3857,7 +3667,7 @@ var copyTree = async (source, destination, onEntryCopied) => {
3857
3667
  const info = await lstat(source);
3858
3668
  if (info.isDirectory()) {
3859
3669
  await mkdir8(destination, { mode: info.mode & 4095 });
3860
- const entries = (await readdir4(source)).sort();
3670
+ const entries = (await readdir5(source)).sort();
3861
3671
  for (const entry of entries) {
3862
3672
  await copyTree(path9.join(source, entry), path9.join(destination, entry), onEntryCopied);
3863
3673
  }
@@ -3894,8 +3704,8 @@ var verifyTree = async (source, destination) => {
3894
3704
  }
3895
3705
  if (sourceInfo.isDirectory()) {
3896
3706
  const [sourceEntries, destinationEntries] = await Promise.all([
3897
- readdir4(source),
3898
- readdir4(destination)
3707
+ readdir5(source),
3708
+ readdir5(destination)
3899
3709
  ]);
3900
3710
  sourceEntries.sort();
3901
3711
  destinationEntries.sort();
@@ -3973,7 +3783,6 @@ var writeJournal = async (journalPath, journal) => {
3973
3783
  }
3974
3784
  };
3975
3785
  var syncDirectory2 = async (directory) => {
3976
- if (process.platform === "win32") return;
3977
3786
  const handle = await open2(directory, constants2.O_RDONLY);
3978
3787
  try {
3979
3788
  await handle.sync();
@@ -4321,7 +4130,7 @@ function isNonEmptyString(value) {
4321
4130
  }
4322
4131
 
4323
4132
  // src/html-preview.ts
4324
- import { readFile as readFile8, realpath as realpath2, stat as stat4 } from "fs/promises";
4133
+ import { readFile as readFile8, realpath as realpath3, stat as stat4 } from "fs/promises";
4325
4134
  import { createServer as createServer2 } from "http";
4326
4135
  import path10 from "path";
4327
4136
  var PREVIEW_HOST = "127.0.0.1";
@@ -4411,7 +4220,7 @@ var resolveWorkspaceFile = async (input) => {
4411
4220
  const virtualPath = input.filePath.replaceAll("\\", "/");
4412
4221
  if (virtualPath.includes("\0")) throw new Error("HTML preview path contains invalid data.");
4413
4222
  const mountPath = path10.resolve(input.mounts[input.rootVolume]);
4414
- const root = await realpath2(mountPath);
4223
+ const root = await realpath3(mountPath);
4415
4224
  let relativePath = virtualPath;
4416
4225
  let explicitVolume;
4417
4226
  for (const volume of ["agent", "space"]) {
@@ -4441,7 +4250,7 @@ var resolveWorkspaceFile = async (input) => {
4441
4250
  }
4442
4251
  const candidate = path10.resolve(root, ...segments);
4443
4252
  assertContained(root, candidate);
4444
- const canonicalFile = await realpath2(candidate);
4253
+ const canonicalFile = await realpath3(candidate);
4445
4254
  assertContained(root, canonicalFile);
4446
4255
  const fileStat = await stat4(canonicalFile);
4447
4256
  if (!fileStat.isFile()) throw new Error("HTML preview path must identify a file.");
@@ -4503,10 +4312,10 @@ var serveStaticFile = async (folder, method, requestUrl) => {
4503
4312
  try {
4504
4313
  const candidate = path10.resolve(folder, ...segments);
4505
4314
  assertContained(folder, candidate);
4506
- let canonicalFile = await realpath2(candidate);
4315
+ let canonicalFile = await realpath3(candidate);
4507
4316
  assertContained(folder, canonicalFile);
4508
4317
  if ((await stat4(canonicalFile)).isDirectory()) {
4509
- canonicalFile = await realpath2(path10.join(canonicalFile, "index.html"));
4318
+ canonicalFile = await realpath3(path10.join(canonicalFile, "index.html"));
4510
4319
  assertContained(folder, canonicalFile);
4511
4320
  }
4512
4321
  if (!(await stat4(canonicalFile)).isFile()) throw new Error("Not a file");
@@ -4539,19 +4348,14 @@ import {
4539
4348
  copyFile as copyFile2,
4540
4349
  lstat as lstat2,
4541
4350
  mkdir as mkdir9,
4542
- readdir as readdir5,
4351
+ readdir as readdir6,
4543
4352
  readFile as readFile9,
4544
- realpath as realpath3,
4353
+ realpath as realpath4,
4545
4354
  rm as rm3,
4546
4355
  symlink as symlink2,
4547
4356
  writeFile as writeFile4
4548
4357
  } from "fs/promises";
4549
4358
  import path11 from "path";
4550
-
4551
- // src/directory-link.ts
4552
- var directoryLinkType = (platform4 = process.platform) => platform4 === "win32" ? "junction" : "dir";
4553
-
4554
- // src/workspace-volume-lifecycle.ts
4555
4359
  var MOUNT_DIRECTORY = "mount";
4556
4360
  var ARCHIVE_MARKER = "archived.json";
4557
4361
  var AGENT_CONTEXT_TEMPLATE_FILES = [
@@ -4649,7 +4453,7 @@ var WorkspaceVolumeLifecycle = class {
4649
4453
  const bootstrapped = [];
4650
4454
  const skipped = [];
4651
4455
  const entries = new Set(
4652
- (await readdir5(templateDirectory, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isFile()).map((entry) => entry.name)
4456
+ (await readdir6(templateDirectory, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isFile()).map((entry) => entry.name)
4653
4457
  );
4654
4458
  for (const filename of AGENT_CONTEXT_TEMPLATE_FILES) {
4655
4459
  if (!entries.has(filename)) continue;
@@ -4684,7 +4488,7 @@ var WorkspaceVolumeLifecycle = class {
4684
4488
  ]) {
4685
4489
  await mkdir9(volume, { recursive: true, mode: 448 });
4686
4490
  const info = await lstat2(volume);
4687
- if (info.isSymbolicLink() || !info.isDirectory() || await realpath3(volume) !== volume) {
4491
+ if (info.isSymbolicLink() || !info.isDirectory() || await realpath4(volume) !== volume) {
4688
4492
  throw new Error("Workspace volume must be a canonical real directory");
4689
4493
  }
4690
4494
  }
@@ -4697,17 +4501,17 @@ var isMissingFile = (error) => error instanceof Error && "code" in error && erro
4697
4501
  var ensureVolumeLink = async (target, linkPath) => {
4698
4502
  try {
4699
4503
  const info = await lstat2(linkPath);
4700
- if (!info.isSymbolicLink() || await realpath3(linkPath) !== target) {
4504
+ if (!info.isSymbolicLink() || await realpath4(linkPath) !== target) {
4701
4505
  throw new Error("Workspace mount link does not match its canonical volume");
4702
4506
  }
4703
4507
  } catch (error) {
4704
4508
  if (!isMissingFile(error)) throw error;
4705
4509
  try {
4706
- await symlink2(target, linkPath, directoryLinkType());
4510
+ await symlink2(target, linkPath, "dir");
4707
4511
  } catch (createError) {
4708
4512
  if (!isAlreadyExists3(createError)) throw createError;
4709
4513
  const info = await lstat2(linkPath);
4710
- if (!info.isSymbolicLink() || await realpath3(linkPath) !== target) {
4514
+ if (!info.isSymbolicLink() || await realpath4(linkPath) !== target) {
4711
4515
  throw new Error("Workspace mount link raced with an invalid target");
4712
4516
  }
4713
4517
  }
@@ -4718,7 +4522,6 @@ var isAlreadyExists3 = (error) => error instanceof Error && "code" in error && e
4718
4522
  // src/relay.ts
4719
4523
  var RELAY_PING_INTERVAL_MS = 2e4;
4720
4524
  var RELAY_PONG_TIMEOUT_MS = 1e4;
4721
- var RELAY_ACTIVE_REQUEST_PONG_TIMEOUT_LIMIT = 2;
4722
4525
  var RELAY_HANDSHAKE_TIMEOUT_MS = 1e4;
4723
4526
  var RELAY_RECONNECT_MAX_MS = 3e4;
4724
4527
  var RELAY_RECONNECT_JITTER = 0.2;
@@ -4824,7 +4627,6 @@ function startConnectorRelay(config, bunnyDaemon, relayProtocol, workspaceRegist
4824
4627
  socket = ws;
4825
4628
  const inflight = /* @__PURE__ */ new Map();
4826
4629
  currentInflight = inflight;
4827
- let consecutivePongTimeouts = 0;
4828
4630
  let detached = false;
4829
4631
  const detach = () => {
4830
4632
  if (detached) return;
@@ -4837,7 +4639,6 @@ function startConnectorRelay(config, bunnyDaemon, relayProtocol, workspaceRegist
4837
4639
  void writeLog("info", `relay connected ${relayUrl}`);
4838
4640
  hadOpenConnection = true;
4839
4641
  reconnectDelayMs = RELAY_RECONNECT_INTERVAL_MS;
4840
- consecutivePongTimeouts = 0;
4841
4642
  pingTimer = setInterval(() => {
4842
4643
  if (ws.readyState !== WebSocket2.OPEN) return;
4843
4644
  try {
@@ -4849,33 +4650,18 @@ function startConnectorRelay(config, bunnyDaemon, relayProtocol, workspaceRegist
4849
4650
  }
4850
4651
  if (pongTimer) clearTimeout(pongTimer);
4851
4652
  pongTimer = setTimeout(() => {
4852
- pongTimer = null;
4853
- consecutivePongTimeouts += 1;
4854
- if (inflight.size > 0 && consecutivePongTimeouts < RELAY_ACTIVE_REQUEST_PONG_TIMEOUT_LIMIT) {
4855
- void writeLog(
4856
- "warn",
4857
- `relay pong timeout deferred while ${inflight.size} request(s) are active`
4858
- );
4859
- return;
4860
- }
4861
4653
  void writeLog("warn", "relay pong timeout, terminating");
4862
4654
  ws.terminate();
4863
4655
  }, RELAY_PONG_TIMEOUT_MS);
4864
4656
  }, RELAY_PING_INTERVAL_MS);
4865
4657
  });
4866
4658
  ws.on("pong", () => {
4867
- consecutivePongTimeouts = 0;
4868
4659
  if (pongTimer) {
4869
4660
  clearTimeout(pongTimer);
4870
4661
  pongTimer = null;
4871
4662
  }
4872
4663
  });
4873
4664
  ws.on("message", (raw) => {
4874
- consecutivePongTimeouts = 0;
4875
- if (pongTimer) {
4876
- clearTimeout(pongTimer);
4877
- pongTimer = null;
4878
- }
4879
4665
  void handleRelayMessage(
4880
4666
  ws,
4881
4667
  bunnyDaemon,
@@ -5051,7 +4837,7 @@ async function handleRelayMessage(socket, bunnyDaemon, raw, inflight, expectedCo
5051
4837
  });
5052
4838
  executionIdentity = message.executionIdentity;
5053
4839
  } catch (error) {
5054
- await sendRelayError(socket, message.requestId, error).catch(() => void 0);
4840
+ sendRelayError(socket, message.requestId, error);
5055
4841
  return;
5056
4842
  }
5057
4843
  }
@@ -5137,7 +4923,7 @@ async function handleRelayMessage(socket, bunnyDaemon, raw, inflight, expectedCo
5137
4923
  durationMs: Date.now() - codingRunStartedAt
5138
4924
  });
5139
4925
  }
5140
- await sendRelayMessage(socket, {
4926
+ sendRelayMessage(socket, {
5141
4927
  type: "start",
5142
4928
  requestId: message.requestId,
5143
4929
  status: response.status,
@@ -5157,7 +4943,7 @@ async function handleRelayMessage(socket, bunnyDaemon, raw, inflight, expectedCo
5157
4943
  while (true) {
5158
4944
  const { value, done } = await reader.read();
5159
4945
  if (done) break;
5160
- await sendRelayMessage(socket, {
4946
+ sendRelayMessage(socket, {
5161
4947
  type: "chunk",
5162
4948
  requestId: message.requestId,
5163
4949
  data: binaryResponse ? Buffer.from(value).toString("base64") : decoder?.decode(value, { stream: true }) ?? "",
@@ -5166,7 +4952,7 @@ async function handleRelayMessage(socket, bunnyDaemon, raw, inflight, expectedCo
5166
4952
  }
5167
4953
  const tail = decoder?.decode() ?? "";
5168
4954
  if (tail) {
5169
- await sendRelayMessage(socket, {
4955
+ sendRelayMessage(socket, {
5170
4956
  type: "chunk",
5171
4957
  requestId: message.requestId,
5172
4958
  data: tail
@@ -5177,7 +4963,7 @@ async function handleRelayMessage(socket, bunnyDaemon, raw, inflight, expectedCo
5177
4963
  reader.releaseLock();
5178
4964
  }
5179
4965
  }
5180
- await sendRelayMessage(socket, {
4966
+ sendRelayMessage(socket, {
5181
4967
  type: "end",
5182
4968
  requestId: message.requestId
5183
4969
  });
@@ -5189,16 +4975,14 @@ async function handleRelayMessage(socket, bunnyDaemon, raw, inflight, expectedCo
5189
4975
  });
5190
4976
  }
5191
4977
  } catch (error) {
5192
- controller.abort();
5193
4978
  if (codingRunTrace && codingRunStartedAt !== void 0) {
5194
4979
  await writeLog("error", "relay request coding run error", {
5195
4980
  ...codingRunTrace,
5196
4981
  status: codingRunStatus,
5197
- durationMs: Date.now() - codingRunStartedAt,
5198
- error: formatConnectorRelayError(error)
4982
+ durationMs: Date.now() - codingRunStartedAt
5199
4983
  });
5200
4984
  }
5201
- await sendRelayError(socket, message.requestId, error).catch(() => void 0);
4985
+ sendRelayError(socket, message.requestId, error);
5202
4986
  } finally {
5203
4987
  inflight.delete(message.requestId);
5204
4988
  }
@@ -5252,20 +5036,17 @@ var normalizeWorkspaceRequest = (request, mounts) => {
5252
5036
  if (!mount || relative === void 0) {
5253
5037
  throw new ConnectorRelayProtocolError("WORKSPACE_MISMATCH");
5254
5038
  }
5255
- const systemEnv = body.systemEnv && typeof body.systemEnv === "object" && !Array.isArray(body.systemEnv) ? body.systemEnv : {};
5256
- const workspaceEnv = {
5257
- BUDA_AGENT_ROOT: mounts.agent,
5258
- BUDA_SPACE_ROOT: mounts.space,
5259
- BUDA_SYSTEM_ROOT: mounts.system
5260
- };
5039
+ const env = body.env && typeof body.env === "object" && !Array.isArray(body.env) ? body.env : {};
5261
5040
  return {
5262
5041
  ...request,
5263
5042
  body: {
5264
5043
  ...body,
5265
5044
  cwd: path12.join(mount, relative),
5266
- systemEnv: {
5267
- ...systemEnv,
5268
- ...workspaceEnv
5045
+ env: {
5046
+ ...env,
5047
+ BUDA_AGENT_ROOT: mounts.agent,
5048
+ BUDA_SPACE_ROOT: mounts.space,
5049
+ BUDA_SYSTEM_ROOT: mounts.system
5269
5050
  }
5270
5051
  }
5271
5052
  };
@@ -5297,24 +5078,17 @@ var normalizeConnectorFilesystemRequest = (request, mounts) => {
5297
5078
  }
5298
5079
  return request;
5299
5080
  };
5300
- async function sendRelayError(socket, requestId, error) {
5301
- await sendRelayMessage(socket, {
5081
+ function sendRelayError(socket, requestId, error) {
5082
+ sendRelayMessage(socket, {
5302
5083
  type: "error",
5303
5084
  requestId,
5304
5085
  error: error instanceof Error ? error.message : String(error),
5305
5086
  ...error instanceof ConnectorRelayProtocolError ? { code: error.code } : {}
5306
5087
  });
5307
5088
  }
5308
- async function sendRelayMessage(socket, message) {
5309
- if (socket.readyState !== WebSocket2.OPEN) {
5310
- throw new Error(`Connector relay socket closed before ${message.type} could be sent.`);
5311
- }
5312
- await new Promise((resolve, reject) => {
5313
- socket.send(JSON.stringify(message), (error) => {
5314
- if (error) reject(error);
5315
- else resolve();
5316
- });
5317
- });
5089
+ function sendRelayMessage(socket, message) {
5090
+ if (socket.readyState !== WebSocket2.OPEN) return;
5091
+ socket.send(JSON.stringify(message));
5318
5092
  }
5319
5093
  function headersToRecord(headers) {
5320
5094
  const result = {};
@@ -5373,7 +5147,7 @@ async function ensureSymlink(target, linkPath) {
5373
5147
  } catch {
5374
5148
  }
5375
5149
  await mkdir10(path13.dirname(linkPath), { recursive: true });
5376
- await symlink3(target, linkPath, directoryLinkType());
5150
+ await symlink3(target, linkPath, "dir");
5377
5151
  }
5378
5152
 
5379
5153
  // src/workspace-registry.ts
@@ -5384,7 +5158,7 @@ import {
5384
5158
  mkdir as mkdir11,
5385
5159
  open as open3,
5386
5160
  readFile as readFile10,
5387
- realpath as realpath4,
5161
+ realpath as realpath5,
5388
5162
  rename as rename6,
5389
5163
  statfs,
5390
5164
  unlink as unlink7
@@ -5548,7 +5322,7 @@ var reconcileWorkspace = async (workspace, storageRoot) => {
5548
5322
  for (const volume of volumes) {
5549
5323
  assertContained2(storageRoot, volume);
5550
5324
  await ensurePrivateDirectory2(volume);
5551
- const canonical = await realpath4(volume);
5325
+ const canonical = await realpath5(volume);
5552
5326
  if (canonical !== volume) throw corrupt2("Workspace path resolves through a symbolic link");
5553
5327
  }
5554
5328
  };
@@ -5594,7 +5368,7 @@ var canonicalDirectory = async (directory, label) => {
5594
5368
  if (info.isSymbolicLink() || !info.isDirectory()) {
5595
5369
  throw new WorkspaceRegistryError("STORAGE_UNSAFE", `${label} must be a real directory`);
5596
5370
  }
5597
- return realpath4(directory);
5371
+ return realpath5(directory);
5598
5372
  };
5599
5373
  var rejectUnsafeFilesystem = async (storageRoot) => {
5600
5374
  const stats = await statfs(storageRoot, { bigint: true });
@@ -5830,30 +5604,7 @@ import { mkdir as mkdir12 } from "fs/promises";
5830
5604
  import path15 from "path";
5831
5605
  var RECOVERED_DAEMON_STOP_TIMEOUT_MS = 1e4;
5832
5606
  var RECOVERED_DAEMON_STOP_POLL_MS = 100;
5833
- var SHUTDOWN_FORCE_EXIT_MS = 1e4;
5834
- var SHUTDOWN_STEP_TIMEOUT_MS = 2e3;
5835
5607
  var wait3 = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
5836
- var runShutdownStep = async (label, operation) => {
5837
- let timeout;
5838
- try {
5839
- await Promise.race([
5840
- Promise.resolve().then(operation),
5841
- new Promise((_, reject) => {
5842
- timeout = setTimeout(
5843
- () => reject(new Error(`${label} timed out after ${SHUTDOWN_STEP_TIMEOUT_MS}ms`)),
5844
- SHUTDOWN_STEP_TIMEOUT_MS
5845
- );
5846
- })
5847
- ]);
5848
- } catch (error) {
5849
- await writeLog(
5850
- "warn",
5851
- `${label} failed during shutdown: ${error instanceof Error ? error.message : String(error)}`
5852
- ).catch(() => void 0);
5853
- } finally {
5854
- if (timeout) clearTimeout(timeout);
5855
- }
5856
- };
5857
5608
  async function stopRecoveredIdleDaemon(config, configRoot) {
5858
5609
  const stopStatus = await requestDaemonControl(config, "stop-if-idle", configRoot).catch(
5859
5610
  () => null
@@ -5973,30 +5724,18 @@ async function runConnectorDaemon(launchConfig, options = {}) {
5973
5724
  const shutdown = async (shutdownOptions = {}) => {
5974
5725
  if (shuttingDown) return;
5975
5726
  shuttingDown = true;
5976
- const forceExit = options.exitOnShutdown === false ? void 0 : setTimeout(() => process.exit(0), SHUTDOWN_FORCE_EXIT_MS);
5977
5727
  if (interval) clearInterval(interval);
5978
5728
  relay.stop();
5979
5729
  try {
5980
- await Promise.all([
5981
- runShutdownStep("browser session cleanup", closeAllBrowserSessions),
5982
- ...shutdownOptions.unregister === false ? [] : [
5983
- runShutdownStep(
5984
- "connector unregister",
5985
- () => unregisterConnector(config, launchConfig)
5986
- )
5987
- ],
5988
- runShutdownStep(
5989
- "embedded Bunny daemon shutdown",
5990
- () => stopEmbeddedBunnyDaemon(activeBunnyDaemon)
5991
- )
5992
- ]);
5730
+ await closeAllBrowserSessions();
5731
+ if (shutdownOptions.unregister !== false) {
5732
+ await unregisterConnector(config, launchConfig);
5733
+ }
5734
+ await stopEmbeddedBunnyDaemon(activeBunnyDaemon);
5993
5735
  } finally {
5994
- await Promise.all([
5995
- runShutdownStep("control channel shutdown", async () => control?.close()),
5996
- runShutdownStep("daemon lock release", () => daemonLock.release()),
5997
- runShutdownStep("console capture restore", async () => restoreConsole?.())
5998
- ]);
5999
- if (forceExit) clearTimeout(forceExit);
5736
+ await control?.close();
5737
+ await daemonLock.release();
5738
+ await restoreConsole?.();
6000
5739
  if (options.exitOnShutdown !== false) process.exit(0);
6001
5740
  }
6002
5741
  };
@@ -6011,11 +5750,8 @@ async function runConnectorDaemon(launchConfig, options = {}) {
6011
5750
  });
6012
5751
  }).catch((error) => {
6013
5752
  heartbeatHealthy = false;
6014
- if (error instanceof ConnectorDeletedError || error instanceof ConnectorUnauthorizedError) {
6015
- void writeLog(
6016
- "warn",
6017
- error instanceof ConnectorDeletedError ? "connector deleted remotely; stopping daemon" : "connector authentication rejected remotely; stopping daemon"
6018
- );
5753
+ if (error instanceof ConnectorDeletedError) {
5754
+ void writeLog("warn", "connector deleted remotely; stopping daemon");
6019
5755
  void shutdown({ unregister: false });
6020
5756
  return;
6021
5757
  }
@@ -6039,10 +5775,6 @@ async function runConnectorDaemon(launchConfig, options = {}) {
6039
5775
  process.once("SIGINT", () => void shutdown());
6040
5776
  process.once("SIGTERM", () => void shutdown());
6041
5777
  } catch (error) {
6042
- await writeLog(
6043
- "error",
6044
- `daemon startup failed: ${error instanceof Error ? error.stack || error.message : String(error)}`
6045
- ).catch(() => void 0);
6046
5778
  if (bunnyDaemon) await stopEmbeddedBunnyDaemon(bunnyDaemon);
6047
5779
  await daemonLock.release();
6048
5780
  await restoreConsole?.();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buda-ai/connector",
3
- "version": "0.1.30",
3
+ "version": "0.1.32",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -18,7 +18,6 @@
18
18
  "dev": "tsx src/cli.ts dev",
19
19
  "daemon": "tsx src/cli.ts daemon",
20
20
  "new": "tsx src/cli.ts new",
21
- "prepack": "tsup --silent",
22
21
  "start": "node dist/cli.js daemon",
23
22
  "build": "tsup",
24
23
  "test": "vitest run",
@@ -27,7 +26,7 @@
27
26
  "lint:err": "biome format . --write && biome check . --write --diagnostic-level=error"
28
27
  },
29
28
  "dependencies": {
30
- "@bunny-agent/daemon": "0.9.57",
29
+ "@bunny-agent/daemon": "0.9.51",
31
30
  "@clack/prompts": "^1.0.0",
32
31
  "playwright-core": "^1.52.0",
33
32
  "ws": "^8.21.0"