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