@buda-ai/connector 0.1.24 → 0.1.26

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.
Files changed (2) hide show
  1. package/dist/cli.js +88 -12
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -523,7 +523,7 @@ import path2 from "path";
523
523
  // package.json
524
524
  var package_default = {
525
525
  name: "@buda-ai/connector",
526
- version: "0.1.24",
526
+ version: "0.1.26",
527
527
  private: false,
528
528
  type: "module",
529
529
  publishConfig: {
@@ -550,7 +550,7 @@ var package_default = {
550
550
  "lint:err": "biome format . --write && biome check . --write --diagnostic-level=error"
551
551
  },
552
552
  dependencies: {
553
- "@bunny-agent/daemon": "0.9.55",
553
+ "@bunny-agent/daemon": "0.9.56",
554
554
  "@clack/prompts": "^1.0.0",
555
555
  "playwright-core": "^1.52.0",
556
556
  ws: "^8.21.0"
@@ -2601,6 +2601,7 @@ async function ensureStableConnectorRuntime() {
2601
2601
  // src/daemon-client.ts
2602
2602
  var START_TIMEOUT_MS = 45e3;
2603
2603
  var BOOTSTRAP_TIMEOUT_MS = 5 * 6e4;
2604
+ var LOCK_ONLY_RECOVERY_MAX_AGE_MS = 5 * 6e4;
2604
2605
  var POLL_INTERVAL_MS = 250;
2605
2606
  var wait2 = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
2606
2607
  var getConfigRoot = () => process.env.BUDA_CONNECTOR_CONFIG_ROOT;
@@ -2668,8 +2669,13 @@ async function spawnDetachedDaemonProcess(command, args, options, dependencies =
2668
2669
  });
2669
2670
  try {
2670
2671
  await waitForChildSpawn(child);
2672
+ if (!child.pid) {
2673
+ throw Object.assign(new Error("spawned Connector daemon did not expose a PID"), {
2674
+ code: "NO_PID"
2675
+ });
2676
+ }
2671
2677
  child.unref();
2672
- return strategy.name;
2678
+ return { pid: child.pid, strategy: strategy.name };
2673
2679
  } catch (error) {
2674
2680
  const code = getErrorCode(error);
2675
2681
  const shouldRetry = platform4 === "win32" && index === 0 && code === "EINVAL";
@@ -2688,14 +2694,18 @@ async function spawnDetachedDaemonProcess(command, args, options, dependencies =
2688
2694
  }
2689
2695
  throw new Error("Connector daemon process creation failed without a spawn attempt");
2690
2696
  }
2697
+ var readReadyDaemon = async (launchConfig) => {
2698
+ const config = await readDeviceConfig(launchConfig, getConfigRoot());
2699
+ if (!config) return null;
2700
+ const status = await readLiveStatus(config);
2701
+ return status ? { config, status } : null;
2702
+ };
2691
2703
  var waitForDaemon = async (launchConfig, deadline) => {
2692
2704
  let lastConfig = null;
2693
2705
  while (Date.now() < deadline) {
2706
+ const ready = await readReadyDaemon(launchConfig);
2707
+ if (ready) return ready;
2694
2708
  lastConfig = await readDeviceConfig(launchConfig, getConfigRoot());
2695
- if (lastConfig) {
2696
- const status = await readLiveStatus(lastConfig);
2697
- if (status) return { config: lastConfig, status };
2698
- }
2699
2709
  await wait2(POLL_INTERVAL_MS);
2700
2710
  }
2701
2711
  throw new Error(
@@ -2705,7 +2715,7 @@ var waitForDaemon = async (launchConfig, deadline) => {
2705
2715
  var spawnInternalDaemon = async (launchConfig, mode, preparedRuntime, options = {}) => {
2706
2716
  const runtime = preparedRuntime ?? await ensureStableConnectorRuntime();
2707
2717
  const debugDaemonStdio = process.env.BUDA_CONNECTOR_DEBUG_DAEMON_STDIO === "1";
2708
- await spawnDetachedDaemonProcess(
2718
+ return spawnDetachedDaemonProcess(
2709
2719
  runtime.nodeExecutable,
2710
2720
  getRuntimeCommandArgs(runtime, mode, options),
2711
2721
  {
@@ -2717,6 +2727,70 @@ var spawnInternalDaemon = async (launchConfig, mode, preparedRuntime, options =
2717
2727
  }
2718
2728
  );
2719
2729
  };
2730
+ var waitForProcessExit = async (pid, timeoutMs) => {
2731
+ const deadline = Date.now() + timeoutMs;
2732
+ while (Date.now() < deadline) {
2733
+ if (!isProcessAlive2(pid)) return true;
2734
+ await wait2(50);
2735
+ }
2736
+ return !isProcessAlive2(pid);
2737
+ };
2738
+ var signalProcess = (pid, signal) => {
2739
+ try {
2740
+ process.kill(pid, signal);
2741
+ return true;
2742
+ } catch (error) {
2743
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ESRCH") {
2744
+ return false;
2745
+ }
2746
+ throw error;
2747
+ }
2748
+ };
2749
+ var stopUnresponsiveDaemonProcess = async (pid) => {
2750
+ if (!isProcessAlive2(pid)) return;
2751
+ if (!signalProcess(pid, "SIGTERM")) return;
2752
+ if (await waitForProcessExit(pid, 2e3)) return;
2753
+ if (!signalProcess(pid, "SIGKILL")) return;
2754
+ if (await waitForProcessExit(pid, 3e3)) return;
2755
+ throw new Error(`Timed-out Connector daemon PID ${pid} could not be stopped safely`);
2756
+ };
2757
+ var readDaemonLockOwnerPid = async (config) => {
2758
+ const lockPath = `${getConnectorDaemonLockTarget(
2759
+ config.serverUrl,
2760
+ config.connectorId,
2761
+ getConfigRoot()
2762
+ )}.lock`;
2763
+ const raw = await readFile6(lockPath, "utf8").catch(() => "");
2764
+ try {
2765
+ const metadata = JSON.parse(raw);
2766
+ const age = typeof metadata.createdAt === "number" && Number.isFinite(metadata.createdAt) ? Date.now() - metadata.createdAt : Number.POSITIVE_INFINITY;
2767
+ return typeof metadata.pid === "number" && Number.isSafeInteger(metadata.pid) && metadata.pid > 0 && age >= 0 && age <= LOCK_ONLY_RECOVERY_MAX_AGE_MS ? metadata.pid : null;
2768
+ } catch {
2769
+ return null;
2770
+ }
2771
+ };
2772
+ async function startDaemonWithRecovery(dependencies, maxAttempts = 2) {
2773
+ let lastError;
2774
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
2775
+ const spawned = await dependencies.spawnDaemon();
2776
+ try {
2777
+ return await dependencies.waitForReady();
2778
+ } catch (error) {
2779
+ lastError = error;
2780
+ const lateReady = await dependencies.readReady().catch(() => null);
2781
+ if (lateReady) return lateReady;
2782
+ await dependencies.stopDaemon(spawned.pid);
2783
+ if (attempt === maxAttempts) throw error;
2784
+ }
2785
+ }
2786
+ throw lastError;
2787
+ }
2788
+ var startInternalDaemonWithRecovery = (launchConfig, mode, preparedRuntime, options = {}) => startDaemonWithRecovery({
2789
+ readReady: () => readReadyDaemon(launchConfig),
2790
+ spawnDaemon: () => spawnInternalDaemon(launchConfig, mode, preparedRuntime, options),
2791
+ stopDaemon: stopUnresponsiveDaemonProcess,
2792
+ waitForReady: () => waitForDaemon(launchConfig, Date.now() + START_TIMEOUT_MS)
2793
+ });
2720
2794
  var stopOlderIdleDaemon = async (config, status) => {
2721
2795
  if (!shouldReplaceDaemonVersion(status.version, CONNECTOR_RUNTIME_VERSION)) return false;
2722
2796
  if (!status.idle) return false;
@@ -2749,8 +2823,7 @@ async function ensureConnectorDaemon(launchConfig, mode, options = {}) {
2749
2823
  const status = mode === "persistent" ? await requestDaemonControl(existingConfig, "persist", getConfigRoot()) : existingStatus;
2750
2824
  return { config: existingConfig, status };
2751
2825
  }
2752
- await spawnInternalDaemon(launchConfig, mode, preparedRuntime, options);
2753
- return await waitForDaemon(launchConfig, Date.now() + START_TIMEOUT_MS);
2826
+ return await startInternalDaemonWithRecovery(launchConfig, mode, preparedRuntime, options);
2754
2827
  }
2755
2828
  const persistedStatus = await readPersistedDaemonStatus(
2756
2829
  existingConfig,
@@ -2761,9 +2834,12 @@ async function ensureConnectorDaemon(launchConfig, mode, options = {}) {
2761
2834
  `Connector daemon PID ${persistedStatus.pid} is running but its local control channel is not responding. Stop that process and retry.`
2762
2835
  );
2763
2836
  }
2837
+ const lockOwnerPid = await readDaemonLockOwnerPid(existingConfig);
2838
+ if (lockOwnerPid && isProcessAlive2(lockOwnerPid)) {
2839
+ await stopUnresponsiveDaemonProcess(lockOwnerPid);
2840
+ }
2764
2841
  }
2765
- await spawnInternalDaemon(launchConfig, mode, void 0, options);
2766
- return await waitForDaemon(launchConfig, Date.now() + START_TIMEOUT_MS);
2842
+ return await startInternalDaemonWithRecovery(launchConfig, mode, void 0, options);
2767
2843
  } finally {
2768
2844
  await bootstrapLock.release();
2769
2845
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buda-ai/connector",
3
- "version": "0.1.24",
3
+ "version": "0.1.26",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -27,7 +27,7 @@
27
27
  "lint:err": "biome format . --write && biome check . --write --diagnostic-level=error"
28
28
  },
29
29
  "dependencies": {
30
- "@bunny-agent/daemon": "0.9.55",
30
+ "@bunny-agent/daemon": "0.9.56",
31
31
  "@clack/prompts": "^1.0.0",
32
32
  "playwright-core": "^1.52.0",
33
33
  "ws": "^8.21.0"