@buildautomaton/cli 0.1.4 → 0.1.6

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