@buda-ai/connector 0.1.23 → 0.1.25
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 +100 -18
- package/package.json +1 -1
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(() =>
|
|
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
|
-
|
|
39
|
-
|
|
38
|
+
try {
|
|
39
|
+
await handle.writeFile(
|
|
40
|
+
`${JSON.stringify({ pid: process.pid, ownerToken, createdAt: Date.now() })}
|
|
40
41
|
`
|
|
41
|
-
|
|
42
|
-
|
|
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
|
}
|
|
@@ -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.
|
|
526
|
+
version: "0.1.25",
|
|
521
527
|
private: false,
|
|
522
528
|
type: "module",
|
|
523
529
|
publishConfig: {
|
|
@@ -2595,6 +2601,7 @@ async function ensureStableConnectorRuntime() {
|
|
|
2595
2601
|
// src/daemon-client.ts
|
|
2596
2602
|
var START_TIMEOUT_MS = 45e3;
|
|
2597
2603
|
var BOOTSTRAP_TIMEOUT_MS = 5 * 6e4;
|
|
2604
|
+
var LOCK_ONLY_RECOVERY_MAX_AGE_MS = 5 * 6e4;
|
|
2598
2605
|
var POLL_INTERVAL_MS = 250;
|
|
2599
2606
|
var wait2 = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
2600
2607
|
var getConfigRoot = () => process.env.BUDA_CONNECTOR_CONFIG_ROOT;
|
|
@@ -2662,8 +2669,13 @@ async function spawnDetachedDaemonProcess(command, args, options, dependencies =
|
|
|
2662
2669
|
});
|
|
2663
2670
|
try {
|
|
2664
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
|
+
}
|
|
2665
2677
|
child.unref();
|
|
2666
|
-
return strategy.name;
|
|
2678
|
+
return { pid: child.pid, strategy: strategy.name };
|
|
2667
2679
|
} catch (error) {
|
|
2668
2680
|
const code = getErrorCode(error);
|
|
2669
2681
|
const shouldRetry = platform4 === "win32" && index === 0 && code === "EINVAL";
|
|
@@ -2682,14 +2694,18 @@ async function spawnDetachedDaemonProcess(command, args, options, dependencies =
|
|
|
2682
2694
|
}
|
|
2683
2695
|
throw new Error("Connector daemon process creation failed without a spawn attempt");
|
|
2684
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
|
+
};
|
|
2685
2703
|
var waitForDaemon = async (launchConfig, deadline) => {
|
|
2686
2704
|
let lastConfig = null;
|
|
2687
2705
|
while (Date.now() < deadline) {
|
|
2706
|
+
const ready = await readReadyDaemon(launchConfig);
|
|
2707
|
+
if (ready) return ready;
|
|
2688
2708
|
lastConfig = await readDeviceConfig(launchConfig, getConfigRoot());
|
|
2689
|
-
if (lastConfig) {
|
|
2690
|
-
const status = await readLiveStatus(lastConfig);
|
|
2691
|
-
if (status) return { config: lastConfig, status };
|
|
2692
|
-
}
|
|
2693
2709
|
await wait2(POLL_INTERVAL_MS);
|
|
2694
2710
|
}
|
|
2695
2711
|
throw new Error(
|
|
@@ -2699,7 +2715,7 @@ var waitForDaemon = async (launchConfig, deadline) => {
|
|
|
2699
2715
|
var spawnInternalDaemon = async (launchConfig, mode, preparedRuntime, options = {}) => {
|
|
2700
2716
|
const runtime = preparedRuntime ?? await ensureStableConnectorRuntime();
|
|
2701
2717
|
const debugDaemonStdio = process.env.BUDA_CONNECTOR_DEBUG_DAEMON_STDIO === "1";
|
|
2702
|
-
|
|
2718
|
+
return spawnDetachedDaemonProcess(
|
|
2703
2719
|
runtime.nodeExecutable,
|
|
2704
2720
|
getRuntimeCommandArgs(runtime, mode, options),
|
|
2705
2721
|
{
|
|
@@ -2711,6 +2727,70 @@ var spawnInternalDaemon = async (launchConfig, mode, preparedRuntime, options =
|
|
|
2711
2727
|
}
|
|
2712
2728
|
);
|
|
2713
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
|
+
});
|
|
2714
2794
|
var stopOlderIdleDaemon = async (config, status) => {
|
|
2715
2795
|
if (!shouldReplaceDaemonVersion(status.version, CONNECTOR_RUNTIME_VERSION)) return false;
|
|
2716
2796
|
if (!status.idle) return false;
|
|
@@ -2743,8 +2823,7 @@ async function ensureConnectorDaemon(launchConfig, mode, options = {}) {
|
|
|
2743
2823
|
const status = mode === "persistent" ? await requestDaemonControl(existingConfig, "persist", getConfigRoot()) : existingStatus;
|
|
2744
2824
|
return { config: existingConfig, status };
|
|
2745
2825
|
}
|
|
2746
|
-
await
|
|
2747
|
-
return await waitForDaemon(launchConfig, Date.now() + START_TIMEOUT_MS);
|
|
2826
|
+
return await startInternalDaemonWithRecovery(launchConfig, mode, preparedRuntime, options);
|
|
2748
2827
|
}
|
|
2749
2828
|
const persistedStatus = await readPersistedDaemonStatus(
|
|
2750
2829
|
existingConfig,
|
|
@@ -2755,9 +2834,12 @@ async function ensureConnectorDaemon(launchConfig, mode, options = {}) {
|
|
|
2755
2834
|
`Connector daemon PID ${persistedStatus.pid} is running but its local control channel is not responding. Stop that process and retry.`
|
|
2756
2835
|
);
|
|
2757
2836
|
}
|
|
2837
|
+
const lockOwnerPid = await readDaemonLockOwnerPid(existingConfig);
|
|
2838
|
+
if (lockOwnerPid && isProcessAlive2(lockOwnerPid)) {
|
|
2839
|
+
await stopUnresponsiveDaemonProcess(lockOwnerPid);
|
|
2840
|
+
}
|
|
2758
2841
|
}
|
|
2759
|
-
await
|
|
2760
|
-
return await waitForDaemon(launchConfig, Date.now() + START_TIMEOUT_MS);
|
|
2842
|
+
return await startInternalDaemonWithRecovery(launchConfig, mode, void 0, options);
|
|
2761
2843
|
} finally {
|
|
2762
2844
|
await bootstrapLock.release();
|
|
2763
2845
|
}
|