@buildautomaton/cli 0.1.4 → 0.1.6
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 +1048 -607
- package/dist/cli.js.map +4 -4
- package/dist/index.js +1088 -651
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -21966,12 +21966,12 @@ var require_src2 = __commonJS({
|
|
|
21966
21966
|
function check2(path24, isFile, isDirectory) {
|
|
21967
21967
|
log2(`checking %s`, path24);
|
|
21968
21968
|
try {
|
|
21969
|
-
const
|
|
21970
|
-
if (
|
|
21969
|
+
const stat2 = fs_1.statSync(path24);
|
|
21970
|
+
if (stat2.isFile() && isFile) {
|
|
21971
21971
|
log2(`[OK] path represents a file`);
|
|
21972
21972
|
return true;
|
|
21973
21973
|
}
|
|
21974
|
-
if (
|
|
21974
|
+
if (stat2.isDirectory() && isDirectory) {
|
|
21975
21975
|
log2(`[OK] path represents a directory`);
|
|
21976
21976
|
return true;
|
|
21977
21977
|
}
|
|
@@ -22061,16 +22061,39 @@ var import_websocket = __toESM(require_websocket(), 1);
|
|
|
22061
22061
|
var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
22062
22062
|
var wrapper_default = import_websocket.default;
|
|
22063
22063
|
|
|
22064
|
+
// src/net/apply-cli-outbound-network-prefs.ts
|
|
22065
|
+
import dns from "node:dns";
|
|
22066
|
+
var applied = false;
|
|
22067
|
+
function applyCliOutboundNetworkPreferences() {
|
|
22068
|
+
if (applied) return;
|
|
22069
|
+
applied = true;
|
|
22070
|
+
try {
|
|
22071
|
+
dns.setDefaultResultOrder("ipv4first");
|
|
22072
|
+
} catch {
|
|
22073
|
+
}
|
|
22074
|
+
}
|
|
22075
|
+
|
|
22064
22076
|
// src/bridge/connection/create-ws-bridge.ts
|
|
22065
22077
|
var BRIDGE_AUTH_ERROR_HEADER = "x-bridge-auth-error";
|
|
22066
22078
|
var BRIDGE_AUTH_ERROR_TOKEN_INVALID = "token_invalid";
|
|
22067
22079
|
function createWsBridge(options) {
|
|
22068
|
-
const { url: url2, onMessage, onOpen, onClose, onError: onError2, onAuthInvalid } = options;
|
|
22069
|
-
|
|
22080
|
+
const { url: url2, onMessage, onOpen, onClose, onError: onError2, onAuthInvalid, clientPingIntervalMs } = options;
|
|
22081
|
+
applyCliOutboundNetworkPreferences();
|
|
22082
|
+
const wsOptions = {
|
|
22083
|
+
perMessageDeflate: false,
|
|
22084
|
+
family: 4
|
|
22085
|
+
};
|
|
22070
22086
|
if (url2.startsWith("wss://")) {
|
|
22071
|
-
wsOptions.agent = new https.Agent({ rejectUnauthorized: false });
|
|
22087
|
+
wsOptions.agent = new https.Agent({ rejectUnauthorized: false, family: 4 });
|
|
22072
22088
|
}
|
|
22073
22089
|
const ws = new wrapper_default(url2, wsOptions);
|
|
22090
|
+
let clientPingTimer = null;
|
|
22091
|
+
function clearClientPing() {
|
|
22092
|
+
if (clientPingTimer != null) {
|
|
22093
|
+
clearInterval(clientPingTimer);
|
|
22094
|
+
clientPingTimer = null;
|
|
22095
|
+
}
|
|
22096
|
+
}
|
|
22074
22097
|
ws.on("unexpected-response", (request, response) => {
|
|
22075
22098
|
const status = response?.statusCode ?? 0;
|
|
22076
22099
|
const headers = response?.headers ?? {};
|
|
@@ -22080,6 +22103,17 @@ function createWsBridge(options) {
|
|
|
22080
22103
|
}
|
|
22081
22104
|
});
|
|
22082
22105
|
ws.on("open", () => {
|
|
22106
|
+
clearClientPing();
|
|
22107
|
+
if (clientPingIntervalMs != null && clientPingIntervalMs > 0) {
|
|
22108
|
+
clientPingTimer = setInterval(() => {
|
|
22109
|
+
if (ws.readyState === wrapper_default.OPEN) {
|
|
22110
|
+
try {
|
|
22111
|
+
ws.ping();
|
|
22112
|
+
} catch {
|
|
22113
|
+
}
|
|
22114
|
+
}
|
|
22115
|
+
}, clientPingIntervalMs);
|
|
22116
|
+
}
|
|
22083
22117
|
onOpen?.();
|
|
22084
22118
|
});
|
|
22085
22119
|
ws.on("message", (raw) => {
|
|
@@ -22099,9 +22133,11 @@ function createWsBridge(options) {
|
|
|
22099
22133
|
}
|
|
22100
22134
|
});
|
|
22101
22135
|
ws.on("close", (code, reason) => {
|
|
22136
|
+
clearClientPing();
|
|
22102
22137
|
onClose?.(code, reason.toString());
|
|
22103
22138
|
});
|
|
22104
22139
|
ws.on("error", (err) => {
|
|
22140
|
+
clearClientPing();
|
|
22105
22141
|
onError2?.(err);
|
|
22106
22142
|
});
|
|
22107
22143
|
return ws;
|
|
@@ -22112,9 +22148,87 @@ function sendWsMessage(ws, payload) {
|
|
|
22112
22148
|
}
|
|
22113
22149
|
}
|
|
22114
22150
|
|
|
22115
|
-
// src/acp/clients/acp-client.ts
|
|
22151
|
+
// src/acp/clients/sdk-stdio-acp-client.ts
|
|
22116
22152
|
import { spawn } from "node:child_process";
|
|
22153
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
22154
|
+
import { dirname } from "node:path";
|
|
22117
22155
|
import { Readable, Writable } from "node:stream";
|
|
22156
|
+
|
|
22157
|
+
// src/files/diff/unified-diff.ts
|
|
22158
|
+
function computeLineDiff(oldText, newText) {
|
|
22159
|
+
const oldLines = oldText.split("\n");
|
|
22160
|
+
const newLines = newText.split("\n");
|
|
22161
|
+
const m = oldLines.length;
|
|
22162
|
+
const n = newLines.length;
|
|
22163
|
+
const dp = Array(m + 1);
|
|
22164
|
+
for (let i2 = 0; i2 <= m; i2++) dp[i2] = Array(n + 1).fill(0);
|
|
22165
|
+
for (let i2 = 1; i2 <= m; i2++) {
|
|
22166
|
+
for (let j2 = 1; j2 <= n; j2++) {
|
|
22167
|
+
if (oldLines[i2 - 1] === newLines[j2 - 1]) {
|
|
22168
|
+
dp[i2][j2] = dp[i2 - 1][j2 - 1] + 1;
|
|
22169
|
+
} else {
|
|
22170
|
+
dp[i2][j2] = Math.max(dp[i2 - 1][j2], dp[i2][j2 - 1]);
|
|
22171
|
+
}
|
|
22172
|
+
}
|
|
22173
|
+
}
|
|
22174
|
+
const result = [];
|
|
22175
|
+
let i = m;
|
|
22176
|
+
let j = n;
|
|
22177
|
+
while (i > 0 || j > 0) {
|
|
22178
|
+
if (i > 0 && j > 0 && oldLines[i - 1] === newLines[j - 1]) {
|
|
22179
|
+
result.unshift({ type: "context", line: oldLines[i - 1] });
|
|
22180
|
+
i--;
|
|
22181
|
+
j--;
|
|
22182
|
+
} else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
|
|
22183
|
+
result.unshift({ type: "add", line: newLines[j - 1] });
|
|
22184
|
+
j--;
|
|
22185
|
+
} else {
|
|
22186
|
+
result.unshift({ type: "remove", line: oldLines[i - 1] });
|
|
22187
|
+
i--;
|
|
22188
|
+
}
|
|
22189
|
+
}
|
|
22190
|
+
return result;
|
|
22191
|
+
}
|
|
22192
|
+
function editSnippetToUnifiedDiff(filePath, oldText, newText) {
|
|
22193
|
+
const lines = computeLineDiff(oldText, newText);
|
|
22194
|
+
const out = [`--- ${filePath}`, `+++ ${filePath}`];
|
|
22195
|
+
for (const d of lines) {
|
|
22196
|
+
if (d.type === "add") out.push(`+${d.line}`);
|
|
22197
|
+
else if (d.type === "remove") out.push(`-${d.line}`);
|
|
22198
|
+
else out.push(` ${d.line}`);
|
|
22199
|
+
}
|
|
22200
|
+
return out.join("\n");
|
|
22201
|
+
}
|
|
22202
|
+
|
|
22203
|
+
// src/files/cwd/bridge-workspace-directory.ts
|
|
22204
|
+
import * as path from "node:path";
|
|
22205
|
+
var bridgeWorkspaceDirectory = null;
|
|
22206
|
+
function getBridgeWorkspaceDirectory() {
|
|
22207
|
+
if (bridgeWorkspaceDirectory == null) {
|
|
22208
|
+
bridgeWorkspaceDirectory = path.resolve(process.cwd());
|
|
22209
|
+
}
|
|
22210
|
+
return bridgeWorkspaceDirectory;
|
|
22211
|
+
}
|
|
22212
|
+
|
|
22213
|
+
// src/acp/safe-fs-path.ts
|
|
22214
|
+
import * as path2 from "node:path";
|
|
22215
|
+
function resolveSafePathUnderCwd(cwd, filePath) {
|
|
22216
|
+
const trimmed2 = filePath.trim();
|
|
22217
|
+
if (!trimmed2) return null;
|
|
22218
|
+
const normalizedCwd = path2.resolve(cwd);
|
|
22219
|
+
const resolved = path2.isAbsolute(trimmed2) ? path2.normalize(trimmed2) : path2.resolve(normalizedCwd, trimmed2);
|
|
22220
|
+
const rel = path2.relative(normalizedCwd, resolved);
|
|
22221
|
+
if (rel.startsWith("..") || path2.isAbsolute(rel)) return null;
|
|
22222
|
+
return resolved;
|
|
22223
|
+
}
|
|
22224
|
+
function toDisplayPathRelativeToCwd(cwd, absolutePath) {
|
|
22225
|
+
const normalizedCwd = path2.resolve(cwd);
|
|
22226
|
+
const rel = path2.relative(normalizedCwd, path2.resolve(absolutePath));
|
|
22227
|
+
if (!rel || rel === "") return path2.basename(absolutePath);
|
|
22228
|
+
return rel.split(path2.sep).join("/");
|
|
22229
|
+
}
|
|
22230
|
+
|
|
22231
|
+
// src/acp/clients/sdk-stdio-acp-client.ts
|
|
22118
22232
|
function formatSpawnError(err, command) {
|
|
22119
22233
|
if (err.code === "ENOENT") {
|
|
22120
22234
|
return `Command "${command}" not found. Install the agent (e.g. Cursor CLI) or add it to PATH.`;
|
|
@@ -22129,16 +22243,36 @@ function toErrorMessage(err) {
|
|
|
22129
22243
|
if (err != null && typeof err === "object") return JSON.stringify(err);
|
|
22130
22244
|
return String(err);
|
|
22131
22245
|
}
|
|
22132
|
-
|
|
22133
|
-
|
|
22134
|
-
const
|
|
22246
|
+
function sliceFileContentRange(content, line, limit) {
|
|
22247
|
+
if (line == null && limit == null) return content;
|
|
22248
|
+
const lines = content.split("\n");
|
|
22249
|
+
const start = line != null && line > 0 ? line - 1 : 0;
|
|
22250
|
+
const end = limit != null && limit > 0 ? start + limit : lines.length;
|
|
22251
|
+
return lines.slice(start, end).join("\n");
|
|
22252
|
+
}
|
|
22253
|
+
function bridgePayloadFromSdkSessionNotification(params) {
|
|
22254
|
+
return { sessionId: params.sessionId, ...params.update };
|
|
22255
|
+
}
|
|
22256
|
+
async function createSdkStdioAcpClient(options) {
|
|
22257
|
+
const { ClientSideConnection: ClientSideConnection2, ndJsonStream: ndJsonStream2, PROTOCOL_VERSION: PROTOCOL_VERSION2 } = await Promise.resolve().then(() => (init_acp(), acp_exports));
|
|
22258
|
+
const {
|
|
22259
|
+
command,
|
|
22260
|
+
cwd = getBridgeWorkspaceDirectory(),
|
|
22261
|
+
onSessionUpdate,
|
|
22262
|
+
onFileChange,
|
|
22263
|
+
killSubprocessAfterCancelMs,
|
|
22264
|
+
onAgentSubprocessExit
|
|
22265
|
+
} = options;
|
|
22135
22266
|
const isWindows = process.platform === "win32";
|
|
22136
22267
|
const child = spawn(command[0], command.slice(1), {
|
|
22137
|
-
cwd
|
|
22268
|
+
cwd,
|
|
22138
22269
|
stdio: ["pipe", "pipe", "inherit"],
|
|
22139
22270
|
env: process.env,
|
|
22140
22271
|
shell: isWindows
|
|
22141
22272
|
});
|
|
22273
|
+
child.once("close", (code, signal) => {
|
|
22274
|
+
onAgentSubprocessExit?.({ code, signal });
|
|
22275
|
+
});
|
|
22142
22276
|
return new Promise((resolve14, reject) => {
|
|
22143
22277
|
child.on("error", (err) => {
|
|
22144
22278
|
child.kill();
|
|
@@ -22150,11 +22284,43 @@ async function createAcpClient(options) {
|
|
|
22150
22284
|
const readable = Readable.toWeb(child.stdout);
|
|
22151
22285
|
const stream = ndJsonStream2(writable, readable);
|
|
22152
22286
|
const client = (_agent) => ({
|
|
22153
|
-
async requestPermission(
|
|
22154
|
-
|
|
22287
|
+
async requestPermission(params) {
|
|
22288
|
+
const opt = params?.options?.[0];
|
|
22289
|
+
if (opt && typeof opt.optionId === "string") {
|
|
22290
|
+
return { outcome: { outcome: "selected", optionId: opt.optionId } };
|
|
22291
|
+
}
|
|
22292
|
+
return { outcome: { outcome: "cancelled" } };
|
|
22293
|
+
},
|
|
22294
|
+
async readTextFile(params) {
|
|
22295
|
+
const abs = resolveSafePathUnderCwd(cwd, params.path);
|
|
22296
|
+
if (!abs) throw new Error("Invalid or disallowed path");
|
|
22297
|
+
try {
|
|
22298
|
+
let content = readFileSync(abs, "utf8");
|
|
22299
|
+
content = sliceFileContentRange(content, params.line, params.limit);
|
|
22300
|
+
return { content };
|
|
22301
|
+
} catch (e) {
|
|
22302
|
+
if (e.code === "ENOENT") return { content: "" };
|
|
22303
|
+
throw e;
|
|
22304
|
+
}
|
|
22305
|
+
},
|
|
22306
|
+
async writeTextFile(params) {
|
|
22307
|
+
const abs = resolveSafePathUnderCwd(cwd, params.path);
|
|
22308
|
+
if (!abs) throw new Error("Invalid or disallowed path");
|
|
22309
|
+
let oldText = "";
|
|
22310
|
+
try {
|
|
22311
|
+
oldText = readFileSync(abs, "utf8");
|
|
22312
|
+
} catch (e) {
|
|
22313
|
+
if (e.code !== "ENOENT") throw e;
|
|
22314
|
+
}
|
|
22315
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
22316
|
+
writeFileSync(abs, params.content, "utf8");
|
|
22317
|
+
const displayPath = toDisplayPathRelativeToCwd(cwd, abs);
|
|
22318
|
+
const patchContent = editSnippetToUnifiedDiff(displayPath, oldText, params.content);
|
|
22319
|
+
onFileChange?.({ path: displayPath, oldText, newText: params.content, patchContent });
|
|
22320
|
+
return {};
|
|
22155
22321
|
},
|
|
22156
22322
|
async sessionUpdate(params) {
|
|
22157
|
-
onSessionUpdate?.(params);
|
|
22323
|
+
onSessionUpdate?.(bridgePayloadFromSdkSessionNotification(params));
|
|
22158
22324
|
}
|
|
22159
22325
|
});
|
|
22160
22326
|
const connection = new ClientSideConnection2(client, stream);
|
|
@@ -22162,11 +22328,13 @@ async function createAcpClient(options) {
|
|
|
22162
22328
|
child.kill();
|
|
22163
22329
|
});
|
|
22164
22330
|
await connection.initialize({
|
|
22165
|
-
protocolVersion:
|
|
22166
|
-
|
|
22331
|
+
protocolVersion: PROTOCOL_VERSION2,
|
|
22332
|
+
clientCapabilities: {
|
|
22333
|
+
fs: { readTextFile: true, writeTextFile: true }
|
|
22334
|
+
},
|
|
22167
22335
|
clientInfo: { name: "buildautomaton-cli", version: "0.1.0" }
|
|
22168
22336
|
});
|
|
22169
|
-
const newSessionRes = await connection.newSession({
|
|
22337
|
+
const newSessionRes = await connection.newSession({ cwd, mcpServers: [] });
|
|
22170
22338
|
const sessionId = newSessionRes.sessionId;
|
|
22171
22339
|
resolve14({
|
|
22172
22340
|
sessionId,
|
|
@@ -22174,7 +22342,7 @@ async function createAcpClient(options) {
|
|
|
22174
22342
|
try {
|
|
22175
22343
|
const response = await connection.prompt({
|
|
22176
22344
|
sessionId,
|
|
22177
|
-
prompt: { type: "text", text: prompt }
|
|
22345
|
+
prompt: [{ type: "text", text: prompt }]
|
|
22178
22346
|
});
|
|
22179
22347
|
const r = response;
|
|
22180
22348
|
const cancelled = (r?.stopReason ?? "").toLowerCase() === "cancelled";
|
|
@@ -22192,9 +22360,17 @@ async function createAcpClient(options) {
|
|
|
22192
22360
|
}
|
|
22193
22361
|
},
|
|
22194
22362
|
async cancel() {
|
|
22195
|
-
|
|
22196
|
-
|
|
22197
|
-
|
|
22363
|
+
try {
|
|
22364
|
+
await connection.cancel({ sessionId });
|
|
22365
|
+
} catch {
|
|
22366
|
+
}
|
|
22367
|
+
if (killSubprocessAfterCancelMs != null && killSubprocessAfterCancelMs >= 0) {
|
|
22368
|
+
const t = setTimeout(() => {
|
|
22369
|
+
if (child.exitCode == null && child.signalCode == null) {
|
|
22370
|
+
child.kill("SIGTERM");
|
|
22371
|
+
}
|
|
22372
|
+
}, killSubprocessAfterCancelMs);
|
|
22373
|
+
t.unref?.();
|
|
22198
22374
|
}
|
|
22199
22375
|
},
|
|
22200
22376
|
resolveRequest() {
|
|
@@ -22399,7 +22575,7 @@ var previewSkill = {
|
|
|
22399
22575
|
const exe = parts[0];
|
|
22400
22576
|
const args = parts.slice(1);
|
|
22401
22577
|
previewProcess = spawn2(isWindows && exe === "npm" ? "npm.cmd" : exe, args, {
|
|
22402
|
-
cwd:
|
|
22578
|
+
cwd: getBridgeWorkspaceDirectory(),
|
|
22403
22579
|
stdio: ["ignore", "pipe", "pipe"],
|
|
22404
22580
|
env: {
|
|
22405
22581
|
...process.env,
|
|
@@ -22508,11 +22684,11 @@ function logImmediate(line) {
|
|
|
22508
22684
|
|
|
22509
22685
|
// src/config.ts
|
|
22510
22686
|
import fs from "node:fs";
|
|
22511
|
-
import
|
|
22687
|
+
import path3 from "node:path";
|
|
22512
22688
|
import os from "node:os";
|
|
22513
22689
|
function getConfigPath() {
|
|
22514
|
-
const dir =
|
|
22515
|
-
return
|
|
22690
|
+
const dir = path3.join(os.homedir(), ".buildautomaton");
|
|
22691
|
+
return path3.join(dir, "config.json");
|
|
22516
22692
|
}
|
|
22517
22693
|
function normalizeApiUrl(url2) {
|
|
22518
22694
|
return url2.replace(/\/$/, "");
|
|
@@ -22528,7 +22704,7 @@ function readRawConfig() {
|
|
|
22528
22704
|
}
|
|
22529
22705
|
function writeConfigForApi(apiUrl, auth) {
|
|
22530
22706
|
const p = getConfigPath();
|
|
22531
|
-
const dir =
|
|
22707
|
+
const dir = path3.dirname(p);
|
|
22532
22708
|
const key = normalizeApiUrl(apiUrl);
|
|
22533
22709
|
const prev = readRawConfig() ?? {};
|
|
22534
22710
|
const servers = { ...prev.servers ?? {}, [key]: { ...auth } };
|
|
@@ -22537,7 +22713,7 @@ function writeConfigForApi(apiUrl, auth) {
|
|
|
22537
22713
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
22538
22714
|
fs.writeFileSync(p, JSON.stringify(next, null, 2), "utf8");
|
|
22539
22715
|
} catch (e) {
|
|
22540
|
-
console.error("Could not save
|
|
22716
|
+
console.error("Could not save authentication config:", e);
|
|
22541
22717
|
}
|
|
22542
22718
|
}
|
|
22543
22719
|
function clearConfigForApi(apiUrl) {
|
|
@@ -22599,6 +22775,28 @@ function openBrowser(connectionId, initialWorkspaceId, preferredBridgeName, apiU
|
|
|
22599
22775
|
}
|
|
22600
22776
|
}
|
|
22601
22777
|
|
|
22778
|
+
// src/bridge/connection/reconnect/constants.ts
|
|
22779
|
+
var RECONNECT_FIRST_MS = 100;
|
|
22780
|
+
var RECONNECT_MAX_MS = 3e4;
|
|
22781
|
+
var RECONNECT_QUIET_MS = 2e3;
|
|
22782
|
+
function reconnectDelayMs(attemptBeforeIncrement) {
|
|
22783
|
+
return Math.min(RECONNECT_FIRST_MS * 2 ** attemptBeforeIncrement, RECONNECT_MAX_MS);
|
|
22784
|
+
}
|
|
22785
|
+
|
|
22786
|
+
// src/bridge/connection/reconnect/format-reconnect-delay-for-log.ts
|
|
22787
|
+
function formatReconnectDelayForLog(delayMs) {
|
|
22788
|
+
if (delayMs < 1e3) return `${delayMs}ms`;
|
|
22789
|
+
const s = delayMs / 1e3;
|
|
22790
|
+
return Number.isInteger(s) ? `${s}s` : `${s.toFixed(1)}s`;
|
|
22791
|
+
}
|
|
22792
|
+
|
|
22793
|
+
// src/bridge/connection/reconnect/log-next-reconnect-attempt.ts
|
|
22794
|
+
function logNextReconnectAttempt(log2, serviceLabel, quiet, delayMs, attempt) {
|
|
22795
|
+
if (!quiet.verboseLogs) return;
|
|
22796
|
+
const delayLabel = formatReconnectDelayForLog(delayMs);
|
|
22797
|
+
log2(`${serviceLabel} Next connection attempt in ${delayLabel} (attempt ${attempt}).`);
|
|
22798
|
+
}
|
|
22799
|
+
|
|
22602
22800
|
// src/bridge/connection/ws-close-diagnostics.ts
|
|
22603
22801
|
function describeWebSocketCloseCode(code) {
|
|
22604
22802
|
const known = {
|
|
@@ -22627,11 +22825,122 @@ function formatWebSocketClose(label, code, reason, extra) {
|
|
|
22627
22825
|
const r = reason.trim();
|
|
22628
22826
|
const reasonPart = r ? ` reason="${r}"` : "";
|
|
22629
22827
|
const extraPart = extra ? ` ${extra}` : "";
|
|
22630
|
-
return `${label}
|
|
22828
|
+
return `${label} Disconnected: code=${code} (${describeWebSocketCloseCode(code)})${reasonPart}${extraPart}`;
|
|
22829
|
+
}
|
|
22830
|
+
|
|
22831
|
+
// src/bridge/connection/reconnect/reconnect-quiet-slot.ts
|
|
22832
|
+
function createEmptyReconnectQuietSlot() {
|
|
22833
|
+
return { timer: null, verboseLogs: false, pendingCloseLog: null };
|
|
22834
|
+
}
|
|
22835
|
+
function clearReconnectQuietTimer(quiet) {
|
|
22836
|
+
if (quiet.timer != null) {
|
|
22837
|
+
clearTimeout(quiet.timer);
|
|
22838
|
+
quiet.timer = null;
|
|
22839
|
+
}
|
|
22840
|
+
}
|
|
22841
|
+
function clearReconnectQuietOnSuccessfulConnection(quiet, log2, reconnectedMessage) {
|
|
22842
|
+
clearReconnectQuietTimer(quiet);
|
|
22843
|
+
quiet.pendingCloseLog = null;
|
|
22844
|
+
if (quiet.verboseLogs) {
|
|
22845
|
+
log2(reconnectedMessage);
|
|
22846
|
+
quiet.verboseLogs = false;
|
|
22847
|
+
}
|
|
22848
|
+
}
|
|
22849
|
+
function beginDeferredDisconnectForReconnect(options) {
|
|
22850
|
+
const {
|
|
22851
|
+
isClosedByUser,
|
|
22852
|
+
quiet,
|
|
22853
|
+
code,
|
|
22854
|
+
reason,
|
|
22855
|
+
willReconnect,
|
|
22856
|
+
log: log2,
|
|
22857
|
+
serviceLabel,
|
|
22858
|
+
shutdownDetail,
|
|
22859
|
+
reconnectingDetail,
|
|
22860
|
+
quietMs = RECONNECT_QUIET_MS,
|
|
22861
|
+
shouldAbortQuietWindow
|
|
22862
|
+
} = options;
|
|
22863
|
+
if (!willReconnect) {
|
|
22864
|
+
log2(formatWebSocketClose(serviceLabel, code, reason, shutdownDetail));
|
|
22865
|
+
return;
|
|
22866
|
+
}
|
|
22867
|
+
quiet.pendingCloseLog = { code, reason };
|
|
22868
|
+
if (quiet.timer == null) {
|
|
22869
|
+
quiet.timer = setTimeout(() => {
|
|
22870
|
+
quiet.timer = null;
|
|
22871
|
+
if (isClosedByUser()) return;
|
|
22872
|
+
if (shouldAbortQuietWindow()) return;
|
|
22873
|
+
if (quiet.pendingCloseLog) {
|
|
22874
|
+
const { code: c, reason: r } = quiet.pendingCloseLog;
|
|
22875
|
+
quiet.pendingCloseLog = null;
|
|
22876
|
+
log2(formatWebSocketClose(serviceLabel, c, r, reconnectingDetail));
|
|
22877
|
+
}
|
|
22878
|
+
quiet.verboseLogs = true;
|
|
22879
|
+
}, quietMs);
|
|
22880
|
+
}
|
|
22881
|
+
}
|
|
22882
|
+
|
|
22883
|
+
// src/bridge/connection/reconnect/bridge-main-reconnect.ts
|
|
22884
|
+
function beginMainBridgeDeferredDisconnect(state, code, reason, log2, willReconnect) {
|
|
22885
|
+
beginDeferredDisconnectForReconnect({
|
|
22886
|
+
isClosedByUser: () => state.closedByUser,
|
|
22887
|
+
quiet: state.mainQuiet,
|
|
22888
|
+
code,
|
|
22889
|
+
reason,
|
|
22890
|
+
willReconnect,
|
|
22891
|
+
log: log2,
|
|
22892
|
+
serviceLabel: "[Bridge service]",
|
|
22893
|
+
shutdownDetail: "Not reconnecting (shutting down).",
|
|
22894
|
+
reconnectingDetail: "Reconnecting\u2026",
|
|
22895
|
+
shouldAbortQuietWindow: () => {
|
|
22896
|
+
const w = state.currentWs;
|
|
22897
|
+
return w != null && w.readyState === wrapper_default.OPEN;
|
|
22898
|
+
}
|
|
22899
|
+
});
|
|
22900
|
+
}
|
|
22901
|
+
function clearMainBridgeReconnectQuietOnOpen(state, log2) {
|
|
22902
|
+
clearReconnectQuietOnSuccessfulConnection(state.mainQuiet, log2, "Bridge connection restored.");
|
|
22903
|
+
}
|
|
22904
|
+
function scheduleMainBridgeReconnect(state, connect, log2) {
|
|
22905
|
+
if (state.closedByUser || state.currentWs != null) return;
|
|
22906
|
+
const delay2 = reconnectDelayMs(state.reconnectAttempt);
|
|
22907
|
+
state.reconnectAttempt += 1;
|
|
22908
|
+
logNextReconnectAttempt(log2, "[Bridge service]", state.mainQuiet, delay2, state.reconnectAttempt);
|
|
22909
|
+
state.reconnectTimeout = setTimeout(() => {
|
|
22910
|
+
state.reconnectTimeout = null;
|
|
22911
|
+
connect();
|
|
22912
|
+
}, delay2);
|
|
22913
|
+
}
|
|
22914
|
+
|
|
22915
|
+
// src/bridge/connection/reconnect/firehose-reconnect.ts
|
|
22916
|
+
var PROXY_AND_LOG_SERVICE_LABEL = "[Proxy and log service]";
|
|
22917
|
+
function beginFirehoseDeferredDisconnect(ctx, code, reason, log2) {
|
|
22918
|
+
beginDeferredDisconnectForReconnect({
|
|
22919
|
+
isClosedByUser: () => ctx.closedByUser,
|
|
22920
|
+
quiet: ctx.firehoseQuiet,
|
|
22921
|
+
code,
|
|
22922
|
+
reason,
|
|
22923
|
+
willReconnect: true,
|
|
22924
|
+
log: log2,
|
|
22925
|
+
serviceLabel: PROXY_AND_LOG_SERVICE_LABEL,
|
|
22926
|
+
shutdownDetail: "Not reconnecting (shutting down).",
|
|
22927
|
+
reconnectingDetail: "Reconnecting\u2026",
|
|
22928
|
+
shouldAbortQuietWindow: () => {
|
|
22929
|
+
const w = ctx.currentWs;
|
|
22930
|
+
if (!w || w.readyState !== wrapper_default.OPEN) return true;
|
|
22931
|
+
return ctx.firehoseHandle?.isConnected() ?? false;
|
|
22932
|
+
}
|
|
22933
|
+
});
|
|
22934
|
+
}
|
|
22935
|
+
function clearFirehoseReconnectQuietOnOpen(ctx, log2) {
|
|
22936
|
+
clearReconnectQuietOnSuccessfulConnection(
|
|
22937
|
+
ctx.firehoseQuiet,
|
|
22938
|
+
log2,
|
|
22939
|
+
"Preview tunnel restored (local HTTP proxy and dev logs)."
|
|
22940
|
+
);
|
|
22631
22941
|
}
|
|
22632
22942
|
|
|
22633
22943
|
// src/auth/run-pending-auth.ts
|
|
22634
|
-
var PENDING_RECONNECT_MS = 2e3;
|
|
22635
22944
|
var PENDING_KEEPALIVE_MS = 25e3;
|
|
22636
22945
|
var BROWSER_OPEN_FALLBACK_MS = 4e3;
|
|
22637
22946
|
function buildPendingBridgeUrl(apiUrl, connectionId) {
|
|
@@ -22653,7 +22962,32 @@ function runPendingAuth(options) {
|
|
|
22653
22962
|
const authPromise = new Promise((resolve14) => {
|
|
22654
22963
|
resolveAuth = resolve14;
|
|
22655
22964
|
});
|
|
22965
|
+
let reconnectAttempt = 0;
|
|
22966
|
+
const signInQuiet = createEmptyReconnectQuietSlot();
|
|
22967
|
+
function clearQuietOnOpen() {
|
|
22968
|
+
clearReconnectQuietOnSuccessfulConnection(
|
|
22969
|
+
signInQuiet,
|
|
22970
|
+
logFn,
|
|
22971
|
+
"[Bridge service] Sign-in connection restored."
|
|
22972
|
+
);
|
|
22973
|
+
reconnectAttempt = 0;
|
|
22974
|
+
}
|
|
22975
|
+
function beginDeferredPendingCloseLog(code, reason) {
|
|
22976
|
+
beginDeferredDisconnectForReconnect({
|
|
22977
|
+
isClosedByUser: () => resolved,
|
|
22978
|
+
quiet: signInQuiet,
|
|
22979
|
+
code,
|
|
22980
|
+
reason,
|
|
22981
|
+
willReconnect: true,
|
|
22982
|
+
log: logFn,
|
|
22983
|
+
serviceLabel: "[Bridge service]",
|
|
22984
|
+
shutdownDetail: "Not reconnecting (shutting down).",
|
|
22985
|
+
reconnectingDetail: "Waiting for browser sign-in; reconnecting\u2026",
|
|
22986
|
+
shouldAbortQuietWindow: () => ws != null && ws.readyState === wrapper_default.OPEN
|
|
22987
|
+
});
|
|
22988
|
+
}
|
|
22656
22989
|
function cleanup() {
|
|
22990
|
+
clearReconnectQuietTimer(signInQuiet);
|
|
22657
22991
|
if (reconnectTimeout) {
|
|
22658
22992
|
clearTimeout(reconnectTimeout);
|
|
22659
22993
|
reconnectTimeout = null;
|
|
@@ -22677,6 +23011,7 @@ function runPendingAuth(options) {
|
|
|
22677
23011
|
ws = createWsBridge({
|
|
22678
23012
|
url: url2,
|
|
22679
23013
|
onOpen: () => {
|
|
23014
|
+
clearQuietOnOpen();
|
|
22680
23015
|
sendWsMessage(ws, { type: "identify", role: "cli" });
|
|
22681
23016
|
keepaliveInterval = setInterval(() => {
|
|
22682
23017
|
if (resolved || !ws || ws.readyState !== 1) return;
|
|
@@ -22697,15 +23032,21 @@ function runPendingAuth(options) {
|
|
|
22697
23032
|
keepaliveInterval = null;
|
|
22698
23033
|
}
|
|
22699
23034
|
if (resolved) return;
|
|
22700
|
-
|
|
22701
|
-
|
|
22702
|
-
|
|
23035
|
+
beginDeferredPendingCloseLog(code, reason);
|
|
23036
|
+
const delay2 = reconnectDelayMs(reconnectAttempt);
|
|
23037
|
+
reconnectAttempt += 1;
|
|
23038
|
+
if (signInQuiet.verboseLogs) {
|
|
23039
|
+
const delayLabel = formatReconnectDelayForLog(delay2);
|
|
23040
|
+
logFn(
|
|
23041
|
+
`[Bridge service] Next sign-in connection attempt in ${delayLabel} (attempt ${reconnectAttempt}).`
|
|
23042
|
+
);
|
|
23043
|
+
}
|
|
22703
23044
|
reconnectTimeout = setTimeout(() => {
|
|
22704
23045
|
reconnectTimeout = null;
|
|
22705
23046
|
connect();
|
|
22706
|
-
},
|
|
23047
|
+
}, delay2);
|
|
22707
23048
|
},
|
|
22708
|
-
onError: (err) => logFn(`[Bridge service] WebSocket error
|
|
23049
|
+
onError: (err) => logFn(`[Bridge service] WebSocket error while waiting for sign-in: ${err.message}`),
|
|
22709
23050
|
onMessage: (data) => {
|
|
22710
23051
|
const msg = data;
|
|
22711
23052
|
if (msg.type === "auth_token" && typeof msg.token === "string") {
|
|
@@ -22746,7 +23087,7 @@ function buildBridgeUrl(apiUrl, workspaceId, authToken) {
|
|
|
22746
23087
|
|
|
22747
23088
|
// src/git/discover-repos.ts
|
|
22748
23089
|
import * as fs2 from "node:fs";
|
|
22749
|
-
import * as
|
|
23090
|
+
import * as path4 from "node:path";
|
|
22750
23091
|
|
|
22751
23092
|
// ../../node_modules/.pnpm/simple-git@3.32.3/node_modules/simple-git/dist/esm/index.js
|
|
22752
23093
|
var import_file_exists = __toESM(require_dist(), 1);
|
|
@@ -22755,7 +23096,7 @@ var import_promise_deferred = __toESM(require_dist2(), 1);
|
|
|
22755
23096
|
var import_promise_deferred2 = __toESM(require_dist2(), 1);
|
|
22756
23097
|
import { Buffer as Buffer2 } from "node:buffer";
|
|
22757
23098
|
import { spawn as spawn3 } from "child_process";
|
|
22758
|
-
import { normalize } from "node:path";
|
|
23099
|
+
import { normalize as normalize2 } from "node:path";
|
|
22759
23100
|
import { EventEmitter } from "node:events";
|
|
22760
23101
|
var __defProp2 = Object.defineProperty;
|
|
22761
23102
|
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
|
@@ -24008,8 +24349,8 @@ var init_git_executor_chain = __esm2({
|
|
|
24008
24349
|
get cwd() {
|
|
24009
24350
|
return this._cwd || this._executor.cwd;
|
|
24010
24351
|
}
|
|
24011
|
-
set cwd(
|
|
24012
|
-
this._cwd =
|
|
24352
|
+
set cwd(cwd) {
|
|
24353
|
+
this._cwd = cwd;
|
|
24013
24354
|
}
|
|
24014
24355
|
get env() {
|
|
24015
24356
|
return this._executor.env;
|
|
@@ -24200,8 +24541,8 @@ var init_git_executor = __esm2({
|
|
|
24200
24541
|
"use strict";
|
|
24201
24542
|
init_git_executor_chain();
|
|
24202
24543
|
GitExecutor = class {
|
|
24203
|
-
constructor(
|
|
24204
|
-
this.cwd =
|
|
24544
|
+
constructor(cwd, _scheduler, _plugins) {
|
|
24545
|
+
this.cwd = cwd;
|
|
24205
24546
|
this._scheduler = _scheduler;
|
|
24206
24547
|
this._plugins = _plugins;
|
|
24207
24548
|
this._chain = new GitExecutorChain(this, this._scheduler, this._plugins);
|
|
@@ -26058,7 +26399,7 @@ var init_branch = __esm2({
|
|
|
26058
26399
|
});
|
|
26059
26400
|
function toPath(input) {
|
|
26060
26401
|
const path24 = input.trim().replace(/^["']|["']$/g, "");
|
|
26061
|
-
return path24 &&
|
|
26402
|
+
return path24 && normalize2(path24);
|
|
26062
26403
|
}
|
|
26063
26404
|
var parseCheckIgnore;
|
|
26064
26405
|
var init_CheckIgnore = __esm2({
|
|
@@ -27331,9 +27672,9 @@ async function isGitRepoDirectory(dirPath) {
|
|
|
27331
27672
|
}
|
|
27332
27673
|
|
|
27333
27674
|
// src/git/discover-repos.ts
|
|
27334
|
-
async function discoverGitRepos(
|
|
27675
|
+
async function discoverGitRepos(cwd = getBridgeWorkspaceDirectory()) {
|
|
27335
27676
|
const result = [];
|
|
27336
|
-
const cwdResolved =
|
|
27677
|
+
const cwdResolved = path4.resolve(cwd);
|
|
27337
27678
|
if (await isGitRepoDirectory(cwdResolved)) {
|
|
27338
27679
|
const remoteUrl = await getRemoteOriginUrl(cwdResolved);
|
|
27339
27680
|
result.push({ absolutePath: cwdResolved, remoteUrl });
|
|
@@ -27346,7 +27687,7 @@ async function discoverGitRepos(cwd3 = process.cwd()) {
|
|
|
27346
27687
|
}
|
|
27347
27688
|
for (const ent of entries) {
|
|
27348
27689
|
if (!ent.isDirectory()) continue;
|
|
27349
|
-
const childPath =
|
|
27690
|
+
const childPath = path4.join(cwdResolved, ent.name);
|
|
27350
27691
|
if (await isGitRepoDirectory(childPath)) {
|
|
27351
27692
|
const remoteUrl = await getRemoteOriginUrl(childPath);
|
|
27352
27693
|
result.push({ absolutePath: childPath, remoteUrl });
|
|
@@ -27355,11 +27696,11 @@ async function discoverGitRepos(cwd3 = process.cwd()) {
|
|
|
27355
27696
|
return result;
|
|
27356
27697
|
}
|
|
27357
27698
|
async function discoverGitReposUnderRoot(rootAbs) {
|
|
27358
|
-
const root =
|
|
27699
|
+
const root = path4.resolve(rootAbs);
|
|
27359
27700
|
const roots = [];
|
|
27360
27701
|
async function walk(dir) {
|
|
27361
27702
|
if (await isGitRepoDirectory(dir)) {
|
|
27362
|
-
roots.push(
|
|
27703
|
+
roots.push(path4.resolve(dir));
|
|
27363
27704
|
return;
|
|
27364
27705
|
}
|
|
27365
27706
|
let entries;
|
|
@@ -27370,7 +27711,7 @@ async function discoverGitReposUnderRoot(rootAbs) {
|
|
|
27370
27711
|
}
|
|
27371
27712
|
for (const ent of entries) {
|
|
27372
27713
|
if (!ent.isDirectory() || ent.name === ".git") continue;
|
|
27373
|
-
await walk(
|
|
27714
|
+
await walk(path4.join(dir, ent.name));
|
|
27374
27715
|
}
|
|
27375
27716
|
}
|
|
27376
27717
|
await walk(root);
|
|
@@ -27397,56 +27738,40 @@ function reportGitRepos(getWs, log2) {
|
|
|
27397
27738
|
}
|
|
27398
27739
|
}
|
|
27399
27740
|
}).catch((err) => {
|
|
27400
|
-
log2(
|
|
27741
|
+
log2(
|
|
27742
|
+
`[Bridge service] Git repository discovery failed: ${err instanceof Error ? err.message : String(err)}`
|
|
27743
|
+
);
|
|
27401
27744
|
});
|
|
27402
27745
|
});
|
|
27403
27746
|
}
|
|
27404
27747
|
|
|
27405
|
-
// src/bridge/connection/schedule-reconnect.ts
|
|
27406
|
-
var RECONNECT_BASE_MS = 1e3;
|
|
27407
|
-
var RECONNECT_MAX_MS = 3e4;
|
|
27408
|
-
function scheduleReconnect(state, connect, log2) {
|
|
27409
|
-
if (state.closedByUser || state.currentWs != null) return;
|
|
27410
|
-
const delay2 = Math.min(
|
|
27411
|
-
RECONNECT_BASE_MS * 2 ** state.reconnectAttempt,
|
|
27412
|
-
RECONNECT_MAX_MS
|
|
27413
|
-
);
|
|
27414
|
-
state.reconnectAttempt += 1;
|
|
27415
|
-
log2(`[Bridge service] reconnect attempt ${state.reconnectAttempt} in ${delay2 / 1e3}s\u2026`);
|
|
27416
|
-
state.reconnectTimeout = setTimeout(() => {
|
|
27417
|
-
state.reconnectTimeout = null;
|
|
27418
|
-
connect();
|
|
27419
|
-
}, delay2);
|
|
27420
|
-
}
|
|
27421
|
-
|
|
27422
27748
|
// src/bridge/connection/close-bridge-connection.ts
|
|
27423
27749
|
async function closeBridgeConnection(state, acpManager, devServerManager, log2) {
|
|
27424
|
-
log2
|
|
27750
|
+
const say = log2 ?? logImmediate;
|
|
27751
|
+
say("Cleaning up connections\u2026");
|
|
27425
27752
|
await new Promise((resolve14) => setImmediate(resolve14));
|
|
27426
|
-
if (devServerManager) {
|
|
27427
|
-
log2?.("Requesting dev server processes to stop\u2026");
|
|
27428
|
-
await devServerManager.shutdownAllGraceful();
|
|
27429
|
-
}
|
|
27430
27753
|
state.closedByUser = true;
|
|
27754
|
+
clearReconnectQuietTimer(state.mainQuiet);
|
|
27755
|
+
clearReconnectQuietTimer(state.firehoseQuiet);
|
|
27431
27756
|
if (state.reconnectTimeout != null) {
|
|
27432
|
-
|
|
27757
|
+
say("Cancelling bridge reconnect timer\u2026");
|
|
27433
27758
|
clearTimeout(state.reconnectTimeout);
|
|
27434
27759
|
state.reconnectTimeout = null;
|
|
27435
27760
|
}
|
|
27436
27761
|
if (state.firehoseReconnectTimeout != null) {
|
|
27437
|
-
|
|
27762
|
+
say("Cancelling preview tunnel reconnect timer\u2026");
|
|
27438
27763
|
clearTimeout(state.firehoseReconnectTimeout);
|
|
27439
27764
|
state.firehoseReconnectTimeout = null;
|
|
27440
27765
|
}
|
|
27441
27766
|
if (state.firehoseHandle) {
|
|
27442
|
-
|
|
27767
|
+
say("Closing preview tunnel (local HTTP proxy and dev logs)\u2026");
|
|
27443
27768
|
state.firehoseHandle.close();
|
|
27444
27769
|
state.firehoseHandle = null;
|
|
27445
27770
|
}
|
|
27446
|
-
|
|
27771
|
+
say("Disconnecting local agent\u2026");
|
|
27447
27772
|
acpManager.disconnect();
|
|
27448
27773
|
if (state.currentWs) {
|
|
27449
|
-
|
|
27774
|
+
say("Closing bridge connection to the cloud\u2026");
|
|
27450
27775
|
state.currentWs.removeAllListeners();
|
|
27451
27776
|
const wsState = state.currentWs.readyState;
|
|
27452
27777
|
if (wsState === 1 || wsState === 2) {
|
|
@@ -27459,14 +27784,28 @@ async function closeBridgeConnection(state, acpManager, devServerManager, log2)
|
|
|
27459
27784
|
}
|
|
27460
27785
|
state.currentWs = null;
|
|
27461
27786
|
}
|
|
27787
|
+
if (devServerManager) {
|
|
27788
|
+
say("Stopping local dev server processes\u2026");
|
|
27789
|
+
await devServerManager.shutdownAllGraceful();
|
|
27790
|
+
}
|
|
27791
|
+
say("Shutdown complete.");
|
|
27462
27792
|
}
|
|
27463
27793
|
|
|
27464
27794
|
// src/git/session-git-queue.ts
|
|
27795
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
27796
|
+
import { readFile, stat } from "node:fs/promises";
|
|
27797
|
+
import { promisify as promisify2 } from "node:util";
|
|
27798
|
+
import * as path6 from "node:path";
|
|
27799
|
+
|
|
27800
|
+
// src/git/pre-turn-snapshot.ts
|
|
27801
|
+
import * as fs3 from "node:fs";
|
|
27802
|
+
import * as path5 from "node:path";
|
|
27465
27803
|
import { execFile } from "node:child_process";
|
|
27466
27804
|
import { promisify } from "node:util";
|
|
27467
|
-
import * as path3 from "node:path";
|
|
27468
27805
|
var execFileAsync = promisify(execFile);
|
|
27469
|
-
|
|
27806
|
+
function snapshotsDirForCwd(agentCwd) {
|
|
27807
|
+
return path5.join(agentCwd, ".buildautomaton", "snapshots");
|
|
27808
|
+
}
|
|
27470
27809
|
async function gitStashCreate(repoRoot, log2) {
|
|
27471
27810
|
try {
|
|
27472
27811
|
const { stdout } = await execFileAsync("git", ["stash", "create"], {
|
|
@@ -27475,91 +27814,200 @@ async function gitStashCreate(repoRoot, log2) {
|
|
|
27475
27814
|
});
|
|
27476
27815
|
return stdout.trim();
|
|
27477
27816
|
} catch (e) {
|
|
27478
|
-
log2(
|
|
27817
|
+
log2(
|
|
27818
|
+
`[snapshot] Git stash create failed in ${repoRoot}: ${e instanceof Error ? e.message : String(e)}`
|
|
27819
|
+
);
|
|
27479
27820
|
return "";
|
|
27480
27821
|
}
|
|
27481
27822
|
}
|
|
27482
|
-
async function
|
|
27483
|
-
|
|
27484
|
-
|
|
27823
|
+
async function gitRun(repoRoot, args, log2, label) {
|
|
27824
|
+
try {
|
|
27825
|
+
await execFileAsync("git", args, { cwd: repoRoot, maxBuffer: 10 * 1024 * 1024 });
|
|
27826
|
+
return { ok: true };
|
|
27827
|
+
} catch (e) {
|
|
27828
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
27829
|
+
log2(`[snapshot] Git ${label} failed in ${repoRoot}: ${msg}`);
|
|
27830
|
+
return { ok: false, error: msg };
|
|
27831
|
+
}
|
|
27832
|
+
}
|
|
27833
|
+
async function resolveSnapshotRepoRoots(options) {
|
|
27834
|
+
const { worktreePaths, fallbackCwd, log: log2 } = options;
|
|
27835
|
+
if (worktreePaths?.length) {
|
|
27836
|
+
const uniq = [...new Set(worktreePaths.map((p) => path5.resolve(p)))];
|
|
27837
|
+
return uniq;
|
|
27838
|
+
}
|
|
27839
|
+
try {
|
|
27840
|
+
const repos = await discoverGitReposUnderRoot(fallbackCwd);
|
|
27841
|
+
return repos.map((r) => r.absolutePath);
|
|
27842
|
+
} catch (e) {
|
|
27843
|
+
log2(`[snapshot] Discover repositories failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
27844
|
+
return [];
|
|
27845
|
+
}
|
|
27846
|
+
}
|
|
27847
|
+
async function capturePreTurnSnapshot(options) {
|
|
27848
|
+
const { runId, repoRoots, agentCwd, log: log2 } = options;
|
|
27849
|
+
if (!runId || !repoRoots.length) {
|
|
27850
|
+
return { ok: false, error: "No git repos to snapshot" };
|
|
27851
|
+
}
|
|
27485
27852
|
const repos = [];
|
|
27486
27853
|
for (const root of repoRoots) {
|
|
27487
27854
|
const stashSha = await gitStashCreate(root, log2);
|
|
27488
27855
|
repos.push({ path: root, stashSha });
|
|
27489
27856
|
}
|
|
27490
|
-
|
|
27491
|
-
|
|
27492
|
-
}
|
|
27493
|
-
|
|
27494
|
-
|
|
27495
|
-
const repos = sessionBoundaryBySessionId.get(sessionId);
|
|
27496
|
-
if (!repos?.length) {
|
|
27497
|
-
log2(`[session-git-queue] no boundary for ${sessionId.slice(0, 8)}\u2026; skip aggregate diff`);
|
|
27498
|
-
return;
|
|
27857
|
+
const dir = snapshotsDirForCwd(agentCwd);
|
|
27858
|
+
try {
|
|
27859
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
27860
|
+
} catch (e) {
|
|
27861
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
27499
27862
|
}
|
|
27500
|
-
|
|
27501
|
-
|
|
27502
|
-
|
|
27503
|
-
|
|
27504
|
-
|
|
27505
|
-
|
|
27506
|
-
|
|
27507
|
-
|
|
27508
|
-
|
|
27509
|
-
|
|
27510
|
-
namesRaw = stdout;
|
|
27511
|
-
} catch (e) {
|
|
27512
|
-
log2(
|
|
27513
|
-
`[session-git-queue] git diff --name-only failed in ${repo.path}: ${e instanceof Error ? e.message : String(e)}`
|
|
27514
|
-
);
|
|
27515
|
-
continue;
|
|
27516
|
-
}
|
|
27517
|
-
const lines = namesRaw.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
27518
|
-
const slug = path3.basename(repo.path).replace(/[^\w.-]+/g, "_") || "repo";
|
|
27519
|
-
for (const rel of lines) {
|
|
27520
|
-
if (rel.includes("..")) continue;
|
|
27521
|
-
try {
|
|
27522
|
-
const { stdout: patchContent } = await execFileAsync(
|
|
27523
|
-
"git",
|
|
27524
|
-
["diff", "--no-color", repo.stashSha, "--", rel],
|
|
27525
|
-
{
|
|
27526
|
-
cwd: repo.path,
|
|
27527
|
-
maxBuffer: 50 * 1024 * 1024
|
|
27528
|
-
}
|
|
27529
|
-
);
|
|
27530
|
-
if (!patchContent.trim()) continue;
|
|
27531
|
-
const displayPath = multiRepo ? `${slug}/${rel}` : rel;
|
|
27532
|
-
sendSessionUpdate({
|
|
27533
|
-
type: "session_file_change",
|
|
27534
|
-
sessionId,
|
|
27535
|
-
runId,
|
|
27536
|
-
path: displayPath,
|
|
27537
|
-
patchContent
|
|
27538
|
-
});
|
|
27539
|
-
} catch (e) {
|
|
27540
|
-
log2(`[session-git-queue] git diff failed for ${rel}: ${e instanceof Error ? e.message : String(e)}`);
|
|
27541
|
-
}
|
|
27542
|
-
}
|
|
27863
|
+
const payload = {
|
|
27864
|
+
runId,
|
|
27865
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
27866
|
+
repos
|
|
27867
|
+
};
|
|
27868
|
+
const filePath = path5.join(dir, `${runId}.json`);
|
|
27869
|
+
try {
|
|
27870
|
+
fs3.writeFileSync(filePath, JSON.stringify(payload, null, 2), "utf8");
|
|
27871
|
+
} catch (e) {
|
|
27872
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
27543
27873
|
}
|
|
27874
|
+
const repoList = repos.map((r) => r.path).join(", ");
|
|
27875
|
+
log2(
|
|
27876
|
+
`[snapshot] Saved pre-turn snapshot ${runId.slice(0, 8)}\u2026 (${repos.length} repo(s)): ${repoList}`
|
|
27877
|
+
);
|
|
27878
|
+
return { ok: true, filePath, repos };
|
|
27544
27879
|
}
|
|
27545
|
-
|
|
27546
|
-
|
|
27547
|
-
|
|
27548
|
-
|
|
27880
|
+
async function applyPreTurnSnapshot(filePath, log2) {
|
|
27881
|
+
let data;
|
|
27882
|
+
try {
|
|
27883
|
+
const raw = fs3.readFileSync(filePath, "utf8");
|
|
27884
|
+
data = JSON.parse(raw);
|
|
27885
|
+
} catch (e) {
|
|
27886
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
27887
|
+
}
|
|
27888
|
+
if (!Array.isArray(data.repos)) {
|
|
27889
|
+
return { ok: false, error: "Invalid snapshot file" };
|
|
27890
|
+
}
|
|
27891
|
+
for (const r of data.repos) {
|
|
27892
|
+
if (!r.path) continue;
|
|
27893
|
+
const reset = await gitRun(r.path, ["reset", "--hard", "HEAD"], log2, "reset --hard");
|
|
27894
|
+
if (!reset.ok) return reset;
|
|
27895
|
+
const clean = await gitRun(r.path, ["clean", "-fd"], log2, "clean -fd");
|
|
27896
|
+
if (!clean.ok) return clean;
|
|
27897
|
+
if (r.stashSha) {
|
|
27898
|
+
const ap = await gitRun(r.path, ["stash", "apply", r.stashSha], log2, "stash apply");
|
|
27899
|
+
if (!ap.ok) return ap;
|
|
27900
|
+
}
|
|
27901
|
+
}
|
|
27902
|
+
log2(`[snapshot] Restored pre-turn state for ${data.runId.slice(0, 8)}\u2026`);
|
|
27903
|
+
return { ok: true };
|
|
27904
|
+
}
|
|
27905
|
+
function snapshotFilePath(agentCwd, runId) {
|
|
27906
|
+
return path5.join(snapshotsDirForCwd(agentCwd), `${runId}.json`);
|
|
27907
|
+
}
|
|
27908
|
+
|
|
27909
|
+
// src/git/session-git-queue.ts
|
|
27910
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
27911
|
+
var MAX_FULL_FILE_TEXT_BYTES = 512 * 1024;
|
|
27912
|
+
async function readWorkspaceFileAsUtf8(absPath) {
|
|
27913
|
+
try {
|
|
27914
|
+
const st = await stat(absPath);
|
|
27915
|
+
if (!st.isFile() || st.size > MAX_FULL_FILE_TEXT_BYTES) return void 0;
|
|
27916
|
+
return await readFile(absPath, "utf8");
|
|
27917
|
+
} catch {
|
|
27918
|
+
return void 0;
|
|
27919
|
+
}
|
|
27920
|
+
}
|
|
27921
|
+
async function collectTurnGitDiffFromPreTurnSnapshot(options) {
|
|
27922
|
+
const { sessionId, runId, agentCwd, sendSessionUpdate, log: log2 } = options;
|
|
27923
|
+
const filePath = snapshotFilePath(agentCwd, runId);
|
|
27924
|
+
let data;
|
|
27925
|
+
try {
|
|
27926
|
+
const raw = await readFile(filePath, "utf8");
|
|
27927
|
+
data = JSON.parse(raw);
|
|
27928
|
+
} catch (e) {
|
|
27929
|
+
log2(
|
|
27930
|
+
`[session-git-queue] No pre-turn snapshot for run ${runId.slice(0, 8)}\u2026: ${e instanceof Error ? e.message : String(e)}`
|
|
27931
|
+
);
|
|
27932
|
+
return;
|
|
27933
|
+
}
|
|
27934
|
+
if (!Array.isArray(data.repos) || !data.repos.length) {
|
|
27935
|
+
log2(`[session-git-queue] Empty repos in snapshot ${runId.slice(0, 8)}\u2026; skipping aggregate diff.`);
|
|
27936
|
+
return;
|
|
27937
|
+
}
|
|
27938
|
+
const multiRepo = data.repos.length > 1;
|
|
27939
|
+
for (const repo of data.repos) {
|
|
27940
|
+
if (!repo.stashSha) continue;
|
|
27941
|
+
let namesRaw;
|
|
27942
|
+
try {
|
|
27943
|
+
const { stdout } = await execFileAsync2("git", ["diff", "--name-only", repo.stashSha], {
|
|
27944
|
+
cwd: repo.path,
|
|
27945
|
+
maxBuffer: 10 * 1024 * 1024
|
|
27946
|
+
});
|
|
27947
|
+
namesRaw = stdout;
|
|
27948
|
+
} catch (e) {
|
|
27949
|
+
log2(
|
|
27950
|
+
`[session-git-queue] Git diff --name-only failed in ${repo.path}: ${e instanceof Error ? e.message : String(e)}`
|
|
27951
|
+
);
|
|
27952
|
+
continue;
|
|
27953
|
+
}
|
|
27954
|
+
const lines = namesRaw.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
27955
|
+
const slug = path6.basename(repo.path).replace(/[^\w.-]+/g, "_") || "repo";
|
|
27956
|
+
for (const rel of lines) {
|
|
27957
|
+
if (rel.includes("..")) continue;
|
|
27958
|
+
try {
|
|
27959
|
+
const { stdout: patchContent } = await execFileAsync2(
|
|
27960
|
+
"git",
|
|
27961
|
+
["diff", "--no-color", repo.stashSha, "--", rel],
|
|
27962
|
+
{
|
|
27963
|
+
cwd: repo.path,
|
|
27964
|
+
maxBuffer: 50 * 1024 * 1024
|
|
27965
|
+
}
|
|
27966
|
+
);
|
|
27967
|
+
if (!patchContent.trim()) continue;
|
|
27968
|
+
const displayPath = multiRepo ? `${slug}/${rel}` : rel;
|
|
27969
|
+
const absFile = path6.join(repo.path, rel);
|
|
27970
|
+
const newText = await readWorkspaceFileAsUtf8(absFile);
|
|
27971
|
+
sendSessionUpdate({
|
|
27972
|
+
type: "session_file_change",
|
|
27973
|
+
sessionId,
|
|
27974
|
+
runId,
|
|
27975
|
+
path: displayPath,
|
|
27976
|
+
patchContent,
|
|
27977
|
+
...newText !== void 0 ? { newText } : {}
|
|
27978
|
+
});
|
|
27979
|
+
} catch (e) {
|
|
27980
|
+
log2(
|
|
27981
|
+
`[session-git-queue] Git diff failed for ${rel}: ${e instanceof Error ? e.message : String(e)}`
|
|
27982
|
+
);
|
|
27983
|
+
}
|
|
27984
|
+
}
|
|
27985
|
+
}
|
|
27986
|
+
}
|
|
27987
|
+
|
|
27988
|
+
// src/acp/send-prompt-to-agent.ts
|
|
27989
|
+
async function sendPromptToAgent(options) {
|
|
27990
|
+
const {
|
|
27549
27991
|
handle,
|
|
27550
27992
|
promptText,
|
|
27551
27993
|
promptId,
|
|
27552
27994
|
sessionId,
|
|
27553
27995
|
runId,
|
|
27996
|
+
agentCwd,
|
|
27554
27997
|
sendResult,
|
|
27555
27998
|
sendSessionUpdate,
|
|
27556
|
-
collectSessionDiffAfterTurn,
|
|
27557
27999
|
log: log2
|
|
27558
28000
|
} = options;
|
|
27559
28001
|
try {
|
|
27560
28002
|
const result = await handle.sendPrompt(promptText, {});
|
|
27561
|
-
if (
|
|
27562
|
-
await
|
|
28003
|
+
if (sessionId && runId && sendSessionUpdate && agentCwd && result.success) {
|
|
28004
|
+
await collectTurnGitDiffFromPreTurnSnapshot({
|
|
28005
|
+
sessionId,
|
|
28006
|
+
runId,
|
|
28007
|
+
agentCwd,
|
|
28008
|
+
sendSessionUpdate,
|
|
28009
|
+
log: log2
|
|
28010
|
+
});
|
|
27563
28011
|
}
|
|
27564
28012
|
sendResult({
|
|
27565
28013
|
type: "prompt_result",
|
|
@@ -27569,12 +28017,11 @@ async function sendPromptToAgent(options) {
|
|
|
27569
28017
|
...result
|
|
27570
28018
|
});
|
|
27571
28019
|
if (!result.success) {
|
|
27572
|
-
log2(`[
|
|
28020
|
+
log2(`[Agent] ${result.error ?? "Error"}`);
|
|
27573
28021
|
}
|
|
27574
28022
|
} catch (err) {
|
|
27575
28023
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
27576
|
-
log2(`[
|
|
27577
|
-
if (err instanceof Error && err.stack) log2(`[agent] ${err.stack}`);
|
|
28024
|
+
log2(`[Agent] Send failed: ${errMsg}`);
|
|
27578
28025
|
sendResult({
|
|
27579
28026
|
type: "prompt_result",
|
|
27580
28027
|
id: promptId,
|
|
@@ -27587,8 +28034,8 @@ async function sendPromptToAgent(options) {
|
|
|
27587
28034
|
}
|
|
27588
28035
|
|
|
27589
28036
|
// src/acp/ensure-acp-client.ts
|
|
27590
|
-
import * as
|
|
27591
|
-
import * as
|
|
28037
|
+
import * as fs4 from "node:fs";
|
|
28038
|
+
import * as path9 from "node:path";
|
|
27592
28039
|
|
|
27593
28040
|
// src/error-message.ts
|
|
27594
28041
|
function errorMessage(err) {
|
|
@@ -27606,79 +28053,49 @@ function isCodexAcpCommand(command) {
|
|
|
27606
28053
|
const i = command.indexOf("@zed-industries/codex-acp");
|
|
27607
28054
|
return i >= 0 && (i === 0 || command[i - 1] === "npx" || command[i - 1] === "bunx");
|
|
27608
28055
|
}
|
|
28056
|
+
function buildCodexAcpSpawnCommand(base, _sessionMode) {
|
|
28057
|
+
return [...base];
|
|
28058
|
+
}
|
|
27609
28059
|
async function createCodexAcpClient(options) {
|
|
27610
|
-
const
|
|
27611
|
-
|
|
28060
|
+
const base = options.command?.length && options.command.some((a) => a.includes("codex-acp")) ? options.command : [...DEFAULT_CODEX_ACP_COMMAND];
|
|
28061
|
+
const command = buildCodexAcpSpawnCommand(base, options.sessionMode);
|
|
28062
|
+
return createSdkStdioAcpClient({ ...options, command });
|
|
28063
|
+
}
|
|
28064
|
+
|
|
28065
|
+
// src/acp/clients/claude-code-acp-client.ts
|
|
28066
|
+
function buildClaudeCodeAcpSpawnCommand(base, sessionMode) {
|
|
28067
|
+
if (!sessionMode) return [...base];
|
|
28068
|
+
const m = sessionMode.trim();
|
|
28069
|
+
if (m === "plan") return [...base, "--permission-mode", "plan"];
|
|
28070
|
+
return [...base];
|
|
28071
|
+
}
|
|
28072
|
+
async function createClaudeCodeAcpClient(options) {
|
|
28073
|
+
const command = buildClaudeCodeAcpSpawnCommand(options.command, options.sessionMode);
|
|
28074
|
+
return createSdkStdioAcpClient({
|
|
28075
|
+
...options,
|
|
28076
|
+
command,
|
|
28077
|
+
/** Claude-based agents sometimes ignore `session/cancel`; unblocks stop / stuck prompt. */
|
|
28078
|
+
killSubprocessAfterCancelMs: options.killSubprocessAfterCancelMs ?? 1e3
|
|
28079
|
+
});
|
|
27612
28080
|
}
|
|
27613
28081
|
|
|
27614
28082
|
// src/acp/clients/cursor-acp-client.ts
|
|
27615
|
-
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
27616
|
-
import { dirname } from "node:path";
|
|
28083
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "node:fs";
|
|
28084
|
+
import { dirname as dirname2 } from "node:path";
|
|
27617
28085
|
import { spawn as spawn4 } from "node:child_process";
|
|
27618
28086
|
import * as readline from "node:readline";
|
|
27619
28087
|
|
|
27620
|
-
// src/acp/
|
|
27621
|
-
|
|
27622
|
-
|
|
27623
|
-
|
|
27624
|
-
|
|
27625
|
-
|
|
27626
|
-
|
|
27627
|
-
|
|
27628
|
-
|
|
27629
|
-
return
|
|
27630
|
-
|
|
27631
|
-
function toDisplayPathRelativeToCwd(cwd3, absolutePath) {
|
|
27632
|
-
const normalizedCwd = path4.resolve(cwd3);
|
|
27633
|
-
const rel = path4.relative(normalizedCwd, path4.resolve(absolutePath));
|
|
27634
|
-
if (!rel || rel === "") return path4.basename(absolutePath);
|
|
27635
|
-
return rel.split(path4.sep).join("/");
|
|
27636
|
-
}
|
|
27637
|
-
|
|
27638
|
-
// src/files/diff/unified-diff.ts
|
|
27639
|
-
function computeLineDiff(oldText, newText) {
|
|
27640
|
-
const oldLines = oldText.split("\n");
|
|
27641
|
-
const newLines = newText.split("\n");
|
|
27642
|
-
const m = oldLines.length;
|
|
27643
|
-
const n = newLines.length;
|
|
27644
|
-
const dp = Array(m + 1);
|
|
27645
|
-
for (let i2 = 0; i2 <= m; i2++) dp[i2] = Array(n + 1).fill(0);
|
|
27646
|
-
for (let i2 = 1; i2 <= m; i2++) {
|
|
27647
|
-
for (let j2 = 1; j2 <= n; j2++) {
|
|
27648
|
-
if (oldLines[i2 - 1] === newLines[j2 - 1]) {
|
|
27649
|
-
dp[i2][j2] = dp[i2 - 1][j2 - 1] + 1;
|
|
27650
|
-
} else {
|
|
27651
|
-
dp[i2][j2] = Math.max(dp[i2 - 1][j2], dp[i2][j2 - 1]);
|
|
27652
|
-
}
|
|
27653
|
-
}
|
|
27654
|
-
}
|
|
27655
|
-
const result = [];
|
|
27656
|
-
let i = m;
|
|
27657
|
-
let j = n;
|
|
27658
|
-
while (i > 0 || j > 0) {
|
|
27659
|
-
if (i > 0 && j > 0 && oldLines[i - 1] === newLines[j - 1]) {
|
|
27660
|
-
result.unshift({ type: "context", line: oldLines[i - 1] });
|
|
27661
|
-
i--;
|
|
27662
|
-
j--;
|
|
27663
|
-
} else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
|
|
27664
|
-
result.unshift({ type: "add", line: newLines[j - 1] });
|
|
27665
|
-
j--;
|
|
27666
|
-
} else {
|
|
27667
|
-
result.unshift({ type: "remove", line: oldLines[i - 1] });
|
|
27668
|
-
i--;
|
|
27669
|
-
}
|
|
27670
|
-
}
|
|
27671
|
-
return result;
|
|
27672
|
-
}
|
|
27673
|
-
function editSnippetToUnifiedDiff(filePath, oldText, newText) {
|
|
27674
|
-
const lines = computeLineDiff(oldText, newText);
|
|
27675
|
-
const out = [`--- ${filePath}`, `+++ ${filePath}`];
|
|
27676
|
-
for (const d of lines) {
|
|
27677
|
-
if (d.type === "add") out.push(`+${d.line}`);
|
|
27678
|
-
else if (d.type === "remove") out.push(`-${d.line}`);
|
|
27679
|
-
else out.push(` ${d.line}`);
|
|
27680
|
-
}
|
|
27681
|
-
return out.join("\n");
|
|
28088
|
+
// src/acp/format-session-update-kind-for-log.ts
|
|
28089
|
+
var SESSION_UPDATE_KIND_LABELS = {
|
|
28090
|
+
tool_call: "Tool call",
|
|
28091
|
+
tool_call_update: "Tool call status",
|
|
28092
|
+
agent_message_chunk: "Agent message chunk",
|
|
28093
|
+
update: "Session update"
|
|
28094
|
+
};
|
|
28095
|
+
function formatSessionUpdateKindForLog(kind) {
|
|
28096
|
+
const known = SESSION_UPDATE_KIND_LABELS[kind];
|
|
28097
|
+
if (known) return known;
|
|
28098
|
+
return kind.split("_").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(" ");
|
|
27682
28099
|
}
|
|
27683
28100
|
|
|
27684
28101
|
// src/acp/clients/cursor-acp-client.ts
|
|
@@ -27705,12 +28122,19 @@ function sliceLinesByRange(content, line, limit) {
|
|
|
27705
28122
|
const end = limit != null && limit > 0 ? start + limit : lines.length;
|
|
27706
28123
|
return lines.slice(start, end).join("\n");
|
|
27707
28124
|
}
|
|
28125
|
+
function buildCursorAcpSpawnCommand(base, sessionMode) {
|
|
28126
|
+
if (!sessionMode) return [...base];
|
|
28127
|
+
const m = sessionMode.trim();
|
|
28128
|
+
if (m !== "ask" && m !== "plan") return [...base];
|
|
28129
|
+
return [...base, "--mode", m];
|
|
28130
|
+
}
|
|
27708
28131
|
async function createCursorAcpClient(options) {
|
|
27709
|
-
const
|
|
28132
|
+
const command = buildCursorAcpSpawnCommand(options.command, options.sessionMode);
|
|
28133
|
+
const { cwd = getBridgeWorkspaceDirectory(), onSessionUpdate, onRequest, onFileChange } = options;
|
|
27710
28134
|
const dbgFs = process.env.BUILDAMATON_DEBUG_ACP_FS === "1";
|
|
27711
28135
|
const isWindows = process.platform === "win32";
|
|
27712
28136
|
const child = spawn4(command[0], command.slice(1), {
|
|
27713
|
-
cwd
|
|
28137
|
+
cwd,
|
|
27714
28138
|
stdio: ["pipe", "pipe", "inherit"],
|
|
27715
28139
|
env: process.env,
|
|
27716
28140
|
shell: isWindows
|
|
@@ -27768,7 +28192,10 @@ async function createCursorAcpClient(options) {
|
|
|
27768
28192
|
const toolCall = update.toolCall ?? update.tool_call;
|
|
27769
28193
|
const toolName = typeof toolCall?.name === "string" ? toolCall.name : "";
|
|
27770
28194
|
if (dbgFs && (sessionUpdate === "tool_call" || sessionUpdate === "tool_call_update")) {
|
|
27771
|
-
|
|
28195
|
+
const kindLabel = formatSessionUpdateKindForLog(sessionUpdate ?? "update");
|
|
28196
|
+
console.error(
|
|
28197
|
+
`[acp] Received session update (${kindLabel}) tool=${toolName || "(none)"}`
|
|
28198
|
+
);
|
|
27772
28199
|
}
|
|
27773
28200
|
const isTextChunk = sessionUpdate === "agent_message_chunk" && update.content?.text;
|
|
27774
28201
|
if (isTextChunk && update.content?.text) {
|
|
@@ -27788,14 +28215,14 @@ async function createCursorAcpClient(options) {
|
|
|
27788
28215
|
if (dbgFs) {
|
|
27789
28216
|
console.error(`[acp-fs] ${method} path=${filePath.slice(0, 200)}${filePath.length > 200 ? "\u2026" : ""}`);
|
|
27790
28217
|
}
|
|
27791
|
-
const abs = resolveSafePathUnderCwd(
|
|
28218
|
+
const abs = resolveSafePathUnderCwd(cwd, filePath);
|
|
27792
28219
|
if (!abs) {
|
|
27793
28220
|
if (dbgFs) console.error(`[acp-fs] ${method} rejected path (outside cwd or empty)`);
|
|
27794
28221
|
respondJsonRpcError(id, -32602, "Invalid or disallowed path");
|
|
27795
28222
|
return;
|
|
27796
28223
|
}
|
|
27797
28224
|
try {
|
|
27798
|
-
let content =
|
|
28225
|
+
let content = readFileSync3(abs, "utf8");
|
|
27799
28226
|
const line2 = typeof params.line === "number" ? params.line : void 0;
|
|
27800
28227
|
const limit = typeof params.limit === "number" ? params.limit : void 0;
|
|
27801
28228
|
content = sliceLinesByRange(content, line2, limit);
|
|
@@ -27819,7 +28246,7 @@ async function createCursorAcpClient(options) {
|
|
|
27819
28246
|
`[acp-fs] ${method} path=${filePath.slice(0, 200)}${filePath.length > 200 ? "\u2026" : ""} newBytes=${newText.length}`
|
|
27820
28247
|
);
|
|
27821
28248
|
}
|
|
27822
|
-
const abs = resolveSafePathUnderCwd(
|
|
28249
|
+
const abs = resolveSafePathUnderCwd(cwd, filePath);
|
|
27823
28250
|
if (!abs) {
|
|
27824
28251
|
if (dbgFs) console.error(`[acp-fs] ${method} rejected path (outside cwd or empty): ${filePath.slice(0, 120)}`);
|
|
27825
28252
|
respondJsonRpcError(id, -32602, "Invalid or disallowed path");
|
|
@@ -27827,7 +28254,7 @@ async function createCursorAcpClient(options) {
|
|
|
27827
28254
|
}
|
|
27828
28255
|
let oldText = "";
|
|
27829
28256
|
try {
|
|
27830
|
-
oldText =
|
|
28257
|
+
oldText = readFileSync3(abs, "utf8");
|
|
27831
28258
|
} catch (e) {
|
|
27832
28259
|
if (e.code !== "ENOENT") {
|
|
27833
28260
|
respondJsonRpcError(id, -32e3, e instanceof Error ? e.message : String(e));
|
|
@@ -27835,13 +28262,13 @@ async function createCursorAcpClient(options) {
|
|
|
27835
28262
|
}
|
|
27836
28263
|
}
|
|
27837
28264
|
try {
|
|
27838
|
-
|
|
27839
|
-
|
|
28265
|
+
mkdirSync3(dirname2(abs), { recursive: true });
|
|
28266
|
+
writeFileSync3(abs, newText, "utf8");
|
|
27840
28267
|
} catch (e) {
|
|
27841
28268
|
respondJsonRpcError(id, -32e3, e instanceof Error ? e.message : String(e));
|
|
27842
28269
|
return;
|
|
27843
28270
|
}
|
|
27844
|
-
const displayPath = toDisplayPathRelativeToCwd(
|
|
28271
|
+
const displayPath = toDisplayPathRelativeToCwd(cwd, abs);
|
|
27845
28272
|
const patchContent = editSnippetToUnifiedDiff(displayPath, oldText, newText);
|
|
27846
28273
|
onFileChange?.({ path: displayPath, oldText, newText, patchContent });
|
|
27847
28274
|
respond(id, null);
|
|
@@ -27883,7 +28310,7 @@ async function createCursorAcpClient(options) {
|
|
|
27883
28310
|
clientInfo: { name: "buildautomaton-cli", version: "0.1.0" }
|
|
27884
28311
|
});
|
|
27885
28312
|
await send("authenticate", { methodId: "cursor_login" });
|
|
27886
|
-
const newResult = await send("session/new", { cwd
|
|
28313
|
+
const newResult = await send("session/new", { cwd, mcpServers: [] });
|
|
27887
28314
|
const sessionId = newResult?.sessionId ?? "";
|
|
27888
28315
|
if (!sessionId) throw new Error("Cursor ACP session/new did not return sessionId");
|
|
27889
28316
|
resolve14({
|
|
@@ -27934,8 +28361,20 @@ async function createCursorAcpClient(options) {
|
|
|
27934
28361
|
var AGENT_TYPE_DEFAULT_COMMANDS = {
|
|
27935
28362
|
"cursor-cli": ["agent", "acp"],
|
|
27936
28363
|
"codex-acp": [...DEFAULT_CODEX_ACP_COMMAND],
|
|
27937
|
-
|
|
28364
|
+
/** ACP stdio agent; `@anthropic-ai/claude-code` is the interactive CLI and does not speak ACP on stdout. */
|
|
28365
|
+
"claude-code": ["npx", "--yes", "@agentclientprotocol/claude-agent-acp"]
|
|
27938
28366
|
};
|
|
28367
|
+
var AGENT_TYPE_DISPLAY_NAMES = {
|
|
28368
|
+
"cursor-cli": "Cursor",
|
|
28369
|
+
"codex-acp": "Codex",
|
|
28370
|
+
"claude-code": "Claude Code"
|
|
28371
|
+
};
|
|
28372
|
+
function getAgentTypeDisplayName(agentType) {
|
|
28373
|
+
if (agentType == null || agentType === "") return "Unknown agent";
|
|
28374
|
+
const known = AGENT_TYPE_DISPLAY_NAMES[agentType];
|
|
28375
|
+
if (known) return known;
|
|
28376
|
+
return agentType.split(/[-_]/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(" ");
|
|
28377
|
+
}
|
|
27939
28378
|
function useCursorAcp(agentType, command) {
|
|
27940
28379
|
if (agentType === "cursor-cli") return true;
|
|
27941
28380
|
return command[0] === "agent" && command[1] === "acp";
|
|
@@ -27948,9 +28387,28 @@ function resolveAgentCommand(preferredAgentType) {
|
|
|
27948
28387
|
if (!preferredAgentType) return null;
|
|
27949
28388
|
const command = AGENT_TYPE_DEFAULT_COMMANDS[preferredAgentType];
|
|
27950
28389
|
if (!command?.length) return null;
|
|
27951
|
-
|
|
27952
|
-
|
|
27953
|
-
|
|
28390
|
+
if (useCursorAcp(preferredAgentType, command)) {
|
|
28391
|
+
return {
|
|
28392
|
+
command,
|
|
28393
|
+
label: preferredAgentType,
|
|
28394
|
+
createClient: createCursorAcpClient,
|
|
28395
|
+
spawnCommandForSession: (sessionMode) => buildCursorAcpSpawnCommand(command, sessionMode)
|
|
28396
|
+
};
|
|
28397
|
+
}
|
|
28398
|
+
if (useCodexAcp(preferredAgentType, command)) {
|
|
28399
|
+
return {
|
|
28400
|
+
command,
|
|
28401
|
+
label: preferredAgentType,
|
|
28402
|
+
createClient: createCodexAcpClient,
|
|
28403
|
+
spawnCommandForSession: (sessionMode) => buildCodexAcpSpawnCommand(command, sessionMode)
|
|
28404
|
+
};
|
|
28405
|
+
}
|
|
28406
|
+
return {
|
|
28407
|
+
command,
|
|
28408
|
+
label: preferredAgentType,
|
|
28409
|
+
createClient: createClaudeCodeAcpClient,
|
|
28410
|
+
spawnCommandForSession: (sessionMode) => buildClaudeCodeAcpSpawnCommand(command, sessionMode)
|
|
28411
|
+
};
|
|
27954
28412
|
}
|
|
27955
28413
|
|
|
27956
28414
|
// src/acp/session-file-change-path-kind.ts
|
|
@@ -27959,16 +28417,16 @@ import { existsSync, statSync } from "node:fs";
|
|
|
27959
28417
|
|
|
27960
28418
|
// src/git/get-git-repo-root-sync.ts
|
|
27961
28419
|
import { execFileSync } from "node:child_process";
|
|
27962
|
-
import * as
|
|
28420
|
+
import * as path7 from "node:path";
|
|
27963
28421
|
function getGitRepoRootSync(startDir) {
|
|
27964
28422
|
try {
|
|
27965
28423
|
const out = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
27966
|
-
cwd:
|
|
28424
|
+
cwd: path7.resolve(startDir),
|
|
27967
28425
|
encoding: "utf8",
|
|
27968
28426
|
stdio: ["ignore", "pipe", "ignore"],
|
|
27969
28427
|
maxBuffer: 1024 * 1024
|
|
27970
28428
|
}).trim();
|
|
27971
|
-
return out ?
|
|
28429
|
+
return out ? path7.resolve(out) : null;
|
|
27972
28430
|
} catch {
|
|
27973
28431
|
return null;
|
|
27974
28432
|
}
|
|
@@ -27976,65 +28434,65 @@ function getGitRepoRootSync(startDir) {
|
|
|
27976
28434
|
|
|
27977
28435
|
// src/acp/workspace-files.ts
|
|
27978
28436
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
27979
|
-
import { readFileSync as
|
|
27980
|
-
import * as
|
|
27981
|
-
function resolveWorkspaceFilePath(
|
|
28437
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
28438
|
+
import * as path8 from "node:path";
|
|
28439
|
+
function resolveWorkspaceFilePath(cwd, rawPath) {
|
|
27982
28440
|
const trimmed2 = rawPath.trim();
|
|
27983
28441
|
if (!trimmed2) return null;
|
|
27984
|
-
const normalizedCwd =
|
|
27985
|
-
let abs = resolveSafePathUnderCwd(
|
|
28442
|
+
const normalizedCwd = path8.resolve(cwd);
|
|
28443
|
+
let abs = resolveSafePathUnderCwd(cwd, trimmed2);
|
|
27986
28444
|
if (!abs) {
|
|
27987
|
-
const candidate =
|
|
27988
|
-
const gitRoot2 = getGitRepoRootSync(
|
|
28445
|
+
const candidate = path8.isAbsolute(trimmed2) ? path8.normalize(trimmed2) : path8.normalize(path8.resolve(normalizedCwd, trimmed2));
|
|
28446
|
+
const gitRoot2 = getGitRepoRootSync(cwd);
|
|
27989
28447
|
if (!gitRoot2) return null;
|
|
27990
|
-
const rel =
|
|
27991
|
-
if (rel.startsWith("..") ||
|
|
28448
|
+
const rel = path8.relative(gitRoot2, candidate);
|
|
28449
|
+
if (rel.startsWith("..") || path8.isAbsolute(rel)) return null;
|
|
27992
28450
|
abs = candidate;
|
|
27993
28451
|
}
|
|
27994
|
-
const gitRoot = getGitRepoRootSync(
|
|
28452
|
+
const gitRoot = getGitRepoRootSync(cwd);
|
|
27995
28453
|
if (gitRoot) {
|
|
27996
|
-
const relFromRoot =
|
|
27997
|
-
if (!relFromRoot.startsWith("..") && !
|
|
27998
|
-
return { abs, display: relFromRoot.split(
|
|
28454
|
+
const relFromRoot = path8.relative(gitRoot, abs);
|
|
28455
|
+
if (!relFromRoot.startsWith("..") && !path8.isAbsolute(relFromRoot)) {
|
|
28456
|
+
return { abs, display: relFromRoot.split(path8.sep).join("/") };
|
|
27999
28457
|
}
|
|
28000
28458
|
}
|
|
28001
|
-
return { abs, display: toDisplayPathRelativeToCwd(
|
|
28459
|
+
return { abs, display: toDisplayPathRelativeToCwd(cwd, abs) };
|
|
28002
28460
|
}
|
|
28003
|
-
function readUtf8WorkspaceFile(
|
|
28461
|
+
function readUtf8WorkspaceFile(cwd, displayPath) {
|
|
28004
28462
|
if (!displayPath || displayPath.includes("..")) return "";
|
|
28005
|
-
const gitRoot = getGitRepoRootSync(
|
|
28463
|
+
const gitRoot = getGitRepoRootSync(cwd);
|
|
28006
28464
|
if (gitRoot) {
|
|
28007
|
-
const abs2 =
|
|
28008
|
-
const rel =
|
|
28009
|
-
if (!rel.startsWith("..") && !
|
|
28465
|
+
const abs2 = path8.resolve(gitRoot, displayPath);
|
|
28466
|
+
const rel = path8.relative(gitRoot, abs2);
|
|
28467
|
+
if (!rel.startsWith("..") && !path8.isAbsolute(rel)) {
|
|
28010
28468
|
try {
|
|
28011
|
-
return
|
|
28469
|
+
return readFileSync4(abs2, "utf8");
|
|
28012
28470
|
} catch {
|
|
28013
28471
|
}
|
|
28014
28472
|
}
|
|
28015
28473
|
}
|
|
28016
|
-
const abs = resolveSafePathUnderCwd(
|
|
28474
|
+
const abs = resolveSafePathUnderCwd(cwd, displayPath);
|
|
28017
28475
|
if (!abs) return "";
|
|
28018
28476
|
try {
|
|
28019
|
-
return
|
|
28477
|
+
return readFileSync4(abs, "utf8");
|
|
28020
28478
|
} catch {
|
|
28021
28479
|
return "";
|
|
28022
28480
|
}
|
|
28023
28481
|
}
|
|
28024
|
-
function tryWorkspaceDisplayToAbs(
|
|
28482
|
+
function tryWorkspaceDisplayToAbs(cwd, displayPath) {
|
|
28025
28483
|
if (!displayPath || displayPath.includes("..")) return null;
|
|
28026
|
-
const gitRoot = getGitRepoRootSync(
|
|
28484
|
+
const gitRoot = getGitRepoRootSync(cwd);
|
|
28027
28485
|
if (gitRoot) {
|
|
28028
|
-
const abs =
|
|
28029
|
-
const rel =
|
|
28030
|
-
if (!rel.startsWith("..") && !
|
|
28486
|
+
const abs = path8.resolve(gitRoot, displayPath);
|
|
28487
|
+
const rel = path8.relative(gitRoot, abs);
|
|
28488
|
+
if (!rel.startsWith("..") && !path8.isAbsolute(rel)) return abs;
|
|
28031
28489
|
}
|
|
28032
|
-
return resolveSafePathUnderCwd(
|
|
28490
|
+
return resolveSafePathUnderCwd(cwd, displayPath);
|
|
28033
28491
|
}
|
|
28034
|
-
function readGitHeadBlob(
|
|
28492
|
+
function readGitHeadBlob(cwd, displayPath) {
|
|
28035
28493
|
if (!displayPath || displayPath.includes("..")) return "";
|
|
28036
|
-
const gitRoot = getGitRepoRootSync(
|
|
28037
|
-
const execCwd = gitRoot ??
|
|
28494
|
+
const gitRoot = getGitRepoRootSync(cwd);
|
|
28495
|
+
const execCwd = gitRoot ?? cwd;
|
|
28038
28496
|
try {
|
|
28039
28497
|
return execFileSync2("git", ["show", `HEAD:${displayPath}`], {
|
|
28040
28498
|
cwd: execCwd,
|
|
@@ -28047,9 +28505,9 @@ function readGitHeadBlob(cwd3, displayPath) {
|
|
|
28047
28505
|
}
|
|
28048
28506
|
|
|
28049
28507
|
// src/acp/session-file-change-path-kind.ts
|
|
28050
|
-
function gitHeadPathObjectType(
|
|
28508
|
+
function gitHeadPathObjectType(cwd, displayPath) {
|
|
28051
28509
|
if (!displayPath || displayPath.includes("..")) return null;
|
|
28052
|
-
const gitRoot = getGitRepoRootSync(
|
|
28510
|
+
const gitRoot = getGitRepoRootSync(cwd);
|
|
28053
28511
|
if (!gitRoot) return null;
|
|
28054
28512
|
try {
|
|
28055
28513
|
return execFileSync3("git", ["cat-file", "-t", `HEAD:${displayPath}`], {
|
|
@@ -28060,8 +28518,8 @@ function gitHeadPathObjectType(cwd3, displayPath) {
|
|
|
28060
28518
|
return null;
|
|
28061
28519
|
}
|
|
28062
28520
|
}
|
|
28063
|
-
function getSessionFileChangeDirectoryFlags(
|
|
28064
|
-
const abs = tryWorkspaceDisplayToAbs(
|
|
28521
|
+
function getSessionFileChangeDirectoryFlags(cwd, displayPath) {
|
|
28522
|
+
const abs = tryWorkspaceDisplayToAbs(cwd, displayPath);
|
|
28065
28523
|
if (abs && existsSync(abs)) {
|
|
28066
28524
|
try {
|
|
28067
28525
|
if (statSync(abs).isDirectory()) {
|
|
@@ -28072,7 +28530,7 @@ function getSessionFileChangeDirectoryFlags(cwd3, displayPath) {
|
|
|
28072
28530
|
return { isDirectory: false, directoryRemoved: false };
|
|
28073
28531
|
}
|
|
28074
28532
|
}
|
|
28075
|
-
if (gitHeadPathObjectType(
|
|
28533
|
+
if (gitHeadPathObjectType(cwd, displayPath) === "tree") {
|
|
28076
28534
|
return { isDirectory: true, directoryRemoved: true };
|
|
28077
28535
|
}
|
|
28078
28536
|
return { isDirectory: false, directoryRemoved: false };
|
|
@@ -28086,11 +28544,13 @@ function createBridgeOnFileChange(opts) {
|
|
|
28086
28544
|
const sessionId = routing.sessionId;
|
|
28087
28545
|
const send = getSendSessionUpdate();
|
|
28088
28546
|
if (!send || !runId || !sessionId) {
|
|
28089
|
-
log2(
|
|
28547
|
+
log2(
|
|
28548
|
+
`[Bridge service] File change not forwarded (${evt.path}): session or run not wired to the bridge.`
|
|
28549
|
+
);
|
|
28090
28550
|
return;
|
|
28091
28551
|
}
|
|
28092
|
-
const
|
|
28093
|
-
const dirFlags = getSessionFileChangeDirectoryFlags(
|
|
28552
|
+
const cwd = getBridgeWorkspaceDirectory();
|
|
28553
|
+
const dirFlags = getSessionFileChangeDirectoryFlags(cwd, evt.path);
|
|
28094
28554
|
try {
|
|
28095
28555
|
send({
|
|
28096
28556
|
type: "session_file_change",
|
|
@@ -28104,7 +28564,7 @@ function createBridgeOnFileChange(opts) {
|
|
|
28104
28564
|
directoryRemoved: dirFlags.directoryRemoved
|
|
28105
28565
|
});
|
|
28106
28566
|
} catch (err) {
|
|
28107
|
-
log2(`[Bridge service]
|
|
28567
|
+
log2(`[Bridge service] Session file change failed for ${evt.path}: ${errorMessage(err)}`);
|
|
28108
28568
|
}
|
|
28109
28569
|
};
|
|
28110
28570
|
}
|
|
@@ -28131,7 +28591,9 @@ function createBridgeOnRequest(opts) {
|
|
|
28131
28591
|
}
|
|
28132
28592
|
});
|
|
28133
28593
|
} catch (err) {
|
|
28134
|
-
log2(
|
|
28594
|
+
log2(
|
|
28595
|
+
`[Bridge service] Agent protocol request forward failed (${request.method}): ${errorMessage(err)}`
|
|
28596
|
+
);
|
|
28135
28597
|
}
|
|
28136
28598
|
};
|
|
28137
28599
|
}
|
|
@@ -28163,12 +28625,12 @@ function extractDiffPath(o) {
|
|
|
28163
28625
|
}
|
|
28164
28626
|
|
|
28165
28627
|
// src/acp/hooks/extract-acp-file-diffs-from-update/push-diff.ts
|
|
28166
|
-
function pushDiffIfComplete(o,
|
|
28628
|
+
function pushDiffIfComplete(o, cwd, out) {
|
|
28167
28629
|
const t = o.type;
|
|
28168
28630
|
if (typeof t !== "string" || t.toLowerCase() !== "diff") return;
|
|
28169
28631
|
const rawPath = extractDiffPath(o);
|
|
28170
28632
|
if (!rawPath) return;
|
|
28171
|
-
const resolved = resolveWorkspaceFilePath(
|
|
28633
|
+
const resolved = resolveWorkspaceFilePath(cwd, rawPath);
|
|
28172
28634
|
if (!resolved) return;
|
|
28173
28635
|
const oldText = readOptionalTextField(o.oldText ?? o.old_text ?? o.before ?? o.oldContent);
|
|
28174
28636
|
const newText = readOptionalTextField(o.newText ?? o.new_text ?? o.after ?? o.newContent);
|
|
@@ -28187,17 +28649,17 @@ var NEST_KEYS = [
|
|
|
28187
28649
|
"data",
|
|
28188
28650
|
"arguments"
|
|
28189
28651
|
];
|
|
28190
|
-
function walkValue(value,
|
|
28652
|
+
function walkValue(value, cwd, depth, out) {
|
|
28191
28653
|
if (depth > 12 || value == null) return;
|
|
28192
28654
|
if (Array.isArray(value)) {
|
|
28193
|
-
for (const x of value) walkValue(x,
|
|
28655
|
+
for (const x of value) walkValue(x, cwd, depth + 1, out);
|
|
28194
28656
|
return;
|
|
28195
28657
|
}
|
|
28196
28658
|
if (typeof value !== "object") return;
|
|
28197
28659
|
const o = value;
|
|
28198
|
-
pushDiffIfComplete(o,
|
|
28660
|
+
pushDiffIfComplete(o, cwd, out);
|
|
28199
28661
|
if (o.type === "content" && o.content != null && typeof o.content === "object") {
|
|
28200
|
-
walkValue(o.content,
|
|
28662
|
+
walkValue(o.content, cwd, depth + 1, out);
|
|
28201
28663
|
}
|
|
28202
28664
|
for (const k of NEST_KEYS) {
|
|
28203
28665
|
if (o.type === "content" && k === "content") continue;
|
|
@@ -28205,23 +28667,23 @@ function walkValue(value, cwd3, depth, out) {
|
|
|
28205
28667
|
if (v == null) continue;
|
|
28206
28668
|
if (k === "arguments" && typeof v === "string") {
|
|
28207
28669
|
try {
|
|
28208
|
-
walkValue(JSON.parse(v),
|
|
28670
|
+
walkValue(JSON.parse(v), cwd, depth + 1, out);
|
|
28209
28671
|
} catch {
|
|
28210
28672
|
}
|
|
28211
28673
|
continue;
|
|
28212
28674
|
}
|
|
28213
|
-
walkValue(v,
|
|
28675
|
+
walkValue(v, cwd, depth + 1, out);
|
|
28214
28676
|
}
|
|
28215
28677
|
}
|
|
28216
28678
|
|
|
28217
28679
|
// src/acp/hooks/extract-acp-file-diffs-from-update/extract.ts
|
|
28218
|
-
function extractAcpFileDiffsFromUpdate(update,
|
|
28680
|
+
function extractAcpFileDiffsFromUpdate(update, cwd) {
|
|
28219
28681
|
if (!update || typeof update !== "object") return [];
|
|
28220
28682
|
const u = update;
|
|
28221
28683
|
const out = [];
|
|
28222
28684
|
const content = u.content;
|
|
28223
28685
|
if (Array.isArray(content)) {
|
|
28224
|
-
for (const x of content) walkValue(x,
|
|
28686
|
+
for (const x of content) walkValue(x, cwd, 0, out);
|
|
28225
28687
|
}
|
|
28226
28688
|
const byPath = /* @__PURE__ */ new Map();
|
|
28227
28689
|
for (const d of out) byPath.set(d.path, d);
|
|
@@ -28229,18 +28691,18 @@ function extractAcpFileDiffsFromUpdate(update, cwd3) {
|
|
|
28229
28691
|
}
|
|
28230
28692
|
|
|
28231
28693
|
// src/acp/hooks/extract-tool-target-paths.ts
|
|
28232
|
-
function addPath(
|
|
28694
|
+
function addPath(cwd, raw, out) {
|
|
28233
28695
|
if (typeof raw !== "string") return;
|
|
28234
28696
|
const trimmed2 = raw.trim();
|
|
28235
28697
|
if (!trimmed2) return;
|
|
28236
|
-
const resolved = resolveWorkspaceFilePath(
|
|
28698
|
+
const resolved = resolveWorkspaceFilePath(cwd, trimmed2);
|
|
28237
28699
|
if (!resolved) return;
|
|
28238
28700
|
out.add(resolved.display);
|
|
28239
28701
|
}
|
|
28240
|
-
function walkLocations(
|
|
28702
|
+
function walkLocations(cwd, loc, out) {
|
|
28241
28703
|
if (!Array.isArray(loc)) return;
|
|
28242
28704
|
for (const item of loc) {
|
|
28243
|
-
if (item && typeof item === "object") addPath(
|
|
28705
|
+
if (item && typeof item === "object") addPath(cwd, item.path, out);
|
|
28244
28706
|
}
|
|
28245
28707
|
}
|
|
28246
28708
|
var PATH_KEYS = [
|
|
@@ -28253,56 +28715,56 @@ var PATH_KEYS = [
|
|
|
28253
28715
|
"file_path",
|
|
28254
28716
|
"target_file"
|
|
28255
28717
|
];
|
|
28256
|
-
function collectFromObject(
|
|
28718
|
+
function collectFromObject(cwd, obj, out, depth) {
|
|
28257
28719
|
if (depth > 10) return;
|
|
28258
28720
|
for (const k of PATH_KEYS) {
|
|
28259
|
-
if (k in obj) addPath(
|
|
28721
|
+
if (k in obj) addPath(cwd, obj[k], out);
|
|
28260
28722
|
}
|
|
28261
28723
|
for (const v of Object.values(obj)) {
|
|
28262
|
-
if (v != null && typeof v === "object") collectUnknown(
|
|
28724
|
+
if (v != null && typeof v === "object") collectUnknown(cwd, v, out, depth + 1);
|
|
28263
28725
|
}
|
|
28264
28726
|
}
|
|
28265
|
-
function collectUnknown(
|
|
28727
|
+
function collectUnknown(cwd, v, out, depth) {
|
|
28266
28728
|
if (depth > 10 || v == null) return;
|
|
28267
28729
|
if (Array.isArray(v)) {
|
|
28268
|
-
for (const x of v) collectUnknown(
|
|
28730
|
+
for (const x of v) collectUnknown(cwd, x, out, depth + 1);
|
|
28269
28731
|
return;
|
|
28270
28732
|
}
|
|
28271
|
-
if (typeof v === "object") collectFromObject(
|
|
28733
|
+
if (typeof v === "object") collectFromObject(cwd, v, out, depth);
|
|
28272
28734
|
}
|
|
28273
|
-
function walkContentArray(
|
|
28735
|
+
function walkContentArray(cwd, content, out) {
|
|
28274
28736
|
if (!Array.isArray(content)) return;
|
|
28275
28737
|
for (const item of content) {
|
|
28276
28738
|
if (!item || typeof item !== "object") continue;
|
|
28277
28739
|
const o = item;
|
|
28278
28740
|
if (typeof o.type === "string" && o.type.toLowerCase() === "diff") {
|
|
28279
|
-
for (const k of PATH_KEYS) if (k in o) addPath(
|
|
28741
|
+
for (const k of PATH_KEYS) if (k in o) addPath(cwd, o[k], out);
|
|
28280
28742
|
}
|
|
28281
28743
|
if (o.type === "content" && o.content != null && typeof o.content === "object") {
|
|
28282
28744
|
const inner = o.content;
|
|
28283
28745
|
if (typeof inner.type === "string" && inner.type.toLowerCase() === "diff") {
|
|
28284
|
-
for (const k of PATH_KEYS) if (k in inner) addPath(
|
|
28746
|
+
for (const k of PATH_KEYS) if (k in inner) addPath(cwd, inner[k], out);
|
|
28285
28747
|
}
|
|
28286
28748
|
}
|
|
28287
28749
|
}
|
|
28288
28750
|
}
|
|
28289
|
-
function extractToolTargetDisplayPaths(update,
|
|
28751
|
+
function extractToolTargetDisplayPaths(update, cwd) {
|
|
28290
28752
|
const out = /* @__PURE__ */ new Set();
|
|
28291
28753
|
if (!update || typeof update !== "object") return [];
|
|
28292
28754
|
const u = update;
|
|
28293
|
-
walkLocations(
|
|
28294
|
-
walkLocations(
|
|
28295
|
-
walkLocations(
|
|
28755
|
+
walkLocations(cwd, u.locations, out);
|
|
28756
|
+
walkLocations(cwd, u.fileLocations, out);
|
|
28757
|
+
walkLocations(cwd, u.file_locations, out);
|
|
28296
28758
|
const tc = u.toolCall ?? u.tool_call;
|
|
28297
28759
|
if (tc && typeof tc.arguments === "string") {
|
|
28298
28760
|
try {
|
|
28299
28761
|
const parsed = JSON.parse(tc.arguments);
|
|
28300
|
-
collectUnknown(
|
|
28762
|
+
collectUnknown(cwd, parsed, out, 0);
|
|
28301
28763
|
} catch {
|
|
28302
28764
|
}
|
|
28303
28765
|
}
|
|
28304
|
-
walkContentArray(
|
|
28305
|
-
collectFromObject(
|
|
28766
|
+
walkContentArray(cwd, u.content, out);
|
|
28767
|
+
collectFromObject(cwd, u, out, 0);
|
|
28306
28768
|
return [...out];
|
|
28307
28769
|
}
|
|
28308
28770
|
|
|
@@ -28368,7 +28830,7 @@ var PathSnapshotTracker = class {
|
|
|
28368
28830
|
this.beforeByToolKey.delete(toolKey);
|
|
28369
28831
|
this.accumulatedPathsByToolKey.delete(toolKey);
|
|
28370
28832
|
}
|
|
28371
|
-
captureBeforeFromDisk(toolKey, paths,
|
|
28833
|
+
captureBeforeFromDisk(toolKey, paths, cwd) {
|
|
28372
28834
|
if (paths.length === 0) return;
|
|
28373
28835
|
let m = this.beforeByToolKey.get(toolKey);
|
|
28374
28836
|
if (!m) {
|
|
@@ -28377,10 +28839,10 @@ var PathSnapshotTracker = class {
|
|
|
28377
28839
|
}
|
|
28378
28840
|
for (const p of paths) {
|
|
28379
28841
|
if (m.has(p)) continue;
|
|
28380
|
-
m.set(p, readUtf8WorkspaceFile(
|
|
28842
|
+
m.set(p, readUtf8WorkspaceFile(cwd, p));
|
|
28381
28843
|
}
|
|
28382
28844
|
}
|
|
28383
|
-
ensureBeforeFromHeadForMissing(toolKey, paths,
|
|
28845
|
+
ensureBeforeFromHeadForMissing(toolKey, paths, cwd) {
|
|
28384
28846
|
let m = this.beforeByToolKey.get(toolKey);
|
|
28385
28847
|
if (!m) {
|
|
28386
28848
|
m = /* @__PURE__ */ new Map();
|
|
@@ -28388,10 +28850,10 @@ var PathSnapshotTracker = class {
|
|
|
28388
28850
|
}
|
|
28389
28851
|
for (const p of paths) {
|
|
28390
28852
|
if (m.has(p)) continue;
|
|
28391
|
-
m.set(p, readGitHeadBlob(
|
|
28853
|
+
m.set(p, readGitHeadBlob(cwd, p));
|
|
28392
28854
|
}
|
|
28393
28855
|
}
|
|
28394
|
-
flushPathSnapshots(toolKey,
|
|
28856
|
+
flushPathSnapshots(toolKey, cwd, sentPaths, send, runId, sessionId, log2) {
|
|
28395
28857
|
const t = this.debouncers.get(toolKey);
|
|
28396
28858
|
if (t) clearTimeout(t);
|
|
28397
28859
|
this.debouncers.delete(toolKey);
|
|
@@ -28400,10 +28862,10 @@ var PathSnapshotTracker = class {
|
|
|
28400
28862
|
this.beforeByToolKey.delete(toolKey);
|
|
28401
28863
|
if (!send || !runId || !sessionId) return;
|
|
28402
28864
|
for (const [displayPath, oldText] of beforeMap) {
|
|
28403
|
-
const newText = readUtf8WorkspaceFile(
|
|
28865
|
+
const newText = readUtf8WorkspaceFile(cwd, displayPath);
|
|
28404
28866
|
if (oldText === newText) continue;
|
|
28405
28867
|
const patchContent = editSnippetToUnifiedDiff(displayPath, oldText, newText);
|
|
28406
|
-
const dirFlags = getSessionFileChangeDirectoryFlags(
|
|
28868
|
+
const dirFlags = getSessionFileChangeDirectoryFlags(cwd, displayPath);
|
|
28407
28869
|
try {
|
|
28408
28870
|
send({
|
|
28409
28871
|
type: "session_file_change",
|
|
@@ -28418,35 +28880,35 @@ var PathSnapshotTracker = class {
|
|
|
28418
28880
|
});
|
|
28419
28881
|
sentPaths.add(displayPath);
|
|
28420
28882
|
} catch (err) {
|
|
28421
|
-
log2(`[Bridge service]
|
|
28883
|
+
log2(`[Bridge service] Session file change failed for ${displayPath}: ${errorMessage(err)}`);
|
|
28422
28884
|
}
|
|
28423
28885
|
}
|
|
28424
28886
|
}
|
|
28425
|
-
scheduleDebouncedFlush(toolKey,
|
|
28887
|
+
scheduleDebouncedFlush(toolKey, cwd, sentPaths, send, runId, sessionId, log2) {
|
|
28426
28888
|
const prev = this.debouncers.get(toolKey);
|
|
28427
28889
|
if (prev) clearTimeout(prev);
|
|
28428
28890
|
this.debouncers.set(
|
|
28429
28891
|
toolKey,
|
|
28430
28892
|
setTimeout(() => {
|
|
28431
28893
|
this.debouncers.delete(toolKey);
|
|
28432
|
-
this.flushPathSnapshots(toolKey,
|
|
28894
|
+
this.flushPathSnapshots(toolKey, cwd, sentPaths, send, runId, sessionId, log2);
|
|
28433
28895
|
}, PATH_SNAPSHOT_DEBOUNCE_MS)
|
|
28434
28896
|
);
|
|
28435
28897
|
}
|
|
28436
|
-
handleToolCallLifecycle(updateKind, toolKey, toolPaths, status,
|
|
28898
|
+
handleToolCallLifecycle(updateKind, toolKey, toolPaths, status, cwd, sentPaths, send, runId, sessionId, log2) {
|
|
28437
28899
|
if (updateKind === "tool_call") {
|
|
28438
28900
|
this.resetToolSnapshots(toolKey);
|
|
28439
28901
|
}
|
|
28440
28902
|
if (updateKind === "tool_call") {
|
|
28441
|
-
this.captureBeforeFromDisk(toolKey, toolPaths,
|
|
28903
|
+
this.captureBeforeFromDisk(toolKey, toolPaths, cwd);
|
|
28442
28904
|
} else if (updateKind === "tool_call_update") {
|
|
28443
28905
|
if (isCompletedToolStatus(status)) {
|
|
28444
|
-
this.ensureBeforeFromHeadForMissing(toolKey, toolPaths,
|
|
28445
|
-
this.flushPathSnapshots(toolKey,
|
|
28906
|
+
this.ensureBeforeFromHeadForMissing(toolKey, toolPaths, cwd);
|
|
28907
|
+
this.flushPathSnapshots(toolKey, cwd, sentPaths, send, runId, sessionId, log2);
|
|
28446
28908
|
} else {
|
|
28447
|
-
this.captureBeforeFromDisk(toolKey, toolPaths,
|
|
28909
|
+
this.captureBeforeFromDisk(toolKey, toolPaths, cwd);
|
|
28448
28910
|
if (this.beforeByToolKey.has(toolKey)) {
|
|
28449
|
-
this.scheduleDebouncedFlush(toolKey,
|
|
28911
|
+
this.scheduleDebouncedFlush(toolKey, cwd, sentPaths, send, runId, sessionId, log2);
|
|
28450
28912
|
}
|
|
28451
28913
|
}
|
|
28452
28914
|
}
|
|
@@ -28454,11 +28916,11 @@ var PathSnapshotTracker = class {
|
|
|
28454
28916
|
};
|
|
28455
28917
|
|
|
28456
28918
|
// src/acp/hooks/bridge-on-session-update/send-structured-file-changes.ts
|
|
28457
|
-
function sendExtractedDiffsAsSessionFileChanges(diffs, send,
|
|
28919
|
+
function sendExtractedDiffsAsSessionFileChanges(diffs, send, cwd, sessionId, runId, sentPaths, log2) {
|
|
28458
28920
|
for (const d of diffs) {
|
|
28459
28921
|
try {
|
|
28460
28922
|
const patchContent = editSnippetToUnifiedDiff(d.path, d.oldText, d.newText);
|
|
28461
|
-
const dirFlags = getSessionFileChangeDirectoryFlags(
|
|
28923
|
+
const dirFlags = getSessionFileChangeDirectoryFlags(cwd, d.path);
|
|
28462
28924
|
send({
|
|
28463
28925
|
type: "session_file_change",
|
|
28464
28926
|
sessionId,
|
|
@@ -28472,19 +28934,19 @@ function sendExtractedDiffsAsSessionFileChanges(diffs, send, cwd3, sessionId, ru
|
|
|
28472
28934
|
});
|
|
28473
28935
|
sentPaths.add(d.path);
|
|
28474
28936
|
} catch (err) {
|
|
28475
|
-
log2(`[Bridge service]
|
|
28937
|
+
log2(`[Bridge service] Session file change failed for ${d.path}: ${errorMessage(err)}`);
|
|
28476
28938
|
}
|
|
28477
28939
|
}
|
|
28478
28940
|
}
|
|
28479
|
-
function sendGitHeadVsWorkspaceForToolPaths(mergedPaths, sentPaths, send,
|
|
28941
|
+
function sendGitHeadVsWorkspaceForToolPaths(mergedPaths, sentPaths, send, cwd, sessionId, runId, log2) {
|
|
28480
28942
|
for (const displayPath of mergedPaths) {
|
|
28481
28943
|
if (sentPaths.has(displayPath)) continue;
|
|
28482
|
-
const oldText = readGitHeadBlob(
|
|
28483
|
-
const newText = readUtf8WorkspaceFile(
|
|
28944
|
+
const oldText = readGitHeadBlob(cwd, displayPath);
|
|
28945
|
+
const newText = readUtf8WorkspaceFile(cwd, displayPath);
|
|
28484
28946
|
if (oldText === newText) continue;
|
|
28485
28947
|
try {
|
|
28486
28948
|
const patchContent = editSnippetToUnifiedDiff(displayPath, oldText, newText);
|
|
28487
|
-
const dirFlags = getSessionFileChangeDirectoryFlags(
|
|
28949
|
+
const dirFlags = getSessionFileChangeDirectoryFlags(cwd, displayPath);
|
|
28488
28950
|
send({
|
|
28489
28951
|
type: "session_file_change",
|
|
28490
28952
|
sessionId,
|
|
@@ -28498,7 +28960,7 @@ function sendGitHeadVsWorkspaceForToolPaths(mergedPaths, sentPaths, send, cwd3,
|
|
|
28498
28960
|
});
|
|
28499
28961
|
sentPaths.add(displayPath);
|
|
28500
28962
|
} catch (err) {
|
|
28501
|
-
log2(`[Bridge service]
|
|
28963
|
+
log2(`[Bridge service] Session file change failed for ${displayPath}: ${errorMessage(err)}`);
|
|
28502
28964
|
}
|
|
28503
28965
|
}
|
|
28504
28966
|
}
|
|
@@ -28511,7 +28973,7 @@ function createBridgeOnSessionUpdate(opts) {
|
|
|
28511
28973
|
const runId = routing.runId;
|
|
28512
28974
|
const sessionId = routing.sessionId;
|
|
28513
28975
|
pathTracker.onRunIdChanged(runId);
|
|
28514
|
-
const
|
|
28976
|
+
const cwd = getBridgeWorkspaceDirectory();
|
|
28515
28977
|
const send = getSendSessionUpdate();
|
|
28516
28978
|
const sentFileChangePaths = /* @__PURE__ */ new Set();
|
|
28517
28979
|
const p = params;
|
|
@@ -28519,7 +28981,7 @@ function createBridgeOnSessionUpdate(opts) {
|
|
|
28519
28981
|
const isCompletedToolCallUpdate = updateKind === "tool_call_update" && isCompletedToolStatus(p.status);
|
|
28520
28982
|
const toolName = p.toolCall?.name ?? p.tool_call?.name ?? "";
|
|
28521
28983
|
const isToolUpdate = updateKind === "tool_call" || updateKind === "tool_call_update" || typeof toolName === "string" && toolName.length > 0;
|
|
28522
|
-
const toolPaths = isToolUpdate ? extractToolTargetDisplayPaths(params,
|
|
28984
|
+
const toolPaths = isToolUpdate ? extractToolTargetDisplayPaths(params, cwd) : [];
|
|
28523
28985
|
const toolKey = isToolUpdate ? pathTracker.resolveToolKey(params, updateKind) : "";
|
|
28524
28986
|
if (updateKind === "tool_call") {
|
|
28525
28987
|
pathTracker.resetToolSnapshots(toolKey);
|
|
@@ -28534,7 +28996,7 @@ function createBridgeOnSessionUpdate(opts) {
|
|
|
28534
28996
|
toolKey,
|
|
28535
28997
|
toolPaths,
|
|
28536
28998
|
p.status,
|
|
28537
|
-
|
|
28999
|
+
cwd,
|
|
28538
29000
|
sentFileChangePaths,
|
|
28539
29001
|
deliver,
|
|
28540
29002
|
runId ?? "",
|
|
@@ -28542,18 +29004,18 @@ function createBridgeOnSessionUpdate(opts) {
|
|
|
28542
29004
|
log2
|
|
28543
29005
|
);
|
|
28544
29006
|
}
|
|
28545
|
-
const diffs = extractAcpFileDiffsFromUpdate(params,
|
|
29007
|
+
const diffs = extractAcpFileDiffsFromUpdate(params, cwd);
|
|
28546
29008
|
if (diffs.length > 0 && send && runId && sessionId) {
|
|
28547
|
-
sendExtractedDiffsAsSessionFileChanges(diffs, send,
|
|
29009
|
+
sendExtractedDiffsAsSessionFileChanges(diffs, send, cwd, sessionId, runId, sentFileChangePaths, log2);
|
|
28548
29010
|
} else if (diffs.length > 0) {
|
|
28549
29011
|
log2(
|
|
28550
|
-
`[Bridge service]
|
|
29012
|
+
`[Bridge service] Agent file diff(s) not forwarded (${diffs.length}): session or run not wired to the bridge.`
|
|
28551
29013
|
);
|
|
28552
29014
|
}
|
|
28553
29015
|
if (isCompletedToolCallUpdate && send && runId && sessionId) {
|
|
28554
29016
|
const acc = pathTracker.accumulatedPathsByToolKey.get(toolKey);
|
|
28555
29017
|
const merged = [.../* @__PURE__ */ new Set([...acc ? [...acc] : [], ...toolPaths])];
|
|
28556
|
-
sendGitHeadVsWorkspaceForToolPaths(merged, sentFileChangePaths, send,
|
|
29018
|
+
sendGitHeadVsWorkspaceForToolPaths(merged, sentFileChangePaths, send, cwd, sessionId, runId, log2);
|
|
28557
29019
|
pathTracker.accumulatedPathsByToolKey.delete(toolKey);
|
|
28558
29020
|
}
|
|
28559
29021
|
if (runId && send) {
|
|
@@ -28566,7 +29028,9 @@ function createBridgeOnSessionUpdate(opts) {
|
|
|
28566
29028
|
payload: params
|
|
28567
29029
|
});
|
|
28568
29030
|
} catch (err) {
|
|
28569
|
-
log2(
|
|
29031
|
+
log2(
|
|
29032
|
+
`[Bridge service] Session update send failed (${formatSessionUpdateKindForLog(updateKind)}): ${errorMessage(err)}`
|
|
29033
|
+
);
|
|
28570
29034
|
}
|
|
28571
29035
|
}
|
|
28572
29036
|
};
|
|
@@ -28583,11 +29047,11 @@ function buildAcpSessionBridgeHooks(opts) {
|
|
|
28583
29047
|
|
|
28584
29048
|
// src/acp/ensure-acp-client.ts
|
|
28585
29049
|
async function ensureAcpClient(options) {
|
|
28586
|
-
const { state, preferredAgentType, mode, cwd
|
|
28587
|
-
const targetCwd =
|
|
28588
|
-
|
|
29050
|
+
const { state, preferredAgentType, mode, cwd, routing, sendSessionUpdate, sendRequest, log: log2 } = options;
|
|
29051
|
+
const targetCwd = path9.resolve(
|
|
29052
|
+
cwd != null && String(cwd).trim() !== "" ? String(cwd).trim() : getBridgeWorkspaceDirectory()
|
|
28589
29053
|
);
|
|
28590
|
-
if (state.acpHandle && state.lastAcpCwd != null &&
|
|
29054
|
+
if (state.acpHandle && state.lastAcpCwd != null && path9.resolve(state.lastAcpCwd) !== path9.resolve(targetCwd)) {
|
|
28591
29055
|
try {
|
|
28592
29056
|
state.acpHandle.disconnect();
|
|
28593
29057
|
} catch {
|
|
@@ -28599,12 +29063,13 @@ async function ensureAcpClient(options) {
|
|
|
28599
29063
|
const resolved = resolveAgentCommand(preferredAgentType);
|
|
28600
29064
|
if (!resolved) {
|
|
28601
29065
|
log2(
|
|
28602
|
-
`[
|
|
29066
|
+
`[Agent] No local agent type (${preferredAgentType === null ? "none" : `"${preferredAgentType}"`}). Send agent type on prompts or agent configuration after identify.`
|
|
28603
29067
|
);
|
|
28604
29068
|
state.lastAcpStartError = "No agent type: ensure the app sends agentType on prompts or agent_config for this bridge.";
|
|
28605
29069
|
return null;
|
|
28606
29070
|
}
|
|
28607
|
-
const
|
|
29071
|
+
const fullCmd = resolved.spawnCommandForSession(mode);
|
|
29072
|
+
const agentKey = `${resolved.label}::${fullCmd.join("\0")}`;
|
|
28608
29073
|
if (state.acpHandle && state.acpAgentKey !== agentKey) {
|
|
28609
29074
|
try {
|
|
28610
29075
|
state.acpHandle.disconnect();
|
|
@@ -28618,21 +29083,19 @@ async function ensureAcpClient(options) {
|
|
|
28618
29083
|
if (!state.acpStartPromise) {
|
|
28619
29084
|
let statOk = false;
|
|
28620
29085
|
try {
|
|
28621
|
-
const st =
|
|
29086
|
+
const st = fs4.statSync(targetCwd);
|
|
28622
29087
|
statOk = st.isDirectory();
|
|
28623
29088
|
if (!statOk) {
|
|
28624
29089
|
state.lastAcpStartError = `Agent cwd is not a directory: ${targetCwd}`;
|
|
28625
|
-
log2(`[
|
|
29090
|
+
log2(`[Agent] ${state.lastAcpStartError}`);
|
|
28626
29091
|
}
|
|
28627
29092
|
} catch {
|
|
28628
29093
|
state.lastAcpStartError = `Agent cwd missing or inaccessible: ${targetCwd}`;
|
|
28629
|
-
log2(`[
|
|
29094
|
+
log2(`[Agent] ${state.lastAcpStartError}`);
|
|
28630
29095
|
}
|
|
28631
29096
|
if (!statOk) {
|
|
28632
29097
|
return null;
|
|
28633
29098
|
}
|
|
28634
|
-
const modeFlag = mode && ["ask", "plan"].includes(mode) ? ["--mode", mode] : [];
|
|
28635
|
-
const fullCmd = [...resolved.command, ...modeFlag];
|
|
28636
29099
|
const hooks = buildAcpSessionBridgeHooks({
|
|
28637
29100
|
routing,
|
|
28638
29101
|
getSendSessionUpdate: () => sendSessionUpdate,
|
|
@@ -28640,8 +29103,15 @@ async function ensureAcpClient(options) {
|
|
|
28640
29103
|
log: log2
|
|
28641
29104
|
});
|
|
28642
29105
|
state.acpStartPromise = resolved.createClient({
|
|
28643
|
-
command:
|
|
29106
|
+
command: resolved.command,
|
|
29107
|
+
sessionMode: mode,
|
|
28644
29108
|
cwd: targetCwd,
|
|
29109
|
+
onAgentSubprocessExit: () => {
|
|
29110
|
+
state.acpHandle = null;
|
|
29111
|
+
state.acpStartPromise = null;
|
|
29112
|
+
state.acpAgentKey = null;
|
|
29113
|
+
state.lastAcpStartError = "Agent subprocess exited";
|
|
29114
|
+
},
|
|
28645
29115
|
...hooks
|
|
28646
29116
|
}).then((h) => {
|
|
28647
29117
|
state.lastAcpStartError = null;
|
|
@@ -28651,7 +29121,7 @@ async function ensureAcpClient(options) {
|
|
|
28651
29121
|
return h;
|
|
28652
29122
|
}).catch((err) => {
|
|
28653
29123
|
state.lastAcpStartError = errorMessage(err);
|
|
28654
|
-
log2(`[
|
|
29124
|
+
log2(`[Agent] Failed to start: ${state.lastAcpStartError}`);
|
|
28655
29125
|
state.acpStartPromise = null;
|
|
28656
29126
|
state.acpAgentKey = null;
|
|
28657
29127
|
return null;
|
|
@@ -28679,6 +29149,14 @@ async function createAcpManager(options) {
|
|
|
28679
29149
|
backendFallbackAgentType = agentType;
|
|
28680
29150
|
}
|
|
28681
29151
|
}
|
|
29152
|
+
function logPromptReceivedFromBridge(opts) {
|
|
29153
|
+
const { agentType, mode } = opts;
|
|
29154
|
+
const preferredForPrompt = agentType ?? backendFallbackAgentType ?? null;
|
|
29155
|
+
const modeLabel = typeof mode === "string" && mode.trim() !== "" ? mode.trim() : "not set";
|
|
29156
|
+
log2(
|
|
29157
|
+
`[Agent] Prompt received (${getAgentTypeDisplayName(preferredForPrompt)}, mode: ${modeLabel})`
|
|
29158
|
+
);
|
|
29159
|
+
}
|
|
28682
29160
|
function handlePrompt(opts) {
|
|
28683
29161
|
const {
|
|
28684
29162
|
promptText,
|
|
@@ -28687,10 +29165,9 @@ async function createAcpManager(options) {
|
|
|
28687
29165
|
runId,
|
|
28688
29166
|
mode,
|
|
28689
29167
|
agentType,
|
|
28690
|
-
cwd
|
|
29168
|
+
cwd,
|
|
28691
29169
|
sendResult,
|
|
28692
|
-
sendSessionUpdate
|
|
28693
|
-
collectSessionDiffAfterTurn
|
|
29170
|
+
sendSessionUpdate
|
|
28694
29171
|
} = opts;
|
|
28695
29172
|
const preferredForPrompt = agentType ?? backendFallbackAgentType ?? null;
|
|
28696
29173
|
pendingCancelRunId = void 0;
|
|
@@ -28702,7 +29179,7 @@ async function createAcpManager(options) {
|
|
|
28702
29179
|
state,
|
|
28703
29180
|
preferredAgentType: preferredForPrompt,
|
|
28704
29181
|
mode,
|
|
28705
|
-
cwd
|
|
29182
|
+
cwd,
|
|
28706
29183
|
routing: promptRouting,
|
|
28707
29184
|
sendSessionUpdate,
|
|
28708
29185
|
sendRequest: sendSessionUpdate,
|
|
@@ -28743,9 +29220,9 @@ async function createAcpManager(options) {
|
|
|
28743
29220
|
promptId,
|
|
28744
29221
|
sessionId,
|
|
28745
29222
|
runId,
|
|
29223
|
+
agentCwd: cwd,
|
|
28746
29224
|
sendResult,
|
|
28747
29225
|
sendSessionUpdate,
|
|
28748
|
-
collectSessionDiffAfterTurn,
|
|
28749
29226
|
log: log2
|
|
28750
29227
|
});
|
|
28751
29228
|
}
|
|
@@ -28760,14 +29237,16 @@ async function createAcpManager(options) {
|
|
|
28760
29237
|
if (promptRouting.runId !== runId) return false;
|
|
28761
29238
|
const handle = state.acpHandle;
|
|
28762
29239
|
if (handle?.cancel) {
|
|
29240
|
+
log2("[Agent] Stop requested");
|
|
28763
29241
|
try {
|
|
28764
29242
|
await handle.cancel();
|
|
28765
29243
|
return true;
|
|
28766
29244
|
} catch (err) {
|
|
28767
|
-
log2(`[
|
|
29245
|
+
log2(`[Agent] Cancel failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
28768
29246
|
return false;
|
|
28769
29247
|
}
|
|
28770
29248
|
}
|
|
29249
|
+
log2("[Agent] Stop requested (agent still starting)");
|
|
28771
29250
|
pendingCancelRunId = runId;
|
|
28772
29251
|
return true;
|
|
28773
29252
|
}
|
|
@@ -28780,7 +29259,14 @@ async function createAcpManager(options) {
|
|
|
28780
29259
|
state.acpStartPromise = null;
|
|
28781
29260
|
state.acpAgentKey = null;
|
|
28782
29261
|
}
|
|
28783
|
-
return {
|
|
29262
|
+
return {
|
|
29263
|
+
setPreferredAgentType,
|
|
29264
|
+
logPromptReceivedFromBridge,
|
|
29265
|
+
handlePrompt,
|
|
29266
|
+
cancelRun,
|
|
29267
|
+
resolveRequest,
|
|
29268
|
+
disconnect
|
|
29269
|
+
};
|
|
28784
29270
|
}
|
|
28785
29271
|
|
|
28786
29272
|
// src/bridge/routing/handlers/auth-token.ts
|
|
@@ -28801,7 +29287,9 @@ var handleBridgeIdentified = (msg, deps) => {
|
|
|
28801
29287
|
try {
|
|
28802
29288
|
await deps.reportAutoDetectedAgents?.();
|
|
28803
29289
|
} catch (e) {
|
|
28804
|
-
deps.log(
|
|
29290
|
+
deps.log(
|
|
29291
|
+
`[Bridge service] Auto-detect agents failed: ${e instanceof Error ? e.message : String(e)}`
|
|
29292
|
+
);
|
|
28805
29293
|
}
|
|
28806
29294
|
})();
|
|
28807
29295
|
});
|
|
@@ -28809,7 +29297,9 @@ var handleBridgeIdentified = (msg, deps) => {
|
|
|
28809
29297
|
try {
|
|
28810
29298
|
deps.sendLocalSkillsReport?.();
|
|
28811
29299
|
} catch (e) {
|
|
28812
|
-
deps.log(
|
|
29300
|
+
deps.log(
|
|
29301
|
+
`[Bridge service] Local skills report failed: ${e instanceof Error ? e.message : String(e)}`
|
|
29302
|
+
);
|
|
28813
29303
|
}
|
|
28814
29304
|
});
|
|
28815
29305
|
};
|
|
@@ -28826,12 +29316,12 @@ var handleAgentConfigMessage = (msg, deps) => {
|
|
|
28826
29316
|
};
|
|
28827
29317
|
|
|
28828
29318
|
// src/acp/from-bridge/handle-bridge-prompt.ts
|
|
28829
|
-
import * as
|
|
29319
|
+
import * as path11 from "node:path";
|
|
28830
29320
|
import { execFile as execFile3 } from "node:child_process";
|
|
28831
29321
|
import { promisify as promisify3 } from "node:util";
|
|
28832
29322
|
|
|
28833
29323
|
// src/git/bridge-queue-key.ts
|
|
28834
|
-
import * as
|
|
29324
|
+
import * as path10 from "node:path";
|
|
28835
29325
|
import { createHash } from "node:crypto";
|
|
28836
29326
|
function normalizeCanonicalGitUrl(url2) {
|
|
28837
29327
|
let s = url2.trim();
|
|
@@ -28859,13 +29349,13 @@ function canonicalUrlToRepoIdSync(url2) {
|
|
|
28859
29349
|
return createHash("sha256").update(normalized).digest("hex").slice(0, 32);
|
|
28860
29350
|
}
|
|
28861
29351
|
function fallbackRepoIdFromPath(absPath) {
|
|
28862
|
-
return createHash("sha256").update(
|
|
29352
|
+
return createHash("sha256").update(path10.resolve(absPath)).digest("hex").slice(0, 32);
|
|
28863
29353
|
}
|
|
28864
29354
|
async function resolveBridgeQueueBindFields(options) {
|
|
28865
29355
|
const { effectiveCwd, worktreePaths, primaryRepoRoots, log: log2 } = options;
|
|
28866
|
-
const cwdAbs = worktreePaths.length > 0 ?
|
|
29356
|
+
const cwdAbs = worktreePaths.length > 0 ? path10.resolve(worktreePaths[0]) : path10.resolve(effectiveCwd);
|
|
28867
29357
|
if (!primaryRepoRoots.length) {
|
|
28868
|
-
log2("[Bridge service]
|
|
29358
|
+
log2("[Bridge service] Prompt queue bind skipped: no Git repository roots under the working directory.");
|
|
28869
29359
|
return null;
|
|
28870
29360
|
}
|
|
28871
29361
|
let primaryRoot = primaryRepoRoots[0];
|
|
@@ -28885,115 +29375,11 @@ async function resolveBridgeQueueBindFields(options) {
|
|
|
28885
29375
|
return { canonicalQueueKey, repoId, cwdAbs };
|
|
28886
29376
|
}
|
|
28887
29377
|
|
|
28888
|
-
// src/git/pre-turn-snapshot.ts
|
|
28889
|
-
import * as fs4 from "node:fs";
|
|
28890
|
-
import * as path9 from "node:path";
|
|
28891
|
-
import { execFile as execFile2 } from "node:child_process";
|
|
28892
|
-
import { promisify as promisify2 } from "node:util";
|
|
28893
|
-
var execFileAsync2 = promisify2(execFile2);
|
|
28894
|
-
function snapshotsDirForCwd(agentCwd) {
|
|
28895
|
-
return path9.join(agentCwd, ".buildautomaton", "snapshots");
|
|
28896
|
-
}
|
|
28897
|
-
async function gitStashCreate2(repoRoot, log2) {
|
|
28898
|
-
try {
|
|
28899
|
-
const { stdout } = await execFileAsync2("git", ["stash", "create"], {
|
|
28900
|
-
cwd: repoRoot,
|
|
28901
|
-
maxBuffer: 10 * 1024 * 1024
|
|
28902
|
-
});
|
|
28903
|
-
return stdout.trim();
|
|
28904
|
-
} catch (e) {
|
|
28905
|
-
log2(`[snapshot] git stash create failed in ${repoRoot}: ${e instanceof Error ? e.message : String(e)}`);
|
|
28906
|
-
return "";
|
|
28907
|
-
}
|
|
28908
|
-
}
|
|
28909
|
-
async function gitRun(repoRoot, args, log2, label) {
|
|
28910
|
-
try {
|
|
28911
|
-
await execFileAsync2("git", args, { cwd: repoRoot, maxBuffer: 10 * 1024 * 1024 });
|
|
28912
|
-
return { ok: true };
|
|
28913
|
-
} catch (e) {
|
|
28914
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
28915
|
-
log2(`[snapshot] git ${label} failed in ${repoRoot}: ${msg}`);
|
|
28916
|
-
return { ok: false, error: msg };
|
|
28917
|
-
}
|
|
28918
|
-
}
|
|
28919
|
-
async function resolveSnapshotRepoRoots(options) {
|
|
28920
|
-
const { worktreePaths, fallbackCwd, log: log2 } = options;
|
|
28921
|
-
if (worktreePaths?.length) {
|
|
28922
|
-
const uniq = [...new Set(worktreePaths.map((p) => path9.resolve(p)))];
|
|
28923
|
-
return uniq;
|
|
28924
|
-
}
|
|
28925
|
-
try {
|
|
28926
|
-
const repos = await discoverGitReposUnderRoot(fallbackCwd);
|
|
28927
|
-
return repos.map((r) => r.absolutePath);
|
|
28928
|
-
} catch (e) {
|
|
28929
|
-
log2(`[snapshot] discover repos failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
28930
|
-
return [];
|
|
28931
|
-
}
|
|
28932
|
-
}
|
|
28933
|
-
async function capturePreTurnSnapshot(options) {
|
|
28934
|
-
const { runId, repoRoots, agentCwd, log: log2 } = options;
|
|
28935
|
-
if (!runId || !repoRoots.length) {
|
|
28936
|
-
return { ok: false, error: "No git repos to snapshot" };
|
|
28937
|
-
}
|
|
28938
|
-
const repos = [];
|
|
28939
|
-
for (const root of repoRoots) {
|
|
28940
|
-
const stashSha = await gitStashCreate2(root, log2);
|
|
28941
|
-
repos.push({ path: root, stashSha });
|
|
28942
|
-
}
|
|
28943
|
-
const dir = snapshotsDirForCwd(agentCwd);
|
|
28944
|
-
try {
|
|
28945
|
-
fs4.mkdirSync(dir, { recursive: true });
|
|
28946
|
-
} catch (e) {
|
|
28947
|
-
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
28948
|
-
}
|
|
28949
|
-
const payload = {
|
|
28950
|
-
runId,
|
|
28951
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
28952
|
-
repos
|
|
28953
|
-
};
|
|
28954
|
-
const filePath = path9.join(dir, `${runId}.json`);
|
|
28955
|
-
try {
|
|
28956
|
-
fs4.writeFileSync(filePath, JSON.stringify(payload, null, 2), "utf8");
|
|
28957
|
-
} catch (e) {
|
|
28958
|
-
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
28959
|
-
}
|
|
28960
|
-
log2(`[snapshot] Saved pre-turn snapshot ${runId.slice(0, 8)}\u2026 (${repos.length} repo(s)) \u2192 ${filePath}`);
|
|
28961
|
-
return { ok: true, filePath, repos };
|
|
28962
|
-
}
|
|
28963
|
-
async function applyPreTurnSnapshot(filePath, log2) {
|
|
28964
|
-
let data;
|
|
28965
|
-
try {
|
|
28966
|
-
const raw = fs4.readFileSync(filePath, "utf8");
|
|
28967
|
-
data = JSON.parse(raw);
|
|
28968
|
-
} catch (e) {
|
|
28969
|
-
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
28970
|
-
}
|
|
28971
|
-
if (!Array.isArray(data.repos)) {
|
|
28972
|
-
return { ok: false, error: "Invalid snapshot file" };
|
|
28973
|
-
}
|
|
28974
|
-
for (const r of data.repos) {
|
|
28975
|
-
if (!r.path) continue;
|
|
28976
|
-
const reset = await gitRun(r.path, ["reset", "--hard", "HEAD"], log2, "reset --hard");
|
|
28977
|
-
if (!reset.ok) return reset;
|
|
28978
|
-
const clean = await gitRun(r.path, ["clean", "-fd"], log2, "clean -fd");
|
|
28979
|
-
if (!clean.ok) return clean;
|
|
28980
|
-
if (r.stashSha) {
|
|
28981
|
-
const ap = await gitRun(r.path, ["stash", "apply", r.stashSha], log2, "stash apply");
|
|
28982
|
-
if (!ap.ok) return ap;
|
|
28983
|
-
}
|
|
28984
|
-
}
|
|
28985
|
-
log2(`[snapshot] Restored pre-turn state for ${data.runId.slice(0, 8)}\u2026`);
|
|
28986
|
-
return { ok: true };
|
|
28987
|
-
}
|
|
28988
|
-
function snapshotFilePath(agentCwd, runId) {
|
|
28989
|
-
return path9.join(snapshotsDirForCwd(agentCwd), `${runId}.json`);
|
|
28990
|
-
}
|
|
28991
|
-
|
|
28992
29378
|
// src/acp/from-bridge/handle-bridge-prompt.ts
|
|
28993
29379
|
var execFileAsync3 = promisify3(execFile3);
|
|
28994
|
-
async function readGitBranch(
|
|
29380
|
+
async function readGitBranch(cwd) {
|
|
28995
29381
|
try {
|
|
28996
|
-
const { stdout } = await execFileAsync3("git", ["branch", "--show-current"], { cwd
|
|
29382
|
+
const { stdout } = await execFileAsync3("git", ["branch", "--show-current"], { cwd, maxBuffer: 64 * 1024 });
|
|
28997
29383
|
const b = stdout.trim();
|
|
28998
29384
|
return b || null;
|
|
28999
29385
|
} catch {
|
|
@@ -29006,7 +29392,7 @@ function handleBridgePrompt(msg, deps) {
|
|
|
29006
29392
|
const promptText = typeof rawPrompt === "string" ? rawPrompt : rawPrompt != null ? String(rawPrompt) : "";
|
|
29007
29393
|
if (!promptText.trim()) {
|
|
29008
29394
|
log2(
|
|
29009
|
-
`[Bridge service]
|
|
29395
|
+
`[Bridge service] Prompt ignored: empty or missing prompt text (session ${typeof msg.sessionId === "string" ? msg.sessionId.slice(0, 8) : "\u2014"}\u2026, run ${typeof msg.runId === "string" ? msg.runId.slice(0, 8) : "\u2014"}\u2026).`
|
|
29010
29396
|
);
|
|
29011
29397
|
return;
|
|
29012
29398
|
}
|
|
@@ -29015,8 +29401,8 @@ function handleBridgePrompt(msg, deps) {
|
|
|
29015
29401
|
const sessionWorktreesEnabled = msg.sessionWorktreesEnabled === true;
|
|
29016
29402
|
const agentType = typeof msg.agentType === "string" && msg.agentType.trim() ? msg.agentType.trim() : void 0;
|
|
29017
29403
|
const runId = typeof msg.runId === "string" ? msg.runId : void 0;
|
|
29018
|
-
const
|
|
29019
|
-
|
|
29404
|
+
const mode = typeof msg.mode === "string" && msg.mode.trim() ? msg.mode.trim() : void 0;
|
|
29405
|
+
acpManager.logPromptReceivedFromBridge({ agentType, mode });
|
|
29020
29406
|
const sendResult = (result) => {
|
|
29021
29407
|
const s = getWs();
|
|
29022
29408
|
if (s) sendWsMessage(s, result);
|
|
@@ -29024,7 +29410,7 @@ function handleBridgePrompt(msg, deps) {
|
|
|
29024
29410
|
const sendSessionUpdate = (payload) => {
|
|
29025
29411
|
const s = getWs();
|
|
29026
29412
|
if (!s) {
|
|
29027
|
-
log2("[Bridge service]
|
|
29413
|
+
log2("[Bridge service] Session update not sent: not connected to the bridge.");
|
|
29028
29414
|
return;
|
|
29029
29415
|
}
|
|
29030
29416
|
const p = payload;
|
|
@@ -29032,7 +29418,7 @@ function handleBridgePrompt(msg, deps) {
|
|
|
29032
29418
|
};
|
|
29033
29419
|
async function preambleAndPrompt(resolvedCwd) {
|
|
29034
29420
|
const s = getWs();
|
|
29035
|
-
const effectiveCwd =
|
|
29421
|
+
const effectiveCwd = path11.resolve(resolvedCwd ?? getBridgeWorkspaceDirectory());
|
|
29036
29422
|
const worktreePaths = sessionWorktreeManager.getWorktreePathsForSession(sessionId) ?? [];
|
|
29037
29423
|
const repoRoots = await resolveSnapshotRepoRoots({
|
|
29038
29424
|
worktreePaths,
|
|
@@ -29065,9 +29451,6 @@ function handleBridgePrompt(msg, deps) {
|
|
|
29065
29451
|
agentUsesWorktree: sessionWorktreeManager.usesWorktreeSession(sessionId)
|
|
29066
29452
|
});
|
|
29067
29453
|
}
|
|
29068
|
-
if (sessionGitQueueStart && sessionId && repoRoots.length > 0) {
|
|
29069
|
-
await captureSessionGitBoundary({ sessionId, repoRoots, log: log2 });
|
|
29070
|
-
}
|
|
29071
29454
|
if (s && sessionId && runId) {
|
|
29072
29455
|
const cap = repoRoots.length > 0 ? await capturePreTurnSnapshot({ runId, repoRoots, agentCwd: effectiveCwd, log: log2 }) : { ok: false, error: "No git repos" };
|
|
29073
29456
|
sendWsMessage(s, {
|
|
@@ -29082,16 +29465,15 @@ function handleBridgePrompt(msg, deps) {
|
|
|
29082
29465
|
promptId: msg.id,
|
|
29083
29466
|
sessionId,
|
|
29084
29467
|
runId,
|
|
29085
|
-
mode
|
|
29468
|
+
mode,
|
|
29086
29469
|
agentType,
|
|
29087
29470
|
cwd: effectiveCwd,
|
|
29088
29471
|
sendResult,
|
|
29089
|
-
sendSessionUpdate
|
|
29090
|
-
collectSessionDiffAfterTurn
|
|
29472
|
+
sendSessionUpdate
|
|
29091
29473
|
});
|
|
29092
29474
|
}
|
|
29093
|
-
void sessionWorktreeManager.resolveCwdForPrompt(sessionId, { isNewSession, sessionWorktreesEnabled }).then((
|
|
29094
|
-
log2(`[
|
|
29475
|
+
void sessionWorktreeManager.resolveCwdForPrompt(sessionId, { isNewSession, sessionWorktreesEnabled }).then((cwd) => preambleAndPrompt(cwd)).catch((err) => {
|
|
29476
|
+
log2(`[Agent] Worktree resolve failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
29095
29477
|
void preambleAndPrompt(void 0);
|
|
29096
29478
|
});
|
|
29097
29479
|
}
|
|
@@ -29108,7 +29490,7 @@ function handleBridgeCancelRun(msg, { log: log2, acpManager }) {
|
|
|
29108
29490
|
void acpManager.cancelRun(runId).then((sent) => {
|
|
29109
29491
|
if (!sent) {
|
|
29110
29492
|
log2(
|
|
29111
|
-
`[
|
|
29493
|
+
`[Agent] Cancel ignored for run ${runId.slice(0, 8)}\u2026 (no active run or cancel not available).`
|
|
29112
29494
|
);
|
|
29113
29495
|
}
|
|
29114
29496
|
});
|
|
@@ -29136,7 +29518,7 @@ function handleSkillCall(msg, socket, log2) {
|
|
|
29136
29518
|
sendWsMessage(socket, { type: "skill_result", id: msg.id, result });
|
|
29137
29519
|
}).catch((err) => {
|
|
29138
29520
|
sendWsMessage(socket, { type: "skill_result", id: msg.id, error: String(err) });
|
|
29139
|
-
log2(`[Bridge service]
|
|
29521
|
+
log2(`[Bridge service] Skill invocation failed (${msg.skillId}/${msg.operationId}): ${err}`);
|
|
29140
29522
|
});
|
|
29141
29523
|
}
|
|
29142
29524
|
|
|
@@ -29154,31 +29536,30 @@ var handleSkillCallMessage = (msg, { getWs, log: log2 }) => {
|
|
|
29154
29536
|
|
|
29155
29537
|
// src/files/list-dir.ts
|
|
29156
29538
|
import fs5 from "node:fs";
|
|
29157
|
-
import
|
|
29539
|
+
import path13 from "node:path";
|
|
29158
29540
|
|
|
29159
29541
|
// src/files/ensure-under-cwd.ts
|
|
29160
|
-
import
|
|
29161
|
-
function ensureUnderCwd(relativePath,
|
|
29162
|
-
const normalized =
|
|
29163
|
-
const resolved =
|
|
29164
|
-
if (!resolved.startsWith(
|
|
29542
|
+
import path12 from "node:path";
|
|
29543
|
+
function ensureUnderCwd(relativePath, cwd = getBridgeWorkspaceDirectory()) {
|
|
29544
|
+
const normalized = path12.normalize(relativePath).replace(/^(\.\/)+/, "");
|
|
29545
|
+
const resolved = path12.resolve(cwd, normalized);
|
|
29546
|
+
if (!resolved.startsWith(cwd + path12.sep) && resolved !== cwd) {
|
|
29165
29547
|
return null;
|
|
29166
29548
|
}
|
|
29167
29549
|
return resolved;
|
|
29168
29550
|
}
|
|
29169
29551
|
|
|
29170
29552
|
// src/files/list-dir.ts
|
|
29171
|
-
var cwd = process.cwd();
|
|
29172
29553
|
function listDir(relativePath) {
|
|
29173
|
-
const resolved = ensureUnderCwd(relativePath || ".",
|
|
29554
|
+
const resolved = ensureUnderCwd(relativePath || ".", getBridgeWorkspaceDirectory());
|
|
29174
29555
|
if (!resolved) {
|
|
29175
29556
|
return { error: "Path is outside working directory" };
|
|
29176
29557
|
}
|
|
29177
29558
|
try {
|
|
29178
29559
|
const names = fs5.readdirSync(resolved, { withFileTypes: true });
|
|
29179
29560
|
const entries = names.filter((d) => !d.name.startsWith(".")).map((d) => {
|
|
29180
|
-
const entryPath =
|
|
29181
|
-
const fullPath =
|
|
29561
|
+
const entryPath = path13.join(relativePath || ".", d.name).replace(/\\/g, "/");
|
|
29562
|
+
const fullPath = path13.join(resolved, d.name);
|
|
29182
29563
|
let isDir = d.isDirectory();
|
|
29183
29564
|
if (d.isSymbolicLink()) {
|
|
29184
29565
|
try {
|
|
@@ -29208,9 +29589,8 @@ function listDir(relativePath) {
|
|
|
29208
29589
|
// src/files/read-file.ts
|
|
29209
29590
|
import fs6 from "node:fs";
|
|
29210
29591
|
import { StringDecoder } from "node:string_decoder";
|
|
29211
|
-
var cwd2 = process.cwd();
|
|
29212
29592
|
function resolveFilePath(relativePath) {
|
|
29213
|
-
const resolved = ensureUnderCwd(relativePath,
|
|
29593
|
+
const resolved = ensureUnderCwd(relativePath, getBridgeWorkspaceDirectory());
|
|
29214
29594
|
if (!resolved) return { error: "Path is outside working directory" };
|
|
29215
29595
|
let real;
|
|
29216
29596
|
try {
|
|
@@ -29218,8 +29598,8 @@ function resolveFilePath(relativePath) {
|
|
|
29218
29598
|
} catch {
|
|
29219
29599
|
real = resolved;
|
|
29220
29600
|
}
|
|
29221
|
-
const
|
|
29222
|
-
if (!
|
|
29601
|
+
const stat2 = fs6.statSync(real);
|
|
29602
|
+
if (!stat2.isFile()) return { error: "Not a file" };
|
|
29223
29603
|
return real;
|
|
29224
29604
|
}
|
|
29225
29605
|
var LINE_CHUNK_SIZE = 64 * 1024;
|
|
@@ -29376,7 +29756,7 @@ function readFileRange(filePath, startLine, endLine, lineOffsetIn, lineChunkSize
|
|
|
29376
29756
|
fs6.closeSync(fd);
|
|
29377
29757
|
}
|
|
29378
29758
|
}
|
|
29379
|
-
function
|
|
29759
|
+
function readFile2(relativePath, startLine, endLine, lineOffset, lineChunkSize = LINE_CHUNK_SIZE) {
|
|
29380
29760
|
try {
|
|
29381
29761
|
const result = resolveFilePath(relativePath);
|
|
29382
29762
|
if (typeof result === "object") return result;
|
|
@@ -29384,10 +29764,10 @@ function readFile(relativePath, startLine, endLine, lineOffset, lineChunkSize =
|
|
|
29384
29764
|
if (hasRange) {
|
|
29385
29765
|
return readFileRange(result, startLine, endLine, lineOffset, lineChunkSize);
|
|
29386
29766
|
}
|
|
29387
|
-
const
|
|
29767
|
+
const stat2 = fs6.statSync(result);
|
|
29388
29768
|
const raw = fs6.readFileSync(result, "utf8");
|
|
29389
29769
|
const lines = raw.split(/\r?\n/);
|
|
29390
|
-
return { content: raw, totalLines: lines.length, size:
|
|
29770
|
+
return { content: raw, totalLines: lines.length, size: stat2.size };
|
|
29391
29771
|
} catch (err) {
|
|
29392
29772
|
return { error: err instanceof Error ? err.message : String(err) };
|
|
29393
29773
|
}
|
|
@@ -29395,15 +29775,15 @@ function readFile(relativePath, startLine, endLine, lineOffset, lineChunkSize =
|
|
|
29395
29775
|
|
|
29396
29776
|
// src/files/file-index.ts
|
|
29397
29777
|
import fs7 from "node:fs";
|
|
29398
|
-
import
|
|
29778
|
+
import path14 from "node:path";
|
|
29399
29779
|
import os2 from "node:os";
|
|
29400
29780
|
import crypto2 from "node:crypto";
|
|
29401
|
-
var INDEX_DIR =
|
|
29781
|
+
var INDEX_DIR = path14.join(os2.homedir(), ".buildautomaton");
|
|
29402
29782
|
var HASH_LEN = 16;
|
|
29403
29783
|
var INDEX_VERSION = 2;
|
|
29404
|
-
function getIndexPath(
|
|
29405
|
-
const hash = crypto2.createHash("sha256").update(
|
|
29406
|
-
return
|
|
29784
|
+
function getIndexPath(cwd) {
|
|
29785
|
+
const hash = crypto2.createHash("sha256").update(cwd).digest("hex").slice(0, HASH_LEN);
|
|
29786
|
+
return path14.join(INDEX_DIR, `.file-index-${hash}.json`);
|
|
29407
29787
|
}
|
|
29408
29788
|
function getTrigrams(s) {
|
|
29409
29789
|
const lower = s.toLowerCase();
|
|
@@ -29447,23 +29827,23 @@ function walkDir(dir, baseDir, out) {
|
|
|
29447
29827
|
}
|
|
29448
29828
|
for (const name of names) {
|
|
29449
29829
|
if (name.startsWith(".")) continue;
|
|
29450
|
-
const full =
|
|
29451
|
-
let
|
|
29830
|
+
const full = path14.join(dir, name);
|
|
29831
|
+
let stat2;
|
|
29452
29832
|
try {
|
|
29453
|
-
|
|
29833
|
+
stat2 = fs7.statSync(full);
|
|
29454
29834
|
} catch {
|
|
29455
29835
|
continue;
|
|
29456
29836
|
}
|
|
29457
|
-
const relative4 =
|
|
29458
|
-
if (
|
|
29837
|
+
const relative4 = path14.relative(baseDir, full).replace(/\\/g, "/");
|
|
29838
|
+
if (stat2.isDirectory()) {
|
|
29459
29839
|
walkDir(full, baseDir, out);
|
|
29460
|
-
} else if (
|
|
29840
|
+
} else if (stat2.isFile()) {
|
|
29461
29841
|
out.push(relative4);
|
|
29462
29842
|
}
|
|
29463
29843
|
}
|
|
29464
29844
|
}
|
|
29465
|
-
function buildFileIndex(
|
|
29466
|
-
const resolved =
|
|
29845
|
+
function buildFileIndex(cwd) {
|
|
29846
|
+
const resolved = path14.resolve(cwd);
|
|
29467
29847
|
const paths = [];
|
|
29468
29848
|
walkDir(resolved, resolved, paths);
|
|
29469
29849
|
paths.sort((a, b) => a.localeCompare(b, void 0, { sensitivity: "base" }));
|
|
@@ -29488,8 +29868,8 @@ function buildFileIndex(cwd3) {
|
|
|
29488
29868
|
}
|
|
29489
29869
|
return data;
|
|
29490
29870
|
}
|
|
29491
|
-
function loadFileIndex(
|
|
29492
|
-
const resolved =
|
|
29871
|
+
function loadFileIndex(cwd) {
|
|
29872
|
+
const resolved = path14.resolve(cwd);
|
|
29493
29873
|
const indexPath = getIndexPath(resolved);
|
|
29494
29874
|
try {
|
|
29495
29875
|
const raw = fs7.readFileSync(indexPath, "utf8");
|
|
@@ -29509,8 +29889,8 @@ function loadFileIndex(cwd3) {
|
|
|
29509
29889
|
return null;
|
|
29510
29890
|
}
|
|
29511
29891
|
}
|
|
29512
|
-
function ensureFileIndex(
|
|
29513
|
-
const resolved =
|
|
29892
|
+
function ensureFileIndex(cwd) {
|
|
29893
|
+
const resolved = path14.resolve(cwd);
|
|
29514
29894
|
const cached2 = loadFileIndex(resolved);
|
|
29515
29895
|
if (cached2 !== null) return { data: cached2, fromCache: true };
|
|
29516
29896
|
const data = buildFileIndex(resolved);
|
|
@@ -29548,8 +29928,8 @@ function searchFileIndex(index, query, limit = 100) {
|
|
|
29548
29928
|
var SEARCH_LIMIT = 100;
|
|
29549
29929
|
function handleFileBrowserSearch(msg, socket) {
|
|
29550
29930
|
const q = typeof msg.q === "string" ? msg.q : "";
|
|
29551
|
-
const
|
|
29552
|
-
const index = loadFileIndex(
|
|
29931
|
+
const cwd = getBridgeWorkspaceDirectory();
|
|
29932
|
+
const index = loadFileIndex(cwd);
|
|
29553
29933
|
if (index === null) {
|
|
29554
29934
|
sendWsMessage(socket, {
|
|
29555
29935
|
type: "file_browser_search_response",
|
|
@@ -29570,7 +29950,7 @@ function handleFileBrowserSearch(msg, socket) {
|
|
|
29570
29950
|
function triggerFileIndexBuild() {
|
|
29571
29951
|
setImmediate(() => {
|
|
29572
29952
|
try {
|
|
29573
|
-
ensureFileIndex(
|
|
29953
|
+
ensureFileIndex(getBridgeWorkspaceDirectory());
|
|
29574
29954
|
} catch (e) {
|
|
29575
29955
|
console.error("[file-index] Background build failed:", e);
|
|
29576
29956
|
}
|
|
@@ -29596,7 +29976,7 @@ function handleFileBrowserRequest(msg, socket) {
|
|
|
29596
29976
|
const endLine = typeof msg.endLine === "number" ? msg.endLine : void 0;
|
|
29597
29977
|
const lineOffset = typeof msg.lineOffset === "number" ? msg.lineOffset : void 0;
|
|
29598
29978
|
const lineChunkSize = typeof msg.lineChunkSize === "number" ? msg.lineChunkSize : void 0;
|
|
29599
|
-
const result =
|
|
29979
|
+
const result = readFile2(reqPath, startLine, endLine, lineOffset, lineChunkSize);
|
|
29600
29980
|
if ("error" in result) {
|
|
29601
29981
|
sendWsMessage(socket, { type: "file_browser_response", id: msg.id, error: result.error });
|
|
29602
29982
|
} else {
|
|
@@ -29632,13 +30012,13 @@ function handleFileBrowserSearchMessage(msg, { getWs }) {
|
|
|
29632
30012
|
|
|
29633
30013
|
// src/skills/discover-local-agent-skills.ts
|
|
29634
30014
|
import fs8 from "node:fs";
|
|
29635
|
-
import
|
|
30015
|
+
import path15 from "node:path";
|
|
29636
30016
|
var SKILL_DISCOVERY_ROOTS = [".agents/skills", ".claude/skills", ".cursor/skills", "skills"];
|
|
29637
|
-
function discoverLocalSkills(
|
|
30017
|
+
function discoverLocalSkills(cwd) {
|
|
29638
30018
|
const out = [];
|
|
29639
30019
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
29640
30020
|
for (const rel of SKILL_DISCOVERY_ROOTS) {
|
|
29641
|
-
const base =
|
|
30021
|
+
const base = path15.join(cwd, rel);
|
|
29642
30022
|
if (!fs8.existsSync(base) || !fs8.statSync(base).isDirectory()) continue;
|
|
29643
30023
|
let entries = [];
|
|
29644
30024
|
try {
|
|
@@ -29647,13 +30027,13 @@ function discoverLocalSkills(cwd3) {
|
|
|
29647
30027
|
continue;
|
|
29648
30028
|
}
|
|
29649
30029
|
for (const name of entries) {
|
|
29650
|
-
const dir =
|
|
30030
|
+
const dir = path15.join(base, name);
|
|
29651
30031
|
try {
|
|
29652
30032
|
if (!fs8.statSync(dir).isDirectory()) continue;
|
|
29653
30033
|
} catch {
|
|
29654
30034
|
continue;
|
|
29655
30035
|
}
|
|
29656
|
-
const skillMd =
|
|
30036
|
+
const skillMd = path15.join(dir, "SKILL.md");
|
|
29657
30037
|
if (!fs8.existsSync(skillMd)) continue;
|
|
29658
30038
|
const key = `${rel}/${name}`;
|
|
29659
30039
|
if (seenKeys.has(key)) continue;
|
|
@@ -29663,10 +30043,10 @@ function discoverLocalSkills(cwd3) {
|
|
|
29663
30043
|
}
|
|
29664
30044
|
return out;
|
|
29665
30045
|
}
|
|
29666
|
-
function discoverSkillLayoutRoots(
|
|
30046
|
+
function discoverSkillLayoutRoots(cwd) {
|
|
29667
30047
|
const roots = [];
|
|
29668
30048
|
for (const rel of SKILL_DISCOVERY_ROOTS) {
|
|
29669
|
-
const base =
|
|
30049
|
+
const base = path15.join(cwd, rel);
|
|
29670
30050
|
if (!fs8.existsSync(base) || !fs8.statSync(base).isDirectory()) continue;
|
|
29671
30051
|
let entries = [];
|
|
29672
30052
|
try {
|
|
@@ -29676,13 +30056,13 @@ function discoverSkillLayoutRoots(cwd3) {
|
|
|
29676
30056
|
}
|
|
29677
30057
|
const skills2 = [];
|
|
29678
30058
|
for (const name of entries) {
|
|
29679
|
-
const dir =
|
|
30059
|
+
const dir = path15.join(base, name);
|
|
29680
30060
|
try {
|
|
29681
30061
|
if (!fs8.statSync(dir).isDirectory()) continue;
|
|
29682
30062
|
} catch {
|
|
29683
30063
|
continue;
|
|
29684
30064
|
}
|
|
29685
|
-
if (!fs8.existsSync(
|
|
30065
|
+
if (!fs8.existsSync(path15.join(dir, "SKILL.md"))) continue;
|
|
29686
30066
|
const relPath = `${rel}/${name}`.replace(/\\/g, "/");
|
|
29687
30067
|
skills2.push({ name, relPath });
|
|
29688
30068
|
}
|
|
@@ -29697,14 +30077,14 @@ function discoverSkillLayoutRoots(cwd3) {
|
|
|
29697
30077
|
function handleSkillLayoutRequest(msg, deps) {
|
|
29698
30078
|
const socket = deps.getWs();
|
|
29699
30079
|
const id = typeof msg.id === "string" ? msg.id : "";
|
|
29700
|
-
const roots = discoverSkillLayoutRoots(
|
|
30080
|
+
const roots = discoverSkillLayoutRoots(getBridgeWorkspaceDirectory());
|
|
29701
30081
|
socket?.send(JSON.stringify({ type: "skill_layout_response", id, roots }));
|
|
29702
30082
|
}
|
|
29703
30083
|
|
|
29704
30084
|
// src/skills/install-remote-skills.ts
|
|
29705
30085
|
import fs9 from "node:fs";
|
|
29706
|
-
import
|
|
29707
|
-
function installRemoteSkills(
|
|
30086
|
+
import path16 from "node:path";
|
|
30087
|
+
function installRemoteSkills(cwd, targetDir, items) {
|
|
29708
30088
|
const installed = [];
|
|
29709
30089
|
if (!Array.isArray(items)) {
|
|
29710
30090
|
return { success: false, error: "Invalid items" };
|
|
@@ -29714,11 +30094,11 @@ function installRemoteSkills(cwd3, targetDir, items) {
|
|
|
29714
30094
|
if (typeof item.sourceId !== "string" || typeof item.skillName !== "string" || typeof item.versionHash !== "string" || !Array.isArray(item.files)) {
|
|
29715
30095
|
continue;
|
|
29716
30096
|
}
|
|
29717
|
-
const skillDir =
|
|
30097
|
+
const skillDir = path16.join(cwd, targetDir, item.skillName);
|
|
29718
30098
|
for (const f of item.files) {
|
|
29719
30099
|
if (typeof f.path !== "string" || !f.text && !f.base64) continue;
|
|
29720
|
-
const dest =
|
|
29721
|
-
fs9.mkdirSync(
|
|
30100
|
+
const dest = path16.join(skillDir, f.path);
|
|
30101
|
+
fs9.mkdirSync(path16.dirname(dest), { recursive: true });
|
|
29722
30102
|
if (f.text !== void 0) {
|
|
29723
30103
|
fs9.writeFileSync(dest, f.text, "utf8");
|
|
29724
30104
|
} else if (f.base64) {
|
|
@@ -29743,11 +30123,11 @@ var handleInstallSkillsMessage = (msg, deps) => {
|
|
|
29743
30123
|
const id = typeof msg.id === "string" ? msg.id : "";
|
|
29744
30124
|
const targetDir = typeof msg.targetDir === "string" && msg.targetDir.trim() ? msg.targetDir.trim() : ".agents/skills";
|
|
29745
30125
|
const rawItems = msg.items;
|
|
29746
|
-
const
|
|
29747
|
-
const result = installRemoteSkills(
|
|
30126
|
+
const cwd = getBridgeWorkspaceDirectory();
|
|
30127
|
+
const result = installRemoteSkills(cwd, targetDir, rawItems);
|
|
29748
30128
|
if (!result.success) {
|
|
29749
30129
|
const err = result.error ?? "Invalid items";
|
|
29750
|
-
deps.log(`[Bridge service]
|
|
30130
|
+
deps.log(`[Bridge service] Install skills failed: ${err}`);
|
|
29751
30131
|
socket?.send(JSON.stringify({ type: "install_skills_result", id, success: false, error: err }));
|
|
29752
30132
|
return;
|
|
29753
30133
|
}
|
|
@@ -29817,7 +30197,6 @@ var handleSessionDiscardedMessage = (msg, deps) => {
|
|
|
29817
30197
|
|
|
29818
30198
|
// src/bridge/routing/handlers/revert-turn-snapshot.ts
|
|
29819
30199
|
import * as fs10 from "node:fs";
|
|
29820
|
-
import * as path16 from "node:path";
|
|
29821
30200
|
var handleRevertTurnSnapshotMessage = (msg, deps) => {
|
|
29822
30201
|
const id = typeof msg.id === "string" ? msg.id : "";
|
|
29823
30202
|
const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
|
|
@@ -29827,7 +30206,7 @@ var handleRevertTurnSnapshotMessage = (msg, deps) => {
|
|
|
29827
30206
|
void (async () => {
|
|
29828
30207
|
const s = getWs();
|
|
29829
30208
|
if (!s) return;
|
|
29830
|
-
const agentBase = sessionWorktreeManager.getAgentCwdForSession(sessionId) ??
|
|
30209
|
+
const agentBase = sessionWorktreeManager.getAgentCwdForSession(sessionId) ?? getBridgeWorkspaceDirectory();
|
|
29831
30210
|
const file2 = snapshotFilePath(agentBase, turnId);
|
|
29832
30211
|
if (!fs10.existsSync(file2)) {
|
|
29833
30212
|
sendWsMessage(s, {
|
|
@@ -29930,25 +30309,12 @@ function dispatchBridgeMessage(msg, deps) {
|
|
|
29930
30309
|
}
|
|
29931
30310
|
|
|
29932
30311
|
// src/bridge/routing/handle-bridge-message.ts
|
|
29933
|
-
var DEFERRED_INBOUND_TYPES = /* @__PURE__ */ new Set([
|
|
29934
|
-
"server_control",
|
|
29935
|
-
"prompt",
|
|
29936
|
-
"install_skills",
|
|
29937
|
-
"refresh_local_skills",
|
|
29938
|
-
"dev_servers_config"
|
|
29939
|
-
]);
|
|
29940
30312
|
function handleBridgeMessage(data, deps) {
|
|
29941
30313
|
const msg = data;
|
|
29942
|
-
|
|
29943
|
-
|
|
29944
|
-
|
|
29945
|
-
|
|
29946
|
-
setImmediate(() => {
|
|
29947
|
-
dispatchBridgeMessage(msg, deps);
|
|
29948
|
-
});
|
|
29949
|
-
return;
|
|
29950
|
-
}
|
|
29951
|
-
dispatchBridgeMessage(msg, deps);
|
|
30314
|
+
if (!deps.getWs()) return;
|
|
30315
|
+
setImmediate(() => {
|
|
30316
|
+
dispatchBridgeMessage(msg, deps);
|
|
30317
|
+
});
|
|
29952
30318
|
}
|
|
29953
30319
|
|
|
29954
30320
|
// src/worktrees/session-worktree-manager.ts
|
|
@@ -30026,7 +30392,7 @@ async function prepareNewSessionWorktrees(options) {
|
|
|
30026
30392
|
const agentMirrorRoot = path18.join(rootAbs, cwdKey);
|
|
30027
30393
|
const repos = await discoverGitReposUnderRoot(launcherResolved);
|
|
30028
30394
|
if (repos.length === 0) {
|
|
30029
|
-
log2("[worktrees] No
|
|
30395
|
+
log2("[worktrees] No Git repositories under launcher working directory; skipping worktree creation.");
|
|
30030
30396
|
return null;
|
|
30031
30397
|
}
|
|
30032
30398
|
const branch = `session-${sessionId}`;
|
|
@@ -30040,11 +30406,11 @@ async function prepareNewSessionWorktrees(options) {
|
|
|
30040
30406
|
fs12.mkdirSync(path18.dirname(wtPath), { recursive: true });
|
|
30041
30407
|
try {
|
|
30042
30408
|
await gitWorktreeAddBranch(repo.absolutePath, wtPath, branch);
|
|
30043
|
-
log2(`[worktrees] Added worktree ${wtPath} (branch ${branch})
|
|
30409
|
+
log2(`[worktrees] Added worktree ${wtPath} (branch ${branch}).`);
|
|
30044
30410
|
worktreePaths.push(wtPath);
|
|
30045
30411
|
} catch (e) {
|
|
30046
30412
|
log2(
|
|
30047
|
-
`[worktrees]
|
|
30413
|
+
`[worktrees] Worktree add failed for ${repo.absolutePath}: ${e instanceof Error ? e.message : String(e)}`
|
|
30048
30414
|
);
|
|
30049
30415
|
}
|
|
30050
30416
|
}
|
|
@@ -30066,7 +30432,9 @@ async function renameSessionWorktreeBranches(paths, newBranch, log2) {
|
|
|
30066
30432
|
await gitRenameCurrentBranch(wt, safe);
|
|
30067
30433
|
log2(`[worktrees] Renamed branch in ${wt} \u2192 ${safe}`);
|
|
30068
30434
|
} catch (e) {
|
|
30069
|
-
log2(
|
|
30435
|
+
log2(
|
|
30436
|
+
`[worktrees] Branch rename failed in ${wt}: ${e instanceof Error ? e.message : String(e)}`
|
|
30437
|
+
);
|
|
30070
30438
|
}
|
|
30071
30439
|
}
|
|
30072
30440
|
}
|
|
@@ -30108,7 +30476,7 @@ async function removeSessionWorktrees(paths, log2) {
|
|
|
30108
30476
|
await gitWorktreeRemoveForce(wt);
|
|
30109
30477
|
log2(`[worktrees] Removed worktree ${wt}`);
|
|
30110
30478
|
} catch (e) {
|
|
30111
|
-
log2(`[worktrees]
|
|
30479
|
+
log2(`[worktrees] Remove failed for ${wt}: ${e instanceof Error ? e.message : String(e)}`);
|
|
30112
30480
|
try {
|
|
30113
30481
|
fs15.rmSync(wt, { recursive: true, force: true });
|
|
30114
30482
|
} catch {
|
|
@@ -30174,7 +30542,7 @@ var SessionWorktreeManager = class {
|
|
|
30174
30542
|
return this.bridgeWantsWorktrees;
|
|
30175
30543
|
}
|
|
30176
30544
|
/**
|
|
30177
|
-
* Returns cwd for the agent (mirror of launcher tree), or undefined to use
|
|
30545
|
+
* Returns cwd for the agent (mirror of launcher tree), or undefined to use the bridge workspace directory.
|
|
30178
30546
|
*/
|
|
30179
30547
|
async resolveCwdForPrompt(sessionId, opts) {
|
|
30180
30548
|
if (!sessionId || !this.effective() || !opts.sessionWorktreesEnabled) {
|
|
@@ -30187,7 +30555,7 @@ var SessionWorktreeManager = class {
|
|
|
30187
30555
|
}
|
|
30188
30556
|
const prep = await prepareNewSessionWorktrees({
|
|
30189
30557
|
rootAbs: this.rootAbs,
|
|
30190
|
-
launcherCwd:
|
|
30558
|
+
launcherCwd: getBridgeWorkspaceDirectory(),
|
|
30191
30559
|
sessionId,
|
|
30192
30560
|
layout: this.layout,
|
|
30193
30561
|
log: this.log
|
|
@@ -30227,7 +30595,7 @@ var SessionWorktreeManager = class {
|
|
|
30227
30595
|
}
|
|
30228
30596
|
async commitSession(params) {
|
|
30229
30597
|
const paths = this.sessionPaths.get(params.sessionId);
|
|
30230
|
-
const targets = paths?.length ? paths : [
|
|
30598
|
+
const targets = paths?.length ? paths : [getBridgeWorkspaceDirectory()];
|
|
30231
30599
|
return commitSessionWorktrees({
|
|
30232
30600
|
paths: targets,
|
|
30233
30601
|
branch: params.branch,
|
|
@@ -30280,7 +30648,7 @@ function shouldIgnoreRelative(rel) {
|
|
|
30280
30648
|
}
|
|
30281
30649
|
function attachWatchErrorLog(w) {
|
|
30282
30650
|
w.on("error", (err) => {
|
|
30283
|
-
console.error("[file-index]
|
|
30651
|
+
console.error("[file-index] File watcher error:", err);
|
|
30284
30652
|
});
|
|
30285
30653
|
}
|
|
30286
30654
|
function createFsWatcher(resolved, schedule) {
|
|
@@ -30299,7 +30667,7 @@ function createFsWatcher(resolved, schedule) {
|
|
|
30299
30667
|
const code = typeof e === "object" && e !== null && "code" in e ? e.code : void 0;
|
|
30300
30668
|
if (code === "ERR_FEATURE_UNAVAILABLE_ON_PLATFORM") {
|
|
30301
30669
|
console.warn(
|
|
30302
|
-
"[file-index]
|
|
30670
|
+
"[file-index] Recursive file watching is unavailable on this platform; using non-recursive watch (Node 20+ on Linux enables recursive). Nested file changes may be missed until you upgrade."
|
|
30303
30671
|
);
|
|
30304
30672
|
const w = watch(resolved, { recursive: false }, onEvent);
|
|
30305
30673
|
attachWatchErrorLog(w);
|
|
@@ -30308,8 +30676,8 @@ function createFsWatcher(resolved, schedule) {
|
|
|
30308
30676
|
throw e;
|
|
30309
30677
|
}
|
|
30310
30678
|
}
|
|
30311
|
-
function startFileIndexWatcher(
|
|
30312
|
-
const resolved = path21.resolve(
|
|
30679
|
+
function startFileIndexWatcher(cwd = getBridgeWorkspaceDirectory()) {
|
|
30680
|
+
const resolved = path21.resolve(cwd);
|
|
30313
30681
|
try {
|
|
30314
30682
|
buildFileIndex(resolved);
|
|
30315
30683
|
} catch (e) {
|
|
@@ -30363,7 +30731,7 @@ async function sigtermAndWaitForExit(proc, graceMs, log2, shortId) {
|
|
|
30363
30731
|
const exited = new Promise((resolve14) => {
|
|
30364
30732
|
proc.once("exit", () => resolve14());
|
|
30365
30733
|
});
|
|
30366
|
-
log2(`[dev-server] Sending SIGTERM to ${shortId} (pid=${proc.pid ?? "?"})
|
|
30734
|
+
log2(`[dev-server] Sending SIGTERM to ${shortId} (pid=${proc.pid ?? "?"}).`);
|
|
30367
30735
|
try {
|
|
30368
30736
|
proc.kill("SIGTERM");
|
|
30369
30737
|
} catch {
|
|
@@ -30371,7 +30739,9 @@ async function sigtermAndWaitForExit(proc, graceMs, log2, shortId) {
|
|
|
30371
30739
|
await Promise.race([exited, new Promise((resolve14) => setTimeout(resolve14, graceMs))]);
|
|
30372
30740
|
}
|
|
30373
30741
|
function forceKillChild(proc, log2, shortId, graceMs) {
|
|
30374
|
-
log2(
|
|
30742
|
+
log2(
|
|
30743
|
+
`[dev-server] ${shortId} did not exit within ${graceMs}ms; sending SIGKILL (pid=${proc.pid ?? "?"}).`
|
|
30744
|
+
);
|
|
30375
30745
|
proc.removeAllListeners();
|
|
30376
30746
|
try {
|
|
30377
30747
|
proc.kill("SIGKILL");
|
|
@@ -30441,7 +30811,7 @@ function wireDevServerChildProcess(d) {
|
|
|
30441
30811
|
d.rmMergedCleanupDir(cleanupDir);
|
|
30442
30812
|
}
|
|
30443
30813
|
if (signal) {
|
|
30444
|
-
d.log(`[dev-server] ${title} stopped (signal ${String(signal)})
|
|
30814
|
+
d.log(`[dev-server] ${title} stopped (signal: ${String(signal)}).`);
|
|
30445
30815
|
} else if (code !== null && code !== 0) {
|
|
30446
30816
|
const errTail = d.stderrTail.getTail().slice(-3).join("\n");
|
|
30447
30817
|
d.log(`[dev-server] ${title} exited with code ${code}${errTail ? `
|
|
@@ -30580,7 +30950,7 @@ function pipedStdoutStderrFor(attemptStdio) {
|
|
|
30580
30950
|
|
|
30581
30951
|
// src/dev-servers/manager/shell-spawn/try-spawn-piped-via-sh.ts
|
|
30582
30952
|
import { spawn as spawn5 } from "node:child_process";
|
|
30583
|
-
function trySpawnPipedViaSh(command, env,
|
|
30953
|
+
function trySpawnPipedViaSh(command, env, cwd, signal) {
|
|
30584
30954
|
const attempts = [
|
|
30585
30955
|
{ stdio: [devNullReadFd(), "pipe", "pipe"], endStdin: false },
|
|
30586
30956
|
{ stdio: ["ignore", "pipe", "pipe"], endStdin: true },
|
|
@@ -30591,7 +30961,7 @@ function trySpawnPipedViaSh(command, env, cwd3, signal) {
|
|
|
30591
30961
|
const attempt = attempts[i];
|
|
30592
30962
|
const opts = {
|
|
30593
30963
|
env,
|
|
30594
|
-
cwd
|
|
30964
|
+
cwd,
|
|
30595
30965
|
stdio: attempt.stdio,
|
|
30596
30966
|
...signal ? { signal } : {}
|
|
30597
30967
|
};
|
|
@@ -30623,11 +30993,11 @@ function trySpawnPipedViaSh(command, env, cwd3, signal) {
|
|
|
30623
30993
|
|
|
30624
30994
|
// src/dev-servers/manager/shell-spawn/try-spawn-shell-true-piped.ts
|
|
30625
30995
|
import { spawn as spawn6 } from "node:child_process";
|
|
30626
|
-
function trySpawnShellTruePiped(command, env,
|
|
30996
|
+
function trySpawnShellTruePiped(command, env, cwd, devNullFd, signal) {
|
|
30627
30997
|
try {
|
|
30628
30998
|
const opts = {
|
|
30629
30999
|
env,
|
|
30630
|
-
cwd
|
|
31000
|
+
cwd,
|
|
30631
31001
|
stdio: [devNullFd, "pipe", "pipe"],
|
|
30632
31002
|
shell: true,
|
|
30633
31003
|
...signal ? { signal } : {}
|
|
@@ -30647,7 +31017,7 @@ import { spawn as spawn7 } from "node:child_process";
|
|
|
30647
31017
|
import fs18 from "node:fs";
|
|
30648
31018
|
import { tmpdir } from "node:os";
|
|
30649
31019
|
import path22 from "node:path";
|
|
30650
|
-
function trySpawnMergedLogFile(command, env,
|
|
31020
|
+
function trySpawnMergedLogFile(command, env, cwd, signal) {
|
|
30651
31021
|
const tmpRoot = fs18.mkdtempSync(path22.join(tmpdir(), "ba-devsrv-log-"));
|
|
30652
31022
|
const logPath = path22.join(tmpRoot, "combined.log");
|
|
30653
31023
|
let logFd;
|
|
@@ -30663,13 +31033,13 @@ function trySpawnMergedLogFile(command, env, cwd3, signal) {
|
|
|
30663
31033
|
if (process.platform === "win32") {
|
|
30664
31034
|
proc = spawn7(process.env.ComSpec || "cmd.exe", ["/d", "/s", "/c", command], {
|
|
30665
31035
|
env,
|
|
30666
|
-
cwd
|
|
31036
|
+
cwd,
|
|
30667
31037
|
stdio,
|
|
30668
31038
|
windowsHide: true,
|
|
30669
31039
|
...signal ? { signal } : {}
|
|
30670
31040
|
});
|
|
30671
31041
|
} else {
|
|
30672
|
-
proc = spawn7("/bin/sh", ["-c", command], { env, cwd
|
|
31042
|
+
proc = spawn7("/bin/sh", ["-c", command], { env, cwd, stdio, ...signal ? { signal } : {} });
|
|
30673
31043
|
}
|
|
30674
31044
|
fs18.closeSync(logFd);
|
|
30675
31045
|
return {
|
|
@@ -30697,7 +31067,7 @@ import path23 from "node:path";
|
|
|
30697
31067
|
function shSingleQuote(s) {
|
|
30698
31068
|
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
30699
31069
|
}
|
|
30700
|
-
function trySpawnShellScriptLogRedirectUnix(command, env,
|
|
31070
|
+
function trySpawnShellScriptLogRedirectUnix(command, env, cwd, signal) {
|
|
30701
31071
|
const tmpRoot = fs19.mkdtempSync(path23.join(tmpdir2(), "ba-devsrv-sh-"));
|
|
30702
31072
|
const logPath = path23.join(tmpRoot, "combined.log");
|
|
30703
31073
|
const innerPath = path23.join(tmpRoot, "_cmd.sh");
|
|
@@ -30709,7 +31079,7 @@ ${command}
|
|
|
30709
31079
|
fs19.writeFileSync(
|
|
30710
31080
|
runnerPath,
|
|
30711
31081
|
`#!/bin/sh
|
|
30712
|
-
cd ${shSingleQuote(
|
|
31082
|
+
cd ${shSingleQuote(cwd)}
|
|
30713
31083
|
/bin/sh ${shSingleQuote(innerPath)} >>${shSingleQuote(logPath)} 2>&1
|
|
30714
31084
|
`
|
|
30715
31085
|
);
|
|
@@ -30731,7 +31101,7 @@ cd ${shSingleQuote(cwd3)}
|
|
|
30731
31101
|
throw e;
|
|
30732
31102
|
}
|
|
30733
31103
|
}
|
|
30734
|
-
function trySpawnShellScriptLogRedirectWin(command, env,
|
|
31104
|
+
function trySpawnShellScriptLogRedirectWin(command, env, cwd, signal) {
|
|
30735
31105
|
const tmpRoot = fs19.mkdtempSync(path23.join(tmpdir2(), "ba-devsrv-sh-"));
|
|
30736
31106
|
const logPath = path23.join(tmpRoot, "combined.log");
|
|
30737
31107
|
const runnerPath = path23.join(tmpRoot, "_run.bat");
|
|
@@ -30741,7 +31111,7 @@ function trySpawnShellScriptLogRedirectWin(command, env, cwd3, signal) {
|
|
|
30741
31111
|
fs19.writeFileSync(
|
|
30742
31112
|
runnerPath,
|
|
30743
31113
|
`@ECHO OFF\r
|
|
30744
|
-
CD /D ${q(
|
|
31114
|
+
CD /D ${q(cwd)}\r
|
|
30745
31115
|
${command} >> ${q(logPath)} 2>&1\r
|
|
30746
31116
|
`
|
|
30747
31117
|
);
|
|
@@ -30767,10 +31137,10 @@ ${command} >> ${q(logPath)} 2>&1\r
|
|
|
30767
31137
|
|
|
30768
31138
|
// src/dev-servers/manager/shell-spawn/try-spawn-inherit.ts
|
|
30769
31139
|
import { spawn as spawn9 } from "node:child_process";
|
|
30770
|
-
function trySpawnInheritStdio(command, env,
|
|
31140
|
+
function trySpawnInheritStdio(command, env, cwd, signal) {
|
|
30771
31141
|
const opts = {
|
|
30772
31142
|
env,
|
|
30773
|
-
cwd
|
|
31143
|
+
cwd,
|
|
30774
31144
|
stdio: "inherit",
|
|
30775
31145
|
...signal ? { signal } : {}
|
|
30776
31146
|
};
|
|
@@ -30786,27 +31156,27 @@ function trySpawnInheritStdio(command, env, cwd3, signal) {
|
|
|
30786
31156
|
}
|
|
30787
31157
|
|
|
30788
31158
|
// src/dev-servers/manager/shell-spawn/shell-spawn.ts
|
|
30789
|
-
function shellSpawn(command, env,
|
|
31159
|
+
function shellSpawn(command, env, cwd, options) {
|
|
30790
31160
|
const signal = options?.signal;
|
|
30791
|
-
const piped = trySpawnPipedViaSh(command, env,
|
|
31161
|
+
const piped = trySpawnPipedViaSh(command, env, cwd, signal);
|
|
30792
31162
|
if (piped.ok) {
|
|
30793
31163
|
return piped.result;
|
|
30794
31164
|
}
|
|
30795
31165
|
let lastErr = piped.lastErr;
|
|
30796
|
-
const shellTrueProc = trySpawnShellTruePiped(command, env,
|
|
31166
|
+
const shellTrueProc = trySpawnShellTruePiped(command, env, cwd, devNullReadFd(), signal);
|
|
30797
31167
|
if (shellTrueProc) {
|
|
30798
31168
|
return { proc: shellTrueProc, pipedStdoutStderr: true };
|
|
30799
31169
|
}
|
|
30800
|
-
const fileCapture = trySpawnMergedLogFile(command, env,
|
|
31170
|
+
const fileCapture = trySpawnMergedLogFile(command, env, cwd, signal);
|
|
30801
31171
|
if (fileCapture) {
|
|
30802
31172
|
return fileCapture;
|
|
30803
31173
|
}
|
|
30804
|
-
const scriptCapture = process.platform === "win32" ? trySpawnShellScriptLogRedirectWin(command, env,
|
|
31174
|
+
const scriptCapture = process.platform === "win32" ? trySpawnShellScriptLogRedirectWin(command, env, cwd, signal) : trySpawnShellScriptLogRedirectUnix(command, env, cwd, signal);
|
|
30805
31175
|
if (scriptCapture) {
|
|
30806
31176
|
return scriptCapture;
|
|
30807
31177
|
}
|
|
30808
31178
|
try {
|
|
30809
|
-
return trySpawnInheritStdio(command, env,
|
|
31179
|
+
return trySpawnInheritStdio(command, env, cwd, signal);
|
|
30810
31180
|
} catch (e) {
|
|
30811
31181
|
throw lastErr instanceof Error ? lastErr : e instanceof Error ? e : new Error(String(e));
|
|
30812
31182
|
}
|
|
@@ -30864,9 +31234,11 @@ var DevServerManager = class {
|
|
|
30864
31234
|
abortControllersByServerId = /* @__PURE__ */ new Map();
|
|
30865
31235
|
getWs;
|
|
30866
31236
|
log;
|
|
31237
|
+
getBridgeCwd;
|
|
30867
31238
|
constructor(options) {
|
|
30868
31239
|
this.getWs = options.getWs;
|
|
30869
31240
|
this.log = options.log;
|
|
31241
|
+
this.getBridgeCwd = options.getBridgeCwd ?? (() => process.cwd());
|
|
30870
31242
|
}
|
|
30871
31243
|
attachFirehose(send) {
|
|
30872
31244
|
this.firehoseSend = send;
|
|
@@ -30990,7 +31362,7 @@ var DevServerManager = class {
|
|
|
30990
31362
|
this.sendStatus(serverId, "starting", void 0, emptyTails());
|
|
30991
31363
|
const ac = new AbortController();
|
|
30992
31364
|
this.abortControllersByServerId.set(serverId, ac);
|
|
30993
|
-
const
|
|
31365
|
+
const cwd = this.getBridgeCwd();
|
|
30994
31366
|
const childEnv = envForSpawn(process.env, def.env, def.ports);
|
|
30995
31367
|
const cmd = substituteCommand(def.command.trim(), childEnv);
|
|
30996
31368
|
const title = def.name.trim() || serverId.slice(0, 8);
|
|
@@ -31005,7 +31377,7 @@ var DevServerManager = class {
|
|
|
31005
31377
|
let mergedLogPath;
|
|
31006
31378
|
let mergedCleanupDir;
|
|
31007
31379
|
try {
|
|
31008
|
-
const spawned = shellSpawn(cmd, childEnv,
|
|
31380
|
+
const spawned = shellSpawn(cmd, childEnv, cwd, {
|
|
31009
31381
|
signal: ac.signal
|
|
31010
31382
|
});
|
|
31011
31383
|
proc = spawned.proc;
|
|
@@ -31014,7 +31386,7 @@ var DevServerManager = class {
|
|
|
31014
31386
|
mergedCleanupDir = spawned.mergedLogCleanupDir;
|
|
31015
31387
|
} catch (e) {
|
|
31016
31388
|
const msg = e instanceof Error ? e.message : String(e);
|
|
31017
|
-
this.log(`[dev-server] ${title}
|
|
31389
|
+
this.log(`[dev-server] Failed to start ${title}: ${msg}`);
|
|
31018
31390
|
this.abortControllersByServerId.delete(serverId);
|
|
31019
31391
|
this.sendStatus(serverId, "error", msg, emptyTails());
|
|
31020
31392
|
return;
|
|
@@ -31090,7 +31462,9 @@ var DevServerManager = class {
|
|
|
31090
31462
|
async shutdownAllGraceful() {
|
|
31091
31463
|
const pairs = [...this.processes.entries()];
|
|
31092
31464
|
if (pairs.length === 0) return;
|
|
31093
|
-
this.log(
|
|
31465
|
+
this.log(
|
|
31466
|
+
`[dev-server] Stopping ${pairs.length} local dev server process${pairs.length === 1 ? "" : "es"}\u2026`
|
|
31467
|
+
);
|
|
31094
31468
|
await Promise.all(pairs.map(([serverId, proc]) => this.gracefulTerminateOrUnknown(serverId, proc)));
|
|
31095
31469
|
}
|
|
31096
31470
|
async gracefulTerminateOrUnknown(serverId, proc) {
|
|
@@ -31139,7 +31513,7 @@ function startStreamingProxy(ws, log2, pr) {
|
|
|
31139
31513
|
},
|
|
31140
31514
|
onEnd: () => sendWsMessage(ws, { type: "proxy_result_end", id: pr.id }),
|
|
31141
31515
|
onError: (error40) => {
|
|
31142
|
-
log2(`[Proxy and log service]
|
|
31516
|
+
log2(`[Proxy and log service] Streaming preview failed: ${error40}`);
|
|
31143
31517
|
sendWsMessage(ws, { type: "proxy_result_error", id: pr.id, error: error40 });
|
|
31144
31518
|
}
|
|
31145
31519
|
});
|
|
@@ -31212,11 +31586,11 @@ var handleProxyMessage = (msg, deps) => {
|
|
|
31212
31586
|
return;
|
|
31213
31587
|
}
|
|
31214
31588
|
void proxyToLocal(pr).then((res) => {
|
|
31215
|
-
if (res.error) deps.log(`[Proxy and log service]
|
|
31589
|
+
if (res.error) deps.log(`[Proxy and log service] Preview proxy failed: ${res.error}`);
|
|
31216
31590
|
sendWsMessage(deps.ws, { type: "proxy_result", ...res, id: pr.id });
|
|
31217
31591
|
}).catch((err) => {
|
|
31218
31592
|
deps.log(
|
|
31219
|
-
`[Proxy and log service]
|
|
31593
|
+
`[Proxy and log service] Preview proxy failed: ${err instanceof Error ? err.message : String(err)}`
|
|
31220
31594
|
);
|
|
31221
31595
|
sendWsMessage(deps.ws, { type: "proxy_result", id: pr.id, error: String(err) });
|
|
31222
31596
|
});
|
|
@@ -31253,14 +31627,23 @@ function tryConsumeBinaryProxyBody(raw, deps) {
|
|
|
31253
31627
|
}
|
|
31254
31628
|
|
|
31255
31629
|
// src/firehose/connect-firehose.ts
|
|
31630
|
+
var FIREHOSE_CLIENT_PING_MS = 25e3;
|
|
31256
31631
|
function connectFirehose(options) {
|
|
31257
31632
|
const { firehoseServerUrl, workspaceId, bridgeName, proxyPorts, log: log2, devServerManager, onOpen, onClose } = options;
|
|
31258
31633
|
const wsUrl = buildFirehoseCliWsUrl(firehoseServerUrl);
|
|
31259
|
-
|
|
31634
|
+
applyCliOutboundNetworkPreferences();
|
|
31635
|
+
const wsOptions = { perMessageDeflate: false, family: 4 };
|
|
31260
31636
|
if (wsUrl.startsWith("wss://")) {
|
|
31261
|
-
wsOptions.agent = new https2.Agent({ rejectUnauthorized: false });
|
|
31637
|
+
wsOptions.agent = new https2.Agent({ rejectUnauthorized: false, family: 4 });
|
|
31262
31638
|
}
|
|
31263
31639
|
const ws = new wrapper_default(wsUrl, wsOptions);
|
|
31640
|
+
let clientPingTimer = null;
|
|
31641
|
+
function clearClientPing() {
|
|
31642
|
+
if (clientPingTimer != null) {
|
|
31643
|
+
clearInterval(clientPingTimer);
|
|
31644
|
+
clientPingTimer = null;
|
|
31645
|
+
}
|
|
31646
|
+
}
|
|
31264
31647
|
const firehoseSend = (payload) => {
|
|
31265
31648
|
sendWsMessage(ws, payload);
|
|
31266
31649
|
};
|
|
@@ -31273,52 +31656,54 @@ function connectFirehose(options) {
|
|
|
31273
31656
|
startStreamingProxy: (pr) => startStreamingProxy(ws, log2, pr)
|
|
31274
31657
|
};
|
|
31275
31658
|
ws.on("open", () => {
|
|
31659
|
+
clearClientPing();
|
|
31660
|
+
clientPingTimer = setInterval(() => {
|
|
31661
|
+
if (ws.readyState === wrapper_default.OPEN) {
|
|
31662
|
+
try {
|
|
31663
|
+
ws.ping();
|
|
31664
|
+
} catch {
|
|
31665
|
+
}
|
|
31666
|
+
}
|
|
31667
|
+
}, FIREHOSE_CLIENT_PING_MS);
|
|
31276
31668
|
onOpen?.();
|
|
31277
31669
|
devServerManager.attachFirehose(firehoseSend);
|
|
31278
31670
|
sendWsMessage(ws, { type: "identify", workspaceId, bridgeName, proxyPorts });
|
|
31279
31671
|
});
|
|
31280
31672
|
ws.on("message", (raw) => {
|
|
31281
|
-
|
|
31282
|
-
|
|
31283
|
-
|
|
31284
|
-
|
|
31285
|
-
|
|
31286
|
-
|
|
31287
|
-
|
|
31288
|
-
|
|
31289
|
-
}
|
|
31290
|
-
});
|
|
31673
|
+
if (Buffer.isBuffer(raw) && tryConsumeBinaryProxyBody(raw, deps)) {
|
|
31674
|
+
return;
|
|
31675
|
+
}
|
|
31676
|
+
try {
|
|
31677
|
+
const text = Buffer.isBuffer(raw) ? raw.toString("utf8") : String(raw);
|
|
31678
|
+
dispatchFirehoseJsonMessage(JSON.parse(text), deps);
|
|
31679
|
+
} catch {
|
|
31680
|
+
}
|
|
31291
31681
|
});
|
|
31292
31682
|
ws.on("close", (code, reason) => {
|
|
31683
|
+
clearClientPing();
|
|
31293
31684
|
devServerManager.detachFirehose();
|
|
31294
|
-
|
|
31295
|
-
|
|
31296
|
-
"[Proxy and log service]",
|
|
31297
|
-
code,
|
|
31298
|
-
typeof reason === "string" ? reason : reason.toString(),
|
|
31299
|
-
"reconnects automatically if the bridge service stays connected"
|
|
31300
|
-
)
|
|
31301
|
-
);
|
|
31302
|
-
onClose?.();
|
|
31685
|
+
const reasonStr = typeof reason === "string" ? reason : reason.toString();
|
|
31686
|
+
onClose?.(code, reasonStr);
|
|
31303
31687
|
});
|
|
31304
31688
|
ws.on("error", (err) => {
|
|
31689
|
+
clearClientPing();
|
|
31305
31690
|
log2(`[Proxy and log service] WebSocket error: ${err.message}`);
|
|
31306
31691
|
});
|
|
31307
31692
|
return {
|
|
31308
31693
|
close() {
|
|
31694
|
+
clearClientPing();
|
|
31309
31695
|
devServerManager.detachFirehose();
|
|
31310
31696
|
try {
|
|
31311
31697
|
ws.removeAllListeners();
|
|
31312
31698
|
ws.close();
|
|
31313
31699
|
} catch {
|
|
31314
31700
|
}
|
|
31315
|
-
}
|
|
31701
|
+
},
|
|
31702
|
+
isConnected: () => ws.readyState === wrapper_default.OPEN
|
|
31316
31703
|
};
|
|
31317
31704
|
}
|
|
31318
31705
|
|
|
31319
31706
|
// src/bridge/connection/create-bridge-identified-handler.ts
|
|
31320
|
-
var FH_RECONNECT_BASE_MS = 2e3;
|
|
31321
|
-
var FH_RECONNECT_MAX_MS = 3e4;
|
|
31322
31707
|
function createOnBridgeIdentified(opts) {
|
|
31323
31708
|
const { sessionWorktreeManager, devServerManager, firehoseServerUrl, workspaceId, state, logFn } = opts;
|
|
31324
31709
|
function clearFirehoseReconnectTimer() {
|
|
@@ -31327,9 +31712,20 @@ function createOnBridgeIdentified(opts) {
|
|
|
31327
31712
|
state.firehoseReconnectTimeout = null;
|
|
31328
31713
|
}
|
|
31329
31714
|
}
|
|
31715
|
+
function firehoseCtx() {
|
|
31716
|
+
return {
|
|
31717
|
+
closedByUser: state.closedByUser,
|
|
31718
|
+
currentWs: state.currentWs,
|
|
31719
|
+
firehoseHandle: state.firehoseHandle,
|
|
31720
|
+
firehoseQuiet: state.firehoseQuiet
|
|
31721
|
+
};
|
|
31722
|
+
}
|
|
31330
31723
|
function attachFirehose(params) {
|
|
31331
31724
|
state.lastFirehoseParams = params;
|
|
31332
31725
|
clearFirehoseReconnectTimer();
|
|
31726
|
+
if (state.firehoseReconnectAttempt === 0) {
|
|
31727
|
+
logFn("Connecting to preview tunnel (local HTTP proxy and dev logs)\u2026");
|
|
31728
|
+
}
|
|
31333
31729
|
state.firehoseGeneration += 1;
|
|
31334
31730
|
const myGen = state.firehoseGeneration;
|
|
31335
31731
|
if (state.firehoseHandle) {
|
|
@@ -31345,37 +31741,52 @@ function createOnBridgeIdentified(opts) {
|
|
|
31345
31741
|
devServerManager,
|
|
31346
31742
|
onOpen: () => {
|
|
31347
31743
|
if (myGen !== state.firehoseGeneration) return;
|
|
31744
|
+
clearFirehoseReconnectQuietOnOpen({ firehoseQuiet: state.firehoseQuiet }, logFn);
|
|
31745
|
+
const logOpenAsFirehoseReconnect = state.firehoseReconnectAttempt > 0;
|
|
31348
31746
|
state.firehoseReconnectAttempt = 0;
|
|
31747
|
+
if (!logOpenAsFirehoseReconnect) {
|
|
31748
|
+
logFn("Connected to preview tunnel (local HTTP proxy and dev logs).");
|
|
31749
|
+
}
|
|
31349
31750
|
},
|
|
31350
|
-
onClose: () => {
|
|
31751
|
+
onClose: (code, reason) => {
|
|
31351
31752
|
if (myGen !== state.firehoseGeneration) return;
|
|
31352
31753
|
state.firehoseHandle = null;
|
|
31353
31754
|
if (state.closedByUser) return;
|
|
31354
31755
|
const main = state.currentWs;
|
|
31355
31756
|
if (!main || main.readyState !== wrapper_default.OPEN) {
|
|
31356
|
-
logFn(
|
|
31757
|
+
logFn(
|
|
31758
|
+
`${PROXY_AND_LOG_SERVICE_LABEL} Not reconnecting preview and log stream: main bridge connection is not open.`
|
|
31759
|
+
);
|
|
31357
31760
|
return;
|
|
31358
31761
|
}
|
|
31762
|
+
beginFirehoseDeferredDisconnect(firehoseCtx(), code, reason, logFn);
|
|
31359
31763
|
clearFirehoseReconnectTimer();
|
|
31360
|
-
const delay2 =
|
|
31361
|
-
FH_RECONNECT_BASE_MS * 2 ** state.firehoseReconnectAttempt,
|
|
31362
|
-
FH_RECONNECT_MAX_MS
|
|
31363
|
-
);
|
|
31764
|
+
const delay2 = reconnectDelayMs(state.firehoseReconnectAttempt);
|
|
31364
31765
|
state.firehoseReconnectAttempt += 1;
|
|
31365
|
-
|
|
31366
|
-
|
|
31766
|
+
logNextReconnectAttempt(
|
|
31767
|
+
logFn,
|
|
31768
|
+
PROXY_AND_LOG_SERVICE_LABEL,
|
|
31769
|
+
state.firehoseQuiet,
|
|
31770
|
+
delay2,
|
|
31771
|
+
state.firehoseReconnectAttempt
|
|
31367
31772
|
);
|
|
31368
31773
|
state.firehoseReconnectTimeout = setTimeout(() => {
|
|
31369
31774
|
state.firehoseReconnectTimeout = null;
|
|
31370
31775
|
if (state.closedByUser) return;
|
|
31371
31776
|
const w = state.currentWs;
|
|
31372
31777
|
if (!w || w.readyState !== wrapper_default.OPEN) {
|
|
31373
|
-
|
|
31778
|
+
if (state.firehoseQuiet.verboseLogs) {
|
|
31779
|
+
logFn(
|
|
31780
|
+
`${PROXY_AND_LOG_SERVICE_LABEL} Reconnect skipped: main bridge connection closed before preview stream could reconnect.`
|
|
31781
|
+
);
|
|
31782
|
+
}
|
|
31374
31783
|
return;
|
|
31375
31784
|
}
|
|
31376
31785
|
const p = state.lastFirehoseParams;
|
|
31377
31786
|
if (!p) {
|
|
31378
|
-
|
|
31787
|
+
if (state.firehoseQuiet.verboseLogs) {
|
|
31788
|
+
logFn(`${PROXY_AND_LOG_SERVICE_LABEL} Reconnect skipped: no stored connection parameters.`);
|
|
31789
|
+
}
|
|
31379
31790
|
return;
|
|
31380
31791
|
}
|
|
31381
31792
|
attachFirehose(p);
|
|
@@ -31423,6 +31834,7 @@ var CHECKS = {
|
|
|
31423
31834
|
return false;
|
|
31424
31835
|
}
|
|
31425
31836
|
},
|
|
31837
|
+
/** Bridge spawns `@agentclientprotocol/claude-agent-acp` via npx; detection is “Claude toolchain likely present”. */
|
|
31426
31838
|
"claude-code": async () => {
|
|
31427
31839
|
try {
|
|
31428
31840
|
await execFileAsync4("which", ["claude"], { timeout: 4e3 });
|
|
@@ -31458,10 +31870,12 @@ function createSendLocalSkillsReport(getWs, logFn) {
|
|
|
31458
31870
|
try {
|
|
31459
31871
|
const socket = getWs();
|
|
31460
31872
|
if (!socket || socket.readyState !== wrapper_default.OPEN) return;
|
|
31461
|
-
const skills2 = discoverLocalSkills(
|
|
31873
|
+
const skills2 = discoverLocalSkills(getBridgeWorkspaceDirectory());
|
|
31462
31874
|
socket.send(JSON.stringify({ type: "local_skills", skills: skills2 }));
|
|
31463
31875
|
} catch (e) {
|
|
31464
|
-
logFn(
|
|
31876
|
+
logFn(
|
|
31877
|
+
`[Bridge service] Local skills report failed: ${e instanceof Error ? e.message : String(e)}`
|
|
31878
|
+
);
|
|
31465
31879
|
}
|
|
31466
31880
|
};
|
|
31467
31881
|
}
|
|
@@ -31474,12 +31888,15 @@ function createReportAutoDetectedAgents(getWs, logFn) {
|
|
|
31474
31888
|
sendWsMessage(socket, { type: "auto_detected_agents_report", agentTypes: types });
|
|
31475
31889
|
}
|
|
31476
31890
|
} catch (e) {
|
|
31477
|
-
logFn(
|
|
31891
|
+
logFn(
|
|
31892
|
+
`[Bridge service] Auto-detected agents report failed: ${e instanceof Error ? e.message : String(e)}`
|
|
31893
|
+
);
|
|
31478
31894
|
}
|
|
31479
31895
|
};
|
|
31480
31896
|
}
|
|
31481
31897
|
|
|
31482
31898
|
// src/bridge/connection/create-bridge-connection.ts
|
|
31899
|
+
var BRIDGE_CLIENT_PING_MS = 25e3;
|
|
31483
31900
|
async function createBridgeConnection(options) {
|
|
31484
31901
|
const { apiUrl, workspaceId, justAuthenticated, onAuthInvalid, persistTokens } = options;
|
|
31485
31902
|
const firehoseServerUrl = options.firehoseServerUrl ?? options.proxyServerUrl;
|
|
@@ -31490,13 +31907,16 @@ async function createBridgeConnection(options) {
|
|
|
31490
31907
|
const state = {
|
|
31491
31908
|
closedByUser: false,
|
|
31492
31909
|
reconnectAttempt: 0,
|
|
31910
|
+
logBridgeOpenAsReconnect: false,
|
|
31493
31911
|
reconnectTimeout: null,
|
|
31494
31912
|
currentWs: null,
|
|
31913
|
+
mainQuiet: createEmptyReconnectQuietSlot(),
|
|
31495
31914
|
firehoseHandle: null,
|
|
31496
31915
|
lastFirehoseParams: null,
|
|
31497
31916
|
firehoseReconnectTimeout: null,
|
|
31498
31917
|
firehoseReconnectAttempt: 0,
|
|
31499
|
-
firehoseGeneration: 0
|
|
31918
|
+
firehoseGeneration: 0,
|
|
31919
|
+
firehoseQuiet: createEmptyReconnectQuietSlot()
|
|
31500
31920
|
};
|
|
31501
31921
|
const worktreesRootAbs = options.worktreesRootAbs ?? defaultWorktreesRootAbs();
|
|
31502
31922
|
const sessionWorktreeManager = new SessionWorktreeManager({
|
|
@@ -31508,7 +31928,7 @@ async function createBridgeConnection(options) {
|
|
|
31508
31928
|
function getWs() {
|
|
31509
31929
|
return state.currentWs;
|
|
31510
31930
|
}
|
|
31511
|
-
const devServerManager = new DevServerManager({ getWs, log: logFn });
|
|
31931
|
+
const devServerManager = new DevServerManager({ getWs, log: logFn, getBridgeCwd: getBridgeWorkspaceDirectory });
|
|
31512
31932
|
const onBridgeIdentified = createOnBridgeIdentified({
|
|
31513
31933
|
sessionWorktreeManager,
|
|
31514
31934
|
devServerManager,
|
|
@@ -31520,7 +31940,13 @@ async function createBridgeConnection(options) {
|
|
|
31520
31940
|
const sendLocalSkillsReport = createSendLocalSkillsReport(getWs, logFn);
|
|
31521
31941
|
const reportAutoDetectedAgents = createReportAutoDetectedAgents(getWs, logFn);
|
|
31522
31942
|
function handleOpen() {
|
|
31943
|
+
const logOpenAsPostRefreshReconnect = state.logBridgeOpenAsReconnect;
|
|
31944
|
+
clearMainBridgeReconnectQuietOnOpen(state, logFn);
|
|
31523
31945
|
state.reconnectAttempt = 0;
|
|
31946
|
+
state.logBridgeOpenAsReconnect = false;
|
|
31947
|
+
if (!logOpenAsPostRefreshReconnect) {
|
|
31948
|
+
logFn("Connected to bridge service.");
|
|
31949
|
+
}
|
|
31524
31950
|
const socket = getWs();
|
|
31525
31951
|
if (socket) {
|
|
31526
31952
|
sendWsMessage(socket, { type: "identify", role: "cli" });
|
|
@@ -31537,11 +31963,9 @@ async function createBridgeConnection(options) {
|
|
|
31537
31963
|
state.currentWs = null;
|
|
31538
31964
|
if (was) was.removeAllListeners();
|
|
31539
31965
|
const willReconnect = !state.closedByUser;
|
|
31540
|
-
logFn
|
|
31541
|
-
formatWebSocketClose("[Bridge service]", code, reason, willReconnect ? "will schedule reconnect" : "not reconnecting (CLI shutting down)")
|
|
31542
|
-
);
|
|
31966
|
+
beginMainBridgeDeferredDisconnect(state, code, reason, logFn, willReconnect);
|
|
31543
31967
|
if (willReconnect) {
|
|
31544
|
-
|
|
31968
|
+
scheduleMainBridgeReconnect(state, connect, logFn);
|
|
31545
31969
|
}
|
|
31546
31970
|
}
|
|
31547
31971
|
const messageDeps = {
|
|
@@ -31556,6 +31980,13 @@ async function createBridgeConnection(options) {
|
|
|
31556
31980
|
};
|
|
31557
31981
|
function connect() {
|
|
31558
31982
|
if (state.closedByUser) return;
|
|
31983
|
+
if (state.reconnectTimeout != null) {
|
|
31984
|
+
clearTimeout(state.reconnectTimeout);
|
|
31985
|
+
state.reconnectTimeout = null;
|
|
31986
|
+
}
|
|
31987
|
+
if (state.reconnectAttempt === 0) {
|
|
31988
|
+
logFn("Connecting to bridge service\u2026");
|
|
31989
|
+
}
|
|
31559
31990
|
const prev = state.currentWs;
|
|
31560
31991
|
if (prev) {
|
|
31561
31992
|
prev.removeAllListeners();
|
|
@@ -31568,6 +31999,7 @@ async function createBridgeConnection(options) {
|
|
|
31568
31999
|
const url2 = buildBridgeUrl(apiUrl, workspaceId, accessToken);
|
|
31569
32000
|
state.currentWs = createWsBridge({
|
|
31570
32001
|
url: url2,
|
|
32002
|
+
clientPingIntervalMs: BRIDGE_CLIENT_PING_MS,
|
|
31571
32003
|
onAuthInvalid: () => {
|
|
31572
32004
|
if (authRefreshInFlight) return;
|
|
31573
32005
|
void (async () => {
|
|
@@ -31579,8 +32011,9 @@ async function createBridgeConnection(options) {
|
|
|
31579
32011
|
accessToken = next.token;
|
|
31580
32012
|
refreshTok = next.refreshToken;
|
|
31581
32013
|
persistTokens?.({ token: accessToken, refreshToken: refreshTok });
|
|
31582
|
-
logFn("[Bridge service]
|
|
32014
|
+
logFn("[Bridge service] Access token refreshed; reconnecting\u2026");
|
|
31583
32015
|
state.reconnectAttempt = 0;
|
|
32016
|
+
state.logBridgeOpenAsReconnect = true;
|
|
31584
32017
|
authRefreshInFlight = false;
|
|
31585
32018
|
connect();
|
|
31586
32019
|
return;
|
|
@@ -31603,7 +32036,7 @@ async function createBridgeConnection(options) {
|
|
|
31603
32036
|
});
|
|
31604
32037
|
}
|
|
31605
32038
|
connect();
|
|
31606
|
-
const stopFileIndexWatcher = startFileIndexWatcher(
|
|
32039
|
+
const stopFileIndexWatcher = startFileIndexWatcher(getBridgeWorkspaceDirectory());
|
|
31607
32040
|
return {
|
|
31608
32041
|
close: async () => {
|
|
31609
32042
|
stopFileIndexWatcher();
|
|
@@ -31626,15 +32059,17 @@ async function runBridge(options) {
|
|
|
31626
32059
|
onAuth: (_auth) => {
|
|
31627
32060
|
}
|
|
31628
32061
|
});
|
|
31629
|
-
const onSignal2 = (
|
|
31630
|
-
logImmediate(
|
|
32062
|
+
const onSignal2 = (kind) => {
|
|
32063
|
+
logImmediate(
|
|
32064
|
+
kind === "interrupt" ? "Keyboard interrupt (Ctrl+C) \u2014 stopping\u2026" : "Stop requested \u2014 shutting down\u2026"
|
|
32065
|
+
);
|
|
31631
32066
|
setImmediate(() => {
|
|
31632
32067
|
handle2.close();
|
|
31633
32068
|
process.exit(0);
|
|
31634
32069
|
});
|
|
31635
32070
|
};
|
|
31636
|
-
const onSigInt2 = () => onSignal2("
|
|
31637
|
-
const onSigTerm2 = () => onSignal2("
|
|
32071
|
+
const onSigInt2 = () => onSignal2("interrupt");
|
|
32072
|
+
const onSigTerm2 = () => onSignal2("stop");
|
|
31638
32073
|
process.on("SIGINT", onSigInt2);
|
|
31639
32074
|
process.on("SIGTERM", onSigTerm2);
|
|
31640
32075
|
const auth = await handle2.authPromise;
|
|
@@ -31676,29 +32111,31 @@ async function runBridge(options) {
|
|
|
31676
32111
|
});
|
|
31677
32112
|
},
|
|
31678
32113
|
onAuthInvalid: () => {
|
|
31679
|
-
log("[Bridge service] token invalid or revoked; re-authenticating\u2026");
|
|
32114
|
+
log("[Bridge service] Access token invalid or revoked; re-authenticating\u2026");
|
|
31680
32115
|
clearConfigForApi(apiUrl);
|
|
31681
32116
|
void handle.close().then(() => {
|
|
31682
32117
|
void runBridge({ apiUrl, firehoseServerUrl, worktreesRootAbs });
|
|
31683
32118
|
});
|
|
31684
32119
|
}
|
|
31685
32120
|
});
|
|
31686
|
-
const onSignal = (
|
|
31687
|
-
logImmediate(
|
|
32121
|
+
const onSignal = (kind) => {
|
|
32122
|
+
logImmediate(
|
|
32123
|
+
kind === "interrupt" ? "Keyboard interrupt (Ctrl+C) \u2014 stopping\u2026" : "Stop requested \u2014 shutting down\u2026"
|
|
32124
|
+
);
|
|
31688
32125
|
setImmediate(() => {
|
|
31689
32126
|
void handle.close().then(() => {
|
|
31690
32127
|
process.exit(0);
|
|
31691
32128
|
});
|
|
31692
32129
|
});
|
|
31693
32130
|
};
|
|
31694
|
-
const onSigInt = () => onSignal("
|
|
31695
|
-
const onSigTerm = () => onSignal("
|
|
32131
|
+
const onSigInt = () => onSignal("interrupt");
|
|
32132
|
+
const onSigTerm = () => onSignal("stop");
|
|
31696
32133
|
process.on("SIGINT", onSigInt);
|
|
31697
32134
|
process.on("SIGTERM", onSigTerm);
|
|
31698
32135
|
}
|
|
31699
32136
|
export {
|
|
31700
32137
|
callSkill,
|
|
31701
|
-
createAcpClient,
|
|
32138
|
+
createSdkStdioAcpClient as createAcpClient,
|
|
31702
32139
|
createBridgeConnection,
|
|
31703
32140
|
createWsBridge,
|
|
31704
32141
|
getSkill,
|