@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/cli.js
CHANGED
|
@@ -7426,12 +7426,12 @@ var require_src2 = __commonJS({
|
|
|
7426
7426
|
function check2(path25, isFile, isDirectory) {
|
|
7427
7427
|
log2(`checking %s`, path25);
|
|
7428
7428
|
try {
|
|
7429
|
-
const
|
|
7430
|
-
if (
|
|
7429
|
+
const stat2 = fs_1.statSync(path25);
|
|
7430
|
+
if (stat2.isFile() && isFile) {
|
|
7431
7431
|
log2(`[OK] path represents a file`);
|
|
7432
7432
|
return true;
|
|
7433
7433
|
}
|
|
7434
|
-
if (
|
|
7434
|
+
if (stat2.isDirectory() && isDirectory) {
|
|
7435
7435
|
log2(`[OK] path represents a directory`);
|
|
7436
7436
|
return true;
|
|
7437
7437
|
}
|
|
@@ -25118,7 +25118,7 @@ function writeConfigForApi(apiUrl, auth) {
|
|
|
25118
25118
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
25119
25119
|
fs.writeFileSync(p, JSON.stringify(next, null, 2), "utf8");
|
|
25120
25120
|
} catch (e) {
|
|
25121
|
-
console.error("Could not save
|
|
25121
|
+
console.error("Could not save authentication config:", e);
|
|
25122
25122
|
}
|
|
25123
25123
|
}
|
|
25124
25124
|
function clearConfigForApi(apiUrl) {
|
|
@@ -25145,6 +25145,19 @@ function clearConfigForApi(apiUrl) {
|
|
|
25145
25145
|
}
|
|
25146
25146
|
}
|
|
25147
25147
|
|
|
25148
|
+
// src/files/cwd/bridge-workspace-directory.ts
|
|
25149
|
+
import * as path2 from "node:path";
|
|
25150
|
+
var bridgeWorkspaceDirectory = null;
|
|
25151
|
+
function initBridgeWorkspaceDirectory() {
|
|
25152
|
+
bridgeWorkspaceDirectory = path2.resolve(process.cwd());
|
|
25153
|
+
}
|
|
25154
|
+
function getBridgeWorkspaceDirectory() {
|
|
25155
|
+
if (bridgeWorkspaceDirectory == null) {
|
|
25156
|
+
bridgeWorkspaceDirectory = path2.resolve(process.cwd());
|
|
25157
|
+
}
|
|
25158
|
+
return bridgeWorkspaceDirectory;
|
|
25159
|
+
}
|
|
25160
|
+
|
|
25148
25161
|
// src/log.ts
|
|
25149
25162
|
function log(line) {
|
|
25150
25163
|
const time3 = (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
|
|
@@ -25156,9 +25169,6 @@ function logImmediate(line) {
|
|
|
25156
25169
|
`);
|
|
25157
25170
|
}
|
|
25158
25171
|
|
|
25159
|
-
// src/bridge/connection/create-ws-bridge.ts
|
|
25160
|
-
import https from "node:https";
|
|
25161
|
-
|
|
25162
25172
|
// ../../node_modules/.pnpm/ws@8.19.0/node_modules/ws/wrapper.mjs
|
|
25163
25173
|
var import_stream = __toESM(require_stream(), 1);
|
|
25164
25174
|
var import_receiver = __toESM(require_receiver(), 1);
|
|
@@ -25167,16 +25177,42 @@ var import_websocket = __toESM(require_websocket(), 1);
|
|
|
25167
25177
|
var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
25168
25178
|
var wrapper_default = import_websocket.default;
|
|
25169
25179
|
|
|
25180
|
+
// src/bridge/connection/create-ws-bridge.ts
|
|
25181
|
+
import https from "node:https";
|
|
25182
|
+
|
|
25183
|
+
// src/net/apply-cli-outbound-network-prefs.ts
|
|
25184
|
+
import dns from "node:dns";
|
|
25185
|
+
var applied = false;
|
|
25186
|
+
function applyCliOutboundNetworkPreferences() {
|
|
25187
|
+
if (applied) return;
|
|
25188
|
+
applied = true;
|
|
25189
|
+
try {
|
|
25190
|
+
dns.setDefaultResultOrder("ipv4first");
|
|
25191
|
+
} catch {
|
|
25192
|
+
}
|
|
25193
|
+
}
|
|
25194
|
+
|
|
25170
25195
|
// src/bridge/connection/create-ws-bridge.ts
|
|
25171
25196
|
var BRIDGE_AUTH_ERROR_HEADER = "x-bridge-auth-error";
|
|
25172
25197
|
var BRIDGE_AUTH_ERROR_TOKEN_INVALID = "token_invalid";
|
|
25173
25198
|
function createWsBridge(options) {
|
|
25174
|
-
const { url: url2, onMessage, onOpen, onClose, onError: onError2, onAuthInvalid } = options;
|
|
25175
|
-
|
|
25199
|
+
const { url: url2, onMessage, onOpen, onClose, onError: onError2, onAuthInvalid, clientPingIntervalMs } = options;
|
|
25200
|
+
applyCliOutboundNetworkPreferences();
|
|
25201
|
+
const wsOptions = {
|
|
25202
|
+
perMessageDeflate: false,
|
|
25203
|
+
family: 4
|
|
25204
|
+
};
|
|
25176
25205
|
if (url2.startsWith("wss://")) {
|
|
25177
|
-
wsOptions.agent = new https.Agent({ rejectUnauthorized: false });
|
|
25206
|
+
wsOptions.agent = new https.Agent({ rejectUnauthorized: false, family: 4 });
|
|
25178
25207
|
}
|
|
25179
25208
|
const ws = new wrapper_default(url2, wsOptions);
|
|
25209
|
+
let clientPingTimer = null;
|
|
25210
|
+
function clearClientPing() {
|
|
25211
|
+
if (clientPingTimer != null) {
|
|
25212
|
+
clearInterval(clientPingTimer);
|
|
25213
|
+
clientPingTimer = null;
|
|
25214
|
+
}
|
|
25215
|
+
}
|
|
25180
25216
|
ws.on("unexpected-response", (request, response) => {
|
|
25181
25217
|
const status = response?.statusCode ?? 0;
|
|
25182
25218
|
const headers = response?.headers ?? {};
|
|
@@ -25186,6 +25222,17 @@ function createWsBridge(options) {
|
|
|
25186
25222
|
}
|
|
25187
25223
|
});
|
|
25188
25224
|
ws.on("open", () => {
|
|
25225
|
+
clearClientPing();
|
|
25226
|
+
if (clientPingIntervalMs != null && clientPingIntervalMs > 0) {
|
|
25227
|
+
clientPingTimer = setInterval(() => {
|
|
25228
|
+
if (ws.readyState === wrapper_default.OPEN) {
|
|
25229
|
+
try {
|
|
25230
|
+
ws.ping();
|
|
25231
|
+
} catch {
|
|
25232
|
+
}
|
|
25233
|
+
}
|
|
25234
|
+
}, clientPingIntervalMs);
|
|
25235
|
+
}
|
|
25189
25236
|
onOpen?.();
|
|
25190
25237
|
});
|
|
25191
25238
|
ws.on("message", (raw) => {
|
|
@@ -25205,9 +25252,11 @@ function createWsBridge(options) {
|
|
|
25205
25252
|
}
|
|
25206
25253
|
});
|
|
25207
25254
|
ws.on("close", (code, reason) => {
|
|
25255
|
+
clearClientPing();
|
|
25208
25256
|
onClose?.(code, reason.toString());
|
|
25209
25257
|
});
|
|
25210
25258
|
ws.on("error", (err) => {
|
|
25259
|
+
clearClientPing();
|
|
25211
25260
|
onError2?.(err);
|
|
25212
25261
|
});
|
|
25213
25262
|
return ws;
|
|
@@ -25253,6 +25302,28 @@ function openBrowser(connectionId, initialWorkspaceId, preferredBridgeName, apiU
|
|
|
25253
25302
|
}
|
|
25254
25303
|
}
|
|
25255
25304
|
|
|
25305
|
+
// src/bridge/connection/reconnect/constants.ts
|
|
25306
|
+
var RECONNECT_FIRST_MS = 100;
|
|
25307
|
+
var RECONNECT_MAX_MS = 3e4;
|
|
25308
|
+
var RECONNECT_QUIET_MS = 2e3;
|
|
25309
|
+
function reconnectDelayMs(attemptBeforeIncrement) {
|
|
25310
|
+
return Math.min(RECONNECT_FIRST_MS * 2 ** attemptBeforeIncrement, RECONNECT_MAX_MS);
|
|
25311
|
+
}
|
|
25312
|
+
|
|
25313
|
+
// src/bridge/connection/reconnect/format-reconnect-delay-for-log.ts
|
|
25314
|
+
function formatReconnectDelayForLog(delayMs) {
|
|
25315
|
+
if (delayMs < 1e3) return `${delayMs}ms`;
|
|
25316
|
+
const s = delayMs / 1e3;
|
|
25317
|
+
return Number.isInteger(s) ? `${s}s` : `${s.toFixed(1)}s`;
|
|
25318
|
+
}
|
|
25319
|
+
|
|
25320
|
+
// src/bridge/connection/reconnect/log-next-reconnect-attempt.ts
|
|
25321
|
+
function logNextReconnectAttempt(log2, serviceLabel, quiet, delayMs, attempt) {
|
|
25322
|
+
if (!quiet.verboseLogs) return;
|
|
25323
|
+
const delayLabel = formatReconnectDelayForLog(delayMs);
|
|
25324
|
+
log2(`${serviceLabel} Next connection attempt in ${delayLabel} (attempt ${attempt}).`);
|
|
25325
|
+
}
|
|
25326
|
+
|
|
25256
25327
|
// src/bridge/connection/ws-close-diagnostics.ts
|
|
25257
25328
|
function describeWebSocketCloseCode(code) {
|
|
25258
25329
|
const known = {
|
|
@@ -25281,11 +25352,122 @@ function formatWebSocketClose(label, code, reason, extra) {
|
|
|
25281
25352
|
const r = reason.trim();
|
|
25282
25353
|
const reasonPart = r ? ` reason="${r}"` : "";
|
|
25283
25354
|
const extraPart = extra ? ` ${extra}` : "";
|
|
25284
|
-
return `${label}
|
|
25355
|
+
return `${label} Disconnected: code=${code} (${describeWebSocketCloseCode(code)})${reasonPart}${extraPart}`;
|
|
25356
|
+
}
|
|
25357
|
+
|
|
25358
|
+
// src/bridge/connection/reconnect/reconnect-quiet-slot.ts
|
|
25359
|
+
function createEmptyReconnectQuietSlot() {
|
|
25360
|
+
return { timer: null, verboseLogs: false, pendingCloseLog: null };
|
|
25361
|
+
}
|
|
25362
|
+
function clearReconnectQuietTimer(quiet) {
|
|
25363
|
+
if (quiet.timer != null) {
|
|
25364
|
+
clearTimeout(quiet.timer);
|
|
25365
|
+
quiet.timer = null;
|
|
25366
|
+
}
|
|
25367
|
+
}
|
|
25368
|
+
function clearReconnectQuietOnSuccessfulConnection(quiet, log2, reconnectedMessage) {
|
|
25369
|
+
clearReconnectQuietTimer(quiet);
|
|
25370
|
+
quiet.pendingCloseLog = null;
|
|
25371
|
+
if (quiet.verboseLogs) {
|
|
25372
|
+
log2(reconnectedMessage);
|
|
25373
|
+
quiet.verboseLogs = false;
|
|
25374
|
+
}
|
|
25375
|
+
}
|
|
25376
|
+
function beginDeferredDisconnectForReconnect(options) {
|
|
25377
|
+
const {
|
|
25378
|
+
isClosedByUser,
|
|
25379
|
+
quiet,
|
|
25380
|
+
code,
|
|
25381
|
+
reason,
|
|
25382
|
+
willReconnect,
|
|
25383
|
+
log: log2,
|
|
25384
|
+
serviceLabel,
|
|
25385
|
+
shutdownDetail,
|
|
25386
|
+
reconnectingDetail,
|
|
25387
|
+
quietMs = RECONNECT_QUIET_MS,
|
|
25388
|
+
shouldAbortQuietWindow
|
|
25389
|
+
} = options;
|
|
25390
|
+
if (!willReconnect) {
|
|
25391
|
+
log2(formatWebSocketClose(serviceLabel, code, reason, shutdownDetail));
|
|
25392
|
+
return;
|
|
25393
|
+
}
|
|
25394
|
+
quiet.pendingCloseLog = { code, reason };
|
|
25395
|
+
if (quiet.timer == null) {
|
|
25396
|
+
quiet.timer = setTimeout(() => {
|
|
25397
|
+
quiet.timer = null;
|
|
25398
|
+
if (isClosedByUser()) return;
|
|
25399
|
+
if (shouldAbortQuietWindow()) return;
|
|
25400
|
+
if (quiet.pendingCloseLog) {
|
|
25401
|
+
const { code: c, reason: r } = quiet.pendingCloseLog;
|
|
25402
|
+
quiet.pendingCloseLog = null;
|
|
25403
|
+
log2(formatWebSocketClose(serviceLabel, c, r, reconnectingDetail));
|
|
25404
|
+
}
|
|
25405
|
+
quiet.verboseLogs = true;
|
|
25406
|
+
}, quietMs);
|
|
25407
|
+
}
|
|
25408
|
+
}
|
|
25409
|
+
|
|
25410
|
+
// src/bridge/connection/reconnect/bridge-main-reconnect.ts
|
|
25411
|
+
function beginMainBridgeDeferredDisconnect(state, code, reason, log2, willReconnect) {
|
|
25412
|
+
beginDeferredDisconnectForReconnect({
|
|
25413
|
+
isClosedByUser: () => state.closedByUser,
|
|
25414
|
+
quiet: state.mainQuiet,
|
|
25415
|
+
code,
|
|
25416
|
+
reason,
|
|
25417
|
+
willReconnect,
|
|
25418
|
+
log: log2,
|
|
25419
|
+
serviceLabel: "[Bridge service]",
|
|
25420
|
+
shutdownDetail: "Not reconnecting (shutting down).",
|
|
25421
|
+
reconnectingDetail: "Reconnecting\u2026",
|
|
25422
|
+
shouldAbortQuietWindow: () => {
|
|
25423
|
+
const w = state.currentWs;
|
|
25424
|
+
return w != null && w.readyState === wrapper_default.OPEN;
|
|
25425
|
+
}
|
|
25426
|
+
});
|
|
25427
|
+
}
|
|
25428
|
+
function clearMainBridgeReconnectQuietOnOpen(state, log2) {
|
|
25429
|
+
clearReconnectQuietOnSuccessfulConnection(state.mainQuiet, log2, "Bridge connection restored.");
|
|
25430
|
+
}
|
|
25431
|
+
function scheduleMainBridgeReconnect(state, connect, log2) {
|
|
25432
|
+
if (state.closedByUser || state.currentWs != null) return;
|
|
25433
|
+
const delay2 = reconnectDelayMs(state.reconnectAttempt);
|
|
25434
|
+
state.reconnectAttempt += 1;
|
|
25435
|
+
logNextReconnectAttempt(log2, "[Bridge service]", state.mainQuiet, delay2, state.reconnectAttempt);
|
|
25436
|
+
state.reconnectTimeout = setTimeout(() => {
|
|
25437
|
+
state.reconnectTimeout = null;
|
|
25438
|
+
connect();
|
|
25439
|
+
}, delay2);
|
|
25440
|
+
}
|
|
25441
|
+
|
|
25442
|
+
// src/bridge/connection/reconnect/firehose-reconnect.ts
|
|
25443
|
+
var PROXY_AND_LOG_SERVICE_LABEL = "[Proxy and log service]";
|
|
25444
|
+
function beginFirehoseDeferredDisconnect(ctx, code, reason, log2) {
|
|
25445
|
+
beginDeferredDisconnectForReconnect({
|
|
25446
|
+
isClosedByUser: () => ctx.closedByUser,
|
|
25447
|
+
quiet: ctx.firehoseQuiet,
|
|
25448
|
+
code,
|
|
25449
|
+
reason,
|
|
25450
|
+
willReconnect: true,
|
|
25451
|
+
log: log2,
|
|
25452
|
+
serviceLabel: PROXY_AND_LOG_SERVICE_LABEL,
|
|
25453
|
+
shutdownDetail: "Not reconnecting (shutting down).",
|
|
25454
|
+
reconnectingDetail: "Reconnecting\u2026",
|
|
25455
|
+
shouldAbortQuietWindow: () => {
|
|
25456
|
+
const w = ctx.currentWs;
|
|
25457
|
+
if (!w || w.readyState !== wrapper_default.OPEN) return true;
|
|
25458
|
+
return ctx.firehoseHandle?.isConnected() ?? false;
|
|
25459
|
+
}
|
|
25460
|
+
});
|
|
25461
|
+
}
|
|
25462
|
+
function clearFirehoseReconnectQuietOnOpen(ctx, log2) {
|
|
25463
|
+
clearReconnectQuietOnSuccessfulConnection(
|
|
25464
|
+
ctx.firehoseQuiet,
|
|
25465
|
+
log2,
|
|
25466
|
+
"Preview tunnel restored (local HTTP proxy and dev logs)."
|
|
25467
|
+
);
|
|
25285
25468
|
}
|
|
25286
25469
|
|
|
25287
25470
|
// src/auth/run-pending-auth.ts
|
|
25288
|
-
var PENDING_RECONNECT_MS = 2e3;
|
|
25289
25471
|
var PENDING_KEEPALIVE_MS = 25e3;
|
|
25290
25472
|
var BROWSER_OPEN_FALLBACK_MS = 4e3;
|
|
25291
25473
|
function buildPendingBridgeUrl(apiUrl, connectionId) {
|
|
@@ -25307,7 +25489,32 @@ function runPendingAuth(options) {
|
|
|
25307
25489
|
const authPromise = new Promise((resolve15) => {
|
|
25308
25490
|
resolveAuth = resolve15;
|
|
25309
25491
|
});
|
|
25492
|
+
let reconnectAttempt = 0;
|
|
25493
|
+
const signInQuiet = createEmptyReconnectQuietSlot();
|
|
25494
|
+
function clearQuietOnOpen() {
|
|
25495
|
+
clearReconnectQuietOnSuccessfulConnection(
|
|
25496
|
+
signInQuiet,
|
|
25497
|
+
logFn,
|
|
25498
|
+
"[Bridge service] Sign-in connection restored."
|
|
25499
|
+
);
|
|
25500
|
+
reconnectAttempt = 0;
|
|
25501
|
+
}
|
|
25502
|
+
function beginDeferredPendingCloseLog(code, reason) {
|
|
25503
|
+
beginDeferredDisconnectForReconnect({
|
|
25504
|
+
isClosedByUser: () => resolved,
|
|
25505
|
+
quiet: signInQuiet,
|
|
25506
|
+
code,
|
|
25507
|
+
reason,
|
|
25508
|
+
willReconnect: true,
|
|
25509
|
+
log: logFn,
|
|
25510
|
+
serviceLabel: "[Bridge service]",
|
|
25511
|
+
shutdownDetail: "Not reconnecting (shutting down).",
|
|
25512
|
+
reconnectingDetail: "Waiting for browser sign-in; reconnecting\u2026",
|
|
25513
|
+
shouldAbortQuietWindow: () => ws != null && ws.readyState === wrapper_default.OPEN
|
|
25514
|
+
});
|
|
25515
|
+
}
|
|
25310
25516
|
function cleanup() {
|
|
25517
|
+
clearReconnectQuietTimer(signInQuiet);
|
|
25311
25518
|
if (reconnectTimeout) {
|
|
25312
25519
|
clearTimeout(reconnectTimeout);
|
|
25313
25520
|
reconnectTimeout = null;
|
|
@@ -25331,6 +25538,7 @@ function runPendingAuth(options) {
|
|
|
25331
25538
|
ws = createWsBridge({
|
|
25332
25539
|
url: url2,
|
|
25333
25540
|
onOpen: () => {
|
|
25541
|
+
clearQuietOnOpen();
|
|
25334
25542
|
sendWsMessage(ws, { type: "identify", role: "cli" });
|
|
25335
25543
|
keepaliveInterval = setInterval(() => {
|
|
25336
25544
|
if (resolved || !ws || ws.readyState !== 1) return;
|
|
@@ -25351,15 +25559,21 @@ function runPendingAuth(options) {
|
|
|
25351
25559
|
keepaliveInterval = null;
|
|
25352
25560
|
}
|
|
25353
25561
|
if (resolved) return;
|
|
25354
|
-
|
|
25355
|
-
|
|
25356
|
-
|
|
25562
|
+
beginDeferredPendingCloseLog(code, reason);
|
|
25563
|
+
const delay2 = reconnectDelayMs(reconnectAttempt);
|
|
25564
|
+
reconnectAttempt += 1;
|
|
25565
|
+
if (signInQuiet.verboseLogs) {
|
|
25566
|
+
const delayLabel = formatReconnectDelayForLog(delay2);
|
|
25567
|
+
logFn(
|
|
25568
|
+
`[Bridge service] Next sign-in connection attempt in ${delayLabel} (attempt ${reconnectAttempt}).`
|
|
25569
|
+
);
|
|
25570
|
+
}
|
|
25357
25571
|
reconnectTimeout = setTimeout(() => {
|
|
25358
25572
|
reconnectTimeout = null;
|
|
25359
25573
|
connect();
|
|
25360
|
-
},
|
|
25574
|
+
}, delay2);
|
|
25361
25575
|
},
|
|
25362
|
-
onError: (err) => logFn(`[Bridge service] WebSocket error
|
|
25576
|
+
onError: (err) => logFn(`[Bridge service] WebSocket error while waiting for sign-in: ${err.message}`),
|
|
25363
25577
|
onMessage: (data) => {
|
|
25364
25578
|
const msg = data;
|
|
25365
25579
|
if (msg.type === "auth_token" && typeof msg.token === "string") {
|
|
@@ -25400,7 +25614,7 @@ function buildBridgeUrl(apiUrl, workspaceId, authToken) {
|
|
|
25400
25614
|
|
|
25401
25615
|
// src/git/discover-repos.ts
|
|
25402
25616
|
import * as fs2 from "node:fs";
|
|
25403
|
-
import * as
|
|
25617
|
+
import * as path3 from "node:path";
|
|
25404
25618
|
|
|
25405
25619
|
// ../../node_modules/.pnpm/simple-git@3.32.3/node_modules/simple-git/dist/esm/index.js
|
|
25406
25620
|
var import_file_exists = __toESM(require_dist(), 1);
|
|
@@ -26662,8 +26876,8 @@ var init_git_executor_chain = __esm2({
|
|
|
26662
26876
|
get cwd() {
|
|
26663
26877
|
return this._cwd || this._executor.cwd;
|
|
26664
26878
|
}
|
|
26665
|
-
set cwd(
|
|
26666
|
-
this._cwd =
|
|
26879
|
+
set cwd(cwd) {
|
|
26880
|
+
this._cwd = cwd;
|
|
26667
26881
|
}
|
|
26668
26882
|
get env() {
|
|
26669
26883
|
return this._executor.env;
|
|
@@ -26854,8 +27068,8 @@ var init_git_executor = __esm2({
|
|
|
26854
27068
|
"use strict";
|
|
26855
27069
|
init_git_executor_chain();
|
|
26856
27070
|
GitExecutor = class {
|
|
26857
|
-
constructor(
|
|
26858
|
-
this.cwd =
|
|
27071
|
+
constructor(cwd, _scheduler, _plugins) {
|
|
27072
|
+
this.cwd = cwd;
|
|
26859
27073
|
this._scheduler = _scheduler;
|
|
26860
27074
|
this._plugins = _plugins;
|
|
26861
27075
|
this._chain = new GitExecutorChain(this, this._scheduler, this._plugins);
|
|
@@ -29985,9 +30199,9 @@ async function isGitRepoDirectory(dirPath) {
|
|
|
29985
30199
|
}
|
|
29986
30200
|
|
|
29987
30201
|
// src/git/discover-repos.ts
|
|
29988
|
-
async function discoverGitRepos(
|
|
30202
|
+
async function discoverGitRepos(cwd = getBridgeWorkspaceDirectory()) {
|
|
29989
30203
|
const result = [];
|
|
29990
|
-
const cwdResolved =
|
|
30204
|
+
const cwdResolved = path3.resolve(cwd);
|
|
29991
30205
|
if (await isGitRepoDirectory(cwdResolved)) {
|
|
29992
30206
|
const remoteUrl = await getRemoteOriginUrl(cwdResolved);
|
|
29993
30207
|
result.push({ absolutePath: cwdResolved, remoteUrl });
|
|
@@ -30000,7 +30214,7 @@ async function discoverGitRepos(cwd3 = process.cwd()) {
|
|
|
30000
30214
|
}
|
|
30001
30215
|
for (const ent of entries) {
|
|
30002
30216
|
if (!ent.isDirectory()) continue;
|
|
30003
|
-
const childPath =
|
|
30217
|
+
const childPath = path3.join(cwdResolved, ent.name);
|
|
30004
30218
|
if (await isGitRepoDirectory(childPath)) {
|
|
30005
30219
|
const remoteUrl = await getRemoteOriginUrl(childPath);
|
|
30006
30220
|
result.push({ absolutePath: childPath, remoteUrl });
|
|
@@ -30009,11 +30223,11 @@ async function discoverGitRepos(cwd3 = process.cwd()) {
|
|
|
30009
30223
|
return result;
|
|
30010
30224
|
}
|
|
30011
30225
|
async function discoverGitReposUnderRoot(rootAbs) {
|
|
30012
|
-
const root =
|
|
30226
|
+
const root = path3.resolve(rootAbs);
|
|
30013
30227
|
const roots = [];
|
|
30014
30228
|
async function walk(dir) {
|
|
30015
30229
|
if (await isGitRepoDirectory(dir)) {
|
|
30016
|
-
roots.push(
|
|
30230
|
+
roots.push(path3.resolve(dir));
|
|
30017
30231
|
return;
|
|
30018
30232
|
}
|
|
30019
30233
|
let entries;
|
|
@@ -30024,7 +30238,7 @@ async function discoverGitReposUnderRoot(rootAbs) {
|
|
|
30024
30238
|
}
|
|
30025
30239
|
for (const ent of entries) {
|
|
30026
30240
|
if (!ent.isDirectory() || ent.name === ".git") continue;
|
|
30027
|
-
await walk(
|
|
30241
|
+
await walk(path3.join(dir, ent.name));
|
|
30028
30242
|
}
|
|
30029
30243
|
}
|
|
30030
30244
|
await walk(root);
|
|
@@ -30051,56 +30265,40 @@ function reportGitRepos(getWs, log2) {
|
|
|
30051
30265
|
}
|
|
30052
30266
|
}
|
|
30053
30267
|
}).catch((err) => {
|
|
30054
|
-
log2(
|
|
30268
|
+
log2(
|
|
30269
|
+
`[Bridge service] Git repository discovery failed: ${err instanceof Error ? err.message : String(err)}`
|
|
30270
|
+
);
|
|
30055
30271
|
});
|
|
30056
30272
|
});
|
|
30057
30273
|
}
|
|
30058
30274
|
|
|
30059
|
-
// src/bridge/connection/schedule-reconnect.ts
|
|
30060
|
-
var RECONNECT_BASE_MS = 1e3;
|
|
30061
|
-
var RECONNECT_MAX_MS = 3e4;
|
|
30062
|
-
function scheduleReconnect(state, connect, log2) {
|
|
30063
|
-
if (state.closedByUser || state.currentWs != null) return;
|
|
30064
|
-
const delay2 = Math.min(
|
|
30065
|
-
RECONNECT_BASE_MS * 2 ** state.reconnectAttempt,
|
|
30066
|
-
RECONNECT_MAX_MS
|
|
30067
|
-
);
|
|
30068
|
-
state.reconnectAttempt += 1;
|
|
30069
|
-
log2(`[Bridge service] reconnect attempt ${state.reconnectAttempt} in ${delay2 / 1e3}s\u2026`);
|
|
30070
|
-
state.reconnectTimeout = setTimeout(() => {
|
|
30071
|
-
state.reconnectTimeout = null;
|
|
30072
|
-
connect();
|
|
30073
|
-
}, delay2);
|
|
30074
|
-
}
|
|
30075
|
-
|
|
30076
30275
|
// src/bridge/connection/close-bridge-connection.ts
|
|
30077
30276
|
async function closeBridgeConnection(state, acpManager, devServerManager, log2) {
|
|
30078
|
-
log2
|
|
30277
|
+
const say = log2 ?? logImmediate;
|
|
30278
|
+
say("Cleaning up connections\u2026");
|
|
30079
30279
|
await new Promise((resolve15) => setImmediate(resolve15));
|
|
30080
|
-
if (devServerManager) {
|
|
30081
|
-
log2?.("Requesting dev server processes to stop\u2026");
|
|
30082
|
-
await devServerManager.shutdownAllGraceful();
|
|
30083
|
-
}
|
|
30084
30280
|
state.closedByUser = true;
|
|
30281
|
+
clearReconnectQuietTimer(state.mainQuiet);
|
|
30282
|
+
clearReconnectQuietTimer(state.firehoseQuiet);
|
|
30085
30283
|
if (state.reconnectTimeout != null) {
|
|
30086
|
-
|
|
30284
|
+
say("Cancelling bridge reconnect timer\u2026");
|
|
30087
30285
|
clearTimeout(state.reconnectTimeout);
|
|
30088
30286
|
state.reconnectTimeout = null;
|
|
30089
30287
|
}
|
|
30090
30288
|
if (state.firehoseReconnectTimeout != null) {
|
|
30091
|
-
|
|
30289
|
+
say("Cancelling preview tunnel reconnect timer\u2026");
|
|
30092
30290
|
clearTimeout(state.firehoseReconnectTimeout);
|
|
30093
30291
|
state.firehoseReconnectTimeout = null;
|
|
30094
30292
|
}
|
|
30095
30293
|
if (state.firehoseHandle) {
|
|
30096
|
-
|
|
30294
|
+
say("Closing preview tunnel (local HTTP proxy and dev logs)\u2026");
|
|
30097
30295
|
state.firehoseHandle.close();
|
|
30098
30296
|
state.firehoseHandle = null;
|
|
30099
30297
|
}
|
|
30100
|
-
|
|
30298
|
+
say("Disconnecting local agent\u2026");
|
|
30101
30299
|
acpManager.disconnect();
|
|
30102
30300
|
if (state.currentWs) {
|
|
30103
|
-
|
|
30301
|
+
say("Closing bridge connection to the cloud\u2026");
|
|
30104
30302
|
state.currentWs.removeAllListeners();
|
|
30105
30303
|
const wsState = state.currentWs.readyState;
|
|
30106
30304
|
if (wsState === 1 || wsState === 2) {
|
|
@@ -30113,14 +30311,28 @@ async function closeBridgeConnection(state, acpManager, devServerManager, log2)
|
|
|
30113
30311
|
}
|
|
30114
30312
|
state.currentWs = null;
|
|
30115
30313
|
}
|
|
30314
|
+
if (devServerManager) {
|
|
30315
|
+
say("Stopping local dev server processes\u2026");
|
|
30316
|
+
await devServerManager.shutdownAllGraceful();
|
|
30317
|
+
}
|
|
30318
|
+
say("Shutdown complete.");
|
|
30116
30319
|
}
|
|
30117
30320
|
|
|
30118
30321
|
// src/git/session-git-queue.ts
|
|
30322
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
30323
|
+
import { readFile, stat } from "node:fs/promises";
|
|
30324
|
+
import { promisify as promisify2 } from "node:util";
|
|
30325
|
+
import * as path5 from "node:path";
|
|
30326
|
+
|
|
30327
|
+
// src/git/pre-turn-snapshot.ts
|
|
30328
|
+
import * as fs3 from "node:fs";
|
|
30329
|
+
import * as path4 from "node:path";
|
|
30119
30330
|
import { execFile } from "node:child_process";
|
|
30120
30331
|
import { promisify } from "node:util";
|
|
30121
|
-
import * as path3 from "node:path";
|
|
30122
30332
|
var execFileAsync = promisify(execFile);
|
|
30123
|
-
|
|
30333
|
+
function snapshotsDirForCwd(agentCwd) {
|
|
30334
|
+
return path4.join(agentCwd, ".buildautomaton", "snapshots");
|
|
30335
|
+
}
|
|
30124
30336
|
async function gitStashCreate(repoRoot, log2) {
|
|
30125
30337
|
try {
|
|
30126
30338
|
const { stdout } = await execFileAsync("git", ["stash", "create"], {
|
|
@@ -30129,51 +30341,149 @@ async function gitStashCreate(repoRoot, log2) {
|
|
|
30129
30341
|
});
|
|
30130
30342
|
return stdout.trim();
|
|
30131
30343
|
} catch (e) {
|
|
30132
|
-
log2(
|
|
30344
|
+
log2(
|
|
30345
|
+
`[snapshot] Git stash create failed in ${repoRoot}: ${e instanceof Error ? e.message : String(e)}`
|
|
30346
|
+
);
|
|
30133
30347
|
return "";
|
|
30134
30348
|
}
|
|
30135
30349
|
}
|
|
30136
|
-
async function
|
|
30137
|
-
|
|
30138
|
-
|
|
30350
|
+
async function gitRun(repoRoot, args, log2, label) {
|
|
30351
|
+
try {
|
|
30352
|
+
await execFileAsync("git", args, { cwd: repoRoot, maxBuffer: 10 * 1024 * 1024 });
|
|
30353
|
+
return { ok: true };
|
|
30354
|
+
} catch (e) {
|
|
30355
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
30356
|
+
log2(`[snapshot] Git ${label} failed in ${repoRoot}: ${msg}`);
|
|
30357
|
+
return { ok: false, error: msg };
|
|
30358
|
+
}
|
|
30359
|
+
}
|
|
30360
|
+
async function resolveSnapshotRepoRoots(options) {
|
|
30361
|
+
const { worktreePaths, fallbackCwd, log: log2 } = options;
|
|
30362
|
+
if (worktreePaths?.length) {
|
|
30363
|
+
const uniq = [...new Set(worktreePaths.map((p) => path4.resolve(p)))];
|
|
30364
|
+
return uniq;
|
|
30365
|
+
}
|
|
30366
|
+
try {
|
|
30367
|
+
const repos = await discoverGitReposUnderRoot(fallbackCwd);
|
|
30368
|
+
return repos.map((r) => r.absolutePath);
|
|
30369
|
+
} catch (e) {
|
|
30370
|
+
log2(`[snapshot] Discover repositories failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
30371
|
+
return [];
|
|
30372
|
+
}
|
|
30373
|
+
}
|
|
30374
|
+
async function capturePreTurnSnapshot(options) {
|
|
30375
|
+
const { runId, repoRoots, agentCwd, log: log2 } = options;
|
|
30376
|
+
if (!runId || !repoRoots.length) {
|
|
30377
|
+
return { ok: false, error: "No git repos to snapshot" };
|
|
30378
|
+
}
|
|
30139
30379
|
const repos = [];
|
|
30140
30380
|
for (const root of repoRoots) {
|
|
30141
30381
|
const stashSha = await gitStashCreate(root, log2);
|
|
30142
30382
|
repos.push({ path: root, stashSha });
|
|
30143
30383
|
}
|
|
30144
|
-
|
|
30145
|
-
|
|
30384
|
+
const dir = snapshotsDirForCwd(agentCwd);
|
|
30385
|
+
try {
|
|
30386
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
30387
|
+
} catch (e) {
|
|
30388
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
30389
|
+
}
|
|
30390
|
+
const payload = {
|
|
30391
|
+
runId,
|
|
30392
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
30393
|
+
repos
|
|
30394
|
+
};
|
|
30395
|
+
const filePath = path4.join(dir, `${runId}.json`);
|
|
30396
|
+
try {
|
|
30397
|
+
fs3.writeFileSync(filePath, JSON.stringify(payload, null, 2), "utf8");
|
|
30398
|
+
} catch (e) {
|
|
30399
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
30400
|
+
}
|
|
30401
|
+
const repoList = repos.map((r) => r.path).join(", ");
|
|
30402
|
+
log2(
|
|
30403
|
+
`[snapshot] Saved pre-turn snapshot ${runId.slice(0, 8)}\u2026 (${repos.length} repo(s)): ${repoList}`
|
|
30404
|
+
);
|
|
30405
|
+
return { ok: true, filePath, repos };
|
|
30406
|
+
}
|
|
30407
|
+
async function applyPreTurnSnapshot(filePath, log2) {
|
|
30408
|
+
let data;
|
|
30409
|
+
try {
|
|
30410
|
+
const raw = fs3.readFileSync(filePath, "utf8");
|
|
30411
|
+
data = JSON.parse(raw);
|
|
30412
|
+
} catch (e) {
|
|
30413
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
30414
|
+
}
|
|
30415
|
+
if (!Array.isArray(data.repos)) {
|
|
30416
|
+
return { ok: false, error: "Invalid snapshot file" };
|
|
30417
|
+
}
|
|
30418
|
+
for (const r of data.repos) {
|
|
30419
|
+
if (!r.path) continue;
|
|
30420
|
+
const reset = await gitRun(r.path, ["reset", "--hard", "HEAD"], log2, "reset --hard");
|
|
30421
|
+
if (!reset.ok) return reset;
|
|
30422
|
+
const clean = await gitRun(r.path, ["clean", "-fd"], log2, "clean -fd");
|
|
30423
|
+
if (!clean.ok) return clean;
|
|
30424
|
+
if (r.stashSha) {
|
|
30425
|
+
const ap = await gitRun(r.path, ["stash", "apply", r.stashSha], log2, "stash apply");
|
|
30426
|
+
if (!ap.ok) return ap;
|
|
30427
|
+
}
|
|
30428
|
+
}
|
|
30429
|
+
log2(`[snapshot] Restored pre-turn state for ${data.runId.slice(0, 8)}\u2026`);
|
|
30430
|
+
return { ok: true };
|
|
30431
|
+
}
|
|
30432
|
+
function snapshotFilePath(agentCwd, runId) {
|
|
30433
|
+
return path4.join(snapshotsDirForCwd(agentCwd), `${runId}.json`);
|
|
30434
|
+
}
|
|
30435
|
+
|
|
30436
|
+
// src/git/session-git-queue.ts
|
|
30437
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
30438
|
+
var MAX_FULL_FILE_TEXT_BYTES = 512 * 1024;
|
|
30439
|
+
async function readWorkspaceFileAsUtf8(absPath) {
|
|
30440
|
+
try {
|
|
30441
|
+
const st = await stat(absPath);
|
|
30442
|
+
if (!st.isFile() || st.size > MAX_FULL_FILE_TEXT_BYTES) return void 0;
|
|
30443
|
+
return await readFile(absPath, "utf8");
|
|
30444
|
+
} catch {
|
|
30445
|
+
return void 0;
|
|
30446
|
+
}
|
|
30146
30447
|
}
|
|
30147
|
-
async function
|
|
30148
|
-
const { sessionId, runId, sendSessionUpdate, log: log2 } = options;
|
|
30149
|
-
const
|
|
30150
|
-
|
|
30151
|
-
|
|
30448
|
+
async function collectTurnGitDiffFromPreTurnSnapshot(options) {
|
|
30449
|
+
const { sessionId, runId, agentCwd, sendSessionUpdate, log: log2 } = options;
|
|
30450
|
+
const filePath = snapshotFilePath(agentCwd, runId);
|
|
30451
|
+
let data;
|
|
30452
|
+
try {
|
|
30453
|
+
const raw = await readFile(filePath, "utf8");
|
|
30454
|
+
data = JSON.parse(raw);
|
|
30455
|
+
} catch (e) {
|
|
30456
|
+
log2(
|
|
30457
|
+
`[session-git-queue] No pre-turn snapshot for run ${runId.slice(0, 8)}\u2026: ${e instanceof Error ? e.message : String(e)}`
|
|
30458
|
+
);
|
|
30152
30459
|
return;
|
|
30153
30460
|
}
|
|
30154
|
-
|
|
30155
|
-
|
|
30156
|
-
|
|
30461
|
+
if (!Array.isArray(data.repos) || !data.repos.length) {
|
|
30462
|
+
log2(`[session-git-queue] Empty repos in snapshot ${runId.slice(0, 8)}\u2026; skipping aggregate diff.`);
|
|
30463
|
+
return;
|
|
30464
|
+
}
|
|
30465
|
+
const multiRepo = data.repos.length > 1;
|
|
30466
|
+
for (const repo of data.repos) {
|
|
30157
30467
|
if (!repo.stashSha) continue;
|
|
30158
30468
|
let namesRaw;
|
|
30159
30469
|
try {
|
|
30160
|
-
const { stdout } = await
|
|
30470
|
+
const { stdout } = await execFileAsync2("git", ["diff", "--name-only", repo.stashSha], {
|
|
30161
30471
|
cwd: repo.path,
|
|
30162
30472
|
maxBuffer: 10 * 1024 * 1024
|
|
30163
30473
|
});
|
|
30164
30474
|
namesRaw = stdout;
|
|
30165
30475
|
} catch (e) {
|
|
30166
30476
|
log2(
|
|
30167
|
-
`[session-git-queue]
|
|
30477
|
+
`[session-git-queue] Git diff --name-only failed in ${repo.path}: ${e instanceof Error ? e.message : String(e)}`
|
|
30168
30478
|
);
|
|
30169
30479
|
continue;
|
|
30170
30480
|
}
|
|
30171
30481
|
const lines = namesRaw.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
30172
|
-
const slug =
|
|
30482
|
+
const slug = path5.basename(repo.path).replace(/[^\w.-]+/g, "_") || "repo";
|
|
30173
30483
|
for (const rel of lines) {
|
|
30174
30484
|
if (rel.includes("..")) continue;
|
|
30175
30485
|
try {
|
|
30176
|
-
const { stdout: patchContent } = await
|
|
30486
|
+
const { stdout: patchContent } = await execFileAsync2(
|
|
30177
30487
|
"git",
|
|
30178
30488
|
["diff", "--no-color", repo.stashSha, "--", rel],
|
|
30179
30489
|
{
|
|
@@ -30183,15 +30493,20 @@ async function collectSessionDiffAndNotify(options) {
|
|
|
30183
30493
|
);
|
|
30184
30494
|
if (!patchContent.trim()) continue;
|
|
30185
30495
|
const displayPath = multiRepo ? `${slug}/${rel}` : rel;
|
|
30496
|
+
const absFile = path5.join(repo.path, rel);
|
|
30497
|
+
const newText = await readWorkspaceFileAsUtf8(absFile);
|
|
30186
30498
|
sendSessionUpdate({
|
|
30187
30499
|
type: "session_file_change",
|
|
30188
30500
|
sessionId,
|
|
30189
30501
|
runId,
|
|
30190
30502
|
path: displayPath,
|
|
30191
|
-
patchContent
|
|
30503
|
+
patchContent,
|
|
30504
|
+
...newText !== void 0 ? { newText } : {}
|
|
30192
30505
|
});
|
|
30193
30506
|
} catch (e) {
|
|
30194
|
-
log2(
|
|
30507
|
+
log2(
|
|
30508
|
+
`[session-git-queue] Git diff failed for ${rel}: ${e instanceof Error ? e.message : String(e)}`
|
|
30509
|
+
);
|
|
30195
30510
|
}
|
|
30196
30511
|
}
|
|
30197
30512
|
}
|
|
@@ -30205,15 +30520,21 @@ async function sendPromptToAgent(options) {
|
|
|
30205
30520
|
promptId,
|
|
30206
30521
|
sessionId,
|
|
30207
30522
|
runId,
|
|
30523
|
+
agentCwd,
|
|
30208
30524
|
sendResult,
|
|
30209
30525
|
sendSessionUpdate,
|
|
30210
|
-
collectSessionDiffAfterTurn,
|
|
30211
30526
|
log: log2
|
|
30212
30527
|
} = options;
|
|
30213
30528
|
try {
|
|
30214
30529
|
const result = await handle.sendPrompt(promptText, {});
|
|
30215
|
-
if (
|
|
30216
|
-
await
|
|
30530
|
+
if (sessionId && runId && sendSessionUpdate && agentCwd && result.success) {
|
|
30531
|
+
await collectTurnGitDiffFromPreTurnSnapshot({
|
|
30532
|
+
sessionId,
|
|
30533
|
+
runId,
|
|
30534
|
+
agentCwd,
|
|
30535
|
+
sendSessionUpdate,
|
|
30536
|
+
log: log2
|
|
30537
|
+
});
|
|
30217
30538
|
}
|
|
30218
30539
|
sendResult({
|
|
30219
30540
|
type: "prompt_result",
|
|
@@ -30223,12 +30544,11 @@ async function sendPromptToAgent(options) {
|
|
|
30223
30544
|
...result
|
|
30224
30545
|
});
|
|
30225
30546
|
if (!result.success) {
|
|
30226
|
-
log2(`[
|
|
30547
|
+
log2(`[Agent] ${result.error ?? "Error"}`);
|
|
30227
30548
|
}
|
|
30228
30549
|
} catch (err) {
|
|
30229
30550
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
30230
|
-
log2(`[
|
|
30231
|
-
if (err instanceof Error && err.stack) log2(`[agent] ${err.stack}`);
|
|
30551
|
+
log2(`[Agent] Send failed: ${errMsg}`);
|
|
30232
30552
|
sendResult({
|
|
30233
30553
|
type: "prompt_result",
|
|
30234
30554
|
id: promptId,
|
|
@@ -30241,8 +30561,8 @@ async function sendPromptToAgent(options) {
|
|
|
30241
30561
|
}
|
|
30242
30562
|
|
|
30243
30563
|
// src/acp/ensure-acp-client.ts
|
|
30244
|
-
import * as
|
|
30245
|
-
import * as
|
|
30564
|
+
import * as fs4 from "node:fs";
|
|
30565
|
+
import * as path9 from "node:path";
|
|
30246
30566
|
|
|
30247
30567
|
// src/error-message.ts
|
|
30248
30568
|
function errorMessage(err) {
|
|
@@ -30254,9 +30574,77 @@ function errorMessage(err) {
|
|
|
30254
30574
|
return String(err);
|
|
30255
30575
|
}
|
|
30256
30576
|
|
|
30257
|
-
// src/acp/clients/acp-client.ts
|
|
30577
|
+
// src/acp/clients/sdk-stdio-acp-client.ts
|
|
30258
30578
|
import { spawn as spawn2 } from "node:child_process";
|
|
30579
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
30580
|
+
import { dirname } from "node:path";
|
|
30259
30581
|
import { Readable, Writable } from "node:stream";
|
|
30582
|
+
|
|
30583
|
+
// src/files/diff/unified-diff.ts
|
|
30584
|
+
function computeLineDiff(oldText, newText) {
|
|
30585
|
+
const oldLines = oldText.split("\n");
|
|
30586
|
+
const newLines = newText.split("\n");
|
|
30587
|
+
const m = oldLines.length;
|
|
30588
|
+
const n = newLines.length;
|
|
30589
|
+
const dp = Array(m + 1);
|
|
30590
|
+
for (let i2 = 0; i2 <= m; i2++) dp[i2] = Array(n + 1).fill(0);
|
|
30591
|
+
for (let i2 = 1; i2 <= m; i2++) {
|
|
30592
|
+
for (let j2 = 1; j2 <= n; j2++) {
|
|
30593
|
+
if (oldLines[i2 - 1] === newLines[j2 - 1]) {
|
|
30594
|
+
dp[i2][j2] = dp[i2 - 1][j2 - 1] + 1;
|
|
30595
|
+
} else {
|
|
30596
|
+
dp[i2][j2] = Math.max(dp[i2 - 1][j2], dp[i2][j2 - 1]);
|
|
30597
|
+
}
|
|
30598
|
+
}
|
|
30599
|
+
}
|
|
30600
|
+
const result = [];
|
|
30601
|
+
let i = m;
|
|
30602
|
+
let j = n;
|
|
30603
|
+
while (i > 0 || j > 0) {
|
|
30604
|
+
if (i > 0 && j > 0 && oldLines[i - 1] === newLines[j - 1]) {
|
|
30605
|
+
result.unshift({ type: "context", line: oldLines[i - 1] });
|
|
30606
|
+
i--;
|
|
30607
|
+
j--;
|
|
30608
|
+
} else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
|
|
30609
|
+
result.unshift({ type: "add", line: newLines[j - 1] });
|
|
30610
|
+
j--;
|
|
30611
|
+
} else {
|
|
30612
|
+
result.unshift({ type: "remove", line: oldLines[i - 1] });
|
|
30613
|
+
i--;
|
|
30614
|
+
}
|
|
30615
|
+
}
|
|
30616
|
+
return result;
|
|
30617
|
+
}
|
|
30618
|
+
function editSnippetToUnifiedDiff(filePath, oldText, newText) {
|
|
30619
|
+
const lines = computeLineDiff(oldText, newText);
|
|
30620
|
+
const out = [`--- ${filePath}`, `+++ ${filePath}`];
|
|
30621
|
+
for (const d of lines) {
|
|
30622
|
+
if (d.type === "add") out.push(`+${d.line}`);
|
|
30623
|
+
else if (d.type === "remove") out.push(`-${d.line}`);
|
|
30624
|
+
else out.push(` ${d.line}`);
|
|
30625
|
+
}
|
|
30626
|
+
return out.join("\n");
|
|
30627
|
+
}
|
|
30628
|
+
|
|
30629
|
+
// src/acp/safe-fs-path.ts
|
|
30630
|
+
import * as path6 from "node:path";
|
|
30631
|
+
function resolveSafePathUnderCwd(cwd, filePath) {
|
|
30632
|
+
const trimmed2 = filePath.trim();
|
|
30633
|
+
if (!trimmed2) return null;
|
|
30634
|
+
const normalizedCwd = path6.resolve(cwd);
|
|
30635
|
+
const resolved = path6.isAbsolute(trimmed2) ? path6.normalize(trimmed2) : path6.resolve(normalizedCwd, trimmed2);
|
|
30636
|
+
const rel = path6.relative(normalizedCwd, resolved);
|
|
30637
|
+
if (rel.startsWith("..") || path6.isAbsolute(rel)) return null;
|
|
30638
|
+
return resolved;
|
|
30639
|
+
}
|
|
30640
|
+
function toDisplayPathRelativeToCwd(cwd, absolutePath) {
|
|
30641
|
+
const normalizedCwd = path6.resolve(cwd);
|
|
30642
|
+
const rel = path6.relative(normalizedCwd, path6.resolve(absolutePath));
|
|
30643
|
+
if (!rel || rel === "") return path6.basename(absolutePath);
|
|
30644
|
+
return rel.split(path6.sep).join("/");
|
|
30645
|
+
}
|
|
30646
|
+
|
|
30647
|
+
// src/acp/clients/sdk-stdio-acp-client.ts
|
|
30260
30648
|
function formatSpawnError(err, command) {
|
|
30261
30649
|
if (err.code === "ENOENT") {
|
|
30262
30650
|
return `Command "${command}" not found. Install the agent (e.g. Cursor CLI) or add it to PATH.`;
|
|
@@ -30271,16 +30659,36 @@ function toErrorMessage(err) {
|
|
|
30271
30659
|
if (err != null && typeof err === "object") return JSON.stringify(err);
|
|
30272
30660
|
return String(err);
|
|
30273
30661
|
}
|
|
30274
|
-
|
|
30275
|
-
|
|
30276
|
-
const
|
|
30662
|
+
function sliceFileContentRange(content, line, limit) {
|
|
30663
|
+
if (line == null && limit == null) return content;
|
|
30664
|
+
const lines = content.split("\n");
|
|
30665
|
+
const start = line != null && line > 0 ? line - 1 : 0;
|
|
30666
|
+
const end = limit != null && limit > 0 ? start + limit : lines.length;
|
|
30667
|
+
return lines.slice(start, end).join("\n");
|
|
30668
|
+
}
|
|
30669
|
+
function bridgePayloadFromSdkSessionNotification(params) {
|
|
30670
|
+
return { sessionId: params.sessionId, ...params.update };
|
|
30671
|
+
}
|
|
30672
|
+
async function createSdkStdioAcpClient(options) {
|
|
30673
|
+
const { ClientSideConnection: ClientSideConnection2, ndJsonStream: ndJsonStream2, PROTOCOL_VERSION: PROTOCOL_VERSION2 } = await Promise.resolve().then(() => (init_acp(), acp_exports));
|
|
30674
|
+
const {
|
|
30675
|
+
command,
|
|
30676
|
+
cwd = getBridgeWorkspaceDirectory(),
|
|
30677
|
+
onSessionUpdate,
|
|
30678
|
+
onFileChange,
|
|
30679
|
+
killSubprocessAfterCancelMs,
|
|
30680
|
+
onAgentSubprocessExit
|
|
30681
|
+
} = options;
|
|
30277
30682
|
const isWindows = process.platform === "win32";
|
|
30278
30683
|
const child = spawn2(command[0], command.slice(1), {
|
|
30279
|
-
cwd
|
|
30684
|
+
cwd,
|
|
30280
30685
|
stdio: ["pipe", "pipe", "inherit"],
|
|
30281
30686
|
env: process.env,
|
|
30282
30687
|
shell: isWindows
|
|
30283
30688
|
});
|
|
30689
|
+
child.once("close", (code, signal) => {
|
|
30690
|
+
onAgentSubprocessExit?.({ code, signal });
|
|
30691
|
+
});
|
|
30284
30692
|
return new Promise((resolve15, reject) => {
|
|
30285
30693
|
child.on("error", (err) => {
|
|
30286
30694
|
child.kill();
|
|
@@ -30292,11 +30700,43 @@ async function createAcpClient(options) {
|
|
|
30292
30700
|
const readable = Readable.toWeb(child.stdout);
|
|
30293
30701
|
const stream = ndJsonStream2(writable, readable);
|
|
30294
30702
|
const client = (_agent) => ({
|
|
30295
|
-
async requestPermission(
|
|
30296
|
-
|
|
30703
|
+
async requestPermission(params) {
|
|
30704
|
+
const opt = params?.options?.[0];
|
|
30705
|
+
if (opt && typeof opt.optionId === "string") {
|
|
30706
|
+
return { outcome: { outcome: "selected", optionId: opt.optionId } };
|
|
30707
|
+
}
|
|
30708
|
+
return { outcome: { outcome: "cancelled" } };
|
|
30709
|
+
},
|
|
30710
|
+
async readTextFile(params) {
|
|
30711
|
+
const abs = resolveSafePathUnderCwd(cwd, params.path);
|
|
30712
|
+
if (!abs) throw new Error("Invalid or disallowed path");
|
|
30713
|
+
try {
|
|
30714
|
+
let content = readFileSync2(abs, "utf8");
|
|
30715
|
+
content = sliceFileContentRange(content, params.line, params.limit);
|
|
30716
|
+
return { content };
|
|
30717
|
+
} catch (e) {
|
|
30718
|
+
if (e.code === "ENOENT") return { content: "" };
|
|
30719
|
+
throw e;
|
|
30720
|
+
}
|
|
30721
|
+
},
|
|
30722
|
+
async writeTextFile(params) {
|
|
30723
|
+
const abs = resolveSafePathUnderCwd(cwd, params.path);
|
|
30724
|
+
if (!abs) throw new Error("Invalid or disallowed path");
|
|
30725
|
+
let oldText = "";
|
|
30726
|
+
try {
|
|
30727
|
+
oldText = readFileSync2(abs, "utf8");
|
|
30728
|
+
} catch (e) {
|
|
30729
|
+
if (e.code !== "ENOENT") throw e;
|
|
30730
|
+
}
|
|
30731
|
+
mkdirSync2(dirname(abs), { recursive: true });
|
|
30732
|
+
writeFileSync2(abs, params.content, "utf8");
|
|
30733
|
+
const displayPath = toDisplayPathRelativeToCwd(cwd, abs);
|
|
30734
|
+
const patchContent = editSnippetToUnifiedDiff(displayPath, oldText, params.content);
|
|
30735
|
+
onFileChange?.({ path: displayPath, oldText, newText: params.content, patchContent });
|
|
30736
|
+
return {};
|
|
30297
30737
|
},
|
|
30298
30738
|
async sessionUpdate(params) {
|
|
30299
|
-
onSessionUpdate?.(params);
|
|
30739
|
+
onSessionUpdate?.(bridgePayloadFromSdkSessionNotification(params));
|
|
30300
30740
|
}
|
|
30301
30741
|
});
|
|
30302
30742
|
const connection = new ClientSideConnection2(client, stream);
|
|
@@ -30304,11 +30744,13 @@ async function createAcpClient(options) {
|
|
|
30304
30744
|
child.kill();
|
|
30305
30745
|
});
|
|
30306
30746
|
await connection.initialize({
|
|
30307
|
-
protocolVersion:
|
|
30308
|
-
|
|
30747
|
+
protocolVersion: PROTOCOL_VERSION2,
|
|
30748
|
+
clientCapabilities: {
|
|
30749
|
+
fs: { readTextFile: true, writeTextFile: true }
|
|
30750
|
+
},
|
|
30309
30751
|
clientInfo: { name: "buildautomaton-cli", version: "0.1.0" }
|
|
30310
30752
|
});
|
|
30311
|
-
const newSessionRes = await connection.newSession({
|
|
30753
|
+
const newSessionRes = await connection.newSession({ cwd, mcpServers: [] });
|
|
30312
30754
|
const sessionId = newSessionRes.sessionId;
|
|
30313
30755
|
resolve15({
|
|
30314
30756
|
sessionId,
|
|
@@ -30316,7 +30758,7 @@ async function createAcpClient(options) {
|
|
|
30316
30758
|
try {
|
|
30317
30759
|
const response = await connection.prompt({
|
|
30318
30760
|
sessionId,
|
|
30319
|
-
prompt: { type: "text", text: prompt }
|
|
30761
|
+
prompt: [{ type: "text", text: prompt }]
|
|
30320
30762
|
});
|
|
30321
30763
|
const r = response;
|
|
30322
30764
|
const cancelled = (r?.stopReason ?? "").toLowerCase() === "cancelled";
|
|
@@ -30334,9 +30776,17 @@ async function createAcpClient(options) {
|
|
|
30334
30776
|
}
|
|
30335
30777
|
},
|
|
30336
30778
|
async cancel() {
|
|
30337
|
-
|
|
30338
|
-
|
|
30339
|
-
|
|
30779
|
+
try {
|
|
30780
|
+
await connection.cancel({ sessionId });
|
|
30781
|
+
} catch {
|
|
30782
|
+
}
|
|
30783
|
+
if (killSubprocessAfterCancelMs != null && killSubprocessAfterCancelMs >= 0) {
|
|
30784
|
+
const t = setTimeout(() => {
|
|
30785
|
+
if (child.exitCode == null && child.signalCode == null) {
|
|
30786
|
+
child.kill("SIGTERM");
|
|
30787
|
+
}
|
|
30788
|
+
}, killSubprocessAfterCancelMs);
|
|
30789
|
+
t.unref?.();
|
|
30340
30790
|
}
|
|
30341
30791
|
},
|
|
30342
30792
|
resolveRequest() {
|
|
@@ -30359,79 +30809,49 @@ function isCodexAcpCommand(command) {
|
|
|
30359
30809
|
const i = command.indexOf("@zed-industries/codex-acp");
|
|
30360
30810
|
return i >= 0 && (i === 0 || command[i - 1] === "npx" || command[i - 1] === "bunx");
|
|
30361
30811
|
}
|
|
30812
|
+
function buildCodexAcpSpawnCommand(base, _sessionMode) {
|
|
30813
|
+
return [...base];
|
|
30814
|
+
}
|
|
30362
30815
|
async function createCodexAcpClient(options) {
|
|
30363
|
-
const
|
|
30364
|
-
|
|
30816
|
+
const base = options.command?.length && options.command.some((a) => a.includes("codex-acp")) ? options.command : [...DEFAULT_CODEX_ACP_COMMAND];
|
|
30817
|
+
const command = buildCodexAcpSpawnCommand(base, options.sessionMode);
|
|
30818
|
+
return createSdkStdioAcpClient({ ...options, command });
|
|
30819
|
+
}
|
|
30820
|
+
|
|
30821
|
+
// src/acp/clients/claude-code-acp-client.ts
|
|
30822
|
+
function buildClaudeCodeAcpSpawnCommand(base, sessionMode) {
|
|
30823
|
+
if (!sessionMode) return [...base];
|
|
30824
|
+
const m = sessionMode.trim();
|
|
30825
|
+
if (m === "plan") return [...base, "--permission-mode", "plan"];
|
|
30826
|
+
return [...base];
|
|
30827
|
+
}
|
|
30828
|
+
async function createClaudeCodeAcpClient(options) {
|
|
30829
|
+
const command = buildClaudeCodeAcpSpawnCommand(options.command, options.sessionMode);
|
|
30830
|
+
return createSdkStdioAcpClient({
|
|
30831
|
+
...options,
|
|
30832
|
+
command,
|
|
30833
|
+
/** Claude-based agents sometimes ignore `session/cancel`; unblocks stop / stuck prompt. */
|
|
30834
|
+
killSubprocessAfterCancelMs: options.killSubprocessAfterCancelMs ?? 1e3
|
|
30835
|
+
});
|
|
30365
30836
|
}
|
|
30366
30837
|
|
|
30367
30838
|
// src/acp/clients/cursor-acp-client.ts
|
|
30368
|
-
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
30369
|
-
import { dirname } from "node:path";
|
|
30839
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "node:fs";
|
|
30840
|
+
import { dirname as dirname2 } from "node:path";
|
|
30370
30841
|
import { spawn as spawn3 } from "node:child_process";
|
|
30371
30842
|
import * as readline from "node:readline";
|
|
30372
30843
|
|
|
30373
|
-
// src/acp/
|
|
30374
|
-
|
|
30375
|
-
|
|
30376
|
-
|
|
30377
|
-
|
|
30378
|
-
|
|
30379
|
-
|
|
30380
|
-
|
|
30381
|
-
|
|
30382
|
-
return
|
|
30383
|
-
|
|
30384
|
-
function toDisplayPathRelativeToCwd(cwd3, absolutePath) {
|
|
30385
|
-
const normalizedCwd = path4.resolve(cwd3);
|
|
30386
|
-
const rel = path4.relative(normalizedCwd, path4.resolve(absolutePath));
|
|
30387
|
-
if (!rel || rel === "") return path4.basename(absolutePath);
|
|
30388
|
-
return rel.split(path4.sep).join("/");
|
|
30389
|
-
}
|
|
30390
|
-
|
|
30391
|
-
// src/files/diff/unified-diff.ts
|
|
30392
|
-
function computeLineDiff(oldText, newText) {
|
|
30393
|
-
const oldLines = oldText.split("\n");
|
|
30394
|
-
const newLines = newText.split("\n");
|
|
30395
|
-
const m = oldLines.length;
|
|
30396
|
-
const n = newLines.length;
|
|
30397
|
-
const dp = Array(m + 1);
|
|
30398
|
-
for (let i2 = 0; i2 <= m; i2++) dp[i2] = Array(n + 1).fill(0);
|
|
30399
|
-
for (let i2 = 1; i2 <= m; i2++) {
|
|
30400
|
-
for (let j2 = 1; j2 <= n; j2++) {
|
|
30401
|
-
if (oldLines[i2 - 1] === newLines[j2 - 1]) {
|
|
30402
|
-
dp[i2][j2] = dp[i2 - 1][j2 - 1] + 1;
|
|
30403
|
-
} else {
|
|
30404
|
-
dp[i2][j2] = Math.max(dp[i2 - 1][j2], dp[i2][j2 - 1]);
|
|
30405
|
-
}
|
|
30406
|
-
}
|
|
30407
|
-
}
|
|
30408
|
-
const result = [];
|
|
30409
|
-
let i = m;
|
|
30410
|
-
let j = n;
|
|
30411
|
-
while (i > 0 || j > 0) {
|
|
30412
|
-
if (i > 0 && j > 0 && oldLines[i - 1] === newLines[j - 1]) {
|
|
30413
|
-
result.unshift({ type: "context", line: oldLines[i - 1] });
|
|
30414
|
-
i--;
|
|
30415
|
-
j--;
|
|
30416
|
-
} else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
|
|
30417
|
-
result.unshift({ type: "add", line: newLines[j - 1] });
|
|
30418
|
-
j--;
|
|
30419
|
-
} else {
|
|
30420
|
-
result.unshift({ type: "remove", line: oldLines[i - 1] });
|
|
30421
|
-
i--;
|
|
30422
|
-
}
|
|
30423
|
-
}
|
|
30424
|
-
return result;
|
|
30425
|
-
}
|
|
30426
|
-
function editSnippetToUnifiedDiff(filePath, oldText, newText) {
|
|
30427
|
-
const lines = computeLineDiff(oldText, newText);
|
|
30428
|
-
const out = [`--- ${filePath}`, `+++ ${filePath}`];
|
|
30429
|
-
for (const d of lines) {
|
|
30430
|
-
if (d.type === "add") out.push(`+${d.line}`);
|
|
30431
|
-
else if (d.type === "remove") out.push(`-${d.line}`);
|
|
30432
|
-
else out.push(` ${d.line}`);
|
|
30433
|
-
}
|
|
30434
|
-
return out.join("\n");
|
|
30844
|
+
// src/acp/format-session-update-kind-for-log.ts
|
|
30845
|
+
var SESSION_UPDATE_KIND_LABELS = {
|
|
30846
|
+
tool_call: "Tool call",
|
|
30847
|
+
tool_call_update: "Tool call status",
|
|
30848
|
+
agent_message_chunk: "Agent message chunk",
|
|
30849
|
+
update: "Session update"
|
|
30850
|
+
};
|
|
30851
|
+
function formatSessionUpdateKindForLog(kind) {
|
|
30852
|
+
const known = SESSION_UPDATE_KIND_LABELS[kind];
|
|
30853
|
+
if (known) return known;
|
|
30854
|
+
return kind.split("_").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(" ");
|
|
30435
30855
|
}
|
|
30436
30856
|
|
|
30437
30857
|
// src/acp/clients/cursor-acp-client.ts
|
|
@@ -30458,12 +30878,19 @@ function sliceLinesByRange(content, line, limit) {
|
|
|
30458
30878
|
const end = limit != null && limit > 0 ? start + limit : lines.length;
|
|
30459
30879
|
return lines.slice(start, end).join("\n");
|
|
30460
30880
|
}
|
|
30881
|
+
function buildCursorAcpSpawnCommand(base, sessionMode) {
|
|
30882
|
+
if (!sessionMode) return [...base];
|
|
30883
|
+
const m = sessionMode.trim();
|
|
30884
|
+
if (m !== "ask" && m !== "plan") return [...base];
|
|
30885
|
+
return [...base, "--mode", m];
|
|
30886
|
+
}
|
|
30461
30887
|
async function createCursorAcpClient(options) {
|
|
30462
|
-
const
|
|
30888
|
+
const command = buildCursorAcpSpawnCommand(options.command, options.sessionMode);
|
|
30889
|
+
const { cwd = getBridgeWorkspaceDirectory(), onSessionUpdate, onRequest, onFileChange } = options;
|
|
30463
30890
|
const dbgFs = process.env.BUILDAMATON_DEBUG_ACP_FS === "1";
|
|
30464
30891
|
const isWindows = process.platform === "win32";
|
|
30465
30892
|
const child = spawn3(command[0], command.slice(1), {
|
|
30466
|
-
cwd
|
|
30893
|
+
cwd,
|
|
30467
30894
|
stdio: ["pipe", "pipe", "inherit"],
|
|
30468
30895
|
env: process.env,
|
|
30469
30896
|
shell: isWindows
|
|
@@ -30521,7 +30948,10 @@ async function createCursorAcpClient(options) {
|
|
|
30521
30948
|
const toolCall = update.toolCall ?? update.tool_call;
|
|
30522
30949
|
const toolName = typeof toolCall?.name === "string" ? toolCall.name : "";
|
|
30523
30950
|
if (dbgFs && (sessionUpdate === "tool_call" || sessionUpdate === "tool_call_update")) {
|
|
30524
|
-
|
|
30951
|
+
const kindLabel = formatSessionUpdateKindForLog(sessionUpdate ?? "update");
|
|
30952
|
+
console.error(
|
|
30953
|
+
`[acp] Received session update (${kindLabel}) tool=${toolName || "(none)"}`
|
|
30954
|
+
);
|
|
30525
30955
|
}
|
|
30526
30956
|
const isTextChunk = sessionUpdate === "agent_message_chunk" && update.content?.text;
|
|
30527
30957
|
if (isTextChunk && update.content?.text) {
|
|
@@ -30541,14 +30971,14 @@ async function createCursorAcpClient(options) {
|
|
|
30541
30971
|
if (dbgFs) {
|
|
30542
30972
|
console.error(`[acp-fs] ${method} path=${filePath.slice(0, 200)}${filePath.length > 200 ? "\u2026" : ""}`);
|
|
30543
30973
|
}
|
|
30544
|
-
const abs = resolveSafePathUnderCwd(
|
|
30974
|
+
const abs = resolveSafePathUnderCwd(cwd, filePath);
|
|
30545
30975
|
if (!abs) {
|
|
30546
30976
|
if (dbgFs) console.error(`[acp-fs] ${method} rejected path (outside cwd or empty)`);
|
|
30547
30977
|
respondJsonRpcError(id, -32602, "Invalid or disallowed path");
|
|
30548
30978
|
return;
|
|
30549
30979
|
}
|
|
30550
30980
|
try {
|
|
30551
|
-
let content =
|
|
30981
|
+
let content = readFileSync3(abs, "utf8");
|
|
30552
30982
|
const line2 = typeof params.line === "number" ? params.line : void 0;
|
|
30553
30983
|
const limit = typeof params.limit === "number" ? params.limit : void 0;
|
|
30554
30984
|
content = sliceLinesByRange(content, line2, limit);
|
|
@@ -30572,7 +31002,7 @@ async function createCursorAcpClient(options) {
|
|
|
30572
31002
|
`[acp-fs] ${method} path=${filePath.slice(0, 200)}${filePath.length > 200 ? "\u2026" : ""} newBytes=${newText.length}`
|
|
30573
31003
|
);
|
|
30574
31004
|
}
|
|
30575
|
-
const abs = resolveSafePathUnderCwd(
|
|
31005
|
+
const abs = resolveSafePathUnderCwd(cwd, filePath);
|
|
30576
31006
|
if (!abs) {
|
|
30577
31007
|
if (dbgFs) console.error(`[acp-fs] ${method} rejected path (outside cwd or empty): ${filePath.slice(0, 120)}`);
|
|
30578
31008
|
respondJsonRpcError(id, -32602, "Invalid or disallowed path");
|
|
@@ -30580,7 +31010,7 @@ async function createCursorAcpClient(options) {
|
|
|
30580
31010
|
}
|
|
30581
31011
|
let oldText = "";
|
|
30582
31012
|
try {
|
|
30583
|
-
oldText =
|
|
31013
|
+
oldText = readFileSync3(abs, "utf8");
|
|
30584
31014
|
} catch (e) {
|
|
30585
31015
|
if (e.code !== "ENOENT") {
|
|
30586
31016
|
respondJsonRpcError(id, -32e3, e instanceof Error ? e.message : String(e));
|
|
@@ -30588,13 +31018,13 @@ async function createCursorAcpClient(options) {
|
|
|
30588
31018
|
}
|
|
30589
31019
|
}
|
|
30590
31020
|
try {
|
|
30591
|
-
|
|
30592
|
-
|
|
31021
|
+
mkdirSync3(dirname2(abs), { recursive: true });
|
|
31022
|
+
writeFileSync3(abs, newText, "utf8");
|
|
30593
31023
|
} catch (e) {
|
|
30594
31024
|
respondJsonRpcError(id, -32e3, e instanceof Error ? e.message : String(e));
|
|
30595
31025
|
return;
|
|
30596
31026
|
}
|
|
30597
|
-
const displayPath = toDisplayPathRelativeToCwd(
|
|
31027
|
+
const displayPath = toDisplayPathRelativeToCwd(cwd, abs);
|
|
30598
31028
|
const patchContent = editSnippetToUnifiedDiff(displayPath, oldText, newText);
|
|
30599
31029
|
onFileChange?.({ path: displayPath, oldText, newText, patchContent });
|
|
30600
31030
|
respond(id, null);
|
|
@@ -30636,7 +31066,7 @@ async function createCursorAcpClient(options) {
|
|
|
30636
31066
|
clientInfo: { name: "buildautomaton-cli", version: "0.1.0" }
|
|
30637
31067
|
});
|
|
30638
31068
|
await send("authenticate", { methodId: "cursor_login" });
|
|
30639
|
-
const newResult = await send("session/new", { cwd
|
|
31069
|
+
const newResult = await send("session/new", { cwd, mcpServers: [] });
|
|
30640
31070
|
const sessionId = newResult?.sessionId ?? "";
|
|
30641
31071
|
if (!sessionId) throw new Error("Cursor ACP session/new did not return sessionId");
|
|
30642
31072
|
resolve15({
|
|
@@ -30687,8 +31117,20 @@ async function createCursorAcpClient(options) {
|
|
|
30687
31117
|
var AGENT_TYPE_DEFAULT_COMMANDS = {
|
|
30688
31118
|
"cursor-cli": ["agent", "acp"],
|
|
30689
31119
|
"codex-acp": [...DEFAULT_CODEX_ACP_COMMAND],
|
|
30690
|
-
|
|
31120
|
+
/** ACP stdio agent; `@anthropic-ai/claude-code` is the interactive CLI and does not speak ACP on stdout. */
|
|
31121
|
+
"claude-code": ["npx", "--yes", "@agentclientprotocol/claude-agent-acp"]
|
|
30691
31122
|
};
|
|
31123
|
+
var AGENT_TYPE_DISPLAY_NAMES = {
|
|
31124
|
+
"cursor-cli": "Cursor",
|
|
31125
|
+
"codex-acp": "Codex",
|
|
31126
|
+
"claude-code": "Claude Code"
|
|
31127
|
+
};
|
|
31128
|
+
function getAgentTypeDisplayName(agentType) {
|
|
31129
|
+
if (agentType == null || agentType === "") return "Unknown agent";
|
|
31130
|
+
const known = AGENT_TYPE_DISPLAY_NAMES[agentType];
|
|
31131
|
+
if (known) return known;
|
|
31132
|
+
return agentType.split(/[-_]/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(" ");
|
|
31133
|
+
}
|
|
30692
31134
|
function useCursorAcp(agentType, command) {
|
|
30693
31135
|
if (agentType === "cursor-cli") return true;
|
|
30694
31136
|
return command[0] === "agent" && command[1] === "acp";
|
|
@@ -30701,9 +31143,28 @@ function resolveAgentCommand(preferredAgentType) {
|
|
|
30701
31143
|
if (!preferredAgentType) return null;
|
|
30702
31144
|
const command = AGENT_TYPE_DEFAULT_COMMANDS[preferredAgentType];
|
|
30703
31145
|
if (!command?.length) return null;
|
|
30704
|
-
|
|
30705
|
-
|
|
30706
|
-
|
|
31146
|
+
if (useCursorAcp(preferredAgentType, command)) {
|
|
31147
|
+
return {
|
|
31148
|
+
command,
|
|
31149
|
+
label: preferredAgentType,
|
|
31150
|
+
createClient: createCursorAcpClient,
|
|
31151
|
+
spawnCommandForSession: (sessionMode) => buildCursorAcpSpawnCommand(command, sessionMode)
|
|
31152
|
+
};
|
|
31153
|
+
}
|
|
31154
|
+
if (useCodexAcp(preferredAgentType, command)) {
|
|
31155
|
+
return {
|
|
31156
|
+
command,
|
|
31157
|
+
label: preferredAgentType,
|
|
31158
|
+
createClient: createCodexAcpClient,
|
|
31159
|
+
spawnCommandForSession: (sessionMode) => buildCodexAcpSpawnCommand(command, sessionMode)
|
|
31160
|
+
};
|
|
31161
|
+
}
|
|
31162
|
+
return {
|
|
31163
|
+
command,
|
|
31164
|
+
label: preferredAgentType,
|
|
31165
|
+
createClient: createClaudeCodeAcpClient,
|
|
31166
|
+
spawnCommandForSession: (sessionMode) => buildClaudeCodeAcpSpawnCommand(command, sessionMode)
|
|
31167
|
+
};
|
|
30707
31168
|
}
|
|
30708
31169
|
|
|
30709
31170
|
// src/acp/session-file-change-path-kind.ts
|
|
@@ -30712,16 +31173,16 @@ import { existsSync, statSync } from "node:fs";
|
|
|
30712
31173
|
|
|
30713
31174
|
// src/git/get-git-repo-root-sync.ts
|
|
30714
31175
|
import { execFileSync } from "node:child_process";
|
|
30715
|
-
import * as
|
|
31176
|
+
import * as path7 from "node:path";
|
|
30716
31177
|
function getGitRepoRootSync(startDir) {
|
|
30717
31178
|
try {
|
|
30718
31179
|
const out = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
30719
|
-
cwd:
|
|
31180
|
+
cwd: path7.resolve(startDir),
|
|
30720
31181
|
encoding: "utf8",
|
|
30721
31182
|
stdio: ["ignore", "pipe", "ignore"],
|
|
30722
31183
|
maxBuffer: 1024 * 1024
|
|
30723
31184
|
}).trim();
|
|
30724
|
-
return out ?
|
|
31185
|
+
return out ? path7.resolve(out) : null;
|
|
30725
31186
|
} catch {
|
|
30726
31187
|
return null;
|
|
30727
31188
|
}
|
|
@@ -30729,65 +31190,65 @@ function getGitRepoRootSync(startDir) {
|
|
|
30729
31190
|
|
|
30730
31191
|
// src/acp/workspace-files.ts
|
|
30731
31192
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
30732
|
-
import { readFileSync as
|
|
30733
|
-
import * as
|
|
30734
|
-
function resolveWorkspaceFilePath(
|
|
31193
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
31194
|
+
import * as path8 from "node:path";
|
|
31195
|
+
function resolveWorkspaceFilePath(cwd, rawPath) {
|
|
30735
31196
|
const trimmed2 = rawPath.trim();
|
|
30736
31197
|
if (!trimmed2) return null;
|
|
30737
|
-
const normalizedCwd =
|
|
30738
|
-
let abs = resolveSafePathUnderCwd(
|
|
31198
|
+
const normalizedCwd = path8.resolve(cwd);
|
|
31199
|
+
let abs = resolveSafePathUnderCwd(cwd, trimmed2);
|
|
30739
31200
|
if (!abs) {
|
|
30740
|
-
const candidate =
|
|
30741
|
-
const gitRoot2 = getGitRepoRootSync(
|
|
31201
|
+
const candidate = path8.isAbsolute(trimmed2) ? path8.normalize(trimmed2) : path8.normalize(path8.resolve(normalizedCwd, trimmed2));
|
|
31202
|
+
const gitRoot2 = getGitRepoRootSync(cwd);
|
|
30742
31203
|
if (!gitRoot2) return null;
|
|
30743
|
-
const rel =
|
|
30744
|
-
if (rel.startsWith("..") ||
|
|
31204
|
+
const rel = path8.relative(gitRoot2, candidate);
|
|
31205
|
+
if (rel.startsWith("..") || path8.isAbsolute(rel)) return null;
|
|
30745
31206
|
abs = candidate;
|
|
30746
31207
|
}
|
|
30747
|
-
const gitRoot = getGitRepoRootSync(
|
|
31208
|
+
const gitRoot = getGitRepoRootSync(cwd);
|
|
30748
31209
|
if (gitRoot) {
|
|
30749
|
-
const relFromRoot =
|
|
30750
|
-
if (!relFromRoot.startsWith("..") && !
|
|
30751
|
-
return { abs, display: relFromRoot.split(
|
|
31210
|
+
const relFromRoot = path8.relative(gitRoot, abs);
|
|
31211
|
+
if (!relFromRoot.startsWith("..") && !path8.isAbsolute(relFromRoot)) {
|
|
31212
|
+
return { abs, display: relFromRoot.split(path8.sep).join("/") };
|
|
30752
31213
|
}
|
|
30753
31214
|
}
|
|
30754
|
-
return { abs, display: toDisplayPathRelativeToCwd(
|
|
31215
|
+
return { abs, display: toDisplayPathRelativeToCwd(cwd, abs) };
|
|
30755
31216
|
}
|
|
30756
|
-
function readUtf8WorkspaceFile(
|
|
31217
|
+
function readUtf8WorkspaceFile(cwd, displayPath) {
|
|
30757
31218
|
if (!displayPath || displayPath.includes("..")) return "";
|
|
30758
|
-
const gitRoot = getGitRepoRootSync(
|
|
31219
|
+
const gitRoot = getGitRepoRootSync(cwd);
|
|
30759
31220
|
if (gitRoot) {
|
|
30760
|
-
const abs2 =
|
|
30761
|
-
const rel =
|
|
30762
|
-
if (!rel.startsWith("..") && !
|
|
31221
|
+
const abs2 = path8.resolve(gitRoot, displayPath);
|
|
31222
|
+
const rel = path8.relative(gitRoot, abs2);
|
|
31223
|
+
if (!rel.startsWith("..") && !path8.isAbsolute(rel)) {
|
|
30763
31224
|
try {
|
|
30764
|
-
return
|
|
31225
|
+
return readFileSync4(abs2, "utf8");
|
|
30765
31226
|
} catch {
|
|
30766
31227
|
}
|
|
30767
31228
|
}
|
|
30768
31229
|
}
|
|
30769
|
-
const abs = resolveSafePathUnderCwd(
|
|
31230
|
+
const abs = resolveSafePathUnderCwd(cwd, displayPath);
|
|
30770
31231
|
if (!abs) return "";
|
|
30771
31232
|
try {
|
|
30772
|
-
return
|
|
31233
|
+
return readFileSync4(abs, "utf8");
|
|
30773
31234
|
} catch {
|
|
30774
31235
|
return "";
|
|
30775
31236
|
}
|
|
30776
31237
|
}
|
|
30777
|
-
function tryWorkspaceDisplayToAbs(
|
|
31238
|
+
function tryWorkspaceDisplayToAbs(cwd, displayPath) {
|
|
30778
31239
|
if (!displayPath || displayPath.includes("..")) return null;
|
|
30779
|
-
const gitRoot = getGitRepoRootSync(
|
|
31240
|
+
const gitRoot = getGitRepoRootSync(cwd);
|
|
30780
31241
|
if (gitRoot) {
|
|
30781
|
-
const abs =
|
|
30782
|
-
const rel =
|
|
30783
|
-
if (!rel.startsWith("..") && !
|
|
31242
|
+
const abs = path8.resolve(gitRoot, displayPath);
|
|
31243
|
+
const rel = path8.relative(gitRoot, abs);
|
|
31244
|
+
if (!rel.startsWith("..") && !path8.isAbsolute(rel)) return abs;
|
|
30784
31245
|
}
|
|
30785
|
-
return resolveSafePathUnderCwd(
|
|
31246
|
+
return resolveSafePathUnderCwd(cwd, displayPath);
|
|
30786
31247
|
}
|
|
30787
|
-
function readGitHeadBlob(
|
|
31248
|
+
function readGitHeadBlob(cwd, displayPath) {
|
|
30788
31249
|
if (!displayPath || displayPath.includes("..")) return "";
|
|
30789
|
-
const gitRoot = getGitRepoRootSync(
|
|
30790
|
-
const execCwd = gitRoot ??
|
|
31250
|
+
const gitRoot = getGitRepoRootSync(cwd);
|
|
31251
|
+
const execCwd = gitRoot ?? cwd;
|
|
30791
31252
|
try {
|
|
30792
31253
|
return execFileSync2("git", ["show", `HEAD:${displayPath}`], {
|
|
30793
31254
|
cwd: execCwd,
|
|
@@ -30800,9 +31261,9 @@ function readGitHeadBlob(cwd3, displayPath) {
|
|
|
30800
31261
|
}
|
|
30801
31262
|
|
|
30802
31263
|
// src/acp/session-file-change-path-kind.ts
|
|
30803
|
-
function gitHeadPathObjectType(
|
|
31264
|
+
function gitHeadPathObjectType(cwd, displayPath) {
|
|
30804
31265
|
if (!displayPath || displayPath.includes("..")) return null;
|
|
30805
|
-
const gitRoot = getGitRepoRootSync(
|
|
31266
|
+
const gitRoot = getGitRepoRootSync(cwd);
|
|
30806
31267
|
if (!gitRoot) return null;
|
|
30807
31268
|
try {
|
|
30808
31269
|
return execFileSync3("git", ["cat-file", "-t", `HEAD:${displayPath}`], {
|
|
@@ -30813,8 +31274,8 @@ function gitHeadPathObjectType(cwd3, displayPath) {
|
|
|
30813
31274
|
return null;
|
|
30814
31275
|
}
|
|
30815
31276
|
}
|
|
30816
|
-
function getSessionFileChangeDirectoryFlags(
|
|
30817
|
-
const abs = tryWorkspaceDisplayToAbs(
|
|
31277
|
+
function getSessionFileChangeDirectoryFlags(cwd, displayPath) {
|
|
31278
|
+
const abs = tryWorkspaceDisplayToAbs(cwd, displayPath);
|
|
30818
31279
|
if (abs && existsSync(abs)) {
|
|
30819
31280
|
try {
|
|
30820
31281
|
if (statSync(abs).isDirectory()) {
|
|
@@ -30825,7 +31286,7 @@ function getSessionFileChangeDirectoryFlags(cwd3, displayPath) {
|
|
|
30825
31286
|
return { isDirectory: false, directoryRemoved: false };
|
|
30826
31287
|
}
|
|
30827
31288
|
}
|
|
30828
|
-
if (gitHeadPathObjectType(
|
|
31289
|
+
if (gitHeadPathObjectType(cwd, displayPath) === "tree") {
|
|
30829
31290
|
return { isDirectory: true, directoryRemoved: true };
|
|
30830
31291
|
}
|
|
30831
31292
|
return { isDirectory: false, directoryRemoved: false };
|
|
@@ -30839,11 +31300,13 @@ function createBridgeOnFileChange(opts) {
|
|
|
30839
31300
|
const sessionId = routing.sessionId;
|
|
30840
31301
|
const send = getSendSessionUpdate();
|
|
30841
31302
|
if (!send || !runId || !sessionId) {
|
|
30842
|
-
log2(
|
|
31303
|
+
log2(
|
|
31304
|
+
`[Bridge service] File change not forwarded (${evt.path}): session or run not wired to the bridge.`
|
|
31305
|
+
);
|
|
30843
31306
|
return;
|
|
30844
31307
|
}
|
|
30845
|
-
const
|
|
30846
|
-
const dirFlags = getSessionFileChangeDirectoryFlags(
|
|
31308
|
+
const cwd = getBridgeWorkspaceDirectory();
|
|
31309
|
+
const dirFlags = getSessionFileChangeDirectoryFlags(cwd, evt.path);
|
|
30847
31310
|
try {
|
|
30848
31311
|
send({
|
|
30849
31312
|
type: "session_file_change",
|
|
@@ -30857,7 +31320,7 @@ function createBridgeOnFileChange(opts) {
|
|
|
30857
31320
|
directoryRemoved: dirFlags.directoryRemoved
|
|
30858
31321
|
});
|
|
30859
31322
|
} catch (err) {
|
|
30860
|
-
log2(`[Bridge service]
|
|
31323
|
+
log2(`[Bridge service] Session file change failed for ${evt.path}: ${errorMessage(err)}`);
|
|
30861
31324
|
}
|
|
30862
31325
|
};
|
|
30863
31326
|
}
|
|
@@ -30884,7 +31347,9 @@ function createBridgeOnRequest(opts) {
|
|
|
30884
31347
|
}
|
|
30885
31348
|
});
|
|
30886
31349
|
} catch (err) {
|
|
30887
|
-
log2(
|
|
31350
|
+
log2(
|
|
31351
|
+
`[Bridge service] Agent protocol request forward failed (${request.method}): ${errorMessage(err)}`
|
|
31352
|
+
);
|
|
30888
31353
|
}
|
|
30889
31354
|
};
|
|
30890
31355
|
}
|
|
@@ -30916,12 +31381,12 @@ function extractDiffPath(o) {
|
|
|
30916
31381
|
}
|
|
30917
31382
|
|
|
30918
31383
|
// src/acp/hooks/extract-acp-file-diffs-from-update/push-diff.ts
|
|
30919
|
-
function pushDiffIfComplete(o,
|
|
31384
|
+
function pushDiffIfComplete(o, cwd, out) {
|
|
30920
31385
|
const t = o.type;
|
|
30921
31386
|
if (typeof t !== "string" || t.toLowerCase() !== "diff") return;
|
|
30922
31387
|
const rawPath = extractDiffPath(o);
|
|
30923
31388
|
if (!rawPath) return;
|
|
30924
|
-
const resolved = resolveWorkspaceFilePath(
|
|
31389
|
+
const resolved = resolveWorkspaceFilePath(cwd, rawPath);
|
|
30925
31390
|
if (!resolved) return;
|
|
30926
31391
|
const oldText = readOptionalTextField(o.oldText ?? o.old_text ?? o.before ?? o.oldContent);
|
|
30927
31392
|
const newText = readOptionalTextField(o.newText ?? o.new_text ?? o.after ?? o.newContent);
|
|
@@ -30940,17 +31405,17 @@ var NEST_KEYS = [
|
|
|
30940
31405
|
"data",
|
|
30941
31406
|
"arguments"
|
|
30942
31407
|
];
|
|
30943
|
-
function walkValue(value,
|
|
31408
|
+
function walkValue(value, cwd, depth, out) {
|
|
30944
31409
|
if (depth > 12 || value == null) return;
|
|
30945
31410
|
if (Array.isArray(value)) {
|
|
30946
|
-
for (const x of value) walkValue(x,
|
|
31411
|
+
for (const x of value) walkValue(x, cwd, depth + 1, out);
|
|
30947
31412
|
return;
|
|
30948
31413
|
}
|
|
30949
31414
|
if (typeof value !== "object") return;
|
|
30950
31415
|
const o = value;
|
|
30951
|
-
pushDiffIfComplete(o,
|
|
31416
|
+
pushDiffIfComplete(o, cwd, out);
|
|
30952
31417
|
if (o.type === "content" && o.content != null && typeof o.content === "object") {
|
|
30953
|
-
walkValue(o.content,
|
|
31418
|
+
walkValue(o.content, cwd, depth + 1, out);
|
|
30954
31419
|
}
|
|
30955
31420
|
for (const k of NEST_KEYS) {
|
|
30956
31421
|
if (o.type === "content" && k === "content") continue;
|
|
@@ -30958,23 +31423,23 @@ function walkValue(value, cwd3, depth, out) {
|
|
|
30958
31423
|
if (v == null) continue;
|
|
30959
31424
|
if (k === "arguments" && typeof v === "string") {
|
|
30960
31425
|
try {
|
|
30961
|
-
walkValue(JSON.parse(v),
|
|
31426
|
+
walkValue(JSON.parse(v), cwd, depth + 1, out);
|
|
30962
31427
|
} catch {
|
|
30963
31428
|
}
|
|
30964
31429
|
continue;
|
|
30965
31430
|
}
|
|
30966
|
-
walkValue(v,
|
|
31431
|
+
walkValue(v, cwd, depth + 1, out);
|
|
30967
31432
|
}
|
|
30968
31433
|
}
|
|
30969
31434
|
|
|
30970
31435
|
// src/acp/hooks/extract-acp-file-diffs-from-update/extract.ts
|
|
30971
|
-
function extractAcpFileDiffsFromUpdate(update,
|
|
31436
|
+
function extractAcpFileDiffsFromUpdate(update, cwd) {
|
|
30972
31437
|
if (!update || typeof update !== "object") return [];
|
|
30973
31438
|
const u = update;
|
|
30974
31439
|
const out = [];
|
|
30975
31440
|
const content = u.content;
|
|
30976
31441
|
if (Array.isArray(content)) {
|
|
30977
|
-
for (const x of content) walkValue(x,
|
|
31442
|
+
for (const x of content) walkValue(x, cwd, 0, out);
|
|
30978
31443
|
}
|
|
30979
31444
|
const byPath = /* @__PURE__ */ new Map();
|
|
30980
31445
|
for (const d of out) byPath.set(d.path, d);
|
|
@@ -30982,18 +31447,18 @@ function extractAcpFileDiffsFromUpdate(update, cwd3) {
|
|
|
30982
31447
|
}
|
|
30983
31448
|
|
|
30984
31449
|
// src/acp/hooks/extract-tool-target-paths.ts
|
|
30985
|
-
function addPath(
|
|
31450
|
+
function addPath(cwd, raw, out) {
|
|
30986
31451
|
if (typeof raw !== "string") return;
|
|
30987
31452
|
const trimmed2 = raw.trim();
|
|
30988
31453
|
if (!trimmed2) return;
|
|
30989
|
-
const resolved = resolveWorkspaceFilePath(
|
|
31454
|
+
const resolved = resolveWorkspaceFilePath(cwd, trimmed2);
|
|
30990
31455
|
if (!resolved) return;
|
|
30991
31456
|
out.add(resolved.display);
|
|
30992
31457
|
}
|
|
30993
|
-
function walkLocations(
|
|
31458
|
+
function walkLocations(cwd, loc, out) {
|
|
30994
31459
|
if (!Array.isArray(loc)) return;
|
|
30995
31460
|
for (const item of loc) {
|
|
30996
|
-
if (item && typeof item === "object") addPath(
|
|
31461
|
+
if (item && typeof item === "object") addPath(cwd, item.path, out);
|
|
30997
31462
|
}
|
|
30998
31463
|
}
|
|
30999
31464
|
var PATH_KEYS = [
|
|
@@ -31006,56 +31471,56 @@ var PATH_KEYS = [
|
|
|
31006
31471
|
"file_path",
|
|
31007
31472
|
"target_file"
|
|
31008
31473
|
];
|
|
31009
|
-
function collectFromObject(
|
|
31474
|
+
function collectFromObject(cwd, obj, out, depth) {
|
|
31010
31475
|
if (depth > 10) return;
|
|
31011
31476
|
for (const k of PATH_KEYS) {
|
|
31012
|
-
if (k in obj) addPath(
|
|
31477
|
+
if (k in obj) addPath(cwd, obj[k], out);
|
|
31013
31478
|
}
|
|
31014
31479
|
for (const v of Object.values(obj)) {
|
|
31015
|
-
if (v != null && typeof v === "object") collectUnknown(
|
|
31480
|
+
if (v != null && typeof v === "object") collectUnknown(cwd, v, out, depth + 1);
|
|
31016
31481
|
}
|
|
31017
31482
|
}
|
|
31018
|
-
function collectUnknown(
|
|
31483
|
+
function collectUnknown(cwd, v, out, depth) {
|
|
31019
31484
|
if (depth > 10 || v == null) return;
|
|
31020
31485
|
if (Array.isArray(v)) {
|
|
31021
|
-
for (const x of v) collectUnknown(
|
|
31486
|
+
for (const x of v) collectUnknown(cwd, x, out, depth + 1);
|
|
31022
31487
|
return;
|
|
31023
31488
|
}
|
|
31024
|
-
if (typeof v === "object") collectFromObject(
|
|
31489
|
+
if (typeof v === "object") collectFromObject(cwd, v, out, depth);
|
|
31025
31490
|
}
|
|
31026
|
-
function walkContentArray(
|
|
31491
|
+
function walkContentArray(cwd, content, out) {
|
|
31027
31492
|
if (!Array.isArray(content)) return;
|
|
31028
31493
|
for (const item of content) {
|
|
31029
31494
|
if (!item || typeof item !== "object") continue;
|
|
31030
31495
|
const o = item;
|
|
31031
31496
|
if (typeof o.type === "string" && o.type.toLowerCase() === "diff") {
|
|
31032
|
-
for (const k of PATH_KEYS) if (k in o) addPath(
|
|
31497
|
+
for (const k of PATH_KEYS) if (k in o) addPath(cwd, o[k], out);
|
|
31033
31498
|
}
|
|
31034
31499
|
if (o.type === "content" && o.content != null && typeof o.content === "object") {
|
|
31035
31500
|
const inner = o.content;
|
|
31036
31501
|
if (typeof inner.type === "string" && inner.type.toLowerCase() === "diff") {
|
|
31037
|
-
for (const k of PATH_KEYS) if (k in inner) addPath(
|
|
31502
|
+
for (const k of PATH_KEYS) if (k in inner) addPath(cwd, inner[k], out);
|
|
31038
31503
|
}
|
|
31039
31504
|
}
|
|
31040
31505
|
}
|
|
31041
31506
|
}
|
|
31042
|
-
function extractToolTargetDisplayPaths(update,
|
|
31507
|
+
function extractToolTargetDisplayPaths(update, cwd) {
|
|
31043
31508
|
const out = /* @__PURE__ */ new Set();
|
|
31044
31509
|
if (!update || typeof update !== "object") return [];
|
|
31045
31510
|
const u = update;
|
|
31046
|
-
walkLocations(
|
|
31047
|
-
walkLocations(
|
|
31048
|
-
walkLocations(
|
|
31511
|
+
walkLocations(cwd, u.locations, out);
|
|
31512
|
+
walkLocations(cwd, u.fileLocations, out);
|
|
31513
|
+
walkLocations(cwd, u.file_locations, out);
|
|
31049
31514
|
const tc = u.toolCall ?? u.tool_call;
|
|
31050
31515
|
if (tc && typeof tc.arguments === "string") {
|
|
31051
31516
|
try {
|
|
31052
31517
|
const parsed = JSON.parse(tc.arguments);
|
|
31053
|
-
collectUnknown(
|
|
31518
|
+
collectUnknown(cwd, parsed, out, 0);
|
|
31054
31519
|
} catch {
|
|
31055
31520
|
}
|
|
31056
31521
|
}
|
|
31057
|
-
walkContentArray(
|
|
31058
|
-
collectFromObject(
|
|
31522
|
+
walkContentArray(cwd, u.content, out);
|
|
31523
|
+
collectFromObject(cwd, u, out, 0);
|
|
31059
31524
|
return [...out];
|
|
31060
31525
|
}
|
|
31061
31526
|
|
|
@@ -31121,7 +31586,7 @@ var PathSnapshotTracker = class {
|
|
|
31121
31586
|
this.beforeByToolKey.delete(toolKey);
|
|
31122
31587
|
this.accumulatedPathsByToolKey.delete(toolKey);
|
|
31123
31588
|
}
|
|
31124
|
-
captureBeforeFromDisk(toolKey, paths,
|
|
31589
|
+
captureBeforeFromDisk(toolKey, paths, cwd) {
|
|
31125
31590
|
if (paths.length === 0) return;
|
|
31126
31591
|
let m = this.beforeByToolKey.get(toolKey);
|
|
31127
31592
|
if (!m) {
|
|
@@ -31130,10 +31595,10 @@ var PathSnapshotTracker = class {
|
|
|
31130
31595
|
}
|
|
31131
31596
|
for (const p of paths) {
|
|
31132
31597
|
if (m.has(p)) continue;
|
|
31133
|
-
m.set(p, readUtf8WorkspaceFile(
|
|
31598
|
+
m.set(p, readUtf8WorkspaceFile(cwd, p));
|
|
31134
31599
|
}
|
|
31135
31600
|
}
|
|
31136
|
-
ensureBeforeFromHeadForMissing(toolKey, paths,
|
|
31601
|
+
ensureBeforeFromHeadForMissing(toolKey, paths, cwd) {
|
|
31137
31602
|
let m = this.beforeByToolKey.get(toolKey);
|
|
31138
31603
|
if (!m) {
|
|
31139
31604
|
m = /* @__PURE__ */ new Map();
|
|
@@ -31141,10 +31606,10 @@ var PathSnapshotTracker = class {
|
|
|
31141
31606
|
}
|
|
31142
31607
|
for (const p of paths) {
|
|
31143
31608
|
if (m.has(p)) continue;
|
|
31144
|
-
m.set(p, readGitHeadBlob(
|
|
31609
|
+
m.set(p, readGitHeadBlob(cwd, p));
|
|
31145
31610
|
}
|
|
31146
31611
|
}
|
|
31147
|
-
flushPathSnapshots(toolKey,
|
|
31612
|
+
flushPathSnapshots(toolKey, cwd, sentPaths, send, runId, sessionId, log2) {
|
|
31148
31613
|
const t = this.debouncers.get(toolKey);
|
|
31149
31614
|
if (t) clearTimeout(t);
|
|
31150
31615
|
this.debouncers.delete(toolKey);
|
|
@@ -31153,10 +31618,10 @@ var PathSnapshotTracker = class {
|
|
|
31153
31618
|
this.beforeByToolKey.delete(toolKey);
|
|
31154
31619
|
if (!send || !runId || !sessionId) return;
|
|
31155
31620
|
for (const [displayPath, oldText] of beforeMap) {
|
|
31156
|
-
const newText = readUtf8WorkspaceFile(
|
|
31621
|
+
const newText = readUtf8WorkspaceFile(cwd, displayPath);
|
|
31157
31622
|
if (oldText === newText) continue;
|
|
31158
31623
|
const patchContent = editSnippetToUnifiedDiff(displayPath, oldText, newText);
|
|
31159
|
-
const dirFlags = getSessionFileChangeDirectoryFlags(
|
|
31624
|
+
const dirFlags = getSessionFileChangeDirectoryFlags(cwd, displayPath);
|
|
31160
31625
|
try {
|
|
31161
31626
|
send({
|
|
31162
31627
|
type: "session_file_change",
|
|
@@ -31171,35 +31636,35 @@ var PathSnapshotTracker = class {
|
|
|
31171
31636
|
});
|
|
31172
31637
|
sentPaths.add(displayPath);
|
|
31173
31638
|
} catch (err) {
|
|
31174
|
-
log2(`[Bridge service]
|
|
31639
|
+
log2(`[Bridge service] Session file change failed for ${displayPath}: ${errorMessage(err)}`);
|
|
31175
31640
|
}
|
|
31176
31641
|
}
|
|
31177
31642
|
}
|
|
31178
|
-
scheduleDebouncedFlush(toolKey,
|
|
31643
|
+
scheduleDebouncedFlush(toolKey, cwd, sentPaths, send, runId, sessionId, log2) {
|
|
31179
31644
|
const prev = this.debouncers.get(toolKey);
|
|
31180
31645
|
if (prev) clearTimeout(prev);
|
|
31181
31646
|
this.debouncers.set(
|
|
31182
31647
|
toolKey,
|
|
31183
31648
|
setTimeout(() => {
|
|
31184
31649
|
this.debouncers.delete(toolKey);
|
|
31185
|
-
this.flushPathSnapshots(toolKey,
|
|
31650
|
+
this.flushPathSnapshots(toolKey, cwd, sentPaths, send, runId, sessionId, log2);
|
|
31186
31651
|
}, PATH_SNAPSHOT_DEBOUNCE_MS)
|
|
31187
31652
|
);
|
|
31188
31653
|
}
|
|
31189
|
-
handleToolCallLifecycle(updateKind, toolKey, toolPaths, status,
|
|
31654
|
+
handleToolCallLifecycle(updateKind, toolKey, toolPaths, status, cwd, sentPaths, send, runId, sessionId, log2) {
|
|
31190
31655
|
if (updateKind === "tool_call") {
|
|
31191
31656
|
this.resetToolSnapshots(toolKey);
|
|
31192
31657
|
}
|
|
31193
31658
|
if (updateKind === "tool_call") {
|
|
31194
|
-
this.captureBeforeFromDisk(toolKey, toolPaths,
|
|
31659
|
+
this.captureBeforeFromDisk(toolKey, toolPaths, cwd);
|
|
31195
31660
|
} else if (updateKind === "tool_call_update") {
|
|
31196
31661
|
if (isCompletedToolStatus(status)) {
|
|
31197
|
-
this.ensureBeforeFromHeadForMissing(toolKey, toolPaths,
|
|
31198
|
-
this.flushPathSnapshots(toolKey,
|
|
31662
|
+
this.ensureBeforeFromHeadForMissing(toolKey, toolPaths, cwd);
|
|
31663
|
+
this.flushPathSnapshots(toolKey, cwd, sentPaths, send, runId, sessionId, log2);
|
|
31199
31664
|
} else {
|
|
31200
|
-
this.captureBeforeFromDisk(toolKey, toolPaths,
|
|
31665
|
+
this.captureBeforeFromDisk(toolKey, toolPaths, cwd);
|
|
31201
31666
|
if (this.beforeByToolKey.has(toolKey)) {
|
|
31202
|
-
this.scheduleDebouncedFlush(toolKey,
|
|
31667
|
+
this.scheduleDebouncedFlush(toolKey, cwd, sentPaths, send, runId, sessionId, log2);
|
|
31203
31668
|
}
|
|
31204
31669
|
}
|
|
31205
31670
|
}
|
|
@@ -31207,11 +31672,11 @@ var PathSnapshotTracker = class {
|
|
|
31207
31672
|
};
|
|
31208
31673
|
|
|
31209
31674
|
// src/acp/hooks/bridge-on-session-update/send-structured-file-changes.ts
|
|
31210
|
-
function sendExtractedDiffsAsSessionFileChanges(diffs, send,
|
|
31675
|
+
function sendExtractedDiffsAsSessionFileChanges(diffs, send, cwd, sessionId, runId, sentPaths, log2) {
|
|
31211
31676
|
for (const d of diffs) {
|
|
31212
31677
|
try {
|
|
31213
31678
|
const patchContent = editSnippetToUnifiedDiff(d.path, d.oldText, d.newText);
|
|
31214
|
-
const dirFlags = getSessionFileChangeDirectoryFlags(
|
|
31679
|
+
const dirFlags = getSessionFileChangeDirectoryFlags(cwd, d.path);
|
|
31215
31680
|
send({
|
|
31216
31681
|
type: "session_file_change",
|
|
31217
31682
|
sessionId,
|
|
@@ -31225,19 +31690,19 @@ function sendExtractedDiffsAsSessionFileChanges(diffs, send, cwd3, sessionId, ru
|
|
|
31225
31690
|
});
|
|
31226
31691
|
sentPaths.add(d.path);
|
|
31227
31692
|
} catch (err) {
|
|
31228
|
-
log2(`[Bridge service]
|
|
31693
|
+
log2(`[Bridge service] Session file change failed for ${d.path}: ${errorMessage(err)}`);
|
|
31229
31694
|
}
|
|
31230
31695
|
}
|
|
31231
31696
|
}
|
|
31232
|
-
function sendGitHeadVsWorkspaceForToolPaths(mergedPaths, sentPaths, send,
|
|
31697
|
+
function sendGitHeadVsWorkspaceForToolPaths(mergedPaths, sentPaths, send, cwd, sessionId, runId, log2) {
|
|
31233
31698
|
for (const displayPath of mergedPaths) {
|
|
31234
31699
|
if (sentPaths.has(displayPath)) continue;
|
|
31235
|
-
const oldText = readGitHeadBlob(
|
|
31236
|
-
const newText = readUtf8WorkspaceFile(
|
|
31700
|
+
const oldText = readGitHeadBlob(cwd, displayPath);
|
|
31701
|
+
const newText = readUtf8WorkspaceFile(cwd, displayPath);
|
|
31237
31702
|
if (oldText === newText) continue;
|
|
31238
31703
|
try {
|
|
31239
31704
|
const patchContent = editSnippetToUnifiedDiff(displayPath, oldText, newText);
|
|
31240
|
-
const dirFlags = getSessionFileChangeDirectoryFlags(
|
|
31705
|
+
const dirFlags = getSessionFileChangeDirectoryFlags(cwd, displayPath);
|
|
31241
31706
|
send({
|
|
31242
31707
|
type: "session_file_change",
|
|
31243
31708
|
sessionId,
|
|
@@ -31251,7 +31716,7 @@ function sendGitHeadVsWorkspaceForToolPaths(mergedPaths, sentPaths, send, cwd3,
|
|
|
31251
31716
|
});
|
|
31252
31717
|
sentPaths.add(displayPath);
|
|
31253
31718
|
} catch (err) {
|
|
31254
|
-
log2(`[Bridge service]
|
|
31719
|
+
log2(`[Bridge service] Session file change failed for ${displayPath}: ${errorMessage(err)}`);
|
|
31255
31720
|
}
|
|
31256
31721
|
}
|
|
31257
31722
|
}
|
|
@@ -31264,7 +31729,7 @@ function createBridgeOnSessionUpdate(opts) {
|
|
|
31264
31729
|
const runId = routing.runId;
|
|
31265
31730
|
const sessionId = routing.sessionId;
|
|
31266
31731
|
pathTracker.onRunIdChanged(runId);
|
|
31267
|
-
const
|
|
31732
|
+
const cwd = getBridgeWorkspaceDirectory();
|
|
31268
31733
|
const send = getSendSessionUpdate();
|
|
31269
31734
|
const sentFileChangePaths = /* @__PURE__ */ new Set();
|
|
31270
31735
|
const p = params;
|
|
@@ -31272,7 +31737,7 @@ function createBridgeOnSessionUpdate(opts) {
|
|
|
31272
31737
|
const isCompletedToolCallUpdate = updateKind === "tool_call_update" && isCompletedToolStatus(p.status);
|
|
31273
31738
|
const toolName = p.toolCall?.name ?? p.tool_call?.name ?? "";
|
|
31274
31739
|
const isToolUpdate = updateKind === "tool_call" || updateKind === "tool_call_update" || typeof toolName === "string" && toolName.length > 0;
|
|
31275
|
-
const toolPaths = isToolUpdate ? extractToolTargetDisplayPaths(params,
|
|
31740
|
+
const toolPaths = isToolUpdate ? extractToolTargetDisplayPaths(params, cwd) : [];
|
|
31276
31741
|
const toolKey = isToolUpdate ? pathTracker.resolveToolKey(params, updateKind) : "";
|
|
31277
31742
|
if (updateKind === "tool_call") {
|
|
31278
31743
|
pathTracker.resetToolSnapshots(toolKey);
|
|
@@ -31287,7 +31752,7 @@ function createBridgeOnSessionUpdate(opts) {
|
|
|
31287
31752
|
toolKey,
|
|
31288
31753
|
toolPaths,
|
|
31289
31754
|
p.status,
|
|
31290
|
-
|
|
31755
|
+
cwd,
|
|
31291
31756
|
sentFileChangePaths,
|
|
31292
31757
|
deliver,
|
|
31293
31758
|
runId ?? "",
|
|
@@ -31295,18 +31760,18 @@ function createBridgeOnSessionUpdate(opts) {
|
|
|
31295
31760
|
log2
|
|
31296
31761
|
);
|
|
31297
31762
|
}
|
|
31298
|
-
const diffs = extractAcpFileDiffsFromUpdate(params,
|
|
31763
|
+
const diffs = extractAcpFileDiffsFromUpdate(params, cwd);
|
|
31299
31764
|
if (diffs.length > 0 && send && runId && sessionId) {
|
|
31300
|
-
sendExtractedDiffsAsSessionFileChanges(diffs, send,
|
|
31765
|
+
sendExtractedDiffsAsSessionFileChanges(diffs, send, cwd, sessionId, runId, sentFileChangePaths, log2);
|
|
31301
31766
|
} else if (diffs.length > 0) {
|
|
31302
31767
|
log2(
|
|
31303
|
-
`[Bridge service]
|
|
31768
|
+
`[Bridge service] Agent file diff(s) not forwarded (${diffs.length}): session or run not wired to the bridge.`
|
|
31304
31769
|
);
|
|
31305
31770
|
}
|
|
31306
31771
|
if (isCompletedToolCallUpdate && send && runId && sessionId) {
|
|
31307
31772
|
const acc = pathTracker.accumulatedPathsByToolKey.get(toolKey);
|
|
31308
31773
|
const merged = [.../* @__PURE__ */ new Set([...acc ? [...acc] : [], ...toolPaths])];
|
|
31309
|
-
sendGitHeadVsWorkspaceForToolPaths(merged, sentFileChangePaths, send,
|
|
31774
|
+
sendGitHeadVsWorkspaceForToolPaths(merged, sentFileChangePaths, send, cwd, sessionId, runId, log2);
|
|
31310
31775
|
pathTracker.accumulatedPathsByToolKey.delete(toolKey);
|
|
31311
31776
|
}
|
|
31312
31777
|
if (runId && send) {
|
|
@@ -31319,7 +31784,9 @@ function createBridgeOnSessionUpdate(opts) {
|
|
|
31319
31784
|
payload: params
|
|
31320
31785
|
});
|
|
31321
31786
|
} catch (err) {
|
|
31322
|
-
log2(
|
|
31787
|
+
log2(
|
|
31788
|
+
`[Bridge service] Session update send failed (${formatSessionUpdateKindForLog(updateKind)}): ${errorMessage(err)}`
|
|
31789
|
+
);
|
|
31323
31790
|
}
|
|
31324
31791
|
}
|
|
31325
31792
|
};
|
|
@@ -31336,11 +31803,11 @@ function buildAcpSessionBridgeHooks(opts) {
|
|
|
31336
31803
|
|
|
31337
31804
|
// src/acp/ensure-acp-client.ts
|
|
31338
31805
|
async function ensureAcpClient(options) {
|
|
31339
|
-
const { state, preferredAgentType, mode, cwd
|
|
31340
|
-
const targetCwd =
|
|
31341
|
-
|
|
31806
|
+
const { state, preferredAgentType, mode, cwd, routing, sendSessionUpdate, sendRequest, log: log2 } = options;
|
|
31807
|
+
const targetCwd = path9.resolve(
|
|
31808
|
+
cwd != null && String(cwd).trim() !== "" ? String(cwd).trim() : getBridgeWorkspaceDirectory()
|
|
31342
31809
|
);
|
|
31343
|
-
if (state.acpHandle && state.lastAcpCwd != null &&
|
|
31810
|
+
if (state.acpHandle && state.lastAcpCwd != null && path9.resolve(state.lastAcpCwd) !== path9.resolve(targetCwd)) {
|
|
31344
31811
|
try {
|
|
31345
31812
|
state.acpHandle.disconnect();
|
|
31346
31813
|
} catch {
|
|
@@ -31352,12 +31819,13 @@ async function ensureAcpClient(options) {
|
|
|
31352
31819
|
const resolved = resolveAgentCommand(preferredAgentType);
|
|
31353
31820
|
if (!resolved) {
|
|
31354
31821
|
log2(
|
|
31355
|
-
`[
|
|
31822
|
+
`[Agent] No local agent type (${preferredAgentType === null ? "none" : `"${preferredAgentType}"`}). Send agent type on prompts or agent configuration after identify.`
|
|
31356
31823
|
);
|
|
31357
31824
|
state.lastAcpStartError = "No agent type: ensure the app sends agentType on prompts or agent_config for this bridge.";
|
|
31358
31825
|
return null;
|
|
31359
31826
|
}
|
|
31360
|
-
const
|
|
31827
|
+
const fullCmd = resolved.spawnCommandForSession(mode);
|
|
31828
|
+
const agentKey = `${resolved.label}::${fullCmd.join("\0")}`;
|
|
31361
31829
|
if (state.acpHandle && state.acpAgentKey !== agentKey) {
|
|
31362
31830
|
try {
|
|
31363
31831
|
state.acpHandle.disconnect();
|
|
@@ -31371,21 +31839,19 @@ async function ensureAcpClient(options) {
|
|
|
31371
31839
|
if (!state.acpStartPromise) {
|
|
31372
31840
|
let statOk = false;
|
|
31373
31841
|
try {
|
|
31374
|
-
const st =
|
|
31842
|
+
const st = fs4.statSync(targetCwd);
|
|
31375
31843
|
statOk = st.isDirectory();
|
|
31376
31844
|
if (!statOk) {
|
|
31377
31845
|
state.lastAcpStartError = `Agent cwd is not a directory: ${targetCwd}`;
|
|
31378
|
-
log2(`[
|
|
31846
|
+
log2(`[Agent] ${state.lastAcpStartError}`);
|
|
31379
31847
|
}
|
|
31380
31848
|
} catch {
|
|
31381
31849
|
state.lastAcpStartError = `Agent cwd missing or inaccessible: ${targetCwd}`;
|
|
31382
|
-
log2(`[
|
|
31850
|
+
log2(`[Agent] ${state.lastAcpStartError}`);
|
|
31383
31851
|
}
|
|
31384
31852
|
if (!statOk) {
|
|
31385
31853
|
return null;
|
|
31386
31854
|
}
|
|
31387
|
-
const modeFlag = mode && ["ask", "plan"].includes(mode) ? ["--mode", mode] : [];
|
|
31388
|
-
const fullCmd = [...resolved.command, ...modeFlag];
|
|
31389
31855
|
const hooks = buildAcpSessionBridgeHooks({
|
|
31390
31856
|
routing,
|
|
31391
31857
|
getSendSessionUpdate: () => sendSessionUpdate,
|
|
@@ -31393,8 +31859,15 @@ async function ensureAcpClient(options) {
|
|
|
31393
31859
|
log: log2
|
|
31394
31860
|
});
|
|
31395
31861
|
state.acpStartPromise = resolved.createClient({
|
|
31396
|
-
command:
|
|
31862
|
+
command: resolved.command,
|
|
31863
|
+
sessionMode: mode,
|
|
31397
31864
|
cwd: targetCwd,
|
|
31865
|
+
onAgentSubprocessExit: () => {
|
|
31866
|
+
state.acpHandle = null;
|
|
31867
|
+
state.acpStartPromise = null;
|
|
31868
|
+
state.acpAgentKey = null;
|
|
31869
|
+
state.lastAcpStartError = "Agent subprocess exited";
|
|
31870
|
+
},
|
|
31398
31871
|
...hooks
|
|
31399
31872
|
}).then((h) => {
|
|
31400
31873
|
state.lastAcpStartError = null;
|
|
@@ -31404,7 +31877,7 @@ async function ensureAcpClient(options) {
|
|
|
31404
31877
|
return h;
|
|
31405
31878
|
}).catch((err) => {
|
|
31406
31879
|
state.lastAcpStartError = errorMessage(err);
|
|
31407
|
-
log2(`[
|
|
31880
|
+
log2(`[Agent] Failed to start: ${state.lastAcpStartError}`);
|
|
31408
31881
|
state.acpStartPromise = null;
|
|
31409
31882
|
state.acpAgentKey = null;
|
|
31410
31883
|
return null;
|
|
@@ -31432,6 +31905,14 @@ async function createAcpManager(options) {
|
|
|
31432
31905
|
backendFallbackAgentType = agentType;
|
|
31433
31906
|
}
|
|
31434
31907
|
}
|
|
31908
|
+
function logPromptReceivedFromBridge(opts) {
|
|
31909
|
+
const { agentType, mode } = opts;
|
|
31910
|
+
const preferredForPrompt = agentType ?? backendFallbackAgentType ?? null;
|
|
31911
|
+
const modeLabel = typeof mode === "string" && mode.trim() !== "" ? mode.trim() : "not set";
|
|
31912
|
+
log2(
|
|
31913
|
+
`[Agent] Prompt received (${getAgentTypeDisplayName(preferredForPrompt)}, mode: ${modeLabel})`
|
|
31914
|
+
);
|
|
31915
|
+
}
|
|
31435
31916
|
function handlePrompt(opts) {
|
|
31436
31917
|
const {
|
|
31437
31918
|
promptText,
|
|
@@ -31440,10 +31921,9 @@ async function createAcpManager(options) {
|
|
|
31440
31921
|
runId,
|
|
31441
31922
|
mode,
|
|
31442
31923
|
agentType,
|
|
31443
|
-
cwd
|
|
31924
|
+
cwd,
|
|
31444
31925
|
sendResult,
|
|
31445
|
-
sendSessionUpdate
|
|
31446
|
-
collectSessionDiffAfterTurn
|
|
31926
|
+
sendSessionUpdate
|
|
31447
31927
|
} = opts;
|
|
31448
31928
|
const preferredForPrompt = agentType ?? backendFallbackAgentType ?? null;
|
|
31449
31929
|
pendingCancelRunId = void 0;
|
|
@@ -31455,7 +31935,7 @@ async function createAcpManager(options) {
|
|
|
31455
31935
|
state,
|
|
31456
31936
|
preferredAgentType: preferredForPrompt,
|
|
31457
31937
|
mode,
|
|
31458
|
-
cwd
|
|
31938
|
+
cwd,
|
|
31459
31939
|
routing: promptRouting,
|
|
31460
31940
|
sendSessionUpdate,
|
|
31461
31941
|
sendRequest: sendSessionUpdate,
|
|
@@ -31496,9 +31976,9 @@ async function createAcpManager(options) {
|
|
|
31496
31976
|
promptId,
|
|
31497
31977
|
sessionId,
|
|
31498
31978
|
runId,
|
|
31979
|
+
agentCwd: cwd,
|
|
31499
31980
|
sendResult,
|
|
31500
31981
|
sendSessionUpdate,
|
|
31501
|
-
collectSessionDiffAfterTurn,
|
|
31502
31982
|
log: log2
|
|
31503
31983
|
});
|
|
31504
31984
|
}
|
|
@@ -31513,14 +31993,16 @@ async function createAcpManager(options) {
|
|
|
31513
31993
|
if (promptRouting.runId !== runId) return false;
|
|
31514
31994
|
const handle = state.acpHandle;
|
|
31515
31995
|
if (handle?.cancel) {
|
|
31996
|
+
log2("[Agent] Stop requested");
|
|
31516
31997
|
try {
|
|
31517
31998
|
await handle.cancel();
|
|
31518
31999
|
return true;
|
|
31519
32000
|
} catch (err) {
|
|
31520
|
-
log2(`[
|
|
32001
|
+
log2(`[Agent] Cancel failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
31521
32002
|
return false;
|
|
31522
32003
|
}
|
|
31523
32004
|
}
|
|
32005
|
+
log2("[Agent] Stop requested (agent still starting)");
|
|
31524
32006
|
pendingCancelRunId = runId;
|
|
31525
32007
|
return true;
|
|
31526
32008
|
}
|
|
@@ -31533,7 +32015,14 @@ async function createAcpManager(options) {
|
|
|
31533
32015
|
state.acpStartPromise = null;
|
|
31534
32016
|
state.acpAgentKey = null;
|
|
31535
32017
|
}
|
|
31536
|
-
return {
|
|
32018
|
+
return {
|
|
32019
|
+
setPreferredAgentType,
|
|
32020
|
+
logPromptReceivedFromBridge,
|
|
32021
|
+
handlePrompt,
|
|
32022
|
+
cancelRun,
|
|
32023
|
+
resolveRequest,
|
|
32024
|
+
disconnect
|
|
32025
|
+
};
|
|
31537
32026
|
}
|
|
31538
32027
|
|
|
31539
32028
|
// src/bridge/routing/handlers/auth-token.ts
|
|
@@ -31554,7 +32043,9 @@ var handleBridgeIdentified = (msg, deps) => {
|
|
|
31554
32043
|
try {
|
|
31555
32044
|
await deps.reportAutoDetectedAgents?.();
|
|
31556
32045
|
} catch (e) {
|
|
31557
|
-
deps.log(
|
|
32046
|
+
deps.log(
|
|
32047
|
+
`[Bridge service] Auto-detect agents failed: ${e instanceof Error ? e.message : String(e)}`
|
|
32048
|
+
);
|
|
31558
32049
|
}
|
|
31559
32050
|
})();
|
|
31560
32051
|
});
|
|
@@ -31562,7 +32053,9 @@ var handleBridgeIdentified = (msg, deps) => {
|
|
|
31562
32053
|
try {
|
|
31563
32054
|
deps.sendLocalSkillsReport?.();
|
|
31564
32055
|
} catch (e) {
|
|
31565
|
-
deps.log(
|
|
32056
|
+
deps.log(
|
|
32057
|
+
`[Bridge service] Local skills report failed: ${e instanceof Error ? e.message : String(e)}`
|
|
32058
|
+
);
|
|
31566
32059
|
}
|
|
31567
32060
|
});
|
|
31568
32061
|
};
|
|
@@ -31579,12 +32072,12 @@ var handleAgentConfigMessage = (msg, deps) => {
|
|
|
31579
32072
|
};
|
|
31580
32073
|
|
|
31581
32074
|
// src/acp/from-bridge/handle-bridge-prompt.ts
|
|
31582
|
-
import * as
|
|
32075
|
+
import * as path11 from "node:path";
|
|
31583
32076
|
import { execFile as execFile3 } from "node:child_process";
|
|
31584
32077
|
import { promisify as promisify3 } from "node:util";
|
|
31585
32078
|
|
|
31586
32079
|
// src/git/bridge-queue-key.ts
|
|
31587
|
-
import * as
|
|
32080
|
+
import * as path10 from "node:path";
|
|
31588
32081
|
import { createHash } from "node:crypto";
|
|
31589
32082
|
function normalizeCanonicalGitUrl(url2) {
|
|
31590
32083
|
let s = url2.trim();
|
|
@@ -31612,13 +32105,13 @@ function canonicalUrlToRepoIdSync(url2) {
|
|
|
31612
32105
|
return createHash("sha256").update(normalized).digest("hex").slice(0, 32);
|
|
31613
32106
|
}
|
|
31614
32107
|
function fallbackRepoIdFromPath(absPath) {
|
|
31615
|
-
return createHash("sha256").update(
|
|
32108
|
+
return createHash("sha256").update(path10.resolve(absPath)).digest("hex").slice(0, 32);
|
|
31616
32109
|
}
|
|
31617
32110
|
async function resolveBridgeQueueBindFields(options) {
|
|
31618
32111
|
const { effectiveCwd, worktreePaths, primaryRepoRoots, log: log2 } = options;
|
|
31619
|
-
const cwdAbs = worktreePaths.length > 0 ?
|
|
32112
|
+
const cwdAbs = worktreePaths.length > 0 ? path10.resolve(worktreePaths[0]) : path10.resolve(effectiveCwd);
|
|
31620
32113
|
if (!primaryRepoRoots.length) {
|
|
31621
|
-
log2("[Bridge service]
|
|
32114
|
+
log2("[Bridge service] Prompt queue bind skipped: no Git repository roots under the working directory.");
|
|
31622
32115
|
return null;
|
|
31623
32116
|
}
|
|
31624
32117
|
let primaryRoot = primaryRepoRoots[0];
|
|
@@ -31638,115 +32131,11 @@ async function resolveBridgeQueueBindFields(options) {
|
|
|
31638
32131
|
return { canonicalQueueKey, repoId, cwdAbs };
|
|
31639
32132
|
}
|
|
31640
32133
|
|
|
31641
|
-
// src/git/pre-turn-snapshot.ts
|
|
31642
|
-
import * as fs4 from "node:fs";
|
|
31643
|
-
import * as path9 from "node:path";
|
|
31644
|
-
import { execFile as execFile2 } from "node:child_process";
|
|
31645
|
-
import { promisify as promisify2 } from "node:util";
|
|
31646
|
-
var execFileAsync2 = promisify2(execFile2);
|
|
31647
|
-
function snapshotsDirForCwd(agentCwd) {
|
|
31648
|
-
return path9.join(agentCwd, ".buildautomaton", "snapshots");
|
|
31649
|
-
}
|
|
31650
|
-
async function gitStashCreate2(repoRoot, log2) {
|
|
31651
|
-
try {
|
|
31652
|
-
const { stdout } = await execFileAsync2("git", ["stash", "create"], {
|
|
31653
|
-
cwd: repoRoot,
|
|
31654
|
-
maxBuffer: 10 * 1024 * 1024
|
|
31655
|
-
});
|
|
31656
|
-
return stdout.trim();
|
|
31657
|
-
} catch (e) {
|
|
31658
|
-
log2(`[snapshot] git stash create failed in ${repoRoot}: ${e instanceof Error ? e.message : String(e)}`);
|
|
31659
|
-
return "";
|
|
31660
|
-
}
|
|
31661
|
-
}
|
|
31662
|
-
async function gitRun(repoRoot, args, log2, label) {
|
|
31663
|
-
try {
|
|
31664
|
-
await execFileAsync2("git", args, { cwd: repoRoot, maxBuffer: 10 * 1024 * 1024 });
|
|
31665
|
-
return { ok: true };
|
|
31666
|
-
} catch (e) {
|
|
31667
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
31668
|
-
log2(`[snapshot] git ${label} failed in ${repoRoot}: ${msg}`);
|
|
31669
|
-
return { ok: false, error: msg };
|
|
31670
|
-
}
|
|
31671
|
-
}
|
|
31672
|
-
async function resolveSnapshotRepoRoots(options) {
|
|
31673
|
-
const { worktreePaths, fallbackCwd, log: log2 } = options;
|
|
31674
|
-
if (worktreePaths?.length) {
|
|
31675
|
-
const uniq = [...new Set(worktreePaths.map((p) => path9.resolve(p)))];
|
|
31676
|
-
return uniq;
|
|
31677
|
-
}
|
|
31678
|
-
try {
|
|
31679
|
-
const repos = await discoverGitReposUnderRoot(fallbackCwd);
|
|
31680
|
-
return repos.map((r) => r.absolutePath);
|
|
31681
|
-
} catch (e) {
|
|
31682
|
-
log2(`[snapshot] discover repos failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
31683
|
-
return [];
|
|
31684
|
-
}
|
|
31685
|
-
}
|
|
31686
|
-
async function capturePreTurnSnapshot(options) {
|
|
31687
|
-
const { runId, repoRoots, agentCwd, log: log2 } = options;
|
|
31688
|
-
if (!runId || !repoRoots.length) {
|
|
31689
|
-
return { ok: false, error: "No git repos to snapshot" };
|
|
31690
|
-
}
|
|
31691
|
-
const repos = [];
|
|
31692
|
-
for (const root of repoRoots) {
|
|
31693
|
-
const stashSha = await gitStashCreate2(root, log2);
|
|
31694
|
-
repos.push({ path: root, stashSha });
|
|
31695
|
-
}
|
|
31696
|
-
const dir = snapshotsDirForCwd(agentCwd);
|
|
31697
|
-
try {
|
|
31698
|
-
fs4.mkdirSync(dir, { recursive: true });
|
|
31699
|
-
} catch (e) {
|
|
31700
|
-
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
31701
|
-
}
|
|
31702
|
-
const payload = {
|
|
31703
|
-
runId,
|
|
31704
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
31705
|
-
repos
|
|
31706
|
-
};
|
|
31707
|
-
const filePath = path9.join(dir, `${runId}.json`);
|
|
31708
|
-
try {
|
|
31709
|
-
fs4.writeFileSync(filePath, JSON.stringify(payload, null, 2), "utf8");
|
|
31710
|
-
} catch (e) {
|
|
31711
|
-
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
31712
|
-
}
|
|
31713
|
-
log2(`[snapshot] Saved pre-turn snapshot ${runId.slice(0, 8)}\u2026 (${repos.length} repo(s)) \u2192 ${filePath}`);
|
|
31714
|
-
return { ok: true, filePath, repos };
|
|
31715
|
-
}
|
|
31716
|
-
async function applyPreTurnSnapshot(filePath, log2) {
|
|
31717
|
-
let data;
|
|
31718
|
-
try {
|
|
31719
|
-
const raw = fs4.readFileSync(filePath, "utf8");
|
|
31720
|
-
data = JSON.parse(raw);
|
|
31721
|
-
} catch (e) {
|
|
31722
|
-
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
31723
|
-
}
|
|
31724
|
-
if (!Array.isArray(data.repos)) {
|
|
31725
|
-
return { ok: false, error: "Invalid snapshot file" };
|
|
31726
|
-
}
|
|
31727
|
-
for (const r of data.repos) {
|
|
31728
|
-
if (!r.path) continue;
|
|
31729
|
-
const reset = await gitRun(r.path, ["reset", "--hard", "HEAD"], log2, "reset --hard");
|
|
31730
|
-
if (!reset.ok) return reset;
|
|
31731
|
-
const clean = await gitRun(r.path, ["clean", "-fd"], log2, "clean -fd");
|
|
31732
|
-
if (!clean.ok) return clean;
|
|
31733
|
-
if (r.stashSha) {
|
|
31734
|
-
const ap = await gitRun(r.path, ["stash", "apply", r.stashSha], log2, "stash apply");
|
|
31735
|
-
if (!ap.ok) return ap;
|
|
31736
|
-
}
|
|
31737
|
-
}
|
|
31738
|
-
log2(`[snapshot] Restored pre-turn state for ${data.runId.slice(0, 8)}\u2026`);
|
|
31739
|
-
return { ok: true };
|
|
31740
|
-
}
|
|
31741
|
-
function snapshotFilePath(agentCwd, runId) {
|
|
31742
|
-
return path9.join(snapshotsDirForCwd(agentCwd), `${runId}.json`);
|
|
31743
|
-
}
|
|
31744
|
-
|
|
31745
32134
|
// src/acp/from-bridge/handle-bridge-prompt.ts
|
|
31746
32135
|
var execFileAsync3 = promisify3(execFile3);
|
|
31747
|
-
async function readGitBranch(
|
|
32136
|
+
async function readGitBranch(cwd) {
|
|
31748
32137
|
try {
|
|
31749
|
-
const { stdout } = await execFileAsync3("git", ["branch", "--show-current"], { cwd
|
|
32138
|
+
const { stdout } = await execFileAsync3("git", ["branch", "--show-current"], { cwd, maxBuffer: 64 * 1024 });
|
|
31750
32139
|
const b = stdout.trim();
|
|
31751
32140
|
return b || null;
|
|
31752
32141
|
} catch {
|
|
@@ -31759,7 +32148,7 @@ function handleBridgePrompt(msg, deps) {
|
|
|
31759
32148
|
const promptText = typeof rawPrompt === "string" ? rawPrompt : rawPrompt != null ? String(rawPrompt) : "";
|
|
31760
32149
|
if (!promptText.trim()) {
|
|
31761
32150
|
log2(
|
|
31762
|
-
`[Bridge service]
|
|
32151
|
+
`[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).`
|
|
31763
32152
|
);
|
|
31764
32153
|
return;
|
|
31765
32154
|
}
|
|
@@ -31768,8 +32157,8 @@ function handleBridgePrompt(msg, deps) {
|
|
|
31768
32157
|
const sessionWorktreesEnabled = msg.sessionWorktreesEnabled === true;
|
|
31769
32158
|
const agentType = typeof msg.agentType === "string" && msg.agentType.trim() ? msg.agentType.trim() : void 0;
|
|
31770
32159
|
const runId = typeof msg.runId === "string" ? msg.runId : void 0;
|
|
31771
|
-
const
|
|
31772
|
-
|
|
32160
|
+
const mode = typeof msg.mode === "string" && msg.mode.trim() ? msg.mode.trim() : void 0;
|
|
32161
|
+
acpManager.logPromptReceivedFromBridge({ agentType, mode });
|
|
31773
32162
|
const sendResult = (result) => {
|
|
31774
32163
|
const s = getWs();
|
|
31775
32164
|
if (s) sendWsMessage(s, result);
|
|
@@ -31777,7 +32166,7 @@ function handleBridgePrompt(msg, deps) {
|
|
|
31777
32166
|
const sendSessionUpdate = (payload) => {
|
|
31778
32167
|
const s = getWs();
|
|
31779
32168
|
if (!s) {
|
|
31780
|
-
log2("[Bridge service]
|
|
32169
|
+
log2("[Bridge service] Session update not sent: not connected to the bridge.");
|
|
31781
32170
|
return;
|
|
31782
32171
|
}
|
|
31783
32172
|
const p = payload;
|
|
@@ -31785,7 +32174,7 @@ function handleBridgePrompt(msg, deps) {
|
|
|
31785
32174
|
};
|
|
31786
32175
|
async function preambleAndPrompt(resolvedCwd) {
|
|
31787
32176
|
const s = getWs();
|
|
31788
|
-
const effectiveCwd =
|
|
32177
|
+
const effectiveCwd = path11.resolve(resolvedCwd ?? getBridgeWorkspaceDirectory());
|
|
31789
32178
|
const worktreePaths = sessionWorktreeManager.getWorktreePathsForSession(sessionId) ?? [];
|
|
31790
32179
|
const repoRoots = await resolveSnapshotRepoRoots({
|
|
31791
32180
|
worktreePaths,
|
|
@@ -31818,9 +32207,6 @@ function handleBridgePrompt(msg, deps) {
|
|
|
31818
32207
|
agentUsesWorktree: sessionWorktreeManager.usesWorktreeSession(sessionId)
|
|
31819
32208
|
});
|
|
31820
32209
|
}
|
|
31821
|
-
if (sessionGitQueueStart && sessionId && repoRoots.length > 0) {
|
|
31822
|
-
await captureSessionGitBoundary({ sessionId, repoRoots, log: log2 });
|
|
31823
|
-
}
|
|
31824
32210
|
if (s && sessionId && runId) {
|
|
31825
32211
|
const cap = repoRoots.length > 0 ? await capturePreTurnSnapshot({ runId, repoRoots, agentCwd: effectiveCwd, log: log2 }) : { ok: false, error: "No git repos" };
|
|
31826
32212
|
sendWsMessage(s, {
|
|
@@ -31835,16 +32221,15 @@ function handleBridgePrompt(msg, deps) {
|
|
|
31835
32221
|
promptId: msg.id,
|
|
31836
32222
|
sessionId,
|
|
31837
32223
|
runId,
|
|
31838
|
-
mode
|
|
32224
|
+
mode,
|
|
31839
32225
|
agentType,
|
|
31840
32226
|
cwd: effectiveCwd,
|
|
31841
32227
|
sendResult,
|
|
31842
|
-
sendSessionUpdate
|
|
31843
|
-
collectSessionDiffAfterTurn
|
|
32228
|
+
sendSessionUpdate
|
|
31844
32229
|
});
|
|
31845
32230
|
}
|
|
31846
|
-
void sessionWorktreeManager.resolveCwdForPrompt(sessionId, { isNewSession, sessionWorktreesEnabled }).then((
|
|
31847
|
-
log2(`[
|
|
32231
|
+
void sessionWorktreeManager.resolveCwdForPrompt(sessionId, { isNewSession, sessionWorktreesEnabled }).then((cwd) => preambleAndPrompt(cwd)).catch((err) => {
|
|
32232
|
+
log2(`[Agent] Worktree resolve failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
31848
32233
|
void preambleAndPrompt(void 0);
|
|
31849
32234
|
});
|
|
31850
32235
|
}
|
|
@@ -31861,7 +32246,7 @@ function handleBridgeCancelRun(msg, { log: log2, acpManager }) {
|
|
|
31861
32246
|
void acpManager.cancelRun(runId).then((sent) => {
|
|
31862
32247
|
if (!sent) {
|
|
31863
32248
|
log2(
|
|
31864
|
-
`[
|
|
32249
|
+
`[Agent] Cancel ignored for run ${runId.slice(0, 8)}\u2026 (no active run or cancel not available).`
|
|
31865
32250
|
);
|
|
31866
32251
|
}
|
|
31867
32252
|
});
|
|
@@ -31964,7 +32349,7 @@ var previewSkill = {
|
|
|
31964
32349
|
const exe = parts[0];
|
|
31965
32350
|
const args = parts.slice(1);
|
|
31966
32351
|
previewProcess = spawn4(isWindows && exe === "npm" ? "npm.cmd" : exe, args, {
|
|
31967
|
-
cwd:
|
|
32352
|
+
cwd: getBridgeWorkspaceDirectory(),
|
|
31968
32353
|
stdio: ["ignore", "pipe", "pipe"],
|
|
31969
32354
|
env: {
|
|
31970
32355
|
...process.env,
|
|
@@ -32063,7 +32448,7 @@ function handleSkillCall(msg, socket, log2) {
|
|
|
32063
32448
|
sendWsMessage(socket, { type: "skill_result", id: msg.id, result });
|
|
32064
32449
|
}).catch((err) => {
|
|
32065
32450
|
sendWsMessage(socket, { type: "skill_result", id: msg.id, error: String(err) });
|
|
32066
|
-
log2(`[Bridge service]
|
|
32451
|
+
log2(`[Bridge service] Skill invocation failed (${msg.skillId}/${msg.operationId}): ${err}`);
|
|
32067
32452
|
});
|
|
32068
32453
|
}
|
|
32069
32454
|
|
|
@@ -32081,31 +32466,30 @@ var handleSkillCallMessage = (msg, { getWs, log: log2 }) => {
|
|
|
32081
32466
|
|
|
32082
32467
|
// src/files/list-dir.ts
|
|
32083
32468
|
import fs5 from "node:fs";
|
|
32084
|
-
import
|
|
32469
|
+
import path13 from "node:path";
|
|
32085
32470
|
|
|
32086
32471
|
// src/files/ensure-under-cwd.ts
|
|
32087
|
-
import
|
|
32088
|
-
function ensureUnderCwd(relativePath,
|
|
32089
|
-
const normalized =
|
|
32090
|
-
const resolved =
|
|
32091
|
-
if (!resolved.startsWith(
|
|
32472
|
+
import path12 from "node:path";
|
|
32473
|
+
function ensureUnderCwd(relativePath, cwd = getBridgeWorkspaceDirectory()) {
|
|
32474
|
+
const normalized = path12.normalize(relativePath).replace(/^(\.\/)+/, "");
|
|
32475
|
+
const resolved = path12.resolve(cwd, normalized);
|
|
32476
|
+
if (!resolved.startsWith(cwd + path12.sep) && resolved !== cwd) {
|
|
32092
32477
|
return null;
|
|
32093
32478
|
}
|
|
32094
32479
|
return resolved;
|
|
32095
32480
|
}
|
|
32096
32481
|
|
|
32097
32482
|
// src/files/list-dir.ts
|
|
32098
|
-
var cwd = process.cwd();
|
|
32099
32483
|
function listDir(relativePath) {
|
|
32100
|
-
const resolved = ensureUnderCwd(relativePath || ".",
|
|
32484
|
+
const resolved = ensureUnderCwd(relativePath || ".", getBridgeWorkspaceDirectory());
|
|
32101
32485
|
if (!resolved) {
|
|
32102
32486
|
return { error: "Path is outside working directory" };
|
|
32103
32487
|
}
|
|
32104
32488
|
try {
|
|
32105
32489
|
const names = fs5.readdirSync(resolved, { withFileTypes: true });
|
|
32106
32490
|
const entries = names.filter((d) => !d.name.startsWith(".")).map((d) => {
|
|
32107
|
-
const entryPath =
|
|
32108
|
-
const fullPath =
|
|
32491
|
+
const entryPath = path13.join(relativePath || ".", d.name).replace(/\\/g, "/");
|
|
32492
|
+
const fullPath = path13.join(resolved, d.name);
|
|
32109
32493
|
let isDir = d.isDirectory();
|
|
32110
32494
|
if (d.isSymbolicLink()) {
|
|
32111
32495
|
try {
|
|
@@ -32135,9 +32519,8 @@ function listDir(relativePath) {
|
|
|
32135
32519
|
// src/files/read-file.ts
|
|
32136
32520
|
import fs6 from "node:fs";
|
|
32137
32521
|
import { StringDecoder } from "node:string_decoder";
|
|
32138
|
-
var cwd2 = process.cwd();
|
|
32139
32522
|
function resolveFilePath(relativePath) {
|
|
32140
|
-
const resolved = ensureUnderCwd(relativePath,
|
|
32523
|
+
const resolved = ensureUnderCwd(relativePath, getBridgeWorkspaceDirectory());
|
|
32141
32524
|
if (!resolved) return { error: "Path is outside working directory" };
|
|
32142
32525
|
let real;
|
|
32143
32526
|
try {
|
|
@@ -32145,8 +32528,8 @@ function resolveFilePath(relativePath) {
|
|
|
32145
32528
|
} catch {
|
|
32146
32529
|
real = resolved;
|
|
32147
32530
|
}
|
|
32148
|
-
const
|
|
32149
|
-
if (!
|
|
32531
|
+
const stat2 = fs6.statSync(real);
|
|
32532
|
+
if (!stat2.isFile()) return { error: "Not a file" };
|
|
32150
32533
|
return real;
|
|
32151
32534
|
}
|
|
32152
32535
|
var LINE_CHUNK_SIZE = 64 * 1024;
|
|
@@ -32303,7 +32686,7 @@ function readFileRange(filePath, startLine, endLine, lineOffsetIn, lineChunkSize
|
|
|
32303
32686
|
fs6.closeSync(fd);
|
|
32304
32687
|
}
|
|
32305
32688
|
}
|
|
32306
|
-
function
|
|
32689
|
+
function readFile2(relativePath, startLine, endLine, lineOffset, lineChunkSize = LINE_CHUNK_SIZE) {
|
|
32307
32690
|
try {
|
|
32308
32691
|
const result = resolveFilePath(relativePath);
|
|
32309
32692
|
if (typeof result === "object") return result;
|
|
@@ -32311,10 +32694,10 @@ function readFile(relativePath, startLine, endLine, lineOffset, lineChunkSize =
|
|
|
32311
32694
|
if (hasRange) {
|
|
32312
32695
|
return readFileRange(result, startLine, endLine, lineOffset, lineChunkSize);
|
|
32313
32696
|
}
|
|
32314
|
-
const
|
|
32697
|
+
const stat2 = fs6.statSync(result);
|
|
32315
32698
|
const raw = fs6.readFileSync(result, "utf8");
|
|
32316
32699
|
const lines = raw.split(/\r?\n/);
|
|
32317
|
-
return { content: raw, totalLines: lines.length, size:
|
|
32700
|
+
return { content: raw, totalLines: lines.length, size: stat2.size };
|
|
32318
32701
|
} catch (err) {
|
|
32319
32702
|
return { error: err instanceof Error ? err.message : String(err) };
|
|
32320
32703
|
}
|
|
@@ -32322,15 +32705,15 @@ function readFile(relativePath, startLine, endLine, lineOffset, lineChunkSize =
|
|
|
32322
32705
|
|
|
32323
32706
|
// src/files/file-index.ts
|
|
32324
32707
|
import fs7 from "node:fs";
|
|
32325
|
-
import
|
|
32708
|
+
import path14 from "node:path";
|
|
32326
32709
|
import os2 from "node:os";
|
|
32327
32710
|
import crypto2 from "node:crypto";
|
|
32328
|
-
var INDEX_DIR =
|
|
32711
|
+
var INDEX_DIR = path14.join(os2.homedir(), ".buildautomaton");
|
|
32329
32712
|
var HASH_LEN = 16;
|
|
32330
32713
|
var INDEX_VERSION = 2;
|
|
32331
|
-
function getIndexPath(
|
|
32332
|
-
const hash = crypto2.createHash("sha256").update(
|
|
32333
|
-
return
|
|
32714
|
+
function getIndexPath(cwd) {
|
|
32715
|
+
const hash = crypto2.createHash("sha256").update(cwd).digest("hex").slice(0, HASH_LEN);
|
|
32716
|
+
return path14.join(INDEX_DIR, `.file-index-${hash}.json`);
|
|
32334
32717
|
}
|
|
32335
32718
|
function getTrigrams(s) {
|
|
32336
32719
|
const lower = s.toLowerCase();
|
|
@@ -32374,23 +32757,23 @@ function walkDir(dir, baseDir, out) {
|
|
|
32374
32757
|
}
|
|
32375
32758
|
for (const name of names) {
|
|
32376
32759
|
if (name.startsWith(".")) continue;
|
|
32377
|
-
const full =
|
|
32378
|
-
let
|
|
32760
|
+
const full = path14.join(dir, name);
|
|
32761
|
+
let stat2;
|
|
32379
32762
|
try {
|
|
32380
|
-
|
|
32763
|
+
stat2 = fs7.statSync(full);
|
|
32381
32764
|
} catch {
|
|
32382
32765
|
continue;
|
|
32383
32766
|
}
|
|
32384
|
-
const relative4 =
|
|
32385
|
-
if (
|
|
32767
|
+
const relative4 = path14.relative(baseDir, full).replace(/\\/g, "/");
|
|
32768
|
+
if (stat2.isDirectory()) {
|
|
32386
32769
|
walkDir(full, baseDir, out);
|
|
32387
|
-
} else if (
|
|
32770
|
+
} else if (stat2.isFile()) {
|
|
32388
32771
|
out.push(relative4);
|
|
32389
32772
|
}
|
|
32390
32773
|
}
|
|
32391
32774
|
}
|
|
32392
|
-
function buildFileIndex(
|
|
32393
|
-
const resolved =
|
|
32775
|
+
function buildFileIndex(cwd) {
|
|
32776
|
+
const resolved = path14.resolve(cwd);
|
|
32394
32777
|
const paths = [];
|
|
32395
32778
|
walkDir(resolved, resolved, paths);
|
|
32396
32779
|
paths.sort((a, b) => a.localeCompare(b, void 0, { sensitivity: "base" }));
|
|
@@ -32415,8 +32798,8 @@ function buildFileIndex(cwd3) {
|
|
|
32415
32798
|
}
|
|
32416
32799
|
return data;
|
|
32417
32800
|
}
|
|
32418
|
-
function loadFileIndex(
|
|
32419
|
-
const resolved =
|
|
32801
|
+
function loadFileIndex(cwd) {
|
|
32802
|
+
const resolved = path14.resolve(cwd);
|
|
32420
32803
|
const indexPath = getIndexPath(resolved);
|
|
32421
32804
|
try {
|
|
32422
32805
|
const raw = fs7.readFileSync(indexPath, "utf8");
|
|
@@ -32436,8 +32819,8 @@ function loadFileIndex(cwd3) {
|
|
|
32436
32819
|
return null;
|
|
32437
32820
|
}
|
|
32438
32821
|
}
|
|
32439
|
-
function ensureFileIndex(
|
|
32440
|
-
const resolved =
|
|
32822
|
+
function ensureFileIndex(cwd) {
|
|
32823
|
+
const resolved = path14.resolve(cwd);
|
|
32441
32824
|
const cached2 = loadFileIndex(resolved);
|
|
32442
32825
|
if (cached2 !== null) return { data: cached2, fromCache: true };
|
|
32443
32826
|
const data = buildFileIndex(resolved);
|
|
@@ -32475,8 +32858,8 @@ function searchFileIndex(index, query, limit = 100) {
|
|
|
32475
32858
|
var SEARCH_LIMIT = 100;
|
|
32476
32859
|
function handleFileBrowserSearch(msg, socket) {
|
|
32477
32860
|
const q = typeof msg.q === "string" ? msg.q : "";
|
|
32478
|
-
const
|
|
32479
|
-
const index = loadFileIndex(
|
|
32861
|
+
const cwd = getBridgeWorkspaceDirectory();
|
|
32862
|
+
const index = loadFileIndex(cwd);
|
|
32480
32863
|
if (index === null) {
|
|
32481
32864
|
sendWsMessage(socket, {
|
|
32482
32865
|
type: "file_browser_search_response",
|
|
@@ -32497,7 +32880,7 @@ function handleFileBrowserSearch(msg, socket) {
|
|
|
32497
32880
|
function triggerFileIndexBuild() {
|
|
32498
32881
|
setImmediate(() => {
|
|
32499
32882
|
try {
|
|
32500
|
-
ensureFileIndex(
|
|
32883
|
+
ensureFileIndex(getBridgeWorkspaceDirectory());
|
|
32501
32884
|
} catch (e) {
|
|
32502
32885
|
console.error("[file-index] Background build failed:", e);
|
|
32503
32886
|
}
|
|
@@ -32523,7 +32906,7 @@ function handleFileBrowserRequest(msg, socket) {
|
|
|
32523
32906
|
const endLine = typeof msg.endLine === "number" ? msg.endLine : void 0;
|
|
32524
32907
|
const lineOffset = typeof msg.lineOffset === "number" ? msg.lineOffset : void 0;
|
|
32525
32908
|
const lineChunkSize = typeof msg.lineChunkSize === "number" ? msg.lineChunkSize : void 0;
|
|
32526
|
-
const result =
|
|
32909
|
+
const result = readFile2(reqPath, startLine, endLine, lineOffset, lineChunkSize);
|
|
32527
32910
|
if ("error" in result) {
|
|
32528
32911
|
sendWsMessage(socket, { type: "file_browser_response", id: msg.id, error: result.error });
|
|
32529
32912
|
} else {
|
|
@@ -32559,13 +32942,13 @@ function handleFileBrowserSearchMessage(msg, { getWs }) {
|
|
|
32559
32942
|
|
|
32560
32943
|
// src/skills/discover-local-agent-skills.ts
|
|
32561
32944
|
import fs8 from "node:fs";
|
|
32562
|
-
import
|
|
32945
|
+
import path15 from "node:path";
|
|
32563
32946
|
var SKILL_DISCOVERY_ROOTS = [".agents/skills", ".claude/skills", ".cursor/skills", "skills"];
|
|
32564
|
-
function discoverLocalSkills(
|
|
32947
|
+
function discoverLocalSkills(cwd) {
|
|
32565
32948
|
const out = [];
|
|
32566
32949
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
32567
32950
|
for (const rel of SKILL_DISCOVERY_ROOTS) {
|
|
32568
|
-
const base =
|
|
32951
|
+
const base = path15.join(cwd, rel);
|
|
32569
32952
|
if (!fs8.existsSync(base) || !fs8.statSync(base).isDirectory()) continue;
|
|
32570
32953
|
let entries = [];
|
|
32571
32954
|
try {
|
|
@@ -32574,13 +32957,13 @@ function discoverLocalSkills(cwd3) {
|
|
|
32574
32957
|
continue;
|
|
32575
32958
|
}
|
|
32576
32959
|
for (const name of entries) {
|
|
32577
|
-
const dir =
|
|
32960
|
+
const dir = path15.join(base, name);
|
|
32578
32961
|
try {
|
|
32579
32962
|
if (!fs8.statSync(dir).isDirectory()) continue;
|
|
32580
32963
|
} catch {
|
|
32581
32964
|
continue;
|
|
32582
32965
|
}
|
|
32583
|
-
const skillMd =
|
|
32966
|
+
const skillMd = path15.join(dir, "SKILL.md");
|
|
32584
32967
|
if (!fs8.existsSync(skillMd)) continue;
|
|
32585
32968
|
const key = `${rel}/${name}`;
|
|
32586
32969
|
if (seenKeys.has(key)) continue;
|
|
@@ -32590,10 +32973,10 @@ function discoverLocalSkills(cwd3) {
|
|
|
32590
32973
|
}
|
|
32591
32974
|
return out;
|
|
32592
32975
|
}
|
|
32593
|
-
function discoverSkillLayoutRoots(
|
|
32976
|
+
function discoverSkillLayoutRoots(cwd) {
|
|
32594
32977
|
const roots = [];
|
|
32595
32978
|
for (const rel of SKILL_DISCOVERY_ROOTS) {
|
|
32596
|
-
const base =
|
|
32979
|
+
const base = path15.join(cwd, rel);
|
|
32597
32980
|
if (!fs8.existsSync(base) || !fs8.statSync(base).isDirectory()) continue;
|
|
32598
32981
|
let entries = [];
|
|
32599
32982
|
try {
|
|
@@ -32603,13 +32986,13 @@ function discoverSkillLayoutRoots(cwd3) {
|
|
|
32603
32986
|
}
|
|
32604
32987
|
const skills2 = [];
|
|
32605
32988
|
for (const name of entries) {
|
|
32606
|
-
const dir =
|
|
32989
|
+
const dir = path15.join(base, name);
|
|
32607
32990
|
try {
|
|
32608
32991
|
if (!fs8.statSync(dir).isDirectory()) continue;
|
|
32609
32992
|
} catch {
|
|
32610
32993
|
continue;
|
|
32611
32994
|
}
|
|
32612
|
-
if (!fs8.existsSync(
|
|
32995
|
+
if (!fs8.existsSync(path15.join(dir, "SKILL.md"))) continue;
|
|
32613
32996
|
const relPath = `${rel}/${name}`.replace(/\\/g, "/");
|
|
32614
32997
|
skills2.push({ name, relPath });
|
|
32615
32998
|
}
|
|
@@ -32624,14 +33007,14 @@ function discoverSkillLayoutRoots(cwd3) {
|
|
|
32624
33007
|
function handleSkillLayoutRequest(msg, deps) {
|
|
32625
33008
|
const socket = deps.getWs();
|
|
32626
33009
|
const id = typeof msg.id === "string" ? msg.id : "";
|
|
32627
|
-
const roots = discoverSkillLayoutRoots(
|
|
33010
|
+
const roots = discoverSkillLayoutRoots(getBridgeWorkspaceDirectory());
|
|
32628
33011
|
socket?.send(JSON.stringify({ type: "skill_layout_response", id, roots }));
|
|
32629
33012
|
}
|
|
32630
33013
|
|
|
32631
33014
|
// src/skills/install-remote-skills.ts
|
|
32632
33015
|
import fs9 from "node:fs";
|
|
32633
|
-
import
|
|
32634
|
-
function installRemoteSkills(
|
|
33016
|
+
import path16 from "node:path";
|
|
33017
|
+
function installRemoteSkills(cwd, targetDir, items) {
|
|
32635
33018
|
const installed = [];
|
|
32636
33019
|
if (!Array.isArray(items)) {
|
|
32637
33020
|
return { success: false, error: "Invalid items" };
|
|
@@ -32641,11 +33024,11 @@ function installRemoteSkills(cwd3, targetDir, items) {
|
|
|
32641
33024
|
if (typeof item.sourceId !== "string" || typeof item.skillName !== "string" || typeof item.versionHash !== "string" || !Array.isArray(item.files)) {
|
|
32642
33025
|
continue;
|
|
32643
33026
|
}
|
|
32644
|
-
const skillDir =
|
|
33027
|
+
const skillDir = path16.join(cwd, targetDir, item.skillName);
|
|
32645
33028
|
for (const f of item.files) {
|
|
32646
33029
|
if (typeof f.path !== "string" || !f.text && !f.base64) continue;
|
|
32647
|
-
const dest =
|
|
32648
|
-
fs9.mkdirSync(
|
|
33030
|
+
const dest = path16.join(skillDir, f.path);
|
|
33031
|
+
fs9.mkdirSync(path16.dirname(dest), { recursive: true });
|
|
32649
33032
|
if (f.text !== void 0) {
|
|
32650
33033
|
fs9.writeFileSync(dest, f.text, "utf8");
|
|
32651
33034
|
} else if (f.base64) {
|
|
@@ -32670,11 +33053,11 @@ var handleInstallSkillsMessage = (msg, deps) => {
|
|
|
32670
33053
|
const id = typeof msg.id === "string" ? msg.id : "";
|
|
32671
33054
|
const targetDir = typeof msg.targetDir === "string" && msg.targetDir.trim() ? msg.targetDir.trim() : ".agents/skills";
|
|
32672
33055
|
const rawItems = msg.items;
|
|
32673
|
-
const
|
|
32674
|
-
const result = installRemoteSkills(
|
|
33056
|
+
const cwd = getBridgeWorkspaceDirectory();
|
|
33057
|
+
const result = installRemoteSkills(cwd, targetDir, rawItems);
|
|
32675
33058
|
if (!result.success) {
|
|
32676
33059
|
const err = result.error ?? "Invalid items";
|
|
32677
|
-
deps.log(`[Bridge service]
|
|
33060
|
+
deps.log(`[Bridge service] Install skills failed: ${err}`);
|
|
32678
33061
|
socket?.send(JSON.stringify({ type: "install_skills_result", id, success: false, error: err }));
|
|
32679
33062
|
return;
|
|
32680
33063
|
}
|
|
@@ -32744,7 +33127,6 @@ var handleSessionDiscardedMessage = (msg, deps) => {
|
|
|
32744
33127
|
|
|
32745
33128
|
// src/bridge/routing/handlers/revert-turn-snapshot.ts
|
|
32746
33129
|
import * as fs10 from "node:fs";
|
|
32747
|
-
import * as path16 from "node:path";
|
|
32748
33130
|
var handleRevertTurnSnapshotMessage = (msg, deps) => {
|
|
32749
33131
|
const id = typeof msg.id === "string" ? msg.id : "";
|
|
32750
33132
|
const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
|
|
@@ -32754,7 +33136,7 @@ var handleRevertTurnSnapshotMessage = (msg, deps) => {
|
|
|
32754
33136
|
void (async () => {
|
|
32755
33137
|
const s = getWs();
|
|
32756
33138
|
if (!s) return;
|
|
32757
|
-
const agentBase = sessionWorktreeManager.getAgentCwdForSession(sessionId) ??
|
|
33139
|
+
const agentBase = sessionWorktreeManager.getAgentCwdForSession(sessionId) ?? getBridgeWorkspaceDirectory();
|
|
32758
33140
|
const file2 = snapshotFilePath(agentBase, turnId);
|
|
32759
33141
|
if (!fs10.existsSync(file2)) {
|
|
32760
33142
|
sendWsMessage(s, {
|
|
@@ -32857,25 +33239,12 @@ function dispatchBridgeMessage(msg, deps) {
|
|
|
32857
33239
|
}
|
|
32858
33240
|
|
|
32859
33241
|
// src/bridge/routing/handle-bridge-message.ts
|
|
32860
|
-
var DEFERRED_INBOUND_TYPES = /* @__PURE__ */ new Set([
|
|
32861
|
-
"server_control",
|
|
32862
|
-
"prompt",
|
|
32863
|
-
"install_skills",
|
|
32864
|
-
"refresh_local_skills",
|
|
32865
|
-
"dev_servers_config"
|
|
32866
|
-
]);
|
|
32867
33242
|
function handleBridgeMessage(data, deps) {
|
|
32868
33243
|
const msg = data;
|
|
32869
|
-
|
|
32870
|
-
|
|
32871
|
-
|
|
32872
|
-
|
|
32873
|
-
setImmediate(() => {
|
|
32874
|
-
dispatchBridgeMessage(msg, deps);
|
|
32875
|
-
});
|
|
32876
|
-
return;
|
|
32877
|
-
}
|
|
32878
|
-
dispatchBridgeMessage(msg, deps);
|
|
33244
|
+
if (!deps.getWs()) return;
|
|
33245
|
+
setImmediate(() => {
|
|
33246
|
+
dispatchBridgeMessage(msg, deps);
|
|
33247
|
+
});
|
|
32879
33248
|
}
|
|
32880
33249
|
|
|
32881
33250
|
// src/worktrees/session-worktree-manager.ts
|
|
@@ -32953,7 +33322,7 @@ async function prepareNewSessionWorktrees(options) {
|
|
|
32953
33322
|
const agentMirrorRoot = path18.join(rootAbs, cwdKey);
|
|
32954
33323
|
const repos = await discoverGitReposUnderRoot(launcherResolved);
|
|
32955
33324
|
if (repos.length === 0) {
|
|
32956
|
-
log2("[worktrees] No
|
|
33325
|
+
log2("[worktrees] No Git repositories under launcher working directory; skipping worktree creation.");
|
|
32957
33326
|
return null;
|
|
32958
33327
|
}
|
|
32959
33328
|
const branch = `session-${sessionId}`;
|
|
@@ -32967,11 +33336,11 @@ async function prepareNewSessionWorktrees(options) {
|
|
|
32967
33336
|
fs12.mkdirSync(path18.dirname(wtPath), { recursive: true });
|
|
32968
33337
|
try {
|
|
32969
33338
|
await gitWorktreeAddBranch(repo.absolutePath, wtPath, branch);
|
|
32970
|
-
log2(`[worktrees] Added worktree ${wtPath} (branch ${branch})
|
|
33339
|
+
log2(`[worktrees] Added worktree ${wtPath} (branch ${branch}).`);
|
|
32971
33340
|
worktreePaths.push(wtPath);
|
|
32972
33341
|
} catch (e) {
|
|
32973
33342
|
log2(
|
|
32974
|
-
`[worktrees]
|
|
33343
|
+
`[worktrees] Worktree add failed for ${repo.absolutePath}: ${e instanceof Error ? e.message : String(e)}`
|
|
32975
33344
|
);
|
|
32976
33345
|
}
|
|
32977
33346
|
}
|
|
@@ -32993,7 +33362,9 @@ async function renameSessionWorktreeBranches(paths, newBranch, log2) {
|
|
|
32993
33362
|
await gitRenameCurrentBranch(wt, safe);
|
|
32994
33363
|
log2(`[worktrees] Renamed branch in ${wt} \u2192 ${safe}`);
|
|
32995
33364
|
} catch (e) {
|
|
32996
|
-
log2(
|
|
33365
|
+
log2(
|
|
33366
|
+
`[worktrees] Branch rename failed in ${wt}: ${e instanceof Error ? e.message : String(e)}`
|
|
33367
|
+
);
|
|
32997
33368
|
}
|
|
32998
33369
|
}
|
|
32999
33370
|
}
|
|
@@ -33035,7 +33406,7 @@ async function removeSessionWorktrees(paths, log2) {
|
|
|
33035
33406
|
await gitWorktreeRemoveForce(wt);
|
|
33036
33407
|
log2(`[worktrees] Removed worktree ${wt}`);
|
|
33037
33408
|
} catch (e) {
|
|
33038
|
-
log2(`[worktrees]
|
|
33409
|
+
log2(`[worktrees] Remove failed for ${wt}: ${e instanceof Error ? e.message : String(e)}`);
|
|
33039
33410
|
try {
|
|
33040
33411
|
fs15.rmSync(wt, { recursive: true, force: true });
|
|
33041
33412
|
} catch {
|
|
@@ -33101,7 +33472,7 @@ var SessionWorktreeManager = class {
|
|
|
33101
33472
|
return this.bridgeWantsWorktrees;
|
|
33102
33473
|
}
|
|
33103
33474
|
/**
|
|
33104
|
-
* Returns cwd for the agent (mirror of launcher tree), or undefined to use
|
|
33475
|
+
* Returns cwd for the agent (mirror of launcher tree), or undefined to use the bridge workspace directory.
|
|
33105
33476
|
*/
|
|
33106
33477
|
async resolveCwdForPrompt(sessionId, opts) {
|
|
33107
33478
|
if (!sessionId || !this.effective() || !opts.sessionWorktreesEnabled) {
|
|
@@ -33114,7 +33485,7 @@ var SessionWorktreeManager = class {
|
|
|
33114
33485
|
}
|
|
33115
33486
|
const prep = await prepareNewSessionWorktrees({
|
|
33116
33487
|
rootAbs: this.rootAbs,
|
|
33117
|
-
launcherCwd:
|
|
33488
|
+
launcherCwd: getBridgeWorkspaceDirectory(),
|
|
33118
33489
|
sessionId,
|
|
33119
33490
|
layout: this.layout,
|
|
33120
33491
|
log: this.log
|
|
@@ -33154,7 +33525,7 @@ var SessionWorktreeManager = class {
|
|
|
33154
33525
|
}
|
|
33155
33526
|
async commitSession(params) {
|
|
33156
33527
|
const paths = this.sessionPaths.get(params.sessionId);
|
|
33157
|
-
const targets = paths?.length ? paths : [
|
|
33528
|
+
const targets = paths?.length ? paths : [getBridgeWorkspaceDirectory()];
|
|
33158
33529
|
return commitSessionWorktrees({
|
|
33159
33530
|
paths: targets,
|
|
33160
33531
|
branch: params.branch,
|
|
@@ -33207,7 +33578,7 @@ function shouldIgnoreRelative(rel) {
|
|
|
33207
33578
|
}
|
|
33208
33579
|
function attachWatchErrorLog(w) {
|
|
33209
33580
|
w.on("error", (err) => {
|
|
33210
|
-
console.error("[file-index]
|
|
33581
|
+
console.error("[file-index] File watcher error:", err);
|
|
33211
33582
|
});
|
|
33212
33583
|
}
|
|
33213
33584
|
function createFsWatcher(resolved, schedule) {
|
|
@@ -33226,7 +33597,7 @@ function createFsWatcher(resolved, schedule) {
|
|
|
33226
33597
|
const code = typeof e === "object" && e !== null && "code" in e ? e.code : void 0;
|
|
33227
33598
|
if (code === "ERR_FEATURE_UNAVAILABLE_ON_PLATFORM") {
|
|
33228
33599
|
console.warn(
|
|
33229
|
-
"[file-index]
|
|
33600
|
+
"[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."
|
|
33230
33601
|
);
|
|
33231
33602
|
const w = watch(resolved, { recursive: false }, onEvent);
|
|
33232
33603
|
attachWatchErrorLog(w);
|
|
@@ -33235,8 +33606,8 @@ function createFsWatcher(resolved, schedule) {
|
|
|
33235
33606
|
throw e;
|
|
33236
33607
|
}
|
|
33237
33608
|
}
|
|
33238
|
-
function startFileIndexWatcher(
|
|
33239
|
-
const resolved = path21.resolve(
|
|
33609
|
+
function startFileIndexWatcher(cwd = getBridgeWorkspaceDirectory()) {
|
|
33610
|
+
const resolved = path21.resolve(cwd);
|
|
33240
33611
|
try {
|
|
33241
33612
|
buildFileIndex(resolved);
|
|
33242
33613
|
} catch (e) {
|
|
@@ -33290,7 +33661,7 @@ async function sigtermAndWaitForExit(proc, graceMs, log2, shortId) {
|
|
|
33290
33661
|
const exited = new Promise((resolve15) => {
|
|
33291
33662
|
proc.once("exit", () => resolve15());
|
|
33292
33663
|
});
|
|
33293
|
-
log2(`[dev-server] Sending SIGTERM to ${shortId} (pid=${proc.pid ?? "?"})
|
|
33664
|
+
log2(`[dev-server] Sending SIGTERM to ${shortId} (pid=${proc.pid ?? "?"}).`);
|
|
33294
33665
|
try {
|
|
33295
33666
|
proc.kill("SIGTERM");
|
|
33296
33667
|
} catch {
|
|
@@ -33298,7 +33669,9 @@ async function sigtermAndWaitForExit(proc, graceMs, log2, shortId) {
|
|
|
33298
33669
|
await Promise.race([exited, new Promise((resolve15) => setTimeout(resolve15, graceMs))]);
|
|
33299
33670
|
}
|
|
33300
33671
|
function forceKillChild(proc, log2, shortId, graceMs) {
|
|
33301
|
-
log2(
|
|
33672
|
+
log2(
|
|
33673
|
+
`[dev-server] ${shortId} did not exit within ${graceMs}ms; sending SIGKILL (pid=${proc.pid ?? "?"}).`
|
|
33674
|
+
);
|
|
33302
33675
|
proc.removeAllListeners();
|
|
33303
33676
|
try {
|
|
33304
33677
|
proc.kill("SIGKILL");
|
|
@@ -33368,7 +33741,7 @@ function wireDevServerChildProcess(d) {
|
|
|
33368
33741
|
d.rmMergedCleanupDir(cleanupDir);
|
|
33369
33742
|
}
|
|
33370
33743
|
if (signal) {
|
|
33371
|
-
d.log(`[dev-server] ${title} stopped (signal ${String(signal)})
|
|
33744
|
+
d.log(`[dev-server] ${title} stopped (signal: ${String(signal)}).`);
|
|
33372
33745
|
} else if (code !== null && code !== 0) {
|
|
33373
33746
|
const errTail = d.stderrTail.getTail().slice(-3).join("\n");
|
|
33374
33747
|
d.log(`[dev-server] ${title} exited with code ${code}${errTail ? `
|
|
@@ -33507,7 +33880,7 @@ function pipedStdoutStderrFor(attemptStdio) {
|
|
|
33507
33880
|
|
|
33508
33881
|
// src/dev-servers/manager/shell-spawn/try-spawn-piped-via-sh.ts
|
|
33509
33882
|
import { spawn as spawn5 } from "node:child_process";
|
|
33510
|
-
function trySpawnPipedViaSh(command, env,
|
|
33883
|
+
function trySpawnPipedViaSh(command, env, cwd, signal) {
|
|
33511
33884
|
const attempts = [
|
|
33512
33885
|
{ stdio: [devNullReadFd(), "pipe", "pipe"], endStdin: false },
|
|
33513
33886
|
{ stdio: ["ignore", "pipe", "pipe"], endStdin: true },
|
|
@@ -33518,7 +33891,7 @@ function trySpawnPipedViaSh(command, env, cwd3, signal) {
|
|
|
33518
33891
|
const attempt = attempts[i];
|
|
33519
33892
|
const opts = {
|
|
33520
33893
|
env,
|
|
33521
|
-
cwd
|
|
33894
|
+
cwd,
|
|
33522
33895
|
stdio: attempt.stdio,
|
|
33523
33896
|
...signal ? { signal } : {}
|
|
33524
33897
|
};
|
|
@@ -33550,11 +33923,11 @@ function trySpawnPipedViaSh(command, env, cwd3, signal) {
|
|
|
33550
33923
|
|
|
33551
33924
|
// src/dev-servers/manager/shell-spawn/try-spawn-shell-true-piped.ts
|
|
33552
33925
|
import { spawn as spawn6 } from "node:child_process";
|
|
33553
|
-
function trySpawnShellTruePiped(command, env,
|
|
33926
|
+
function trySpawnShellTruePiped(command, env, cwd, devNullFd, signal) {
|
|
33554
33927
|
try {
|
|
33555
33928
|
const opts = {
|
|
33556
33929
|
env,
|
|
33557
|
-
cwd
|
|
33930
|
+
cwd,
|
|
33558
33931
|
stdio: [devNullFd, "pipe", "pipe"],
|
|
33559
33932
|
shell: true,
|
|
33560
33933
|
...signal ? { signal } : {}
|
|
@@ -33574,7 +33947,7 @@ import { spawn as spawn7 } from "node:child_process";
|
|
|
33574
33947
|
import fs18 from "node:fs";
|
|
33575
33948
|
import { tmpdir } from "node:os";
|
|
33576
33949
|
import path22 from "node:path";
|
|
33577
|
-
function trySpawnMergedLogFile(command, env,
|
|
33950
|
+
function trySpawnMergedLogFile(command, env, cwd, signal) {
|
|
33578
33951
|
const tmpRoot = fs18.mkdtempSync(path22.join(tmpdir(), "ba-devsrv-log-"));
|
|
33579
33952
|
const logPath = path22.join(tmpRoot, "combined.log");
|
|
33580
33953
|
let logFd;
|
|
@@ -33590,13 +33963,13 @@ function trySpawnMergedLogFile(command, env, cwd3, signal) {
|
|
|
33590
33963
|
if (process.platform === "win32") {
|
|
33591
33964
|
proc = spawn7(process.env.ComSpec || "cmd.exe", ["/d", "/s", "/c", command], {
|
|
33592
33965
|
env,
|
|
33593
|
-
cwd
|
|
33966
|
+
cwd,
|
|
33594
33967
|
stdio,
|
|
33595
33968
|
windowsHide: true,
|
|
33596
33969
|
...signal ? { signal } : {}
|
|
33597
33970
|
});
|
|
33598
33971
|
} else {
|
|
33599
|
-
proc = spawn7("/bin/sh", ["-c", command], { env, cwd
|
|
33972
|
+
proc = spawn7("/bin/sh", ["-c", command], { env, cwd, stdio, ...signal ? { signal } : {} });
|
|
33600
33973
|
}
|
|
33601
33974
|
fs18.closeSync(logFd);
|
|
33602
33975
|
return {
|
|
@@ -33624,7 +33997,7 @@ import path23 from "node:path";
|
|
|
33624
33997
|
function shSingleQuote(s) {
|
|
33625
33998
|
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
33626
33999
|
}
|
|
33627
|
-
function trySpawnShellScriptLogRedirectUnix(command, env,
|
|
34000
|
+
function trySpawnShellScriptLogRedirectUnix(command, env, cwd, signal) {
|
|
33628
34001
|
const tmpRoot = fs19.mkdtempSync(path23.join(tmpdir2(), "ba-devsrv-sh-"));
|
|
33629
34002
|
const logPath = path23.join(tmpRoot, "combined.log");
|
|
33630
34003
|
const innerPath = path23.join(tmpRoot, "_cmd.sh");
|
|
@@ -33636,7 +34009,7 @@ ${command}
|
|
|
33636
34009
|
fs19.writeFileSync(
|
|
33637
34010
|
runnerPath,
|
|
33638
34011
|
`#!/bin/sh
|
|
33639
|
-
cd ${shSingleQuote(
|
|
34012
|
+
cd ${shSingleQuote(cwd)}
|
|
33640
34013
|
/bin/sh ${shSingleQuote(innerPath)} >>${shSingleQuote(logPath)} 2>&1
|
|
33641
34014
|
`
|
|
33642
34015
|
);
|
|
@@ -33658,7 +34031,7 @@ cd ${shSingleQuote(cwd3)}
|
|
|
33658
34031
|
throw e;
|
|
33659
34032
|
}
|
|
33660
34033
|
}
|
|
33661
|
-
function trySpawnShellScriptLogRedirectWin(command, env,
|
|
34034
|
+
function trySpawnShellScriptLogRedirectWin(command, env, cwd, signal) {
|
|
33662
34035
|
const tmpRoot = fs19.mkdtempSync(path23.join(tmpdir2(), "ba-devsrv-sh-"));
|
|
33663
34036
|
const logPath = path23.join(tmpRoot, "combined.log");
|
|
33664
34037
|
const runnerPath = path23.join(tmpRoot, "_run.bat");
|
|
@@ -33668,7 +34041,7 @@ function trySpawnShellScriptLogRedirectWin(command, env, cwd3, signal) {
|
|
|
33668
34041
|
fs19.writeFileSync(
|
|
33669
34042
|
runnerPath,
|
|
33670
34043
|
`@ECHO OFF\r
|
|
33671
|
-
CD /D ${q(
|
|
34044
|
+
CD /D ${q(cwd)}\r
|
|
33672
34045
|
${command} >> ${q(logPath)} 2>&1\r
|
|
33673
34046
|
`
|
|
33674
34047
|
);
|
|
@@ -33694,10 +34067,10 @@ ${command} >> ${q(logPath)} 2>&1\r
|
|
|
33694
34067
|
|
|
33695
34068
|
// src/dev-servers/manager/shell-spawn/try-spawn-inherit.ts
|
|
33696
34069
|
import { spawn as spawn9 } from "node:child_process";
|
|
33697
|
-
function trySpawnInheritStdio(command, env,
|
|
34070
|
+
function trySpawnInheritStdio(command, env, cwd, signal) {
|
|
33698
34071
|
const opts = {
|
|
33699
34072
|
env,
|
|
33700
|
-
cwd
|
|
34073
|
+
cwd,
|
|
33701
34074
|
stdio: "inherit",
|
|
33702
34075
|
...signal ? { signal } : {}
|
|
33703
34076
|
};
|
|
@@ -33713,27 +34086,27 @@ function trySpawnInheritStdio(command, env, cwd3, signal) {
|
|
|
33713
34086
|
}
|
|
33714
34087
|
|
|
33715
34088
|
// src/dev-servers/manager/shell-spawn/shell-spawn.ts
|
|
33716
|
-
function shellSpawn(command, env,
|
|
34089
|
+
function shellSpawn(command, env, cwd, options) {
|
|
33717
34090
|
const signal = options?.signal;
|
|
33718
|
-
const piped = trySpawnPipedViaSh(command, env,
|
|
34091
|
+
const piped = trySpawnPipedViaSh(command, env, cwd, signal);
|
|
33719
34092
|
if (piped.ok) {
|
|
33720
34093
|
return piped.result;
|
|
33721
34094
|
}
|
|
33722
34095
|
let lastErr = piped.lastErr;
|
|
33723
|
-
const shellTrueProc = trySpawnShellTruePiped(command, env,
|
|
34096
|
+
const shellTrueProc = trySpawnShellTruePiped(command, env, cwd, devNullReadFd(), signal);
|
|
33724
34097
|
if (shellTrueProc) {
|
|
33725
34098
|
return { proc: shellTrueProc, pipedStdoutStderr: true };
|
|
33726
34099
|
}
|
|
33727
|
-
const fileCapture = trySpawnMergedLogFile(command, env,
|
|
34100
|
+
const fileCapture = trySpawnMergedLogFile(command, env, cwd, signal);
|
|
33728
34101
|
if (fileCapture) {
|
|
33729
34102
|
return fileCapture;
|
|
33730
34103
|
}
|
|
33731
|
-
const scriptCapture = process.platform === "win32" ? trySpawnShellScriptLogRedirectWin(command, env,
|
|
34104
|
+
const scriptCapture = process.platform === "win32" ? trySpawnShellScriptLogRedirectWin(command, env, cwd, signal) : trySpawnShellScriptLogRedirectUnix(command, env, cwd, signal);
|
|
33732
34105
|
if (scriptCapture) {
|
|
33733
34106
|
return scriptCapture;
|
|
33734
34107
|
}
|
|
33735
34108
|
try {
|
|
33736
|
-
return trySpawnInheritStdio(command, env,
|
|
34109
|
+
return trySpawnInheritStdio(command, env, cwd, signal);
|
|
33737
34110
|
} catch (e) {
|
|
33738
34111
|
throw lastErr instanceof Error ? lastErr : e instanceof Error ? e : new Error(String(e));
|
|
33739
34112
|
}
|
|
@@ -33791,9 +34164,11 @@ var DevServerManager = class {
|
|
|
33791
34164
|
abortControllersByServerId = /* @__PURE__ */ new Map();
|
|
33792
34165
|
getWs;
|
|
33793
34166
|
log;
|
|
34167
|
+
getBridgeCwd;
|
|
33794
34168
|
constructor(options) {
|
|
33795
34169
|
this.getWs = options.getWs;
|
|
33796
34170
|
this.log = options.log;
|
|
34171
|
+
this.getBridgeCwd = options.getBridgeCwd ?? (() => process.cwd());
|
|
33797
34172
|
}
|
|
33798
34173
|
attachFirehose(send) {
|
|
33799
34174
|
this.firehoseSend = send;
|
|
@@ -33917,7 +34292,7 @@ var DevServerManager = class {
|
|
|
33917
34292
|
this.sendStatus(serverId, "starting", void 0, emptyTails());
|
|
33918
34293
|
const ac = new AbortController();
|
|
33919
34294
|
this.abortControllersByServerId.set(serverId, ac);
|
|
33920
|
-
const
|
|
34295
|
+
const cwd = this.getBridgeCwd();
|
|
33921
34296
|
const childEnv = envForSpawn(process.env, def.env, def.ports);
|
|
33922
34297
|
const cmd = substituteCommand(def.command.trim(), childEnv);
|
|
33923
34298
|
const title = def.name.trim() || serverId.slice(0, 8);
|
|
@@ -33932,7 +34307,7 @@ var DevServerManager = class {
|
|
|
33932
34307
|
let mergedLogPath;
|
|
33933
34308
|
let mergedCleanupDir;
|
|
33934
34309
|
try {
|
|
33935
|
-
const spawned = shellSpawn(cmd, childEnv,
|
|
34310
|
+
const spawned = shellSpawn(cmd, childEnv, cwd, {
|
|
33936
34311
|
signal: ac.signal
|
|
33937
34312
|
});
|
|
33938
34313
|
proc = spawned.proc;
|
|
@@ -33941,7 +34316,7 @@ var DevServerManager = class {
|
|
|
33941
34316
|
mergedCleanupDir = spawned.mergedLogCleanupDir;
|
|
33942
34317
|
} catch (e) {
|
|
33943
34318
|
const msg = e instanceof Error ? e.message : String(e);
|
|
33944
|
-
this.log(`[dev-server] ${title}
|
|
34319
|
+
this.log(`[dev-server] Failed to start ${title}: ${msg}`);
|
|
33945
34320
|
this.abortControllersByServerId.delete(serverId);
|
|
33946
34321
|
this.sendStatus(serverId, "error", msg, emptyTails());
|
|
33947
34322
|
return;
|
|
@@ -34017,7 +34392,9 @@ var DevServerManager = class {
|
|
|
34017
34392
|
async shutdownAllGraceful() {
|
|
34018
34393
|
const pairs = [...this.processes.entries()];
|
|
34019
34394
|
if (pairs.length === 0) return;
|
|
34020
|
-
this.log(
|
|
34395
|
+
this.log(
|
|
34396
|
+
`[dev-server] Stopping ${pairs.length} local dev server process${pairs.length === 1 ? "" : "es"}\u2026`
|
|
34397
|
+
);
|
|
34021
34398
|
await Promise.all(pairs.map(([serverId, proc]) => this.gracefulTerminateOrUnknown(serverId, proc)));
|
|
34022
34399
|
}
|
|
34023
34400
|
async gracefulTerminateOrUnknown(serverId, proc) {
|
|
@@ -34173,7 +34550,7 @@ function startStreamingProxy(ws, log2, pr) {
|
|
|
34173
34550
|
},
|
|
34174
34551
|
onEnd: () => sendWsMessage(ws, { type: "proxy_result_end", id: pr.id }),
|
|
34175
34552
|
onError: (error40) => {
|
|
34176
|
-
log2(`[Proxy and log service]
|
|
34553
|
+
log2(`[Proxy and log service] Streaming preview failed: ${error40}`);
|
|
34177
34554
|
sendWsMessage(ws, { type: "proxy_result_error", id: pr.id, error: error40 });
|
|
34178
34555
|
}
|
|
34179
34556
|
});
|
|
@@ -34246,11 +34623,11 @@ var handleProxyMessage = (msg, deps) => {
|
|
|
34246
34623
|
return;
|
|
34247
34624
|
}
|
|
34248
34625
|
void proxyToLocal(pr).then((res) => {
|
|
34249
|
-
if (res.error) deps.log(`[Proxy and log service]
|
|
34626
|
+
if (res.error) deps.log(`[Proxy and log service] Preview proxy failed: ${res.error}`);
|
|
34250
34627
|
sendWsMessage(deps.ws, { type: "proxy_result", ...res, id: pr.id });
|
|
34251
34628
|
}).catch((err) => {
|
|
34252
34629
|
deps.log(
|
|
34253
|
-
`[Proxy and log service]
|
|
34630
|
+
`[Proxy and log service] Preview proxy failed: ${err instanceof Error ? err.message : String(err)}`
|
|
34254
34631
|
);
|
|
34255
34632
|
sendWsMessage(deps.ws, { type: "proxy_result", id: pr.id, error: String(err) });
|
|
34256
34633
|
});
|
|
@@ -34287,14 +34664,23 @@ function tryConsumeBinaryProxyBody(raw, deps) {
|
|
|
34287
34664
|
}
|
|
34288
34665
|
|
|
34289
34666
|
// src/firehose/connect-firehose.ts
|
|
34667
|
+
var FIREHOSE_CLIENT_PING_MS = 25e3;
|
|
34290
34668
|
function connectFirehose(options) {
|
|
34291
34669
|
const { firehoseServerUrl, workspaceId, bridgeName, proxyPorts, log: log2, devServerManager, onOpen, onClose } = options;
|
|
34292
34670
|
const wsUrl = buildFirehoseCliWsUrl(firehoseServerUrl);
|
|
34293
|
-
|
|
34671
|
+
applyCliOutboundNetworkPreferences();
|
|
34672
|
+
const wsOptions = { perMessageDeflate: false, family: 4 };
|
|
34294
34673
|
if (wsUrl.startsWith("wss://")) {
|
|
34295
|
-
wsOptions.agent = new https2.Agent({ rejectUnauthorized: false });
|
|
34674
|
+
wsOptions.agent = new https2.Agent({ rejectUnauthorized: false, family: 4 });
|
|
34296
34675
|
}
|
|
34297
34676
|
const ws = new wrapper_default(wsUrl, wsOptions);
|
|
34677
|
+
let clientPingTimer = null;
|
|
34678
|
+
function clearClientPing() {
|
|
34679
|
+
if (clientPingTimer != null) {
|
|
34680
|
+
clearInterval(clientPingTimer);
|
|
34681
|
+
clientPingTimer = null;
|
|
34682
|
+
}
|
|
34683
|
+
}
|
|
34298
34684
|
const firehoseSend = (payload) => {
|
|
34299
34685
|
sendWsMessage(ws, payload);
|
|
34300
34686
|
};
|
|
@@ -34307,52 +34693,54 @@ function connectFirehose(options) {
|
|
|
34307
34693
|
startStreamingProxy: (pr) => startStreamingProxy(ws, log2, pr)
|
|
34308
34694
|
};
|
|
34309
34695
|
ws.on("open", () => {
|
|
34696
|
+
clearClientPing();
|
|
34697
|
+
clientPingTimer = setInterval(() => {
|
|
34698
|
+
if (ws.readyState === wrapper_default.OPEN) {
|
|
34699
|
+
try {
|
|
34700
|
+
ws.ping();
|
|
34701
|
+
} catch {
|
|
34702
|
+
}
|
|
34703
|
+
}
|
|
34704
|
+
}, FIREHOSE_CLIENT_PING_MS);
|
|
34310
34705
|
onOpen?.();
|
|
34311
34706
|
devServerManager.attachFirehose(firehoseSend);
|
|
34312
34707
|
sendWsMessage(ws, { type: "identify", workspaceId, bridgeName, proxyPorts });
|
|
34313
34708
|
});
|
|
34314
34709
|
ws.on("message", (raw) => {
|
|
34315
|
-
|
|
34316
|
-
|
|
34317
|
-
|
|
34318
|
-
|
|
34319
|
-
|
|
34320
|
-
|
|
34321
|
-
|
|
34322
|
-
|
|
34323
|
-
}
|
|
34324
|
-
});
|
|
34710
|
+
if (Buffer.isBuffer(raw) && tryConsumeBinaryProxyBody(raw, deps)) {
|
|
34711
|
+
return;
|
|
34712
|
+
}
|
|
34713
|
+
try {
|
|
34714
|
+
const text = Buffer.isBuffer(raw) ? raw.toString("utf8") : String(raw);
|
|
34715
|
+
dispatchFirehoseJsonMessage(JSON.parse(text), deps);
|
|
34716
|
+
} catch {
|
|
34717
|
+
}
|
|
34325
34718
|
});
|
|
34326
34719
|
ws.on("close", (code, reason) => {
|
|
34720
|
+
clearClientPing();
|
|
34327
34721
|
devServerManager.detachFirehose();
|
|
34328
|
-
|
|
34329
|
-
|
|
34330
|
-
"[Proxy and log service]",
|
|
34331
|
-
code,
|
|
34332
|
-
typeof reason === "string" ? reason : reason.toString(),
|
|
34333
|
-
"reconnects automatically if the bridge service stays connected"
|
|
34334
|
-
)
|
|
34335
|
-
);
|
|
34336
|
-
onClose?.();
|
|
34722
|
+
const reasonStr = typeof reason === "string" ? reason : reason.toString();
|
|
34723
|
+
onClose?.(code, reasonStr);
|
|
34337
34724
|
});
|
|
34338
34725
|
ws.on("error", (err) => {
|
|
34726
|
+
clearClientPing();
|
|
34339
34727
|
log2(`[Proxy and log service] WebSocket error: ${err.message}`);
|
|
34340
34728
|
});
|
|
34341
34729
|
return {
|
|
34342
34730
|
close() {
|
|
34731
|
+
clearClientPing();
|
|
34343
34732
|
devServerManager.detachFirehose();
|
|
34344
34733
|
try {
|
|
34345
34734
|
ws.removeAllListeners();
|
|
34346
34735
|
ws.close();
|
|
34347
34736
|
} catch {
|
|
34348
34737
|
}
|
|
34349
|
-
}
|
|
34738
|
+
},
|
|
34739
|
+
isConnected: () => ws.readyState === wrapper_default.OPEN
|
|
34350
34740
|
};
|
|
34351
34741
|
}
|
|
34352
34742
|
|
|
34353
34743
|
// src/bridge/connection/create-bridge-identified-handler.ts
|
|
34354
|
-
var FH_RECONNECT_BASE_MS = 2e3;
|
|
34355
|
-
var FH_RECONNECT_MAX_MS = 3e4;
|
|
34356
34744
|
function createOnBridgeIdentified(opts) {
|
|
34357
34745
|
const { sessionWorktreeManager, devServerManager, firehoseServerUrl, workspaceId, state, logFn } = opts;
|
|
34358
34746
|
function clearFirehoseReconnectTimer() {
|
|
@@ -34361,9 +34749,20 @@ function createOnBridgeIdentified(opts) {
|
|
|
34361
34749
|
state.firehoseReconnectTimeout = null;
|
|
34362
34750
|
}
|
|
34363
34751
|
}
|
|
34752
|
+
function firehoseCtx() {
|
|
34753
|
+
return {
|
|
34754
|
+
closedByUser: state.closedByUser,
|
|
34755
|
+
currentWs: state.currentWs,
|
|
34756
|
+
firehoseHandle: state.firehoseHandle,
|
|
34757
|
+
firehoseQuiet: state.firehoseQuiet
|
|
34758
|
+
};
|
|
34759
|
+
}
|
|
34364
34760
|
function attachFirehose(params) {
|
|
34365
34761
|
state.lastFirehoseParams = params;
|
|
34366
34762
|
clearFirehoseReconnectTimer();
|
|
34763
|
+
if (state.firehoseReconnectAttempt === 0) {
|
|
34764
|
+
logFn("Connecting to preview tunnel (local HTTP proxy and dev logs)\u2026");
|
|
34765
|
+
}
|
|
34367
34766
|
state.firehoseGeneration += 1;
|
|
34368
34767
|
const myGen = state.firehoseGeneration;
|
|
34369
34768
|
if (state.firehoseHandle) {
|
|
@@ -34379,37 +34778,52 @@ function createOnBridgeIdentified(opts) {
|
|
|
34379
34778
|
devServerManager,
|
|
34380
34779
|
onOpen: () => {
|
|
34381
34780
|
if (myGen !== state.firehoseGeneration) return;
|
|
34781
|
+
clearFirehoseReconnectQuietOnOpen({ firehoseQuiet: state.firehoseQuiet }, logFn);
|
|
34782
|
+
const logOpenAsFirehoseReconnect = state.firehoseReconnectAttempt > 0;
|
|
34382
34783
|
state.firehoseReconnectAttempt = 0;
|
|
34784
|
+
if (!logOpenAsFirehoseReconnect) {
|
|
34785
|
+
logFn("Connected to preview tunnel (local HTTP proxy and dev logs).");
|
|
34786
|
+
}
|
|
34383
34787
|
},
|
|
34384
|
-
onClose: () => {
|
|
34788
|
+
onClose: (code, reason) => {
|
|
34385
34789
|
if (myGen !== state.firehoseGeneration) return;
|
|
34386
34790
|
state.firehoseHandle = null;
|
|
34387
34791
|
if (state.closedByUser) return;
|
|
34388
34792
|
const main2 = state.currentWs;
|
|
34389
34793
|
if (!main2 || main2.readyState !== wrapper_default.OPEN) {
|
|
34390
|
-
logFn(
|
|
34794
|
+
logFn(
|
|
34795
|
+
`${PROXY_AND_LOG_SERVICE_LABEL} Not reconnecting preview and log stream: main bridge connection is not open.`
|
|
34796
|
+
);
|
|
34391
34797
|
return;
|
|
34392
34798
|
}
|
|
34799
|
+
beginFirehoseDeferredDisconnect(firehoseCtx(), code, reason, logFn);
|
|
34393
34800
|
clearFirehoseReconnectTimer();
|
|
34394
|
-
const delay2 =
|
|
34395
|
-
FH_RECONNECT_BASE_MS * 2 ** state.firehoseReconnectAttempt,
|
|
34396
|
-
FH_RECONNECT_MAX_MS
|
|
34397
|
-
);
|
|
34801
|
+
const delay2 = reconnectDelayMs(state.firehoseReconnectAttempt);
|
|
34398
34802
|
state.firehoseReconnectAttempt += 1;
|
|
34399
|
-
|
|
34400
|
-
|
|
34803
|
+
logNextReconnectAttempt(
|
|
34804
|
+
logFn,
|
|
34805
|
+
PROXY_AND_LOG_SERVICE_LABEL,
|
|
34806
|
+
state.firehoseQuiet,
|
|
34807
|
+
delay2,
|
|
34808
|
+
state.firehoseReconnectAttempt
|
|
34401
34809
|
);
|
|
34402
34810
|
state.firehoseReconnectTimeout = setTimeout(() => {
|
|
34403
34811
|
state.firehoseReconnectTimeout = null;
|
|
34404
34812
|
if (state.closedByUser) return;
|
|
34405
34813
|
const w = state.currentWs;
|
|
34406
34814
|
if (!w || w.readyState !== wrapper_default.OPEN) {
|
|
34407
|
-
|
|
34815
|
+
if (state.firehoseQuiet.verboseLogs) {
|
|
34816
|
+
logFn(
|
|
34817
|
+
`${PROXY_AND_LOG_SERVICE_LABEL} Reconnect skipped: main bridge connection closed before preview stream could reconnect.`
|
|
34818
|
+
);
|
|
34819
|
+
}
|
|
34408
34820
|
return;
|
|
34409
34821
|
}
|
|
34410
34822
|
const p = state.lastFirehoseParams;
|
|
34411
34823
|
if (!p) {
|
|
34412
|
-
|
|
34824
|
+
if (state.firehoseQuiet.verboseLogs) {
|
|
34825
|
+
logFn(`${PROXY_AND_LOG_SERVICE_LABEL} Reconnect skipped: no stored connection parameters.`);
|
|
34826
|
+
}
|
|
34413
34827
|
return;
|
|
34414
34828
|
}
|
|
34415
34829
|
attachFirehose(p);
|
|
@@ -34457,6 +34871,7 @@ var CHECKS = {
|
|
|
34457
34871
|
return false;
|
|
34458
34872
|
}
|
|
34459
34873
|
},
|
|
34874
|
+
/** Bridge spawns `@agentclientprotocol/claude-agent-acp` via npx; detection is “Claude toolchain likely present”. */
|
|
34460
34875
|
"claude-code": async () => {
|
|
34461
34876
|
try {
|
|
34462
34877
|
await execFileAsync4("which", ["claude"], { timeout: 4e3 });
|
|
@@ -34492,10 +34907,12 @@ function createSendLocalSkillsReport(getWs, logFn) {
|
|
|
34492
34907
|
try {
|
|
34493
34908
|
const socket = getWs();
|
|
34494
34909
|
if (!socket || socket.readyState !== wrapper_default.OPEN) return;
|
|
34495
|
-
const skills2 = discoverLocalSkills(
|
|
34910
|
+
const skills2 = discoverLocalSkills(getBridgeWorkspaceDirectory());
|
|
34496
34911
|
socket.send(JSON.stringify({ type: "local_skills", skills: skills2 }));
|
|
34497
34912
|
} catch (e) {
|
|
34498
|
-
logFn(
|
|
34913
|
+
logFn(
|
|
34914
|
+
`[Bridge service] Local skills report failed: ${e instanceof Error ? e.message : String(e)}`
|
|
34915
|
+
);
|
|
34499
34916
|
}
|
|
34500
34917
|
};
|
|
34501
34918
|
}
|
|
@@ -34508,12 +34925,15 @@ function createReportAutoDetectedAgents(getWs, logFn) {
|
|
|
34508
34925
|
sendWsMessage(socket, { type: "auto_detected_agents_report", agentTypes: types });
|
|
34509
34926
|
}
|
|
34510
34927
|
} catch (e) {
|
|
34511
|
-
logFn(
|
|
34928
|
+
logFn(
|
|
34929
|
+
`[Bridge service] Auto-detected agents report failed: ${e instanceof Error ? e.message : String(e)}`
|
|
34930
|
+
);
|
|
34512
34931
|
}
|
|
34513
34932
|
};
|
|
34514
34933
|
}
|
|
34515
34934
|
|
|
34516
34935
|
// src/bridge/connection/create-bridge-connection.ts
|
|
34936
|
+
var BRIDGE_CLIENT_PING_MS = 25e3;
|
|
34517
34937
|
async function createBridgeConnection(options) {
|
|
34518
34938
|
const { apiUrl, workspaceId, justAuthenticated, onAuthInvalid, persistTokens } = options;
|
|
34519
34939
|
const firehoseServerUrl = options.firehoseServerUrl ?? options.proxyServerUrl;
|
|
@@ -34524,13 +34944,16 @@ async function createBridgeConnection(options) {
|
|
|
34524
34944
|
const state = {
|
|
34525
34945
|
closedByUser: false,
|
|
34526
34946
|
reconnectAttempt: 0,
|
|
34947
|
+
logBridgeOpenAsReconnect: false,
|
|
34527
34948
|
reconnectTimeout: null,
|
|
34528
34949
|
currentWs: null,
|
|
34950
|
+
mainQuiet: createEmptyReconnectQuietSlot(),
|
|
34529
34951
|
firehoseHandle: null,
|
|
34530
34952
|
lastFirehoseParams: null,
|
|
34531
34953
|
firehoseReconnectTimeout: null,
|
|
34532
34954
|
firehoseReconnectAttempt: 0,
|
|
34533
|
-
firehoseGeneration: 0
|
|
34955
|
+
firehoseGeneration: 0,
|
|
34956
|
+
firehoseQuiet: createEmptyReconnectQuietSlot()
|
|
34534
34957
|
};
|
|
34535
34958
|
const worktreesRootAbs = options.worktreesRootAbs ?? defaultWorktreesRootAbs();
|
|
34536
34959
|
const sessionWorktreeManager = new SessionWorktreeManager({
|
|
@@ -34542,7 +34965,7 @@ async function createBridgeConnection(options) {
|
|
|
34542
34965
|
function getWs() {
|
|
34543
34966
|
return state.currentWs;
|
|
34544
34967
|
}
|
|
34545
|
-
const devServerManager = new DevServerManager({ getWs, log: logFn });
|
|
34968
|
+
const devServerManager = new DevServerManager({ getWs, log: logFn, getBridgeCwd: getBridgeWorkspaceDirectory });
|
|
34546
34969
|
const onBridgeIdentified = createOnBridgeIdentified({
|
|
34547
34970
|
sessionWorktreeManager,
|
|
34548
34971
|
devServerManager,
|
|
@@ -34554,7 +34977,13 @@ async function createBridgeConnection(options) {
|
|
|
34554
34977
|
const sendLocalSkillsReport = createSendLocalSkillsReport(getWs, logFn);
|
|
34555
34978
|
const reportAutoDetectedAgents = createReportAutoDetectedAgents(getWs, logFn);
|
|
34556
34979
|
function handleOpen() {
|
|
34980
|
+
const logOpenAsPostRefreshReconnect = state.logBridgeOpenAsReconnect;
|
|
34981
|
+
clearMainBridgeReconnectQuietOnOpen(state, logFn);
|
|
34557
34982
|
state.reconnectAttempt = 0;
|
|
34983
|
+
state.logBridgeOpenAsReconnect = false;
|
|
34984
|
+
if (!logOpenAsPostRefreshReconnect) {
|
|
34985
|
+
logFn("Connected to bridge service.");
|
|
34986
|
+
}
|
|
34558
34987
|
const socket = getWs();
|
|
34559
34988
|
if (socket) {
|
|
34560
34989
|
sendWsMessage(socket, { type: "identify", role: "cli" });
|
|
@@ -34571,11 +35000,9 @@ async function createBridgeConnection(options) {
|
|
|
34571
35000
|
state.currentWs = null;
|
|
34572
35001
|
if (was) was.removeAllListeners();
|
|
34573
35002
|
const willReconnect = !state.closedByUser;
|
|
34574
|
-
logFn
|
|
34575
|
-
formatWebSocketClose("[Bridge service]", code, reason, willReconnect ? "will schedule reconnect" : "not reconnecting (CLI shutting down)")
|
|
34576
|
-
);
|
|
35003
|
+
beginMainBridgeDeferredDisconnect(state, code, reason, logFn, willReconnect);
|
|
34577
35004
|
if (willReconnect) {
|
|
34578
|
-
|
|
35005
|
+
scheduleMainBridgeReconnect(state, connect, logFn);
|
|
34579
35006
|
}
|
|
34580
35007
|
}
|
|
34581
35008
|
const messageDeps = {
|
|
@@ -34590,6 +35017,13 @@ async function createBridgeConnection(options) {
|
|
|
34590
35017
|
};
|
|
34591
35018
|
function connect() {
|
|
34592
35019
|
if (state.closedByUser) return;
|
|
35020
|
+
if (state.reconnectTimeout != null) {
|
|
35021
|
+
clearTimeout(state.reconnectTimeout);
|
|
35022
|
+
state.reconnectTimeout = null;
|
|
35023
|
+
}
|
|
35024
|
+
if (state.reconnectAttempt === 0) {
|
|
35025
|
+
logFn("Connecting to bridge service\u2026");
|
|
35026
|
+
}
|
|
34593
35027
|
const prev = state.currentWs;
|
|
34594
35028
|
if (prev) {
|
|
34595
35029
|
prev.removeAllListeners();
|
|
@@ -34602,6 +35036,7 @@ async function createBridgeConnection(options) {
|
|
|
34602
35036
|
const url2 = buildBridgeUrl(apiUrl, workspaceId, accessToken);
|
|
34603
35037
|
state.currentWs = createWsBridge({
|
|
34604
35038
|
url: url2,
|
|
35039
|
+
clientPingIntervalMs: BRIDGE_CLIENT_PING_MS,
|
|
34605
35040
|
onAuthInvalid: () => {
|
|
34606
35041
|
if (authRefreshInFlight) return;
|
|
34607
35042
|
void (async () => {
|
|
@@ -34613,8 +35048,9 @@ async function createBridgeConnection(options) {
|
|
|
34613
35048
|
accessToken = next.token;
|
|
34614
35049
|
refreshTok = next.refreshToken;
|
|
34615
35050
|
persistTokens?.({ token: accessToken, refreshToken: refreshTok });
|
|
34616
|
-
logFn("[Bridge service]
|
|
35051
|
+
logFn("[Bridge service] Access token refreshed; reconnecting\u2026");
|
|
34617
35052
|
state.reconnectAttempt = 0;
|
|
35053
|
+
state.logBridgeOpenAsReconnect = true;
|
|
34618
35054
|
authRefreshInFlight = false;
|
|
34619
35055
|
connect();
|
|
34620
35056
|
return;
|
|
@@ -34637,7 +35073,7 @@ async function createBridgeConnection(options) {
|
|
|
34637
35073
|
});
|
|
34638
35074
|
}
|
|
34639
35075
|
connect();
|
|
34640
|
-
const stopFileIndexWatcher = startFileIndexWatcher(
|
|
35076
|
+
const stopFileIndexWatcher = startFileIndexWatcher(getBridgeWorkspaceDirectory());
|
|
34641
35077
|
return {
|
|
34642
35078
|
close: async () => {
|
|
34643
35079
|
stopFileIndexWatcher();
|
|
@@ -34660,15 +35096,17 @@ async function runBridge(options) {
|
|
|
34660
35096
|
onAuth: (_auth) => {
|
|
34661
35097
|
}
|
|
34662
35098
|
});
|
|
34663
|
-
const onSignal2 = (
|
|
34664
|
-
logImmediate(
|
|
35099
|
+
const onSignal2 = (kind) => {
|
|
35100
|
+
logImmediate(
|
|
35101
|
+
kind === "interrupt" ? "Keyboard interrupt (Ctrl+C) \u2014 stopping\u2026" : "Stop requested \u2014 shutting down\u2026"
|
|
35102
|
+
);
|
|
34665
35103
|
setImmediate(() => {
|
|
34666
35104
|
handle2.close();
|
|
34667
35105
|
process.exit(0);
|
|
34668
35106
|
});
|
|
34669
35107
|
};
|
|
34670
|
-
const onSigInt2 = () => onSignal2("
|
|
34671
|
-
const onSigTerm2 = () => onSignal2("
|
|
35108
|
+
const onSigInt2 = () => onSignal2("interrupt");
|
|
35109
|
+
const onSigTerm2 = () => onSignal2("stop");
|
|
34672
35110
|
process.on("SIGINT", onSigInt2);
|
|
34673
35111
|
process.on("SIGTERM", onSigTerm2);
|
|
34674
35112
|
const auth = await handle2.authPromise;
|
|
@@ -34710,23 +35148,25 @@ async function runBridge(options) {
|
|
|
34710
35148
|
});
|
|
34711
35149
|
},
|
|
34712
35150
|
onAuthInvalid: () => {
|
|
34713
|
-
log("[Bridge service] token invalid or revoked; re-authenticating\u2026");
|
|
35151
|
+
log("[Bridge service] Access token invalid or revoked; re-authenticating\u2026");
|
|
34714
35152
|
clearConfigForApi(apiUrl);
|
|
34715
35153
|
void handle.close().then(() => {
|
|
34716
35154
|
void runBridge({ apiUrl, firehoseServerUrl, worktreesRootAbs });
|
|
34717
35155
|
});
|
|
34718
35156
|
}
|
|
34719
35157
|
});
|
|
34720
|
-
const onSignal = (
|
|
34721
|
-
logImmediate(
|
|
35158
|
+
const onSignal = (kind) => {
|
|
35159
|
+
logImmediate(
|
|
35160
|
+
kind === "interrupt" ? "Keyboard interrupt (Ctrl+C) \u2014 stopping\u2026" : "Stop requested \u2014 shutting down\u2026"
|
|
35161
|
+
);
|
|
34722
35162
|
setImmediate(() => {
|
|
34723
35163
|
void handle.close().then(() => {
|
|
34724
35164
|
process.exit(0);
|
|
34725
35165
|
});
|
|
34726
35166
|
});
|
|
34727
35167
|
};
|
|
34728
|
-
const onSigInt = () => onSignal("
|
|
34729
|
-
const onSigTerm = () => onSignal("
|
|
35168
|
+
const onSigInt = () => onSignal("interrupt");
|
|
35169
|
+
const onSigTerm = () => onSignal("stop");
|
|
34730
35170
|
process.on("SIGINT", onSigInt);
|
|
34731
35171
|
process.on("SIGTERM", onSigTerm);
|
|
34732
35172
|
}
|
|
@@ -34768,6 +35208,7 @@ async function main() {
|
|
|
34768
35208
|
}
|
|
34769
35209
|
process.chdir(resolvedCwd);
|
|
34770
35210
|
}
|
|
35211
|
+
initBridgeWorkspaceDirectory();
|
|
34771
35212
|
let worktreesRootAbs;
|
|
34772
35213
|
if (opts.worktreesRoot && opts.worktreesRoot.trim()) {
|
|
34773
35214
|
worktreesRootAbs = path24.resolve(opts.worktreesRoot.trim());
|