@buda-ai/connector 0.1.30 → 0.1.31
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/agent-templates/shared/AGENTS.md +1 -1
- package/dist/cli.js +103 -412
- package/package.json +2 -3
|
@@ -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`, `
|
|
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
|
-
|
|
29
|
-
|
|
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
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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.
|
|
520
|
+
version: "0.1.31",
|
|
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.
|
|
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
|
|
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,32 @@ 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 { access, cp, mkdir as mkdir5, readdir as readdir4, readFile as readFile5, rename as rename4, rm, writeFile as writeFile3 } from "fs/promises";
|
|
2428
2409
|
import { homedir as homedir3 } from "os";
|
|
2429
2410
|
import path6 from "path";
|
|
2430
2411
|
import { promisify } from "util";
|
|
2431
2412
|
var execFileAsync = promisify(execFile);
|
|
2432
2413
|
var RUNTIME_INSTALL_TIMEOUT_MS = 5 * 6e4;
|
|
2433
|
-
var
|
|
2434
|
-
var
|
|
2435
|
-
const
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
};
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
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")
|
|
2468
|
-
];
|
|
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 };
|
|
2473
|
-
}
|
|
2474
|
-
}
|
|
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 ?? "")})`
|
|
2414
|
+
var MACOS_NODE_LIBRARY_PATTERN = /^libnode(?:\.\d+)*\.dylib$/;
|
|
2415
|
+
var copyNodeRuntime = async (sourceNode, destinationNode, options = {}) => {
|
|
2416
|
+
const sourceDirectory = path6.dirname(sourceNode);
|
|
2417
|
+
const destinationDirectory = path6.dirname(destinationNode);
|
|
2418
|
+
await mkdir5(destinationDirectory, { recursive: true });
|
|
2419
|
+
await cp(sourceNode, destinationNode, { dereference: true, force: true });
|
|
2420
|
+
if ((options.platform ?? process.platform) !== "darwin") return;
|
|
2421
|
+
const companions = await readdir4(sourceDirectory, { withFileTypes: true });
|
|
2422
|
+
await Promise.all(
|
|
2423
|
+
companions.filter(
|
|
2424
|
+
(entry) => (entry.isFile() || entry.isSymbolicLink()) && MACOS_NODE_LIBRARY_PATTERN.test(entry.name)
|
|
2425
|
+
).map(
|
|
2426
|
+
(entry) => cp(path6.join(sourceDirectory, entry.name), path6.join(destinationDirectory, entry.name), {
|
|
2427
|
+
dereference: true,
|
|
2428
|
+
force: true
|
|
2429
|
+
})
|
|
2430
|
+
)
|
|
2477
2431
|
);
|
|
2478
2432
|
};
|
|
2433
|
+
var exists = async (filePath) => access(filePath).then(() => true).catch(() => false);
|
|
2479
2434
|
var runtimeRoot = () => process.env.BUDA_CONNECTOR_RUNTIME_ROOT || path6.join(homedir3(), ".buda", "runtime");
|
|
2480
2435
|
var runtimePlatformKey = `${process.platform}-${process.arch}-node${process.versions.modules}`;
|
|
2481
2436
|
var versionDirectory = () => path6.join(runtimeRoot(), runtimePlatformKey, CONNECTOR_RUNTIME_VERSION);
|
|
@@ -2509,11 +2464,9 @@ var validateRuntime = async (runtime) => {
|
|
|
2509
2464
|
};
|
|
2510
2465
|
var installFromNpm = async (directory) => {
|
|
2511
2466
|
await mkdir5(directory, { recursive: true, mode: 448 });
|
|
2512
|
-
const npm = await resolveNpmInvocation();
|
|
2513
2467
|
await execFileAsync(
|
|
2514
|
-
npm.
|
|
2468
|
+
process.platform === "win32" ? "npm.cmd" : "npm",
|
|
2515
2469
|
[
|
|
2516
|
-
...npm.argsPrefix,
|
|
2517
2470
|
"install",
|
|
2518
2471
|
"--ignore-scripts=false",
|
|
2519
2472
|
"--no-audit",
|
|
@@ -2534,8 +2487,7 @@ var installFromNpm = async (directory) => {
|
|
|
2534
2487
|
"cli.js"
|
|
2535
2488
|
);
|
|
2536
2489
|
const nodeExecutable = path6.join(directory, "node", path6.basename(process.execPath));
|
|
2537
|
-
await
|
|
2538
|
-
await cp(process.execPath, nodeExecutable, { force: true });
|
|
2490
|
+
await copyNodeRuntime(process.execPath, nodeExecutable);
|
|
2539
2491
|
if (!await exists(entrypoint)) throw new Error("Installed Connector runtime is incomplete");
|
|
2540
2492
|
return { entrypoint, nodeExecutable, version: CONNECTOR_RUNTIME_VERSION };
|
|
2541
2493
|
};
|
|
@@ -2548,12 +2500,11 @@ var installFromBundledRuntime = async (directory, sourceRoot) => {
|
|
|
2548
2500
|
if (relativeEntrypoint.startsWith("..") || path6.isAbsolute(relativeEntrypoint)) {
|
|
2549
2501
|
throw new Error("Bundled Connector entrypoint must be inside its runtime root");
|
|
2550
2502
|
}
|
|
2551
|
-
const
|
|
2503
|
+
const relativeNode = path6.relative(sourceRoot, sourceNode);
|
|
2552
2504
|
const entrypoint = path6.join(directory, relativeEntrypoint);
|
|
2553
|
-
const nodeExecutable =
|
|
2554
|
-
if (
|
|
2555
|
-
await
|
|
2556
|
-
await cp(sourceNode, nodeExecutable, { force: true });
|
|
2505
|
+
const nodeExecutable = relativeNode.startsWith("..") ? path6.join(directory, "node", path6.basename(sourceNode)) : path6.join(directory, relativeNode);
|
|
2506
|
+
if (relativeNode.startsWith("..")) {
|
|
2507
|
+
await copyNodeRuntime(sourceNode, nodeExecutable);
|
|
2557
2508
|
}
|
|
2558
2509
|
if (!await exists(entrypoint))
|
|
2559
2510
|
throw new Error("Copied Connector runtime entrypoint is missing");
|
|
@@ -2599,7 +2550,7 @@ async function ensureStableConnectorRuntime() {
|
|
|
2599
2550
|
};
|
|
2600
2551
|
await writeManifest(temporary, rebased);
|
|
2601
2552
|
await rm(finalDirectory, { recursive: true, force: true });
|
|
2602
|
-
await
|
|
2553
|
+
await rename4(temporary, finalDirectory);
|
|
2603
2554
|
return rebased;
|
|
2604
2555
|
} catch (error) {
|
|
2605
2556
|
await rm(temporary, { recursive: true, force: true });
|
|
@@ -2613,8 +2564,6 @@ async function ensureStableConnectorRuntime() {
|
|
|
2613
2564
|
// src/daemon-client.ts
|
|
2614
2565
|
var START_TIMEOUT_MS = 45e3;
|
|
2615
2566
|
var BOOTSTRAP_TIMEOUT_MS = 5 * 6e4;
|
|
2616
|
-
var GRACEFUL_STOP_TIMEOUT_MS = 3e3;
|
|
2617
|
-
var LOCK_ONLY_RECOVERY_MAX_AGE_MS = 5 * 6e4;
|
|
2618
2567
|
var POLL_INTERVAL_MS = 250;
|
|
2619
2568
|
var wait2 = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
2620
2569
|
var getConfigRoot = () => process.env.BUDA_CONNECTOR_CONFIG_ROOT;
|
|
@@ -2647,78 +2596,14 @@ var compareVersions = (left, right) => {
|
|
|
2647
2596
|
return 0;
|
|
2648
2597
|
};
|
|
2649
2598
|
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
2599
|
var waitForDaemon = async (launchConfig, deadline) => {
|
|
2717
2600
|
let lastConfig = null;
|
|
2718
2601
|
while (Date.now() < deadline) {
|
|
2719
|
-
const ready = await readReadyDaemon(launchConfig);
|
|
2720
|
-
if (ready) return ready;
|
|
2721
2602
|
lastConfig = await readDeviceConfig(launchConfig, getConfigRoot());
|
|
2603
|
+
if (lastConfig) {
|
|
2604
|
+
const status = await readLiveStatus(lastConfig);
|
|
2605
|
+
if (status) return { config: lastConfig, status };
|
|
2606
|
+
}
|
|
2722
2607
|
await wait2(POLL_INTERVAL_MS);
|
|
2723
2608
|
}
|
|
2724
2609
|
throw new Error(
|
|
@@ -2727,126 +2612,17 @@ var waitForDaemon = async (launchConfig, deadline) => {
|
|
|
2727
2612
|
};
|
|
2728
2613
|
var spawnInternalDaemon = async (launchConfig, mode, preparedRuntime, options = {}) => {
|
|
2729
2614
|
const runtime = preparedRuntime ?? await ensureStableConnectorRuntime();
|
|
2730
|
-
const
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
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
|
-
}
|
|
2615
|
+
const child = spawn(runtime.nodeExecutable, getRuntimeCommandArgs(runtime, mode, options), {
|
|
2616
|
+
detached: true,
|
|
2617
|
+
env: {
|
|
2618
|
+
...process.env,
|
|
2619
|
+
BUDA_CONNECTOR_INTERNAL_LAUNCH_CONFIG: JSON.stringify(launchConfig ?? {})
|
|
2620
|
+
},
|
|
2621
|
+
stdio: "ignore",
|
|
2622
|
+
windowsHide: true
|
|
2623
|
+
});
|
|
2624
|
+
child.unref();
|
|
2827
2625
|
};
|
|
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
2626
|
var stopOlderIdleDaemon = async (config, status) => {
|
|
2851
2627
|
if (!shouldReplaceDaemonVersion(status.version, CONNECTOR_RUNTIME_VERSION)) return false;
|
|
2852
2628
|
if (!status.idle) return false;
|
|
@@ -2873,19 +2649,14 @@ async function ensureConnectorDaemon(launchConfig, mode, options = {}) {
|
|
|
2873
2649
|
if (existingConfig) {
|
|
2874
2650
|
const existingStatus = await readLiveStatus(existingConfig);
|
|
2875
2651
|
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
2652
|
const preparedRuntime = !shouldReplaceDaemonVersion(existingStatus.version, CONNECTOR_RUNTIME_VERSION) || !existingStatus.idle ? void 0 : await ensureStableConnectorRuntime();
|
|
2883
2653
|
const replaced = await stopOlderIdleDaemon(existingConfig, existingStatus);
|
|
2884
2654
|
if (!replaced) {
|
|
2885
2655
|
const status = mode === "persistent" ? await requestDaemonControl(existingConfig, "persist", getConfigRoot()) : existingStatus;
|
|
2886
2656
|
return { config: existingConfig, status };
|
|
2887
2657
|
}
|
|
2888
|
-
|
|
2658
|
+
await spawnInternalDaemon(launchConfig, mode, preparedRuntime, options);
|
|
2659
|
+
return await waitForDaemon(launchConfig, Date.now() + START_TIMEOUT_MS);
|
|
2889
2660
|
}
|
|
2890
2661
|
const persistedStatus = await readPersistedDaemonStatus(
|
|
2891
2662
|
existingConfig,
|
|
@@ -2896,12 +2667,9 @@ async function ensureConnectorDaemon(launchConfig, mode, options = {}) {
|
|
|
2896
2667
|
`Connector daemon PID ${persistedStatus.pid} is running but its local control channel is not responding. Stop that process and retry.`
|
|
2897
2668
|
);
|
|
2898
2669
|
}
|
|
2899
|
-
const lockOwnerPid = await readDaemonLockOwnerPid(existingConfig);
|
|
2900
|
-
if (lockOwnerPid && isProcessAlive2(lockOwnerPid)) {
|
|
2901
|
-
await stopUnresponsiveDaemonProcess(lockOwnerPid);
|
|
2902
|
-
}
|
|
2903
2670
|
}
|
|
2904
|
-
|
|
2671
|
+
await spawnInternalDaemon(launchConfig, mode, void 0, options);
|
|
2672
|
+
return await waitForDaemon(launchConfig, Date.now() + START_TIMEOUT_MS);
|
|
2905
2673
|
} finally {
|
|
2906
2674
|
await bootstrapLock.release();
|
|
2907
2675
|
}
|
|
@@ -2921,7 +2689,6 @@ var streamDaemonLogs = (options) => {
|
|
|
2921
2689
|
for (const line of lines.slice(seenLines)) {
|
|
2922
2690
|
try {
|
|
2923
2691
|
const entry = JSON.parse(line);
|
|
2924
|
-
if (!shouldDisplayConnectorLogEntry(entry.createdAt, options.startedAt)) continue;
|
|
2925
2692
|
const connectorId = entry.metadata?.connectorId;
|
|
2926
2693
|
if (options.dashboardActive && typeof connectorId === "string") {
|
|
2927
2694
|
const output2 = entry.metadata?.source === "daemon-console" ? entry.message : `[buda-connector] ${entry.message}`;
|
|
@@ -2997,7 +2764,9 @@ async function stopLocalConnectorDaemon(connectorId, serverUrl, configRoot = get
|
|
|
2997
2764
|
(candidate) => candidate.connectorId === connectorId && normalizeServerOrigin(candidate.serverUrl) === serverOrigin
|
|
2998
2765
|
);
|
|
2999
2766
|
if (!config) throw new Error(`Local Connector ${connectorId} was not found`);
|
|
3000
|
-
await
|
|
2767
|
+
const status = await readLiveStatus(config, configRoot);
|
|
2768
|
+
if (!status) return;
|
|
2769
|
+
await requestDaemonControl(config, "stop", configRoot);
|
|
3001
2770
|
}
|
|
3002
2771
|
async function stopAllLocalConnectorDaemons(configRoot = getConfigRoot()) {
|
|
3003
2772
|
const configs = await listDeviceConfigs(configRoot);
|
|
@@ -3008,7 +2777,10 @@ async function stopAllLocalConnectorDaemons(configRoot = getConfigRoot()) {
|
|
|
3008
2777
|
${config.connectorId}`, config);
|
|
3009
2778
|
}
|
|
3010
2779
|
const results = await Promise.allSettled(
|
|
3011
|
-
[...identities.values()].map((config) =>
|
|
2780
|
+
[...identities.values()].map(async (config) => {
|
|
2781
|
+
const status = await readLiveStatus(config, configRoot);
|
|
2782
|
+
if (status) await requestDaemonControl(config, "stop", configRoot);
|
|
2783
|
+
})
|
|
3012
2784
|
);
|
|
3013
2785
|
const failures = results.filter((result) => result.status === "rejected");
|
|
3014
2786
|
if (failures.length) {
|
|
@@ -3027,7 +2799,6 @@ var waitForConnectorViewerExit = async (dashboardClosed) => {
|
|
|
3027
2799
|
process.off("SIGTERM", finishSignal);
|
|
3028
2800
|
};
|
|
3029
2801
|
async function runConnectorDashboard(launchConfig, options = {}) {
|
|
3030
|
-
const startedAt = Date.now();
|
|
3031
2802
|
let finishDashboard = () => void 0;
|
|
3032
2803
|
const dashboardClosed = new Promise((resolve) => {
|
|
3033
2804
|
finishDashboard = resolve;
|
|
@@ -3048,8 +2819,7 @@ async function runConnectorDashboard(launchConfig, options = {}) {
|
|
|
3048
2819
|
connectorId: config.connectorId,
|
|
3049
2820
|
dashboardActive: false,
|
|
3050
2821
|
plain: Boolean(options.plain),
|
|
3051
|
-
serverUrl: config.serverUrl
|
|
3052
|
-
startedAt
|
|
2822
|
+
serverUrl: config.serverUrl
|
|
3053
2823
|
});
|
|
3054
2824
|
if (!process.stdin.isTTY) {
|
|
3055
2825
|
stopLogs2();
|
|
@@ -3076,8 +2846,7 @@ async function runConnectorDashboard(launchConfig, options = {}) {
|
|
|
3076
2846
|
const stopLogs = streamDaemonLogs({
|
|
3077
2847
|
dashboardActive: true,
|
|
3078
2848
|
plain: false,
|
|
3079
|
-
serverUrl: getServerUrl(launchConfig)
|
|
3080
|
-
startedAt
|
|
2849
|
+
serverUrl: getServerUrl(launchConfig)
|
|
3081
2850
|
});
|
|
3082
2851
|
let refreshing = false;
|
|
3083
2852
|
const refresh = async () => {
|
|
@@ -3622,7 +3391,7 @@ import {
|
|
|
3622
3391
|
lstat,
|
|
3623
3392
|
mkdir as mkdir8,
|
|
3624
3393
|
open as open2,
|
|
3625
|
-
readdir as
|
|
3394
|
+
readdir as readdir5,
|
|
3626
3395
|
readFile as readFile7,
|
|
3627
3396
|
readlink,
|
|
3628
3397
|
realpath,
|
|
@@ -3807,7 +3576,7 @@ var createJournal = async (storageRoot, migrationDirectory, owner) => {
|
|
|
3807
3576
|
};
|
|
3808
3577
|
};
|
|
3809
3578
|
var inventoryLegacySources = async (storageRoot) => {
|
|
3810
|
-
const rootEntries = await
|
|
3579
|
+
const rootEntries = await readdir5(storageRoot, { withFileTypes: true });
|
|
3811
3580
|
const templateNames = new Set(LEGACY_TEMPLATE_FILES);
|
|
3812
3581
|
for (const entry of rootEntries) {
|
|
3813
3582
|
if (entry.name.endsWith(".md")) templateNames.add(entry.name);
|
|
@@ -3837,7 +3606,7 @@ var inventoryLegacySources = async (storageRoot) => {
|
|
|
3837
3606
|
`Legacy migration source ${candidate.id} must not be a symbolic link`
|
|
3838
3607
|
);
|
|
3839
3608
|
}
|
|
3840
|
-
if (info.isDirectory() && (await
|
|
3609
|
+
if (info.isDirectory() && (await readdir5(candidate.source)).length === 0) continue;
|
|
3841
3610
|
found.push(candidate);
|
|
3842
3611
|
}
|
|
3843
3612
|
return found;
|
|
@@ -3857,7 +3626,7 @@ var copyTree = async (source, destination, onEntryCopied) => {
|
|
|
3857
3626
|
const info = await lstat(source);
|
|
3858
3627
|
if (info.isDirectory()) {
|
|
3859
3628
|
await mkdir8(destination, { mode: info.mode & 4095 });
|
|
3860
|
-
const entries = (await
|
|
3629
|
+
const entries = (await readdir5(source)).sort();
|
|
3861
3630
|
for (const entry of entries) {
|
|
3862
3631
|
await copyTree(path9.join(source, entry), path9.join(destination, entry), onEntryCopied);
|
|
3863
3632
|
}
|
|
@@ -3894,8 +3663,8 @@ var verifyTree = async (source, destination) => {
|
|
|
3894
3663
|
}
|
|
3895
3664
|
if (sourceInfo.isDirectory()) {
|
|
3896
3665
|
const [sourceEntries, destinationEntries] = await Promise.all([
|
|
3897
|
-
|
|
3898
|
-
|
|
3666
|
+
readdir5(source),
|
|
3667
|
+
readdir5(destination)
|
|
3899
3668
|
]);
|
|
3900
3669
|
sourceEntries.sort();
|
|
3901
3670
|
destinationEntries.sort();
|
|
@@ -3973,7 +3742,6 @@ var writeJournal = async (journalPath, journal) => {
|
|
|
3973
3742
|
}
|
|
3974
3743
|
};
|
|
3975
3744
|
var syncDirectory2 = async (directory) => {
|
|
3976
|
-
if (process.platform === "win32") return;
|
|
3977
3745
|
const handle = await open2(directory, constants2.O_RDONLY);
|
|
3978
3746
|
try {
|
|
3979
3747
|
await handle.sync();
|
|
@@ -4539,7 +4307,7 @@ import {
|
|
|
4539
4307
|
copyFile as copyFile2,
|
|
4540
4308
|
lstat as lstat2,
|
|
4541
4309
|
mkdir as mkdir9,
|
|
4542
|
-
readdir as
|
|
4310
|
+
readdir as readdir6,
|
|
4543
4311
|
readFile as readFile9,
|
|
4544
4312
|
realpath as realpath3,
|
|
4545
4313
|
rm as rm3,
|
|
@@ -4547,11 +4315,6 @@ import {
|
|
|
4547
4315
|
writeFile as writeFile4
|
|
4548
4316
|
} from "fs/promises";
|
|
4549
4317
|
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
4318
|
var MOUNT_DIRECTORY = "mount";
|
|
4556
4319
|
var ARCHIVE_MARKER = "archived.json";
|
|
4557
4320
|
var AGENT_CONTEXT_TEMPLATE_FILES = [
|
|
@@ -4649,7 +4412,7 @@ var WorkspaceVolumeLifecycle = class {
|
|
|
4649
4412
|
const bootstrapped = [];
|
|
4650
4413
|
const skipped = [];
|
|
4651
4414
|
const entries = new Set(
|
|
4652
|
-
(await
|
|
4415
|
+
(await readdir6(templateDirectory, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isFile()).map((entry) => entry.name)
|
|
4653
4416
|
);
|
|
4654
4417
|
for (const filename of AGENT_CONTEXT_TEMPLATE_FILES) {
|
|
4655
4418
|
if (!entries.has(filename)) continue;
|
|
@@ -4703,7 +4466,7 @@ var ensureVolumeLink = async (target, linkPath) => {
|
|
|
4703
4466
|
} catch (error) {
|
|
4704
4467
|
if (!isMissingFile(error)) throw error;
|
|
4705
4468
|
try {
|
|
4706
|
-
await symlink2(target, linkPath,
|
|
4469
|
+
await symlink2(target, linkPath, "dir");
|
|
4707
4470
|
} catch (createError) {
|
|
4708
4471
|
if (!isAlreadyExists3(createError)) throw createError;
|
|
4709
4472
|
const info = await lstat2(linkPath);
|
|
@@ -4718,7 +4481,6 @@ var isAlreadyExists3 = (error) => error instanceof Error && "code" in error && e
|
|
|
4718
4481
|
// src/relay.ts
|
|
4719
4482
|
var RELAY_PING_INTERVAL_MS = 2e4;
|
|
4720
4483
|
var RELAY_PONG_TIMEOUT_MS = 1e4;
|
|
4721
|
-
var RELAY_ACTIVE_REQUEST_PONG_TIMEOUT_LIMIT = 2;
|
|
4722
4484
|
var RELAY_HANDSHAKE_TIMEOUT_MS = 1e4;
|
|
4723
4485
|
var RELAY_RECONNECT_MAX_MS = 3e4;
|
|
4724
4486
|
var RELAY_RECONNECT_JITTER = 0.2;
|
|
@@ -4824,7 +4586,6 @@ function startConnectorRelay(config, bunnyDaemon, relayProtocol, workspaceRegist
|
|
|
4824
4586
|
socket = ws;
|
|
4825
4587
|
const inflight = /* @__PURE__ */ new Map();
|
|
4826
4588
|
currentInflight = inflight;
|
|
4827
|
-
let consecutivePongTimeouts = 0;
|
|
4828
4589
|
let detached = false;
|
|
4829
4590
|
const detach = () => {
|
|
4830
4591
|
if (detached) return;
|
|
@@ -4837,7 +4598,6 @@ function startConnectorRelay(config, bunnyDaemon, relayProtocol, workspaceRegist
|
|
|
4837
4598
|
void writeLog("info", `relay connected ${relayUrl}`);
|
|
4838
4599
|
hadOpenConnection = true;
|
|
4839
4600
|
reconnectDelayMs = RELAY_RECONNECT_INTERVAL_MS;
|
|
4840
|
-
consecutivePongTimeouts = 0;
|
|
4841
4601
|
pingTimer = setInterval(() => {
|
|
4842
4602
|
if (ws.readyState !== WebSocket2.OPEN) return;
|
|
4843
4603
|
try {
|
|
@@ -4849,33 +4609,18 @@ function startConnectorRelay(config, bunnyDaemon, relayProtocol, workspaceRegist
|
|
|
4849
4609
|
}
|
|
4850
4610
|
if (pongTimer) clearTimeout(pongTimer);
|
|
4851
4611
|
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
4612
|
void writeLog("warn", "relay pong timeout, terminating");
|
|
4862
4613
|
ws.terminate();
|
|
4863
4614
|
}, RELAY_PONG_TIMEOUT_MS);
|
|
4864
4615
|
}, RELAY_PING_INTERVAL_MS);
|
|
4865
4616
|
});
|
|
4866
4617
|
ws.on("pong", () => {
|
|
4867
|
-
consecutivePongTimeouts = 0;
|
|
4868
4618
|
if (pongTimer) {
|
|
4869
4619
|
clearTimeout(pongTimer);
|
|
4870
4620
|
pongTimer = null;
|
|
4871
4621
|
}
|
|
4872
4622
|
});
|
|
4873
4623
|
ws.on("message", (raw) => {
|
|
4874
|
-
consecutivePongTimeouts = 0;
|
|
4875
|
-
if (pongTimer) {
|
|
4876
|
-
clearTimeout(pongTimer);
|
|
4877
|
-
pongTimer = null;
|
|
4878
|
-
}
|
|
4879
4624
|
void handleRelayMessage(
|
|
4880
4625
|
ws,
|
|
4881
4626
|
bunnyDaemon,
|
|
@@ -5051,7 +4796,7 @@ async function handleRelayMessage(socket, bunnyDaemon, raw, inflight, expectedCo
|
|
|
5051
4796
|
});
|
|
5052
4797
|
executionIdentity = message.executionIdentity;
|
|
5053
4798
|
} catch (error) {
|
|
5054
|
-
|
|
4799
|
+
sendRelayError(socket, message.requestId, error);
|
|
5055
4800
|
return;
|
|
5056
4801
|
}
|
|
5057
4802
|
}
|
|
@@ -5137,7 +4882,7 @@ async function handleRelayMessage(socket, bunnyDaemon, raw, inflight, expectedCo
|
|
|
5137
4882
|
durationMs: Date.now() - codingRunStartedAt
|
|
5138
4883
|
});
|
|
5139
4884
|
}
|
|
5140
|
-
|
|
4885
|
+
sendRelayMessage(socket, {
|
|
5141
4886
|
type: "start",
|
|
5142
4887
|
requestId: message.requestId,
|
|
5143
4888
|
status: response.status,
|
|
@@ -5157,7 +4902,7 @@ async function handleRelayMessage(socket, bunnyDaemon, raw, inflight, expectedCo
|
|
|
5157
4902
|
while (true) {
|
|
5158
4903
|
const { value, done } = await reader.read();
|
|
5159
4904
|
if (done) break;
|
|
5160
|
-
|
|
4905
|
+
sendRelayMessage(socket, {
|
|
5161
4906
|
type: "chunk",
|
|
5162
4907
|
requestId: message.requestId,
|
|
5163
4908
|
data: binaryResponse ? Buffer.from(value).toString("base64") : decoder?.decode(value, { stream: true }) ?? "",
|
|
@@ -5166,7 +4911,7 @@ async function handleRelayMessage(socket, bunnyDaemon, raw, inflight, expectedCo
|
|
|
5166
4911
|
}
|
|
5167
4912
|
const tail = decoder?.decode() ?? "";
|
|
5168
4913
|
if (tail) {
|
|
5169
|
-
|
|
4914
|
+
sendRelayMessage(socket, {
|
|
5170
4915
|
type: "chunk",
|
|
5171
4916
|
requestId: message.requestId,
|
|
5172
4917
|
data: tail
|
|
@@ -5177,7 +4922,7 @@ async function handleRelayMessage(socket, bunnyDaemon, raw, inflight, expectedCo
|
|
|
5177
4922
|
reader.releaseLock();
|
|
5178
4923
|
}
|
|
5179
4924
|
}
|
|
5180
|
-
|
|
4925
|
+
sendRelayMessage(socket, {
|
|
5181
4926
|
type: "end",
|
|
5182
4927
|
requestId: message.requestId
|
|
5183
4928
|
});
|
|
@@ -5189,16 +4934,14 @@ async function handleRelayMessage(socket, bunnyDaemon, raw, inflight, expectedCo
|
|
|
5189
4934
|
});
|
|
5190
4935
|
}
|
|
5191
4936
|
} catch (error) {
|
|
5192
|
-
controller.abort();
|
|
5193
4937
|
if (codingRunTrace && codingRunStartedAt !== void 0) {
|
|
5194
4938
|
await writeLog("error", "relay request coding run error", {
|
|
5195
4939
|
...codingRunTrace,
|
|
5196
4940
|
status: codingRunStatus,
|
|
5197
|
-
durationMs: Date.now() - codingRunStartedAt
|
|
5198
|
-
error: formatConnectorRelayError(error)
|
|
4941
|
+
durationMs: Date.now() - codingRunStartedAt
|
|
5199
4942
|
});
|
|
5200
4943
|
}
|
|
5201
|
-
|
|
4944
|
+
sendRelayError(socket, message.requestId, error);
|
|
5202
4945
|
} finally {
|
|
5203
4946
|
inflight.delete(message.requestId);
|
|
5204
4947
|
}
|
|
@@ -5252,20 +4995,17 @@ var normalizeWorkspaceRequest = (request, mounts) => {
|
|
|
5252
4995
|
if (!mount || relative === void 0) {
|
|
5253
4996
|
throw new ConnectorRelayProtocolError("WORKSPACE_MISMATCH");
|
|
5254
4997
|
}
|
|
5255
|
-
const
|
|
5256
|
-
const workspaceEnv = {
|
|
5257
|
-
BUDA_AGENT_ROOT: mounts.agent,
|
|
5258
|
-
BUDA_SPACE_ROOT: mounts.space,
|
|
5259
|
-
BUDA_SYSTEM_ROOT: mounts.system
|
|
5260
|
-
};
|
|
4998
|
+
const env = body.env && typeof body.env === "object" && !Array.isArray(body.env) ? body.env : {};
|
|
5261
4999
|
return {
|
|
5262
5000
|
...request,
|
|
5263
5001
|
body: {
|
|
5264
5002
|
...body,
|
|
5265
5003
|
cwd: path12.join(mount, relative),
|
|
5266
|
-
|
|
5267
|
-
...
|
|
5268
|
-
|
|
5004
|
+
env: {
|
|
5005
|
+
...env,
|
|
5006
|
+
BUDA_AGENT_ROOT: mounts.agent,
|
|
5007
|
+
BUDA_SPACE_ROOT: mounts.space,
|
|
5008
|
+
BUDA_SYSTEM_ROOT: mounts.system
|
|
5269
5009
|
}
|
|
5270
5010
|
}
|
|
5271
5011
|
};
|
|
@@ -5297,24 +5037,17 @@ var normalizeConnectorFilesystemRequest = (request, mounts) => {
|
|
|
5297
5037
|
}
|
|
5298
5038
|
return request;
|
|
5299
5039
|
};
|
|
5300
|
-
|
|
5301
|
-
|
|
5040
|
+
function sendRelayError(socket, requestId, error) {
|
|
5041
|
+
sendRelayMessage(socket, {
|
|
5302
5042
|
type: "error",
|
|
5303
5043
|
requestId,
|
|
5304
5044
|
error: error instanceof Error ? error.message : String(error),
|
|
5305
5045
|
...error instanceof ConnectorRelayProtocolError ? { code: error.code } : {}
|
|
5306
5046
|
});
|
|
5307
5047
|
}
|
|
5308
|
-
|
|
5309
|
-
if (socket.readyState !== WebSocket2.OPEN)
|
|
5310
|
-
|
|
5311
|
-
}
|
|
5312
|
-
await new Promise((resolve, reject) => {
|
|
5313
|
-
socket.send(JSON.stringify(message), (error) => {
|
|
5314
|
-
if (error) reject(error);
|
|
5315
|
-
else resolve();
|
|
5316
|
-
});
|
|
5317
|
-
});
|
|
5048
|
+
function sendRelayMessage(socket, message) {
|
|
5049
|
+
if (socket.readyState !== WebSocket2.OPEN) return;
|
|
5050
|
+
socket.send(JSON.stringify(message));
|
|
5318
5051
|
}
|
|
5319
5052
|
function headersToRecord(headers) {
|
|
5320
5053
|
const result = {};
|
|
@@ -5373,7 +5106,7 @@ async function ensureSymlink(target, linkPath) {
|
|
|
5373
5106
|
} catch {
|
|
5374
5107
|
}
|
|
5375
5108
|
await mkdir10(path13.dirname(linkPath), { recursive: true });
|
|
5376
|
-
await symlink3(target, linkPath,
|
|
5109
|
+
await symlink3(target, linkPath, "dir");
|
|
5377
5110
|
}
|
|
5378
5111
|
|
|
5379
5112
|
// src/workspace-registry.ts
|
|
@@ -5830,30 +5563,7 @@ import { mkdir as mkdir12 } from "fs/promises";
|
|
|
5830
5563
|
import path15 from "path";
|
|
5831
5564
|
var RECOVERED_DAEMON_STOP_TIMEOUT_MS = 1e4;
|
|
5832
5565
|
var RECOVERED_DAEMON_STOP_POLL_MS = 100;
|
|
5833
|
-
var SHUTDOWN_FORCE_EXIT_MS = 1e4;
|
|
5834
|
-
var SHUTDOWN_STEP_TIMEOUT_MS = 2e3;
|
|
5835
5566
|
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
5567
|
async function stopRecoveredIdleDaemon(config, configRoot) {
|
|
5858
5568
|
const stopStatus = await requestDaemonControl(config, "stop-if-idle", configRoot).catch(
|
|
5859
5569
|
() => null
|
|
@@ -5973,30 +5683,18 @@ async function runConnectorDaemon(launchConfig, options = {}) {
|
|
|
5973
5683
|
const shutdown = async (shutdownOptions = {}) => {
|
|
5974
5684
|
if (shuttingDown) return;
|
|
5975
5685
|
shuttingDown = true;
|
|
5976
|
-
const forceExit = options.exitOnShutdown === false ? void 0 : setTimeout(() => process.exit(0), SHUTDOWN_FORCE_EXIT_MS);
|
|
5977
5686
|
if (interval) clearInterval(interval);
|
|
5978
5687
|
relay.stop();
|
|
5979
5688
|
try {
|
|
5980
|
-
await
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
() => unregisterConnector(config, launchConfig)
|
|
5986
|
-
)
|
|
5987
|
-
],
|
|
5988
|
-
runShutdownStep(
|
|
5989
|
-
"embedded Bunny daemon shutdown",
|
|
5990
|
-
() => stopEmbeddedBunnyDaemon(activeBunnyDaemon)
|
|
5991
|
-
)
|
|
5992
|
-
]);
|
|
5689
|
+
await closeAllBrowserSessions();
|
|
5690
|
+
if (shutdownOptions.unregister !== false) {
|
|
5691
|
+
await unregisterConnector(config, launchConfig);
|
|
5692
|
+
}
|
|
5693
|
+
await stopEmbeddedBunnyDaemon(activeBunnyDaemon);
|
|
5993
5694
|
} finally {
|
|
5994
|
-
await
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
runShutdownStep("console capture restore", async () => restoreConsole?.())
|
|
5998
|
-
]);
|
|
5999
|
-
if (forceExit) clearTimeout(forceExit);
|
|
5695
|
+
await control?.close();
|
|
5696
|
+
await daemonLock.release();
|
|
5697
|
+
await restoreConsole?.();
|
|
6000
5698
|
if (options.exitOnShutdown !== false) process.exit(0);
|
|
6001
5699
|
}
|
|
6002
5700
|
};
|
|
@@ -6011,11 +5709,8 @@ async function runConnectorDaemon(launchConfig, options = {}) {
|
|
|
6011
5709
|
});
|
|
6012
5710
|
}).catch((error) => {
|
|
6013
5711
|
heartbeatHealthy = false;
|
|
6014
|
-
if (error instanceof ConnectorDeletedError
|
|
6015
|
-
void writeLog(
|
|
6016
|
-
"warn",
|
|
6017
|
-
error instanceof ConnectorDeletedError ? "connector deleted remotely; stopping daemon" : "connector authentication rejected remotely; stopping daemon"
|
|
6018
|
-
);
|
|
5712
|
+
if (error instanceof ConnectorDeletedError) {
|
|
5713
|
+
void writeLog("warn", "connector deleted remotely; stopping daemon");
|
|
6019
5714
|
void shutdown({ unregister: false });
|
|
6020
5715
|
return;
|
|
6021
5716
|
}
|
|
@@ -6039,10 +5734,6 @@ async function runConnectorDaemon(launchConfig, options = {}) {
|
|
|
6039
5734
|
process.once("SIGINT", () => void shutdown());
|
|
6040
5735
|
process.once("SIGTERM", () => void shutdown());
|
|
6041
5736
|
} 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
5737
|
if (bunnyDaemon) await stopEmbeddedBunnyDaemon(bunnyDaemon);
|
|
6047
5738
|
await daemonLock.release();
|
|
6048
5739
|
await restoreConsole?.();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@buda-ai/connector",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.31",
|
|
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.
|
|
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"
|