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