@buda-ai/connector 0.1.29 → 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 +98 -393
- 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,107 +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 shouldRestartUnhealthyDaemonForEnrollment = (status, explicitEnrollment) => explicitEnrollment && status.idle && status.heartbeatHealthy === false;
|
|
2794
|
-
var readDaemonLockOwnerPid = async (config) => {
|
|
2795
|
-
const lockPath = `${getConnectorDaemonLockTarget(
|
|
2796
|
-
config.serverUrl,
|
|
2797
|
-
config.connectorId,
|
|
2798
|
-
getConfigRoot()
|
|
2799
|
-
)}.lock`;
|
|
2800
|
-
const raw = await readFile6(lockPath, "utf8").catch(() => "");
|
|
2801
|
-
try {
|
|
2802
|
-
const metadata = JSON.parse(raw);
|
|
2803
|
-
const age = typeof metadata.createdAt === "number" && Number.isFinite(metadata.createdAt) ? Date.now() - metadata.createdAt : Number.POSITIVE_INFINITY;
|
|
2804
|
-
return typeof metadata.pid === "number" && Number.isSafeInteger(metadata.pid) && metadata.pid > 0 && age >= 0 && age <= LOCK_ONLY_RECOVERY_MAX_AGE_MS ? metadata.pid : null;
|
|
2805
|
-
} catch {
|
|
2806
|
-
return null;
|
|
2807
|
-
}
|
|
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();
|
|
2808
2625
|
};
|
|
2809
|
-
async function startDaemonWithRecovery(dependencies, maxAttempts = 2) {
|
|
2810
|
-
let lastError;
|
|
2811
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
2812
|
-
const spawned = await dependencies.spawnDaemon();
|
|
2813
|
-
try {
|
|
2814
|
-
return await dependencies.waitForReady();
|
|
2815
|
-
} catch (error) {
|
|
2816
|
-
lastError = error;
|
|
2817
|
-
const lateReady = await dependencies.readReady().catch(() => null);
|
|
2818
|
-
if (lateReady) return lateReady;
|
|
2819
|
-
await dependencies.stopDaemon(spawned.pid);
|
|
2820
|
-
if (attempt === maxAttempts) throw error;
|
|
2821
|
-
}
|
|
2822
|
-
}
|
|
2823
|
-
throw lastError;
|
|
2824
|
-
}
|
|
2825
|
-
var startInternalDaemonWithRecovery = (launchConfig, mode, preparedRuntime, options = {}) => startDaemonWithRecovery({
|
|
2826
|
-
readReady: () => readReadyDaemon(launchConfig),
|
|
2827
|
-
spawnDaemon: () => spawnInternalDaemon(launchConfig, mode, preparedRuntime, options),
|
|
2828
|
-
stopDaemon: stopUnresponsiveDaemonProcess,
|
|
2829
|
-
waitForReady: () => waitForDaemon(launchConfig, Date.now() + START_TIMEOUT_MS)
|
|
2830
|
-
});
|
|
2831
2626
|
var stopOlderIdleDaemon = async (config, status) => {
|
|
2832
2627
|
if (!shouldReplaceDaemonVersion(status.version, CONNECTOR_RUNTIME_VERSION)) return false;
|
|
2833
2628
|
if (!status.idle) return false;
|
|
@@ -2854,19 +2649,14 @@ async function ensureConnectorDaemon(launchConfig, mode, options = {}) {
|
|
|
2854
2649
|
if (existingConfig) {
|
|
2855
2650
|
const existingStatus = await readLiveStatus(existingConfig);
|
|
2856
2651
|
if (existingStatus) {
|
|
2857
|
-
if (shouldRestartUnhealthyDaemonForEnrollment(
|
|
2858
|
-
existingStatus,
|
|
2859
|
-
Boolean(options.explicitEnrollment)
|
|
2860
|
-
) && await stopLiveDaemon(existingConfig, existingStatus, getConfigRoot(), "stop-if-idle")) {
|
|
2861
|
-
return await startInternalDaemonWithRecovery(launchConfig, mode, void 0, options);
|
|
2862
|
-
}
|
|
2863
2652
|
const preparedRuntime = !shouldReplaceDaemonVersion(existingStatus.version, CONNECTOR_RUNTIME_VERSION) || !existingStatus.idle ? void 0 : await ensureStableConnectorRuntime();
|
|
2864
2653
|
const replaced = await stopOlderIdleDaemon(existingConfig, existingStatus);
|
|
2865
2654
|
if (!replaced) {
|
|
2866
2655
|
const status = mode === "persistent" ? await requestDaemonControl(existingConfig, "persist", getConfigRoot()) : existingStatus;
|
|
2867
2656
|
return { config: existingConfig, status };
|
|
2868
2657
|
}
|
|
2869
|
-
|
|
2658
|
+
await spawnInternalDaemon(launchConfig, mode, preparedRuntime, options);
|
|
2659
|
+
return await waitForDaemon(launchConfig, Date.now() + START_TIMEOUT_MS);
|
|
2870
2660
|
}
|
|
2871
2661
|
const persistedStatus = await readPersistedDaemonStatus(
|
|
2872
2662
|
existingConfig,
|
|
@@ -2877,12 +2667,9 @@ async function ensureConnectorDaemon(launchConfig, mode, options = {}) {
|
|
|
2877
2667
|
`Connector daemon PID ${persistedStatus.pid} is running but its local control channel is not responding. Stop that process and retry.`
|
|
2878
2668
|
);
|
|
2879
2669
|
}
|
|
2880
|
-
const lockOwnerPid = await readDaemonLockOwnerPid(existingConfig);
|
|
2881
|
-
if (lockOwnerPid && isProcessAlive2(lockOwnerPid)) {
|
|
2882
|
-
await stopUnresponsiveDaemonProcess(lockOwnerPid);
|
|
2883
|
-
}
|
|
2884
2670
|
}
|
|
2885
|
-
|
|
2671
|
+
await spawnInternalDaemon(launchConfig, mode, void 0, options);
|
|
2672
|
+
return await waitForDaemon(launchConfig, Date.now() + START_TIMEOUT_MS);
|
|
2886
2673
|
} finally {
|
|
2887
2674
|
await bootstrapLock.release();
|
|
2888
2675
|
}
|
|
@@ -2902,7 +2689,6 @@ var streamDaemonLogs = (options) => {
|
|
|
2902
2689
|
for (const line of lines.slice(seenLines)) {
|
|
2903
2690
|
try {
|
|
2904
2691
|
const entry = JSON.parse(line);
|
|
2905
|
-
if (!shouldDisplayConnectorLogEntry(entry.createdAt, options.startedAt)) continue;
|
|
2906
2692
|
const connectorId = entry.metadata?.connectorId;
|
|
2907
2693
|
if (options.dashboardActive && typeof connectorId === "string") {
|
|
2908
2694
|
const output2 = entry.metadata?.source === "daemon-console" ? entry.message : `[buda-connector] ${entry.message}`;
|
|
@@ -2980,7 +2766,7 @@ async function stopLocalConnectorDaemon(connectorId, serverUrl, configRoot = get
|
|
|
2980
2766
|
if (!config) throw new Error(`Local Connector ${connectorId} was not found`);
|
|
2981
2767
|
const status = await readLiveStatus(config, configRoot);
|
|
2982
2768
|
if (!status) return;
|
|
2983
|
-
await
|
|
2769
|
+
await requestDaemonControl(config, "stop", configRoot);
|
|
2984
2770
|
}
|
|
2985
2771
|
async function stopAllLocalConnectorDaemons(configRoot = getConfigRoot()) {
|
|
2986
2772
|
const configs = await listDeviceConfigs(configRoot);
|
|
@@ -2993,7 +2779,7 @@ ${config.connectorId}`, config);
|
|
|
2993
2779
|
const results = await Promise.allSettled(
|
|
2994
2780
|
[...identities.values()].map(async (config) => {
|
|
2995
2781
|
const status = await readLiveStatus(config, configRoot);
|
|
2996
|
-
if (status) await
|
|
2782
|
+
if (status) await requestDaemonControl(config, "stop", configRoot);
|
|
2997
2783
|
})
|
|
2998
2784
|
);
|
|
2999
2785
|
const failures = results.filter((result) => result.status === "rejected");
|
|
@@ -3013,7 +2799,6 @@ var waitForConnectorViewerExit = async (dashboardClosed) => {
|
|
|
3013
2799
|
process.off("SIGTERM", finishSignal);
|
|
3014
2800
|
};
|
|
3015
2801
|
async function runConnectorDashboard(launchConfig, options = {}) {
|
|
3016
|
-
const startedAt = Date.now();
|
|
3017
2802
|
let finishDashboard = () => void 0;
|
|
3018
2803
|
const dashboardClosed = new Promise((resolve) => {
|
|
3019
2804
|
finishDashboard = resolve;
|
|
@@ -3034,8 +2819,7 @@ async function runConnectorDashboard(launchConfig, options = {}) {
|
|
|
3034
2819
|
connectorId: config.connectorId,
|
|
3035
2820
|
dashboardActive: false,
|
|
3036
2821
|
plain: Boolean(options.plain),
|
|
3037
|
-
serverUrl: config.serverUrl
|
|
3038
|
-
startedAt
|
|
2822
|
+
serverUrl: config.serverUrl
|
|
3039
2823
|
});
|
|
3040
2824
|
if (!process.stdin.isTTY) {
|
|
3041
2825
|
stopLogs2();
|
|
@@ -3062,8 +2846,7 @@ async function runConnectorDashboard(launchConfig, options = {}) {
|
|
|
3062
2846
|
const stopLogs = streamDaemonLogs({
|
|
3063
2847
|
dashboardActive: true,
|
|
3064
2848
|
plain: false,
|
|
3065
|
-
serverUrl: getServerUrl(launchConfig)
|
|
3066
|
-
startedAt
|
|
2849
|
+
serverUrl: getServerUrl(launchConfig)
|
|
3067
2850
|
});
|
|
3068
2851
|
let refreshing = false;
|
|
3069
2852
|
const refresh = async () => {
|
|
@@ -3608,7 +3391,7 @@ import {
|
|
|
3608
3391
|
lstat,
|
|
3609
3392
|
mkdir as mkdir8,
|
|
3610
3393
|
open as open2,
|
|
3611
|
-
readdir as
|
|
3394
|
+
readdir as readdir5,
|
|
3612
3395
|
readFile as readFile7,
|
|
3613
3396
|
readlink,
|
|
3614
3397
|
realpath,
|
|
@@ -3793,7 +3576,7 @@ var createJournal = async (storageRoot, migrationDirectory, owner) => {
|
|
|
3793
3576
|
};
|
|
3794
3577
|
};
|
|
3795
3578
|
var inventoryLegacySources = async (storageRoot) => {
|
|
3796
|
-
const rootEntries = await
|
|
3579
|
+
const rootEntries = await readdir5(storageRoot, { withFileTypes: true });
|
|
3797
3580
|
const templateNames = new Set(LEGACY_TEMPLATE_FILES);
|
|
3798
3581
|
for (const entry of rootEntries) {
|
|
3799
3582
|
if (entry.name.endsWith(".md")) templateNames.add(entry.name);
|
|
@@ -3823,7 +3606,7 @@ var inventoryLegacySources = async (storageRoot) => {
|
|
|
3823
3606
|
`Legacy migration source ${candidate.id} must not be a symbolic link`
|
|
3824
3607
|
);
|
|
3825
3608
|
}
|
|
3826
|
-
if (info.isDirectory() && (await
|
|
3609
|
+
if (info.isDirectory() && (await readdir5(candidate.source)).length === 0) continue;
|
|
3827
3610
|
found.push(candidate);
|
|
3828
3611
|
}
|
|
3829
3612
|
return found;
|
|
@@ -3843,7 +3626,7 @@ var copyTree = async (source, destination, onEntryCopied) => {
|
|
|
3843
3626
|
const info = await lstat(source);
|
|
3844
3627
|
if (info.isDirectory()) {
|
|
3845
3628
|
await mkdir8(destination, { mode: info.mode & 4095 });
|
|
3846
|
-
const entries = (await
|
|
3629
|
+
const entries = (await readdir5(source)).sort();
|
|
3847
3630
|
for (const entry of entries) {
|
|
3848
3631
|
await copyTree(path9.join(source, entry), path9.join(destination, entry), onEntryCopied);
|
|
3849
3632
|
}
|
|
@@ -3880,8 +3663,8 @@ var verifyTree = async (source, destination) => {
|
|
|
3880
3663
|
}
|
|
3881
3664
|
if (sourceInfo.isDirectory()) {
|
|
3882
3665
|
const [sourceEntries, destinationEntries] = await Promise.all([
|
|
3883
|
-
|
|
3884
|
-
|
|
3666
|
+
readdir5(source),
|
|
3667
|
+
readdir5(destination)
|
|
3885
3668
|
]);
|
|
3886
3669
|
sourceEntries.sort();
|
|
3887
3670
|
destinationEntries.sort();
|
|
@@ -3959,7 +3742,6 @@ var writeJournal = async (journalPath, journal) => {
|
|
|
3959
3742
|
}
|
|
3960
3743
|
};
|
|
3961
3744
|
var syncDirectory2 = async (directory) => {
|
|
3962
|
-
if (process.platform === "win32") return;
|
|
3963
3745
|
const handle = await open2(directory, constants2.O_RDONLY);
|
|
3964
3746
|
try {
|
|
3965
3747
|
await handle.sync();
|
|
@@ -4525,7 +4307,7 @@ import {
|
|
|
4525
4307
|
copyFile as copyFile2,
|
|
4526
4308
|
lstat as lstat2,
|
|
4527
4309
|
mkdir as mkdir9,
|
|
4528
|
-
readdir as
|
|
4310
|
+
readdir as readdir6,
|
|
4529
4311
|
readFile as readFile9,
|
|
4530
4312
|
realpath as realpath3,
|
|
4531
4313
|
rm as rm3,
|
|
@@ -4533,11 +4315,6 @@ import {
|
|
|
4533
4315
|
writeFile as writeFile4
|
|
4534
4316
|
} from "fs/promises";
|
|
4535
4317
|
import path11 from "path";
|
|
4536
|
-
|
|
4537
|
-
// src/directory-link.ts
|
|
4538
|
-
var directoryLinkType = (platform4 = process.platform) => platform4 === "win32" ? "junction" : "dir";
|
|
4539
|
-
|
|
4540
|
-
// src/workspace-volume-lifecycle.ts
|
|
4541
4318
|
var MOUNT_DIRECTORY = "mount";
|
|
4542
4319
|
var ARCHIVE_MARKER = "archived.json";
|
|
4543
4320
|
var AGENT_CONTEXT_TEMPLATE_FILES = [
|
|
@@ -4635,7 +4412,7 @@ var WorkspaceVolumeLifecycle = class {
|
|
|
4635
4412
|
const bootstrapped = [];
|
|
4636
4413
|
const skipped = [];
|
|
4637
4414
|
const entries = new Set(
|
|
4638
|
-
(await
|
|
4415
|
+
(await readdir6(templateDirectory, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isFile()).map((entry) => entry.name)
|
|
4639
4416
|
);
|
|
4640
4417
|
for (const filename of AGENT_CONTEXT_TEMPLATE_FILES) {
|
|
4641
4418
|
if (!entries.has(filename)) continue;
|
|
@@ -4689,7 +4466,7 @@ var ensureVolumeLink = async (target, linkPath) => {
|
|
|
4689
4466
|
} catch (error) {
|
|
4690
4467
|
if (!isMissingFile(error)) throw error;
|
|
4691
4468
|
try {
|
|
4692
|
-
await symlink2(target, linkPath,
|
|
4469
|
+
await symlink2(target, linkPath, "dir");
|
|
4693
4470
|
} catch (createError) {
|
|
4694
4471
|
if (!isAlreadyExists3(createError)) throw createError;
|
|
4695
4472
|
const info = await lstat2(linkPath);
|
|
@@ -4704,7 +4481,6 @@ var isAlreadyExists3 = (error) => error instanceof Error && "code" in error && e
|
|
|
4704
4481
|
// src/relay.ts
|
|
4705
4482
|
var RELAY_PING_INTERVAL_MS = 2e4;
|
|
4706
4483
|
var RELAY_PONG_TIMEOUT_MS = 1e4;
|
|
4707
|
-
var RELAY_ACTIVE_REQUEST_PONG_TIMEOUT_LIMIT = 2;
|
|
4708
4484
|
var RELAY_HANDSHAKE_TIMEOUT_MS = 1e4;
|
|
4709
4485
|
var RELAY_RECONNECT_MAX_MS = 3e4;
|
|
4710
4486
|
var RELAY_RECONNECT_JITTER = 0.2;
|
|
@@ -4810,7 +4586,6 @@ function startConnectorRelay(config, bunnyDaemon, relayProtocol, workspaceRegist
|
|
|
4810
4586
|
socket = ws;
|
|
4811
4587
|
const inflight = /* @__PURE__ */ new Map();
|
|
4812
4588
|
currentInflight = inflight;
|
|
4813
|
-
let consecutivePongTimeouts = 0;
|
|
4814
4589
|
let detached = false;
|
|
4815
4590
|
const detach = () => {
|
|
4816
4591
|
if (detached) return;
|
|
@@ -4823,7 +4598,6 @@ function startConnectorRelay(config, bunnyDaemon, relayProtocol, workspaceRegist
|
|
|
4823
4598
|
void writeLog("info", `relay connected ${relayUrl}`);
|
|
4824
4599
|
hadOpenConnection = true;
|
|
4825
4600
|
reconnectDelayMs = RELAY_RECONNECT_INTERVAL_MS;
|
|
4826
|
-
consecutivePongTimeouts = 0;
|
|
4827
4601
|
pingTimer = setInterval(() => {
|
|
4828
4602
|
if (ws.readyState !== WebSocket2.OPEN) return;
|
|
4829
4603
|
try {
|
|
@@ -4835,33 +4609,18 @@ function startConnectorRelay(config, bunnyDaemon, relayProtocol, workspaceRegist
|
|
|
4835
4609
|
}
|
|
4836
4610
|
if (pongTimer) clearTimeout(pongTimer);
|
|
4837
4611
|
pongTimer = setTimeout(() => {
|
|
4838
|
-
pongTimer = null;
|
|
4839
|
-
consecutivePongTimeouts += 1;
|
|
4840
|
-
if (inflight.size > 0 && consecutivePongTimeouts < RELAY_ACTIVE_REQUEST_PONG_TIMEOUT_LIMIT) {
|
|
4841
|
-
void writeLog(
|
|
4842
|
-
"warn",
|
|
4843
|
-
`relay pong timeout deferred while ${inflight.size} request(s) are active`
|
|
4844
|
-
);
|
|
4845
|
-
return;
|
|
4846
|
-
}
|
|
4847
4612
|
void writeLog("warn", "relay pong timeout, terminating");
|
|
4848
4613
|
ws.terminate();
|
|
4849
4614
|
}, RELAY_PONG_TIMEOUT_MS);
|
|
4850
4615
|
}, RELAY_PING_INTERVAL_MS);
|
|
4851
4616
|
});
|
|
4852
4617
|
ws.on("pong", () => {
|
|
4853
|
-
consecutivePongTimeouts = 0;
|
|
4854
4618
|
if (pongTimer) {
|
|
4855
4619
|
clearTimeout(pongTimer);
|
|
4856
4620
|
pongTimer = null;
|
|
4857
4621
|
}
|
|
4858
4622
|
});
|
|
4859
4623
|
ws.on("message", (raw) => {
|
|
4860
|
-
consecutivePongTimeouts = 0;
|
|
4861
|
-
if (pongTimer) {
|
|
4862
|
-
clearTimeout(pongTimer);
|
|
4863
|
-
pongTimer = null;
|
|
4864
|
-
}
|
|
4865
4624
|
void handleRelayMessage(
|
|
4866
4625
|
ws,
|
|
4867
4626
|
bunnyDaemon,
|
|
@@ -5037,7 +4796,7 @@ async function handleRelayMessage(socket, bunnyDaemon, raw, inflight, expectedCo
|
|
|
5037
4796
|
});
|
|
5038
4797
|
executionIdentity = message.executionIdentity;
|
|
5039
4798
|
} catch (error) {
|
|
5040
|
-
|
|
4799
|
+
sendRelayError(socket, message.requestId, error);
|
|
5041
4800
|
return;
|
|
5042
4801
|
}
|
|
5043
4802
|
}
|
|
@@ -5123,7 +4882,7 @@ async function handleRelayMessage(socket, bunnyDaemon, raw, inflight, expectedCo
|
|
|
5123
4882
|
durationMs: Date.now() - codingRunStartedAt
|
|
5124
4883
|
});
|
|
5125
4884
|
}
|
|
5126
|
-
|
|
4885
|
+
sendRelayMessage(socket, {
|
|
5127
4886
|
type: "start",
|
|
5128
4887
|
requestId: message.requestId,
|
|
5129
4888
|
status: response.status,
|
|
@@ -5143,7 +4902,7 @@ async function handleRelayMessage(socket, bunnyDaemon, raw, inflight, expectedCo
|
|
|
5143
4902
|
while (true) {
|
|
5144
4903
|
const { value, done } = await reader.read();
|
|
5145
4904
|
if (done) break;
|
|
5146
|
-
|
|
4905
|
+
sendRelayMessage(socket, {
|
|
5147
4906
|
type: "chunk",
|
|
5148
4907
|
requestId: message.requestId,
|
|
5149
4908
|
data: binaryResponse ? Buffer.from(value).toString("base64") : decoder?.decode(value, { stream: true }) ?? "",
|
|
@@ -5152,7 +4911,7 @@ async function handleRelayMessage(socket, bunnyDaemon, raw, inflight, expectedCo
|
|
|
5152
4911
|
}
|
|
5153
4912
|
const tail = decoder?.decode() ?? "";
|
|
5154
4913
|
if (tail) {
|
|
5155
|
-
|
|
4914
|
+
sendRelayMessage(socket, {
|
|
5156
4915
|
type: "chunk",
|
|
5157
4916
|
requestId: message.requestId,
|
|
5158
4917
|
data: tail
|
|
@@ -5163,7 +4922,7 @@ async function handleRelayMessage(socket, bunnyDaemon, raw, inflight, expectedCo
|
|
|
5163
4922
|
reader.releaseLock();
|
|
5164
4923
|
}
|
|
5165
4924
|
}
|
|
5166
|
-
|
|
4925
|
+
sendRelayMessage(socket, {
|
|
5167
4926
|
type: "end",
|
|
5168
4927
|
requestId: message.requestId
|
|
5169
4928
|
});
|
|
@@ -5175,16 +4934,14 @@ async function handleRelayMessage(socket, bunnyDaemon, raw, inflight, expectedCo
|
|
|
5175
4934
|
});
|
|
5176
4935
|
}
|
|
5177
4936
|
} catch (error) {
|
|
5178
|
-
controller.abort();
|
|
5179
4937
|
if (codingRunTrace && codingRunStartedAt !== void 0) {
|
|
5180
4938
|
await writeLog("error", "relay request coding run error", {
|
|
5181
4939
|
...codingRunTrace,
|
|
5182
4940
|
status: codingRunStatus,
|
|
5183
|
-
durationMs: Date.now() - codingRunStartedAt
|
|
5184
|
-
error: formatConnectorRelayError(error)
|
|
4941
|
+
durationMs: Date.now() - codingRunStartedAt
|
|
5185
4942
|
});
|
|
5186
4943
|
}
|
|
5187
|
-
|
|
4944
|
+
sendRelayError(socket, message.requestId, error);
|
|
5188
4945
|
} finally {
|
|
5189
4946
|
inflight.delete(message.requestId);
|
|
5190
4947
|
}
|
|
@@ -5238,20 +4995,17 @@ var normalizeWorkspaceRequest = (request, mounts) => {
|
|
|
5238
4995
|
if (!mount || relative === void 0) {
|
|
5239
4996
|
throw new ConnectorRelayProtocolError("WORKSPACE_MISMATCH");
|
|
5240
4997
|
}
|
|
5241
|
-
const
|
|
5242
|
-
const workspaceEnv = {
|
|
5243
|
-
BUDA_AGENT_ROOT: mounts.agent,
|
|
5244
|
-
BUDA_SPACE_ROOT: mounts.space,
|
|
5245
|
-
BUDA_SYSTEM_ROOT: mounts.system
|
|
5246
|
-
};
|
|
4998
|
+
const env = body.env && typeof body.env === "object" && !Array.isArray(body.env) ? body.env : {};
|
|
5247
4999
|
return {
|
|
5248
5000
|
...request,
|
|
5249
5001
|
body: {
|
|
5250
5002
|
...body,
|
|
5251
5003
|
cwd: path12.join(mount, relative),
|
|
5252
|
-
|
|
5253
|
-
...
|
|
5254
|
-
|
|
5004
|
+
env: {
|
|
5005
|
+
...env,
|
|
5006
|
+
BUDA_AGENT_ROOT: mounts.agent,
|
|
5007
|
+
BUDA_SPACE_ROOT: mounts.space,
|
|
5008
|
+
BUDA_SYSTEM_ROOT: mounts.system
|
|
5255
5009
|
}
|
|
5256
5010
|
}
|
|
5257
5011
|
};
|
|
@@ -5283,24 +5037,17 @@ var normalizeConnectorFilesystemRequest = (request, mounts) => {
|
|
|
5283
5037
|
}
|
|
5284
5038
|
return request;
|
|
5285
5039
|
};
|
|
5286
|
-
|
|
5287
|
-
|
|
5040
|
+
function sendRelayError(socket, requestId, error) {
|
|
5041
|
+
sendRelayMessage(socket, {
|
|
5288
5042
|
type: "error",
|
|
5289
5043
|
requestId,
|
|
5290
5044
|
error: error instanceof Error ? error.message : String(error),
|
|
5291
5045
|
...error instanceof ConnectorRelayProtocolError ? { code: error.code } : {}
|
|
5292
5046
|
});
|
|
5293
5047
|
}
|
|
5294
|
-
|
|
5295
|
-
if (socket.readyState !== WebSocket2.OPEN)
|
|
5296
|
-
|
|
5297
|
-
}
|
|
5298
|
-
await new Promise((resolve, reject) => {
|
|
5299
|
-
socket.send(JSON.stringify(message), (error) => {
|
|
5300
|
-
if (error) reject(error);
|
|
5301
|
-
else resolve();
|
|
5302
|
-
});
|
|
5303
|
-
});
|
|
5048
|
+
function sendRelayMessage(socket, message) {
|
|
5049
|
+
if (socket.readyState !== WebSocket2.OPEN) return;
|
|
5050
|
+
socket.send(JSON.stringify(message));
|
|
5304
5051
|
}
|
|
5305
5052
|
function headersToRecord(headers) {
|
|
5306
5053
|
const result = {};
|
|
@@ -5359,7 +5106,7 @@ async function ensureSymlink(target, linkPath) {
|
|
|
5359
5106
|
} catch {
|
|
5360
5107
|
}
|
|
5361
5108
|
await mkdir10(path13.dirname(linkPath), { recursive: true });
|
|
5362
|
-
await symlink3(target, linkPath,
|
|
5109
|
+
await symlink3(target, linkPath, "dir");
|
|
5363
5110
|
}
|
|
5364
5111
|
|
|
5365
5112
|
// src/workspace-registry.ts
|
|
@@ -5816,30 +5563,7 @@ import { mkdir as mkdir12 } from "fs/promises";
|
|
|
5816
5563
|
import path15 from "path";
|
|
5817
5564
|
var RECOVERED_DAEMON_STOP_TIMEOUT_MS = 1e4;
|
|
5818
5565
|
var RECOVERED_DAEMON_STOP_POLL_MS = 100;
|
|
5819
|
-
var SHUTDOWN_FORCE_EXIT_MS = 1e4;
|
|
5820
|
-
var SHUTDOWN_STEP_TIMEOUT_MS = 2e3;
|
|
5821
5566
|
var wait3 = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
5822
|
-
var runShutdownStep = async (label, operation) => {
|
|
5823
|
-
let timeout;
|
|
5824
|
-
try {
|
|
5825
|
-
await Promise.race([
|
|
5826
|
-
Promise.resolve().then(operation),
|
|
5827
|
-
new Promise((_, reject) => {
|
|
5828
|
-
timeout = setTimeout(
|
|
5829
|
-
() => reject(new Error(`${label} timed out after ${SHUTDOWN_STEP_TIMEOUT_MS}ms`)),
|
|
5830
|
-
SHUTDOWN_STEP_TIMEOUT_MS
|
|
5831
|
-
);
|
|
5832
|
-
})
|
|
5833
|
-
]);
|
|
5834
|
-
} catch (error) {
|
|
5835
|
-
await writeLog(
|
|
5836
|
-
"warn",
|
|
5837
|
-
`${label} failed during shutdown: ${error instanceof Error ? error.message : String(error)}`
|
|
5838
|
-
).catch(() => void 0);
|
|
5839
|
-
} finally {
|
|
5840
|
-
if (timeout) clearTimeout(timeout);
|
|
5841
|
-
}
|
|
5842
|
-
};
|
|
5843
5567
|
async function stopRecoveredIdleDaemon(config, configRoot) {
|
|
5844
5568
|
const stopStatus = await requestDaemonControl(config, "stop-if-idle", configRoot).catch(
|
|
5845
5569
|
() => null
|
|
@@ -5959,30 +5683,18 @@ async function runConnectorDaemon(launchConfig, options = {}) {
|
|
|
5959
5683
|
const shutdown = async (shutdownOptions = {}) => {
|
|
5960
5684
|
if (shuttingDown) return;
|
|
5961
5685
|
shuttingDown = true;
|
|
5962
|
-
const forceExit = options.exitOnShutdown === false ? void 0 : setTimeout(() => process.exit(0), SHUTDOWN_FORCE_EXIT_MS);
|
|
5963
5686
|
if (interval) clearInterval(interval);
|
|
5964
5687
|
relay.stop();
|
|
5965
5688
|
try {
|
|
5966
|
-
await
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
() => unregisterConnector(config, launchConfig)
|
|
5972
|
-
)
|
|
5973
|
-
],
|
|
5974
|
-
runShutdownStep(
|
|
5975
|
-
"embedded Bunny daemon shutdown",
|
|
5976
|
-
() => stopEmbeddedBunnyDaemon(activeBunnyDaemon)
|
|
5977
|
-
)
|
|
5978
|
-
]);
|
|
5689
|
+
await closeAllBrowserSessions();
|
|
5690
|
+
if (shutdownOptions.unregister !== false) {
|
|
5691
|
+
await unregisterConnector(config, launchConfig);
|
|
5692
|
+
}
|
|
5693
|
+
await stopEmbeddedBunnyDaemon(activeBunnyDaemon);
|
|
5979
5694
|
} finally {
|
|
5980
|
-
await
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
runShutdownStep("console capture restore", async () => restoreConsole?.())
|
|
5984
|
-
]);
|
|
5985
|
-
if (forceExit) clearTimeout(forceExit);
|
|
5695
|
+
await control?.close();
|
|
5696
|
+
await daemonLock.release();
|
|
5697
|
+
await restoreConsole?.();
|
|
5986
5698
|
if (options.exitOnShutdown !== false) process.exit(0);
|
|
5987
5699
|
}
|
|
5988
5700
|
};
|
|
@@ -5997,11 +5709,8 @@ async function runConnectorDaemon(launchConfig, options = {}) {
|
|
|
5997
5709
|
});
|
|
5998
5710
|
}).catch((error) => {
|
|
5999
5711
|
heartbeatHealthy = false;
|
|
6000
|
-
if (error instanceof ConnectorDeletedError
|
|
6001
|
-
void writeLog(
|
|
6002
|
-
"warn",
|
|
6003
|
-
error instanceof ConnectorDeletedError ? "connector deleted remotely; stopping daemon" : "connector authentication rejected remotely; stopping daemon"
|
|
6004
|
-
);
|
|
5712
|
+
if (error instanceof ConnectorDeletedError) {
|
|
5713
|
+
void writeLog("warn", "connector deleted remotely; stopping daemon");
|
|
6005
5714
|
void shutdown({ unregister: false });
|
|
6006
5715
|
return;
|
|
6007
5716
|
}
|
|
@@ -6025,10 +5734,6 @@ async function runConnectorDaemon(launchConfig, options = {}) {
|
|
|
6025
5734
|
process.once("SIGINT", () => void shutdown());
|
|
6026
5735
|
process.once("SIGTERM", () => void shutdown());
|
|
6027
5736
|
} catch (error) {
|
|
6028
|
-
await writeLog(
|
|
6029
|
-
"error",
|
|
6030
|
-
`daemon startup failed: ${error instanceof Error ? error.stack || error.message : String(error)}`
|
|
6031
|
-
).catch(() => void 0);
|
|
6032
5737
|
if (bunnyDaemon) await stopEmbeddedBunnyDaemon(bunnyDaemon);
|
|
6033
5738
|
await daemonLock.release();
|
|
6034
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"
|