@buildautomaton/cli 0.1.23 → 0.1.25

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
@@ -973,8 +973,8 @@ var require_command = __commonJS({
973
973
  "../../node_modules/.pnpm/commander@12.1.0/node_modules/commander/lib/command.js"(exports) {
974
974
  var EventEmitter2 = __require("node:events").EventEmitter;
975
975
  var childProcess2 = __require("node:child_process");
976
- var path36 = __require("node:path");
977
- var fs35 = __require("node:fs");
976
+ var path37 = __require("node:path");
977
+ var fs36 = __require("node:fs");
978
978
  var process8 = __require("node:process");
979
979
  var { Argument: Argument2, humanReadableArgName } = require_argument();
980
980
  var { CommanderError: CommanderError2 } = require_error();
@@ -1906,11 +1906,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
1906
1906
  let launchWithNode = false;
1907
1907
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
1908
1908
  function findFile(baseDir, baseName) {
1909
- const localBin = path36.resolve(baseDir, baseName);
1910
- if (fs35.existsSync(localBin)) return localBin;
1911
- if (sourceExt.includes(path36.extname(baseName))) return void 0;
1909
+ const localBin = path37.resolve(baseDir, baseName);
1910
+ if (fs36.existsSync(localBin)) return localBin;
1911
+ if (sourceExt.includes(path37.extname(baseName))) return void 0;
1912
1912
  const foundExt = sourceExt.find(
1913
- (ext) => fs35.existsSync(`${localBin}${ext}`)
1913
+ (ext) => fs36.existsSync(`${localBin}${ext}`)
1914
1914
  );
1915
1915
  if (foundExt) return `${localBin}${foundExt}`;
1916
1916
  return void 0;
@@ -1922,21 +1922,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
1922
1922
  if (this._scriptPath) {
1923
1923
  let resolvedScriptPath;
1924
1924
  try {
1925
- resolvedScriptPath = fs35.realpathSync(this._scriptPath);
1925
+ resolvedScriptPath = fs36.realpathSync(this._scriptPath);
1926
1926
  } catch (err) {
1927
1927
  resolvedScriptPath = this._scriptPath;
1928
1928
  }
1929
- executableDir = path36.resolve(
1930
- path36.dirname(resolvedScriptPath),
1929
+ executableDir = path37.resolve(
1930
+ path37.dirname(resolvedScriptPath),
1931
1931
  executableDir
1932
1932
  );
1933
1933
  }
1934
1934
  if (executableDir) {
1935
1935
  let localFile = findFile(executableDir, executableFile);
1936
1936
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
1937
- const legacyName = path36.basename(
1937
+ const legacyName = path37.basename(
1938
1938
  this._scriptPath,
1939
- path36.extname(this._scriptPath)
1939
+ path37.extname(this._scriptPath)
1940
1940
  );
1941
1941
  if (legacyName !== this._name) {
1942
1942
  localFile = findFile(
@@ -1947,7 +1947,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1947
1947
  }
1948
1948
  executableFile = localFile || executableFile;
1949
1949
  }
1950
- launchWithNode = sourceExt.includes(path36.extname(executableFile));
1950
+ launchWithNode = sourceExt.includes(path37.extname(executableFile));
1951
1951
  let proc;
1952
1952
  if (process8.platform !== "win32") {
1953
1953
  if (launchWithNode) {
@@ -2787,7 +2787,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2787
2787
  * @return {Command}
2788
2788
  */
2789
2789
  nameFromFilename(filename) {
2790
- this._name = path36.basename(filename, path36.extname(filename));
2790
+ this._name = path37.basename(filename, path37.extname(filename));
2791
2791
  return this;
2792
2792
  }
2793
2793
  /**
@@ -2801,9 +2801,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
2801
2801
  * @param {string} [path]
2802
2802
  * @return {(string|null|Command)}
2803
2803
  */
2804
- executableDir(path37) {
2805
- if (path37 === void 0) return this._executableDir;
2806
- this._executableDir = path37;
2804
+ executableDir(path38) {
2805
+ if (path38 === void 0) return this._executableDir;
2806
+ this._executableDir = path38;
2807
2807
  return this;
2808
2808
  }
2809
2809
  /**
@@ -7061,8 +7061,8 @@ var init_parseUtil = __esm({
7061
7061
  init_errors();
7062
7062
  init_en();
7063
7063
  makeIssue = (params) => {
7064
- const { data, path: path36, errorMaps, issueData } = params;
7065
- const fullPath = [...path36, ...issueData.path || []];
7064
+ const { data, path: path37, errorMaps, issueData } = params;
7065
+ const fullPath = [...path37, ...issueData.path || []];
7066
7066
  const fullIssue = {
7067
7067
  ...issueData,
7068
7068
  path: fullPath
@@ -7370,11 +7370,11 @@ var init_types = __esm({
7370
7370
  init_parseUtil();
7371
7371
  init_util();
7372
7372
  ParseInputLazyPath = class {
7373
- constructor(parent, value, path36, key) {
7373
+ constructor(parent, value, path37, key) {
7374
7374
  this._cachedPath = [];
7375
7375
  this.parent = parent;
7376
7376
  this.data = value;
7377
- this._path = path36;
7377
+ this._path = path37;
7378
7378
  this._key = key;
7379
7379
  }
7380
7380
  get path() {
@@ -11235,7 +11235,7 @@ var require_has_flag = __commonJS({
11235
11235
  var require_supports_color = __commonJS({
11236
11236
  "../../node_modules/.pnpm/supports-color@7.2.0/node_modules/supports-color/index.js"(exports, module) {
11237
11237
  "use strict";
11238
- var os7 = __require("os");
11238
+ var os8 = __require("os");
11239
11239
  var tty = __require("tty");
11240
11240
  var hasFlag = require_has_flag();
11241
11241
  var { env } = process;
@@ -11283,7 +11283,7 @@ var require_supports_color = __commonJS({
11283
11283
  return min;
11284
11284
  }
11285
11285
  if (process.platform === "win32") {
11286
- const osRelease = os7.release().split(".");
11286
+ const osRelease = os8.release().split(".");
11287
11287
  if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
11288
11288
  return Number(osRelease[2]) >= 14931 ? 3 : 2;
11289
11289
  }
@@ -11529,10 +11529,10 @@ var require_src2 = __commonJS({
11529
11529
  var fs_1 = __require("fs");
11530
11530
  var debug_1 = __importDefault(require_src());
11531
11531
  var log2 = debug_1.default("@kwsites/file-exists");
11532
- function check2(path36, isFile, isDirectory) {
11533
- log2(`checking %s`, path36);
11532
+ function check2(path37, isFile, isDirectory) {
11533
+ log2(`checking %s`, path37);
11534
11534
  try {
11535
- const stat3 = fs_1.statSync(path36);
11535
+ const stat3 = fs_1.statSync(path37);
11536
11536
  if (stat3.isFile() && isFile) {
11537
11537
  log2(`[OK] path represents a file`);
11538
11538
  return true;
@@ -11552,8 +11552,8 @@ var require_src2 = __commonJS({
11552
11552
  throw e;
11553
11553
  }
11554
11554
  }
11555
- function exists2(path36, type = exports.READABLE) {
11556
- return check2(path36, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
11555
+ function exists2(path37, type = exports.READABLE) {
11556
+ return check2(path37, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
11557
11557
  }
11558
11558
  exports.exists = exists2;
11559
11559
  exports.FILE = 1;
@@ -11850,10 +11850,10 @@ function assignProp(target, prop, value) {
11850
11850
  configurable: true
11851
11851
  });
11852
11852
  }
11853
- function getElementAtPath(obj, path36) {
11854
- if (!path36)
11853
+ function getElementAtPath(obj, path37) {
11854
+ if (!path37)
11855
11855
  return obj;
11856
- return path36.reduce((acc, key) => acc?.[key], obj);
11856
+ return path37.reduce((acc, key) => acc?.[key], obj);
11857
11857
  }
11858
11858
  function promiseAllObject(promisesObj) {
11859
11859
  const keys = Object.keys(promisesObj);
@@ -12102,11 +12102,11 @@ function aborted(x, startIndex = 0) {
12102
12102
  }
12103
12103
  return false;
12104
12104
  }
12105
- function prefixIssues(path36, issues) {
12105
+ function prefixIssues(path37, issues) {
12106
12106
  return issues.map((iss) => {
12107
12107
  var _a2;
12108
12108
  (_a2 = iss).path ?? (_a2.path = []);
12109
- iss.path.unshift(path36);
12109
+ iss.path.unshift(path37);
12110
12110
  return iss;
12111
12111
  });
12112
12112
  }
@@ -12295,7 +12295,7 @@ function treeifyError(error40, _mapper) {
12295
12295
  return issue2.message;
12296
12296
  };
12297
12297
  const result = { errors: [] };
12298
- const processError = (error41, path36 = []) => {
12298
+ const processError = (error41, path37 = []) => {
12299
12299
  var _a2, _b;
12300
12300
  for (const issue2 of error41.issues) {
12301
12301
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -12305,7 +12305,7 @@ function treeifyError(error40, _mapper) {
12305
12305
  } else if (issue2.code === "invalid_element") {
12306
12306
  processError({ issues: issue2.issues }, issue2.path);
12307
12307
  } else {
12308
- const fullpath = [...path36, ...issue2.path];
12308
+ const fullpath = [...path37, ...issue2.path];
12309
12309
  if (fullpath.length === 0) {
12310
12310
  result.errors.push(mapper(issue2));
12311
12311
  continue;
@@ -12335,9 +12335,9 @@ function treeifyError(error40, _mapper) {
12335
12335
  processError(error40);
12336
12336
  return result;
12337
12337
  }
12338
- function toDotPath(path36) {
12338
+ function toDotPath(path37) {
12339
12339
  const segs = [];
12340
- for (const seg of path36) {
12340
+ for (const seg of path37) {
12341
12341
  if (typeof seg === "number")
12342
12342
  segs.push(`[${seg}]`);
12343
12343
  else if (typeof seg === "symbol")
@@ -25064,15 +25064,27 @@ var {
25064
25064
  } = import_index.default;
25065
25065
 
25066
25066
  // src/cli-version.ts
25067
- var CLI_VERSION = "0.1.23".length > 0 ? "0.1.23" : "0.0.0-dev";
25067
+ var CLI_VERSION = "0.1.25".length > 0 ? "0.1.25" : "0.0.0-dev";
25068
25068
 
25069
25069
  // src/cli/defaults.ts
25070
25070
  var DEFAULT_API_URL = process.env.BUILDAUTOMATON_API_URL ?? "https://api.buildautomaton.com";
25071
25071
  var DEFAULT_FIREHOSE_URL = "https://buildautomaton-firehose.fly.dev";
25072
25072
 
25073
25073
  // src/cli/run-cli-action.ts
25074
- import * as fs34 from "node:fs";
25075
- import * as path35 from "node:path";
25074
+ import * as fs35 from "node:fs";
25075
+ import * as path36 from "node:path";
25076
+
25077
+ // src/cli-log-level.ts
25078
+ var verbosity = "info";
25079
+ function setCliLogVerbosity(level) {
25080
+ verbosity = level;
25081
+ }
25082
+ function getCliLogVerbosity() {
25083
+ return verbosity;
25084
+ }
25085
+ function isCliTrace() {
25086
+ return verbosity === "trace";
25087
+ }
25076
25088
 
25077
25089
  // src/config.ts
25078
25090
  import fs from "node:fs";
@@ -25442,15 +25454,26 @@ function getBridgeRoot() {
25442
25454
  }
25443
25455
 
25444
25456
  // src/log.ts
25445
- function log(line) {
25457
+ function timestampPrefix() {
25446
25458
  const time3 = (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
25447
- console.log(`[${time3}] ${line}`);
25459
+ return `[${time3}]`;
25460
+ }
25461
+ function log(line) {
25462
+ console.log(`${timestampPrefix()} ${line}`);
25448
25463
  }
25449
25464
  function logImmediate(line) {
25450
- const time3 = (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
25451
- process.stdout.write(`[${time3}] ${line}
25465
+ process.stdout.write(`${timestampPrefix()} ${line}
25452
25466
  `);
25453
25467
  }
25468
+ function logDebug(line) {
25469
+ const v = getCliLogVerbosity();
25470
+ if (v !== "debug" && v !== "trace") return;
25471
+ console.log(`${timestampPrefix()} [debug] ${line}`);
25472
+ }
25473
+ function logTrace(line) {
25474
+ if (getCliLogVerbosity() !== "trace") return;
25475
+ console.log(`${timestampPrefix()} [trace] ${line}`);
25476
+ }
25454
25477
 
25455
25478
  // src/process-bridge-resilience.ts
25456
25479
  var installed = false;
@@ -25485,7 +25508,7 @@ function applyCliOutboundNetworkPreferences() {
25485
25508
  }
25486
25509
  }
25487
25510
 
25488
- // src/bridge/connection/cli-ws-client.ts
25511
+ // src/connection/cli-ws-client.ts
25489
25512
  import https from "node:https";
25490
25513
  var CLI_WEBSOCKET_CLIENT_PING_MS = 25e3;
25491
25514
  function attachWebSocketClientPing(ws, intervalMs) {
@@ -25545,7 +25568,7 @@ function safeSendWebSocketBinary(ws, data) {
25545
25568
  }
25546
25569
  }
25547
25570
 
25548
- // src/bridge/connection/create-ws-bridge.ts
25571
+ // src/connection/create-ws-bridge.ts
25549
25572
  var BRIDGE_AUTH_ERROR_HEADER = "x-bridge-auth-error";
25550
25573
  var BRIDGE_AUTH_ERROR_TOKEN_INVALID = "token_invalid";
25551
25574
  function createWsBridge(options) {
@@ -26156,29 +26179,22 @@ async function openBrowser(connectionId, initialWorkspaceId, preferredBridgeName
26156
26179
  }
26157
26180
  }
26158
26181
 
26159
- // src/bridge/connection/reconnect/constants.ts
26160
- var RECONNECT_FIRST_MS = 100;
26161
- var RECONNECT_MAX_MS = 3e4;
26182
+ // src/connection/reconnect/constants.ts
26162
26183
  var RECONNECT_QUIET_MS = 2e3;
26163
- function reconnectDelayMs(attemptBeforeIncrement) {
26164
- return Math.min(RECONNECT_FIRST_MS * 2 ** attemptBeforeIncrement, RECONNECT_MAX_MS);
26184
+ var PENDING_AUTH_RECONNECT_FIRST_MS = 100;
26185
+ var PENDING_AUTH_RECONNECT_MAX_MS = 3e4;
26186
+ function pendingAuthReconnectDelayMs(attemptBeforeIncrement) {
26187
+ return Math.min(PENDING_AUTH_RECONNECT_FIRST_MS * 2 ** attemptBeforeIncrement, PENDING_AUTH_RECONNECT_MAX_MS);
26165
26188
  }
26166
26189
 
26167
- // src/bridge/connection/reconnect/format-reconnect-delay-for-log.ts
26190
+ // src/connection/reconnect/format-reconnect-delay-for-log.ts
26168
26191
  function formatReconnectDelayForLog(delayMs) {
26169
26192
  if (delayMs < 1e3) return `${delayMs}ms`;
26170
26193
  const s = delayMs / 1e3;
26171
26194
  return Number.isInteger(s) ? `${s}s` : `${s.toFixed(1)}s`;
26172
26195
  }
26173
26196
 
26174
- // src/bridge/connection/reconnect/log-next-reconnect-attempt.ts
26175
- function logNextReconnectAttempt(log2, serviceLabel, quiet, delayMs, attempt) {
26176
- if (!quiet.verboseLogs) return;
26177
- const delayLabel = formatReconnectDelayForLog(delayMs);
26178
- log2(`${serviceLabel} Next connection attempt in ${delayLabel} (attempt ${attempt}).`);
26179
- }
26180
-
26181
- // src/bridge/connection/ws-close-diagnostics.ts
26197
+ // src/connection/ws-close-diagnostics.ts
26182
26198
  function describeWebSocketCloseCode(code) {
26183
26199
  const known = {
26184
26200
  1e3: "normal closure",
@@ -26209,7 +26225,7 @@ function formatWebSocketClose(label, code, reason, extra) {
26209
26225
  return `${label} Disconnected: code=${code} (${describeWebSocketCloseCode(code)})${reasonPart}${extraPart}`;
26210
26226
  }
26211
26227
 
26212
- // src/bridge/connection/reconnect/reconnect-quiet-slot.ts
26228
+ // src/connection/reconnect/reconnect-quiet-slot.ts
26213
26229
  function createEmptyReconnectQuietSlot() {
26214
26230
  return { timer: null, verboseLogs: false, pendingCloseLog: null };
26215
26231
  }
@@ -26219,6 +26235,11 @@ function clearReconnectQuietTimer(quiet) {
26219
26235
  quiet.timer = null;
26220
26236
  }
26221
26237
  }
26238
+ function abandonReconnectQuietWindow(quiet) {
26239
+ clearReconnectQuietTimer(quiet);
26240
+ quiet.pendingCloseLog = null;
26241
+ quiet.verboseLogs = false;
26242
+ }
26222
26243
  function clearReconnectQuietOnSuccessfulConnection(quiet, log2, reconnectedMessage) {
26223
26244
  clearReconnectQuietTimer(quiet);
26224
26245
  quiet.pendingCloseLog = null;
@@ -26239,12 +26260,16 @@ function beginDeferredDisconnectForReconnect(options) {
26239
26260
  shutdownDetail,
26240
26261
  reconnectingDetail,
26241
26262
  quietMs = RECONNECT_QUIET_MS,
26242
- shouldAbortQuietWindow
26263
+ shouldAbortQuietWindow,
26264
+ silentWhileReconnect = false
26243
26265
  } = options;
26244
26266
  if (!willReconnect) {
26245
26267
  log2(formatWebSocketClose(serviceLabel, code, reason, shutdownDetail));
26246
26268
  return;
26247
26269
  }
26270
+ if (silentWhileReconnect) {
26271
+ return;
26272
+ }
26248
26273
  quiet.pendingCloseLog = { code, reason };
26249
26274
  if (quiet.timer == null) {
26250
26275
  quiet.timer = setTimeout(() => {
@@ -26261,18 +26286,178 @@ function beginDeferredDisconnectForReconnect(options) {
26261
26286
  }
26262
26287
  }
26263
26288
 
26264
- // src/bridge/connection/reconnect/bridge-main-reconnect.ts
26265
- function beginMainBridgeDeferredDisconnect(state, code, reason, log2, willReconnect) {
26289
+ // src/connection/reconnect/reconnect-outage-plan.ts
26290
+ function createEmptyReconnectOutageTracker() {
26291
+ return {
26292
+ startedAt: null,
26293
+ totalFailures: 0,
26294
+ lastLoggedTierIndex: -1,
26295
+ originalCloseCode: null,
26296
+ originalCloseReason: null
26297
+ };
26298
+ }
26299
+ function resetReconnectOutageTracker(tracker) {
26300
+ tracker.startedAt = null;
26301
+ tracker.totalFailures = 0;
26302
+ tracker.lastLoggedTierIndex = -1;
26303
+ tracker.originalCloseCode = null;
26304
+ tracker.originalCloseReason = null;
26305
+ }
26306
+ function reconnectTierIndexForOutageElapsedMs(elapsedMs) {
26307
+ if (elapsedMs < 6e4) return 0;
26308
+ if (elapsedMs < 36e4) return 1;
26309
+ if (elapsedMs < 12e5) return 2;
26310
+ return 3;
26311
+ }
26312
+ function reconnectDelayMsForOutageElapsedMs(elapsedMs) {
26313
+ if (elapsedMs < 6e4) return 3e3;
26314
+ if (elapsedMs < 36e4) return 1e4;
26315
+ if (elapsedMs < 12e5) return 2e4;
26316
+ return 6e4;
26317
+ }
26318
+ function disconnectDetail(code, reason) {
26319
+ const r = reason.trim();
26320
+ const reasonPart = r ? ` reason="${r}"` : "";
26321
+ return `code=${code} (${describeWebSocketCloseCode(code)})${reasonPart}`;
26322
+ }
26323
+ function formatTierPromotionLogLine(parts) {
26324
+ const detail = disconnectDetail(parts.code, parts.reason);
26325
+ return `${parts.serviceLabel} ${parts.disconnectLeadIn}: ${detail}. Trying to re-establish a connection in the background (retry attempts so far: ${parts.totalFailures}).`;
26326
+ }
26327
+ function planReconnectAfterFailure(tracker, nowMs, serviceLabel, disconnectLeadIn, closeCode, closeReason) {
26328
+ if (tracker.startedAt == null) {
26329
+ tracker.startedAt = nowMs;
26330
+ }
26331
+ if (closeCode != null && tracker.originalCloseCode == null) {
26332
+ tracker.originalCloseCode = closeCode;
26333
+ tracker.originalCloseReason = closeReason ?? "";
26334
+ }
26335
+ tracker.totalFailures += 1;
26336
+ const elapsed = nowMs - tracker.startedAt;
26337
+ const tier = reconnectTierIndexForOutageElapsedMs(elapsed);
26338
+ const delayMs = reconnectDelayMsForOutageElapsedMs(elapsed);
26339
+ let logLine = null;
26340
+ if (tier > tracker.lastLoggedTierIndex) {
26341
+ tracker.lastLoggedTierIndex = tier;
26342
+ const code = tracker.originalCloseCode ?? closeCode ?? 0;
26343
+ const reason = tracker.originalCloseReason ?? closeReason ?? "";
26344
+ logLine = formatTierPromotionLogLine({
26345
+ serviceLabel,
26346
+ disconnectLeadIn,
26347
+ code,
26348
+ reason,
26349
+ totalFailures: tracker.totalFailures
26350
+ });
26351
+ }
26352
+ return { delayMs, logLine };
26353
+ }
26354
+
26355
+ // src/connection/reconnect/tiered-channel-reconnect.ts
26356
+ var BRIDGE_SERVICE_LABEL = "[Bridge service]";
26357
+ var BRIDGE_DISCONNECT_LEAD_IN = "Bridge connection was disconnected";
26358
+ var PREVIEW_TUNNEL_SERVICE_LABEL = "[Proxy and log service]";
26359
+ var PREVIEW_TUNNEL_DISCONNECT_LEAD_IN = "Preview tunnel connection was disconnected";
26360
+ var SHUTDOWN_DETAIL = "Not reconnecting (shutting down).";
26361
+ var RECONNECTING_DETAIL = "Reconnecting\u2026";
26362
+ function applyTieredReconnectPlanAndLog(tracker, log2, serviceLabel, disconnectLeadIn, closeCode, closeReason) {
26363
+ try {
26364
+ const { delayMs, logLine } = planReconnectAfterFailure(
26365
+ tracker,
26366
+ Date.now(),
26367
+ serviceLabel,
26368
+ disconnectLeadIn,
26369
+ closeCode,
26370
+ closeReason
26371
+ );
26372
+ if (logLine) {
26373
+ try {
26374
+ log2(logLine);
26375
+ } catch {
26376
+ }
26377
+ }
26378
+ return delayMs;
26379
+ } catch {
26380
+ return 3e3;
26381
+ }
26382
+ }
26383
+ function armReconnectDelayTimer(options) {
26384
+ const {
26385
+ delayMs,
26386
+ bumpAttempt,
26387
+ clearTimer,
26388
+ setTimer,
26389
+ shouldAbortBeforeRun,
26390
+ run,
26391
+ reschedule
26392
+ } = options;
26393
+ clearTimer();
26394
+ bumpAttempt();
26395
+ setTimer(
26396
+ setTimeout(() => {
26397
+ setTimer(null);
26398
+ if (shouldAbortBeforeRun()) return;
26399
+ try {
26400
+ run();
26401
+ } catch {
26402
+ try {
26403
+ if (!shouldAbortBeforeRun()) {
26404
+ reschedule();
26405
+ }
26406
+ } catch {
26407
+ }
26408
+ }
26409
+ }, delayMs)
26410
+ );
26411
+ }
26412
+ function beginTieredSilentReconnectDisconnect(options) {
26413
+ const {
26414
+ isClosedByUser,
26415
+ quiet,
26416
+ code,
26417
+ reason,
26418
+ willReconnect,
26419
+ log: log2,
26420
+ serviceLabel,
26421
+ shouldAbortQuietWindow
26422
+ } = options;
26266
26423
  beginDeferredDisconnectForReconnect({
26424
+ isClosedByUser,
26425
+ quiet,
26426
+ code,
26427
+ reason,
26428
+ willReconnect,
26429
+ log: log2,
26430
+ serviceLabel,
26431
+ shutdownDetail: SHUTDOWN_DETAIL,
26432
+ reconnectingDetail: RECONNECTING_DETAIL,
26433
+ shouldAbortQuietWindow,
26434
+ silentWhileReconnect: willReconnect
26435
+ });
26436
+ }
26437
+ function clearTieredReconnectChannelOnOpen(options) {
26438
+ const { quiet, outage, clearLastCloseMeta, log: log2, restoredMessage } = options;
26439
+ const hadOutage = outage.startedAt != null || outage.totalFailures > 0;
26440
+ abandonReconnectQuietWindow(quiet);
26441
+ resetReconnectOutageTracker(outage);
26442
+ clearLastCloseMeta();
26443
+ if (hadOutage) {
26444
+ try {
26445
+ log2(restoredMessage);
26446
+ } catch {
26447
+ }
26448
+ }
26449
+ }
26450
+
26451
+ // src/connection/reconnect/bridge-main-reconnect.ts
26452
+ function beginMainBridgeDeferredDisconnect(state, code, reason, log2, willReconnect) {
26453
+ beginTieredSilentReconnectDisconnect({
26267
26454
  isClosedByUser: () => state.closedByUser,
26268
26455
  quiet: state.mainQuiet,
26269
26456
  code,
26270
26457
  reason,
26271
26458
  willReconnect,
26272
26459
  log: log2,
26273
- serviceLabel: "[Bridge service]",
26274
- shutdownDetail: "Not reconnecting (shutting down).",
26275
- reconnectingDetail: "Reconnecting\u2026",
26460
+ serviceLabel: BRIDGE_SERVICE_LABEL,
26276
26461
  shouldAbortQuietWindow: () => {
26277
26462
  const w = state.currentWs;
26278
26463
  return w != null && w.readyState === wrapper_default.OPEN;
@@ -26280,32 +26465,59 @@ function beginMainBridgeDeferredDisconnect(state, code, reason, log2, willReconn
26280
26465
  });
26281
26466
  }
26282
26467
  function clearMainBridgeReconnectQuietOnOpen(state, log2) {
26283
- clearReconnectQuietOnSuccessfulConnection(state.mainQuiet, log2, "Bridge connection restored.");
26468
+ clearTieredReconnectChannelOnOpen({
26469
+ quiet: state.mainQuiet,
26470
+ outage: state.mainOutage,
26471
+ clearLastCloseMeta: () => {
26472
+ state.lastReconnectCloseMeta = null;
26473
+ },
26474
+ log: log2,
26475
+ restoredMessage: "Bridge connection restored."
26476
+ });
26284
26477
  }
26285
- function scheduleMainBridgeReconnect(state, connect, log2) {
26478
+ function scheduleMainBridgeReconnect(state, connect, log2, closeMeta) {
26286
26479
  if (state.closedByUser || state.currentWs != null) return;
26287
- const delay2 = reconnectDelayMs(state.reconnectAttempt);
26288
- state.reconnectAttempt += 1;
26289
- logNextReconnectAttempt(log2, "[Bridge service]", state.mainQuiet, delay2, state.reconnectAttempt);
26290
- state.reconnectTimeout = setTimeout(() => {
26291
- state.reconnectTimeout = null;
26292
- connect();
26293
- }, delay2);
26480
+ const meta = closeMeta ?? state.lastReconnectCloseMeta ?? void 0;
26481
+ const delay2 = applyTieredReconnectPlanAndLog(
26482
+ state.mainOutage,
26483
+ log2,
26484
+ BRIDGE_SERVICE_LABEL,
26485
+ BRIDGE_DISCONNECT_LEAD_IN,
26486
+ meta?.code,
26487
+ meta?.reason
26488
+ );
26489
+ armReconnectDelayTimer({
26490
+ delayMs: delay2,
26491
+ bumpAttempt: () => {
26492
+ state.reconnectAttempt += 1;
26493
+ },
26494
+ clearTimer: () => {
26495
+ if (state.reconnectTimeout != null) {
26496
+ clearTimeout(state.reconnectTimeout);
26497
+ state.reconnectTimeout = null;
26498
+ }
26499
+ },
26500
+ setTimer: (id) => {
26501
+ state.reconnectTimeout = id;
26502
+ },
26503
+ shouldAbortBeforeRun: () => state.closedByUser || state.currentWs != null,
26504
+ run: connect,
26505
+ reschedule: () => {
26506
+ scheduleMainBridgeReconnect(state, connect, log2);
26507
+ }
26508
+ });
26294
26509
  }
26295
26510
 
26296
- // src/bridge/connection/reconnect/firehose-reconnect.ts
26297
- var PROXY_AND_LOG_SERVICE_LABEL = "[Proxy and log service]";
26511
+ // src/connection/reconnect/firehose-reconnect.ts
26298
26512
  function beginFirehoseDeferredDisconnect(ctx, code, reason, log2) {
26299
- beginDeferredDisconnectForReconnect({
26513
+ beginTieredSilentReconnectDisconnect({
26300
26514
  isClosedByUser: () => ctx.closedByUser,
26301
26515
  quiet: ctx.firehoseQuiet,
26302
26516
  code,
26303
26517
  reason,
26304
26518
  willReconnect: true,
26305
26519
  log: log2,
26306
- serviceLabel: PROXY_AND_LOG_SERVICE_LABEL,
26307
- shutdownDetail: "Not reconnecting (shutting down).",
26308
- reconnectingDetail: "Reconnecting\u2026",
26520
+ serviceLabel: PREVIEW_TUNNEL_SERVICE_LABEL,
26309
26521
  shouldAbortQuietWindow: () => {
26310
26522
  const w = ctx.currentWs;
26311
26523
  if (!w || w.readyState !== wrapper_default.OPEN) return true;
@@ -26314,11 +26526,15 @@ function beginFirehoseDeferredDisconnect(ctx, code, reason, log2) {
26314
26526
  });
26315
26527
  }
26316
26528
  function clearFirehoseReconnectQuietOnOpen(ctx, log2) {
26317
- clearReconnectQuietOnSuccessfulConnection(
26318
- ctx.firehoseQuiet,
26319
- log2,
26320
- "Preview tunnel restored (local HTTP proxy and dev logs)."
26321
- );
26529
+ clearTieredReconnectChannelOnOpen({
26530
+ quiet: ctx.firehoseQuiet,
26531
+ outage: ctx.firehoseOutage,
26532
+ clearLastCloseMeta: () => {
26533
+ ctx.lastFirehoseReconnectCloseMeta = null;
26534
+ },
26535
+ log: log2,
26536
+ restoredMessage: "Preview tunnel restored (local HTTP proxy and dev logs)."
26537
+ });
26322
26538
  }
26323
26539
 
26324
26540
  // src/auth/run-pending-auth.ts
@@ -26414,7 +26630,7 @@ function runPendingAuth(options) {
26414
26630
  }
26415
26631
  if (resolved) return;
26416
26632
  beginDeferredPendingCloseLog(code, reason);
26417
- const delay2 = reconnectDelayMs(reconnectAttempt);
26633
+ const delay2 = pendingAuthReconnectDelayMs(reconnectAttempt);
26418
26634
  reconnectAttempt += 1;
26419
26635
  if (signInQuiet.verboseLogs) {
26420
26636
  const delayLabel = formatReconnectDelayForLog(delay2);
@@ -26459,7 +26675,7 @@ function runPendingAuth(options) {
26459
26675
  };
26460
26676
  }
26461
26677
 
26462
- // src/bridge/connection/close-bridge-connection.ts
26678
+ // src/connection/close-bridge-connection.ts
26463
26679
  async function closeBridgeConnection(state, acpManager, devServerManager, log2) {
26464
26680
  const say = log2 ?? logImmediate;
26465
26681
  say("Cleaning up connections\u2026");
@@ -26847,9 +27063,9 @@ function parseChangeSummaryJson(raw, allowedPaths, options) {
26847
27063
  const rawPath = typeof o.path === "string" ? o.path.trim() : "";
26848
27064
  const summary = typeof o.summary === "string" ? o.summary.trim() : "";
26849
27065
  if (!rawPath || !summary) continue;
26850
- const path36 = skip ? normalizeRepoRelativePath(rawPath) || rawPath : resolveChangeSummaryPathAgainstAllowed(rawPath, allowedPaths);
26851
- if (!path36) continue;
26852
- rows.push({ path: path36, summary: clampSummaryToAtMostTwoLines(summary) });
27066
+ const path37 = skip ? normalizeRepoRelativePath(rawPath) || rawPath : resolveChangeSummaryPathAgainstAllowed(rawPath, allowedPaths);
27067
+ if (!path37) continue;
27068
+ rows.push({ path: path37, summary: clampSummaryToAtMostTwoLines(summary) });
26853
27069
  }
26854
27070
  return rows;
26855
27071
  }
@@ -26974,6 +27190,30 @@ var GitRepoMetaSchema = external_exports.object({
26974
27190
  updatedAt: external_exports.string()
26975
27191
  });
26976
27192
 
27193
+ // ../types/src/claude-code-permission-mode.ts
27194
+ var CLAUDE_CODE_PERMISSION_MODES = [
27195
+ "default",
27196
+ "acceptEdits",
27197
+ "plan",
27198
+ "auto",
27199
+ "dontAsk",
27200
+ "bypassPermissions"
27201
+ ];
27202
+ var MODE_SET = new Set(CLAUDE_CODE_PERMISSION_MODES);
27203
+ function isClaudeCodePermissionMode(value) {
27204
+ return MODE_SET.has(value);
27205
+ }
27206
+
27207
+ // ../types/src/agent-config.ts
27208
+ var AGENT_CONFIG_CLAUDE_PERMISSION_MODE_KEY = "claude_permission_mode";
27209
+ function getClaudePermissionModeFromAgentConfig(config2) {
27210
+ if (!config2) return null;
27211
+ const raw = config2[AGENT_CONFIG_CLAUDE_PERMISSION_MODE_KEY];
27212
+ if (typeof raw !== "string") return null;
27213
+ const t = raw.trim();
27214
+ return isClaudeCodePermissionMode(t) ? t : null;
27215
+ }
27216
+
26977
27217
  // src/git/session-git-queue.ts
26978
27218
  import { execFile as execFile7 } from "node:child_process";
26979
27219
  import { readFile as readFile2, stat as stat2 } from "node:fs/promises";
@@ -27027,8 +27267,8 @@ function pathspec(...paths) {
27027
27267
  cache.set(key, paths);
27028
27268
  return key;
27029
27269
  }
27030
- function isPathSpec(path36) {
27031
- return path36 instanceof String && cache.has(path36);
27270
+ function isPathSpec(path37) {
27271
+ return path37 instanceof String && cache.has(path37);
27032
27272
  }
27033
27273
  function toPaths(pathSpec) {
27034
27274
  return cache.get(pathSpec) || [];
@@ -27117,8 +27357,8 @@ function toLinesWithContent(input = "", trimmed2 = true, separator = "\n") {
27117
27357
  function forEachLineWithContent(input, callback) {
27118
27358
  return toLinesWithContent(input, true).map((line) => callback(line));
27119
27359
  }
27120
- function folderExists(path36) {
27121
- return (0, import_file_exists.exists)(path36, import_file_exists.FOLDER);
27360
+ function folderExists(path37) {
27361
+ return (0, import_file_exists.exists)(path37, import_file_exists.FOLDER);
27122
27362
  }
27123
27363
  function append(target, item) {
27124
27364
  if (Array.isArray(target)) {
@@ -27522,8 +27762,8 @@ function checkIsRepoRootTask() {
27522
27762
  commands,
27523
27763
  format: "utf-8",
27524
27764
  onError,
27525
- parser(path36) {
27526
- return /^\.(git)?$/.test(path36.trim());
27765
+ parser(path37) {
27766
+ return /^\.(git)?$/.test(path37.trim());
27527
27767
  }
27528
27768
  };
27529
27769
  }
@@ -27957,11 +28197,11 @@ function parseGrep(grep) {
27957
28197
  const paths = /* @__PURE__ */ new Set();
27958
28198
  const results = {};
27959
28199
  forEachLineWithContent(grep, (input) => {
27960
- const [path36, line, preview] = input.split(NULL);
27961
- paths.add(path36);
27962
- (results[path36] = results[path36] || []).push({
28200
+ const [path37, line, preview] = input.split(NULL);
28201
+ paths.add(path37);
28202
+ (results[path37] = results[path37] || []).push({
27963
28203
  line: asNumber(line),
27964
- path: path36,
28204
+ path: path37,
27965
28205
  preview
27966
28206
  });
27967
28207
  });
@@ -28726,14 +28966,14 @@ var init_hash_object = __esm2({
28726
28966
  init_task();
28727
28967
  }
28728
28968
  });
28729
- function parseInit(bare, path36, text) {
28969
+ function parseInit(bare, path37, text) {
28730
28970
  const response = String(text).trim();
28731
28971
  let result;
28732
28972
  if (result = initResponseRegex.exec(response)) {
28733
- return new InitSummary(bare, path36, false, result[1]);
28973
+ return new InitSummary(bare, path37, false, result[1]);
28734
28974
  }
28735
28975
  if (result = reInitResponseRegex.exec(response)) {
28736
- return new InitSummary(bare, path36, true, result[1]);
28976
+ return new InitSummary(bare, path37, true, result[1]);
28737
28977
  }
28738
28978
  let gitDir = "";
28739
28979
  const tokens = response.split(" ");
@@ -28744,7 +28984,7 @@ function parseInit(bare, path36, text) {
28744
28984
  break;
28745
28985
  }
28746
28986
  }
28747
- return new InitSummary(bare, path36, /^re/i.test(response), gitDir);
28987
+ return new InitSummary(bare, path37, /^re/i.test(response), gitDir);
28748
28988
  }
28749
28989
  var InitSummary;
28750
28990
  var initResponseRegex;
@@ -28753,9 +28993,9 @@ var init_InitSummary = __esm2({
28753
28993
  "src/lib/responses/InitSummary.ts"() {
28754
28994
  "use strict";
28755
28995
  InitSummary = class {
28756
- constructor(bare, path36, existing, gitDir) {
28996
+ constructor(bare, path37, existing, gitDir) {
28757
28997
  this.bare = bare;
28758
- this.path = path36;
28998
+ this.path = path37;
28759
28999
  this.existing = existing;
28760
29000
  this.gitDir = gitDir;
28761
29001
  }
@@ -28767,7 +29007,7 @@ var init_InitSummary = __esm2({
28767
29007
  function hasBareCommand(command) {
28768
29008
  return command.includes(bareCommand);
28769
29009
  }
28770
- function initTask(bare = false, path36, customArgs) {
29010
+ function initTask(bare = false, path37, customArgs) {
28771
29011
  const commands = ["init", ...customArgs];
28772
29012
  if (bare && !hasBareCommand(commands)) {
28773
29013
  commands.splice(1, 0, bareCommand);
@@ -28776,7 +29016,7 @@ function initTask(bare = false, path36, customArgs) {
28776
29016
  commands,
28777
29017
  format: "utf-8",
28778
29018
  parser(text) {
28779
- return parseInit(commands.includes("--bare"), path36, text);
29019
+ return parseInit(commands.includes("--bare"), path37, text);
28780
29020
  }
28781
29021
  };
28782
29022
  }
@@ -29592,12 +29832,12 @@ var init_FileStatusSummary = __esm2({
29592
29832
  "use strict";
29593
29833
  fromPathRegex = /^(.+)\0(.+)$/;
29594
29834
  FileStatusSummary = class {
29595
- constructor(path36, index, working_dir) {
29596
- this.path = path36;
29835
+ constructor(path37, index, working_dir) {
29836
+ this.path = path37;
29597
29837
  this.index = index;
29598
29838
  this.working_dir = working_dir;
29599
29839
  if (index === "R" || working_dir === "R") {
29600
- const detail = fromPathRegex.exec(path36) || [null, path36, path36];
29840
+ const detail = fromPathRegex.exec(path37) || [null, path37, path37];
29601
29841
  this.from = detail[2] || "";
29602
29842
  this.path = detail[1] || "";
29603
29843
  }
@@ -29628,14 +29868,14 @@ function splitLine(result, lineStr) {
29628
29868
  default:
29629
29869
  return;
29630
29870
  }
29631
- function data(index, workingDir, path36) {
29871
+ function data(index, workingDir, path37) {
29632
29872
  const raw = `${index}${workingDir}`;
29633
29873
  const handler = parsers6.get(raw);
29634
29874
  if (handler) {
29635
- handler(result, path36);
29875
+ handler(result, path37);
29636
29876
  }
29637
29877
  if (raw !== "##" && raw !== "!!") {
29638
- result.files.push(new FileStatusSummary(path36, index, workingDir));
29878
+ result.files.push(new FileStatusSummary(path37, index, workingDir));
29639
29879
  }
29640
29880
  }
29641
29881
  }
@@ -29944,9 +30184,9 @@ var init_simple_git_api = __esm2({
29944
30184
  next
29945
30185
  );
29946
30186
  }
29947
- hashObject(path36, write) {
30187
+ hashObject(path37, write) {
29948
30188
  return this._runTask(
29949
- hashObjectTask(path36, write === true),
30189
+ hashObjectTask(path37, write === true),
29950
30190
  trailingFunctionArgument(arguments)
29951
30191
  );
29952
30192
  }
@@ -30299,8 +30539,8 @@ var init_branch = __esm2({
30299
30539
  }
30300
30540
  });
30301
30541
  function toPath(input) {
30302
- const path36 = input.trim().replace(/^["']|["']$/g, "");
30303
- return path36 && normalize(path36);
30542
+ const path37 = input.trim().replace(/^["']|["']$/g, "");
30543
+ return path37 && normalize(path37);
30304
30544
  }
30305
30545
  var parseCheckIgnore;
30306
30546
  var init_CheckIgnore = __esm2({
@@ -30614,8 +30854,8 @@ __export2(sub_module_exports, {
30614
30854
  subModuleTask: () => subModuleTask,
30615
30855
  updateSubModuleTask: () => updateSubModuleTask
30616
30856
  });
30617
- function addSubModuleTask(repo, path36) {
30618
- return subModuleTask(["add", repo, path36]);
30857
+ function addSubModuleTask(repo, path37) {
30858
+ return subModuleTask(["add", repo, path37]);
30619
30859
  }
30620
30860
  function initSubModuleTask(customArgs) {
30621
30861
  return subModuleTask(["init", ...customArgs]);
@@ -30948,8 +31188,8 @@ var require_git = __commonJS2({
30948
31188
  }
30949
31189
  return this._runTask(straightThroughStringTask2(command, this._trimmed), next);
30950
31190
  };
30951
- Git2.prototype.submoduleAdd = function(repo, path36, then) {
30952
- return this._runTask(addSubModuleTask2(repo, path36), trailingFunctionArgument2(arguments));
31191
+ Git2.prototype.submoduleAdd = function(repo, path37, then) {
31192
+ return this._runTask(addSubModuleTask2(repo, path37), trailingFunctionArgument2(arguments));
30953
31193
  };
30954
31194
  Git2.prototype.submoduleUpdate = function(args, then) {
30955
31195
  return this._runTask(
@@ -31549,10 +31789,28 @@ function gitInstanceFactory(baseDir, options) {
31549
31789
  init_git_response_error();
31550
31790
  var simpleGit = gitInstanceFactory;
31551
31791
 
31792
+ // src/git/cli-simple-git.ts
31793
+ function cliSimpleGit(baseDir) {
31794
+ const git = simpleGit({ baseDir });
31795
+ git.outputHandler((command, stdout, stderr) => {
31796
+ const trace = isCliTrace();
31797
+ const onChunk = (label) => (chunk) => {
31798
+ const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
31799
+ const line = text.replace(/\s+$/, "");
31800
+ if (trace && line) {
31801
+ logTrace(`[git ${command}] ${label}: ${line}`);
31802
+ }
31803
+ };
31804
+ stdout?.on("data", onChunk("stdout"));
31805
+ stderr?.on("data", onChunk("stderr"));
31806
+ });
31807
+ return git;
31808
+ }
31809
+
31552
31810
  // src/git/remote-origin-url.ts
31553
31811
  async function getRemoteOriginUrl(gitDir) {
31554
31812
  try {
31555
- const git = simpleGit(gitDir);
31813
+ const git = cliSimpleGit(gitDir);
31556
31814
  const remotes = await git.getRemotes(true);
31557
31815
  const list = Array.isArray(remotes) ? remotes : [];
31558
31816
  const origin = list.find((r) => r.name === "origin");
@@ -31566,7 +31824,7 @@ async function getRemoteOriginUrl(gitDir) {
31566
31824
  // src/git/is-git-repo.ts
31567
31825
  async function isGitRepoDirectory(dirPath) {
31568
31826
  try {
31569
- return await simpleGit(dirPath).checkIsRepo();
31827
+ return await cliSimpleGit(dirPath).checkIsRepo();
31570
31828
  } catch {
31571
31829
  return false;
31572
31830
  }
@@ -31818,9 +32076,9 @@ async function collectTurnGitDiffFromPreTurnSnapshot(options) {
31818
32076
  // src/agents/acp/put-summarize-change-summaries.ts
31819
32077
  async function putEncryptedChangeSummaryRows(params) {
31820
32078
  const base = params.apiBaseUrl.replace(/\/+$/, "");
31821
- const entries = params.rows.map(({ path: path36, summary }) => {
32079
+ const entries = params.rows.map(({ path: path37, summary }) => {
31822
32080
  const enc = params.e2ee.encryptFields({ summary }, ["summary"]);
31823
- return { path: path36, summary: JSON.stringify(enc) };
32081
+ return { path: path37, summary: JSON.stringify(enc) };
31824
32082
  });
31825
32083
  const res = await fetch(
31826
32084
  `${base}/api/sessions/${encodeURIComponent(params.sessionId)}/follow-ups/summarize-changes`,
@@ -32002,8 +32260,8 @@ async function sendPromptToAgent(options) {
32002
32260
  }
32003
32261
 
32004
32262
  // src/agents/acp/ensure-acp-client.ts
32005
- import * as fs10 from "node:fs";
32006
- import * as path12 from "node:path";
32263
+ import * as fs11 from "node:fs";
32264
+ import * as path13 from "node:path";
32007
32265
 
32008
32266
  // src/error-message.ts
32009
32267
  function errorMessage(err) {
@@ -32039,76 +32297,10 @@ async function isCommandOnPath(command, timeoutMs = 4e3) {
32039
32297
  }
32040
32298
  }
32041
32299
 
32042
- // src/agents/acp/clients/sdk-stdio-acp-client.ts
32300
+ // src/agents/acp/clients/sdk/sdk-stdio-acp-client.ts
32043
32301
  import { spawn as spawn2 } from "node:child_process";
32044
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
32045
- import { dirname as dirname2 } from "node:path";
32046
32302
  import { Readable, Writable } from "node:stream";
32047
32303
 
32048
- // src/files/diff/unified-diff.ts
32049
- function computeLineDiff(oldText, newText) {
32050
- const oldLines = oldText.split("\n");
32051
- const newLines = newText.split("\n");
32052
- const m = oldLines.length;
32053
- const n = newLines.length;
32054
- const dp = Array(m + 1);
32055
- for (let i2 = 0; i2 <= m; i2++) dp[i2] = Array(n + 1).fill(0);
32056
- for (let i2 = 1; i2 <= m; i2++) {
32057
- for (let j2 = 1; j2 <= n; j2++) {
32058
- if (oldLines[i2 - 1] === newLines[j2 - 1]) {
32059
- dp[i2][j2] = dp[i2 - 1][j2 - 1] + 1;
32060
- } else {
32061
- dp[i2][j2] = Math.max(dp[i2 - 1][j2], dp[i2][j2 - 1]);
32062
- }
32063
- }
32064
- }
32065
- const result = [];
32066
- let i = m;
32067
- let j = n;
32068
- while (i > 0 || j > 0) {
32069
- if (i > 0 && j > 0 && oldLines[i - 1] === newLines[j - 1]) {
32070
- result.unshift({ type: "context", line: oldLines[i - 1] });
32071
- i--;
32072
- j--;
32073
- } else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
32074
- result.unshift({ type: "add", line: newLines[j - 1] });
32075
- j--;
32076
- } else {
32077
- result.unshift({ type: "remove", line: oldLines[i - 1] });
32078
- i--;
32079
- }
32080
- }
32081
- return result;
32082
- }
32083
- function editSnippetToUnifiedDiff(filePath, oldText, newText) {
32084
- const lines = computeLineDiff(oldText, newText);
32085
- const out = [`--- ${filePath}`, `+++ ${filePath}`];
32086
- for (const d of lines) {
32087
- if (d.type === "add") out.push(`+${d.line}`);
32088
- else if (d.type === "remove") out.push(`-${d.line}`);
32089
- else out.push(` ${d.line}`);
32090
- }
32091
- return out.join("\n");
32092
- }
32093
-
32094
- // src/agents/acp/safe-fs-path.ts
32095
- import * as path9 from "node:path";
32096
- function resolveSafePathUnderCwd(cwd, filePath) {
32097
- const trimmed2 = filePath.trim();
32098
- if (!trimmed2) return null;
32099
- const normalizedCwd = path9.resolve(cwd);
32100
- const resolved = path9.isAbsolute(trimmed2) ? path9.normalize(trimmed2) : path9.resolve(normalizedCwd, trimmed2);
32101
- const rel = path9.relative(normalizedCwd, resolved);
32102
- if (rel.startsWith("..") || path9.isAbsolute(rel)) return null;
32103
- return resolved;
32104
- }
32105
- function toDisplayPathRelativeToCwd(cwd, absolutePath) {
32106
- const normalizedCwd = path9.resolve(cwd);
32107
- const rel = path9.relative(normalizedCwd, path9.resolve(absolutePath));
32108
- if (!rel || rel === "") return path9.basename(absolutePath);
32109
- return rel.split(path9.sep).join("/");
32110
- }
32111
-
32112
32304
  // src/agents/acp/clients/agent-stderr-capture.ts
32113
32305
  var STDERR_CAPTURE_MAX = 48e3;
32114
32306
  function createStderrCapture(child) {
@@ -32173,7 +32365,7 @@ function createKiroSdkExtNotificationHandler(options) {
32173
32365
  };
32174
32366
  }
32175
32367
 
32176
- // src/agents/acp/clients/sdk-stdio-ext-notifications.ts
32368
+ // src/agents/acp/clients/sdk/sdk-stdio-ext-notifications.ts
32177
32369
  var noopExtNotification = async () => {
32178
32370
  };
32179
32371
  function createSdkStdioExtNotificationHandler(options) {
@@ -32232,23 +32424,364 @@ function enrichAcpPermissionRpcResultFromRequestParams(result, params) {
32232
32424
  };
32233
32425
  }
32234
32426
 
32235
- // src/agents/acp/clients/sdk-stdio-acp-client.ts
32236
- function formatSpawnError(err, command) {
32237
- if (err.code === "ENOENT") {
32238
- return `Command "${command}" not found. Install the agent (e.g. Cursor CLI) or add it to PATH.`;
32427
+ // src/agents/acp/clients/shared/acp-fs-read-write.ts
32428
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
32429
+ import { dirname as dirname2 } from "node:path";
32430
+
32431
+ // src/files/diff/unified-diff.ts
32432
+ function computeLineDiff(oldText, newText) {
32433
+ const oldLines = oldText.split("\n");
32434
+ const newLines = newText.split("\n");
32435
+ const m = oldLines.length;
32436
+ const n = newLines.length;
32437
+ const dp = Array(m + 1);
32438
+ for (let i2 = 0; i2 <= m; i2++) dp[i2] = Array(n + 1).fill(0);
32439
+ for (let i2 = 1; i2 <= m; i2++) {
32440
+ for (let j2 = 1; j2 <= n; j2++) {
32441
+ if (oldLines[i2 - 1] === newLines[j2 - 1]) {
32442
+ dp[i2][j2] = dp[i2 - 1][j2 - 1] + 1;
32443
+ } else {
32444
+ dp[i2][j2] = Math.max(dp[i2 - 1][j2], dp[i2][j2 - 1]);
32445
+ }
32446
+ }
32239
32447
  }
32240
- return err.message || String(err);
32448
+ const result = [];
32449
+ let i = m;
32450
+ let j = n;
32451
+ while (i > 0 || j > 0) {
32452
+ if (i > 0 && j > 0 && oldLines[i - 1] === newLines[j - 1]) {
32453
+ result.unshift({ type: "context", line: oldLines[i - 1] });
32454
+ i--;
32455
+ j--;
32456
+ } else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
32457
+ result.unshift({ type: "add", line: newLines[j - 1] });
32458
+ j--;
32459
+ } else {
32460
+ result.unshift({ type: "remove", line: oldLines[i - 1] });
32461
+ i--;
32462
+ }
32463
+ }
32464
+ return result;
32465
+ }
32466
+ function editSnippetToUnifiedDiff(filePath, oldText, newText) {
32467
+ const lines = computeLineDiff(oldText, newText);
32468
+ const out = [`--- ${filePath}`, `+++ ${filePath}`];
32469
+ for (const d of lines) {
32470
+ if (d.type === "add") out.push(`+${d.line}`);
32471
+ else if (d.type === "remove") out.push(`-${d.line}`);
32472
+ else out.push(` ${d.line}`);
32473
+ }
32474
+ return out.join("\n");
32475
+ }
32476
+
32477
+ // src/agents/acp/safe-fs-path.ts
32478
+ import * as path9 from "node:path";
32479
+ function resolveSafePathUnderCwd(cwd, filePath) {
32480
+ const trimmed2 = filePath.trim();
32481
+ if (!trimmed2) return null;
32482
+ const normalizedCwd = path9.resolve(cwd);
32483
+ const resolved = path9.isAbsolute(trimmed2) ? path9.normalize(trimmed2) : path9.resolve(normalizedCwd, trimmed2);
32484
+ const rel = path9.relative(normalizedCwd, resolved);
32485
+ if (rel.startsWith("..") || path9.isAbsolute(rel)) return null;
32486
+ return resolved;
32241
32487
  }
32242
- function sliceFileContentRange(content, line, limit) {
32488
+ function toDisplayPathRelativeToCwd(cwd, absolutePath) {
32489
+ const normalizedCwd = path9.resolve(cwd);
32490
+ const rel = path9.relative(normalizedCwd, path9.resolve(absolutePath));
32491
+ if (!rel || rel === "") return path9.basename(absolutePath);
32492
+ return rel.split(path9.sep).join("/");
32493
+ }
32494
+
32495
+ // src/agents/acp/clients/shared/acp-fs-read-write.ts
32496
+ function sliceFileContentForAcp(content, line, limit) {
32243
32497
  if (line == null && limit == null) return content;
32244
32498
  const lines = content.split("\n");
32245
32499
  const start = line != null && line > 0 ? line - 1 : 0;
32246
32500
  const end = limit != null && limit > 0 ? start + limit : lines.length;
32247
32501
  return lines.slice(start, end).join("\n");
32248
32502
  }
32249
- function bridgePayloadFromSdkSessionNotification(params) {
32503
+ function acpReadTextFileInProcess(ctx, filePath, line, limit) {
32504
+ const resolvedPath = resolveSafePathUnderCwd(ctx.cwd, filePath);
32505
+ if (!resolvedPath) throw new Error("Invalid or disallowed path");
32506
+ try {
32507
+ let content = readFileSync2(resolvedPath, "utf8");
32508
+ content = sliceFileContentForAcp(content, line, limit);
32509
+ return { content };
32510
+ } catch (e) {
32511
+ if (e.code === "ENOENT") return { content: "" };
32512
+ throw e;
32513
+ }
32514
+ }
32515
+ function acpWriteTextFileInProcess(ctx, filePath, newText) {
32516
+ const resolvedPath = resolveSafePathUnderCwd(ctx.cwd, filePath);
32517
+ if (!resolvedPath) throw new Error("Invalid or disallowed path");
32518
+ let oldText = "";
32519
+ try {
32520
+ oldText = readFileSync2(resolvedPath, "utf8");
32521
+ } catch (e) {
32522
+ if (e.code !== "ENOENT") throw e;
32523
+ }
32524
+ mkdirSync2(dirname2(resolvedPath), { recursive: true });
32525
+ writeFileSync2(resolvedPath, newText, "utf8");
32526
+ const displayPath = toDisplayPathRelativeToCwd(ctx.cwd, resolvedPath);
32527
+ const patchContent = editSnippetToUnifiedDiff(displayPath, oldText, newText);
32528
+ ctx.onFileChange?.({ path: displayPath, oldText, newText, patchContent });
32529
+ return {};
32530
+ }
32531
+
32532
+ // src/agents/acp/claude-acp-permission-from-session.ts
32533
+ function flattenSelectOptions(options) {
32534
+ if (options == null || options.length === 0) return [];
32535
+ const first2 = options[0];
32536
+ if (first2 != null && typeof first2 === "object" && "group" in first2 && first2.group != null) {
32537
+ return options.flatMap(
32538
+ (g) => Array.isArray(g.options) ? g.options : []
32539
+ );
32540
+ }
32541
+ return options;
32542
+ }
32543
+ function pickModeConfigOption(configOptions) {
32544
+ if (configOptions == null || configOptions.length === 0) return null;
32545
+ const byCategory = configOptions.find((o) => o.category === "mode");
32546
+ if (byCategory) return byCategory;
32547
+ return configOptions.find((o) => o.id === "mode") ?? null;
32548
+ }
32549
+ async function applyClaudePermissionFromAcpSession(params) {
32550
+ const { sessionId, agentConfig, configOptions, modes, setSessionConfigOption, setSessionMode, logDebug: logDebug2 } = params;
32551
+ const desiredMode = getClaudePermissionModeFromAgentConfig(agentConfig);
32552
+ if (desiredMode == null) return;
32553
+ const modeOpt = pickModeConfigOption(configOptions ?? null);
32554
+ if (modeOpt != null) {
32555
+ const flat = flattenSelectOptions(modeOpt.options);
32556
+ const allowed = flat.some((o) => o.value === desiredMode);
32557
+ if (allowed && modeOpt.currentValue !== desiredMode) {
32558
+ try {
32559
+ logDebug2(
32560
+ `[Agent] Claude Code: sending ACP session/set_config_option (permission mode) configId=${JSON.stringify(modeOpt.id)} value=${JSON.stringify(desiredMode)} was=${JSON.stringify(modeOpt.currentValue)} sessionId=${sessionId.slice(0, 8)}\u2026`
32561
+ );
32562
+ await setSessionConfigOption({ sessionId, configId: modeOpt.id, value: desiredMode });
32563
+ } catch (e) {
32564
+ logDebug2(
32565
+ `[Agent] Claude Code: session/set_config_option failed: ${e instanceof Error ? e.message : String(e)}`
32566
+ );
32567
+ }
32568
+ }
32569
+ return;
32570
+ }
32571
+ if (modes?.availableModes?.length) {
32572
+ const allowed = modes.availableModes.some((m) => m.id === desiredMode);
32573
+ if (allowed && desiredMode !== modes.currentModeId) {
32574
+ try {
32575
+ logDebug2(
32576
+ `[Agent] Claude Code: sending ACP session/set_mode (permission mode) modeId=${JSON.stringify(desiredMode)} was=${JSON.stringify(modes.currentModeId ?? null)} sessionId=${sessionId.slice(0, 8)}\u2026`
32577
+ );
32578
+ await setSessionMode({ sessionId, modeId: desiredMode });
32579
+ } catch (e) {
32580
+ logDebug2(`[Agent] Claude Code: session/set_mode failed: ${e instanceof Error ? e.message : String(e)}`);
32581
+ }
32582
+ }
32583
+ }
32584
+ }
32585
+
32586
+ // src/agents/acp/clients/shared/config-options-for-permission.ts
32587
+ function configOptionsForPermission(getActive, established) {
32588
+ const mem = getActive?.();
32589
+ if (Array.isArray(mem) && mem.length > 0) return mem;
32590
+ return established ?? void 0;
32591
+ }
32592
+
32593
+ // src/agents/acp/clients/shared/establish-acp-session.ts
32594
+ function establishedFromResult(raw, sessionId) {
32595
+ const r = raw && typeof raw === "object" ? raw : {};
32596
+ return {
32597
+ sessionId,
32598
+ configOptions: Array.isArray(r.configOptions) ? r.configOptions : null,
32599
+ modes: r.modes ?? null
32600
+ };
32601
+ }
32602
+ function sessionIdFromNewSessionResult(raw) {
32603
+ const r = raw && typeof raw === "object" ? raw : {};
32604
+ return typeof r.sessionId === "string" ? r.sessionId : "";
32605
+ }
32606
+ async function establishAcpSessionWithTransport(transport, ctx, canResume, canLoad) {
32607
+ const { cwd, mcpServers, persistedAcpSessionId, agentLabel, suppressLoadReplay } = ctx;
32608
+ const prev = typeof persistedAcpSessionId === "string" && persistedAcpSessionId.trim() !== "" ? persistedAcpSessionId.trim() : "";
32609
+ if (prev) {
32610
+ if (canResume) {
32611
+ try {
32612
+ logDebug(`[Agent] ${agentLabel} ACP session/resume for stored session ${prev.slice(0, 8)}\u2026`);
32613
+ const result2 = await transport.resumeSession({ sessionId: prev, cwd, mcpServers });
32614
+ return establishedFromResult(result2, prev);
32615
+ } catch (e) {
32616
+ logDebug(`[Agent] ${agentLabel} ACP session/resume failed: ${e instanceof Error ? e.message : String(e)}`);
32617
+ }
32618
+ }
32619
+ if (canLoad) {
32620
+ suppressLoadReplay.value = true;
32621
+ try {
32622
+ logDebug(`[Agent] ${agentLabel} ACP session/load for stored session ${prev.slice(0, 8)}\u2026`);
32623
+ const result2 = await transport.loadSession({ sessionId: prev, cwd, mcpServers });
32624
+ return establishedFromResult(result2, prev);
32625
+ } catch (e) {
32626
+ logDebug(`[Agent] ${agentLabel} ACP session/load failed: ${e instanceof Error ? e.message : String(e)}`);
32627
+ } finally {
32628
+ suppressLoadReplay.value = false;
32629
+ }
32630
+ }
32631
+ }
32632
+ const result = await transport.newSession({ cwd, mcpServers });
32633
+ const sid = sessionIdFromNewSessionResult(result);
32634
+ if (!sid) throw new Error(`${agentLabel} ACP session/new did not return sessionId`);
32635
+ return establishedFromResult(result, sid);
32636
+ }
32637
+
32638
+ // src/agents/acp/clients/shared/parse-acp-init-capabilities.ts
32639
+ function parseAcpInitAgentCapabilities(initResult) {
32640
+ const agentCapabilities = initResult?.agentCapabilities;
32641
+ const canLoad = agentCapabilities?.loadSession === true;
32642
+ const sessionCaps = agentCapabilities?.sessionCapabilities;
32643
+ const canResume = Boolean(sessionCaps?.resume);
32644
+ return { canResume, canLoad };
32645
+ }
32646
+
32647
+ // src/agents/acp/clients/shared/bootstrap-acp-wire-session.ts
32648
+ async function bootstrapAcpWireSession(transport, ctx, initializeRequest) {
32649
+ const initResult = await transport.initialize(initializeRequest);
32650
+ const { canResume, canLoad } = parseAcpInitAgentCapabilities(initResult);
32651
+ await transport.afterInitialize?.();
32652
+ const established = await establishAcpSessionWithTransport(transport, ctx, canResume, canLoad);
32653
+ const sessionId = established.sessionId;
32654
+ ctx.onAcpSessionEstablished?.({
32655
+ acpSessionId: sessionId,
32656
+ configOptions: established.configOptions,
32657
+ modes: established.modes
32658
+ });
32659
+ if (ctx.backendAgentType === "claude-code") {
32660
+ const cfg = ctx.agentConfig != null && typeof ctx.agentConfig === "object" && !Array.isArray(ctx.agentConfig) ? ctx.agentConfig : null;
32661
+ const configOptionsTyped = established.configOptions;
32662
+ const modesTyped = established.modes;
32663
+ await applyClaudePermissionFromAcpSession({
32664
+ sessionId,
32665
+ agentConfig: cfg,
32666
+ configOptions: configOptionsForPermission(ctx.getActiveConfigOptions, configOptionsTyped),
32667
+ modes: modesTyped,
32668
+ setSessionConfigOption: transport.setSessionConfigOption ? (p) => transport.setSessionConfigOption(p) : async () => {
32669
+ },
32670
+ setSessionMode: transport.setSessionMode ? (p) => transport.setSessionMode(p) : async () => {
32671
+ },
32672
+ logDebug: ctx.logDebug
32673
+ });
32674
+ }
32675
+ return established;
32676
+ }
32677
+
32678
+ // src/agents/acp/clients/shared/dispatch-session-update.ts
32679
+ function dispatchAcpSessionUpdate(opts) {
32680
+ const { flatPayload, onAcpConfigOptionsUpdated, onSessionUpdate, suppressLoadReplay } = opts;
32681
+ const su = flatPayload.sessionUpdate ?? flatPayload.session_update;
32682
+ if (su === "config_option_update") {
32683
+ const co = flatPayload.configOptions;
32684
+ if (Array.isArray(co)) onAcpConfigOptionsUpdated?.(co);
32685
+ return;
32686
+ }
32687
+ if (suppressLoadReplay()) return;
32688
+ onSessionUpdate?.(flatPayload);
32689
+ }
32690
+
32691
+ // src/agents/acp/clients/shared/flatten-sdk-session-notification.ts
32692
+ function flattenSdkSessionNotificationParams(params) {
32250
32693
  return { sessionId: params.sessionId, ...params.update };
32251
32694
  }
32695
+
32696
+ // src/agents/acp/clients/shared/normalize-acp-prompt-result.ts
32697
+ function normalizeAcpPromptTurnSuccess(opts) {
32698
+ const { stopReason, output, stderrCaptureText, backendAgentType } = opts;
32699
+ const mergedOutput = output || void 0;
32700
+ const stop = (stopReason ?? "").toLowerCase();
32701
+ const cancelled = stop === "cancelled";
32702
+ const refusal = stop === "refusal";
32703
+ const stderrEvaluated = Boolean(stderrCaptureText && backendAgentType);
32704
+ const stderrSuggestsAuth = stderrEvaluated ? localAgentErrorSuggestsAuth(backendAgentType, stderrCaptureText) : false;
32705
+ if (cancelled) {
32706
+ return {
32707
+ success: false,
32708
+ stopReason,
32709
+ output: mergedOutput,
32710
+ error: mergeErrorWithStderr("Stopped by user", stderrCaptureText)
32711
+ };
32712
+ }
32713
+ if (refusal) {
32714
+ return {
32715
+ success: false,
32716
+ stopReason,
32717
+ output: mergedOutput,
32718
+ error: mergeErrorWithStderr("The agent refused the request.", stderrCaptureText)
32719
+ };
32720
+ }
32721
+ if (stderrSuggestsAuth) {
32722
+ return {
32723
+ success: false,
32724
+ stopReason,
32725
+ output: mergedOutput,
32726
+ error: stderrCaptureText
32727
+ };
32728
+ }
32729
+ return {
32730
+ success: true,
32731
+ stopReason,
32732
+ output: mergedOutput
32733
+ };
32734
+ }
32735
+ function normalizeAcpPromptTurnFailure(err, stderrCaptureText) {
32736
+ const merged = mergeErrorWithStderr(formatJsonRpcStyleError(err), stderrCaptureText);
32737
+ return { success: false, error: merged };
32738
+ }
32739
+
32740
+ // src/agents/acp/clients/shared/send-acp-prompt-via-transport.ts
32741
+ async function sendAcpPromptViaTransport(transport, ctx, sessionId, promptText) {
32742
+ try {
32743
+ const response = await transport.prompt({
32744
+ sessionId,
32745
+ prompt: [{ type: "text", text: promptText }]
32746
+ });
32747
+ await new Promise((r2) => setImmediate(r2));
32748
+ const r = response;
32749
+ return normalizeAcpPromptTurnSuccess({
32750
+ stopReason: r?.stopReason,
32751
+ output: r?.output,
32752
+ stderrCaptureText: ctx.getStderrText(),
32753
+ backendAgentType: ctx.backendAgentType
32754
+ });
32755
+ } catch (err) {
32756
+ await new Promise((r) => setImmediate(r));
32757
+ return normalizeAcpPromptTurnFailure(err, ctx.getStderrText());
32758
+ }
32759
+ }
32760
+
32761
+ // src/agents/acp/clients/sdk/sdk-acp-session-transport.ts
32762
+ function createSdkAcpSessionTransport(connection) {
32763
+ const c = connection;
32764
+ return {
32765
+ initialize: (request) => c.initialize(request),
32766
+ resumeSession: (p) => c.unstable_resumeSession(p),
32767
+ loadSession: (p) => c.loadSession(p),
32768
+ newSession: (p) => c.newSession(p),
32769
+ prompt: (p) => c.prompt(p),
32770
+ cancelSession: async (sessionId) => {
32771
+ await c.cancel({ sessionId });
32772
+ },
32773
+ setSessionConfigOption: c.setSessionConfigOption ? (p) => c.setSessionConfigOption(p) : void 0,
32774
+ setSessionMode: c.setSessionMode ? (p) => c.setSessionMode(p) : void 0
32775
+ };
32776
+ }
32777
+
32778
+ // src/agents/acp/clients/sdk/sdk-stdio-acp-client.ts
32779
+ function formatSpawnError(err, command) {
32780
+ if (err.code === "ENOENT") {
32781
+ return `Command "${command}" not found. Install the agent (e.g. Cursor CLI) or add it to PATH.`;
32782
+ }
32783
+ return err.message || String(err);
32784
+ }
32252
32785
  async function createSdkStdioAcpClient(options) {
32253
32786
  const { ClientSideConnection: ClientSideConnection2, ndJsonStream: ndJsonStream2, PROTOCOL_VERSION: PROTOCOL_VERSION2 } = await Promise.resolve().then(() => (init_acp(), acp_exports));
32254
32787
  const {
@@ -32259,7 +32792,12 @@ async function createSdkStdioAcpClient(options) {
32259
32792
  onRequest,
32260
32793
  onFileChange,
32261
32794
  killSubprocessAfterCancelMs,
32262
- onAgentSubprocessExit
32795
+ onAgentSubprocessExit,
32796
+ agentConfig,
32797
+ persistedAcpSessionId,
32798
+ onAcpSessionEstablished,
32799
+ onAcpConfigOptionsUpdated,
32800
+ getActiveConfigOptions
32263
32801
  } = options;
32264
32802
  const isWindows = process.platform === "win32";
32265
32803
  const child = spawn2(command[0], command.slice(1), {
@@ -32308,6 +32846,22 @@ async function createSdkStdioAcpClient(options) {
32308
32846
  backendAgentType,
32309
32847
  onSessionUpdate
32310
32848
  });
32849
+ const suppressLoadReplayRef = { value: false };
32850
+ const sessionCtx = {
32851
+ cwd,
32852
+ onFileChange,
32853
+ mcpServers: [],
32854
+ persistedAcpSessionId,
32855
+ agentLabel: "ACP",
32856
+ suppressLoadReplay: suppressLoadReplayRef,
32857
+ backendAgentType: backendAgentType ?? null,
32858
+ agentConfig,
32859
+ getActiveConfigOptions,
32860
+ onAcpSessionEstablished,
32861
+ onAcpConfigOptionsUpdated,
32862
+ logDebug,
32863
+ getStderrText: () => stderrCapture.getText()
32864
+ };
32311
32865
  let permissionSeq = 0;
32312
32866
  const pendingPermissionReplies = /* @__PURE__ */ new Map();
32313
32867
  const client = (_agent) => ({
@@ -32327,35 +32881,19 @@ async function createSdkStdioAcpClient(options) {
32327
32881
  });
32328
32882
  },
32329
32883
  async readTextFile(params) {
32330
- const resolvedPath = resolveSafePathUnderCwd(cwd, params.path);
32331
- if (!resolvedPath) throw new Error("Invalid or disallowed path");
32332
- try {
32333
- let content = readFileSync2(resolvedPath, "utf8");
32334
- content = sliceFileContentRange(content, params.line, params.limit);
32335
- return { content };
32336
- } catch (e) {
32337
- if (e.code === "ENOENT") return { content: "" };
32338
- throw e;
32339
- }
32884
+ return acpReadTextFileInProcess(sessionCtx, params.path, params.line, params.limit);
32340
32885
  },
32341
32886
  async writeTextFile(params) {
32342
- const resolvedPath = resolveSafePathUnderCwd(cwd, params.path);
32343
- if (!resolvedPath) throw new Error("Invalid or disallowed path");
32344
- let oldText = "";
32345
- try {
32346
- oldText = readFileSync2(resolvedPath, "utf8");
32347
- } catch (e) {
32348
- if (e.code !== "ENOENT") throw e;
32349
- }
32350
- mkdirSync2(dirname2(resolvedPath), { recursive: true });
32351
- writeFileSync2(resolvedPath, params.content, "utf8");
32352
- const displayPath = toDisplayPathRelativeToCwd(cwd, resolvedPath);
32353
- const patchContent = editSnippetToUnifiedDiff(displayPath, oldText, params.content);
32354
- onFileChange?.({ path: displayPath, oldText, newText: params.content, patchContent });
32355
- return {};
32887
+ return acpWriteTextFileInProcess(sessionCtx, params.path, params.content);
32356
32888
  },
32357
32889
  async sessionUpdate(params) {
32358
- onSessionUpdate?.(bridgePayloadFromSdkSessionNotification(params));
32890
+ const bridged = flattenSdkSessionNotificationParams(params);
32891
+ dispatchAcpSessionUpdate({
32892
+ flatPayload: bridged,
32893
+ onAcpConfigOptionsUpdated: sessionCtx.onAcpConfigOptionsUpdated,
32894
+ onSessionUpdate,
32895
+ suppressLoadReplay: () => sessionCtx.suppressLoadReplay.value
32896
+ });
32359
32897
  },
32360
32898
  async extNotification(method, params) {
32361
32899
  await extNotification(method, params);
@@ -32365,70 +32903,19 @@ async function createSdkStdioAcpClient(options) {
32365
32903
  connection.signal.addEventListener("abort", () => {
32366
32904
  child.kill();
32367
32905
  });
32368
- await connection.initialize({
32906
+ const transport = createSdkAcpSessionTransport(connection);
32907
+ const established = await bootstrapAcpWireSession(transport, sessionCtx, {
32369
32908
  protocolVersion: PROTOCOL_VERSION2,
32370
32909
  clientCapabilities: {
32371
32910
  fs: { readTextFile: true, writeTextFile: true }
32372
32911
  },
32373
32912
  clientInfo: { name: "buildautomaton-cli", version: "0.1.0" }
32374
32913
  });
32375
- const newSessionRes = await connection.newSession({ cwd, mcpServers: [] });
32376
- const sessionId = newSessionRes.sessionId;
32914
+ const sessionId = established.sessionId;
32377
32915
  settleResolve({
32378
32916
  sessionId,
32379
32917
  async sendPrompt(prompt, _options) {
32380
- try {
32381
- const response = await connection.prompt({
32382
- sessionId,
32383
- prompt: [{ type: "text", text: prompt }]
32384
- });
32385
- await new Promise((r2) => setImmediate(r2));
32386
- const r = response;
32387
- const stopReason = (r?.stopReason ?? "").toLowerCase();
32388
- const cancelled = stopReason === "cancelled";
32389
- const refusal = stopReason === "refusal";
32390
- const stderrAfter = stderrCapture.getText();
32391
- const agentType = backendAgentType ?? null;
32392
- const stderrEvaluated = Boolean(stderrAfter && agentType);
32393
- const stderrSuggestsAuth = stderrEvaluated ? localAgentErrorSuggestsAuth(agentType, stderrAfter) : false;
32394
- if (cancelled) {
32395
- return {
32396
- success: false,
32397
- stopReason: r?.stopReason,
32398
- output: r?.output,
32399
- error: mergeErrorWithStderr("Stopped by user", stderrAfter)
32400
- };
32401
- }
32402
- if (refusal) {
32403
- return {
32404
- success: false,
32405
- stopReason: r?.stopReason,
32406
- output: r?.output,
32407
- error: mergeErrorWithStderr("The agent refused the request.", stderrAfter)
32408
- };
32409
- }
32410
- if (stderrSuggestsAuth) {
32411
- return {
32412
- success: false,
32413
- stopReason: r?.stopReason,
32414
- output: r?.output,
32415
- error: stderrAfter
32416
- };
32417
- }
32418
- return {
32419
- success: true,
32420
- stopReason: r?.stopReason,
32421
- output: r?.output
32422
- };
32423
- } catch (err) {
32424
- await new Promise((r) => setImmediate(r));
32425
- const stderrAfter = stderrCapture.getText();
32426
- const merged = mergeErrorWithStderr(formatJsonRpcStyleError(err), stderrAfter);
32427
- return {
32428
- success: false,
32429
- error: merged
32430
- };
32431
- }
32918
+ return sendAcpPromptViaTransport(transport, sessionCtx, sessionId, prompt);
32432
32919
  },
32433
32920
  async cancel() {
32434
32921
  for (const [id, entry] of [...pendingPermissionReplies.entries()]) {
@@ -32436,7 +32923,7 @@ async function createSdkStdioAcpClient(options) {
32436
32923
  entry.resolve({ outcome: { outcome: "cancelled" } });
32437
32924
  }
32438
32925
  try {
32439
- await connection.cancel({ sessionId });
32926
+ await transport.cancelSession(sessionId);
32440
32927
  } catch {
32441
32928
  }
32442
32929
  if (killSubprocessAfterCancelMs != null && killSubprocessAfterCancelMs >= 0) {
@@ -32485,10 +32972,7 @@ async function detectLocalAgentPresence() {
32485
32972
  return false;
32486
32973
  }
32487
32974
  }
32488
- function buildClaudeCodeAcpSpawnCommand(base, sessionMode) {
32489
- if (!sessionMode) return [...base];
32490
- const m = sessionMode.trim();
32491
- if (m === "plan") return [...base, "--permission-mode", "plan"];
32975
+ function buildClaudeCodeAcpSpawnCommand(base, _sessionMode) {
32492
32976
  return [...base];
32493
32977
  }
32494
32978
  async function createClaudeCodeAcpClient(options) {
@@ -32530,7 +33014,7 @@ async function createCodexAcpClient(options) {
32530
33014
  return createSdkStdioAcpClient({ ...options, command });
32531
33015
  }
32532
33016
 
32533
- // src/agents/acp/clients/cursor-acp-client.ts
33017
+ // src/agents/acp/clients/cursor/cursor-acp-client.ts
32534
33018
  var cursor_acp_client_exports = {};
32535
33019
  __export(cursor_acp_client_exports, {
32536
33020
  BACKEND_LOCAL_AGENT_TYPE: () => BACKEND_LOCAL_AGENT_TYPE3,
@@ -32538,8 +33022,6 @@ __export(cursor_acp_client_exports, {
32538
33022
  createCursorAcpClient: () => createCursorAcpClient,
32539
33023
  detectLocalAgentPresence: () => detectLocalAgentPresence3
32540
33024
  });
32541
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "node:fs";
32542
- import { dirname as dirname3 } from "node:path";
32543
33025
  import { spawn as spawn3 } from "node:child_process";
32544
33026
  import * as readline from "node:readline";
32545
33027
 
@@ -32556,7 +33038,23 @@ function formatSessionUpdateKindForLog(kind) {
32556
33038
  return kind.split("_").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(" ");
32557
33039
  }
32558
33040
 
32559
- // src/agents/acp/clients/cursor-acp-client.ts
33041
+ // src/agents/acp/clients/cursor/cursor-json-rpc-acp-transport.ts
33042
+ function createCursorJsonRpcAcpTransport(deps) {
33043
+ const { send, cancelSessionNotification } = deps;
33044
+ return {
33045
+ initialize: (request) => send("initialize", request),
33046
+ afterInitialize: async () => {
33047
+ await send("authenticate", { methodId: "cursor_login" });
33048
+ },
33049
+ resumeSession: (p) => send("session/resume", p),
33050
+ loadSession: (p) => send("session/load", p),
33051
+ newSession: (p) => send("session/new", p),
33052
+ prompt: (p) => send("session/prompt", p),
33053
+ cancelSession: (sessionId) => cancelSessionNotification(sessionId)
33054
+ };
33055
+ }
33056
+
33057
+ // src/agents/acp/clients/cursor/cursor-acp-client.ts
32560
33058
  var FS_READ_METHODS = /* @__PURE__ */ new Set(["fs/read_text_file", "fs/readTextFile"]);
32561
33059
  var FS_WRITE_METHODS = /* @__PURE__ */ new Set(["fs/write_text_file", "fs/writeTextFile"]);
32562
33060
  function formatSpawnError2(err, command) {
@@ -32573,13 +33071,6 @@ function safeJsonParse(value) {
32573
33071
  return null;
32574
33072
  }
32575
33073
  }
32576
- function sliceLinesByRange(content, line, limit) {
32577
- if (line == null && limit == null) return content;
32578
- const lines = content.split("\n");
32579
- const start = line != null && line > 0 ? line - 1 : 0;
32580
- const end = limit != null && limit > 0 ? start + limit : lines.length;
32581
- return lines.slice(start, end).join("\n");
32582
- }
32583
33074
  function buildCursorAcpSpawnCommand(base, sessionMode) {
32584
33075
  if (!sessionMode) return [...base];
32585
33076
  const m = sessionMode.trim();
@@ -32593,7 +33084,11 @@ async function createCursorAcpClient(options) {
32593
33084
  backendAgentType,
32594
33085
  onSessionUpdate,
32595
33086
  onRequest,
32596
- onFileChange
33087
+ onFileChange,
33088
+ persistedAcpSessionId,
33089
+ onAcpSessionEstablished,
33090
+ onAcpConfigOptionsUpdated,
33091
+ onAgentSubprocessExit
32597
33092
  } = options;
32598
33093
  const dbgFs = process.env.BUILDAUTOMATON_DEBUG_ACP_FS === "1";
32599
33094
  const isWindows = process.platform === "win32";
@@ -32605,6 +33100,25 @@ async function createCursorAcpClient(options) {
32605
33100
  });
32606
33101
  const stderrCapture = createStderrCapture(child);
32607
33102
  child.stderr?.on("data", (chunk) => stderrCapture.append(chunk));
33103
+ child.once("close", (code, signal) => {
33104
+ onAgentSubprocessExit?.({ code, signal });
33105
+ });
33106
+ const suppressLoadReplayRef = { value: false };
33107
+ const sessionCtx = {
33108
+ cwd,
33109
+ onFileChange,
33110
+ mcpServers: [],
33111
+ persistedAcpSessionId,
33112
+ agentLabel: "Cursor",
33113
+ suppressLoadReplay: suppressLoadReplayRef,
33114
+ backendAgentType: backendAgentType ?? null,
33115
+ agentConfig: options.agentConfig,
33116
+ getActiveConfigOptions: options.getActiveConfigOptions,
33117
+ onAcpSessionEstablished,
33118
+ onAcpConfigOptionsUpdated,
33119
+ logDebug,
33120
+ getStderrText: () => stderrCapture.getText()
33121
+ };
32608
33122
  return new Promise((resolve18, reject) => {
32609
33123
  child.on("error", (err) => {
32610
33124
  child.kill();
@@ -32613,6 +33127,16 @@ async function createCursorAcpClient(options) {
32613
33127
  let nextId = 1;
32614
33128
  const pending = /* @__PURE__ */ new Map();
32615
33129
  const pendingRequests = /* @__PURE__ */ new Map();
33130
+ function cancelSessionNotification(sessionId) {
33131
+ const line = JSON.stringify({
33132
+ jsonrpc: "2.0",
33133
+ method: "session/cancel",
33134
+ params: { sessionId }
33135
+ }) + "\n";
33136
+ return new Promise((res, rej) => {
33137
+ child.stdin.write(line, (err) => err ? rej(err) : res());
33138
+ });
33139
+ }
32616
33140
  function send(method, params) {
32617
33141
  const id = nextId++;
32618
33142
  const line = JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n";
@@ -32636,7 +33160,6 @@ async function createCursorAcpClient(options) {
32636
33160
  respond(requestId, payload);
32637
33161
  pendingRequests.delete(requestId);
32638
33162
  }
32639
- let promptOutputBuffer = "";
32640
33163
  const rl = readline.createInterface({ input: child.stdout });
32641
33164
  rl.on("line", (line) => {
32642
33165
  const msg = safeJsonParse(line);
@@ -32665,11 +33188,12 @@ async function createCursorAcpClient(options) {
32665
33188
  `[acp] Received session update (${kindLabel}) tool=${toolName || "(none)"}`
32666
33189
  );
32667
33190
  }
32668
- const isTextChunk = sessionUpdate === "agent_message_chunk" && update.content?.text;
32669
- if (isTextChunk && update.content?.text) {
32670
- promptOutputBuffer += update.content.text;
32671
- }
32672
- onSessionUpdate?.(update);
33191
+ dispatchAcpSessionUpdate({
33192
+ flatPayload: update,
33193
+ onAcpConfigOptionsUpdated: sessionCtx.onAcpConfigOptionsUpdated,
33194
+ onSessionUpdate,
33195
+ suppressLoadReplay: () => sessionCtx.suppressLoadReplay.value
33196
+ });
32673
33197
  return;
32674
33198
  }
32675
33199
  if (method === "session/request_permission" && typeof id === "number") {
@@ -32689,21 +33213,13 @@ async function createCursorAcpClient(options) {
32689
33213
  if (dbgFs) {
32690
33214
  console.error(`[acp-fs] ${method} path=${filePath.slice(0, 200)}${filePath.length > 200 ? "\u2026" : ""}`);
32691
33215
  }
32692
- const resolvedPath = resolveSafePathUnderCwd(cwd, filePath);
32693
- if (!resolvedPath) {
32694
- if (dbgFs) console.error(`[acp-fs] ${method} rejected path (outside cwd or empty)`);
32695
- respondJsonRpcError(id, -32602, "Invalid or disallowed path");
32696
- return;
32697
- }
32698
33216
  try {
32699
- let content = readFileSync3(resolvedPath, "utf8");
32700
- const line2 = typeof params.line === "number" ? params.line : void 0;
32701
- const limit = typeof params.limit === "number" ? params.limit : void 0;
32702
- content = sliceLinesByRange(content, line2, limit);
32703
- respond(id, { content });
33217
+ const lineNum = typeof params.line === "number" ? params.line : void 0;
33218
+ const limitNum = typeof params.limit === "number" ? params.limit : void 0;
33219
+ const out = acpReadTextFileInProcess(sessionCtx, filePath, lineNum, limitNum);
33220
+ respond(id, out);
32704
33221
  } catch (e) {
32705
- const code = e?.code;
32706
- if (code === "ENOENT") {
33222
+ if (e?.code === "ENOENT") {
32707
33223
  respond(id, { content: "" });
32708
33224
  } else {
32709
33225
  respondJsonRpcError(id, -32e3, e instanceof Error ? e.message : String(e));
@@ -32720,32 +33236,17 @@ async function createCursorAcpClient(options) {
32720
33236
  `[acp-fs] ${method} path=${filePath.slice(0, 200)}${filePath.length > 200 ? "\u2026" : ""} newBytes=${newText.length}`
32721
33237
  );
32722
33238
  }
32723
- const resolvedPath = resolveSafePathUnderCwd(cwd, filePath);
32724
- if (!resolvedPath) {
32725
- if (dbgFs) console.error(`[acp-fs] ${method} rejected path (outside cwd or empty): ${filePath.slice(0, 120)}`);
32726
- respondJsonRpcError(id, -32602, "Invalid or disallowed path");
32727
- return;
32728
- }
32729
- let oldText = "";
32730
33239
  try {
32731
- oldText = readFileSync3(resolvedPath, "utf8");
33240
+ acpWriteTextFileInProcess(sessionCtx, filePath, newText);
33241
+ respond(id, null);
32732
33242
  } catch (e) {
32733
- if (e.code !== "ENOENT") {
33243
+ if (e.message === "Invalid or disallowed path") {
33244
+ if (dbgFs) console.error(`[acp-fs] ${method} rejected path (outside cwd or empty): ${filePath.slice(0, 120)}`);
33245
+ respondJsonRpcError(id, -32602, "Invalid or disallowed path");
33246
+ } else {
32734
33247
  respondJsonRpcError(id, -32e3, e instanceof Error ? e.message : String(e));
32735
- return;
32736
33248
  }
32737
33249
  }
32738
- try {
32739
- mkdirSync3(dirname3(resolvedPath), { recursive: true });
32740
- writeFileSync3(resolvedPath, newText, "utf8");
32741
- } catch (e) {
32742
- respondJsonRpcError(id, -32e3, e instanceof Error ? e.message : String(e));
32743
- return;
32744
- }
32745
- const displayPath = toDisplayPathRelativeToCwd(cwd, resolvedPath);
32746
- const patchContent = editSnippetToUnifiedDiff(displayPath, oldText, newText);
32747
- onFileChange?.({ path: displayPath, oldText, newText, patchContent });
32748
- respond(id, null);
32749
33250
  return;
32750
33251
  }
32751
33252
  if (method === "cursor/create_plan" || method === "cursor/ask_question") {
@@ -32767,16 +33268,7 @@ async function createCursorAcpClient(options) {
32767
33268
  });
32768
33269
  (async () => {
32769
33270
  try {
32770
- let sendSessionCancelNotification2 = function() {
32771
- const line = JSON.stringify({
32772
- jsonrpc: "2.0",
32773
- method: "session/cancel",
32774
- params: { sessionId }
32775
- }) + "\n";
32776
- return new Promise((res, rej) => {
32777
- child.stdin.write(line, (err) => err ? rej(err) : res());
32778
- });
32779
- }, cancelPendingPermissionRequests2 = function() {
33271
+ let cancelPendingPermissionRequests2 = function() {
32780
33272
  for (const [reqId, pending2] of [...pendingRequests.entries()]) {
32781
33273
  if (pending2.method === "session/request_permission") {
32782
33274
  respond(reqId, { outcome: { outcome: "cancelled" } });
@@ -32784,76 +33276,25 @@ async function createCursorAcpClient(options) {
32784
33276
  }
32785
33277
  }
32786
33278
  };
32787
- var sendSessionCancelNotification = sendSessionCancelNotification2, cancelPendingPermissionRequests = cancelPendingPermissionRequests2;
32788
- await send("initialize", {
33279
+ var cancelPendingPermissionRequests = cancelPendingPermissionRequests2;
33280
+ const transport = createCursorJsonRpcAcpTransport({
33281
+ send,
33282
+ cancelSessionNotification
33283
+ });
33284
+ const established = await bootstrapAcpWireSession(transport, sessionCtx, {
32789
33285
  protocolVersion: 1,
32790
33286
  clientCapabilities: { fs: { readTextFile: true, writeTextFile: true }, terminal: false },
32791
33287
  clientInfo: { name: "buildautomaton-cli", version: "0.1.0" }
32792
33288
  });
32793
- await send("authenticate", { methodId: "cursor_login" });
32794
- const newResult = await send("session/new", { cwd, mcpServers: [] });
32795
- const sessionId = newResult?.sessionId ?? "";
32796
- if (!sessionId) throw new Error("Cursor ACP session/new did not return sessionId");
33289
+ const sessionId = established.sessionId;
32797
33290
  resolve18({
32798
33291
  sessionId,
32799
33292
  async sendPrompt(prompt, _options) {
32800
- promptOutputBuffer = "";
32801
- try {
32802
- const result = await send("session/prompt", {
32803
- sessionId,
32804
- prompt: [{ type: "text", text: prompt }]
32805
- });
32806
- await new Promise((r) => setImmediate(r));
32807
- const output = (result?.output ?? promptOutputBuffer) || void 0;
32808
- const stopReason = (result?.stopReason ?? "").toLowerCase();
32809
- const cancelled = stopReason === "cancelled";
32810
- const refusal = stopReason === "refusal";
32811
- const stderrAfter = stderrCapture.getText();
32812
- const agentType = backendAgentType ?? null;
32813
- const stderrEvaluated = Boolean(stderrAfter && agentType);
32814
- const stderrSuggestsAuth = stderrEvaluated ? localAgentErrorSuggestsAuth(agentType, stderrAfter) : false;
32815
- if (cancelled) {
32816
- return {
32817
- success: false,
32818
- stopReason: result?.stopReason,
32819
- output: output || void 0,
32820
- error: mergeErrorWithStderr("Stopped by user", stderrAfter)
32821
- };
32822
- }
32823
- if (refusal) {
32824
- return {
32825
- success: false,
32826
- stopReason: result?.stopReason,
32827
- output: output || void 0,
32828
- error: mergeErrorWithStderr("The agent refused the request.", stderrAfter)
32829
- };
32830
- }
32831
- if (stderrSuggestsAuth) {
32832
- return {
32833
- success: false,
32834
- stopReason: result?.stopReason,
32835
- output: output || void 0,
32836
- error: stderrAfter
32837
- };
32838
- }
32839
- return {
32840
- success: true,
32841
- stopReason: result?.stopReason,
32842
- output: output || void 0
32843
- };
32844
- } catch (err) {
32845
- await new Promise((r) => setImmediate(r));
32846
- const stderrAfter = stderrCapture.getText();
32847
- const merged = mergeErrorWithStderr(formatJsonRpcStyleError(err), stderrAfter);
32848
- return {
32849
- success: false,
32850
- error: merged
32851
- };
32852
- }
33293
+ return sendAcpPromptViaTransport(transport, sessionCtx, sessionId, prompt);
32853
33294
  },
32854
33295
  async cancel() {
32855
33296
  cancelPendingPermissionRequests2();
32856
- await sendSessionCancelNotification2();
33297
+ await transport.cancelSession(sessionId);
32857
33298
  },
32858
33299
  resolveRequest(requestId, result) {
32859
33300
  const numericId = Number(requestId);
@@ -32951,7 +33392,7 @@ function resolveAgentCommand(preferredAgentType) {
32951
33392
  command,
32952
33393
  label: preferredAgentType,
32953
33394
  createClient: createCursorAcpClient,
32954
- spawnCommandForSession: (sessionMode) => buildCursorAcpSpawnCommand(command, sessionMode)
33395
+ spawnCommandForSession: (sessionMode, _agentConfig) => buildCursorAcpSpawnCommand(command, sessionMode)
32955
33396
  };
32956
33397
  }
32957
33398
  if (useCodexAcp(preferredAgentType, command)) {
@@ -32959,7 +33400,7 @@ function resolveAgentCommand(preferredAgentType) {
32959
33400
  command,
32960
33401
  label: preferredAgentType,
32961
33402
  createClient: createCodexAcpClient,
32962
- spawnCommandForSession: (sessionMode) => buildCodexAcpSpawnCommand(command, sessionMode)
33403
+ spawnCommandForSession: (sessionMode, _agentConfig) => buildCodexAcpSpawnCommand(command, sessionMode)
32963
33404
  };
32964
33405
  }
32965
33406
  if (useKiroAcp(preferredAgentType, command)) {
@@ -32967,7 +33408,7 @@ function resolveAgentCommand(preferredAgentType) {
32967
33408
  command,
32968
33409
  label: preferredAgentType,
32969
33410
  createClient: createKiroAcpClient,
32970
- spawnCommandForSession: (sessionMode) => buildKiroAcpSpawnCommand(command, sessionMode)
33411
+ spawnCommandForSession: (sessionMode, _agentConfig) => buildKiroAcpSpawnCommand(command, sessionMode)
32971
33412
  };
32972
33413
  }
32973
33414
  return {
@@ -33001,7 +33442,7 @@ function getGitRepoRootSync(startDir) {
33001
33442
 
33002
33443
  // src/agents/acp/workspace-files.ts
33003
33444
  import { execFileSync as execFileSync3 } from "node:child_process";
33004
- import { readFileSync as readFileSync4 } from "node:fs";
33445
+ import { readFileSync as readFileSync3 } from "node:fs";
33005
33446
  import * as path11 from "node:path";
33006
33447
  function resolveWorkspaceFilePath(sessionParentPath, rawPath) {
33007
33448
  const trimmed2 = rawPath.trim();
@@ -33033,7 +33474,7 @@ function readUtf8WorkspaceFile(sessionParentPath, displayPath) {
33033
33474
  const rel = path11.relative(gitRoot, resolvedPath2);
33034
33475
  if (!rel.startsWith("..") && !path11.isAbsolute(rel)) {
33035
33476
  try {
33036
- return readFileSync4(resolvedPath2, "utf8");
33477
+ return readFileSync3(resolvedPath2, "utf8");
33037
33478
  } catch {
33038
33479
  }
33039
33480
  }
@@ -33041,7 +33482,7 @@ function readUtf8WorkspaceFile(sessionParentPath, displayPath) {
33041
33482
  const resolvedPath = resolveSafePathUnderCwd(sessionParentPath, displayPath);
33042
33483
  if (!resolvedPath) return "";
33043
33484
  try {
33044
- return readFileSync4(resolvedPath, "utf8");
33485
+ return readFileSync3(resolvedPath, "utf8");
33045
33486
  } catch {
33046
33487
  return "";
33047
33488
  }
@@ -33545,6 +33986,9 @@ function createBridgeOnSessionUpdate(opts) {
33545
33986
  const sentFileChangePaths = /* @__PURE__ */ new Set();
33546
33987
  const p = params;
33547
33988
  const updateKind = p.sessionUpdate ?? p.session_update ?? p.type ?? "update";
33989
+ if (updateKind === "config_option_update") {
33990
+ return;
33991
+ }
33548
33992
  const isCompletedToolCallUpdate = updateKind === "tool_call_update" && isCompletedToolStatus(p.status);
33549
33993
  const toolName = p.toolCall?.name ?? p.tool_call?.name ?? "";
33550
33994
  const isToolUpdate = updateKind === "tool_call" || updateKind === "tool_call_update" || typeof toolName === "string" && toolName.length > 0;
@@ -33631,14 +34075,72 @@ function buildAcpSessionBridgeHooks(opts) {
33631
34075
  };
33632
34076
  }
33633
34077
 
34078
+ // src/agents/acp/local-agent-session-file.ts
34079
+ import fs10 from "node:fs";
34080
+ import os3 from "node:os";
34081
+ import path12 from "node:path";
34082
+ var LOCAL_AGENT_SESSION_DIR = path12.join(os3.homedir(), ".buildautomaton", "agent-sessions");
34083
+ function safeFileSlug(cloudSessionId) {
34084
+ const t = cloudSessionId.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 220);
34085
+ return t.length > 0 ? t : "session";
34086
+ }
34087
+ function localAgentSessionFilePath(cloudSessionId) {
34088
+ return path12.join(LOCAL_AGENT_SESSION_DIR, `${safeFileSlug(cloudSessionId)}.json`);
34089
+ }
34090
+ function readLocalAgentSessionFile(cloudSessionId) {
34091
+ try {
34092
+ const p = localAgentSessionFilePath(cloudSessionId);
34093
+ const raw = fs10.readFileSync(p, "utf8");
34094
+ const parsed = JSON.parse(raw);
34095
+ if (parsed.v !== 1) return null;
34096
+ return {
34097
+ v: 1,
34098
+ acpSessionId: typeof parsed.acpSessionId === "string" ? parsed.acpSessionId : null,
34099
+ backendAgentType: typeof parsed.backendAgentType === "string" ? parsed.backendAgentType : null,
34100
+ configOptions: Array.isArray(parsed.configOptions) ? parsed.configOptions : null,
34101
+ updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : (/* @__PURE__ */ new Date()).toISOString()
34102
+ };
34103
+ } catch {
34104
+ return null;
34105
+ }
34106
+ }
34107
+ function writeLocalAgentSessionFile(cloudSessionId, patch) {
34108
+ try {
34109
+ const dir = LOCAL_AGENT_SESSION_DIR;
34110
+ if (!fs10.existsSync(dir)) fs10.mkdirSync(dir, { recursive: true });
34111
+ const p = localAgentSessionFilePath(cloudSessionId);
34112
+ const prev = readLocalAgentSessionFile(cloudSessionId);
34113
+ const next = {
34114
+ v: 1,
34115
+ acpSessionId: patch.acpSessionId !== void 0 ? patch.acpSessionId : prev?.acpSessionId ?? null,
34116
+ backendAgentType: patch.backendAgentType !== void 0 ? patch.backendAgentType : prev?.backendAgentType ?? null,
34117
+ configOptions: patch.configOptions !== void 0 ? patch.configOptions : prev?.configOptions ?? null,
34118
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
34119
+ };
34120
+ fs10.writeFileSync(p, JSON.stringify(next, null, 2), "utf8");
34121
+ } catch {
34122
+ }
34123
+ }
34124
+
33634
34125
  // src/agents/acp/ensure-acp-client.ts
33635
34126
  async function ensureAcpClient(options) {
33636
- const { state, preferredAgentType, mode, sessionParentPath, routing, sendSessionUpdate, sendRequest, log: log2 } = options;
34127
+ const {
34128
+ state,
34129
+ preferredAgentType,
34130
+ mode,
34131
+ agentConfig,
34132
+ sessionParentPath,
34133
+ routing,
34134
+ cloudSessionId,
34135
+ sendSessionUpdate,
34136
+ sendRequest,
34137
+ log: log2
34138
+ } = options;
33637
34139
  const targetSessionParentPath = resolveSessionParentPathForAgentProcess(sessionParentPath);
33638
34140
  if (state.acpStartPromise && !state.acpHandle) {
33639
34141
  await state.acpStartPromise;
33640
34142
  }
33641
- if (state.acpHandle && state.lastAcpCwd != null && path12.resolve(state.lastAcpCwd) !== path12.resolve(targetSessionParentPath)) {
34143
+ if (state.acpHandle && state.lastAcpCwd != null && path13.resolve(state.lastAcpCwd) !== path13.resolve(targetSessionParentPath)) {
33642
34144
  try {
33643
34145
  state.acpHandle.disconnect();
33644
34146
  } catch {
@@ -33646,6 +34148,7 @@ async function ensureAcpClient(options) {
33646
34148
  state.acpHandle = null;
33647
34149
  state.acpStartPromise = null;
33648
34150
  state.acpAgentKey = null;
34151
+ state.activeSessionConfigOptions = null;
33649
34152
  }
33650
34153
  const resolved = resolveAgentCommand(preferredAgentType);
33651
34154
  if (!resolved) {
@@ -33655,8 +34158,9 @@ async function ensureAcpClient(options) {
33655
34158
  state.lastAcpStartError = "No agent type: ensure the app sends agentType on prompts or agent_config for this bridge.";
33656
34159
  return null;
33657
34160
  }
33658
- const fullCmd = resolved.spawnCommandForSession(mode);
33659
- const agentKey = `${resolved.label}::${fullCmd.join("\0")}`;
34161
+ const fullCmd = resolved.spawnCommandForSession(mode, agentConfig ?? null);
34162
+ const cacheConfig = agentConfig != null && typeof agentConfig === "object" && !Array.isArray(agentConfig) && Object.keys(agentConfig).length > 0 ? `\0${JSON.stringify(agentConfig, Object.keys(agentConfig).sort())}` : "";
34163
+ const agentKey = `${resolved.label}::${fullCmd.join("\0")}${cacheConfig}`;
33660
34164
  if (state.acpHandle && state.acpAgentKey !== agentKey) {
33661
34165
  try {
33662
34166
  state.acpHandle.disconnect();
@@ -33665,12 +34169,13 @@ async function ensureAcpClient(options) {
33665
34169
  state.acpHandle = null;
33666
34170
  state.acpStartPromise = null;
33667
34171
  state.acpAgentKey = null;
34172
+ state.activeSessionConfigOptions = null;
33668
34173
  }
33669
34174
  if (state.acpHandle) return state.acpHandle;
33670
34175
  if (!state.acpStartPromise) {
33671
34176
  let statOk = false;
33672
34177
  try {
33673
- const st = fs10.statSync(targetSessionParentPath);
34178
+ const st = fs11.statSync(targetSessionParentPath);
33674
34179
  statOk = st.isDirectory();
33675
34180
  if (!statOk) {
33676
34181
  state.lastAcpStartError = `Agent cwd is not a directory: ${targetSessionParentPath}`;
@@ -33690,14 +34195,40 @@ async function ensureAcpClient(options) {
33690
34195
  getSendRequest: () => sendRequest,
33691
34196
  log: log2
33692
34197
  });
34198
+ const persisted = cloudSessionId != null && cloudSessionId !== "" && preferredAgentType != null && preferredAgentType !== "" ? readLocalAgentSessionFile(cloudSessionId) : null;
34199
+ const persistedAcpSessionId = persisted && persisted.backendAgentType === preferredAgentType && typeof persisted.acpSessionId === "string" && persisted.acpSessionId.trim() !== "" ? persisted.acpSessionId.trim() : null;
34200
+ state.activeSessionConfigOptions = Array.isArray(persisted?.configOptions) ? persisted.configOptions : null;
33693
34201
  state.acpStartPromise = resolved.createClient({
33694
34202
  command: resolved.command,
33695
34203
  sessionMode: mode,
34204
+ agentConfig: agentConfig ?? null,
33696
34205
  backendAgentType: preferredAgentType,
34206
+ persistedAcpSessionId,
34207
+ getActiveConfigOptions: () => state.activeSessionConfigOptions,
34208
+ onAcpSessionEstablished: (info) => {
34209
+ state.activeSessionConfigOptions = info.configOptions ?? state.activeSessionConfigOptions;
34210
+ if (cloudSessionId != null && cloudSessionId !== "" && preferredAgentType != null && preferredAgentType !== "") {
34211
+ writeLocalAgentSessionFile(cloudSessionId, {
34212
+ acpSessionId: info.acpSessionId,
34213
+ configOptions: info.configOptions,
34214
+ backendAgentType: preferredAgentType
34215
+ });
34216
+ }
34217
+ },
34218
+ onAcpConfigOptionsUpdated: (configOptions) => {
34219
+ state.activeSessionConfigOptions = configOptions;
34220
+ if (cloudSessionId != null && cloudSessionId !== "" && preferredAgentType != null && preferredAgentType !== "") {
34221
+ writeLocalAgentSessionFile(cloudSessionId, {
34222
+ configOptions,
34223
+ backendAgentType: preferredAgentType
34224
+ });
34225
+ }
34226
+ },
33697
34227
  onAgentSubprocessExit: () => {
33698
34228
  state.acpHandle = null;
33699
34229
  state.acpStartPromise = null;
33700
34230
  state.acpAgentKey = null;
34231
+ state.activeSessionConfigOptions = null;
33701
34232
  state.lastAcpStartError = "Agent subprocess exited";
33702
34233
  },
33703
34234
  ...hooks,
@@ -33727,7 +34258,8 @@ async function createAcpManager(options) {
33727
34258
  acpStartPromise: null,
33728
34259
  lastAcpStartError: null,
33729
34260
  lastAcpCwd: null,
33730
- acpAgentKey: null
34261
+ acpAgentKey: null,
34262
+ activeSessionConfigOptions: null
33731
34263
  };
33732
34264
  let backendFallbackAgentType = null;
33733
34265
  const promptRouting = {};
@@ -33754,6 +34286,7 @@ async function createAcpManager(options) {
33754
34286
  runId,
33755
34287
  mode,
33756
34288
  agentType,
34289
+ agentConfig,
33757
34290
  sessionParentPath,
33758
34291
  sendResult: sendResult2,
33759
34292
  sendSessionUpdate,
@@ -33773,8 +34306,10 @@ async function createAcpManager(options) {
33773
34306
  state,
33774
34307
  preferredAgentType: preferredForPrompt,
33775
34308
  mode,
34309
+ agentConfig: agentConfig ?? null,
33776
34310
  sessionParentPath,
33777
34311
  routing: promptRouting,
34312
+ cloudSessionId: sessionId,
33778
34313
  sendSessionUpdate,
33779
34314
  sendRequest: sendSessionUpdate,
33780
34315
  log: log2
@@ -33865,6 +34400,7 @@ async function createAcpManager(options) {
33865
34400
  state.acpHandle = null;
33866
34401
  state.acpStartPromise = null;
33867
34402
  state.acpAgentKey = null;
34403
+ state.activeSessionConfigOptions = null;
33868
34404
  }
33869
34405
  return {
33870
34406
  setPreferredAgentType,
@@ -33877,26 +34413,26 @@ async function createAcpManager(options) {
33877
34413
  }
33878
34414
 
33879
34415
  // src/worktrees/session-worktree-manager.ts
33880
- import * as path19 from "node:path";
33881
- import os4 from "node:os";
34416
+ import * as path20 from "node:path";
34417
+ import os5 from "node:os";
33882
34418
 
33883
34419
  // src/worktrees/prepare-new-session-worktrees.ts
33884
- import * as fs12 from "node:fs";
33885
- import * as path14 from "node:path";
34420
+ import * as fs13 from "node:fs";
34421
+ import * as path15 from "node:path";
33886
34422
 
33887
34423
  // src/git/worktree-add.ts
33888
34424
  async function gitWorktreeAddBranch(mainRepoPath, worktreePath, branch) {
33889
- const mainGit = simpleGit(mainRepoPath);
34425
+ const mainGit = cliSimpleGit(mainRepoPath);
33890
34426
  await mainGit.raw(["worktree", "add", "-b", branch, worktreePath, "HEAD"]);
33891
34427
  }
33892
34428
 
33893
34429
  // src/worktrees/worktree-layout-file.ts
33894
- import * as fs11 from "node:fs";
33895
- import * as path13 from "node:path";
33896
- import os3 from "node:os";
34430
+ import * as fs12 from "node:fs";
34431
+ import * as path14 from "node:path";
34432
+ import os4 from "node:os";
33897
34433
  var LAYOUT_FILENAME = "worktree-launcher-layout.json";
33898
34434
  function defaultWorktreeLayoutPath() {
33899
- return path13.join(os3.homedir(), ".buildautomaton", LAYOUT_FILENAME);
34435
+ return path14.join(os4.homedir(), ".buildautomaton", LAYOUT_FILENAME);
33900
34436
  }
33901
34437
  function normalizeLoadedLayout(raw) {
33902
34438
  if (raw && typeof raw === "object" && "launcherCwds" in raw) {
@@ -33908,8 +34444,8 @@ function normalizeLoadedLayout(raw) {
33908
34444
  function loadWorktreeLayout() {
33909
34445
  try {
33910
34446
  const p = defaultWorktreeLayoutPath();
33911
- if (!fs11.existsSync(p)) return { launcherCwds: [] };
33912
- const raw = JSON.parse(fs11.readFileSync(p, "utf8"));
34447
+ if (!fs12.existsSync(p)) return { launcherCwds: [] };
34448
+ const raw = JSON.parse(fs12.readFileSync(p, "utf8"));
33913
34449
  return normalizeLoadedLayout(raw);
33914
34450
  } catch {
33915
34451
  return { launcherCwds: [] };
@@ -33917,24 +34453,24 @@ function loadWorktreeLayout() {
33917
34453
  }
33918
34454
  function saveWorktreeLayout(layout) {
33919
34455
  try {
33920
- const dir = path13.dirname(defaultWorktreeLayoutPath());
33921
- fs11.mkdirSync(dir, { recursive: true });
33922
- fs11.writeFileSync(defaultWorktreeLayoutPath(), JSON.stringify(layout, null, 2), "utf8");
34456
+ const dir = path14.dirname(defaultWorktreeLayoutPath());
34457
+ fs12.mkdirSync(dir, { recursive: true });
34458
+ fs12.writeFileSync(defaultWorktreeLayoutPath(), JSON.stringify(layout, null, 2), "utf8");
33923
34459
  } catch {
33924
34460
  }
33925
34461
  }
33926
34462
  function baseNameSafe(pathString) {
33927
- return path13.basename(pathString).replace(/[^a-zA-Z0-9._-]+/g, "-") || "cwd";
34463
+ return path14.basename(pathString).replace(/[^a-zA-Z0-9._-]+/g, "-") || "cwd";
33928
34464
  }
33929
34465
  function getLauncherDirNameIfPresent(layout, bridgeRootPath2) {
33930
- const norm = path13.resolve(bridgeRootPath2);
33931
- const existing = layout.launcherCwds.find((e) => path13.resolve(e.absolutePath) === norm);
34466
+ const norm = path14.resolve(bridgeRootPath2);
34467
+ const existing = layout.launcherCwds.find((e) => path14.resolve(e.absolutePath) === norm);
33932
34468
  return existing?.dirName;
33933
34469
  }
33934
34470
  function allocateDirNameForLauncherCwd(layout, bridgeRootPath2) {
33935
34471
  const existing = getLauncherDirNameIfPresent(layout, bridgeRootPath2);
33936
34472
  if (existing) return existing;
33937
- const norm = path13.resolve(bridgeRootPath2);
34473
+ const norm = path14.resolve(bridgeRootPath2);
33938
34474
  const base = baseNameSafe(norm);
33939
34475
  const used = new Set(layout.launcherCwds.map((e) => e.dirName));
33940
34476
  let name = base;
@@ -33951,10 +34487,10 @@ function allocateDirNameForLauncherCwd(layout, bridgeRootPath2) {
33951
34487
  // src/worktrees/prepare-new-session-worktrees.ts
33952
34488
  async function prepareNewSessionWorktrees(options) {
33953
34489
  const { worktreesRootPath, bridgeRoot, sessionId, layout, log: log2 } = options;
33954
- const bridgeResolved = path14.resolve(bridgeRoot);
34490
+ const bridgeResolved = path15.resolve(bridgeRoot);
33955
34491
  const cwdKey = allocateDirNameForLauncherCwd(layout, bridgeResolved);
33956
- const bridgeKeyDir = path14.join(worktreesRootPath, cwdKey);
33957
- const sessionDir = path14.join(bridgeKeyDir, sessionId);
34492
+ const bridgeKeyDir = path15.join(worktreesRootPath, cwdKey);
34493
+ const sessionDir = path15.join(bridgeKeyDir, sessionId);
33958
34494
  const repos = await discoverGitReposUnderRoot(bridgeResolved);
33959
34495
  if (repos.length === 0) {
33960
34496
  log2("[worktrees] No Git repositories under bridge root; skipping worktree creation.");
@@ -33962,14 +34498,14 @@ async function prepareNewSessionWorktrees(options) {
33962
34498
  }
33963
34499
  const branch = `session-${sessionId}`;
33964
34500
  const worktreePaths = [];
33965
- fs12.mkdirSync(sessionDir, { recursive: true });
34501
+ fs13.mkdirSync(sessionDir, { recursive: true });
33966
34502
  for (const repo of repos) {
33967
- let rel = path14.relative(bridgeResolved, repo.absolutePath);
33968
- if (rel.startsWith("..") || path14.isAbsolute(rel)) continue;
34503
+ let rel = path15.relative(bridgeResolved, repo.absolutePath);
34504
+ if (rel.startsWith("..") || path15.isAbsolute(rel)) continue;
33969
34505
  const relNorm = rel === "" ? "." : rel;
33970
- const wtPath = relNorm === "." ? sessionDir : path14.join(sessionDir, relNorm);
34506
+ const wtPath = relNorm === "." ? sessionDir : path15.join(sessionDir, relNorm);
33971
34507
  if (relNorm !== ".") {
33972
- fs12.mkdirSync(path14.dirname(wtPath), { recursive: true });
34508
+ fs13.mkdirSync(path15.dirname(wtPath), { recursive: true });
33973
34509
  }
33974
34510
  try {
33975
34511
  await gitWorktreeAddBranch(repo.absolutePath, wtPath, branch);
@@ -33991,7 +34527,7 @@ async function prepareNewSessionWorktrees(options) {
33991
34527
 
33992
34528
  // src/git/rename-branch.ts
33993
34529
  async function gitRenameCurrentBranch(repoDir, newName) {
33994
- const g = simpleGit(repoDir);
34530
+ const g = cliSimpleGit(repoDir);
33995
34531
  await g.raw(["branch", "-m", newName]);
33996
34532
  }
33997
34533
 
@@ -34011,32 +34547,32 @@ async function renameSessionWorktreeBranches(paths, newBranch, log2) {
34011
34547
  }
34012
34548
 
34013
34549
  // src/worktrees/remove-session-worktrees.ts
34014
- import * as fs15 from "node:fs";
34550
+ import * as fs16 from "node:fs";
34015
34551
 
34016
34552
  // src/git/worktree-remove.ts
34017
- import * as fs14 from "node:fs";
34553
+ import * as fs15 from "node:fs";
34018
34554
 
34019
34555
  // src/git/resolve-main-repo-from-git-file.ts
34020
- import * as fs13 from "node:fs";
34021
- import * as path15 from "node:path";
34556
+ import * as fs14 from "node:fs";
34557
+ import * as path16 from "node:path";
34022
34558
  function resolveMainRepoFromWorktreeGitFile(wt) {
34023
- const gitDirFile = path15.join(wt, ".git");
34024
- if (!fs13.existsSync(gitDirFile) || !fs13.statSync(gitDirFile).isFile()) return "";
34025
- const first2 = fs13.readFileSync(gitDirFile, "utf8").trim();
34559
+ const gitDirFile = path16.join(wt, ".git");
34560
+ if (!fs14.existsSync(gitDirFile) || !fs14.statSync(gitDirFile).isFile()) return "";
34561
+ const first2 = fs14.readFileSync(gitDirFile, "utf8").trim();
34026
34562
  const m = first2.match(/^gitdir:\s*(.+)$/im);
34027
34563
  if (!m) return "";
34028
- const gitWorktreePath = path15.resolve(wt, m[1].trim());
34029
- const gitDir = path15.dirname(path15.dirname(gitWorktreePath));
34030
- return path15.dirname(gitDir);
34564
+ const gitWorktreePath = path16.resolve(wt, m[1].trim());
34565
+ const gitDir = path16.dirname(path16.dirname(gitWorktreePath));
34566
+ return path16.dirname(gitDir);
34031
34567
  }
34032
34568
 
34033
34569
  // src/git/worktree-remove.ts
34034
34570
  async function gitWorktreeRemoveForce(worktreePath) {
34035
34571
  const mainRepo = resolveMainRepoFromWorktreeGitFile(worktreePath);
34036
34572
  if (mainRepo) {
34037
- await simpleGit(mainRepo).raw(["worktree", "remove", "--force", worktreePath]);
34573
+ await cliSimpleGit(mainRepo).raw(["worktree", "remove", "--force", worktreePath]);
34038
34574
  } else {
34039
- fs14.rmSync(worktreePath, { recursive: true, force: true });
34575
+ fs15.rmSync(worktreePath, { recursive: true, force: true });
34040
34576
  }
34041
34577
  }
34042
34578
 
@@ -34049,7 +34585,7 @@ async function removeSessionWorktrees(paths, log2) {
34049
34585
  } catch (e) {
34050
34586
  log2(`[worktrees] Remove failed for ${wt}: ${e instanceof Error ? e.message : String(e)}`);
34051
34587
  try {
34052
- fs15.rmSync(wt, { recursive: true, force: true });
34588
+ fs16.rmSync(wt, { recursive: true, force: true });
34053
34589
  } catch {
34054
34590
  }
34055
34591
  }
@@ -34164,7 +34700,7 @@ async function gitLogNotReachableFromBase(g, baseSha, headSha) {
34164
34700
  }
34165
34701
  }
34166
34702
  async function commitsAheadOfRemoteTracking(repoDir) {
34167
- const g = simpleGit(repoDir);
34703
+ const g = cliSimpleGit(repoDir);
34168
34704
  const headSha = await revParseSafe(g, "HEAD");
34169
34705
  if (!headSha) return 0;
34170
34706
  const baseSha = await resolveBaseShaForUnpushedCommits(g);
@@ -34178,14 +34714,14 @@ async function commitsAheadOfRemoteTracking(repoDir) {
34178
34714
  }
34179
34715
  }
34180
34716
  async function getRepoWorkingTreeStatus(repoDir) {
34181
- const g = simpleGit(repoDir);
34717
+ const g = cliSimpleGit(repoDir);
34182
34718
  const st = await g.status();
34183
34719
  const hasUncommittedChanges = (st.files?.length ?? 0) > 0;
34184
34720
  const ahead = await commitsAheadOfRemoteTracking(repoDir);
34185
34721
  return { hasUncommittedChanges, hasUnpushedCommits: ahead > 0 };
34186
34722
  }
34187
34723
  async function listUnpushedCommits(repoDir) {
34188
- const g = simpleGit(repoDir);
34724
+ const g = cliSimpleGit(repoDir);
34189
34725
  const headSha = await revParseSafe(g, "HEAD");
34190
34726
  if (!headSha) return [];
34191
34727
  const baseSha = await resolveBaseShaForUnpushedCommits(g);
@@ -34204,7 +34740,7 @@ async function aggregateSessionPathsWorkingTreeStatus(paths) {
34204
34740
  }
34205
34741
  async function pushAheadOfUpstreamForPaths(paths) {
34206
34742
  for (const p of paths) {
34207
- const g = simpleGit(p);
34743
+ const g = cliSimpleGit(p);
34208
34744
  const ahead = await commitsAheadOfRemoteTracking(p);
34209
34745
  if (ahead <= 0) continue;
34210
34746
  await g.push();
@@ -34269,7 +34805,7 @@ function formatRemoteDisplayLabel(remoteUrl) {
34269
34805
  }
34270
34806
 
34271
34807
  // src/git/working-directory/changes/get-working-tree-change-repo-details.ts
34272
- import * as path17 from "node:path";
34808
+ import * as path18 from "node:path";
34273
34809
 
34274
34810
  // src/git/working-directory/changes/parse-git-status.ts
34275
34811
  function parseNameStatusLines(lines) {
@@ -34346,7 +34882,7 @@ async function parentForCommitDiff(g, sha) {
34346
34882
  }
34347
34883
  }
34348
34884
  async function listChangedFilesForCommit(repoGitCwd, repoRelPath, commitSha) {
34349
- const g = simpleGit(repoGitCwd);
34885
+ const g = cliSimpleGit(repoGitCwd);
34350
34886
  const parent = await parentForCommitDiff(g, commitSha);
34351
34887
  const range = `${parent}..${commitSha}`;
34352
34888
  const [nameStatusRaw, numstatRaw] = await Promise.all([
@@ -34389,8 +34925,8 @@ async function listChangedFilesForCommit(repoGitCwd, repoRelPath, commitSha) {
34389
34925
  }
34390
34926
 
34391
34927
  // src/git/working-directory/changes/list-changed-files-for-repo.ts
34392
- import * as fs17 from "node:fs";
34393
- import * as path16 from "node:path";
34928
+ import * as fs18 from "node:fs";
34929
+ import * as path17 from "node:path";
34394
34930
 
34395
34931
  // src/git/working-directory/changes/count-lines.ts
34396
34932
  import { createReadStream } from "node:fs";
@@ -34414,14 +34950,14 @@ async function countTextFileLines(filePath) {
34414
34950
  }
34415
34951
 
34416
34952
  // src/git/working-directory/changes/hydrate-patch.ts
34417
- import * as fs16 from "node:fs";
34953
+ import * as fs17 from "node:fs";
34418
34954
  var UNIFIED_HUNK_HEADER_RE = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
34419
34955
  var MAX_HYDRATE_LINES_PER_GAP = 8e3;
34420
34956
  var MAX_HYDRATE_LINES_PER_FILE = 8e4;
34421
34957
  async function readGitBlobLines(repoCwd, pathInRepo) {
34422
34958
  try {
34423
34959
  const rel = pathInRepo.replace(/\\/g, "/");
34424
- const raw = await simpleGit(repoCwd).show([`HEAD:${rel}`]);
34960
+ const raw = await cliSimpleGit(repoCwd).show([`HEAD:${rel}`]);
34425
34961
  return String(raw).split(/\r?\n/);
34426
34962
  } catch {
34427
34963
  return null;
@@ -34429,7 +34965,7 @@ async function readGitBlobLines(repoCwd, pathInRepo) {
34429
34965
  }
34430
34966
  async function readWorktreeFileLines(filePath) {
34431
34967
  try {
34432
- const raw = await fs16.promises.readFile(filePath, "utf8");
34968
+ const raw = await fs17.promises.readFile(filePath, "utf8");
34433
34969
  return raw.split(/\r?\n/);
34434
34970
  } catch {
34435
34971
  return null;
@@ -34531,7 +35067,7 @@ async function hydrateUnifiedPatchWithFileContext(patch, filePath, repoGitCwd, p
34531
35067
 
34532
35068
  // src/git/working-directory/changes/unified-diff-for-file.ts
34533
35069
  async function unifiedDiffForFile(repoCwd, pathInRepo, change) {
34534
- const g = simpleGit(repoCwd);
35070
+ const g = cliSimpleGit(repoCwd);
34535
35071
  try {
34536
35072
  let raw;
34537
35073
  if (change === "added") {
@@ -34550,7 +35086,7 @@ async function unifiedDiffForFile(repoCwd, pathInRepo, change) {
34550
35086
 
34551
35087
  // src/git/working-directory/changes/list-changed-files-for-repo.ts
34552
35088
  async function listChangedFilesForRepo(repoGitCwd, repoRelPath) {
34553
- const g = simpleGit(repoGitCwd);
35089
+ const g = cliSimpleGit(repoGitCwd);
34554
35090
  const [nameStatusRaw, numstatRaw, untrackedRaw] = await Promise.all([
34555
35091
  g.raw(["diff", "--name-status", "HEAD"]).catch(() => ""),
34556
35092
  g.raw(["diff", "HEAD", "--numstat"]).catch(() => ""),
@@ -34564,7 +35100,7 @@ async function listChangedFilesForRepo(repoGitCwd, repoRelPath) {
34564
35100
  const rows = [];
34565
35101
  for (const pathInRepo of paths) {
34566
35102
  const relLauncher = posixJoinDirFile(repoRelPath, pathInRepo.replace(/\\/g, "/"));
34567
- const repoFilePath = path16.join(repoGitCwd, pathInRepo);
35103
+ const repoFilePath = path17.join(repoGitCwd, pathInRepo);
34568
35104
  const nums = numByPath.get(pathInRepo);
34569
35105
  let additions = nums?.additions ?? 0;
34570
35106
  let deletions = nums?.deletions ?? 0;
@@ -34577,7 +35113,7 @@ async function listChangedFilesForRepo(repoGitCwd, repoRelPath) {
34577
35113
  deletions = fromGit.deletions;
34578
35114
  } else {
34579
35115
  try {
34580
- const st = await fs17.promises.stat(repoFilePath);
35116
+ const st = await fs18.promises.stat(repoFilePath);
34581
35117
  if (st.isFile()) additions = await countTextFileLines(repoFilePath);
34582
35118
  else additions = 0;
34583
35119
  } catch {
@@ -34603,7 +35139,7 @@ async function listChangedFilesForRepo(repoGitCwd, repoRelPath) {
34603
35139
  } else {
34604
35140
  pathInRepo = row.pathRelLauncher;
34605
35141
  }
34606
- const filePath = path16.join(repoGitCwd, pathInRepo);
35142
+ const filePath = path17.join(repoGitCwd, pathInRepo);
34607
35143
  let patch = await unifiedDiffForFile(repoGitCwd, pathInRepo, row.change);
34608
35144
  if (patch) {
34609
35145
  patch = await hydrateUnifiedPatchWithFileContext(patch, filePath, repoGitCwd, pathInRepo, row.change);
@@ -34619,8 +35155,8 @@ function normRepoRel(p) {
34619
35155
  return x === "" ? "." : x;
34620
35156
  }
34621
35157
  async function getWorkingTreeChangeRepoDetails(options) {
34622
- const bridgeRoot = path17.resolve(getBridgeRoot());
34623
- const sessionWtRoot = options.sessionWorktreeRootPath ? path17.resolve(options.sessionWorktreeRootPath) : null;
35158
+ const bridgeRoot = path18.resolve(getBridgeRoot());
35159
+ const sessionWtRoot = options.sessionWorktreeRootPath ? path18.resolve(options.sessionWorktreeRootPath) : null;
34624
35160
  const legacyNested = options.legacyRepoNestedSessionLayout === true;
34625
35161
  const out = [];
34626
35162
  const filter = options.repoFilterRelPath != null ? normRepoRel(options.repoFilterRelPath) : null;
@@ -34633,9 +35169,9 @@ async function getWorkingTreeChangeRepoDetails(options) {
34633
35169
  }
34634
35170
  const basis = filter == null && basisInput.kind === "commit" ? { kind: "working" } : basisInput;
34635
35171
  for (const target of options.commitTargetPaths) {
34636
- const t = path17.resolve(target);
35172
+ const t = path18.resolve(target);
34637
35173
  if (!await isGitRepoDirectory(t)) continue;
34638
- const g = simpleGit(t);
35174
+ const g = cliSimpleGit(t);
34639
35175
  let branch = "HEAD";
34640
35176
  try {
34641
35177
  branch = (await g.raw(["rev-parse", "--abbrev-ref", "HEAD"])).trim() || "HEAD";
@@ -34646,8 +35182,8 @@ async function getWorkingTreeChangeRepoDetails(options) {
34646
35182
  const remoteDisplay = formatRemoteDisplayLabel(remoteUrl);
34647
35183
  let repoRelPath;
34648
35184
  if (sessionWtRoot) {
34649
- const anchor = legacyNested ? path17.dirname(t) : t;
34650
- const relNorm = path17.relative(sessionWtRoot, anchor);
35185
+ const anchor = legacyNested ? path18.dirname(t) : t;
35186
+ const relNorm = path18.relative(sessionWtRoot, anchor);
34651
35187
  repoRelPath = relNorm === "" ? "." : relNorm.replace(/\\/g, "/");
34652
35188
  } else {
34653
35189
  let top = t;
@@ -34656,8 +35192,8 @@ async function getWorkingTreeChangeRepoDetails(options) {
34656
35192
  } catch {
34657
35193
  top = t;
34658
35194
  }
34659
- const rel = path17.relative(bridgeRoot, path17.resolve(top)).replace(/\\/g, "/") || ".";
34660
- repoRelPath = rel.startsWith("..") ? path17.basename(path17.resolve(top)) : rel;
35195
+ const rel = path18.relative(bridgeRoot, path18.resolve(top)).replace(/\\/g, "/") || ".";
35196
+ repoRelPath = rel.startsWith("..") ? path18.basename(path18.resolve(top)) : rel;
34661
35197
  }
34662
35198
  const norm = normRepoRel(repoRelPath === "" ? "." : repoRelPath);
34663
35199
  if (filter && norm !== filter) continue;
@@ -34686,7 +35222,7 @@ async function getWorkingTreeChangeRepoDetails(options) {
34686
35222
 
34687
35223
  // src/git/commit-and-push.ts
34688
35224
  async function gitCommitAllIfDirty(repoDir, message, options) {
34689
- const g = simpleGit(repoDir);
35225
+ const g = cliSimpleGit(repoDir);
34690
35226
  const st = await g.status();
34691
35227
  if (!st.files?.length) return;
34692
35228
  const branch = options.branch.trim();
@@ -34722,11 +35258,11 @@ async function commitSessionWorktrees(options) {
34722
35258
  }
34723
35259
 
34724
35260
  // src/worktrees/discover-session-worktree-on-disk.ts
34725
- import * as fs18 from "node:fs";
34726
- import * as path18 from "node:path";
35261
+ import * as fs19 from "node:fs";
35262
+ import * as path19 from "node:path";
34727
35263
  function isGitDir(dirPath) {
34728
35264
  try {
34729
- return fs18.existsSync(path18.join(dirPath, ".git"));
35265
+ return fs19.existsSync(path19.join(dirPath, ".git"));
34730
35266
  } catch {
34731
35267
  return false;
34732
35268
  }
@@ -34735,23 +35271,23 @@ function collectGitRepoRootsUnderDirectory(rootPath) {
34735
35271
  const out = [];
34736
35272
  const walk = (dir) => {
34737
35273
  if (isGitDir(dir)) {
34738
- out.push(path18.resolve(dir));
35274
+ out.push(path19.resolve(dir));
34739
35275
  return;
34740
35276
  }
34741
35277
  let entries;
34742
35278
  try {
34743
- entries = fs18.readdirSync(dir, { withFileTypes: true });
35279
+ entries = fs19.readdirSync(dir, { withFileTypes: true });
34744
35280
  } catch {
34745
35281
  return;
34746
35282
  }
34747
35283
  for (const e of entries) {
34748
35284
  if (e.name.startsWith(".")) continue;
34749
- const full = path18.join(dir, e.name);
35285
+ const full = path19.join(dir, e.name);
34750
35286
  if (!e.isDirectory()) continue;
34751
35287
  walk(full);
34752
35288
  }
34753
35289
  };
34754
- walk(path18.resolve(rootPath));
35290
+ walk(path19.resolve(rootPath));
34755
35291
  return [...new Set(out)];
34756
35292
  }
34757
35293
  function collectWorktreeRootsNamed(root, sessionId, maxDepth) {
@@ -34760,16 +35296,16 @@ function collectWorktreeRootsNamed(root, sessionId, maxDepth) {
34760
35296
  if (depth > maxDepth) return;
34761
35297
  let entries;
34762
35298
  try {
34763
- entries = fs18.readdirSync(dir, { withFileTypes: true });
35299
+ entries = fs19.readdirSync(dir, { withFileTypes: true });
34764
35300
  } catch {
34765
35301
  return;
34766
35302
  }
34767
35303
  for (const e of entries) {
34768
35304
  if (e.name.startsWith(".")) continue;
34769
- const full = path18.join(dir, e.name);
35305
+ const full = path19.join(dir, e.name);
34770
35306
  if (!e.isDirectory()) continue;
34771
35307
  if (e.name === sessionId) {
34772
- if (isGitDir(full)) out.push(path18.resolve(full));
35308
+ if (isGitDir(full)) out.push(path19.resolve(full));
34773
35309
  } else {
34774
35310
  walk(full, depth + 1);
34775
35311
  }
@@ -34781,14 +35317,14 @@ function collectWorktreeRootsNamed(root, sessionId, maxDepth) {
34781
35317
  function tryBindingFromSessionDirectory(sessionDir) {
34782
35318
  let st;
34783
35319
  try {
34784
- st = fs18.statSync(sessionDir);
35320
+ st = fs19.statSync(sessionDir);
34785
35321
  } catch {
34786
35322
  return null;
34787
35323
  }
34788
35324
  if (!st.isDirectory()) return null;
34789
35325
  const worktreePaths = collectGitRepoRootsUnderDirectory(sessionDir);
34790
35326
  if (worktreePaths.length === 0) return null;
34791
- const abs = path18.resolve(sessionDir);
35327
+ const abs = path19.resolve(sessionDir);
34792
35328
  return {
34793
35329
  sessionParentPath: abs,
34794
35330
  workingTreeRelRoot: abs,
@@ -34798,20 +35334,20 @@ function tryBindingFromSessionDirectory(sessionDir) {
34798
35334
  function discoverLegacyBindingAscendingFromCheckout(sessionId, checkoutPath) {
34799
35335
  const sid = sessionId.trim();
34800
35336
  if (!sid) return null;
34801
- const hintR = path18.resolve(checkoutPath);
35337
+ const hintR = path19.resolve(checkoutPath);
34802
35338
  let best = null;
34803
- let cur = path18.dirname(hintR);
35339
+ let cur = path19.dirname(hintR);
34804
35340
  for (let i = 0; i < 40; i++) {
34805
35341
  const paths = collectWorktreeRootsNamed(cur, sid, 24);
34806
- if (paths.some((p) => path18.resolve(p) === hintR)) {
34807
- const isolated = resolveIsolatedSessionParentPathFromCheckouts(paths) ?? path18.resolve(paths[0]);
35342
+ if (paths.some((p) => path19.resolve(p) === hintR)) {
35343
+ const isolated = resolveIsolatedSessionParentPathFromCheckouts(paths) ?? path19.resolve(paths[0]);
34808
35344
  best = {
34809
- sessionParentPath: path18.resolve(isolated),
34810
- workingTreeRelRoot: path18.resolve(cur),
34811
- repoCheckoutPaths: paths.map((p) => path18.resolve(p))
35345
+ sessionParentPath: path19.resolve(isolated),
35346
+ workingTreeRelRoot: path19.resolve(cur),
35347
+ repoCheckoutPaths: paths.map((p) => path19.resolve(p))
34812
35348
  };
34813
35349
  }
34814
- const next = path18.dirname(cur);
35350
+ const next = path19.dirname(cur);
34815
35351
  if (next === cur) break;
34816
35352
  cur = next;
34817
35353
  }
@@ -34819,33 +35355,33 @@ function discoverLegacyBindingAscendingFromCheckout(sessionId, checkoutPath) {
34819
35355
  }
34820
35356
  function discoverSessionWorktreeOnDisk(options) {
34821
35357
  const { sessionId, worktreesRootPath, layout, bridgeRoot } = options;
34822
- if (!sessionId.trim() || !fs18.existsSync(worktreesRootPath)) return null;
35358
+ if (!sessionId.trim() || !fs19.existsSync(worktreesRootPath)) return null;
34823
35359
  const preferredKey = getLauncherDirNameIfPresent(layout, bridgeRoot);
34824
35360
  const keys = [];
34825
35361
  if (preferredKey) keys.push(preferredKey);
34826
35362
  try {
34827
- for (const name of fs18.readdirSync(worktreesRootPath)) {
35363
+ for (const name of fs19.readdirSync(worktreesRootPath)) {
34828
35364
  if (name.startsWith(".")) continue;
34829
- const p = path18.join(worktreesRootPath, name);
34830
- if (!fs18.statSync(p).isDirectory()) continue;
35365
+ const p = path19.join(worktreesRootPath, name);
35366
+ if (!fs19.statSync(p).isDirectory()) continue;
34831
35367
  if (name !== preferredKey) keys.push(name);
34832
35368
  }
34833
35369
  } catch {
34834
35370
  return null;
34835
35371
  }
34836
35372
  for (const key of keys) {
34837
- const layoutRoot = path18.join(worktreesRootPath, key);
34838
- if (!fs18.existsSync(layoutRoot) || !fs18.statSync(layoutRoot).isDirectory()) continue;
34839
- const sessionDir = path18.join(layoutRoot, sessionId);
35373
+ const layoutRoot = path19.join(worktreesRootPath, key);
35374
+ if (!fs19.existsSync(layoutRoot) || !fs19.statSync(layoutRoot).isDirectory()) continue;
35375
+ const sessionDir = path19.join(layoutRoot, sessionId);
34840
35376
  const nested = tryBindingFromSessionDirectory(sessionDir);
34841
35377
  if (nested) return nested;
34842
35378
  const legacyPaths = collectWorktreeRootsNamed(layoutRoot, sessionId, 24);
34843
35379
  if (legacyPaths.length > 0) {
34844
- const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths) ?? path18.resolve(legacyPaths[0]);
35380
+ const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths) ?? path19.resolve(legacyPaths[0]);
34845
35381
  return {
34846
- sessionParentPath: path18.resolve(isolated),
34847
- workingTreeRelRoot: path18.resolve(layoutRoot),
34848
- repoCheckoutPaths: legacyPaths.map((p) => path18.resolve(p))
35382
+ sessionParentPath: path19.resolve(isolated),
35383
+ workingTreeRelRoot: path19.resolve(layoutRoot),
35384
+ repoCheckoutPaths: legacyPaths.map((p) => path19.resolve(p))
34849
35385
  };
34850
35386
  }
34851
35387
  }
@@ -34854,12 +35390,12 @@ function discoverSessionWorktreeOnDisk(options) {
34854
35390
  function discoverSessionWorktreesUnderSessionWorktreeRoot(sessionWorktreeRootPathOrHint, sessionId) {
34855
35391
  const sid = sessionId.trim();
34856
35392
  if (!sid) return null;
34857
- const hint = path18.resolve(sessionWorktreeRootPathOrHint);
34858
- const underHint = tryBindingFromSessionDirectory(path18.join(hint, sid));
35393
+ const hint = path19.resolve(sessionWorktreeRootPathOrHint);
35394
+ const underHint = tryBindingFromSessionDirectory(path19.join(hint, sid));
34859
35395
  if (underHint) return underHint;
34860
35396
  const direct = tryBindingFromSessionDirectory(hint);
34861
35397
  if (direct) {
34862
- if (path18.basename(hint) === sid && isGitDir(hint)) {
35398
+ if (path19.basename(hint) === sid && isGitDir(hint)) {
34863
35399
  const legacyFromCheckout = discoverLegacyBindingAscendingFromCheckout(sid, hint);
34864
35400
  if (legacyFromCheckout && legacyFromCheckout.repoCheckoutPaths.length > direct.repoCheckoutPaths.length) {
34865
35401
  return legacyFromCheckout;
@@ -34867,24 +35403,24 @@ function discoverSessionWorktreesUnderSessionWorktreeRoot(sessionWorktreeRootPat
34867
35403
  }
34868
35404
  return direct;
34869
35405
  }
34870
- if (path18.basename(hint) === sid && isGitDir(hint)) {
35406
+ if (path19.basename(hint) === sid && isGitDir(hint)) {
34871
35407
  const legacyFromCheckout = discoverLegacyBindingAscendingFromCheckout(sid, hint);
34872
35408
  if (legacyFromCheckout) return legacyFromCheckout;
34873
35409
  }
34874
35410
  let st;
34875
35411
  try {
34876
- st = fs18.statSync(hint);
35412
+ st = fs19.statSync(hint);
34877
35413
  } catch {
34878
35414
  return null;
34879
35415
  }
34880
35416
  if (!st.isDirectory()) return null;
34881
35417
  const legacyPaths = collectWorktreeRootsNamed(hint, sid, 24);
34882
35418
  if (legacyPaths.length === 0) return null;
34883
- const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths) ?? path18.resolve(legacyPaths[0]);
35419
+ const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths) ?? path19.resolve(legacyPaths[0]);
34884
35420
  return {
34885
- sessionParentPath: path18.resolve(isolated),
35421
+ sessionParentPath: path19.resolve(isolated),
34886
35422
  workingTreeRelRoot: hint,
34887
- repoCheckoutPaths: legacyPaths.map((p) => path18.resolve(p))
35423
+ repoCheckoutPaths: legacyPaths.map((p) => path19.resolve(p))
34888
35424
  };
34889
35425
  }
34890
35426
 
@@ -34907,10 +35443,10 @@ var SessionWorktreeManager = class {
34907
35443
  this.layout = loadWorktreeLayout();
34908
35444
  }
34909
35445
  rememberSessionWorktrees(sessionId, binding) {
34910
- const paths = binding.repoCheckoutPaths.map((p) => path19.resolve(p));
35446
+ const paths = binding.repoCheckoutPaths.map((p) => path20.resolve(p));
34911
35447
  this.sessionRepoCheckoutPaths.set(sessionId, paths);
34912
- this.sessionParentPathBySession.set(sessionId, path19.resolve(binding.sessionParentPath));
34913
- this.sessionWorkingTreeRelRootBySession.set(sessionId, path19.resolve(binding.workingTreeRelRoot));
35448
+ this.sessionParentPathBySession.set(sessionId, path20.resolve(binding.sessionParentPath));
35449
+ this.sessionWorkingTreeRelRootBySession.set(sessionId, path20.resolve(binding.workingTreeRelRoot));
34914
35450
  }
34915
35451
  sessionParentPathAfterRemember(sessionId) {
34916
35452
  return this.sessionParentPathBySession.get(sessionId);
@@ -34927,7 +35463,7 @@ var SessionWorktreeManager = class {
34927
35463
  const parent = this.sessionParentPathBySession.get(sessionId);
34928
35464
  const relRoot = this.sessionWorkingTreeRelRootBySession.get(sessionId);
34929
35465
  if (!parent || !relRoot) return false;
34930
- return path19.resolve(parent) !== path19.resolve(relRoot);
35466
+ return path20.resolve(parent) !== path20.resolve(relRoot);
34931
35467
  }
34932
35468
  /**
34933
35469
  * Session parent path for `worktrees_root`: the per-session directory (new layout) or primary checkout (legacy).
@@ -34936,7 +35472,7 @@ var SessionWorktreeManager = class {
34936
35472
  if (!sessionId) return null;
34937
35473
  const sid = sessionId.trim();
34938
35474
  const cached2 = this.sessionParentPathBySession.get(sid);
34939
- if (cached2) return path19.resolve(cached2);
35475
+ if (cached2) return path20.resolve(cached2);
34940
35476
  const paths = this.ensureRepoCheckoutPathsForSession(sid) ?? this.getRepoCheckoutPathsForSession(sid);
34941
35477
  if (!paths?.length) return null;
34942
35478
  return resolveIsolatedSessionParentPathFromCheckouts(paths);
@@ -34950,7 +35486,7 @@ var SessionWorktreeManager = class {
34950
35486
  const sid = sessionId.trim();
34951
35487
  const parentPathRaw = opts.sessionParentPath?.trim();
34952
35488
  if (parentPathRaw) {
34953
- const resolved = path19.resolve(parentPathRaw);
35489
+ const resolved = path20.resolve(parentPathRaw);
34954
35490
  if (sid && parseSessionParent(opts.sessionParent) === "worktrees_root") {
34955
35491
  const diskFirst = this.tryDiscoverFromDisk(sid);
34956
35492
  if (diskFirst) {
@@ -34969,7 +35505,7 @@ var SessionWorktreeManager = class {
34969
35505
  this.rememberSessionWorktrees(sid, tryRoot);
34970
35506
  return this.sessionParentPathAfterRemember(sid);
34971
35507
  }
34972
- const next = path19.dirname(cur);
35508
+ const next = path20.dirname(cur);
34973
35509
  if (next === cur) break;
34974
35510
  cur = next;
34975
35511
  }
@@ -35122,15 +35658,15 @@ var SessionWorktreeManager = class {
35122
35658
  }
35123
35659
  };
35124
35660
  function defaultWorktreesRootPath() {
35125
- return path19.join(os4.homedir(), ".buildautomaton", "worktrees");
35661
+ return path20.join(os5.homedir(), ".buildautomaton", "worktrees");
35126
35662
  }
35127
35663
 
35128
35664
  // src/files/watch-file-index.ts
35129
35665
  import { watch } from "node:fs";
35130
- import path26 from "node:path";
35666
+ import path27 from "node:path";
35131
35667
 
35132
35668
  // src/files/index/build-file-index.ts
35133
- import path23 from "node:path";
35669
+ import path24 from "node:path";
35134
35670
 
35135
35671
  // src/runtime/yield-to-event-loop.ts
35136
35672
  function yieldToEventLoop() {
@@ -35138,14 +35674,14 @@ function yieldToEventLoop() {
35138
35674
  }
35139
35675
 
35140
35676
  // src/files/index/walk-workspace-tree.ts
35141
- import fs19 from "node:fs";
35142
- import path21 from "node:path";
35677
+ import fs20 from "node:fs";
35678
+ import path22 from "node:path";
35143
35679
 
35144
35680
  // src/files/index/constants.ts
35145
- import path20 from "node:path";
35146
- import os5 from "node:os";
35681
+ import path21 from "node:path";
35682
+ import os6 from "node:os";
35147
35683
  var INDEX_WORK_YIELD_EVERY = 256;
35148
- var INDEX_DIR = path20.join(os5.homedir(), ".buildautomaton");
35684
+ var INDEX_DIR = path21.join(os6.homedir(), ".buildautomaton");
35149
35685
  var INDEX_HASH_LEN = 16;
35150
35686
  var INDEX_VERSION = 2;
35151
35687
  var INDEX_LOG_PREFIX = "[file-index]";
@@ -35154,20 +35690,20 @@ var INDEX_LOG_PREFIX = "[file-index]";
35154
35690
  function walkWorkspaceTreeSync(dir, baseDir, out) {
35155
35691
  let names;
35156
35692
  try {
35157
- names = fs19.readdirSync(dir);
35693
+ names = fs20.readdirSync(dir);
35158
35694
  } catch {
35159
35695
  return;
35160
35696
  }
35161
35697
  for (const name of names) {
35162
35698
  if (name.startsWith(".")) continue;
35163
- const full = path21.join(dir, name);
35699
+ const full = path22.join(dir, name);
35164
35700
  let stat3;
35165
35701
  try {
35166
- stat3 = fs19.statSync(full);
35702
+ stat3 = fs20.statSync(full);
35167
35703
  } catch {
35168
35704
  continue;
35169
35705
  }
35170
- const relative5 = path21.relative(baseDir, full).replace(/\\/g, "/");
35706
+ const relative5 = path22.relative(baseDir, full).replace(/\\/g, "/");
35171
35707
  if (stat3.isDirectory()) {
35172
35708
  walkWorkspaceTreeSync(full, baseDir, out);
35173
35709
  } else if (stat3.isFile()) {
@@ -35178,7 +35714,7 @@ function walkWorkspaceTreeSync(dir, baseDir, out) {
35178
35714
  async function walkWorkspaceTreeAsync(dir, baseDir, out, state) {
35179
35715
  let names;
35180
35716
  try {
35181
- names = await fs19.promises.readdir(dir);
35717
+ names = await fs20.promises.readdir(dir);
35182
35718
  } catch {
35183
35719
  return;
35184
35720
  }
@@ -35188,14 +35724,14 @@ async function walkWorkspaceTreeAsync(dir, baseDir, out, state) {
35188
35724
  await yieldToEventLoop();
35189
35725
  }
35190
35726
  state.n++;
35191
- const full = path21.join(dir, name);
35727
+ const full = path22.join(dir, name);
35192
35728
  let stat3;
35193
35729
  try {
35194
- stat3 = await fs19.promises.stat(full);
35730
+ stat3 = await fs20.promises.stat(full);
35195
35731
  } catch {
35196
35732
  continue;
35197
35733
  }
35198
- const relative5 = path21.relative(baseDir, full).replace(/\\/g, "/");
35734
+ const relative5 = path22.relative(baseDir, full).replace(/\\/g, "/");
35199
35735
  if (stat3.isDirectory()) {
35200
35736
  await walkWorkspaceTreeAsync(full, baseDir, out, state);
35201
35737
  } else if (stat3.isFile()) {
@@ -35276,22 +35812,22 @@ async function buildTrigramMapForPathsAsync(paths) {
35276
35812
  }
35277
35813
 
35278
35814
  // src/files/index/write-index-file.ts
35279
- import fs20 from "node:fs";
35815
+ import fs21 from "node:fs";
35280
35816
 
35281
35817
  // src/files/index/paths.ts
35282
- import path22 from "node:path";
35818
+ import path23 from "node:path";
35283
35819
  import crypto2 from "node:crypto";
35284
35820
  function getIndexPathForCwd(resolvedCwd) {
35285
35821
  const hash = crypto2.createHash("sha256").update(resolvedCwd).digest("hex").slice(0, INDEX_HASH_LEN);
35286
- return path22.join(INDEX_DIR, `.file-index-${hash}.json`);
35822
+ return path23.join(INDEX_DIR, `.file-index-${hash}.json`);
35287
35823
  }
35288
35824
 
35289
35825
  // src/files/index/write-index-file.ts
35290
35826
  function writeIndexFileSync(resolvedCwd, data) {
35291
35827
  const indexPath = getIndexPathForCwd(resolvedCwd);
35292
35828
  try {
35293
- if (!fs20.existsSync(INDEX_DIR)) fs20.mkdirSync(INDEX_DIR, { recursive: true });
35294
- fs20.writeFileSync(indexPath, JSON.stringify(data), "utf8");
35829
+ if (!fs21.existsSync(INDEX_DIR)) fs21.mkdirSync(INDEX_DIR, { recursive: true });
35830
+ fs21.writeFileSync(indexPath, JSON.stringify(data), "utf8");
35295
35831
  } catch (e) {
35296
35832
  console.error(`${INDEX_LOG_PREFIX} Failed to write index:`, e);
35297
35833
  }
@@ -35299,8 +35835,8 @@ function writeIndexFileSync(resolvedCwd, data) {
35299
35835
  async function writeIndexFileAsync(resolvedCwd, data) {
35300
35836
  const indexPath = getIndexPathForCwd(resolvedCwd);
35301
35837
  try {
35302
- await fs20.promises.mkdir(INDEX_DIR, { recursive: true });
35303
- await fs20.promises.writeFile(indexPath, JSON.stringify(data), "utf8");
35838
+ await fs21.promises.mkdir(INDEX_DIR, { recursive: true });
35839
+ await fs21.promises.writeFile(indexPath, JSON.stringify(data), "utf8");
35304
35840
  } catch (e) {
35305
35841
  console.error(`${INDEX_LOG_PREFIX} Failed to write index:`, e);
35306
35842
  }
@@ -35314,7 +35850,7 @@ function sortPaths(paths) {
35314
35850
  paths.sort((a, b) => a.localeCompare(b, void 0, { sensitivity: "base" }));
35315
35851
  }
35316
35852
  function buildFileIndex(cwd) {
35317
- const resolved = path23.resolve(cwd);
35853
+ const resolved = path24.resolve(cwd);
35318
35854
  const paths = [];
35319
35855
  walkWorkspaceTreeSync(resolved, resolved, paths);
35320
35856
  sortPaths(paths);
@@ -35324,7 +35860,7 @@ function buildFileIndex(cwd) {
35324
35860
  return data;
35325
35861
  }
35326
35862
  async function buildFileIndexAsync(cwd) {
35327
- const resolved = path23.resolve(cwd);
35863
+ const resolved = path24.resolve(cwd);
35328
35864
  const paths = [];
35329
35865
  await walkWorkspaceTreeAsync(resolved, resolved, paths, createWalkYieldState());
35330
35866
  await yieldToEventLoop();
@@ -35336,13 +35872,13 @@ async function buildFileIndexAsync(cwd) {
35336
35872
  }
35337
35873
 
35338
35874
  // src/files/index/load-file-index.ts
35339
- import fs21 from "node:fs";
35340
- import path24 from "node:path";
35875
+ import fs22 from "node:fs";
35876
+ import path25 from "node:path";
35341
35877
  function loadFileIndex(cwd) {
35342
- const resolved = path24.resolve(cwd);
35878
+ const resolved = path25.resolve(cwd);
35343
35879
  const indexPath = getIndexPathForCwd(resolved);
35344
35880
  try {
35345
- const raw = fs21.readFileSync(indexPath, "utf8");
35881
+ const raw = fs22.readFileSync(indexPath, "utf8");
35346
35882
  const parsed = JSON.parse(raw);
35347
35883
  if (parsed !== null && typeof parsed === "object" && Array.isArray(parsed.paths)) {
35348
35884
  const obj = parsed;
@@ -35361,9 +35897,9 @@ function loadFileIndex(cwd) {
35361
35897
  }
35362
35898
 
35363
35899
  // src/files/index/ensure-file-index.ts
35364
- import path25 from "node:path";
35900
+ import path26 from "node:path";
35365
35901
  async function ensureFileIndexAsync(cwd) {
35366
- const resolved = path25.resolve(cwd);
35902
+ const resolved = path26.resolve(cwd);
35367
35903
  const cached2 = loadFileIndex(resolved);
35368
35904
  if (cached2 !== null) return { data: cached2, fromCache: true };
35369
35905
  const data = await buildFileIndexAsync(resolved);
@@ -35446,7 +35982,7 @@ function createFsWatcher(resolved, schedule) {
35446
35982
  }
35447
35983
  }
35448
35984
  function startFileIndexWatcher(cwd = getBridgeRoot()) {
35449
- const resolved = path26.resolve(cwd);
35985
+ const resolved = path27.resolve(cwd);
35450
35986
  void buildFileIndexAsync(resolved).catch((e) => {
35451
35987
  console.error("[file-index] Initial index build failed:", e);
35452
35988
  });
@@ -35473,8 +36009,8 @@ function startFileIndexWatcher(cwd = getBridgeRoot()) {
35473
36009
  };
35474
36010
  }
35475
36011
 
35476
- // src/bridge/connection/create-bridge-connection.ts
35477
- import * as path34 from "node:path";
36012
+ // src/connection/create-bridge-connection.ts
36013
+ import * as path35 from "node:path";
35478
36014
 
35479
36015
  // src/dev-servers/manager/dev-server-manager.ts
35480
36016
  import { rm as rm2 } from "node:fs/promises";
@@ -35518,7 +36054,7 @@ function forceKillChild(proc, log2, shortId, graceMs) {
35518
36054
  }
35519
36055
 
35520
36056
  // src/dev-servers/process/wire-dev-server-child-process.ts
35521
- import fs22 from "node:fs";
36057
+ import fs23 from "node:fs";
35522
36058
 
35523
36059
  // src/dev-servers/manager/forward-pipe.ts
35524
36060
  function forwardChildPipe(childReadable, terminal, onData) {
@@ -35554,7 +36090,7 @@ function wireDevServerChildProcess(d) {
35554
36090
  d.setPollInterval(void 0);
35555
36091
  return;
35556
36092
  }
35557
- fs22.readFile(d.mergedLogPath, (err, buf) => {
36093
+ fs23.readFile(d.mergedLogPath, (err, buf) => {
35558
36094
  if (err || (d.getSpawnGeneration() ?? 0) !== d.scheduledGen) return;
35559
36095
  if (buf.length <= d.mergedReadPos.value) return;
35560
36096
  const chunk = Buffer.from(buf.subarray(d.mergedReadPos.value));
@@ -35592,7 +36128,7 @@ ${errTail}` : ""}`);
35592
36128
  d.sendStatus(code === 0 || code == null ? "stopped" : "error", detail, tails);
35593
36129
  };
35594
36130
  if (mergedPath) {
35595
- fs22.readFile(mergedPath, (err, buf) => {
36131
+ fs23.readFile(mergedPath, (err, buf) => {
35596
36132
  if (!err && buf.length > d.mergedReadPos.value) {
35597
36133
  const chunk = Buffer.from(buf.subarray(d.mergedReadPos.value));
35598
36134
  if (chunk.length > 0) {
@@ -35694,13 +36230,13 @@ function parseDevServerDefs(servers) {
35694
36230
  }
35695
36231
 
35696
36232
  // src/dev-servers/manager/shell-spawn/utils.ts
35697
- import fs23 from "node:fs";
36233
+ import fs24 from "node:fs";
35698
36234
  function isSpawnEbadf(e) {
35699
36235
  return typeof e === "object" && e !== null && "code" in e && e.code === "EBADF";
35700
36236
  }
35701
36237
  function rmDirQuiet(dir) {
35702
36238
  try {
35703
- fs23.rmSync(dir, { recursive: true, force: true });
36239
+ fs24.rmSync(dir, { recursive: true, force: true });
35704
36240
  } catch {
35705
36241
  }
35706
36242
  }
@@ -35708,7 +36244,7 @@ var cachedDevNullReadFd;
35708
36244
  function devNullReadFd() {
35709
36245
  if (cachedDevNullReadFd === void 0) {
35710
36246
  const devPath = process.platform === "win32" ? "nul" : "/dev/null";
35711
- cachedDevNullReadFd = fs23.openSync(devPath, "r");
36247
+ cachedDevNullReadFd = fs24.openSync(devPath, "r");
35712
36248
  }
35713
36249
  return cachedDevNullReadFd;
35714
36250
  }
@@ -35782,15 +36318,15 @@ function trySpawnShellTruePiped(command, env, cwd, devNullFd, signal) {
35782
36318
 
35783
36319
  // src/dev-servers/manager/shell-spawn/try-spawn-merged-log-file.ts
35784
36320
  import { spawn as spawn6 } from "node:child_process";
35785
- import fs24 from "node:fs";
36321
+ import fs25 from "node:fs";
35786
36322
  import { tmpdir } from "node:os";
35787
- import path27 from "node:path";
36323
+ import path28 from "node:path";
35788
36324
  function trySpawnMergedLogFile(command, env, cwd, signal) {
35789
- const tmpRoot = fs24.mkdtempSync(path27.join(tmpdir(), "ba-devsrv-log-"));
35790
- const logPath = path27.join(tmpRoot, "combined.log");
36325
+ const tmpRoot = fs25.mkdtempSync(path28.join(tmpdir(), "ba-devsrv-log-"));
36326
+ const logPath = path28.join(tmpRoot, "combined.log");
35791
36327
  let logFd;
35792
36328
  try {
35793
- logFd = fs24.openSync(logPath, "a");
36329
+ logFd = fs25.openSync(logPath, "a");
35794
36330
  } catch {
35795
36331
  rmDirQuiet(tmpRoot);
35796
36332
  return null;
@@ -35809,7 +36345,7 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
35809
36345
  } else {
35810
36346
  proc = spawn6("/bin/sh", ["-c", command], { env, cwd, stdio, ...signal ? { signal } : {} });
35811
36347
  }
35812
- fs24.closeSync(logFd);
36348
+ fs25.closeSync(logFd);
35813
36349
  return {
35814
36350
  proc,
35815
36351
  pipedStdoutStderr: true,
@@ -35818,7 +36354,7 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
35818
36354
  };
35819
36355
  } catch (e) {
35820
36356
  try {
35821
- fs24.closeSync(logFd);
36357
+ fs25.closeSync(logFd);
35822
36358
  } catch {
35823
36359
  }
35824
36360
  rmDirQuiet(tmpRoot);
@@ -35829,22 +36365,22 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
35829
36365
 
35830
36366
  // src/dev-servers/manager/shell-spawn/try-spawn-shell-script-log-redirect.ts
35831
36367
  import { spawn as spawn7 } from "node:child_process";
35832
- import fs25 from "node:fs";
36368
+ import fs26 from "node:fs";
35833
36369
  import { tmpdir as tmpdir2 } from "node:os";
35834
- import path28 from "node:path";
36370
+ import path29 from "node:path";
35835
36371
  function shSingleQuote(s) {
35836
36372
  return `'${s.replace(/'/g, `'\\''`)}'`;
35837
36373
  }
35838
36374
  function trySpawnShellScriptLogRedirectUnix(command, env, cwd, signal) {
35839
- const tmpRoot = fs25.mkdtempSync(path28.join(tmpdir2(), "ba-devsrv-sh-"));
35840
- const logPath = path28.join(tmpRoot, "combined.log");
35841
- const innerPath = path28.join(tmpRoot, "_cmd.sh");
35842
- const runnerPath = path28.join(tmpRoot, "_run.sh");
36375
+ const tmpRoot = fs26.mkdtempSync(path29.join(tmpdir2(), "ba-devsrv-sh-"));
36376
+ const logPath = path29.join(tmpRoot, "combined.log");
36377
+ const innerPath = path29.join(tmpRoot, "_cmd.sh");
36378
+ const runnerPath = path29.join(tmpRoot, "_run.sh");
35843
36379
  try {
35844
- fs25.writeFileSync(innerPath, `#!/bin/sh
36380
+ fs26.writeFileSync(innerPath, `#!/bin/sh
35845
36381
  ${command}
35846
36382
  `);
35847
- fs25.writeFileSync(
36383
+ fs26.writeFileSync(
35848
36384
  runnerPath,
35849
36385
  `#!/bin/sh
35850
36386
  cd ${shSingleQuote(cwd)}
@@ -35870,13 +36406,13 @@ cd ${shSingleQuote(cwd)}
35870
36406
  }
35871
36407
  }
35872
36408
  function trySpawnShellScriptLogRedirectWin(command, env, cwd, signal) {
35873
- const tmpRoot = fs25.mkdtempSync(path28.join(tmpdir2(), "ba-devsrv-sh-"));
35874
- const logPath = path28.join(tmpRoot, "combined.log");
35875
- const runnerPath = path28.join(tmpRoot, "_run.bat");
36409
+ const tmpRoot = fs26.mkdtempSync(path29.join(tmpdir2(), "ba-devsrv-sh-"));
36410
+ const logPath = path29.join(tmpRoot, "combined.log");
36411
+ const runnerPath = path29.join(tmpRoot, "_run.bat");
35876
36412
  const q = (p) => `"${p.replace(/"/g, '""')}"`;
35877
36413
  const com = process.env.ComSpec || "cmd.exe";
35878
36414
  try {
35879
- fs25.writeFileSync(
36415
+ fs26.writeFileSync(
35880
36416
  runnerPath,
35881
36417
  `@ECHO OFF\r
35882
36418
  CD /D ${q(cwd)}\r
@@ -36609,7 +37145,17 @@ function tryConsumeBinaryProxyBody(raw, deps) {
36609
37145
 
36610
37146
  // src/firehose/connect-firehose.ts
36611
37147
  function connectFirehose(options) {
36612
- const { firehoseServerUrl, workspaceId, bridgeName, proxyPorts, log: log2, devServerManager, onOpen, onClose } = options;
37148
+ const {
37149
+ firehoseServerUrl,
37150
+ workspaceId,
37151
+ bridgeName,
37152
+ proxyPorts,
37153
+ log: log2,
37154
+ devServerManager,
37155
+ onOpen,
37156
+ onClose,
37157
+ suppressWebSocketErrors
37158
+ } = options;
36613
37159
  const wsUrl = buildFirehoseCliWsUrl(firehoseServerUrl);
36614
37160
  applyCliOutboundNetworkPreferences();
36615
37161
  const ws = new wrapper_default(wsUrl, buildCliWebSocketClientOptions(wsUrl));
@@ -36654,7 +37200,9 @@ function connectFirehose(options) {
36654
37200
  });
36655
37201
  ws.on("error", (err) => {
36656
37202
  disposeClientPing();
36657
- logCliWebSocketError(log2, "[Proxy and log service]", err);
37203
+ if (!suppressWebSocketErrors?.()) {
37204
+ logCliWebSocketError(log2, "[Proxy and log service]", err);
37205
+ }
36658
37206
  if (ws.readyState === wrapper_default.CONNECTING || ws.readyState === wrapper_default.OPEN) {
36659
37207
  safeCloseWebSocket(ws);
36660
37208
  }
@@ -36669,7 +37217,7 @@ function connectFirehose(options) {
36669
37217
  };
36670
37218
  }
36671
37219
 
36672
- // src/bridge/connection/attach-firehose-after-identified.ts
37220
+ // src/connection/attach-firehose-after-identified.ts
36673
37221
  function attachFirehoseAfterIdentified(ctx, params) {
36674
37222
  const { state, devServerManager, logFn } = ctx;
36675
37223
  function clearFirehoseReconnectTimer() {
@@ -36686,10 +37234,44 @@ function attachFirehoseAfterIdentified(ctx, params) {
36686
37234
  firehoseQuiet: state.firehoseQuiet
36687
37235
  };
36688
37236
  }
37237
+ function scheduleFirehoseRetryAfterDrop(closeMeta) {
37238
+ if (state.closedByUser) return;
37239
+ const meta = closeMeta ?? state.lastFirehoseReconnectCloseMeta ?? void 0;
37240
+ const delay2 = applyTieredReconnectPlanAndLog(
37241
+ state.firehoseOutage,
37242
+ logFn,
37243
+ PREVIEW_TUNNEL_SERVICE_LABEL,
37244
+ PREVIEW_TUNNEL_DISCONNECT_LEAD_IN,
37245
+ meta?.code,
37246
+ meta?.reason
37247
+ );
37248
+ armReconnectDelayTimer({
37249
+ delayMs: delay2,
37250
+ bumpAttempt: () => {
37251
+ state.firehoseReconnectAttempt += 1;
37252
+ },
37253
+ clearTimer: clearFirehoseReconnectTimer,
37254
+ setTimer: (id) => {
37255
+ state.firehoseReconnectTimeout = id;
37256
+ },
37257
+ shouldAbortBeforeRun: () => state.closedByUser,
37258
+ run: () => {
37259
+ const p = state.lastFirehoseParams;
37260
+ if (!p) return;
37261
+ attachFirehoseAfterIdentified(ctx, p);
37262
+ },
37263
+ reschedule: () => {
37264
+ scheduleFirehoseRetryAfterDrop();
37265
+ }
37266
+ });
37267
+ }
36689
37268
  state.lastFirehoseParams = params;
36690
37269
  clearFirehoseReconnectTimer();
36691
37270
  if (state.firehoseReconnectAttempt === 0) {
36692
- logFn("Connecting to preview tunnel (local HTTP proxy and dev logs)\u2026");
37271
+ try {
37272
+ logFn("Connecting to preview tunnel (local HTTP proxy and dev logs)\u2026");
37273
+ } catch {
37274
+ }
36693
37275
  }
36694
37276
  state.firehoseGeneration += 1;
36695
37277
  const myGen = state.firehoseGeneration;
@@ -36704,47 +37286,47 @@ function attachFirehoseAfterIdentified(ctx, params) {
36704
37286
  proxyPorts: params.proxyPorts,
36705
37287
  log: logFn,
36706
37288
  devServerManager,
37289
+ suppressWebSocketErrors: () => !state.closedByUser && state.firehoseOutage.startedAt != null,
36707
37290
  onOpen: () => {
36708
37291
  if (myGen !== state.firehoseGeneration) return;
36709
- clearFirehoseReconnectQuietOnOpen({ firehoseQuiet: state.firehoseQuiet }, logFn);
37292
+ try {
37293
+ clearFirehoseReconnectQuietOnOpen(
37294
+ {
37295
+ firehoseQuiet: state.firehoseQuiet,
37296
+ firehoseOutage: state.firehoseOutage,
37297
+ lastFirehoseReconnectCloseMeta: state.lastFirehoseReconnectCloseMeta
37298
+ },
37299
+ logFn
37300
+ );
37301
+ } catch {
37302
+ }
36710
37303
  const logOpenAsFirehoseReconnect = state.firehoseReconnectAttempt > 0;
36711
37304
  state.firehoseReconnectAttempt = 0;
36712
37305
  if (!logOpenAsFirehoseReconnect) {
36713
- logFn("Connected to preview tunnel (local HTTP proxy and dev logs).");
37306
+ try {
37307
+ logFn("Connected to preview tunnel (local HTTP proxy and dev logs).");
37308
+ } catch {
37309
+ }
36714
37310
  }
36715
37311
  },
36716
37312
  onClose: (code, reason) => {
36717
37313
  if (myGen !== state.firehoseGeneration) return;
36718
37314
  state.firehoseHandle = null;
36719
37315
  if (state.closedByUser) return;
36720
- beginFirehoseDeferredDisconnect(firehoseCtx(), code, reason, logFn);
36721
- clearFirehoseReconnectTimer();
36722
- const delay2 = reconnectDelayMs(state.firehoseReconnectAttempt);
36723
- state.firehoseReconnectAttempt += 1;
36724
- logNextReconnectAttempt(
36725
- logFn,
36726
- PROXY_AND_LOG_SERVICE_LABEL,
36727
- state.firehoseQuiet,
36728
- delay2,
36729
- state.firehoseReconnectAttempt
36730
- );
36731
- state.firehoseReconnectTimeout = setTimeout(() => {
36732
- state.firehoseReconnectTimeout = null;
36733
- if (state.closedByUser) return;
36734
- const p = state.lastFirehoseParams;
36735
- if (!p) {
36736
- if (state.firehoseQuiet.verboseLogs) {
36737
- logFn(`${PROXY_AND_LOG_SERVICE_LABEL} Reconnect skipped: no stored connection parameters.`);
36738
- }
36739
- return;
36740
- }
36741
- attachFirehoseAfterIdentified(ctx, p);
36742
- }, delay2);
37316
+ state.lastFirehoseReconnectCloseMeta = { code, reason };
37317
+ try {
37318
+ beginFirehoseDeferredDisconnect(firehoseCtx(), code, reason, logFn);
37319
+ } catch {
37320
+ }
37321
+ try {
37322
+ scheduleFirehoseRetryAfterDrop({ code, reason });
37323
+ } catch {
37324
+ }
36743
37325
  }
36744
37326
  });
36745
37327
  }
36746
37328
 
36747
- // src/bridge/connection/create-bridge-identified-handler.ts
37329
+ // src/connection/create-bridge-identified-handler.ts
36748
37330
  function createOnBridgeIdentified(opts) {
36749
37331
  const { devServerManager, firehoseServerUrl, workspaceId, state, logFn } = opts;
36750
37332
  const firehoseCtx = { state, devServerManager, logFn };
@@ -36767,30 +37349,30 @@ function createOnBridgeIdentified(opts) {
36767
37349
  }
36768
37350
 
36769
37351
  // src/skills/discover-local-agent-skills.ts
36770
- import fs26 from "node:fs";
36771
- import path29 from "node:path";
37352
+ import fs27 from "node:fs";
37353
+ import path30 from "node:path";
36772
37354
  var SKILL_DISCOVERY_ROOTS = [".agents/skills", ".claude/skills", ".cursor/skills", "skills"];
36773
37355
  function discoverLocalSkills(cwd) {
36774
37356
  const out = [];
36775
37357
  const seenKeys = /* @__PURE__ */ new Set();
36776
37358
  for (const rel of SKILL_DISCOVERY_ROOTS) {
36777
- const base = path29.join(cwd, rel);
36778
- if (!fs26.existsSync(base) || !fs26.statSync(base).isDirectory()) continue;
37359
+ const base = path30.join(cwd, rel);
37360
+ if (!fs27.existsSync(base) || !fs27.statSync(base).isDirectory()) continue;
36779
37361
  let entries = [];
36780
37362
  try {
36781
- entries = fs26.readdirSync(base);
37363
+ entries = fs27.readdirSync(base);
36782
37364
  } catch {
36783
37365
  continue;
36784
37366
  }
36785
37367
  for (const name of entries) {
36786
- const dir = path29.join(base, name);
37368
+ const dir = path30.join(base, name);
36787
37369
  try {
36788
- if (!fs26.statSync(dir).isDirectory()) continue;
37370
+ if (!fs27.statSync(dir).isDirectory()) continue;
36789
37371
  } catch {
36790
37372
  continue;
36791
37373
  }
36792
- const skillMd = path29.join(dir, "SKILL.md");
36793
- if (!fs26.existsSync(skillMd)) continue;
37374
+ const skillMd = path30.join(dir, "SKILL.md");
37375
+ if (!fs27.existsSync(skillMd)) continue;
36794
37376
  const key = `${rel}/${name}`;
36795
37377
  if (seenKeys.has(key)) continue;
36796
37378
  seenKeys.add(key);
@@ -36802,23 +37384,23 @@ function discoverLocalSkills(cwd) {
36802
37384
  function discoverSkillLayoutRoots(cwd) {
36803
37385
  const roots = [];
36804
37386
  for (const rel of SKILL_DISCOVERY_ROOTS) {
36805
- const base = path29.join(cwd, rel);
36806
- if (!fs26.existsSync(base) || !fs26.statSync(base).isDirectory()) continue;
37387
+ const base = path30.join(cwd, rel);
37388
+ if (!fs27.existsSync(base) || !fs27.statSync(base).isDirectory()) continue;
36807
37389
  let entries = [];
36808
37390
  try {
36809
- entries = fs26.readdirSync(base);
37391
+ entries = fs27.readdirSync(base);
36810
37392
  } catch {
36811
37393
  continue;
36812
37394
  }
36813
37395
  const skills2 = [];
36814
37396
  for (const name of entries) {
36815
- const dir = path29.join(base, name);
37397
+ const dir = path30.join(base, name);
36816
37398
  try {
36817
- if (!fs26.statSync(dir).isDirectory()) continue;
37399
+ if (!fs27.statSync(dir).isDirectory()) continue;
36818
37400
  } catch {
36819
37401
  continue;
36820
37402
  }
36821
- if (!fs26.existsSync(path29.join(dir, "SKILL.md"))) continue;
37403
+ if (!fs27.existsSync(path30.join(dir, "SKILL.md"))) continue;
36822
37404
  const relPath = `${rel}/${name}`.replace(/\\/g, "/");
36823
37405
  skills2.push({ name, relPath });
36824
37406
  }
@@ -36855,7 +37437,7 @@ async function detectLocalAgentTypes() {
36855
37437
  }
36856
37438
  }
36857
37439
 
36858
- // src/bridge/connection/create-bridge-local-reports.ts
37440
+ // src/connection/create-bridge-local-reports.ts
36859
37441
  function createSendLocalSkillsReport(getWs, logFn) {
36860
37442
  return () => {
36861
37443
  setImmediate(() => {
@@ -36888,14 +37470,14 @@ function createReportAutoDetectedAgents(getWs, logFn) {
36888
37470
  };
36889
37471
  }
36890
37472
 
36891
- // src/bridge/connection/build-bridge-url.ts
37473
+ // src/connection/build-bridge-url.ts
36892
37474
  function buildBridgeUrl(apiUrl, workspaceId, authToken) {
36893
37475
  const base = apiUrl.startsWith("https") ? apiUrl.replace(/^https/, "wss") : apiUrl.replace(/^http/, "ws");
36894
37476
  const params = new URLSearchParams({ workspaceId, token: authToken });
36895
37477
  return `${base}/ws/bridge?${params.toString()}`;
36896
37478
  }
36897
37479
 
36898
- // src/bridge/connection/report-git-repos.ts
37480
+ // src/connection/report-git-repos.ts
36899
37481
  function reportGitRepos(getWs, log2) {
36900
37482
  setImmediate(() => {
36901
37483
  discoverGitRepos().then((repos) => {
@@ -36951,14 +37533,14 @@ function parseApiToBridgeMessage(data, log2) {
36951
37533
  return data;
36952
37534
  }
36953
37535
 
36954
- // src/bridge/routing/handlers/auth-token.ts
37536
+ // src/routing/handlers/auth-token.ts
36955
37537
  var handleAuthToken = (msg, { log: log2 }) => {
36956
37538
  if (typeof msg.token !== "string") return;
36957
37539
  log2("Received auth token. Save it for future runs:");
36958
37540
  log2(` export BUILDAUTOMATON_AUTH_TOKEN="${msg.token}"`);
36959
37541
  };
36960
37542
 
36961
- // src/bridge/routing/handlers/bridge-identified.ts
37543
+ // src/routing/handlers/bridge-identified.ts
36962
37544
  var handleBridgeIdentified = (msg, deps) => {
36963
37545
  if (typeof msg.bridgeName !== "string") return;
36964
37546
  deps.onBridgeIdentified({
@@ -36994,13 +37576,13 @@ function handleBridgeAgentConfig(msg, { acpManager }) {
36994
37576
  acpManager.setPreferredAgentType(msg.agents[0].type);
36995
37577
  }
36996
37578
 
36997
- // src/bridge/routing/handlers/agent-config.ts
37579
+ // src/routing/handlers/agent-config.ts
36998
37580
  var handleAgentConfigMessage = (msg, deps) => {
36999
37581
  handleBridgeAgentConfig(msg, deps);
37000
37582
  };
37001
37583
 
37002
37584
  // src/prompt-turn-queue/runner.ts
37003
- import fs29 from "node:fs";
37585
+ import fs30 from "node:fs";
37004
37586
 
37005
37587
  // src/prompt-turn-queue/client-report.ts
37006
37588
  function sendPromptQueueClientReport(ws, queues) {
@@ -37010,13 +37592,13 @@ function sendPromptQueueClientReport(ws, queues) {
37010
37592
  }
37011
37593
 
37012
37594
  // src/prompt-turn-queue/disk-store.ts
37013
- import fs28 from "node:fs";
37595
+ import fs29 from "node:fs";
37014
37596
 
37015
37597
  // src/prompt-turn-queue/paths.ts
37016
37598
  import crypto3 from "node:crypto";
37017
- import fs27 from "node:fs";
37018
- import path30 from "node:path";
37019
- import os6 from "node:os";
37599
+ import fs28 from "node:fs";
37600
+ import path31 from "node:path";
37601
+ import os7 from "node:os";
37020
37602
  var QUEUE_KEY_HEX_64 = /^[a-f0-9]{64}$/i;
37021
37603
  function queueStateFileSlug(queueKey) {
37022
37604
  if (QUEUE_KEY_HEX_64.test(queueKey)) return queueKey.toLowerCase();
@@ -37024,15 +37606,15 @@ function queueStateFileSlug(queueKey) {
37024
37606
  }
37025
37607
  function getPromptQueuesDirectory() {
37026
37608
  const override = process.env.BUILDAMATON_PROMPT_QUEUES_DIR?.trim();
37027
- if (override) return path30.resolve(override);
37028
- return path30.join(os6.homedir(), ".buildautomaton", "queues");
37609
+ if (override) return path31.resolve(override);
37610
+ return path31.join(os7.homedir(), ".buildautomaton", "queues");
37029
37611
  }
37030
37612
  function ensurePromptQueuesDirectory() {
37031
37613
  const dir = getPromptQueuesDirectory();
37032
- if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
37614
+ if (!fs28.existsSync(dir)) fs28.mkdirSync(dir, { recursive: true });
37033
37615
  }
37034
37616
  function queueStateFilePath(queueKey) {
37035
- return path30.join(getPromptQueuesDirectory(), `${queueStateFileSlug(queueKey)}.json`);
37617
+ return path31.join(getPromptQueuesDirectory(), `${queueStateFileSlug(queueKey)}.json`);
37036
37618
  }
37037
37619
 
37038
37620
  // src/prompt-turn-queue/disk-store.ts
@@ -37057,7 +37639,7 @@ function parsePersistedQueueFile(raw) {
37057
37639
  function readPersistedQueue(queueKey) {
37058
37640
  const p = queueStateFilePath(queueKey);
37059
37641
  try {
37060
- return parsePersistedQueueFile(fs28.readFileSync(p, "utf8"));
37642
+ return parsePersistedQueueFile(fs29.readFileSync(p, "utf8"));
37061
37643
  } catch {
37062
37644
  return null;
37063
37645
  }
@@ -37065,7 +37647,7 @@ function readPersistedQueue(queueKey) {
37065
37647
  function writePersistedQueue(file2) {
37066
37648
  ensurePromptQueuesDirectory();
37067
37649
  const p = queueStateFilePath(file2.queueKey);
37068
- fs28.writeFileSync(p, JSON.stringify(file2, null, 2), "utf8");
37650
+ fs29.writeFileSync(p, JSON.stringify(file2, null, 2), "utf8");
37069
37651
  }
37070
37652
  function mergeServerQueueSnapshot(queueKey, serverTurns) {
37071
37653
  const prev = readPersistedQueue(queueKey);
@@ -37123,7 +37705,7 @@ async function runLocalRevertBeforeQueuedPrompt(next, deps) {
37123
37705
  const tid = typeof pl.snapshotRevertTurnId === "string" && pl.snapshotRevertTurnId.trim() !== "" ? pl.snapshotRevertTurnId.trim() : next.turnId;
37124
37706
  const agentBase = deps.sessionWorktreeManager.getSessionWorktreeRootForSession(sid) ?? getBridgeRoot();
37125
37707
  const file2 = snapshotFilePath(agentBase, tid);
37126
- if (!fs29.existsSync(file2)) {
37708
+ if (!fs30.existsSync(file2)) {
37127
37709
  deps.log(
37128
37710
  `[Queue] requeued_with_revert: no pre-turn snapshot for ${tid.slice(0, 8)}\u2026; continuing without revert.`
37129
37711
  );
@@ -37153,7 +37735,8 @@ function dispatchLocalPrompt(next, deps) {
37153
37735
  ...typeof pl.followUpCatalogPromptId === "string" ? { followUpCatalogPromptId: pl.followUpCatalogPromptId } : {},
37154
37736
  ...Array.isArray(pl.sessionChangeSummaryFilePaths) ? { sessionChangeSummaryFilePaths: pl.sessionChangeSummaryFilePaths } : {},
37155
37737
  ...Array.isArray(pl.sessionChangeSummaryFileSnapshots) ? { sessionChangeSummaryFileSnapshots: pl.sessionChangeSummaryFileSnapshots } : {},
37156
- ...typeof pl.agentType === "string" && pl.agentType.trim() ? { agentType: pl.agentType.trim() } : {}
37738
+ ...typeof pl.agentType === "string" && pl.agentType.trim() ? { agentType: pl.agentType.trim() } : {},
37739
+ ...pl.agentConfig != null && typeof pl.agentConfig === "object" && !Array.isArray(pl.agentConfig) && Object.keys(pl.agentConfig).length > 0 ? { agentConfig: pl.agentConfig } : {}
37157
37740
  };
37158
37741
  handleBridgePrompt(msg, deps);
37159
37742
  }
@@ -37345,9 +37928,9 @@ function parseChangeSummarySnapshots(raw) {
37345
37928
  for (const item of raw) {
37346
37929
  if (!item || typeof item !== "object") continue;
37347
37930
  const o = item;
37348
- const path36 = typeof o.path === "string" && o.path.trim() !== "" ? o.path.trim() : "";
37349
- if (!path36) continue;
37350
- const row = { path: path36 };
37931
+ const path37 = typeof o.path === "string" && o.path.trim() !== "" ? o.path.trim() : "";
37932
+ if (!path37) continue;
37933
+ const row = { path: path37 };
37351
37934
  if (typeof o.patchContent === "string") row.patchContent = o.patchContent;
37352
37935
  if (typeof o.oldText === "string") row.oldText = o.oldText;
37353
37936
  if (typeof o.newText === "string") row.newText = o.newText;
@@ -37451,6 +38034,7 @@ function handleBridgePrompt(msg, deps) {
37451
38034
  const sessionParentPath = typeof msg.sessionParentPath === "string" && msg.sessionParentPath.trim() ? msg.sessionParentPath.trim() : null;
37452
38035
  const agentType = typeof msg.agentType === "string" && msg.agentType.trim() ? msg.agentType.trim() : void 0;
37453
38036
  const mode = typeof msg.mode === "string" && msg.mode.trim() ? msg.mode.trim() : void 0;
38037
+ const agentConfig = msg.agentConfig != null && typeof msg.agentConfig === "object" && !Array.isArray(msg.agentConfig) ? msg.agentConfig : void 0;
37454
38038
  acpManager.logPromptReceivedFromBridge({ agentType, mode });
37455
38039
  async function preambleAndPrompt(resolvedCwd) {
37456
38040
  const effectiveCwd = resolveSessionParentPathForAgentProcess(resolvedCwd);
@@ -37486,6 +38070,7 @@ function handleBridgePrompt(msg, deps) {
37486
38070
  runId,
37487
38071
  mode,
37488
38072
  agentType,
38073
+ agentConfig,
37489
38074
  sessionParentPath: effectiveCwd,
37490
38075
  sendResult: sendResult2,
37491
38076
  sendSessionUpdate,
@@ -37502,27 +38087,27 @@ function handleBridgePrompt(msg, deps) {
37502
38087
  });
37503
38088
  }
37504
38089
 
37505
- // src/bridge/routing/handlers/prompt.ts
38090
+ // src/routing/handlers/prompt.ts
37506
38091
  var handlePromptMessage = (msg, deps) => {
37507
38092
  handleBridgePrompt(msg, deps);
37508
38093
  };
37509
38094
 
37510
- // src/bridge/routing/handlers/prompt-queue-state.ts
38095
+ // src/routing/handlers/prompt-queue-state.ts
37511
38096
  var handlePromptQueueStateMessage = (msg, deps) => {
37512
38097
  void applyPromptQueueStateFromServer(msg, deps).catch((err) => {
37513
38098
  deps.log(`[Queue] applyPromptQueueStateFromServer failed: ${err instanceof Error ? err.message : String(err)}`);
37514
38099
  });
37515
38100
  };
37516
38101
 
37517
- // src/agents/acp/from-bridge/handle-bridge-cursor-request-response.ts
37518
- function handleBridgeCursorRequestResponse(msg, { acpManager }) {
38102
+ // src/agents/acp/from-bridge/handle-bridge-session-request-response.ts
38103
+ function handleBridgeSessionRequestResponse(msg, { acpManager }) {
37519
38104
  if (typeof msg.requestId !== "string") return;
37520
38105
  acpManager.resolveRequest(msg.requestId, msg.result ?? {});
37521
38106
  }
37522
38107
 
37523
- // src/bridge/routing/handlers/cursor-request-response.ts
37524
- var handleCursorRequestResponseMessage = (msg, deps) => {
37525
- handleBridgeCursorRequestResponse(msg, deps);
38108
+ // src/routing/handlers/session-request-response.ts
38109
+ var handleSessionRequestResponseMessage = (msg, deps) => {
38110
+ handleBridgeSessionRequestResponse(msg, deps);
37526
38111
  };
37527
38112
 
37528
38113
  // src/skills/preview.ts
@@ -37546,8 +38131,8 @@ function randomSecret() {
37546
38131
  }
37547
38132
  return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
37548
38133
  }
37549
- async function requestPreviewApi(port, secret, method, path36, body) {
37550
- const url2 = `http://127.0.0.1:${port}${path36}`;
38134
+ async function requestPreviewApi(port, secret, method, path37, body) {
38135
+ const url2 = `http://127.0.0.1:${port}${path37}`;
37551
38136
  const headers = {
37552
38137
  [PREVIEW_SECRET_HEADER]: secret,
37553
38138
  "Content-Type": "application/json"
@@ -37559,7 +38144,7 @@ async function requestPreviewApi(port, secret, method, path36, body) {
37559
38144
  });
37560
38145
  const data = await res.json().catch(() => ({}));
37561
38146
  if (!res.ok) {
37562
- throw new Error(data?.error ?? `Preview API ${method} ${path36}: ${res.status}`);
38147
+ throw new Error(data?.error ?? `Preview API ${method} ${path37}: ${res.status}`);
37563
38148
  }
37564
38149
  return data;
37565
38150
  }
@@ -37709,7 +38294,7 @@ function handleSkillCall(msg, socket, log2) {
37709
38294
  });
37710
38295
  }
37711
38296
 
37712
- // src/bridge/routing/handlers/skill-call.ts
38297
+ // src/routing/handlers/skill-call.ts
37713
38298
  var handleSkillCallMessage = (msg, { getWs, log: log2 }) => {
37714
38299
  const skillId = typeof msg.skillId === "string" ? msg.skillId : "";
37715
38300
  const operationId = typeof msg.operationId === "string" ? msg.operationId : "";
@@ -37724,15 +38309,15 @@ var handleSkillCallMessage = (msg, { getWs, log: log2 }) => {
37724
38309
  };
37725
38310
 
37726
38311
  // src/files/list-dir.ts
37727
- import fs30 from "node:fs";
37728
- import path32 from "node:path";
38312
+ import fs31 from "node:fs";
38313
+ import path33 from "node:path";
37729
38314
 
37730
38315
  // src/files/ensure-under-cwd.ts
37731
- import path31 from "node:path";
38316
+ import path32 from "node:path";
37732
38317
  function ensureUnderCwd(relativePath, cwd = getBridgeRoot()) {
37733
- const normalized = path31.normalize(relativePath).replace(/^(\.\/)+/, "");
37734
- const resolved = path31.resolve(cwd, normalized);
37735
- if (!resolved.startsWith(cwd + path31.sep) && resolved !== cwd) {
38318
+ const normalized = path32.normalize(relativePath).replace(/^(\.\/)+/, "");
38319
+ const resolved = path32.resolve(cwd, normalized);
38320
+ if (!resolved.startsWith(cwd + path32.sep) && resolved !== cwd) {
37736
38321
  return null;
37737
38322
  }
37738
38323
  return resolved;
@@ -37746,7 +38331,7 @@ async function listDirAsync(relativePath) {
37746
38331
  return { error: "Path is outside working directory" };
37747
38332
  }
37748
38333
  try {
37749
- const names = await fs30.promises.readdir(resolved, { withFileTypes: true });
38334
+ const names = await fs31.promises.readdir(resolved, { withFileTypes: true });
37750
38335
  const visible = names.filter((d) => !d.name.startsWith("."));
37751
38336
  const entries = [];
37752
38337
  for (let i = 0; i < visible.length; i++) {
@@ -37754,12 +38339,12 @@ async function listDirAsync(relativePath) {
37754
38339
  await yieldToEventLoop();
37755
38340
  }
37756
38341
  const d = visible[i];
37757
- const entryPath = path32.join(relativePath || ".", d.name).replace(/\\/g, "/");
37758
- const fullPath = path32.join(resolved, d.name);
38342
+ const entryPath = path33.join(relativePath || ".", d.name).replace(/\\/g, "/");
38343
+ const fullPath = path33.join(resolved, d.name);
37759
38344
  let isDir = d.isDirectory();
37760
38345
  if (d.isSymbolicLink()) {
37761
38346
  try {
37762
- const targetStat = await fs30.promises.stat(fullPath);
38347
+ const targetStat = await fs31.promises.stat(fullPath);
37763
38348
  isDir = targetStat.isDirectory();
37764
38349
  } catch {
37765
38350
  isDir = false;
@@ -37784,25 +38369,25 @@ async function listDirAsync(relativePath) {
37784
38369
  }
37785
38370
 
37786
38371
  // src/files/read-file.ts
37787
- import fs31 from "node:fs";
38372
+ import fs32 from "node:fs";
37788
38373
  import { StringDecoder } from "node:string_decoder";
37789
38374
  function resolveFilePath(relativePath) {
37790
38375
  const resolved = ensureUnderCwd(relativePath, getBridgeRoot());
37791
38376
  if (!resolved) return { error: "Path is outside working directory" };
37792
38377
  let real;
37793
38378
  try {
37794
- real = fs31.realpathSync(resolved);
38379
+ real = fs32.realpathSync(resolved);
37795
38380
  } catch {
37796
38381
  real = resolved;
37797
38382
  }
37798
- const stat3 = fs31.statSync(real);
38383
+ const stat3 = fs32.statSync(real);
37799
38384
  if (!stat3.isFile()) return { error: "Not a file" };
37800
38385
  return real;
37801
38386
  }
37802
38387
  var LINE_CHUNK_SIZE = 64 * 1024;
37803
38388
  function readFileRange(filePath, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
37804
- const fileSize = fs31.statSync(filePath).size;
37805
- const fd = fs31.openSync(filePath, "r");
38389
+ const fileSize = fs32.statSync(filePath).size;
38390
+ const fd = fs32.openSync(filePath, "r");
37806
38391
  const bufSize = 64 * 1024;
37807
38392
  const buf = Buffer.alloc(bufSize);
37808
38393
  const decoder = new StringDecoder("utf8");
@@ -37815,7 +38400,7 @@ function readFileRange(filePath, startLine, endLine, lineOffsetIn, lineChunkSize
37815
38400
  let line0Accum = "";
37816
38401
  try {
37817
38402
  let bytesRead;
37818
- while (!done && (bytesRead = fs31.readSync(fd, buf, 0, bufSize, null)) > 0) {
38403
+ while (!done && (bytesRead = fs32.readSync(fd, buf, 0, bufSize, null)) > 0) {
37819
38404
  const text = partial2 + decoder.write(buf.subarray(0, bytesRead));
37820
38405
  partial2 = "";
37821
38406
  let lineStart = 0;
@@ -37950,7 +38535,7 @@ function readFileRange(filePath, startLine, endLine, lineOffsetIn, lineChunkSize
37950
38535
  }
37951
38536
  return { content: resultLines.join("\n"), size: fileSize };
37952
38537
  } finally {
37953
- fs31.closeSync(fd);
38538
+ fs32.closeSync(fd);
37954
38539
  }
37955
38540
  }
37956
38541
  function readFile3(relativePath, startLine, endLine, lineOffset, lineChunkSize = LINE_CHUNK_SIZE) {
@@ -37961,8 +38546,8 @@ function readFile3(relativePath, startLine, endLine, lineOffset, lineChunkSize =
37961
38546
  if (hasRange) {
37962
38547
  return readFileRange(result, startLine, endLine, lineOffset, lineChunkSize);
37963
38548
  }
37964
- const stat3 = fs31.statSync(result);
37965
- const raw = fs31.readFileSync(result, "utf8");
38549
+ const stat3 = fs32.statSync(result);
38550
+ const raw = fs32.readFileSync(result, "utf8");
37966
38551
  const lines = raw.split(/\r?\n/);
37967
38552
  return { content: raw, totalLines: lines.length, size: stat3.size };
37968
38553
  } catch (err) {
@@ -38051,7 +38636,7 @@ function handleFileBrowserRequest(msg, socket, e2ee) {
38051
38636
  })();
38052
38637
  }
38053
38638
 
38054
- // src/bridge/routing/handlers/file-browser-messages.ts
38639
+ // src/routing/handlers/file-browser-messages.ts
38055
38640
  function handleFileBrowserRequestMessage(msg, { getWs, e2ee }) {
38056
38641
  if (typeof msg.id !== "string" || typeof msg.path !== "string") return;
38057
38642
  const socket = getWs();
@@ -38069,7 +38654,7 @@ function handleFileBrowserSearchMessage(msg, { getWs, e2ee }) {
38069
38654
  handleFileBrowserSearch(msg, socket, e2ee);
38070
38655
  }
38071
38656
 
38072
- // src/bridge/routing/handlers/skill-layout-request.ts
38657
+ // src/routing/handlers/skill-layout-request.ts
38073
38658
  function handleSkillLayoutRequest(msg, deps) {
38074
38659
  const socket = deps.getWs();
38075
38660
  const id = typeof msg.id === "string" ? msg.id : "";
@@ -38080,8 +38665,8 @@ function handleSkillLayoutRequest(msg, deps) {
38080
38665
  }
38081
38666
 
38082
38667
  // src/skills/install-remote-skills.ts
38083
- import fs32 from "node:fs";
38084
- import path33 from "node:path";
38668
+ import fs33 from "node:fs";
38669
+ import path34 from "node:path";
38085
38670
  function installRemoteSkills(cwd, targetDir, items) {
38086
38671
  const installed2 = [];
38087
38672
  if (!Array.isArray(items)) {
@@ -38092,15 +38677,15 @@ function installRemoteSkills(cwd, targetDir, items) {
38092
38677
  if (typeof item.sourceId !== "string" || typeof item.skillName !== "string" || typeof item.versionHash !== "string" || !Array.isArray(item.files)) {
38093
38678
  continue;
38094
38679
  }
38095
- const skillDir = path33.join(cwd, targetDir, item.skillName);
38680
+ const skillDir = path34.join(cwd, targetDir, item.skillName);
38096
38681
  for (const f of item.files) {
38097
38682
  if (typeof f.path !== "string" || !f.text && !f.base64) continue;
38098
- const dest = path33.join(skillDir, f.path);
38099
- fs32.mkdirSync(path33.dirname(dest), { recursive: true });
38683
+ const dest = path34.join(skillDir, f.path);
38684
+ fs33.mkdirSync(path34.dirname(dest), { recursive: true });
38100
38685
  if (f.text !== void 0) {
38101
- fs32.writeFileSync(dest, f.text, "utf8");
38686
+ fs33.writeFileSync(dest, f.text, "utf8");
38102
38687
  } else if (f.base64) {
38103
- fs32.writeFileSync(dest, Buffer.from(f.base64, "base64"));
38688
+ fs33.writeFileSync(dest, Buffer.from(f.base64, "base64"));
38104
38689
  }
38105
38690
  }
38106
38691
  installed2.push({
@@ -38115,7 +38700,7 @@ function installRemoteSkills(cwd, targetDir, items) {
38115
38700
  }
38116
38701
  }
38117
38702
 
38118
- // src/bridge/routing/handlers/install-skills.ts
38703
+ // src/routing/handlers/install-skills.ts
38119
38704
  var handleInstallSkillsMessage = (msg, deps) => {
38120
38705
  const socket = deps.getWs();
38121
38706
  const id = typeof msg.id === "string" ? msg.id : "";
@@ -38136,12 +38721,12 @@ var handleInstallSkillsMessage = (msg, deps) => {
38136
38721
  }
38137
38722
  };
38138
38723
 
38139
- // src/bridge/routing/handlers/refresh-local-skills.ts
38724
+ // src/routing/handlers/refresh-local-skills.ts
38140
38725
  var handleRefreshLocalSkills = (_msg, deps) => {
38141
38726
  deps.sendLocalSkillsReport?.();
38142
38727
  };
38143
38728
 
38144
- // src/bridge/routing/handlers/session-git-request.ts
38729
+ // src/routing/handlers/session-git-request.ts
38145
38730
  function sendResult(ws, id, payload, e2ee, encryptedFields = []) {
38146
38731
  if (!ws) return;
38147
38732
  const message = { type: "session_git_result", id, ...payload };
@@ -38227,7 +38812,7 @@ var handleSessionGitRequestMessage = (msg, deps) => {
38227
38812
  })();
38228
38813
  };
38229
38814
 
38230
- // src/bridge/routing/handlers/rename-session-branch.ts
38815
+ // src/routing/handlers/rename-session-branch.ts
38231
38816
  var handleRenameSessionBranchMessage = (msg, deps) => {
38232
38817
  const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
38233
38818
  const newBranch = typeof msg.newBranch === "string" ? msg.newBranch : "";
@@ -38235,22 +38820,22 @@ var handleRenameSessionBranchMessage = (msg, deps) => {
38235
38820
  void deps.sessionWorktreeManager.renameSessionBranch(sessionId, newBranch);
38236
38821
  };
38237
38822
 
38238
- // src/bridge/routing/handlers/session-archived.ts
38823
+ // src/routing/handlers/session-archived.ts
38239
38824
  var handleSessionArchivedMessage = (msg, deps) => {
38240
38825
  const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
38241
38826
  if (!sessionId) return;
38242
38827
  void deps.sessionWorktreeManager.removeSessionWorktrees(sessionId);
38243
38828
  };
38244
38829
 
38245
- // src/bridge/routing/handlers/session-discarded.ts
38830
+ // src/routing/handlers/session-discarded.ts
38246
38831
  var handleSessionDiscardedMessage = (msg, deps) => {
38247
38832
  const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
38248
38833
  if (!sessionId) return;
38249
38834
  void deps.sessionWorktreeManager.removeSessionWorktrees(sessionId);
38250
38835
  };
38251
38836
 
38252
- // src/bridge/routing/handlers/revert-turn-snapshot.ts
38253
- import * as fs33 from "node:fs";
38837
+ // src/routing/handlers/revert-turn-snapshot.ts
38838
+ import * as fs34 from "node:fs";
38254
38839
  var handleRevertTurnSnapshotMessage = (msg, deps) => {
38255
38840
  const id = typeof msg.id === "string" ? msg.id : "";
38256
38841
  const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
@@ -38262,7 +38847,7 @@ var handleRevertTurnSnapshotMessage = (msg, deps) => {
38262
38847
  if (!s) return;
38263
38848
  const agentBase = sessionWorktreeManager.getSessionWorktreeRootForSession(sessionId) ?? getBridgeRoot();
38264
38849
  const file2 = snapshotFilePath(agentBase, turnId);
38265
- if (!fs33.existsSync(file2)) {
38850
+ if (!fs34.existsSync(file2)) {
38266
38851
  sendWsMessage(s, {
38267
38852
  type: "revert_turn_snapshot_result",
38268
38853
  id,
@@ -38281,7 +38866,7 @@ var handleRevertTurnSnapshotMessage = (msg, deps) => {
38281
38866
  })();
38282
38867
  };
38283
38868
 
38284
- // src/bridge/routing/handlers/dev-server-control.ts
38869
+ // src/routing/handlers/dev-server-control.ts
38285
38870
  var handleDevServerControl = (msg, deps) => {
38286
38871
  let wire;
38287
38872
  try {
@@ -38296,13 +38881,13 @@ var handleDevServerControl = (msg, deps) => {
38296
38881
  deps.devServerManager?.handleControl(serverId, action);
38297
38882
  };
38298
38883
 
38299
- // src/bridge/routing/handlers/dev-servers-config.ts
38884
+ // src/routing/handlers/dev-servers-config.ts
38300
38885
  var handleDevServersConfig = (msg, deps) => {
38301
38886
  const devServers = msg.devServers;
38302
38887
  deps.devServerManager?.applyConfig(devServers ?? []);
38303
38888
  };
38304
38889
 
38305
- // src/bridge/routing/dispatch-bridge-message.ts
38890
+ // src/routing/dispatch-bridge-message.ts
38306
38891
  function dispatchBridgeMessage(msg, deps) {
38307
38892
  switch (msg.type) {
38308
38893
  case "auth_token":
@@ -38342,7 +38927,7 @@ function dispatchBridgeMessage(msg, deps) {
38342
38927
  handleRevertTurnSnapshotMessage(msg, deps);
38343
38928
  break;
38344
38929
  case "cursor_request_response":
38345
- handleCursorRequestResponseMessage(msg, deps);
38930
+ handleSessionRequestResponseMessage(msg, deps);
38346
38931
  break;
38347
38932
  case "skill_call":
38348
38933
  handleSkillCallMessage(msg, deps);
@@ -38365,7 +38950,7 @@ function dispatchBridgeMessage(msg, deps) {
38365
38950
  }
38366
38951
  }
38367
38952
 
38368
- // src/bridge/routing/handle-bridge-message.ts
38953
+ // src/routing/handle-bridge-message.ts
38369
38954
  function handleBridgeMessage(data, deps) {
38370
38955
  if (!deps.getWs()) return;
38371
38956
  const msg = parseApiToBridgeMessage(data, deps.log);
@@ -38402,7 +38987,7 @@ async function refreshBridgeTokens(params) {
38402
38987
  }
38403
38988
  }
38404
38989
 
38405
- // src/bridge/connection/main-bridge-ws-lifecycle.ts
38990
+ // src/connection/main-bridge-ws-lifecycle.ts
38406
38991
  function createMainBridgeWebSocketLifecycle(params) {
38407
38992
  const {
38408
38993
  state,
@@ -38448,13 +39033,26 @@ function createMainBridgeWebSocketLifecycle(params) {
38448
39033
  }
38449
39034
  }
38450
39035
  function handleClose(code, reason) {
38451
- const was = state.currentWs;
38452
- state.currentWs = null;
38453
- if (was) was.removeAllListeners();
38454
- const willReconnect = !state.closedByUser;
38455
- beginMainBridgeDeferredDisconnect(state, code, reason, logFn, willReconnect);
38456
- if (willReconnect) {
38457
- scheduleMainBridgeReconnect(state, connect, logFn);
39036
+ try {
39037
+ const was = state.currentWs;
39038
+ state.currentWs = null;
39039
+ if (was) was.removeAllListeners();
39040
+ const willReconnect = !state.closedByUser;
39041
+ beginMainBridgeDeferredDisconnect(state, code, reason, logFn, willReconnect);
39042
+ if (willReconnect) {
39043
+ state.lastReconnectCloseMeta = { code, reason };
39044
+ try {
39045
+ scheduleMainBridgeReconnect(state, connect, logFn, { code, reason });
39046
+ } catch {
39047
+ }
39048
+ }
39049
+ } catch {
39050
+ if (!state.closedByUser) {
39051
+ try {
39052
+ scheduleMainBridgeReconnect(state, connect, logFn);
39053
+ } catch {
39054
+ }
39055
+ }
38458
39056
  }
38459
39057
  }
38460
39058
  function connect() {
@@ -38464,7 +39062,10 @@ function createMainBridgeWebSocketLifecycle(params) {
38464
39062
  state.reconnectTimeout = null;
38465
39063
  }
38466
39064
  if (state.reconnectAttempt === 0) {
38467
- logFn("Connecting to bridge service\u2026");
39065
+ try {
39066
+ logFn("Connecting to bridge service\u2026");
39067
+ } catch {
39068
+ }
38468
39069
  }
38469
39070
  const prev = state.currentWs;
38470
39071
  if (prev) {
@@ -38480,53 +39081,84 @@ function createMainBridgeWebSocketLifecycle(params) {
38480
39081
  }
38481
39082
  state.currentWs = null;
38482
39083
  }
38483
- const url2 = buildBridgeUrl(apiUrl, workspaceId, tokens.accessToken);
38484
- state.currentWs = createWsBridge({
38485
- url: url2,
38486
- clientPingIntervalMs: CLI_WEBSOCKET_CLIENT_PING_MS,
38487
- onAuthInvalid: () => {
38488
- if (authRefreshInFlight) return;
38489
- void (async () => {
38490
- authRefreshInFlight = true;
38491
- try {
38492
- if (tokens.refreshToken) {
38493
- const next = await refreshBridgeTokens({
38494
- apiUrl,
38495
- workspaceId,
38496
- refreshToken: tokens.refreshToken
38497
- });
38498
- if (next?.token && next.refreshToken) {
38499
- tokens.accessToken = next.token;
38500
- tokens.refreshToken = next.refreshToken;
38501
- persistTokens?.({ token: tokens.accessToken, refreshToken: tokens.refreshToken });
38502
- logFn("[Bridge service] Access token refreshed; reconnecting\u2026");
38503
- state.reconnectAttempt = 0;
38504
- state.logBridgeOpenAsReconnect = true;
38505
- authRefreshInFlight = false;
38506
- connect();
38507
- return;
39084
+ try {
39085
+ const url2 = buildBridgeUrl(apiUrl, workspaceId, tokens.accessToken);
39086
+ state.currentWs = createWsBridge({
39087
+ url: url2,
39088
+ clientPingIntervalMs: CLI_WEBSOCKET_CLIENT_PING_MS,
39089
+ onAuthInvalid: () => {
39090
+ if (authRefreshInFlight) return;
39091
+ void (async () => {
39092
+ authRefreshInFlight = true;
39093
+ try {
39094
+ if (tokens.refreshToken) {
39095
+ const next = await refreshBridgeTokens({
39096
+ apiUrl,
39097
+ workspaceId,
39098
+ refreshToken: tokens.refreshToken
39099
+ });
39100
+ if (next?.token && next.refreshToken) {
39101
+ tokens.accessToken = next.token;
39102
+ tokens.refreshToken = next.refreshToken;
39103
+ persistTokens?.({ token: tokens.accessToken, refreshToken: tokens.refreshToken });
39104
+ try {
39105
+ logFn("[Bridge service] Access token refreshed; reconnecting\u2026");
39106
+ } catch {
39107
+ }
39108
+ state.reconnectAttempt = 0;
39109
+ resetReconnectOutageTracker(state.mainOutage);
39110
+ state.lastReconnectCloseMeta = null;
39111
+ state.logBridgeOpenAsReconnect = true;
39112
+ authRefreshInFlight = false;
39113
+ connect();
39114
+ return;
39115
+ }
38508
39116
  }
39117
+ authRefreshInFlight = false;
39118
+ state.reconnectAttempt = 0;
39119
+ resetReconnectOutageTracker(state.mainOutage);
39120
+ state.lastReconnectCloseMeta = null;
39121
+ onAuthInvalid();
39122
+ } catch {
39123
+ authRefreshInFlight = false;
39124
+ state.reconnectAttempt = 0;
39125
+ resetReconnectOutageTracker(state.mainOutage);
39126
+ state.lastReconnectCloseMeta = null;
39127
+ onAuthInvalid();
38509
39128
  }
38510
- authRefreshInFlight = false;
38511
- state.reconnectAttempt = 0;
38512
- onAuthInvalid();
39129
+ })();
39130
+ },
39131
+ onOpen: handleOpen,
39132
+ onClose: handleClose,
39133
+ onError: (err) => {
39134
+ if (state.mainOutage.startedAt != null) {
39135
+ return;
39136
+ }
39137
+ try {
39138
+ logCliWebSocketError(logFn, "[Bridge service]", err);
38513
39139
  } catch {
38514
- authRefreshInFlight = false;
38515
- state.reconnectAttempt = 0;
38516
- onAuthInvalid();
38517
39140
  }
38518
- })();
38519
- },
38520
- onOpen: handleOpen,
38521
- onClose: handleClose,
38522
- onError: (err) => logCliWebSocketError(logFn, "[Bridge service]", err),
38523
- onMessage: (data) => handleBridgeMessage(data, messageDeps)
38524
- });
39141
+ },
39142
+ onMessage: (data) => {
39143
+ try {
39144
+ handleBridgeMessage(data, messageDeps);
39145
+ } catch {
39146
+ }
39147
+ }
39148
+ });
39149
+ } catch {
39150
+ if (!state.closedByUser && state.currentWs == null) {
39151
+ try {
39152
+ scheduleMainBridgeReconnect(state, connect, logFn);
39153
+ } catch {
39154
+ }
39155
+ }
39156
+ }
38525
39157
  }
38526
39158
  return { connect };
38527
39159
  }
38528
39160
 
38529
- // src/bridge/connection/create-bridge-connection.ts
39161
+ // src/connection/create-bridge-connection.ts
38530
39162
  async function createBridgeConnection(options) {
38531
39163
  const { apiUrl, workspaceId, justAuthenticated, onAuthInvalid, persistTokens } = options;
38532
39164
  const firehoseServerUrl = options.firehoseServerUrl ?? options.proxyServerUrl;
@@ -38538,16 +39170,20 @@ async function createBridgeConnection(options) {
38538
39170
  const state = {
38539
39171
  closedByUser: false,
38540
39172
  reconnectAttempt: 0,
39173
+ lastReconnectCloseMeta: null,
38541
39174
  logBridgeOpenAsReconnect: false,
38542
39175
  reconnectTimeout: null,
38543
39176
  currentWs: null,
38544
39177
  mainQuiet: createEmptyReconnectQuietSlot(),
39178
+ mainOutage: createEmptyReconnectOutageTracker(),
38545
39179
  firehoseHandle: null,
38546
39180
  lastFirehoseParams: null,
38547
39181
  firehoseReconnectTimeout: null,
38548
39182
  firehoseReconnectAttempt: 0,
38549
39183
  firehoseGeneration: 0,
38550
- firehoseQuiet: createEmptyReconnectQuietSlot()
39184
+ firehoseQuiet: createEmptyReconnectQuietSlot(),
39185
+ firehoseOutage: createEmptyReconnectOutageTracker(),
39186
+ lastFirehoseReconnectCloseMeta: null
38551
39187
  };
38552
39188
  const worktreesRootPath = options.worktreesRootPath ?? defaultWorktreesRootPath();
38553
39189
  const sessionWorktreeManager = new SessionWorktreeManager({
@@ -38584,8 +39220,8 @@ async function createBridgeConnection(options) {
38584
39220
  getCloudAccessToken: () => tokens.accessToken
38585
39221
  };
38586
39222
  const identifyReportedPaths = {
38587
- bridgeRootPath: path34.resolve(getBridgeRoot()),
38588
- worktreesRootPath: path34.resolve(worktreesRootPath)
39223
+ bridgeRootPath: path35.resolve(getBridgeRoot()),
39224
+ worktreesRootPath: path35.resolve(worktreesRootPath)
38589
39225
  };
38590
39226
  const { connect } = createMainBridgeWebSocketLifecycle({
38591
39227
  state,
@@ -38809,6 +39445,9 @@ function colorize(color, message) {
38809
39445
  return `${color}${message}${RESET}`;
38810
39446
  }
38811
39447
  async function runCliAction(program2, opts) {
39448
+ const trace = opts.trace === true;
39449
+ const debug2 = opts.debug === true || trace;
39450
+ setCliLogVerbosity(trace ? "trace" : debug2 ? "debug" : "info");
38812
39451
  const positionalUrl = program2.args?.[0];
38813
39452
  const urlFromPositional = typeof positionalUrl === "string" && /^https?:\/\//i.test(positionalUrl) ? positionalUrl : void 0;
38814
39453
  const apiUrlFromCli = opts.apiUrl ?? urlFromPositional;
@@ -38818,9 +39457,9 @@ async function runCliAction(program2, opts) {
38818
39457
  const firehoseServerUrl = opts.firehoseUrl ?? opts.proxyUrl ?? process.env.BUILDAUTOMATON_FIREHOSE_URL ?? process.env.BUILDAUTOMATON_PROXY_URL ?? DEFAULT_FIREHOSE_URL;
38819
39458
  const bridgeRootOpt = (opts.bridgeRoot && typeof opts.bridgeRoot === "string" && opts.bridgeRoot.trim() ? opts.bridgeRoot.trim() : null) ?? (opts.cwd && typeof opts.cwd === "string" && opts.cwd.trim() ? opts.cwd.trim() : null);
38820
39459
  if (bridgeRootOpt) {
38821
- const resolvedBridgeRoot = path35.resolve(process.cwd(), bridgeRootOpt);
39460
+ const resolvedBridgeRoot = path36.resolve(process.cwd(), bridgeRootOpt);
38822
39461
  try {
38823
- const st = fs34.statSync(resolvedBridgeRoot);
39462
+ const st = fs35.statSync(resolvedBridgeRoot);
38824
39463
  if (!st.isDirectory()) {
38825
39464
  console.error(`Bridge root is not a directory: ${resolvedBridgeRoot}`);
38826
39465
  process.exit(1);
@@ -38840,7 +39479,7 @@ async function runCliAction(program2, opts) {
38840
39479
  );
38841
39480
  let worktreesRootPath;
38842
39481
  if (opts.worktreesRoot && opts.worktreesRoot.trim()) {
38843
- worktreesRootPath = path35.resolve(opts.worktreesRoot.trim());
39482
+ worktreesRootPath = path36.resolve(opts.worktreesRoot.trim());
38844
39483
  }
38845
39484
  const e2eCertificates = opts.e2eeCertificatesDir?.trim() ? await loadOrCreateE2eCertificates(opts.e2eeCertificatesDir.trim()) : void 0;
38846
39485
  if (e2eCertificates) {
@@ -38900,7 +39539,7 @@ async function main() {
38900
39539
  "--e2ee-certificates-dir <path>",
38901
39540
  "Directory to load or generate E2EE keys for sessions, files, and logs",
38902
39541
  process.env.BUILDAUTOMATON_E2EE_CERTIFICATES_DIR
38903
- ).option("--no-config", "Ignore saved config at ~/.buildautomaton/config.json").action(async (opts) => runCliAction(program2, opts));
39542
+ ).option("--no-config", "Ignore saved config at ~/.buildautomaton/config.json").option("--debug", "Print debug-level messages from the CLI").option("--trace", "Print trace-level messages (e.g. git subprocess output); implies --debug").action(async (opts) => runCliAction(program2, opts));
38904
39543
  await program2.parseAsync(process.argv);
38905
39544
  }
38906
39545
  main().catch((err) => {