@buda-ai/connector 0.1.32 → 0.1.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js
CHANGED
|
@@ -25,8 +25,8 @@ async function acquireExclusiveFileLock(targetPath, options = {}) {
|
|
|
25
25
|
} catch (error) {
|
|
26
26
|
if (!isAlreadyExists(error)) throw error;
|
|
27
27
|
if (await shouldRecoverLock(lockPath, staleMs)) {
|
|
28
|
-
await unlink(lockPath).catch(() =>
|
|
29
|
-
continue;
|
|
28
|
+
const recovered = await unlink(lockPath).then(() => true).catch(() => false);
|
|
29
|
+
if (recovered) continue;
|
|
30
30
|
}
|
|
31
31
|
if (Date.now() >= deadline) {
|
|
32
32
|
throw new Error(`Timed out waiting for exclusive access to ${targetPath}`);
|
|
@@ -35,17 +35,23 @@ async function acquireExclusiveFileLock(targetPath, options = {}) {
|
|
|
35
35
|
continue;
|
|
36
36
|
}
|
|
37
37
|
const ownerToken = randomUUID();
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
try {
|
|
39
|
+
await handle.writeFile(
|
|
40
|
+
`${JSON.stringify({ pid: process.pid, ownerToken, createdAt: Date.now() })}
|
|
40
41
|
`
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
);
|
|
43
|
+
await handle.sync();
|
|
44
|
+
} catch (error) {
|
|
45
|
+
await handle.close().catch(() => void 0);
|
|
46
|
+
await unlink(lockPath).catch(() => void 0);
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
await handle.close();
|
|
43
50
|
let released = false;
|
|
44
51
|
return {
|
|
45
52
|
release: async () => {
|
|
46
53
|
if (released) return;
|
|
47
54
|
released = true;
|
|
48
|
-
await handle.close();
|
|
49
55
|
const currentOwner = await readFile(lockPath, "utf8").then((raw) => JSON.parse(raw).ownerToken).catch(() => void 0);
|
|
50
56
|
if (currentOwner === ownerToken) await unlink(lockPath).catch(() => void 0);
|
|
51
57
|
}
|
|
@@ -98,8 +104,8 @@ var defaultDependencies = {
|
|
|
98
104
|
};
|
|
99
105
|
var readRawSystemId = (dependencies) => {
|
|
100
106
|
if (dependencies.platform === "darwin") {
|
|
101
|
-
const
|
|
102
|
-
return
|
|
107
|
+
const output2 = dependencies.execFile("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"]);
|
|
108
|
+
return output2.match(/"IOPlatformUUID"\s*=\s*"([^"]+)"/)?.[1];
|
|
103
109
|
}
|
|
104
110
|
if (dependencies.platform === "linux") {
|
|
105
111
|
for (const filePath of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
@@ -112,13 +118,13 @@ var readRawSystemId = (dependencies) => {
|
|
|
112
118
|
return void 0;
|
|
113
119
|
}
|
|
114
120
|
if (dependencies.platform === "win32") {
|
|
115
|
-
const
|
|
121
|
+
const output2 = dependencies.execFile("reg", [
|
|
116
122
|
"query",
|
|
117
123
|
String.raw`HKLM\SOFTWARE\Microsoft\Cryptography`,
|
|
118
124
|
"/v",
|
|
119
125
|
"MachineGuid"
|
|
120
126
|
]);
|
|
121
|
-
return
|
|
127
|
+
return output2.match(/MachineGuid\s+REG_SZ\s+([^\r\n]+)/i)?.[1];
|
|
122
128
|
}
|
|
123
129
|
return void 0;
|
|
124
130
|
};
|
|
@@ -517,7 +523,7 @@ import path2 from "path";
|
|
|
517
523
|
// package.json
|
|
518
524
|
var package_default = {
|
|
519
525
|
name: "@buda-ai/connector",
|
|
520
|
-
version: "0.1.
|
|
526
|
+
version: "0.1.34",
|
|
521
527
|
private: false,
|
|
522
528
|
type: "module",
|
|
523
529
|
publishConfig: {
|
|
@@ -535,6 +541,7 @@ var package_default = {
|
|
|
535
541
|
dev: "tsx src/cli.ts dev",
|
|
536
542
|
daemon: "tsx src/cli.ts daemon",
|
|
537
543
|
new: "tsx src/cli.ts new",
|
|
544
|
+
prepack: "tsup --silent",
|
|
538
545
|
start: "node dist/cli.js daemon",
|
|
539
546
|
build: "tsup",
|
|
540
547
|
test: "vitest run",
|
|
@@ -543,7 +550,7 @@ var package_default = {
|
|
|
543
550
|
"lint:err": "biome format . --write && biome check . --write --diagnostic-level=error"
|
|
544
551
|
},
|
|
545
552
|
dependencies: {
|
|
546
|
-
"@bunny-agent/daemon": "0.9.
|
|
553
|
+
"@bunny-agent/daemon": "0.9.60-beta.1",
|
|
547
554
|
"@clack/prompts": "^1.0.0",
|
|
548
555
|
"playwright-core": "^1.52.0",
|
|
549
556
|
ws: "^8.21.0"
|
|
@@ -847,14 +854,14 @@ var openControllingTerminal = () => {
|
|
|
847
854
|
try {
|
|
848
855
|
inputFd = openSync(inputPath, "r");
|
|
849
856
|
outputFd = openSync(outputPath, "w");
|
|
850
|
-
const
|
|
851
|
-
const
|
|
857
|
+
const input2 = new ReadStream(inputFd);
|
|
858
|
+
const output2 = new WriteStream(outputFd);
|
|
852
859
|
return {
|
|
853
|
-
input,
|
|
854
|
-
output,
|
|
860
|
+
input: input2,
|
|
861
|
+
output: output2,
|
|
855
862
|
close: () => {
|
|
856
|
-
|
|
857
|
-
|
|
863
|
+
input2.destroy();
|
|
864
|
+
output2.destroy();
|
|
858
865
|
}
|
|
859
866
|
};
|
|
860
867
|
} catch {
|
|
@@ -1005,8 +1012,8 @@ var getConnectorSectionHeight = (state, height) => state.connectors.length ? Mat
|
|
|
1005
1012
|
Math.max(4, state.connectors.length + 2),
|
|
1006
1013
|
Math.max(4, Math.min(Math.floor(height / 3), height - 13))
|
|
1007
1014
|
) : 0;
|
|
1008
|
-
var parseDashboardMouseWheel = (
|
|
1009
|
-
const event =
|
|
1015
|
+
var parseDashboardMouseWheel = (input2, state, size) => {
|
|
1016
|
+
const event = input2.split("\x1B[<").at(-1);
|
|
1010
1017
|
const match = event?.match(/^(64|65);\d+;(\d+)[Mm]/);
|
|
1011
1018
|
if (!match) return void 0;
|
|
1012
1019
|
const row = Number(match[2]);
|
|
@@ -1117,13 +1124,13 @@ var renderConnectors = (state, height, width) => {
|
|
|
1117
1124
|
const range = state.connectors.length ? `${start + 1}-${start + visible.length}/${state.connectors.length}` : "0/0";
|
|
1118
1125
|
const title = ` Connectors ${state.activeSection === "connectors" ? "[active]" : ""}`;
|
|
1119
1126
|
const headerFill = Math.max(1, innerWidth - title.length - range.length);
|
|
1120
|
-
const
|
|
1127
|
+
const output2 = [
|
|
1121
1128
|
`${state.activeSection === "connectors" ? colors.reverse : ""}+${title}${"-".repeat(headerFill)}${range}+${colors.reset}`
|
|
1122
1129
|
];
|
|
1123
1130
|
for (let index = 0; index < contentHeight; index += 1) {
|
|
1124
1131
|
const connector = visible[index];
|
|
1125
1132
|
if (!connector) {
|
|
1126
|
-
|
|
1133
|
+
output2.push(`|${" ".repeat(innerWidth)}|`);
|
|
1127
1134
|
continue;
|
|
1128
1135
|
}
|
|
1129
1136
|
const marker = connector.connectorId === state.selectedConnectorId ? ">" : " ";
|
|
@@ -1134,10 +1141,10 @@ var renderConnectors = (state, height, width) => {
|
|
|
1134
1141
|
const serverWidth = Math.max(8, availableFieldWidth - connectorWidth - spaceWidth);
|
|
1135
1142
|
const content = `${marker} ${statusText} ${truncate(connector.label ?? connector.connectorId, connectorWidth)} | ${truncate(connector.spaceId, spaceWidth)} | ${truncate(connector.serverUrl, serverWidth)}`;
|
|
1136
1143
|
const color = connector.status === "Stopped" ? colors.dim : connector.status === "Online" ? colors.cyan : connector.status === "Connecting" || connector.status === "Stopping" ? colors.yellow : connector.status === "Error" || connector.status === "Unresponsive" ? colors.red : "";
|
|
1137
|
-
|
|
1144
|
+
output2.push(`|${color}${truncate(content, innerWidth)}${colors.reset}|`);
|
|
1138
1145
|
}
|
|
1139
|
-
|
|
1140
|
-
return
|
|
1146
|
+
output2.push(`+${"-".repeat(innerWidth)}+`);
|
|
1147
|
+
return output2;
|
|
1141
1148
|
};
|
|
1142
1149
|
var renderPane = (title, lines, height, width, active, scroll) => {
|
|
1143
1150
|
const innerWidth = width - 2;
|
|
@@ -1147,22 +1154,22 @@ var renderPane = (title, lines, height, width, active, scroll) => {
|
|
|
1147
1154
|
const headerSuffix = scroll === 0 ? "following" : `${scroll} lines behind`;
|
|
1148
1155
|
const titleText = ` ${title} ${active ? "[active]" : ""}`;
|
|
1149
1156
|
const headerFill = Math.max(1, innerWidth - titleText.length - headerSuffix.length);
|
|
1150
|
-
const
|
|
1157
|
+
const output2 = [
|
|
1151
1158
|
`${active ? colors.reverse : ""}+${titleText}${"-".repeat(headerFill)}${headerSuffix}+${colors.reset}`
|
|
1152
1159
|
];
|
|
1153
1160
|
for (let index = 0; index < contentHeight; index += 1) {
|
|
1154
1161
|
const line = visible[index];
|
|
1155
1162
|
if (!line) {
|
|
1156
|
-
|
|
1163
|
+
output2.push(`|${" ".repeat(innerWidth)}|`);
|
|
1157
1164
|
continue;
|
|
1158
1165
|
}
|
|
1159
1166
|
const levelColor = line.level === "error" ? colors.red : line.level === "warn" ? colors.yellow : colors.dim;
|
|
1160
1167
|
const count = line.count > 1 ? ` x${line.count}` : "";
|
|
1161
1168
|
const content = `${line.time} ${line.level.toUpperCase().padEnd(5)} ${line.message}${count}`;
|
|
1162
|
-
|
|
1169
|
+
output2.push(`|${levelColor}${truncate(content, innerWidth)}${colors.reset}|`);
|
|
1163
1170
|
}
|
|
1164
|
-
|
|
1165
|
-
return
|
|
1171
|
+
output2.push(`+${"-".repeat(innerWidth)}+`);
|
|
1172
|
+
return output2;
|
|
1166
1173
|
};
|
|
1167
1174
|
var renderFooter = (state, width) => {
|
|
1168
1175
|
const managerKeys = state.connectors.length ? "Up/Down select | S stop | Esc exit | " : "";
|
|
@@ -1524,7 +1531,7 @@ var installConnectorConsoleCapture = () => {
|
|
|
1524
1531
|
log: console.log,
|
|
1525
1532
|
warn: console.warn
|
|
1526
1533
|
};
|
|
1527
|
-
const capture = (level,
|
|
1534
|
+
const capture = (level, output2) => (...args) => {
|
|
1528
1535
|
const message = format2(...args);
|
|
1529
1536
|
if (!message.startsWith("[buda-connector]")) {
|
|
1530
1537
|
writes.push(
|
|
@@ -1539,7 +1546,7 @@ var installConnectorConsoleCapture = () => {
|
|
|
1539
1546
|
})
|
|
1540
1547
|
);
|
|
1541
1548
|
}
|
|
1542
|
-
|
|
1549
|
+
output2(...args);
|
|
1543
1550
|
};
|
|
1544
1551
|
console.log = capture("info", original.log);
|
|
1545
1552
|
console.warn = capture("warn", original.warn);
|
|
@@ -1817,7 +1824,7 @@ async function handleBrowserInput(sessionIdValue, body) {
|
|
|
1817
1824
|
if (!sessionId) throw new Error("A valid browser session id is required.");
|
|
1818
1825
|
const ev = body ?? {};
|
|
1819
1826
|
const s = await ensureSession(sessionId);
|
|
1820
|
-
const
|
|
1827
|
+
const input2 = s.inputQueue.catch(() => {
|
|
1821
1828
|
}).then(async () => {
|
|
1822
1829
|
switch (ev.type) {
|
|
1823
1830
|
case "mouseMove":
|
|
@@ -1871,8 +1878,8 @@ async function handleBrowserInput(sessionIdValue, body) {
|
|
|
1871
1878
|
throw new Error(`Unknown browser input event: ${String(ev.type)}`);
|
|
1872
1879
|
}
|
|
1873
1880
|
});
|
|
1874
|
-
s.inputQueue =
|
|
1875
|
-
await
|
|
1881
|
+
s.inputQueue = input2;
|
|
1882
|
+
await input2;
|
|
1876
1883
|
}
|
|
1877
1884
|
var getPageResult = async (s) => ({
|
|
1878
1885
|
url: s.page.url(),
|
|
@@ -1902,16 +1909,16 @@ async function handleBrowserRequest(requestPath, method, body, context = {}) {
|
|
|
1902
1909
|
if (!sessionId)
|
|
1903
1910
|
return jsonResponse(400, { ok: false, error: "valid browser session id required" });
|
|
1904
1911
|
if (route === "start" && method === "POST") {
|
|
1905
|
-
const
|
|
1912
|
+
const input2 = body ?? {};
|
|
1906
1913
|
const viewport = {
|
|
1907
|
-
width: normalizeViewportDimension(
|
|
1908
|
-
height: normalizeViewportDimension(
|
|
1914
|
+
width: normalizeViewportDimension(input2.width, 320, 2560, DEFAULT_VIEWPORT.width),
|
|
1915
|
+
height: normalizeViewportDimension(input2.height, 240, 2560, DEFAULT_VIEWPORT.height)
|
|
1909
1916
|
};
|
|
1910
1917
|
const s = await ensureSession(sessionId, viewport);
|
|
1911
|
-
const clientId = normalizeSessionId(
|
|
1918
|
+
const clientId = normalizeSessionId(input2.clientId);
|
|
1912
1919
|
if (clientId) s.clientIds.add(clientId);
|
|
1913
1920
|
await resizeBrowser(s, viewport);
|
|
1914
|
-
const url =
|
|
1921
|
+
const url = input2.url?.trim() || "about:blank";
|
|
1915
1922
|
await writeLog("info", `browser viewport ${viewport.width}x${viewport.height}`);
|
|
1916
1923
|
await navigateBrowser(s, url);
|
|
1917
1924
|
await writeLog("info", `browser ready at ${s.page.url()}`);
|
|
@@ -1922,8 +1929,8 @@ async function handleBrowserRequest(requestPath, method, body, context = {}) {
|
|
|
1922
1929
|
return jsonResponse(200, { ok: true });
|
|
1923
1930
|
}
|
|
1924
1931
|
if (route === "release" && method === "POST") {
|
|
1925
|
-
const
|
|
1926
|
-
const clientId = normalizeSessionId(
|
|
1932
|
+
const input2 = body ?? {};
|
|
1933
|
+
const clientId = normalizeSessionId(input2.clientId);
|
|
1927
1934
|
if (!clientId) return jsonResponse(400, { ok: false, error: "valid client id required" });
|
|
1928
1935
|
const s = sessions.get(sessionId);
|
|
1929
1936
|
if (!s) return jsonResponse(200, { ok: true, closed: false });
|
|
@@ -1933,13 +1940,13 @@ async function handleBrowserRequest(requestPath, method, body, context = {}) {
|
|
|
1933
1940
|
return jsonResponse(200, { ok: true, closed: true });
|
|
1934
1941
|
}
|
|
1935
1942
|
if (route === "navigate" && method === "POST") {
|
|
1936
|
-
const
|
|
1937
|
-
const url =
|
|
1943
|
+
const input2 = body ?? {};
|
|
1944
|
+
const url = input2.url?.trim();
|
|
1938
1945
|
if (!url) return jsonResponse(400, { ok: false, error: "url required" });
|
|
1939
1946
|
const s = await ensureSession(sessionId);
|
|
1940
1947
|
const response = await navigateBrowser(s, url, {
|
|
1941
|
-
waitUntil:
|
|
1942
|
-
timeout: normalizeTimeout(
|
|
1948
|
+
waitUntil: input2.waitUntil ?? "domcontentloaded",
|
|
1949
|
+
timeout: normalizeTimeout(input2.timeoutMs, 3e4, 6e4)
|
|
1943
1950
|
});
|
|
1944
1951
|
return jsonResponse(200, {
|
|
1945
1952
|
ok: true,
|
|
@@ -1949,14 +1956,14 @@ async function handleBrowserRequest(requestPath, method, body, context = {}) {
|
|
|
1949
1956
|
});
|
|
1950
1957
|
}
|
|
1951
1958
|
if (route === "click" && method === "POST") {
|
|
1952
|
-
const
|
|
1953
|
-
const selector =
|
|
1959
|
+
const input2 = body ?? {};
|
|
1960
|
+
const selector = input2.selector?.trim();
|
|
1954
1961
|
if (!selector) return jsonResponse(400, { ok: false, error: "selector required" });
|
|
1955
1962
|
const s = await ensureSession(sessionId);
|
|
1956
|
-
const button =
|
|
1963
|
+
const button = input2.button === "right" || input2.button === "middle" ? input2.button : "left";
|
|
1957
1964
|
await s.page.locator(selector).click({
|
|
1958
1965
|
button,
|
|
1959
|
-
timeout: normalizeTimeout(
|
|
1966
|
+
timeout: normalizeTimeout(input2.timeoutMs, 1e4, 3e4)
|
|
1960
1967
|
});
|
|
1961
1968
|
await s.page.waitForLoadState("domcontentloaded", { timeout: 5e3 }).catch(() => {
|
|
1962
1969
|
});
|
|
@@ -1968,14 +1975,14 @@ async function handleBrowserRequest(requestPath, method, body, context = {}) {
|
|
|
1968
1975
|
});
|
|
1969
1976
|
}
|
|
1970
1977
|
if (route === "type" && method === "POST") {
|
|
1971
|
-
const
|
|
1972
|
-
const selector =
|
|
1978
|
+
const input2 = body ?? {};
|
|
1979
|
+
const selector = input2.selector?.trim();
|
|
1973
1980
|
if (!selector) return jsonResponse(400, { ok: false, error: "selector required" });
|
|
1974
1981
|
const s = await ensureSession(sessionId);
|
|
1975
1982
|
const locator = s.page.locator(selector);
|
|
1976
|
-
const text2 = typeof
|
|
1977
|
-
const timeout = normalizeTimeout(
|
|
1978
|
-
if (
|
|
1983
|
+
const text2 = typeof input2.text === "string" ? input2.text : String(input2.text ?? "");
|
|
1984
|
+
const timeout = normalizeTimeout(input2.timeoutMs, 1e4, 3e4);
|
|
1985
|
+
if (input2.clear !== false) {
|
|
1979
1986
|
await locator.fill("", { timeout });
|
|
1980
1987
|
await locator.fill(text2, { timeout });
|
|
1981
1988
|
} else {
|
|
@@ -1989,12 +1996,12 @@ async function handleBrowserRequest(requestPath, method, body, context = {}) {
|
|
|
1989
1996
|
});
|
|
1990
1997
|
}
|
|
1991
1998
|
if (route === "extract" && method === "POST") {
|
|
1992
|
-
const
|
|
1993
|
-
const selector =
|
|
1999
|
+
const input2 = body ?? {};
|
|
2000
|
+
const selector = input2.selector?.trim() || "body";
|
|
1994
2001
|
const s = await ensureSession(sessionId);
|
|
1995
2002
|
const locator = s.page.locator(selector).first();
|
|
1996
2003
|
const text2 = (await locator.innerText({ timeout: 1e4 }).catch(async () => s.page.locator("body").innerText({ timeout: 1e4 }))).trim();
|
|
1997
|
-
const maxChars = typeof
|
|
2004
|
+
const maxChars = typeof input2.maxChars === "number" && Number.isFinite(input2.maxChars) ? Math.min(2e4, Math.max(500, Math.round(input2.maxChars))) : 8e3;
|
|
1998
2005
|
return jsonResponse(200, {
|
|
1999
2006
|
ok: true,
|
|
2000
2007
|
action: "extract",
|
|
@@ -2003,13 +2010,13 @@ async function handleBrowserRequest(requestPath, method, body, context = {}) {
|
|
|
2003
2010
|
});
|
|
2004
2011
|
}
|
|
2005
2012
|
if (route === "screenshot" && method === "POST") {
|
|
2006
|
-
const
|
|
2013
|
+
const input2 = body ?? {};
|
|
2007
2014
|
const s = await ensureSession(sessionId);
|
|
2008
|
-
const screenshotPath = resolveScreenshotPath(context.workdirRoot,
|
|
2015
|
+
const screenshotPath = resolveScreenshotPath(context.workdirRoot, input2.file_path);
|
|
2009
2016
|
await mkdir4(path4.dirname(screenshotPath.absolutePath), { recursive: true });
|
|
2010
2017
|
await s.page.screenshot({
|
|
2011
2018
|
path: screenshotPath.absolutePath,
|
|
2012
|
-
fullPage:
|
|
2019
|
+
fullPage: input2.fullPage !== false
|
|
2013
2020
|
});
|
|
2014
2021
|
return jsonResponse(200, {
|
|
2015
2022
|
ok: true,
|
|
@@ -2020,10 +2027,10 @@ async function handleBrowserRequest(requestPath, method, body, context = {}) {
|
|
|
2020
2027
|
});
|
|
2021
2028
|
}
|
|
2022
2029
|
if (route === "resize" && method === "POST") {
|
|
2023
|
-
const
|
|
2030
|
+
const input2 = body ?? {};
|
|
2024
2031
|
const viewport = {
|
|
2025
|
-
width: normalizeViewportDimension(
|
|
2026
|
-
height: normalizeViewportDimension(
|
|
2032
|
+
width: normalizeViewportDimension(input2.width, 320, 2560, DEFAULT_VIEWPORT.width),
|
|
2033
|
+
height: normalizeViewportDimension(input2.height, 240, 2560, DEFAULT_VIEWPORT.height)
|
|
2027
2034
|
};
|
|
2028
2035
|
const s = await ensureSession(sessionId);
|
|
2029
2036
|
await resizeBrowser(s, viewport);
|
|
@@ -2208,6 +2215,13 @@ var ConnectorDeletedError = class extends Error {
|
|
|
2208
2215
|
this.name = "ConnectorDeletedError";
|
|
2209
2216
|
}
|
|
2210
2217
|
};
|
|
2218
|
+
var ConnectorUnauthorizedError = class extends Error {
|
|
2219
|
+
constructor(message) {
|
|
2220
|
+
super(message);
|
|
2221
|
+
this.name = "ConnectorUnauthorizedError";
|
|
2222
|
+
}
|
|
2223
|
+
};
|
|
2224
|
+
var UNREGISTER_TIMEOUT_MS = 3e3;
|
|
2211
2225
|
async function resolveConnectorScope(deviceId, launchConfig) {
|
|
2212
2226
|
const serverUrl = normalizeServerOrigin(getServerUrl(launchConfig));
|
|
2213
2227
|
const headers = { "Content-Type": "application/json" };
|
|
@@ -2294,14 +2308,15 @@ function buildAuthHeaders(config, launchConfig) {
|
|
|
2294
2308
|
if (config.deviceToken) headers["X-Buda-Connector-Device-Token"] = config.deviceToken;
|
|
2295
2309
|
return headers;
|
|
2296
2310
|
}
|
|
2297
|
-
async function postJson(serverUrl, pathname, body, config, launchConfig) {
|
|
2311
|
+
async function postJson(serverUrl, pathname, body, config, launchConfig, options = {}) {
|
|
2298
2312
|
const response = await fetch(`${serverUrl}${pathname}`, {
|
|
2299
2313
|
method: "POST",
|
|
2300
2314
|
headers: {
|
|
2301
2315
|
"Content-Type": "application/json",
|
|
2302
2316
|
...buildAuthHeaders(config, launchConfig)
|
|
2303
2317
|
},
|
|
2304
|
-
body: JSON.stringify(body)
|
|
2318
|
+
body: JSON.stringify(body),
|
|
2319
|
+
...options.timeoutMs ? { signal: AbortSignal.timeout(options.timeoutMs) } : {}
|
|
2305
2320
|
});
|
|
2306
2321
|
if (!response.ok) {
|
|
2307
2322
|
const text2 = await response.text().catch(() => "");
|
|
@@ -2321,6 +2336,9 @@ async function postJson(serverUrl, pathname, body, config, launchConfig) {
|
|
|
2321
2336
|
if (errorCode === "connector_deleted") {
|
|
2322
2337
|
throw new ConnectorDeletedError(errorMessage3 || "Connector no longer exists");
|
|
2323
2338
|
}
|
|
2339
|
+
if (response.status === 401) {
|
|
2340
|
+
throw new ConnectorUnauthorizedError(errorMessage3 || "Connector authentication failed");
|
|
2341
|
+
}
|
|
2324
2342
|
throw new Error(`${pathname} failed: ${response.status} ${errorMessage3}`);
|
|
2325
2343
|
}
|
|
2326
2344
|
return await response.json();
|
|
@@ -2396,7 +2414,8 @@ async function unregisterConnector(config, launchConfig) {
|
|
|
2396
2414
|
connectorId: config.connectorId
|
|
2397
2415
|
},
|
|
2398
2416
|
config,
|
|
2399
|
-
launchConfig
|
|
2417
|
+
launchConfig,
|
|
2418
|
+
{ timeoutMs: UNREGISTER_TIMEOUT_MS }
|
|
2400
2419
|
).catch((error) => {
|
|
2401
2420
|
void writeLog("warn", `unregister failed: ${error instanceof Error ? error.message : error}`);
|
|
2402
2421
|
});
|
|
@@ -2421,11 +2440,19 @@ import path6 from "path";
|
|
|
2421
2440
|
import { promisify } from "util";
|
|
2422
2441
|
var execFileAsync = promisify(execFile);
|
|
2423
2442
|
var RUNTIME_INSTALL_TIMEOUT_MS = 5 * 6e4;
|
|
2443
|
+
var WINDOWS_RENAME_RETRY_DELAYS_MS = [50, 100, 200, 400, 500, 500, 500, 500, 500];
|
|
2424
2444
|
var MACOS_NODE_LIBRARY_PATTERN = /^libnode(?:\.\d+)*\.dylib$/;
|
|
2425
2445
|
var getLoadedSharedObjects = () => {
|
|
2426
2446
|
const report = process.report?.getReport();
|
|
2427
2447
|
return Array.isArray(report?.sharedObjects) ? report.sharedObjects.filter((value) => typeof value === "string") : [];
|
|
2428
2448
|
};
|
|
2449
|
+
var resolveBundledNodePlacement = (sourceRoot, sourceNode, pathApi = path6) => {
|
|
2450
|
+
const relativeNode = pathApi.relative(sourceRoot, sourceNode);
|
|
2451
|
+
return {
|
|
2452
|
+
copyNode: relativeNode.startsWith("..") || pathApi.isAbsolute(relativeNode),
|
|
2453
|
+
relativeNode
|
|
2454
|
+
};
|
|
2455
|
+
};
|
|
2429
2456
|
var copyNodeRuntime = async (sourceNode, destinationNode, options = {}) => {
|
|
2430
2457
|
const resolvedSourceNode = await realpath(sourceNode);
|
|
2431
2458
|
const sourceDirectory = path6.dirname(resolvedSourceNode);
|
|
@@ -2452,8 +2479,7 @@ var copyNodeRuntime = async (sourceNode, destinationNode, options = {}) => {
|
|
|
2452
2479
|
}))
|
|
2453
2480
|
)).flatMap(({ directory, entries }) => entries.map((entry) => ({ directory, entry })));
|
|
2454
2481
|
const companionFiles = /* @__PURE__ */ new Map();
|
|
2455
|
-
const
|
|
2456
|
-
for (const library of loadedLibraries) {
|
|
2482
|
+
for (const library of options.loadedLibraries ?? getLoadedSharedObjects()) {
|
|
2457
2483
|
const name = path6.basename(library);
|
|
2458
2484
|
if (MACOS_NODE_LIBRARY_PATTERN.test(name)) companionFiles.set(name, library);
|
|
2459
2485
|
}
|
|
@@ -2471,7 +2497,44 @@ var copyNodeRuntime = async (sourceNode, destinationNode, options = {}) => {
|
|
|
2471
2497
|
)
|
|
2472
2498
|
);
|
|
2473
2499
|
};
|
|
2500
|
+
var isWindowsRenameContention = (error) => error instanceof Error && "code" in error && ["EACCES", "EBUSY", "EPERM"].includes(String(error.code));
|
|
2501
|
+
var renameStagedRuntime = async (source, destination, options = {}) => {
|
|
2502
|
+
const platform4 = options.platform ?? process.platform;
|
|
2503
|
+
const renamePath = options.renamePath ?? rename4;
|
|
2504
|
+
const wait4 = options.wait ?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
|
|
2505
|
+
const delays = platform4 === "win32" ? WINDOWS_RENAME_RETRY_DELAYS_MS : [];
|
|
2506
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
2507
|
+
try {
|
|
2508
|
+
await renamePath(source, destination);
|
|
2509
|
+
return;
|
|
2510
|
+
} catch (error) {
|
|
2511
|
+
const delay = delays[attempt];
|
|
2512
|
+
if (delay === void 0 || !isWindowsRenameContention(error)) throw error;
|
|
2513
|
+
await wait4(delay);
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
};
|
|
2474
2517
|
var exists = async (filePath) => access(filePath).then(() => true).catch(() => false);
|
|
2518
|
+
var resolveNpmInvocation = async (options = {}) => {
|
|
2519
|
+
const platform4 = options.platform ?? process.platform;
|
|
2520
|
+
if (platform4 !== "win32") return { argsPrefix: [], executable: "npm" };
|
|
2521
|
+
const nodeExecutable = options.nodeExecutable ?? process.execPath;
|
|
2522
|
+
const pathApi = options.pathApi ?? path6;
|
|
2523
|
+
const pathExists = options.pathExists ?? exists;
|
|
2524
|
+
const candidates = [
|
|
2525
|
+
options.npmExecPath ?? process.env.npm_execpath,
|
|
2526
|
+
pathApi.join(pathApi.dirname(nodeExecutable), "node_modules", "npm", "bin", "npm-cli.js")
|
|
2527
|
+
];
|
|
2528
|
+
for (const candidate of candidates) {
|
|
2529
|
+
if (!candidate || pathApi.basename(candidate).toLowerCase() !== "npm-cli.js") continue;
|
|
2530
|
+
if (await pathExists(candidate)) {
|
|
2531
|
+
return { argsPrefix: [candidate], executable: nodeExecutable };
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
throw new Error(
|
|
2535
|
+
`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 ?? "")})`
|
|
2536
|
+
);
|
|
2537
|
+
};
|
|
2475
2538
|
var runtimeRoot = () => process.env.BUDA_CONNECTOR_RUNTIME_ROOT || path6.join(homedir3(), ".buda", "runtime");
|
|
2476
2539
|
var runtimePlatformKey = `${process.platform}-${process.arch}-node${process.versions.modules}`;
|
|
2477
2540
|
var versionDirectory = () => path6.join(runtimeRoot(), runtimePlatformKey, CONNECTOR_RUNTIME_VERSION);
|
|
@@ -2505,9 +2568,11 @@ var validateRuntime = async (runtime) => {
|
|
|
2505
2568
|
};
|
|
2506
2569
|
var installFromNpm = async (directory) => {
|
|
2507
2570
|
await mkdir5(directory, { recursive: true, mode: 448 });
|
|
2571
|
+
const npm = await resolveNpmInvocation();
|
|
2508
2572
|
await execFileAsync(
|
|
2509
|
-
|
|
2573
|
+
npm.executable,
|
|
2510
2574
|
[
|
|
2575
|
+
...npm.argsPrefix,
|
|
2511
2576
|
"install",
|
|
2512
2577
|
"--ignore-scripts=false",
|
|
2513
2578
|
"--no-audit",
|
|
@@ -2541,10 +2606,10 @@ var installFromBundledRuntime = async (directory, sourceRoot) => {
|
|
|
2541
2606
|
if (relativeEntrypoint.startsWith("..") || path6.isAbsolute(relativeEntrypoint)) {
|
|
2542
2607
|
throw new Error("Bundled Connector entrypoint must be inside its runtime root");
|
|
2543
2608
|
}
|
|
2544
|
-
const relativeNode =
|
|
2609
|
+
const { copyNode, relativeNode } = resolveBundledNodePlacement(sourceRoot, sourceNode);
|
|
2545
2610
|
const entrypoint = path6.join(directory, relativeEntrypoint);
|
|
2546
|
-
const nodeExecutable =
|
|
2547
|
-
if (
|
|
2611
|
+
const nodeExecutable = copyNode ? path6.join(directory, "node", path6.basename(sourceNode)) : path6.join(directory, relativeNode);
|
|
2612
|
+
if (copyNode) {
|
|
2548
2613
|
await copyNodeRuntime(sourceNode, nodeExecutable);
|
|
2549
2614
|
}
|
|
2550
2615
|
if (!await exists(entrypoint))
|
|
@@ -2591,7 +2656,7 @@ async function ensureStableConnectorRuntime() {
|
|
|
2591
2656
|
};
|
|
2592
2657
|
await writeManifest(temporary, rebased);
|
|
2593
2658
|
await rm(finalDirectory, { recursive: true, force: true });
|
|
2594
|
-
await
|
|
2659
|
+
await renameStagedRuntime(temporary, finalDirectory);
|
|
2595
2660
|
return rebased;
|
|
2596
2661
|
} catch (error) {
|
|
2597
2662
|
await rm(temporary, { recursive: true, force: true });
|
|
@@ -2605,6 +2670,8 @@ async function ensureStableConnectorRuntime() {
|
|
|
2605
2670
|
// src/daemon-client.ts
|
|
2606
2671
|
var START_TIMEOUT_MS = 45e3;
|
|
2607
2672
|
var BOOTSTRAP_TIMEOUT_MS = 5 * 6e4;
|
|
2673
|
+
var GRACEFUL_STOP_TIMEOUT_MS = 3e3;
|
|
2674
|
+
var LOCK_ONLY_RECOVERY_MAX_AGE_MS = 5 * 6e4;
|
|
2608
2675
|
var POLL_INTERVAL_MS = 250;
|
|
2609
2676
|
var wait2 = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
2610
2677
|
var getConfigRoot = () => process.env.BUDA_CONNECTOR_CONFIG_ROOT;
|
|
@@ -2637,14 +2704,78 @@ var compareVersions = (left, right) => {
|
|
|
2637
2704
|
return 0;
|
|
2638
2705
|
};
|
|
2639
2706
|
var shouldReplaceDaemonVersion = (runningVersion, candidateVersion) => compareVersions(candidateVersion, runningVersion) > 0;
|
|
2707
|
+
var shouldDisplayConnectorLogEntry = (createdAt, startedAt) => {
|
|
2708
|
+
const timestamp = Date.parse(createdAt);
|
|
2709
|
+
return Number.isNaN(timestamp) || timestamp >= startedAt;
|
|
2710
|
+
};
|
|
2711
|
+
var getErrorCode = (error) => typeof error === "object" && error !== null && "code" in error ? String(error.code) : "UNKNOWN";
|
|
2712
|
+
var waitForChildSpawn = (child) => new Promise((resolve, reject) => {
|
|
2713
|
+
const onError = (error) => {
|
|
2714
|
+
child.off("spawn", onSpawn);
|
|
2715
|
+
reject(error);
|
|
2716
|
+
};
|
|
2717
|
+
const onSpawn = () => {
|
|
2718
|
+
child.off("error", onError);
|
|
2719
|
+
resolve();
|
|
2720
|
+
};
|
|
2721
|
+
child.once("error", onError);
|
|
2722
|
+
child.once("spawn", onSpawn);
|
|
2723
|
+
});
|
|
2724
|
+
async function spawnDetachedDaemonProcess(command, args, options, dependencies = {}) {
|
|
2725
|
+
const platform4 = dependencies.platform ?? process.platform;
|
|
2726
|
+
const spawnProcess = dependencies.spawnProcess ?? ((file, fileArgs, spawnOptions) => spawn(file, fileArgs, spawnOptions));
|
|
2727
|
+
const strategies = platform4 === "win32" ? [
|
|
2728
|
+
{ name: "detached-hidden", windowsHide: true },
|
|
2729
|
+
{ name: "detached-unhidden", windowsHide: false }
|
|
2730
|
+
] : [{ name: "detached-hidden", windowsHide: true }];
|
|
2731
|
+
const attempted = [];
|
|
2732
|
+
for (const [index, strategy] of strategies.entries()) {
|
|
2733
|
+
attempted.push(strategy.name);
|
|
2734
|
+
const child = spawnProcess(command, args, {
|
|
2735
|
+
detached: true,
|
|
2736
|
+
env: options.env,
|
|
2737
|
+
stdio: options.stdio,
|
|
2738
|
+
windowsHide: strategy.windowsHide
|
|
2739
|
+
});
|
|
2740
|
+
try {
|
|
2741
|
+
await waitForChildSpawn(child);
|
|
2742
|
+
if (!child.pid) {
|
|
2743
|
+
throw Object.assign(new Error("spawned Connector daemon did not expose a PID"), {
|
|
2744
|
+
code: "NO_PID"
|
|
2745
|
+
});
|
|
2746
|
+
}
|
|
2747
|
+
child.unref();
|
|
2748
|
+
return { pid: child.pid, strategy: strategy.name };
|
|
2749
|
+
} catch (error) {
|
|
2750
|
+
const code = getErrorCode(error);
|
|
2751
|
+
const shouldRetry = platform4 === "win32" && index === 0 && code === "EINVAL";
|
|
2752
|
+
if (shouldRetry) continue;
|
|
2753
|
+
const diagnostics = [
|
|
2754
|
+
`code=${code}`,
|
|
2755
|
+
`strategies=${attempted.join(",")}`,
|
|
2756
|
+
`platform=${platform4}`,
|
|
2757
|
+
`node=${dependencies.nodeVersion ?? process.version}`,
|
|
2758
|
+
`executable=${JSON.stringify(command)}`,
|
|
2759
|
+
`entrypoint=${JSON.stringify(args[0] ?? "")}`,
|
|
2760
|
+
`cwd=${JSON.stringify(dependencies.cwd ?? process.cwd())}`
|
|
2761
|
+
].join(" ");
|
|
2762
|
+
throw new Error(`Connector daemon process creation failed: ${diagnostics}`, { cause: error });
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
throw new Error("Connector daemon process creation failed without a spawn attempt");
|
|
2766
|
+
}
|
|
2767
|
+
var readReadyDaemon = async (launchConfig) => {
|
|
2768
|
+
const config = await readDeviceConfig(launchConfig, getConfigRoot());
|
|
2769
|
+
if (!config) return null;
|
|
2770
|
+
const status = await readLiveStatus(config);
|
|
2771
|
+
return status ? { config, status } : null;
|
|
2772
|
+
};
|
|
2640
2773
|
var waitForDaemon = async (launchConfig, deadline) => {
|
|
2641
2774
|
let lastConfig = null;
|
|
2642
2775
|
while (Date.now() < deadline) {
|
|
2776
|
+
const ready = await readReadyDaemon(launchConfig);
|
|
2777
|
+
if (ready) return ready;
|
|
2643
2778
|
lastConfig = await readDeviceConfig(launchConfig, getConfigRoot());
|
|
2644
|
-
if (lastConfig) {
|
|
2645
|
-
const status = await readLiveStatus(lastConfig);
|
|
2646
|
-
if (status) return { config: lastConfig, status };
|
|
2647
|
-
}
|
|
2648
2779
|
await wait2(POLL_INTERVAL_MS);
|
|
2649
2780
|
}
|
|
2650
2781
|
throw new Error(
|
|
@@ -2653,17 +2784,126 @@ var waitForDaemon = async (launchConfig, deadline) => {
|
|
|
2653
2784
|
};
|
|
2654
2785
|
var spawnInternalDaemon = async (launchConfig, mode, preparedRuntime, options = {}) => {
|
|
2655
2786
|
const runtime = preparedRuntime ?? await ensureStableConnectorRuntime();
|
|
2656
|
-
const
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2787
|
+
const debugDaemonStdio = process.env.BUDA_CONNECTOR_DEBUG_DAEMON_STDIO === "1";
|
|
2788
|
+
return spawnDetachedDaemonProcess(
|
|
2789
|
+
runtime.nodeExecutable,
|
|
2790
|
+
getRuntimeCommandArgs(runtime, mode, options),
|
|
2791
|
+
{
|
|
2792
|
+
env: {
|
|
2793
|
+
...process.env,
|
|
2794
|
+
BUDA_CONNECTOR_INTERNAL_LAUNCH_CONFIG: JSON.stringify(launchConfig ?? {})
|
|
2795
|
+
},
|
|
2796
|
+
stdio: debugDaemonStdio ? ["ignore", "inherit", "inherit"] : "ignore"
|
|
2797
|
+
}
|
|
2798
|
+
);
|
|
2799
|
+
};
|
|
2800
|
+
var waitForProcessExit = async (pid, timeoutMs) => {
|
|
2801
|
+
const deadline = Date.now() + timeoutMs;
|
|
2802
|
+
while (Date.now() < deadline) {
|
|
2803
|
+
if (!isProcessAlive2(pid)) return true;
|
|
2804
|
+
await wait2(50);
|
|
2805
|
+
}
|
|
2806
|
+
return !isProcessAlive2(pid);
|
|
2807
|
+
};
|
|
2808
|
+
var signalProcess = (pid, signal) => {
|
|
2809
|
+
try {
|
|
2810
|
+
process.kill(pid, signal);
|
|
2811
|
+
return true;
|
|
2812
|
+
} catch (error) {
|
|
2813
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "ESRCH") {
|
|
2814
|
+
return false;
|
|
2815
|
+
}
|
|
2816
|
+
throw error;
|
|
2817
|
+
}
|
|
2818
|
+
};
|
|
2819
|
+
var stopUnresponsiveDaemonProcess = async (pid) => {
|
|
2820
|
+
if (!isProcessAlive2(pid)) return;
|
|
2821
|
+
if (!signalProcess(pid, "SIGTERM")) return;
|
|
2822
|
+
if (await waitForProcessExit(pid, 2e3)) return;
|
|
2823
|
+
if (!signalProcess(pid, "SIGKILL")) return;
|
|
2824
|
+
if (await waitForProcessExit(pid, 3e3)) return;
|
|
2825
|
+
throw new Error(`Timed-out Connector daemon PID ${pid} could not be stopped safely`);
|
|
2826
|
+
};
|
|
2827
|
+
var waitForDaemonRelease = async (config, status, configRoot = getConfigRoot()) => {
|
|
2828
|
+
const deadline = Date.now() + GRACEFUL_STOP_TIMEOUT_MS;
|
|
2829
|
+
while (Date.now() < deadline) {
|
|
2830
|
+
if (!isProcessAlive2(status.pid)) return true;
|
|
2831
|
+
const current = await readPersistedDaemonStatus(config, configRoot).catch(() => null);
|
|
2832
|
+
if (!current || current.pid !== status.pid || current.instanceId !== status.instanceId) {
|
|
2833
|
+
return true;
|
|
2834
|
+
}
|
|
2835
|
+
await wait2(50);
|
|
2836
|
+
}
|
|
2837
|
+
return !isProcessAlive2(status.pid);
|
|
2666
2838
|
};
|
|
2839
|
+
var stopLiveDaemon = async (config, status, configRoot = getConfigRoot(), command = "stop") => {
|
|
2840
|
+
const stopStatus = await requestDaemonControl(config, command, configRoot);
|
|
2841
|
+
if (command === "stop-if-idle" && !stopStatus.idle) return false;
|
|
2842
|
+
if (await waitForDaemonRelease(config, status, configRoot)) return true;
|
|
2843
|
+
const current = await readPersistedDaemonStatus(config, configRoot).catch(() => null);
|
|
2844
|
+
if (!current || current.pid !== status.pid || current.instanceId !== status.instanceId) {
|
|
2845
|
+
return true;
|
|
2846
|
+
}
|
|
2847
|
+
await stopUnresponsiveDaemonProcess(status.pid);
|
|
2848
|
+
return true;
|
|
2849
|
+
};
|
|
2850
|
+
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;
|
|
2851
|
+
var readMatchingPersistedDaemonStatus = async (config, configRoot = getConfigRoot()) => {
|
|
2852
|
+
const status = await readPersistedDaemonStatus(config, configRoot).catch(() => null);
|
|
2853
|
+
return status && matchesDaemonIdentity(config, status) ? status : null;
|
|
2854
|
+
};
|
|
2855
|
+
var stopDaemonForIdentity = async (config, configRoot = getConfigRoot()) => {
|
|
2856
|
+
const liveStatus = await readLiveStatus(config, configRoot);
|
|
2857
|
+
if (liveStatus) {
|
|
2858
|
+
await stopLiveDaemon(config, liveStatus, configRoot);
|
|
2859
|
+
return;
|
|
2860
|
+
}
|
|
2861
|
+
const persistedStatus = await readMatchingPersistedDaemonStatus(config, configRoot);
|
|
2862
|
+
if (!persistedStatus || !isProcessAlive2(persistedStatus.pid)) return;
|
|
2863
|
+
const currentStatus = await readMatchingPersistedDaemonStatus(config, configRoot);
|
|
2864
|
+
if (!currentStatus || currentStatus.pid !== persistedStatus.pid || currentStatus.instanceId !== persistedStatus.instanceId || !isProcessAlive2(currentStatus.pid)) {
|
|
2865
|
+
return;
|
|
2866
|
+
}
|
|
2867
|
+
await stopUnresponsiveDaemonProcess(currentStatus.pid);
|
|
2868
|
+
};
|
|
2869
|
+
var shouldRestartUnhealthyDaemonForEnrollment = (status, explicitEnrollment) => explicitEnrollment && status.idle && status.heartbeatHealthy === false;
|
|
2870
|
+
var readDaemonLockOwnerPid = async (config) => {
|
|
2871
|
+
const lockPath = `${getConnectorDaemonLockTarget(
|
|
2872
|
+
config.serverUrl,
|
|
2873
|
+
config.connectorId,
|
|
2874
|
+
getConfigRoot()
|
|
2875
|
+
)}.lock`;
|
|
2876
|
+
const raw = await readFile6(lockPath, "utf8").catch(() => "");
|
|
2877
|
+
try {
|
|
2878
|
+
const metadata = JSON.parse(raw);
|
|
2879
|
+
const age = typeof metadata.createdAt === "number" && Number.isFinite(metadata.createdAt) ? Date.now() - metadata.createdAt : Number.POSITIVE_INFINITY;
|
|
2880
|
+
return typeof metadata.pid === "number" && Number.isSafeInteger(metadata.pid) && metadata.pid > 0 && age >= 0 && age <= LOCK_ONLY_RECOVERY_MAX_AGE_MS ? metadata.pid : null;
|
|
2881
|
+
} catch {
|
|
2882
|
+
return null;
|
|
2883
|
+
}
|
|
2884
|
+
};
|
|
2885
|
+
async function startDaemonWithRecovery(dependencies, maxAttempts = 2) {
|
|
2886
|
+
let lastError;
|
|
2887
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
2888
|
+
const spawned = await dependencies.spawnDaemon();
|
|
2889
|
+
try {
|
|
2890
|
+
return await dependencies.waitForReady();
|
|
2891
|
+
} catch (error) {
|
|
2892
|
+
lastError = error;
|
|
2893
|
+
const lateReady = await dependencies.readReady().catch(() => null);
|
|
2894
|
+
if (lateReady) return lateReady;
|
|
2895
|
+
await dependencies.stopDaemon(spawned.pid);
|
|
2896
|
+
if (attempt === maxAttempts) throw error;
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
throw lastError;
|
|
2900
|
+
}
|
|
2901
|
+
var startInternalDaemonWithRecovery = (launchConfig, mode, preparedRuntime, options = {}) => startDaemonWithRecovery({
|
|
2902
|
+
readReady: () => readReadyDaemon(launchConfig),
|
|
2903
|
+
spawnDaemon: () => spawnInternalDaemon(launchConfig, mode, preparedRuntime, options),
|
|
2904
|
+
stopDaemon: stopUnresponsiveDaemonProcess,
|
|
2905
|
+
waitForReady: () => waitForDaemon(launchConfig, Date.now() + START_TIMEOUT_MS)
|
|
2906
|
+
});
|
|
2667
2907
|
var stopOlderIdleDaemon = async (config, status) => {
|
|
2668
2908
|
if (!shouldReplaceDaemonVersion(status.version, CONNECTOR_RUNTIME_VERSION)) return false;
|
|
2669
2909
|
if (!status.idle) return false;
|
|
@@ -2690,14 +2930,19 @@ async function ensureConnectorDaemon(launchConfig, mode, options = {}) {
|
|
|
2690
2930
|
if (existingConfig) {
|
|
2691
2931
|
const existingStatus = await readLiveStatus(existingConfig);
|
|
2692
2932
|
if (existingStatus) {
|
|
2933
|
+
if (shouldRestartUnhealthyDaemonForEnrollment(
|
|
2934
|
+
existingStatus,
|
|
2935
|
+
Boolean(options.explicitEnrollment)
|
|
2936
|
+
) && await stopLiveDaemon(existingConfig, existingStatus, getConfigRoot(), "stop-if-idle")) {
|
|
2937
|
+
return await startInternalDaemonWithRecovery(launchConfig, mode, void 0, options);
|
|
2938
|
+
}
|
|
2693
2939
|
const preparedRuntime = !shouldReplaceDaemonVersion(existingStatus.version, CONNECTOR_RUNTIME_VERSION) || !existingStatus.idle ? void 0 : await ensureStableConnectorRuntime();
|
|
2694
2940
|
const replaced = await stopOlderIdleDaemon(existingConfig, existingStatus);
|
|
2695
2941
|
if (!replaced) {
|
|
2696
2942
|
const status = mode === "persistent" ? await requestDaemonControl(existingConfig, "persist", getConfigRoot()) : existingStatus;
|
|
2697
2943
|
return { config: existingConfig, status };
|
|
2698
2944
|
}
|
|
2699
|
-
await
|
|
2700
|
-
return await waitForDaemon(launchConfig, Date.now() + START_TIMEOUT_MS);
|
|
2945
|
+
return await startInternalDaemonWithRecovery(launchConfig, mode, preparedRuntime, options);
|
|
2701
2946
|
}
|
|
2702
2947
|
const persistedStatus = await readPersistedDaemonStatus(
|
|
2703
2948
|
existingConfig,
|
|
@@ -2708,9 +2953,12 @@ async function ensureConnectorDaemon(launchConfig, mode, options = {}) {
|
|
|
2708
2953
|
`Connector daemon PID ${persistedStatus.pid} is running but its local control channel is not responding. Stop that process and retry.`
|
|
2709
2954
|
);
|
|
2710
2955
|
}
|
|
2956
|
+
const lockOwnerPid = await readDaemonLockOwnerPid(existingConfig);
|
|
2957
|
+
if (lockOwnerPid && isProcessAlive2(lockOwnerPid)) {
|
|
2958
|
+
await stopUnresponsiveDaemonProcess(lockOwnerPid);
|
|
2959
|
+
}
|
|
2711
2960
|
}
|
|
2712
|
-
await
|
|
2713
|
-
return await waitForDaemon(launchConfig, Date.now() + START_TIMEOUT_MS);
|
|
2961
|
+
return await startInternalDaemonWithRecovery(launchConfig, mode, void 0, options);
|
|
2714
2962
|
} finally {
|
|
2715
2963
|
await bootstrapLock.release();
|
|
2716
2964
|
}
|
|
@@ -2730,17 +2978,18 @@ var streamDaemonLogs = (options) => {
|
|
|
2730
2978
|
for (const line of lines.slice(seenLines)) {
|
|
2731
2979
|
try {
|
|
2732
2980
|
const entry = JSON.parse(line);
|
|
2981
|
+
if (!shouldDisplayConnectorLogEntry(entry.createdAt, options.startedAt)) continue;
|
|
2733
2982
|
const connectorId = entry.metadata?.connectorId;
|
|
2734
2983
|
if (options.dashboardActive && typeof connectorId === "string") {
|
|
2735
|
-
const
|
|
2736
|
-
appendConnectorDashboardLog(connectorId, entry.level,
|
|
2984
|
+
const output3 = entry.metadata?.source === "daemon-console" ? entry.message : `[buda-connector] ${entry.message}`;
|
|
2985
|
+
appendConnectorDashboardLog(connectorId, entry.level, output3);
|
|
2737
2986
|
continue;
|
|
2738
2987
|
}
|
|
2739
2988
|
if (connectorId !== options.connectorId) continue;
|
|
2740
|
-
const
|
|
2741
|
-
if (entry.level === "error") console.error(
|
|
2742
|
-
else if (entry.level === "warn") console.warn(
|
|
2743
|
-
else console.log(
|
|
2989
|
+
const output2 = entry.metadata?.source === "daemon-console" ? entry.message : `[buda-connector] ${entry.message}`;
|
|
2990
|
+
if (entry.level === "error") console.error(output2);
|
|
2991
|
+
else if (entry.level === "warn") console.warn(output2);
|
|
2992
|
+
else console.log(output2);
|
|
2744
2993
|
} catch {
|
|
2745
2994
|
if (options.plain) console.log(line);
|
|
2746
2995
|
}
|
|
@@ -2805,9 +3054,7 @@ async function stopLocalConnectorDaemon(connectorId, serverUrl, configRoot = get
|
|
|
2805
3054
|
(candidate) => candidate.connectorId === connectorId && normalizeServerOrigin(candidate.serverUrl) === serverOrigin
|
|
2806
3055
|
);
|
|
2807
3056
|
if (!config) throw new Error(`Local Connector ${connectorId} was not found`);
|
|
2808
|
-
|
|
2809
|
-
if (!status) return;
|
|
2810
|
-
await requestDaemonControl(config, "stop", configRoot);
|
|
3057
|
+
await stopDaemonForIdentity(config, configRoot);
|
|
2811
3058
|
}
|
|
2812
3059
|
async function stopAllLocalConnectorDaemons(configRoot = getConfigRoot()) {
|
|
2813
3060
|
const configs = await listDeviceConfigs(configRoot);
|
|
@@ -2818,10 +3065,7 @@ async function stopAllLocalConnectorDaemons(configRoot = getConfigRoot()) {
|
|
|
2818
3065
|
${config.connectorId}`, config);
|
|
2819
3066
|
}
|
|
2820
3067
|
const results = await Promise.allSettled(
|
|
2821
|
-
[...identities.values()].map(
|
|
2822
|
-
const status = await readLiveStatus(config, configRoot);
|
|
2823
|
-
if (status) await requestDaemonControl(config, "stop", configRoot);
|
|
2824
|
-
})
|
|
3068
|
+
[...identities.values()].map((config) => stopDaemonForIdentity(config, configRoot))
|
|
2825
3069
|
);
|
|
2826
3070
|
const failures = results.filter((result) => result.status === "rejected");
|
|
2827
3071
|
if (failures.length) {
|
|
@@ -2840,6 +3084,7 @@ var waitForConnectorViewerExit = async (dashboardClosed) => {
|
|
|
2840
3084
|
process.off("SIGTERM", finishSignal);
|
|
2841
3085
|
};
|
|
2842
3086
|
async function runConnectorDashboard(launchConfig, options = {}) {
|
|
3087
|
+
const startedAt = Date.now();
|
|
2843
3088
|
let finishDashboard = () => void 0;
|
|
2844
3089
|
const dashboardClosed = new Promise((resolve) => {
|
|
2845
3090
|
finishDashboard = resolve;
|
|
@@ -2860,7 +3105,8 @@ async function runConnectorDashboard(launchConfig, options = {}) {
|
|
|
2860
3105
|
connectorId: config.connectorId,
|
|
2861
3106
|
dashboardActive: false,
|
|
2862
3107
|
plain: Boolean(options.plain),
|
|
2863
|
-
serverUrl: config.serverUrl
|
|
3108
|
+
serverUrl: config.serverUrl,
|
|
3109
|
+
startedAt
|
|
2864
3110
|
});
|
|
2865
3111
|
if (!process.stdin.isTTY) {
|
|
2866
3112
|
stopLogs2();
|
|
@@ -2887,7 +3133,8 @@ async function runConnectorDashboard(launchConfig, options = {}) {
|
|
|
2887
3133
|
const stopLogs = streamDaemonLogs({
|
|
2888
3134
|
dashboardActive: true,
|
|
2889
3135
|
plain: false,
|
|
2890
|
-
serverUrl: getServerUrl(launchConfig)
|
|
3136
|
+
serverUrl: getServerUrl(launchConfig),
|
|
3137
|
+
startedAt
|
|
2891
3138
|
});
|
|
2892
3139
|
let refreshing = false;
|
|
2893
3140
|
const refresh = async () => {
|
|
@@ -2983,9 +3230,6 @@ var getDaemonModeFlag = () => {
|
|
|
2983
3230
|
return process.argv[index + 1] === "transient" ? "transient" : "persistent";
|
|
2984
3231
|
};
|
|
2985
3232
|
|
|
2986
|
-
// src/onboard.ts
|
|
2987
|
-
import { cancel, intro, isCancel, log, note, outro, spinner, text } from "@clack/prompts";
|
|
2988
|
-
|
|
2989
3233
|
// src/bunny-daemon.ts
|
|
2990
3234
|
import { mkdir as mkdir7 } from "fs/promises";
|
|
2991
3235
|
import { createDaemon } from "@bunny-agent/daemon";
|
|
@@ -3224,17 +3468,17 @@ var installConnectorTerminalEndpoint = (server, options = {}) => {
|
|
|
3224
3468
|
sessions2.clear();
|
|
3225
3469
|
});
|
|
3226
3470
|
};
|
|
3227
|
-
var registerLocalTerminal = (
|
|
3228
|
-
if (!isSafeId(
|
|
3471
|
+
var registerLocalTerminal = (input2) => {
|
|
3472
|
+
if (!isSafeId(input2.workspaceKey) || !isSafeId(input2.tabId)) {
|
|
3229
3473
|
throw new Error("Invalid local terminal workspace session.");
|
|
3230
3474
|
}
|
|
3231
3475
|
const token = randomBytes3(32).toString("base64url");
|
|
3232
3476
|
localGrants.set(token, {
|
|
3233
|
-
sessionKey: `${
|
|
3234
|
-
workspaceKey:
|
|
3235
|
-
cwd:
|
|
3236
|
-
cols:
|
|
3237
|
-
rows:
|
|
3477
|
+
sessionKey: `${input2.workspaceKey}:${input2.tabId}`,
|
|
3478
|
+
workspaceKey: input2.workspaceKey,
|
|
3479
|
+
cwd: input2.cwd,
|
|
3480
|
+
cols: input2.cols,
|
|
3481
|
+
rows: input2.rows,
|
|
3238
3482
|
expiresAt: Date.now() + LOCAL_TERMINAL_GRANT_TTL_MS
|
|
3239
3483
|
});
|
|
3240
3484
|
const cleanupTimer = setTimeout(() => localGrants.delete(token), LOCAL_TERMINAL_GRANT_TTL_MS);
|
|
@@ -3258,16 +3502,16 @@ var connectSocket = async (socket) => new Promise((resolve, reject) => {
|
|
|
3258
3502
|
socket.once("open", onOpen);
|
|
3259
3503
|
socket.once("error", onError);
|
|
3260
3504
|
});
|
|
3261
|
-
var proxyConnectorTerminalTunnel = async (
|
|
3505
|
+
var proxyConnectorTerminalTunnel = async (input2) => {
|
|
3262
3506
|
const cloud = new WebSocket(
|
|
3263
|
-
getConnectorTerminalUrl(
|
|
3507
|
+
getConnectorTerminalUrl(input2.serverUrl, input2.connectorId, input2.sessionId),
|
|
3264
3508
|
{
|
|
3265
|
-
headers: { "X-Buda-Terminal-Token":
|
|
3509
|
+
headers: { "X-Buda-Terminal-Token": input2.cloudToken },
|
|
3266
3510
|
handshakeTimeout: TERMINAL_HANDSHAKE_TIMEOUT_MS
|
|
3267
3511
|
}
|
|
3268
3512
|
);
|
|
3269
|
-
const local = new WebSocket(toLocalTerminalUrl(
|
|
3270
|
-
headers: { "X-Buda-Local-Terminal-Token":
|
|
3513
|
+
const local = new WebSocket(toLocalTerminalUrl(input2.localBaseUrl), {
|
|
3514
|
+
headers: { "X-Buda-Local-Terminal-Token": input2.localToken },
|
|
3271
3515
|
handshakeTimeout: TERMINAL_HANDSHAKE_TIMEOUT_MS
|
|
3272
3516
|
});
|
|
3273
3517
|
let bridgeAccepted = false;
|
|
@@ -3278,7 +3522,7 @@ var proxyConnectorTerminalTunnel = async (input) => {
|
|
|
3278
3522
|
const endActivity = () => {
|
|
3279
3523
|
if (!activityStarted || activityEnded) return;
|
|
3280
3524
|
activityEnded = true;
|
|
3281
|
-
|
|
3525
|
+
input2.onActivityEnd?.();
|
|
3282
3526
|
};
|
|
3283
3527
|
let pendingBytes = 0;
|
|
3284
3528
|
const pendingCloudFrames = [];
|
|
@@ -3334,7 +3578,7 @@ var proxyConnectorTerminalTunnel = async (input) => {
|
|
|
3334
3578
|
if (!bridgeAccepted) {
|
|
3335
3579
|
void writeLog(
|
|
3336
3580
|
"warn",
|
|
3337
|
-
`terminal cloud tunnel closed before readiness session=${
|
|
3581
|
+
`terminal cloud tunnel closed before readiness session=${input2.sessionId} code=${code} reason=${normalizedReason}`
|
|
3338
3582
|
);
|
|
3339
3583
|
}
|
|
3340
3584
|
if (local.readyState === WebSocket.OPEN) {
|
|
@@ -3372,12 +3616,12 @@ var proxyConnectorTerminalTunnel = async (input) => {
|
|
|
3372
3616
|
if (cloud.readyState !== WebSocket.OPEN) return;
|
|
3373
3617
|
if (local.readyState === WebSocket.OPEN) {
|
|
3374
3618
|
activityStarted = true;
|
|
3375
|
-
|
|
3619
|
+
input2.onActivityStart?.();
|
|
3376
3620
|
}
|
|
3377
3621
|
cloud.send(JSON.stringify({ type: "bridge-ready" }));
|
|
3378
3622
|
await writeLog(
|
|
3379
3623
|
"info",
|
|
3380
|
-
`terminal proxy connected session=${
|
|
3624
|
+
`terminal proxy connected session=${input2.sessionId} workspace=${input2.workspaceKey}`
|
|
3381
3625
|
);
|
|
3382
3626
|
};
|
|
3383
3627
|
|
|
@@ -3783,6 +4027,7 @@ var writeJournal = async (journalPath, journal) => {
|
|
|
3783
4027
|
}
|
|
3784
4028
|
};
|
|
3785
4029
|
var syncDirectory2 = async (directory) => {
|
|
4030
|
+
if (process.platform === "win32") return;
|
|
3786
4031
|
const handle = await open2(directory, constants2.O_RDONLY);
|
|
3787
4032
|
try {
|
|
3788
4033
|
await handle.sync();
|
|
@@ -3960,6 +4205,86 @@ var verifyFailure = (detail) => new LegacyWorkspaceMigrationError(
|
|
|
3960
4205
|
var corrupt = (message) => new LegacyWorkspaceMigrationError("MIGRATION_CORRUPT", message);
|
|
3961
4206
|
var errorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
3962
4207
|
|
|
4208
|
+
// src/prompts.ts
|
|
4209
|
+
import { stdin as input, stdout as output } from "process";
|
|
4210
|
+
import { createInterface } from "readline/promises";
|
|
4211
|
+
var CANCELLED = /* @__PURE__ */ Symbol("prompt-cancelled");
|
|
4212
|
+
function printLine(prefix, message) {
|
|
4213
|
+
output.write(`${prefix}${message}
|
|
4214
|
+
`);
|
|
4215
|
+
}
|
|
4216
|
+
function intro(message) {
|
|
4217
|
+
printLine("\n", message);
|
|
4218
|
+
}
|
|
4219
|
+
function outro(message) {
|
|
4220
|
+
printLine("", message);
|
|
4221
|
+
}
|
|
4222
|
+
function cancel(message) {
|
|
4223
|
+
printLine("", message);
|
|
4224
|
+
}
|
|
4225
|
+
function note(message, title) {
|
|
4226
|
+
if (title) {
|
|
4227
|
+
printLine("", `${title}
|
|
4228
|
+
${message}`);
|
|
4229
|
+
return;
|
|
4230
|
+
}
|
|
4231
|
+
printLine("", message);
|
|
4232
|
+
}
|
|
4233
|
+
var log = {
|
|
4234
|
+
info(message) {
|
|
4235
|
+
printLine("", message);
|
|
4236
|
+
},
|
|
4237
|
+
error(message) {
|
|
4238
|
+
printLine("", message);
|
|
4239
|
+
},
|
|
4240
|
+
step(message) {
|
|
4241
|
+
printLine("", message);
|
|
4242
|
+
}
|
|
4243
|
+
};
|
|
4244
|
+
function isCancel(value) {
|
|
4245
|
+
return value === CANCELLED;
|
|
4246
|
+
}
|
|
4247
|
+
function spinner() {
|
|
4248
|
+
let activeMessage = null;
|
|
4249
|
+
return {
|
|
4250
|
+
start(message) {
|
|
4251
|
+
activeMessage = message;
|
|
4252
|
+
printLine("", message);
|
|
4253
|
+
},
|
|
4254
|
+
stop(message) {
|
|
4255
|
+
if (message) {
|
|
4256
|
+
printLine("", message);
|
|
4257
|
+
} else if (activeMessage) {
|
|
4258
|
+
printLine("", activeMessage);
|
|
4259
|
+
}
|
|
4260
|
+
activeMessage = null;
|
|
4261
|
+
}
|
|
4262
|
+
};
|
|
4263
|
+
}
|
|
4264
|
+
async function text(options) {
|
|
4265
|
+
if (!input.isTTY || !output.isTTY) {
|
|
4266
|
+
const fallback = options.defaultValue ?? "";
|
|
4267
|
+
const validationError = options.validate?.(fallback);
|
|
4268
|
+
return validationError ? CANCELLED : fallback;
|
|
4269
|
+
}
|
|
4270
|
+
const rl = createInterface({ input, output });
|
|
4271
|
+
const hint = options.defaultValue ?? options.placeholder;
|
|
4272
|
+
const suffix = hint ? ` (${hint})` : "";
|
|
4273
|
+
try {
|
|
4274
|
+
while (true) {
|
|
4275
|
+
const answer = await rl.question(`${options.message}${suffix}: `);
|
|
4276
|
+
const value = answer.trim() || options.defaultValue || "";
|
|
4277
|
+
const validationError = options.validate?.(value);
|
|
4278
|
+
if (!validationError) return value;
|
|
4279
|
+
printLine("", validationError);
|
|
4280
|
+
}
|
|
4281
|
+
} catch {
|
|
4282
|
+
return CANCELLED;
|
|
4283
|
+
} finally {
|
|
4284
|
+
rl.close();
|
|
4285
|
+
}
|
|
4286
|
+
}
|
|
4287
|
+
|
|
3963
4288
|
// src/relay.ts
|
|
3964
4289
|
import { realpathSync } from "fs";
|
|
3965
4290
|
import path12 from "path";
|
|
@@ -4050,7 +4375,10 @@ function verifyConnectorExecutionIdentity(options) {
|
|
|
4050
4375
|
const signature = decodeSignature(identity.signature);
|
|
4051
4376
|
let verified = false;
|
|
4052
4377
|
try {
|
|
4053
|
-
const publicKey = createPublicKey({
|
|
4378
|
+
const publicKey = createPublicKey({
|
|
4379
|
+
key: protocol.publicKeyJwk,
|
|
4380
|
+
format: "jwk"
|
|
4381
|
+
});
|
|
4054
4382
|
if (publicKey.asymmetricKeyType !== "ed25519") {
|
|
4055
4383
|
throw new ConnectorRelayProtocolError("PROTOCOL_UNSUPPORTED");
|
|
4056
4384
|
}
|
|
@@ -4157,8 +4485,8 @@ var MIME_TYPES = {
|
|
|
4157
4485
|
};
|
|
4158
4486
|
var HtmlPreviewServerRegistry = class {
|
|
4159
4487
|
servers = /* @__PURE__ */ new Map();
|
|
4160
|
-
async open(
|
|
4161
|
-
const htmlFile = await resolveWorkspaceFile(
|
|
4488
|
+
async open(input2) {
|
|
4489
|
+
const htmlFile = await resolveWorkspaceFile(input2);
|
|
4162
4490
|
const folder = path10.dirname(htmlFile);
|
|
4163
4491
|
let pending = this.servers.get(folder);
|
|
4164
4492
|
if (!pending) {
|
|
@@ -4216,10 +4544,10 @@ var handleHtmlPreviewRequest = async (request, mounts, registry) => {
|
|
|
4216
4544
|
);
|
|
4217
4545
|
}
|
|
4218
4546
|
};
|
|
4219
|
-
var resolveWorkspaceFile = async (
|
|
4220
|
-
const virtualPath =
|
|
4547
|
+
var resolveWorkspaceFile = async (input2) => {
|
|
4548
|
+
const virtualPath = input2.filePath.replaceAll("\\", "/");
|
|
4221
4549
|
if (virtualPath.includes("\0")) throw new Error("HTML preview path contains invalid data.");
|
|
4222
|
-
const mountPath = path10.resolve(
|
|
4550
|
+
const mountPath = path10.resolve(input2.mounts[input2.rootVolume]);
|
|
4223
4551
|
const root = await realpath3(mountPath);
|
|
4224
4552
|
let relativePath = virtualPath;
|
|
4225
4553
|
let explicitVolume;
|
|
@@ -4231,7 +4559,7 @@ var resolveWorkspaceFile = async (input) => {
|
|
|
4231
4559
|
break;
|
|
4232
4560
|
}
|
|
4233
4561
|
}
|
|
4234
|
-
if (explicitVolume && explicitVolume !==
|
|
4562
|
+
if (explicitVolume && explicitVolume !== input2.rootVolume) {
|
|
4235
4563
|
throw new Error("HTML preview path does not match the requested root volume.");
|
|
4236
4564
|
}
|
|
4237
4565
|
if (!explicitVolume && (path10.isAbsolute(virtualPath) || /^[A-Za-z]:\//.test(virtualPath))) {
|
|
@@ -4356,6 +4684,11 @@ import {
|
|
|
4356
4684
|
writeFile as writeFile4
|
|
4357
4685
|
} from "fs/promises";
|
|
4358
4686
|
import path11 from "path";
|
|
4687
|
+
|
|
4688
|
+
// src/directory-link.ts
|
|
4689
|
+
var directoryLinkType = (platform4 = process.platform) => platform4 === "win32" ? "junction" : "dir";
|
|
4690
|
+
|
|
4691
|
+
// src/workspace-volume-lifecycle.ts
|
|
4359
4692
|
var MOUNT_DIRECTORY = "mount";
|
|
4360
4693
|
var ARCHIVE_MARKER = "archived.json";
|
|
4361
4694
|
var AGENT_CONTEXT_TEMPLATE_FILES = [
|
|
@@ -4507,7 +4840,7 @@ var ensureVolumeLink = async (target, linkPath) => {
|
|
|
4507
4840
|
} catch (error) {
|
|
4508
4841
|
if (!isMissingFile(error)) throw error;
|
|
4509
4842
|
try {
|
|
4510
|
-
await symlink2(target, linkPath,
|
|
4843
|
+
await symlink2(target, linkPath, directoryLinkType());
|
|
4511
4844
|
} catch (createError) {
|
|
4512
4845
|
if (!isAlreadyExists3(createError)) throw createError;
|
|
4513
4846
|
const info = await lstat2(linkPath);
|
|
@@ -4522,6 +4855,7 @@ var isAlreadyExists3 = (error) => error instanceof Error && "code" in error && e
|
|
|
4522
4855
|
// src/relay.ts
|
|
4523
4856
|
var RELAY_PING_INTERVAL_MS = 2e4;
|
|
4524
4857
|
var RELAY_PONG_TIMEOUT_MS = 1e4;
|
|
4858
|
+
var RELAY_ACTIVE_REQUEST_PONG_TIMEOUT_LIMIT = 2;
|
|
4525
4859
|
var RELAY_HANDSHAKE_TIMEOUT_MS = 1e4;
|
|
4526
4860
|
var RELAY_RECONNECT_MAX_MS = 3e4;
|
|
4527
4861
|
var RELAY_RECONNECT_JITTER = 0.2;
|
|
@@ -4627,6 +4961,7 @@ function startConnectorRelay(config, bunnyDaemon, relayProtocol, workspaceRegist
|
|
|
4627
4961
|
socket = ws;
|
|
4628
4962
|
const inflight = /* @__PURE__ */ new Map();
|
|
4629
4963
|
currentInflight = inflight;
|
|
4964
|
+
let consecutivePongTimeouts = 0;
|
|
4630
4965
|
let detached = false;
|
|
4631
4966
|
const detach = () => {
|
|
4632
4967
|
if (detached) return;
|
|
@@ -4639,6 +4974,7 @@ function startConnectorRelay(config, bunnyDaemon, relayProtocol, workspaceRegist
|
|
|
4639
4974
|
void writeLog("info", `relay connected ${relayUrl}`);
|
|
4640
4975
|
hadOpenConnection = true;
|
|
4641
4976
|
reconnectDelayMs = RELAY_RECONNECT_INTERVAL_MS;
|
|
4977
|
+
consecutivePongTimeouts = 0;
|
|
4642
4978
|
pingTimer = setInterval(() => {
|
|
4643
4979
|
if (ws.readyState !== WebSocket2.OPEN) return;
|
|
4644
4980
|
try {
|
|
@@ -4650,18 +4986,33 @@ function startConnectorRelay(config, bunnyDaemon, relayProtocol, workspaceRegist
|
|
|
4650
4986
|
}
|
|
4651
4987
|
if (pongTimer) clearTimeout(pongTimer);
|
|
4652
4988
|
pongTimer = setTimeout(() => {
|
|
4989
|
+
pongTimer = null;
|
|
4990
|
+
consecutivePongTimeouts += 1;
|
|
4991
|
+
if (inflight.size > 0 && consecutivePongTimeouts < RELAY_ACTIVE_REQUEST_PONG_TIMEOUT_LIMIT) {
|
|
4992
|
+
void writeLog(
|
|
4993
|
+
"warn",
|
|
4994
|
+
`relay pong timeout deferred while ${inflight.size} request(s) are active`
|
|
4995
|
+
);
|
|
4996
|
+
return;
|
|
4997
|
+
}
|
|
4653
4998
|
void writeLog("warn", "relay pong timeout, terminating");
|
|
4654
4999
|
ws.terminate();
|
|
4655
5000
|
}, RELAY_PONG_TIMEOUT_MS);
|
|
4656
5001
|
}, RELAY_PING_INTERVAL_MS);
|
|
4657
5002
|
});
|
|
4658
5003
|
ws.on("pong", () => {
|
|
5004
|
+
consecutivePongTimeouts = 0;
|
|
4659
5005
|
if (pongTimer) {
|
|
4660
5006
|
clearTimeout(pongTimer);
|
|
4661
5007
|
pongTimer = null;
|
|
4662
5008
|
}
|
|
4663
5009
|
});
|
|
4664
5010
|
ws.on("message", (raw) => {
|
|
5011
|
+
consecutivePongTimeouts = 0;
|
|
5012
|
+
if (pongTimer) {
|
|
5013
|
+
clearTimeout(pongTimer);
|
|
5014
|
+
pongTimer = null;
|
|
5015
|
+
}
|
|
4665
5016
|
void handleRelayMessage(
|
|
4666
5017
|
ws,
|
|
4667
5018
|
bunnyDaemon,
|
|
@@ -4837,7 +5188,7 @@ async function handleRelayMessage(socket, bunnyDaemon, raw, inflight, expectedCo
|
|
|
4837
5188
|
});
|
|
4838
5189
|
executionIdentity = message.executionIdentity;
|
|
4839
5190
|
} catch (error) {
|
|
4840
|
-
sendRelayError(socket, message.requestId, error);
|
|
5191
|
+
await sendRelayError(socket, message.requestId, error).catch(() => void 0);
|
|
4841
5192
|
return;
|
|
4842
5193
|
}
|
|
4843
5194
|
}
|
|
@@ -4923,7 +5274,7 @@ async function handleRelayMessage(socket, bunnyDaemon, raw, inflight, expectedCo
|
|
|
4923
5274
|
durationMs: Date.now() - codingRunStartedAt
|
|
4924
5275
|
});
|
|
4925
5276
|
}
|
|
4926
|
-
sendRelayMessage(socket, {
|
|
5277
|
+
await sendRelayMessage(socket, {
|
|
4927
5278
|
type: "start",
|
|
4928
5279
|
requestId: message.requestId,
|
|
4929
5280
|
status: response.status,
|
|
@@ -4943,7 +5294,7 @@ async function handleRelayMessage(socket, bunnyDaemon, raw, inflight, expectedCo
|
|
|
4943
5294
|
while (true) {
|
|
4944
5295
|
const { value, done } = await reader.read();
|
|
4945
5296
|
if (done) break;
|
|
4946
|
-
sendRelayMessage(socket, {
|
|
5297
|
+
await sendRelayMessage(socket, {
|
|
4947
5298
|
type: "chunk",
|
|
4948
5299
|
requestId: message.requestId,
|
|
4949
5300
|
data: binaryResponse ? Buffer.from(value).toString("base64") : decoder?.decode(value, { stream: true }) ?? "",
|
|
@@ -4952,7 +5303,7 @@ async function handleRelayMessage(socket, bunnyDaemon, raw, inflight, expectedCo
|
|
|
4952
5303
|
}
|
|
4953
5304
|
const tail = decoder?.decode() ?? "";
|
|
4954
5305
|
if (tail) {
|
|
4955
|
-
sendRelayMessage(socket, {
|
|
5306
|
+
await sendRelayMessage(socket, {
|
|
4956
5307
|
type: "chunk",
|
|
4957
5308
|
requestId: message.requestId,
|
|
4958
5309
|
data: tail
|
|
@@ -4963,7 +5314,7 @@ async function handleRelayMessage(socket, bunnyDaemon, raw, inflight, expectedCo
|
|
|
4963
5314
|
reader.releaseLock();
|
|
4964
5315
|
}
|
|
4965
5316
|
}
|
|
4966
|
-
sendRelayMessage(socket, {
|
|
5317
|
+
await sendRelayMessage(socket, {
|
|
4967
5318
|
type: "end",
|
|
4968
5319
|
requestId: message.requestId
|
|
4969
5320
|
});
|
|
@@ -4975,14 +5326,16 @@ async function handleRelayMessage(socket, bunnyDaemon, raw, inflight, expectedCo
|
|
|
4975
5326
|
});
|
|
4976
5327
|
}
|
|
4977
5328
|
} catch (error) {
|
|
5329
|
+
controller.abort();
|
|
4978
5330
|
if (codingRunTrace && codingRunStartedAt !== void 0) {
|
|
4979
5331
|
await writeLog("error", "relay request coding run error", {
|
|
4980
5332
|
...codingRunTrace,
|
|
4981
5333
|
status: codingRunStatus,
|
|
4982
|
-
durationMs: Date.now() - codingRunStartedAt
|
|
5334
|
+
durationMs: Date.now() - codingRunStartedAt,
|
|
5335
|
+
error: formatConnectorRelayError(error)
|
|
4983
5336
|
});
|
|
4984
5337
|
}
|
|
4985
|
-
sendRelayError(socket, message.requestId, error);
|
|
5338
|
+
await sendRelayError(socket, message.requestId, error).catch(() => void 0);
|
|
4986
5339
|
} finally {
|
|
4987
5340
|
inflight.delete(message.requestId);
|
|
4988
5341
|
}
|
|
@@ -5036,17 +5389,20 @@ var normalizeWorkspaceRequest = (request, mounts) => {
|
|
|
5036
5389
|
if (!mount || relative === void 0) {
|
|
5037
5390
|
throw new ConnectorRelayProtocolError("WORKSPACE_MISMATCH");
|
|
5038
5391
|
}
|
|
5039
|
-
const
|
|
5392
|
+
const systemEnv = body.systemEnv && typeof body.systemEnv === "object" && !Array.isArray(body.systemEnv) ? body.systemEnv : {};
|
|
5393
|
+
const workspaceEnv = {
|
|
5394
|
+
BUDA_AGENT_ROOT: mounts.agent,
|
|
5395
|
+
BUDA_SPACE_ROOT: mounts.space,
|
|
5396
|
+
BUDA_SYSTEM_ROOT: mounts.system
|
|
5397
|
+
};
|
|
5040
5398
|
return {
|
|
5041
5399
|
...request,
|
|
5042
5400
|
body: {
|
|
5043
5401
|
...body,
|
|
5044
5402
|
cwd: path12.join(mount, relative),
|
|
5045
|
-
|
|
5046
|
-
...
|
|
5047
|
-
|
|
5048
|
-
BUDA_SPACE_ROOT: mounts.space,
|
|
5049
|
-
BUDA_SYSTEM_ROOT: mounts.system
|
|
5403
|
+
systemEnv: {
|
|
5404
|
+
...systemEnv,
|
|
5405
|
+
...workspaceEnv
|
|
5050
5406
|
}
|
|
5051
5407
|
}
|
|
5052
5408
|
};
|
|
@@ -5078,17 +5434,24 @@ var normalizeConnectorFilesystemRequest = (request, mounts) => {
|
|
|
5078
5434
|
}
|
|
5079
5435
|
return request;
|
|
5080
5436
|
};
|
|
5081
|
-
function sendRelayError(socket, requestId, error) {
|
|
5082
|
-
sendRelayMessage(socket, {
|
|
5437
|
+
async function sendRelayError(socket, requestId, error) {
|
|
5438
|
+
await sendRelayMessage(socket, {
|
|
5083
5439
|
type: "error",
|
|
5084
5440
|
requestId,
|
|
5085
5441
|
error: error instanceof Error ? error.message : String(error),
|
|
5086
5442
|
...error instanceof ConnectorRelayProtocolError ? { code: error.code } : {}
|
|
5087
5443
|
});
|
|
5088
5444
|
}
|
|
5089
|
-
function sendRelayMessage(socket, message) {
|
|
5090
|
-
if (socket.readyState !== WebSocket2.OPEN)
|
|
5091
|
-
|
|
5445
|
+
async function sendRelayMessage(socket, message) {
|
|
5446
|
+
if (socket.readyState !== WebSocket2.OPEN) {
|
|
5447
|
+
throw new Error(`Connector relay socket closed before ${message.type} could be sent.`);
|
|
5448
|
+
}
|
|
5449
|
+
await new Promise((resolve, reject) => {
|
|
5450
|
+
socket.send(JSON.stringify(message), (error) => {
|
|
5451
|
+
if (error) reject(error);
|
|
5452
|
+
else resolve();
|
|
5453
|
+
});
|
|
5454
|
+
});
|
|
5092
5455
|
}
|
|
5093
5456
|
function headersToRecord(headers) {
|
|
5094
5457
|
const result = {};
|
|
@@ -5147,7 +5510,7 @@ async function ensureSymlink(target, linkPath) {
|
|
|
5147
5510
|
} catch {
|
|
5148
5511
|
}
|
|
5149
5512
|
await mkdir10(path13.dirname(linkPath), { recursive: true });
|
|
5150
|
-
await symlink3(target, linkPath,
|
|
5513
|
+
await symlink3(target, linkPath, directoryLinkType());
|
|
5151
5514
|
}
|
|
5152
5515
|
|
|
5153
5516
|
// src/workspace-registry.ts
|
|
@@ -5604,7 +5967,30 @@ import { mkdir as mkdir12 } from "fs/promises";
|
|
|
5604
5967
|
import path15 from "path";
|
|
5605
5968
|
var RECOVERED_DAEMON_STOP_TIMEOUT_MS = 1e4;
|
|
5606
5969
|
var RECOVERED_DAEMON_STOP_POLL_MS = 100;
|
|
5970
|
+
var SHUTDOWN_FORCE_EXIT_MS = 1e4;
|
|
5971
|
+
var SHUTDOWN_STEP_TIMEOUT_MS = 2e3;
|
|
5607
5972
|
var wait3 = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
5973
|
+
var runShutdownStep = async (label, operation) => {
|
|
5974
|
+
let timeout;
|
|
5975
|
+
try {
|
|
5976
|
+
await Promise.race([
|
|
5977
|
+
Promise.resolve().then(operation),
|
|
5978
|
+
new Promise((_, reject) => {
|
|
5979
|
+
timeout = setTimeout(
|
|
5980
|
+
() => reject(new Error(`${label} timed out after ${SHUTDOWN_STEP_TIMEOUT_MS}ms`)),
|
|
5981
|
+
SHUTDOWN_STEP_TIMEOUT_MS
|
|
5982
|
+
);
|
|
5983
|
+
})
|
|
5984
|
+
]);
|
|
5985
|
+
} catch (error) {
|
|
5986
|
+
await writeLog(
|
|
5987
|
+
"warn",
|
|
5988
|
+
`${label} failed during shutdown: ${error instanceof Error ? error.message : String(error)}`
|
|
5989
|
+
).catch(() => void 0);
|
|
5990
|
+
} finally {
|
|
5991
|
+
if (timeout) clearTimeout(timeout);
|
|
5992
|
+
}
|
|
5993
|
+
};
|
|
5608
5994
|
async function stopRecoveredIdleDaemon(config, configRoot) {
|
|
5609
5995
|
const stopStatus = await requestDaemonControl(config, "stop-if-idle", configRoot).catch(
|
|
5610
5996
|
() => null
|
|
@@ -5724,18 +6110,30 @@ async function runConnectorDaemon(launchConfig, options = {}) {
|
|
|
5724
6110
|
const shutdown = async (shutdownOptions = {}) => {
|
|
5725
6111
|
if (shuttingDown) return;
|
|
5726
6112
|
shuttingDown = true;
|
|
6113
|
+
const forceExit = options.exitOnShutdown === false ? void 0 : setTimeout(() => process.exit(0), SHUTDOWN_FORCE_EXIT_MS);
|
|
5727
6114
|
if (interval) clearInterval(interval);
|
|
5728
6115
|
relay.stop();
|
|
5729
6116
|
try {
|
|
5730
|
-
await
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
6117
|
+
await Promise.all([
|
|
6118
|
+
runShutdownStep("browser session cleanup", closeAllBrowserSessions),
|
|
6119
|
+
...shutdownOptions.unregister === false ? [] : [
|
|
6120
|
+
runShutdownStep(
|
|
6121
|
+
"connector unregister",
|
|
6122
|
+
() => unregisterConnector(config, launchConfig)
|
|
6123
|
+
)
|
|
6124
|
+
],
|
|
6125
|
+
runShutdownStep(
|
|
6126
|
+
"embedded Bunny daemon shutdown",
|
|
6127
|
+
() => stopEmbeddedBunnyDaemon(activeBunnyDaemon)
|
|
6128
|
+
)
|
|
6129
|
+
]);
|
|
5735
6130
|
} finally {
|
|
5736
|
-
await
|
|
5737
|
-
|
|
5738
|
-
|
|
6131
|
+
await Promise.all([
|
|
6132
|
+
runShutdownStep("control channel shutdown", async () => control?.close()),
|
|
6133
|
+
runShutdownStep("daemon lock release", () => daemonLock.release()),
|
|
6134
|
+
runShutdownStep("console capture restore", async () => restoreConsole?.())
|
|
6135
|
+
]);
|
|
6136
|
+
if (forceExit) clearTimeout(forceExit);
|
|
5739
6137
|
if (options.exitOnShutdown !== false) process.exit(0);
|
|
5740
6138
|
}
|
|
5741
6139
|
};
|
|
@@ -5750,8 +6148,11 @@ async function runConnectorDaemon(launchConfig, options = {}) {
|
|
|
5750
6148
|
});
|
|
5751
6149
|
}).catch((error) => {
|
|
5752
6150
|
heartbeatHealthy = false;
|
|
5753
|
-
if (error instanceof ConnectorDeletedError) {
|
|
5754
|
-
void writeLog(
|
|
6151
|
+
if (error instanceof ConnectorDeletedError || error instanceof ConnectorUnauthorizedError) {
|
|
6152
|
+
void writeLog(
|
|
6153
|
+
"warn",
|
|
6154
|
+
error instanceof ConnectorDeletedError ? "connector deleted remotely; stopping daemon" : "connector authentication rejected remotely; stopping daemon"
|
|
6155
|
+
);
|
|
5755
6156
|
void shutdown({ unregister: false });
|
|
5756
6157
|
return;
|
|
5757
6158
|
}
|
|
@@ -5775,6 +6176,10 @@ async function runConnectorDaemon(launchConfig, options = {}) {
|
|
|
5775
6176
|
process.once("SIGINT", () => void shutdown());
|
|
5776
6177
|
process.once("SIGTERM", () => void shutdown());
|
|
5777
6178
|
} catch (error) {
|
|
6179
|
+
await writeLog(
|
|
6180
|
+
"error",
|
|
6181
|
+
`daemon startup failed: ${error instanceof Error ? error.stack || error.message : String(error)}`
|
|
6182
|
+
).catch(() => void 0);
|
|
5778
6183
|
if (bunnyDaemon) await stopEmbeddedBunnyDaemon(bunnyDaemon);
|
|
5779
6184
|
await daemonLock.release();
|
|
5780
6185
|
await restoreConsole?.();
|