@buildautomaton/cli 0.1.13 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -4065,8 +4065,8 @@ var init_parseUtil = __esm({
4065
4065
  init_errors();
4066
4066
  init_en();
4067
4067
  makeIssue = (params) => {
4068
- const { data, path: path29, errorMaps, issueData } = params;
4069
- const fullPath = [...path29, ...issueData.path || []];
4068
+ const { data, path: path31, errorMaps, issueData } = params;
4069
+ const fullPath = [...path31, ...issueData.path || []];
4070
4070
  const fullIssue = {
4071
4071
  ...issueData,
4072
4072
  path: fullPath
@@ -4374,11 +4374,11 @@ var init_types = __esm({
4374
4374
  init_parseUtil();
4375
4375
  init_util();
4376
4376
  ParseInputLazyPath = class {
4377
- constructor(parent, value, path29, key) {
4377
+ constructor(parent, value, path31, key) {
4378
4378
  this._cachedPath = [];
4379
4379
  this.parent = parent;
4380
4380
  this.data = value;
4381
- this._path = path29;
4381
+ this._path = path31;
4382
4382
  this._key = key;
4383
4383
  }
4384
4384
  get path() {
@@ -7993,15 +7993,15 @@ function assignProp(target, prop, value) {
7993
7993
  configurable: true
7994
7994
  });
7995
7995
  }
7996
- function getElementAtPath(obj, path29) {
7997
- if (!path29)
7996
+ function getElementAtPath(obj, path31) {
7997
+ if (!path31)
7998
7998
  return obj;
7999
- return path29.reduce((acc, key) => acc?.[key], obj);
7999
+ return path31.reduce((acc, key) => acc?.[key], obj);
8000
8000
  }
8001
8001
  function promiseAllObject(promisesObj) {
8002
8002
  const keys = Object.keys(promisesObj);
8003
- const promises = keys.map((key) => promisesObj[key]);
8004
- return Promise.all(promises).then((results) => {
8003
+ const promises3 = keys.map((key) => promisesObj[key]);
8004
+ return Promise.all(promises3).then((results) => {
8005
8005
  const resolvedObj = {};
8006
8006
  for (let i = 0; i < keys.length; i++) {
8007
8007
  resolvedObj[keys[i]] = results[i];
@@ -8245,11 +8245,11 @@ function aborted(x, startIndex = 0) {
8245
8245
  }
8246
8246
  return false;
8247
8247
  }
8248
- function prefixIssues(path29, issues) {
8248
+ function prefixIssues(path31, issues) {
8249
8249
  return issues.map((iss) => {
8250
8250
  var _a2;
8251
8251
  (_a2 = iss).path ?? (_a2.path = []);
8252
- iss.path.unshift(path29);
8252
+ iss.path.unshift(path31);
8253
8253
  return iss;
8254
8254
  });
8255
8255
  }
@@ -8438,7 +8438,7 @@ function treeifyError(error40, _mapper) {
8438
8438
  return issue2.message;
8439
8439
  };
8440
8440
  const result = { errors: [] };
8441
- const processError = (error41, path29 = []) => {
8441
+ const processError = (error41, path31 = []) => {
8442
8442
  var _a2, _b;
8443
8443
  for (const issue2 of error41.issues) {
8444
8444
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -8448,7 +8448,7 @@ function treeifyError(error40, _mapper) {
8448
8448
  } else if (issue2.code === "invalid_element") {
8449
8449
  processError({ issues: issue2.issues }, issue2.path);
8450
8450
  } else {
8451
- const fullpath = [...path29, ...issue2.path];
8451
+ const fullpath = [...path31, ...issue2.path];
8452
8452
  if (fullpath.length === 0) {
8453
8453
  result.errors.push(mapper(issue2));
8454
8454
  continue;
@@ -8478,9 +8478,9 @@ function treeifyError(error40, _mapper) {
8478
8478
  processError(error40);
8479
8479
  return result;
8480
8480
  }
8481
- function toDotPath(path29) {
8481
+ function toDotPath(path31) {
8482
8482
  const segs = [];
8483
- for (const seg of path29) {
8483
+ for (const seg of path31) {
8484
8484
  if (typeof seg === "number")
8485
8485
  segs.push(`[${seg}]`);
8486
8486
  else if (typeof seg === "symbol")
@@ -20943,8 +20943,8 @@ var init_acp = __esm({
20943
20943
  this.#requestHandler = requestHandler;
20944
20944
  this.#notificationHandler = notificationHandler;
20945
20945
  this.#stream = stream;
20946
- this.#closedPromise = new Promise((resolve14) => {
20947
- this.#abortController.signal.addEventListener("abort", () => resolve14());
20946
+ this.#closedPromise = new Promise((resolve15) => {
20947
+ this.#abortController.signal.addEventListener("abort", () => resolve15());
20948
20948
  });
20949
20949
  this.#receive();
20950
20950
  }
@@ -21093,8 +21093,8 @@ var init_acp = __esm({
21093
21093
  }
21094
21094
  async sendRequest(method, params) {
21095
21095
  const id = this.#nextRequestId++;
21096
- const responsePromise = new Promise((resolve14, reject) => {
21097
- this.#pendingResponses.set(id, { resolve: resolve14, reject });
21096
+ const responsePromise = new Promise((resolve15, reject) => {
21097
+ this.#pendingResponses.set(id, { resolve: resolve15, reject });
21098
21098
  });
21099
21099
  await this.#sendMessage({ jsonrpc: "2.0", id, method, params });
21100
21100
  return responsePromise;
@@ -21963,10 +21963,10 @@ var require_src2 = __commonJS({
21963
21963
  var fs_1 = __require("fs");
21964
21964
  var debug_1 = __importDefault(require_src());
21965
21965
  var log2 = debug_1.default("@kwsites/file-exists");
21966
- function check2(path29, isFile, isDirectory) {
21967
- log2(`checking %s`, path29);
21966
+ function check2(path31, isFile, isDirectory) {
21967
+ log2(`checking %s`, path31);
21968
21968
  try {
21969
- const stat2 = fs_1.statSync(path29);
21969
+ const stat2 = fs_1.statSync(path31);
21970
21970
  if (stat2.isFile() && isFile) {
21971
21971
  log2(`[OK] path represents a file`);
21972
21972
  return true;
@@ -21986,8 +21986,8 @@ var require_src2 = __commonJS({
21986
21986
  throw e;
21987
21987
  }
21988
21988
  }
21989
- function exists2(path29, type = exports.READABLE) {
21990
- return check2(path29, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
21989
+ function exists2(path31, type = exports.READABLE) {
21990
+ return check2(path31, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
21991
21991
  }
21992
21992
  exports.exists = exists2;
21993
21993
  exports.FILE = 1;
@@ -22413,7 +22413,7 @@ async function createSdkStdioAcpClient(options) {
22413
22413
  child.once("close", (code, signal) => {
22414
22414
  onAgentSubprocessExit?.({ code, signal });
22415
22415
  });
22416
- return new Promise((resolve14, reject) => {
22416
+ return new Promise((resolve15, reject) => {
22417
22417
  let initSettled = false;
22418
22418
  const settleReject = (err) => {
22419
22419
  if (initSettled) return;
@@ -22427,7 +22427,7 @@ async function createSdkStdioAcpClient(options) {
22427
22427
  const settleResolve = (handle) => {
22428
22428
  if (initSettled) return;
22429
22429
  initSettled = true;
22430
- resolve14(handle);
22430
+ resolve15(handle);
22431
22431
  };
22432
22432
  child.on("error", (err) => {
22433
22433
  settleReject(new Error(formatSpawnError(err, command[0])));
@@ -22463,8 +22463,8 @@ async function createSdkStdioAcpClient(options) {
22463
22463
  });
22464
22464
  } catch {
22465
22465
  }
22466
- return await new Promise((resolve15) => {
22467
- pendingPermissionResolvers.set(requestId, resolve15);
22466
+ return await new Promise((resolve16) => {
22467
+ pendingPermissionResolvers.set(requestId, resolve16);
22468
22468
  });
22469
22469
  },
22470
22470
  async readTextFile(params) {
@@ -22572,9 +22572,9 @@ async function createSdkStdioAcpClient(options) {
22572
22572
  }
22573
22573
  },
22574
22574
  async cancel() {
22575
- for (const [id, resolve15] of [...pendingPermissionResolvers.entries()]) {
22575
+ for (const [id, resolve16] of [...pendingPermissionResolvers.entries()]) {
22576
22576
  pendingPermissionResolvers.delete(id);
22577
- resolve15({ outcome: { outcome: "cancelled" } });
22577
+ resolve16({ outcome: { outcome: "cancelled" } });
22578
22578
  }
22579
22579
  try {
22580
22580
  await connection.cancel({ sessionId });
@@ -22590,10 +22590,10 @@ async function createSdkStdioAcpClient(options) {
22590
22590
  }
22591
22591
  },
22592
22592
  resolveRequest(requestId, result) {
22593
- const resolve15 = pendingPermissionResolvers.get(requestId);
22594
- if (!resolve15) return;
22593
+ const resolve16 = pendingPermissionResolvers.get(requestId);
22594
+ if (!resolve16) return;
22595
22595
  pendingPermissionResolvers.delete(requestId);
22596
- resolve15(result);
22596
+ resolve16(result);
22597
22597
  },
22598
22598
  disconnect() {
22599
22599
  child.kill();
@@ -22700,7 +22700,7 @@ async function proxyToLocal(request) {
22700
22700
  };
22701
22701
  const maxAttempts = isIdempotentProxyMethod(request.method) ? LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS.length + 1 : 1;
22702
22702
  for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
22703
- const once = await new Promise((resolve14) => {
22703
+ const once = await new Promise((resolve15) => {
22704
22704
  const req = mod.request(opts, (res) => {
22705
22705
  const chunks = [];
22706
22706
  res.on("data", (c) => chunks.push(c));
@@ -22711,7 +22711,7 @@ async function proxyToLocal(request) {
22711
22711
  if (typeof v === "string") headers[k] = v;
22712
22712
  else if (Array.isArray(v) && v[0]) headers[k] = v[0];
22713
22713
  }
22714
- resolve14({
22714
+ resolve15({
22715
22715
  id: request.id,
22716
22716
  statusCode: res.statusCode ?? 0,
22717
22717
  headers,
@@ -22720,7 +22720,7 @@ async function proxyToLocal(request) {
22720
22720
  });
22721
22721
  });
22722
22722
  req.on("error", (err) => {
22723
- resolve14({
22723
+ resolve15({
22724
22724
  id: request.id,
22725
22725
  statusCode: 0,
22726
22726
  headers: {},
@@ -22802,8 +22802,8 @@ function randomSecret() {
22802
22802
  }
22803
22803
  return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
22804
22804
  }
22805
- async function requestPreviewApi(port, secret, method, path29, body) {
22806
- const url2 = `http://127.0.0.1:${port}${path29}`;
22805
+ async function requestPreviewApi(port, secret, method, path31, body) {
22806
+ const url2 = `http://127.0.0.1:${port}${path31}`;
22807
22807
  const headers = {
22808
22808
  [PREVIEW_SECRET_HEADER]: secret,
22809
22809
  "Content-Type": "application/json"
@@ -22815,7 +22815,7 @@ async function requestPreviewApi(port, secret, method, path29, body) {
22815
22815
  });
22816
22816
  const data = await res.json().catch(() => ({}));
22817
22817
  if (!res.ok) {
22818
- throw new Error(data?.error ?? `Preview API ${method} ${path29}: ${res.status}`);
22818
+ throw new Error(data?.error ?? `Preview API ${method} ${path31}: ${res.status}`);
22819
22819
  }
22820
22820
  return data;
22821
22821
  }
@@ -23027,6 +23027,19 @@ function clearConfigForApi(apiUrl) {
23027
23027
  }
23028
23028
  }
23029
23029
 
23030
+ // src/process-bridge-resilience.ts
23031
+ var installed = false;
23032
+ function installBridgeProcessResilience() {
23033
+ if (installed) return;
23034
+ installed = true;
23035
+ process.on("uncaughtException", (err) => {
23036
+ logImmediate(`[bridge] uncaughtException \u2014 continuing: ${err.stack ?? String(err)}`);
23037
+ });
23038
+ process.on("unhandledRejection", (reason) => {
23039
+ logImmediate(`[bridge] unhandledRejection \u2014 continuing: ${reason instanceof Error ? reason.stack : String(reason)}`);
23040
+ });
23041
+ }
23042
+
23030
23043
  // src/auth/open-browser.ts
23031
23044
  import { execSync } from "node:child_process";
23032
23045
  function isLocalApiUrl(apiUrl) {
@@ -23246,8 +23259,8 @@ function runPendingAuth(options) {
23246
23259
  let hasOpenedBrowser = false;
23247
23260
  let resolved = false;
23248
23261
  let resolveAuth;
23249
- const authPromise = new Promise((resolve14) => {
23250
- resolveAuth = resolve14;
23262
+ const authPromise = new Promise((resolve15) => {
23263
+ resolveAuth = resolve15;
23251
23264
  });
23252
23265
  let reconnectAttempt = 0;
23253
23266
  const signInQuiet = createEmptyReconnectQuietSlot();
@@ -23369,7 +23382,7 @@ function runPendingAuth(options) {
23369
23382
  async function closeBridgeConnection(state, acpManager, devServerManager, log2) {
23370
23383
  const say = log2 ?? logImmediate;
23371
23384
  say("Cleaning up connections\u2026");
23372
- await new Promise((resolve14) => setImmediate(resolve14));
23385
+ await new Promise((resolve15) => setImmediate(resolve15));
23373
23386
  state.closedByUser = true;
23374
23387
  clearReconnectQuietTimer(state.mainQuiet);
23375
23388
  clearReconnectQuietTimer(state.firehoseQuiet);
@@ -23464,8 +23477,8 @@ function pathspec(...paths) {
23464
23477
  cache.set(key, paths);
23465
23478
  return key;
23466
23479
  }
23467
- function isPathSpec(path29) {
23468
- return path29 instanceof String && cache.has(path29);
23480
+ function isPathSpec(path31) {
23481
+ return path31 instanceof String && cache.has(path31);
23469
23482
  }
23470
23483
  function toPaths(pathSpec) {
23471
23484
  return cache.get(pathSpec) || [];
@@ -23554,8 +23567,8 @@ function toLinesWithContent(input = "", trimmed2 = true, separator = "\n") {
23554
23567
  function forEachLineWithContent(input, callback) {
23555
23568
  return toLinesWithContent(input, true).map((line) => callback(line));
23556
23569
  }
23557
- function folderExists(path29) {
23558
- return (0, import_file_exists.exists)(path29, import_file_exists.FOLDER);
23570
+ function folderExists(path31) {
23571
+ return (0, import_file_exists.exists)(path31, import_file_exists.FOLDER);
23559
23572
  }
23560
23573
  function append(target, item) {
23561
23574
  if (Array.isArray(target)) {
@@ -23959,8 +23972,8 @@ function checkIsRepoRootTask() {
23959
23972
  commands,
23960
23973
  format: "utf-8",
23961
23974
  onError,
23962
- parser(path29) {
23963
- return /^\.(git)?$/.test(path29.trim());
23975
+ parser(path31) {
23976
+ return /^\.(git)?$/.test(path31.trim());
23964
23977
  }
23965
23978
  };
23966
23979
  }
@@ -24394,11 +24407,11 @@ function parseGrep(grep) {
24394
24407
  const paths = /* @__PURE__ */ new Set();
24395
24408
  const results = {};
24396
24409
  forEachLineWithContent(grep, (input) => {
24397
- const [path29, line, preview] = input.split(NULL);
24398
- paths.add(path29);
24399
- (results[path29] = results[path29] || []).push({
24410
+ const [path31, line, preview] = input.split(NULL);
24411
+ paths.add(path31);
24412
+ (results[path31] = results[path31] || []).push({
24400
24413
  line: asNumber(line),
24401
- path: path29,
24414
+ path: path31,
24402
24415
  preview
24403
24416
  });
24404
24417
  });
@@ -25163,14 +25176,14 @@ var init_hash_object = __esm2({
25163
25176
  init_task();
25164
25177
  }
25165
25178
  });
25166
- function parseInit(bare, path29, text) {
25179
+ function parseInit(bare, path31, text) {
25167
25180
  const response = String(text).trim();
25168
25181
  let result;
25169
25182
  if (result = initResponseRegex.exec(response)) {
25170
- return new InitSummary(bare, path29, false, result[1]);
25183
+ return new InitSummary(bare, path31, false, result[1]);
25171
25184
  }
25172
25185
  if (result = reInitResponseRegex.exec(response)) {
25173
- return new InitSummary(bare, path29, true, result[1]);
25186
+ return new InitSummary(bare, path31, true, result[1]);
25174
25187
  }
25175
25188
  let gitDir = "";
25176
25189
  const tokens = response.split(" ");
@@ -25181,7 +25194,7 @@ function parseInit(bare, path29, text) {
25181
25194
  break;
25182
25195
  }
25183
25196
  }
25184
- return new InitSummary(bare, path29, /^re/i.test(response), gitDir);
25197
+ return new InitSummary(bare, path31, /^re/i.test(response), gitDir);
25185
25198
  }
25186
25199
  var InitSummary;
25187
25200
  var initResponseRegex;
@@ -25190,9 +25203,9 @@ var init_InitSummary = __esm2({
25190
25203
  "src/lib/responses/InitSummary.ts"() {
25191
25204
  "use strict";
25192
25205
  InitSummary = class {
25193
- constructor(bare, path29, existing, gitDir) {
25206
+ constructor(bare, path31, existing, gitDir) {
25194
25207
  this.bare = bare;
25195
- this.path = path29;
25208
+ this.path = path31;
25196
25209
  this.existing = existing;
25197
25210
  this.gitDir = gitDir;
25198
25211
  }
@@ -25204,7 +25217,7 @@ var init_InitSummary = __esm2({
25204
25217
  function hasBareCommand(command) {
25205
25218
  return command.includes(bareCommand);
25206
25219
  }
25207
- function initTask(bare = false, path29, customArgs) {
25220
+ function initTask(bare = false, path31, customArgs) {
25208
25221
  const commands = ["init", ...customArgs];
25209
25222
  if (bare && !hasBareCommand(commands)) {
25210
25223
  commands.splice(1, 0, bareCommand);
@@ -25213,7 +25226,7 @@ function initTask(bare = false, path29, customArgs) {
25213
25226
  commands,
25214
25227
  format: "utf-8",
25215
25228
  parser(text) {
25216
- return parseInit(commands.includes("--bare"), path29, text);
25229
+ return parseInit(commands.includes("--bare"), path31, text);
25217
25230
  }
25218
25231
  };
25219
25232
  }
@@ -26029,12 +26042,12 @@ var init_FileStatusSummary = __esm2({
26029
26042
  "use strict";
26030
26043
  fromPathRegex = /^(.+)\0(.+)$/;
26031
26044
  FileStatusSummary = class {
26032
- constructor(path29, index, working_dir) {
26033
- this.path = path29;
26045
+ constructor(path31, index, working_dir) {
26046
+ this.path = path31;
26034
26047
  this.index = index;
26035
26048
  this.working_dir = working_dir;
26036
26049
  if (index === "R" || working_dir === "R") {
26037
- const detail = fromPathRegex.exec(path29) || [null, path29, path29];
26050
+ const detail = fromPathRegex.exec(path31) || [null, path31, path31];
26038
26051
  this.from = detail[2] || "";
26039
26052
  this.path = detail[1] || "";
26040
26053
  }
@@ -26065,14 +26078,14 @@ function splitLine(result, lineStr) {
26065
26078
  default:
26066
26079
  return;
26067
26080
  }
26068
- function data(index, workingDir, path29) {
26081
+ function data(index, workingDir, path31) {
26069
26082
  const raw = `${index}${workingDir}`;
26070
26083
  const handler = parsers6.get(raw);
26071
26084
  if (handler) {
26072
- handler(result, path29);
26085
+ handler(result, path31);
26073
26086
  }
26074
26087
  if (raw !== "##" && raw !== "!!") {
26075
- result.files.push(new FileStatusSummary(path29, index, workingDir));
26088
+ result.files.push(new FileStatusSummary(path31, index, workingDir));
26076
26089
  }
26077
26090
  }
26078
26091
  }
@@ -26243,14 +26256,14 @@ var init_status = __esm2({
26243
26256
  ignoredOptions = ["--null", "-z"];
26244
26257
  }
26245
26258
  });
26246
- function versionResponse(major = 0, minor = 0, patch = 0, agent = "", installed = true) {
26259
+ function versionResponse(major = 0, minor = 0, patch = 0, agent = "", installed2 = true) {
26247
26260
  return Object.defineProperty(
26248
26261
  {
26249
26262
  major,
26250
26263
  minor,
26251
26264
  patch,
26252
26265
  agent,
26253
- installed
26266
+ installed: installed2
26254
26267
  },
26255
26268
  "toString",
26256
26269
  {
@@ -26381,9 +26394,9 @@ var init_simple_git_api = __esm2({
26381
26394
  next
26382
26395
  );
26383
26396
  }
26384
- hashObject(path29, write) {
26397
+ hashObject(path31, write) {
26385
26398
  return this._runTask(
26386
- hashObjectTask(path29, write === true),
26399
+ hashObjectTask(path31, write === true),
26387
26400
  trailingFunctionArgument(arguments)
26388
26401
  );
26389
26402
  }
@@ -26736,8 +26749,8 @@ var init_branch = __esm2({
26736
26749
  }
26737
26750
  });
26738
26751
  function toPath(input) {
26739
- const path29 = input.trim().replace(/^["']|["']$/g, "");
26740
- return path29 && normalize2(path29);
26752
+ const path31 = input.trim().replace(/^["']|["']$/g, "");
26753
+ return path31 && normalize2(path31);
26741
26754
  }
26742
26755
  var parseCheckIgnore;
26743
26756
  var init_CheckIgnore = __esm2({
@@ -27051,8 +27064,8 @@ __export2(sub_module_exports, {
27051
27064
  subModuleTask: () => subModuleTask,
27052
27065
  updateSubModuleTask: () => updateSubModuleTask
27053
27066
  });
27054
- function addSubModuleTask(repo, path29) {
27055
- return subModuleTask(["add", repo, path29]);
27067
+ function addSubModuleTask(repo, path31) {
27068
+ return subModuleTask(["add", repo, path31]);
27056
27069
  }
27057
27070
  function initSubModuleTask(customArgs) {
27058
27071
  return subModuleTask(["init", ...customArgs]);
@@ -27385,8 +27398,8 @@ var require_git = __commonJS2({
27385
27398
  }
27386
27399
  return this._runTask(straightThroughStringTask2(command, this._trimmed), next);
27387
27400
  };
27388
- Git2.prototype.submoduleAdd = function(repo, path29, then) {
27389
- return this._runTask(addSubModuleTask2(repo, path29), trailingFunctionArgument2(arguments));
27401
+ Git2.prototype.submoduleAdd = function(repo, path31, then) {
27402
+ return this._runTask(addSubModuleTask2(repo, path31), trailingFunctionArgument2(arguments));
27390
27403
  };
27391
27404
  Git2.prototype.submoduleUpdate = function(args, then) {
27392
27405
  return this._runTask(
@@ -28475,7 +28488,7 @@ async function createCursorAcpClient(options) {
28475
28488
  });
28476
28489
  const stderrCapture = createStderrCapture(child);
28477
28490
  child.stderr?.on("data", (chunk) => stderrCapture.append(chunk));
28478
- return new Promise((resolve14, reject) => {
28491
+ return new Promise((resolve15, reject) => {
28479
28492
  child.on("error", (err) => {
28480
28493
  child.kill();
28481
28494
  reject(new Error(formatSpawnError2(err, command[0])));
@@ -28662,7 +28675,7 @@ async function createCursorAcpClient(options) {
28662
28675
  const newResult = await send("session/new", { cwd, mcpServers: [] });
28663
28676
  const sessionId = newResult?.sessionId ?? "";
28664
28677
  if (!sessionId) throw new Error("Cursor ACP session/new did not return sessionId");
28665
- resolve14({
28678
+ resolve15({
28666
28679
  sessionId,
28667
28680
  async sendPrompt(prompt, _options) {
28668
28681
  promptOutputBuffer = "";
@@ -29720,7 +29733,7 @@ async function createAcpManager(options) {
29720
29733
  }
29721
29734
 
29722
29735
  // src/worktrees/session-worktree-manager.ts
29723
- import * as path13 from "node:path";
29736
+ import * as path15 from "node:path";
29724
29737
  import os3 from "node:os";
29725
29738
 
29726
29739
  // src/worktrees/prepare-new-session-worktrees.ts
@@ -29887,7 +29900,7 @@ async function removeSessionWorktrees(paths, log2) {
29887
29900
  }
29888
29901
  }
29889
29902
 
29890
- // src/git/working-tree-status.ts
29903
+ // src/git/working-directory/status/working-tree-status.ts
29891
29904
  async function tryConfigGet(g, key) {
29892
29905
  try {
29893
29906
  const out = await g.raw(["config", "--get", key]);
@@ -29897,11 +29910,24 @@ async function tryConfigGet(g, key) {
29897
29910
  return null;
29898
29911
  }
29899
29912
  }
29913
+ async function revParseSafe(g, ref) {
29914
+ try {
29915
+ const v = (await g.raw(["rev-parse", ref])).trim();
29916
+ return v || null;
29917
+ } catch {
29918
+ return null;
29919
+ }
29920
+ }
29900
29921
  async function resolveRemoteTrackingRefForAhead(g) {
29901
29922
  try {
29902
- await g.raw(["rev-parse", "--verify", "@{u}"]);
29903
- return "@{u}";
29923
+ await g.raw(["rev-parse", "--verify", "HEAD@{upstream}"]);
29924
+ return "HEAD@{upstream}";
29904
29925
  } catch {
29926
+ try {
29927
+ await g.raw(["rev-parse", "--verify", "@{u}"]);
29928
+ return "@{u}";
29929
+ } catch {
29930
+ }
29905
29931
  }
29906
29932
  const branch = (await g.raw(["rev-parse", "--abbrev-ref", "HEAD"])).trim();
29907
29933
  if (!branch || branch === "HEAD") return null;
@@ -29924,16 +29950,76 @@ async function resolveRemoteTrackingRefForAhead(g) {
29924
29950
  return null;
29925
29951
  }
29926
29952
  }
29953
+ async function remoteForCurrentBranch(g) {
29954
+ const branch = (await g.raw(["rev-parse", "--abbrev-ref", "HEAD"])).trim();
29955
+ if (!branch || branch === "HEAD") return "origin";
29956
+ return await tryConfigGet(g, `branch.${branch}.remote`) ?? "origin";
29957
+ }
29958
+ async function resolveDefaultRemoteBranchRef(g, remote) {
29959
+ const headSym = `refs/remotes/${remote}/HEAD`;
29960
+ try {
29961
+ const resolved = (await g.raw(["symbolic-ref", "-q", "--verify", headSym])).trim();
29962
+ if (resolved.startsWith("refs/remotes/")) return resolved;
29963
+ } catch {
29964
+ }
29965
+ for (const name of ["main", "master", "trunk", "develop"]) {
29966
+ const r = `refs/remotes/${remote}/${name}`;
29967
+ try {
29968
+ await g.raw(["rev-parse", "--verify", r]);
29969
+ return r;
29970
+ } catch {
29971
+ }
29972
+ }
29973
+ return null;
29974
+ }
29975
+ async function resolveBaseShaForUnpushedCommits(g) {
29976
+ const trackingRef = await resolveRemoteTrackingRefForAhead(g);
29977
+ if (trackingRef) {
29978
+ const sha = await revParseSafe(g, trackingRef);
29979
+ if (sha) return sha;
29980
+ }
29981
+ const remote = await remoteForCurrentBranch(g);
29982
+ const defaultRef = await resolveDefaultRemoteBranchRef(g, remote);
29983
+ if (!defaultRef) return null;
29984
+ return revParseSafe(g, defaultRef);
29985
+ }
29986
+ function parseLogShaDateSubjectLines(raw) {
29987
+ const out = [];
29988
+ for (const line of String(raw).split("\n")) {
29989
+ const l = line.trimEnd();
29990
+ if (!l.trim()) continue;
29991
+ const parts = l.split(" ");
29992
+ if (parts.length < 3) continue;
29993
+ const sha = parts[0].trim();
29994
+ const committedAt = parts[1].trim();
29995
+ const subject = parts.slice(2).join(" ").trim();
29996
+ if (!/^[0-9a-f]{7,40}$/i.test(sha)) continue;
29997
+ out.push({ sha, shortSha: sha.slice(0, 7), subject, committedAt });
29998
+ }
29999
+ return out;
30000
+ }
30001
+ async function gitLogNotReachableFromBase(g, baseSha, headSha) {
30002
+ if (baseSha === headSha) return [];
30003
+ try {
30004
+ const logOut = await g.raw(["log", "--format=%H %cI %s", `${baseSha}..${headSha}`]);
30005
+ return parseLogShaDateSubjectLines(logOut);
30006
+ } catch {
30007
+ return [];
30008
+ }
30009
+ }
29927
30010
  async function commitsAheadOfRemoteTracking(repoDir) {
29928
30011
  const g = simpleGit(repoDir);
29929
- const trackingRef = await resolveRemoteTrackingRefForAhead(g);
29930
- if (!trackingRef) return 0;
29931
- const localSha = (await g.raw(["rev-parse", "HEAD"])).trim();
29932
- const remoteSha = (await g.raw(["rev-parse", trackingRef])).trim();
29933
- if (localSha === remoteSha) return 0;
29934
- const out = await g.raw(["rev-list", "--count", `${trackingRef}..HEAD`]);
29935
- const n = parseInt(String(out).trim(), 10);
29936
- return Number.isNaN(n) ? 0 : n;
30012
+ const headSha = await revParseSafe(g, "HEAD");
30013
+ if (!headSha) return 0;
30014
+ const baseSha = await resolveBaseShaForUnpushedCommits(g);
30015
+ if (!baseSha || baseSha === headSha) return 0;
30016
+ try {
30017
+ const out = await g.raw(["rev-list", "--count", `${baseSha}..${headSha}`]);
30018
+ const n = parseInt(String(out).trim(), 10);
30019
+ return Number.isNaN(n) ? 0 : n;
30020
+ } catch {
30021
+ return 0;
30022
+ }
29937
30023
  }
29938
30024
  async function getRepoWorkingTreeStatus(repoDir) {
29939
30025
  const g = simpleGit(repoDir);
@@ -29942,6 +30028,14 @@ async function getRepoWorkingTreeStatus(repoDir) {
29942
30028
  const ahead = await commitsAheadOfRemoteTracking(repoDir);
29943
30029
  return { hasUncommittedChanges, hasUnpushedCommits: ahead > 0 };
29944
30030
  }
30031
+ async function listUnpushedCommits(repoDir) {
30032
+ const g = simpleGit(repoDir);
30033
+ const headSha = await revParseSafe(g, "HEAD");
30034
+ if (!headSha) return [];
30035
+ const baseSha = await resolveBaseShaForUnpushedCommits(g);
30036
+ if (!baseSha) return [];
30037
+ return gitLogNotReachableFromBase(g, baseSha, headSha);
30038
+ }
29945
30039
  async function aggregateSessionPathsWorkingTreeStatus(paths) {
29946
30040
  let hasUncommittedChanges = false;
29947
30041
  let hasUnpushedCommits = false;
@@ -29961,6 +30055,477 @@ async function pushAheadOfUpstreamForPaths(paths) {
29961
30055
  }
29962
30056
  }
29963
30057
 
30058
+ // src/git/working-directory/changes/types.ts
30059
+ var MAX_PATCH_CHARS = 35e4;
30060
+
30061
+ // src/git/working-directory/changes/repo-format.ts
30062
+ function posixJoinDirFile(dir, file2) {
30063
+ const d = dir === "." || dir === "" ? "" : dir.replace(/\\/g, "/").replace(/\/+$/, "");
30064
+ const f = file2.replace(/\\/g, "/").replace(/^\/+/, "");
30065
+ return d ? `${d}/${f}` : f;
30066
+ }
30067
+ function formatRepoShortTitle(remoteUrl, repoRelPath) {
30068
+ const u = remoteUrl.trim();
30069
+ if (u) {
30070
+ try {
30071
+ if (u.startsWith("git@")) {
30072
+ const colon = u.indexOf(":");
30073
+ if (colon > 0) {
30074
+ const pathPart = u.slice(colon + 1).replace(/\.git$/i, "").replace(/\/+$/, "");
30075
+ if (pathPart.includes("/")) return pathPart;
30076
+ }
30077
+ } else {
30078
+ const parsed = new URL(u);
30079
+ const p = parsed.pathname.replace(/^\//, "").replace(/\.git$/i, "");
30080
+ const parts = p.split("/").filter(Boolean);
30081
+ if (parts.length >= 2) {
30082
+ return `${parts[parts.length - 2]}/${parts[parts.length - 1]}`;
30083
+ }
30084
+ if (parts.length === 1) return parts[0];
30085
+ }
30086
+ } catch {
30087
+ }
30088
+ }
30089
+ if (repoRelPath && repoRelPath !== ".") {
30090
+ const segments = repoRelPath.split("/").filter(Boolean);
30091
+ const last2 = segments[segments.length - 1];
30092
+ if (last2) return last2;
30093
+ }
30094
+ return "Repository";
30095
+ }
30096
+ function formatRemoteDisplayLabel(remoteUrl) {
30097
+ const u = remoteUrl.trim();
30098
+ if (!u) return "";
30099
+ let hostPath = u;
30100
+ try {
30101
+ if (u.startsWith("git@")) {
30102
+ const rest = u.slice("git@".length);
30103
+ const slash = rest.indexOf(":");
30104
+ if (slash > 0) hostPath = `${rest.slice(0, slash)}/${rest.slice(slash + 1)}`;
30105
+ } else {
30106
+ const parsed = new URL(u);
30107
+ hostPath = `${parsed.hostname}${parsed.pathname}`.replace(/\/\.git$/i, "").replace(/\.git$/i, "");
30108
+ }
30109
+ } catch {
30110
+ hostPath = u.replace(/^https?:\/\//i, "").replace(/\.git$/i, "");
30111
+ }
30112
+ return `origin \xB7 ${hostPath}`;
30113
+ }
30114
+
30115
+ // src/git/working-directory/changes/get-working-tree-change-repo-details.ts
30116
+ import * as path14 from "node:path";
30117
+
30118
+ // src/git/working-directory/changes/parse-git-status.ts
30119
+ function parseNameStatusLines(lines) {
30120
+ const m = /* @__PURE__ */ new Map();
30121
+ for (const line of lines) {
30122
+ if (!line.trim()) continue;
30123
+ const tabParts = line.split(" ");
30124
+ if (tabParts.length < 2) continue;
30125
+ const status = tabParts[0].trim();
30126
+ const code = status[0];
30127
+ if (code === "A") {
30128
+ m.set(tabParts[tabParts.length - 1], "added");
30129
+ } else if (code === "D") {
30130
+ m.set(tabParts[tabParts.length - 1], "removed");
30131
+ } else if (code === "R" || code === "C") {
30132
+ if (tabParts.length >= 3) m.set(tabParts[tabParts.length - 1], "modified");
30133
+ } else if (code === "M" || code === "U" || code === "T") {
30134
+ m.set(tabParts[tabParts.length - 1], "modified");
30135
+ }
30136
+ }
30137
+ return m;
30138
+ }
30139
+ function parseNumstatFirstLine(line) {
30140
+ const parts = line.split(" ");
30141
+ if (parts.length < 3) return null;
30142
+ const [a, d] = parts;
30143
+ const additions = a === "-" ? 0 : parseInt(String(a), 10) || 0;
30144
+ const deletions = d === "-" ? 0 : parseInt(String(d), 10) || 0;
30145
+ return { additions, deletions };
30146
+ }
30147
+ function parseNumstat(lines) {
30148
+ const m = /* @__PURE__ */ new Map();
30149
+ for (const line of lines) {
30150
+ if (!line.trim()) continue;
30151
+ const parts = line.split(" ");
30152
+ if (parts.length < 3) continue;
30153
+ const [a, d, p] = parts;
30154
+ const additions = a === "-" ? 0 : parseInt(String(a), 10) || 0;
30155
+ const deletions = d === "-" ? 0 : parseInt(String(d), 10) || 0;
30156
+ m.set(p, { additions, deletions });
30157
+ }
30158
+ return m;
30159
+ }
30160
+ async function numstatFromGitNoIndex(g, pathInRepo) {
30161
+ const devNull = process.platform === "win32" ? "NUL" : "/dev/null";
30162
+ try {
30163
+ const out = await g.raw(["diff", "--numstat", "--no-index", "--", devNull, pathInRepo]);
30164
+ const first2 = String(out).split("\n").find((l) => l.trim()) ?? "";
30165
+ return parseNumstatFirstLine(first2);
30166
+ } catch {
30167
+ return null;
30168
+ }
30169
+ }
30170
+
30171
+ // src/git/working-directory/changes/patch-truncate.ts
30172
+ function truncatePatch(s) {
30173
+ if (s.length <= MAX_PATCH_CHARS) return s;
30174
+ return `${s.slice(0, MAX_PATCH_CHARS)}
30175
+
30176
+ \u2026 (diff truncated)`;
30177
+ }
30178
+
30179
+ // src/git/working-directory/changes/list-changed-files-for-commit.ts
30180
+ var EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
30181
+ async function parentForCommitDiff(g, sha) {
30182
+ try {
30183
+ return (await g.raw(["rev-parse", `${sha}^1`])).trim();
30184
+ } catch {
30185
+ try {
30186
+ return (await g.raw(["rev-parse", `${sha}^`])).trim();
30187
+ } catch {
30188
+ return EMPTY_TREE;
30189
+ }
30190
+ }
30191
+ }
30192
+ async function listChangedFilesForCommit(repoGitCwd, repoRelPath, commitSha) {
30193
+ const g = simpleGit(repoGitCwd);
30194
+ const parent = await parentForCommitDiff(g, commitSha);
30195
+ const range = `${parent}..${commitSha}`;
30196
+ const [nameStatusRaw, numstatRaw] = await Promise.all([
30197
+ g.raw(["diff", "--name-status", range]).catch(() => ""),
30198
+ g.raw(["diff", "--numstat", range]).catch(() => "")
30199
+ ]);
30200
+ const kindByPath = parseNameStatusLines(String(nameStatusRaw).split("\n"));
30201
+ const numByPath = parseNumstat(String(numstatRaw).split("\n"));
30202
+ const paths = new Set([...kindByPath.keys(), ...numByPath.keys()].filter(Boolean));
30203
+ const rows = [];
30204
+ const normRel = repoRelPath === "." || repoRelPath === "" ? "." : repoRelPath;
30205
+ for (const pathInRepo of paths) {
30206
+ const relLauncher = posixJoinDirFile(normRel, pathInRepo.replace(/\\/g, "/"));
30207
+ const nums = numByPath.get(pathInRepo);
30208
+ let additions = nums?.additions ?? 0;
30209
+ let deletions = nums?.deletions ?? 0;
30210
+ let change = kindByPath.get(pathInRepo) ?? "modified";
30211
+ if (!kindByPath.has(pathInRepo) && nums) {
30212
+ if (additions > 0 && deletions === 0) change = "added";
30213
+ else if (deletions > 0 && additions === 0) change = "removed";
30214
+ else change = "modified";
30215
+ }
30216
+ rows.push({ pathRelLauncher: relLauncher, additions, deletions, change });
30217
+ }
30218
+ for (const row of rows) {
30219
+ let pathInRepo;
30220
+ if (normRel === ".") {
30221
+ pathInRepo = row.pathRelLauncher;
30222
+ } else if (row.pathRelLauncher.startsWith(`${normRel}/`)) {
30223
+ pathInRepo = row.pathRelLauncher.slice(normRel.length + 1);
30224
+ } else {
30225
+ pathInRepo = row.pathRelLauncher;
30226
+ }
30227
+ const raw = await g.raw(["diff", "-U20000", range, "--", pathInRepo]).catch(() => "");
30228
+ const t = String(raw).trim();
30229
+ row.patchContent = t ? truncatePatch(t) : void 0;
30230
+ }
30231
+ rows.sort((a, b) => a.pathRelLauncher.localeCompare(b.pathRelLauncher));
30232
+ return rows;
30233
+ }
30234
+
30235
+ // src/git/working-directory/changes/list-changed-files-for-repo.ts
30236
+ import * as fs11 from "node:fs";
30237
+ import * as path13 from "node:path";
30238
+
30239
+ // src/git/working-directory/changes/count-lines.ts
30240
+ import { createReadStream } from "node:fs";
30241
+ import * as readline2 from "node:readline";
30242
+ async function countTextFileLines(absFile) {
30243
+ let bytes = 0;
30244
+ const maxBytes = 512e3;
30245
+ let lines = 0;
30246
+ const stream = createReadStream(absFile, { encoding: "utf8" });
30247
+ const rl = readline2.createInterface({ input: stream, crlfDelay: Infinity });
30248
+ for await (const _line of rl) {
30249
+ lines += 1;
30250
+ bytes += Buffer.byteLength(String(_line), "utf8") + 1;
30251
+ if (bytes > maxBytes) {
30252
+ rl.close();
30253
+ stream.destroy();
30254
+ return lines;
30255
+ }
30256
+ }
30257
+ return lines;
30258
+ }
30259
+
30260
+ // src/git/working-directory/changes/hydrate-patch.ts
30261
+ import * as fs10 from "node:fs";
30262
+ var UNIFIED_HUNK_HEADER_RE = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
30263
+ var MAX_HYDRATE_LINES_PER_GAP = 8e3;
30264
+ var MAX_HYDRATE_LINES_PER_FILE = 8e4;
30265
+ async function readGitBlobLines(repoCwd, pathInRepo) {
30266
+ try {
30267
+ const rel = pathInRepo.replace(/\\/g, "/");
30268
+ const raw = await simpleGit(repoCwd).show([`HEAD:${rel}`]);
30269
+ return String(raw).split(/\r?\n/);
30270
+ } catch {
30271
+ return null;
30272
+ }
30273
+ }
30274
+ async function readWorktreeFileLines(abs) {
30275
+ try {
30276
+ const raw = await fs10.promises.readFile(abs, "utf8");
30277
+ return raw.split(/\r?\n/);
30278
+ } catch {
30279
+ return null;
30280
+ }
30281
+ }
30282
+ async function hydrateUnifiedPatchWithFileContext(patch, absFile, repoGitCwd, pathInRepo, change) {
30283
+ if (!patch.trim() || patch.includes("Binary files")) return patch;
30284
+ const all = patch.split("\n");
30285
+ const out = [];
30286
+ let prevOldEnd = 0;
30287
+ let prevNewEnd = 0;
30288
+ let injectedTotal = 0;
30289
+ let i = 0;
30290
+ let blobCache;
30291
+ let diskCache;
30292
+ const blobLines = async () => {
30293
+ if (blobCache !== void 0) return blobCache;
30294
+ blobCache = await readGitBlobLines(repoGitCwd, pathInRepo);
30295
+ return blobCache;
30296
+ };
30297
+ const diskLines = async () => {
30298
+ if (diskCache !== void 0) return diskCache;
30299
+ diskCache = await readWorktreeFileLines(absFile);
30300
+ return diskCache;
30301
+ };
30302
+ while (i < all.length) {
30303
+ const line = all[i];
30304
+ const hm = line.match(UNIFIED_HUNK_HEADER_RE);
30305
+ if (!hm) {
30306
+ out.push(line);
30307
+ i++;
30308
+ continue;
30309
+ }
30310
+ const oldStart = parseInt(hm[1], 10) || 0;
30311
+ const newStart = parseInt(hm[3], 10) || 0;
30312
+ const gapOldStart = prevOldEnd + 1;
30313
+ const gapOldEnd = oldStart - 1;
30314
+ const gapNewStart = prevNewEnd + 1;
30315
+ const gapNewEnd = newStart - 1;
30316
+ if (injectedTotal < MAX_HYDRATE_LINES_PER_FILE) {
30317
+ let inject = null;
30318
+ if (gapNewEnd >= gapNewStart && change !== "removed") {
30319
+ const nNew = gapNewEnd - gapNewStart + 1;
30320
+ if (gapOldEnd < gapOldStart || gapOldEnd - gapOldStart + 1 === nNew) {
30321
+ const cap = Math.min(nNew, MAX_HYDRATE_LINES_PER_GAP, MAX_HYDRATE_LINES_PER_FILE - injectedTotal);
30322
+ const dl = await diskLines();
30323
+ if (dl && cap > 0) {
30324
+ inject = dl.slice(gapNewStart - 1, gapNewStart - 1 + cap);
30325
+ }
30326
+ }
30327
+ } else if (gapOldEnd >= gapOldStart && change === "removed") {
30328
+ const nOld = gapOldEnd - gapOldStart + 1;
30329
+ const cap = Math.min(nOld, MAX_HYDRATE_LINES_PER_GAP, MAX_HYDRATE_LINES_PER_FILE - injectedTotal);
30330
+ const bl = await blobLines();
30331
+ if (bl && cap > 0) {
30332
+ inject = bl.slice(gapOldStart - 1, gapOldStart - 1 + cap);
30333
+ }
30334
+ }
30335
+ if (inject && inject.length > 0) {
30336
+ for (const t of inject) {
30337
+ out.push(` ${t}`);
30338
+ injectedTotal++;
30339
+ }
30340
+ }
30341
+ }
30342
+ out.push(line);
30343
+ i++;
30344
+ let oldConsumed = 0;
30345
+ let newConsumed = 0;
30346
+ while (i < all.length) {
30347
+ const bl = all[i];
30348
+ if (UNIFIED_HUNK_HEADER_RE.test(bl)) break;
30349
+ out.push(bl);
30350
+ i++;
30351
+ if (bl.startsWith("\\")) continue;
30352
+ const ch = bl[0];
30353
+ if (ch === " ") {
30354
+ oldConsumed++;
30355
+ newConsumed++;
30356
+ } else if (ch === "-") {
30357
+ oldConsumed++;
30358
+ } else if (ch === "+") {
30359
+ newConsumed++;
30360
+ }
30361
+ }
30362
+ if (oldStart > 0) {
30363
+ prevOldEnd = oldStart + oldConsumed - 1;
30364
+ } else {
30365
+ prevOldEnd = 0;
30366
+ }
30367
+ if (newStart > 0) {
30368
+ prevNewEnd = newStart + newConsumed - 1;
30369
+ } else {
30370
+ prevNewEnd = 0;
30371
+ }
30372
+ }
30373
+ return truncatePatch(out.join("\n"));
30374
+ }
30375
+
30376
+ // src/git/working-directory/changes/unified-diff-for-file.ts
30377
+ async function unifiedDiffForFile(repoCwd, pathInRepo, change) {
30378
+ const g = simpleGit(repoCwd);
30379
+ try {
30380
+ let raw;
30381
+ if (change === "added") {
30382
+ const devNull = process.platform === "win32" ? "NUL" : "/dev/null";
30383
+ raw = await g.raw(["diff", "--no-index", "--", devNull, pathInRepo]);
30384
+ } else {
30385
+ raw = await g.raw(["diff", "HEAD", "--", pathInRepo]);
30386
+ }
30387
+ const t = String(raw).trim();
30388
+ if (!t) return void 0;
30389
+ return truncatePatch(t);
30390
+ } catch {
30391
+ return void 0;
30392
+ }
30393
+ }
30394
+
30395
+ // src/git/working-directory/changes/list-changed-files-for-repo.ts
30396
+ async function listChangedFilesForRepo(repoGitCwd, repoRelPath) {
30397
+ const g = simpleGit(repoGitCwd);
30398
+ const [nameStatusRaw, numstatRaw, untrackedRaw] = await Promise.all([
30399
+ g.raw(["diff", "--name-status", "HEAD"]).catch(() => ""),
30400
+ g.raw(["diff", "HEAD", "--numstat"]).catch(() => ""),
30401
+ g.raw(["ls-files", "--others", "--exclude-standard"]).catch(() => "")
30402
+ ]);
30403
+ const kindByPath = parseNameStatusLines(String(nameStatusRaw).split("\n"));
30404
+ const numByPath = parseNumstat(String(numstatRaw).split("\n"));
30405
+ const paths = /* @__PURE__ */ new Set([...kindByPath.keys(), ...numByPath.keys()]);
30406
+ const untracked = String(untrackedRaw).split("\n").map((s) => s.trim()).filter(Boolean);
30407
+ for (const p of untracked) paths.add(p);
30408
+ const rows = [];
30409
+ for (const pathInRepo of paths) {
30410
+ const relLauncher = posixJoinDirFile(repoRelPath, pathInRepo.replace(/\\/g, "/"));
30411
+ const abs = path13.join(repoGitCwd, pathInRepo);
30412
+ const nums = numByPath.get(pathInRepo);
30413
+ let additions = nums?.additions ?? 0;
30414
+ let deletions = nums?.deletions ?? 0;
30415
+ let change = kindByPath.get(pathInRepo) ?? "modified";
30416
+ if (untracked.includes(pathInRepo) && !kindByPath.has(pathInRepo)) {
30417
+ change = "added";
30418
+ const fromGit = await numstatFromGitNoIndex(g, pathInRepo);
30419
+ if (fromGit) {
30420
+ additions = fromGit.additions;
30421
+ deletions = fromGit.deletions;
30422
+ } else {
30423
+ try {
30424
+ const st = await fs11.promises.stat(abs);
30425
+ if (st.isFile()) additions = await countTextFileLines(abs);
30426
+ else additions = 0;
30427
+ } catch {
30428
+ additions = 0;
30429
+ }
30430
+ deletions = 0;
30431
+ }
30432
+ }
30433
+ if (!kindByPath.has(pathInRepo) && nums) {
30434
+ if (additions > 0 && deletions === 0) change = "added";
30435
+ else if (deletions > 0 && additions === 0) change = "removed";
30436
+ else change = "modified";
30437
+ }
30438
+ rows.push({ pathRelLauncher: relLauncher, additions, deletions, change });
30439
+ }
30440
+ const normRel = repoRelPath === "." || repoRelPath === "" ? "." : repoRelPath;
30441
+ for (const row of rows) {
30442
+ let pathInRepo;
30443
+ if (normRel === ".") {
30444
+ pathInRepo = row.pathRelLauncher;
30445
+ } else if (row.pathRelLauncher.startsWith(`${normRel}/`)) {
30446
+ pathInRepo = row.pathRelLauncher.slice(normRel.length + 1);
30447
+ } else {
30448
+ pathInRepo = row.pathRelLauncher;
30449
+ }
30450
+ const absFile = path13.join(repoGitCwd, pathInRepo);
30451
+ let patch = await unifiedDiffForFile(repoGitCwd, pathInRepo, row.change);
30452
+ if (patch) {
30453
+ patch = await hydrateUnifiedPatchWithFileContext(patch, absFile, repoGitCwd, pathInRepo, row.change);
30454
+ }
30455
+ row.patchContent = patch;
30456
+ }
30457
+ return rows;
30458
+ }
30459
+
30460
+ // src/git/working-directory/changes/get-working-tree-change-repo-details.ts
30461
+ function normRepoRel(p) {
30462
+ const x = p.replace(/\\/g, "/").trim();
30463
+ return x === "" ? "." : x;
30464
+ }
30465
+ async function getWorkingTreeChangeRepoDetails(options) {
30466
+ const launcher = path14.resolve(getBridgeWorkspaceDirectory());
30467
+ const mirror = options.agentMirrorRootAbs ? path14.resolve(options.agentMirrorRootAbs) : null;
30468
+ const out = [];
30469
+ const filter = options.repoFilterRelPath != null ? normRepoRel(options.repoFilterRelPath) : null;
30470
+ const basisInput = options.basis ?? { kind: "working" };
30471
+ if (basisInput.kind === "commit" && !filter) {
30472
+ throw new Error("repoFilterRelPath is required for commit changes");
30473
+ }
30474
+ if (basisInput.kind === "commit" && !basisInput.sha.trim()) {
30475
+ throw new Error("commit sha is required for commit changes");
30476
+ }
30477
+ const basis = filter == null && basisInput.kind === "commit" ? { kind: "working" } : basisInput;
30478
+ for (const target of options.commitTargetAbsDirs) {
30479
+ const t = path14.resolve(target);
30480
+ if (!await isGitRepoDirectory(t)) continue;
30481
+ const g = simpleGit(t);
30482
+ let branch = "HEAD";
30483
+ try {
30484
+ branch = (await g.raw(["rev-parse", "--abbrev-ref", "HEAD"])).trim() || "HEAD";
30485
+ } catch {
30486
+ branch = "HEAD";
30487
+ }
30488
+ const remoteUrl = await getRemoteOriginUrl(t);
30489
+ const remoteDisplay = formatRemoteDisplayLabel(remoteUrl);
30490
+ let repoRelPath;
30491
+ if (mirror) {
30492
+ const relNorm = path14.relative(mirror, path14.dirname(t));
30493
+ repoRelPath = relNorm === "" ? "." : relNorm.replace(/\\/g, "/");
30494
+ } else {
30495
+ let top = t;
30496
+ try {
30497
+ top = (await g.raw(["rev-parse", "--show-toplevel"])).trim();
30498
+ } catch {
30499
+ top = t;
30500
+ }
30501
+ const rel = path14.relative(launcher, path14.resolve(top)).replace(/\\/g, "/") || ".";
30502
+ repoRelPath = rel.startsWith("..") ? path14.basename(path14.resolve(top)) : rel;
30503
+ }
30504
+ const norm = normRepoRel(repoRelPath === "" ? "." : repoRelPath);
30505
+ if (filter && norm !== filter) continue;
30506
+ const repoDisplayName = formatRepoShortTitle(remoteUrl, norm === "." ? "." : norm);
30507
+ const relForList = norm === "." ? "." : norm;
30508
+ const files = basis.kind === "commit" ? await listChangedFilesForCommit(t, relForList, basis.sha.trim()) : await listChangedFilesForRepo(t, relForList);
30509
+ const st = await g.status();
30510
+ const hasUncommittedChanges = (st.files?.length ?? 0) > 0;
30511
+ const unpushedCommits = await listUnpushedCommits(t);
30512
+ out.push({
30513
+ repoRelPath: norm,
30514
+ repoDisplayName,
30515
+ branch,
30516
+ remoteUrl,
30517
+ remoteDisplay,
30518
+ files,
30519
+ hasUncommittedChanges,
30520
+ unpushedCommits,
30521
+ changesView: basis.kind === "commit" ? "commit" : "working",
30522
+ changesCommitSha: basis.kind === "commit" ? basis.sha.trim() : null
30523
+ });
30524
+ if (filter) return out;
30525
+ }
30526
+ return out;
30527
+ }
30528
+
29964
30529
  // src/git/commit-and-push.ts
29965
30530
  async function gitCommitAllIfDirty(repoDir, message, options) {
29966
30531
  const g = simpleGit(repoDir);
@@ -30026,7 +30591,7 @@ var SessionWorktreeManager = class {
30026
30591
  }
30027
30592
  if (!opts.isNewSession) {
30028
30593
  const agentCwd = this.sessionAgentCwd.get(sessionId);
30029
- if (agentCwd) return path13.resolve(agentCwd);
30594
+ if (agentCwd) return path15.resolve(agentCwd);
30030
30595
  return void 0;
30031
30596
  }
30032
30597
  const prep = await prepareNewSessionWorktrees({
@@ -30039,7 +30604,7 @@ var SessionWorktreeManager = class {
30039
30604
  if (!prep) return void 0;
30040
30605
  this.sessionPaths.set(sessionId, prep.worktreePaths);
30041
30606
  this.sessionAgentCwd.set(sessionId, prep.agentCwd);
30042
- return path13.resolve(prep.agentCwd);
30607
+ return path15.resolve(prep.agentCwd);
30043
30608
  }
30044
30609
  async renameSessionBranch(sessionId, newBranch) {
30045
30610
  const paths = this.sessionPaths.get(sessionId);
@@ -30060,7 +30625,7 @@ var SessionWorktreeManager = class {
30060
30625
  getAgentCwdForSession(sessionId) {
30061
30626
  if (!sessionId) return null;
30062
30627
  const c = this.sessionAgentCwd.get(sessionId);
30063
- return c ? path13.resolve(c) : null;
30628
+ return c ? path15.resolve(c) : null;
30064
30629
  }
30065
30630
  async removeSessionWorktrees(sessionId) {
30066
30631
  const paths = this.sessionPaths.get(sessionId);
@@ -30086,6 +30651,17 @@ var SessionWorktreeManager = class {
30086
30651
  async getSessionWorkingTreeStatus(sessionId) {
30087
30652
  return aggregateSessionPathsWorkingTreeStatus(this.resolveCommitTargets(sessionId));
30088
30653
  }
30654
+ /** Per-repo changed files vs HEAD (or a single commit vs parent) for the same git roots used for commit/push. */
30655
+ async getSessionWorkingTreeChangeDetails(sessionId, opts) {
30656
+ const targets = this.resolveCommitTargets(sessionId);
30657
+ const mirror = this.getAgentCwdForSession(sessionId);
30658
+ return getWorkingTreeChangeRepoDetails({
30659
+ commitTargetAbsDirs: targets,
30660
+ agentMirrorRootAbs: mirror,
30661
+ repoFilterRelPath: opts?.repoRelPath?.trim() ? opts.repoRelPath.trim() : null,
30662
+ basis: opts?.basis
30663
+ });
30664
+ }
30089
30665
  async pushSessionUpstream(sessionId) {
30090
30666
  try {
30091
30667
  await pushAheadOfUpstreamForPaths(this.resolveCommitTargets(sessionId));
@@ -30097,30 +30673,30 @@ var SessionWorktreeManager = class {
30097
30673
  }
30098
30674
  };
30099
30675
  function defaultWorktreesRootAbs() {
30100
- return path13.join(os3.homedir(), ".buildautomaton", "worktrees");
30676
+ return path15.join(os3.homedir(), ".buildautomaton", "worktrees");
30101
30677
  }
30102
30678
 
30103
30679
  // src/files/watch-file-index.ts
30104
30680
  import { watch } from "node:fs";
30105
- import path20 from "node:path";
30681
+ import path22 from "node:path";
30106
30682
 
30107
30683
  // src/files/index/build-file-index.ts
30108
- import path17 from "node:path";
30684
+ import path19 from "node:path";
30109
30685
 
30110
30686
  // src/runtime/yield-to-event-loop.ts
30111
30687
  function yieldToEventLoop() {
30112
- return new Promise((resolve14) => setImmediate(resolve14));
30688
+ return new Promise((resolve15) => setImmediate(resolve15));
30113
30689
  }
30114
30690
 
30115
30691
  // src/files/index/walk-workspace-tree.ts
30116
- import fs10 from "node:fs";
30117
- import path15 from "node:path";
30692
+ import fs12 from "node:fs";
30693
+ import path17 from "node:path";
30118
30694
 
30119
30695
  // src/files/index/constants.ts
30120
- import path14 from "node:path";
30696
+ import path16 from "node:path";
30121
30697
  import os4 from "node:os";
30122
30698
  var INDEX_WORK_YIELD_EVERY = 256;
30123
- var INDEX_DIR = path14.join(os4.homedir(), ".buildautomaton");
30699
+ var INDEX_DIR = path16.join(os4.homedir(), ".buildautomaton");
30124
30700
  var INDEX_HASH_LEN = 16;
30125
30701
  var INDEX_VERSION = 2;
30126
30702
  var INDEX_LOG_PREFIX = "[file-index]";
@@ -30129,31 +30705,31 @@ var INDEX_LOG_PREFIX = "[file-index]";
30129
30705
  function walkWorkspaceTreeSync(dir, baseDir, out) {
30130
30706
  let names;
30131
30707
  try {
30132
- names = fs10.readdirSync(dir);
30708
+ names = fs12.readdirSync(dir);
30133
30709
  } catch {
30134
30710
  return;
30135
30711
  }
30136
30712
  for (const name of names) {
30137
30713
  if (name.startsWith(".")) continue;
30138
- const full = path15.join(dir, name);
30714
+ const full = path17.join(dir, name);
30139
30715
  let stat2;
30140
30716
  try {
30141
- stat2 = fs10.statSync(full);
30717
+ stat2 = fs12.statSync(full);
30142
30718
  } catch {
30143
30719
  continue;
30144
30720
  }
30145
- const relative4 = path15.relative(baseDir, full).replace(/\\/g, "/");
30721
+ const relative5 = path17.relative(baseDir, full).replace(/\\/g, "/");
30146
30722
  if (stat2.isDirectory()) {
30147
30723
  walkWorkspaceTreeSync(full, baseDir, out);
30148
30724
  } else if (stat2.isFile()) {
30149
- out.push(relative4);
30725
+ out.push(relative5);
30150
30726
  }
30151
30727
  }
30152
30728
  }
30153
30729
  async function walkWorkspaceTreeAsync(dir, baseDir, out, state) {
30154
30730
  let names;
30155
30731
  try {
30156
- names = await fs10.promises.readdir(dir);
30732
+ names = await fs12.promises.readdir(dir);
30157
30733
  } catch {
30158
30734
  return;
30159
30735
  }
@@ -30163,18 +30739,18 @@ async function walkWorkspaceTreeAsync(dir, baseDir, out, state) {
30163
30739
  await yieldToEventLoop();
30164
30740
  }
30165
30741
  state.n++;
30166
- const full = path15.join(dir, name);
30742
+ const full = path17.join(dir, name);
30167
30743
  let stat2;
30168
30744
  try {
30169
- stat2 = await fs10.promises.stat(full);
30745
+ stat2 = await fs12.promises.stat(full);
30170
30746
  } catch {
30171
30747
  continue;
30172
30748
  }
30173
- const relative4 = path15.relative(baseDir, full).replace(/\\/g, "/");
30749
+ const relative5 = path17.relative(baseDir, full).replace(/\\/g, "/");
30174
30750
  if (stat2.isDirectory()) {
30175
30751
  await walkWorkspaceTreeAsync(full, baseDir, out, state);
30176
30752
  } else if (stat2.isFile()) {
30177
- out.push(relative4);
30753
+ out.push(relative5);
30178
30754
  }
30179
30755
  }
30180
30756
  }
@@ -30251,22 +30827,22 @@ async function buildTrigramMapForPathsAsync(paths) {
30251
30827
  }
30252
30828
 
30253
30829
  // src/files/index/write-index-file.ts
30254
- import fs11 from "node:fs";
30830
+ import fs13 from "node:fs";
30255
30831
 
30256
30832
  // src/files/index/paths.ts
30257
- import path16 from "node:path";
30833
+ import path18 from "node:path";
30258
30834
  import crypto2 from "node:crypto";
30259
30835
  function getIndexPathForCwd(resolvedCwd) {
30260
30836
  const hash = crypto2.createHash("sha256").update(resolvedCwd).digest("hex").slice(0, INDEX_HASH_LEN);
30261
- return path16.join(INDEX_DIR, `.file-index-${hash}.json`);
30837
+ return path18.join(INDEX_DIR, `.file-index-${hash}.json`);
30262
30838
  }
30263
30839
 
30264
30840
  // src/files/index/write-index-file.ts
30265
30841
  function writeIndexFileSync(resolvedCwd, data) {
30266
30842
  const indexPath = getIndexPathForCwd(resolvedCwd);
30267
30843
  try {
30268
- if (!fs11.existsSync(INDEX_DIR)) fs11.mkdirSync(INDEX_DIR, { recursive: true });
30269
- fs11.writeFileSync(indexPath, JSON.stringify(data), "utf8");
30844
+ if (!fs13.existsSync(INDEX_DIR)) fs13.mkdirSync(INDEX_DIR, { recursive: true });
30845
+ fs13.writeFileSync(indexPath, JSON.stringify(data), "utf8");
30270
30846
  } catch (e) {
30271
30847
  console.error(`${INDEX_LOG_PREFIX} Failed to write index:`, e);
30272
30848
  }
@@ -30274,8 +30850,8 @@ function writeIndexFileSync(resolvedCwd, data) {
30274
30850
  async function writeIndexFileAsync(resolvedCwd, data) {
30275
30851
  const indexPath = getIndexPathForCwd(resolvedCwd);
30276
30852
  try {
30277
- await fs11.promises.mkdir(INDEX_DIR, { recursive: true });
30278
- await fs11.promises.writeFile(indexPath, JSON.stringify(data), "utf8");
30853
+ await fs13.promises.mkdir(INDEX_DIR, { recursive: true });
30854
+ await fs13.promises.writeFile(indexPath, JSON.stringify(data), "utf8");
30279
30855
  } catch (e) {
30280
30856
  console.error(`${INDEX_LOG_PREFIX} Failed to write index:`, e);
30281
30857
  }
@@ -30289,7 +30865,7 @@ function sortPaths(paths) {
30289
30865
  paths.sort((a, b) => a.localeCompare(b, void 0, { sensitivity: "base" }));
30290
30866
  }
30291
30867
  function buildFileIndex(cwd) {
30292
- const resolved = path17.resolve(cwd);
30868
+ const resolved = path19.resolve(cwd);
30293
30869
  const paths = [];
30294
30870
  walkWorkspaceTreeSync(resolved, resolved, paths);
30295
30871
  sortPaths(paths);
@@ -30299,7 +30875,7 @@ function buildFileIndex(cwd) {
30299
30875
  return data;
30300
30876
  }
30301
30877
  async function buildFileIndexAsync(cwd) {
30302
- const resolved = path17.resolve(cwd);
30878
+ const resolved = path19.resolve(cwd);
30303
30879
  const paths = [];
30304
30880
  await walkWorkspaceTreeAsync(resolved, resolved, paths, createWalkYieldState());
30305
30881
  await yieldToEventLoop();
@@ -30311,13 +30887,13 @@ async function buildFileIndexAsync(cwd) {
30311
30887
  }
30312
30888
 
30313
30889
  // src/files/index/load-file-index.ts
30314
- import fs12 from "node:fs";
30315
- import path18 from "node:path";
30890
+ import fs14 from "node:fs";
30891
+ import path20 from "node:path";
30316
30892
  function loadFileIndex(cwd) {
30317
- const resolved = path18.resolve(cwd);
30893
+ const resolved = path20.resolve(cwd);
30318
30894
  const indexPath = getIndexPathForCwd(resolved);
30319
30895
  try {
30320
- const raw = fs12.readFileSync(indexPath, "utf8");
30896
+ const raw = fs14.readFileSync(indexPath, "utf8");
30321
30897
  const parsed = JSON.parse(raw);
30322
30898
  if (parsed !== null && typeof parsed === "object" && Array.isArray(parsed.paths)) {
30323
30899
  const obj = parsed;
@@ -30336,9 +30912,9 @@ function loadFileIndex(cwd) {
30336
30912
  }
30337
30913
 
30338
30914
  // src/files/index/ensure-file-index.ts
30339
- import path19 from "node:path";
30915
+ import path21 from "node:path";
30340
30916
  async function ensureFileIndexAsync(cwd) {
30341
- const resolved = path19.resolve(cwd);
30917
+ const resolved = path21.resolve(cwd);
30342
30918
  const cached2 = loadFileIndex(resolved);
30343
30919
  if (cached2 !== null) return { data: cached2, fromCache: true };
30344
30920
  const data = await buildFileIndexAsync(resolved);
@@ -30421,7 +30997,7 @@ function createFsWatcher(resolved, schedule) {
30421
30997
  }
30422
30998
  }
30423
30999
  function startFileIndexWatcher(cwd = getBridgeWorkspaceDirectory()) {
30424
- const resolved = path20.resolve(cwd);
31000
+ const resolved = path22.resolve(cwd);
30425
31001
  void buildFileIndexAsync(resolved).catch((e) => {
30426
31002
  console.error("[file-index] Initial index build failed:", e);
30427
31003
  });
@@ -30468,15 +31044,15 @@ function sendDevServerStatus(getWs, serverId, status, options) {
30468
31044
 
30469
31045
  // src/dev-servers/process/terminate-child-process.ts
30470
31046
  async function sigtermAndWaitForExit(proc, graceMs, log2, shortId) {
30471
- const exited = new Promise((resolve14) => {
30472
- proc.once("exit", () => resolve14());
31047
+ const exited = new Promise((resolve15) => {
31048
+ proc.once("exit", () => resolve15());
30473
31049
  });
30474
31050
  log2(`[dev-server] Sending SIGTERM to ${shortId} (pid=${proc.pid ?? "?"}).`);
30475
31051
  try {
30476
31052
  proc.kill("SIGTERM");
30477
31053
  } catch {
30478
31054
  }
30479
- await Promise.race([exited, new Promise((resolve14) => setTimeout(resolve14, graceMs))]);
31055
+ await Promise.race([exited, new Promise((resolve15) => setTimeout(resolve15, graceMs))]);
30480
31056
  }
30481
31057
  function forceKillChild(proc, log2, shortId, graceMs) {
30482
31058
  log2(
@@ -30490,7 +31066,7 @@ function forceKillChild(proc, log2, shortId, graceMs) {
30490
31066
  }
30491
31067
 
30492
31068
  // src/dev-servers/process/wire-dev-server-child-process.ts
30493
- import fs13 from "node:fs";
31069
+ import fs15 from "node:fs";
30494
31070
 
30495
31071
  // src/dev-servers/manager/forward-pipe.ts
30496
31072
  function forwardChildPipe(childReadable, terminal, onData) {
@@ -30526,7 +31102,7 @@ function wireDevServerChildProcess(d) {
30526
31102
  d.setPollInterval(void 0);
30527
31103
  return;
30528
31104
  }
30529
- fs13.readFile(d.mergedLogPath, (err, buf) => {
31105
+ fs15.readFile(d.mergedLogPath, (err, buf) => {
30530
31106
  if (err || (d.getSpawnGeneration() ?? 0) !== d.scheduledGen) return;
30531
31107
  if (buf.length <= d.mergedReadPos.value) return;
30532
31108
  const chunk = Buffer.from(buf.subarray(d.mergedReadPos.value));
@@ -30564,7 +31140,7 @@ ${errTail}` : ""}`);
30564
31140
  d.sendStatus(code === 0 || code == null ? "stopped" : "error", detail, tails);
30565
31141
  };
30566
31142
  if (mergedPath) {
30567
- fs13.readFile(mergedPath, (err, buf) => {
31143
+ fs15.readFile(mergedPath, (err, buf) => {
30568
31144
  if (!err && buf.length > d.mergedReadPos.value) {
30569
31145
  const chunk = Buffer.from(buf.subarray(d.mergedReadPos.value));
30570
31146
  if (chunk.length > 0) {
@@ -30666,13 +31242,13 @@ function parseDevServerDefs(servers) {
30666
31242
  }
30667
31243
 
30668
31244
  // src/dev-servers/manager/shell-spawn/utils.ts
30669
- import fs14 from "node:fs";
31245
+ import fs16 from "node:fs";
30670
31246
  function isSpawnEbadf(e) {
30671
31247
  return typeof e === "object" && e !== null && "code" in e && e.code === "EBADF";
30672
31248
  }
30673
31249
  function rmDirQuiet(dir) {
30674
31250
  try {
30675
- fs14.rmSync(dir, { recursive: true, force: true });
31251
+ fs16.rmSync(dir, { recursive: true, force: true });
30676
31252
  } catch {
30677
31253
  }
30678
31254
  }
@@ -30680,7 +31256,7 @@ var cachedDevNullReadFd;
30680
31256
  function devNullReadFd() {
30681
31257
  if (cachedDevNullReadFd === void 0) {
30682
31258
  const devPath = process.platform === "win32" ? "nul" : "/dev/null";
30683
- cachedDevNullReadFd = fs14.openSync(devPath, "r");
31259
+ cachedDevNullReadFd = fs16.openSync(devPath, "r");
30684
31260
  }
30685
31261
  return cachedDevNullReadFd;
30686
31262
  }
@@ -30754,15 +31330,15 @@ function trySpawnShellTruePiped(command, env, cwd, devNullFd, signal) {
30754
31330
 
30755
31331
  // src/dev-servers/manager/shell-spawn/try-spawn-merged-log-file.ts
30756
31332
  import { spawn as spawn7 } from "node:child_process";
30757
- import fs15 from "node:fs";
31333
+ import fs17 from "node:fs";
30758
31334
  import { tmpdir } from "node:os";
30759
- import path21 from "node:path";
31335
+ import path23 from "node:path";
30760
31336
  function trySpawnMergedLogFile(command, env, cwd, signal) {
30761
- const tmpRoot = fs15.mkdtempSync(path21.join(tmpdir(), "ba-devsrv-log-"));
30762
- const logPath = path21.join(tmpRoot, "combined.log");
31337
+ const tmpRoot = fs17.mkdtempSync(path23.join(tmpdir(), "ba-devsrv-log-"));
31338
+ const logPath = path23.join(tmpRoot, "combined.log");
30763
31339
  let logFd;
30764
31340
  try {
30765
- logFd = fs15.openSync(logPath, "a");
31341
+ logFd = fs17.openSync(logPath, "a");
30766
31342
  } catch {
30767
31343
  rmDirQuiet(tmpRoot);
30768
31344
  return null;
@@ -30781,7 +31357,7 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
30781
31357
  } else {
30782
31358
  proc = spawn7("/bin/sh", ["-c", command], { env, cwd, stdio, ...signal ? { signal } : {} });
30783
31359
  }
30784
- fs15.closeSync(logFd);
31360
+ fs17.closeSync(logFd);
30785
31361
  return {
30786
31362
  proc,
30787
31363
  pipedStdoutStderr: true,
@@ -30790,7 +31366,7 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
30790
31366
  };
30791
31367
  } catch (e) {
30792
31368
  try {
30793
- fs15.closeSync(logFd);
31369
+ fs17.closeSync(logFd);
30794
31370
  } catch {
30795
31371
  }
30796
31372
  rmDirQuiet(tmpRoot);
@@ -30801,22 +31377,22 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
30801
31377
 
30802
31378
  // src/dev-servers/manager/shell-spawn/try-spawn-shell-script-log-redirect.ts
30803
31379
  import { spawn as spawn8 } from "node:child_process";
30804
- import fs16 from "node:fs";
31380
+ import fs18 from "node:fs";
30805
31381
  import { tmpdir as tmpdir2 } from "node:os";
30806
- import path22 from "node:path";
31382
+ import path24 from "node:path";
30807
31383
  function shSingleQuote(s) {
30808
31384
  return `'${s.replace(/'/g, `'\\''`)}'`;
30809
31385
  }
30810
31386
  function trySpawnShellScriptLogRedirectUnix(command, env, cwd, signal) {
30811
- const tmpRoot = fs16.mkdtempSync(path22.join(tmpdir2(), "ba-devsrv-sh-"));
30812
- const logPath = path22.join(tmpRoot, "combined.log");
30813
- const innerPath = path22.join(tmpRoot, "_cmd.sh");
30814
- const runnerPath = path22.join(tmpRoot, "_run.sh");
31387
+ const tmpRoot = fs18.mkdtempSync(path24.join(tmpdir2(), "ba-devsrv-sh-"));
31388
+ const logPath = path24.join(tmpRoot, "combined.log");
31389
+ const innerPath = path24.join(tmpRoot, "_cmd.sh");
31390
+ const runnerPath = path24.join(tmpRoot, "_run.sh");
30815
31391
  try {
30816
- fs16.writeFileSync(innerPath, `#!/bin/sh
31392
+ fs18.writeFileSync(innerPath, `#!/bin/sh
30817
31393
  ${command}
30818
31394
  `);
30819
- fs16.writeFileSync(
31395
+ fs18.writeFileSync(
30820
31396
  runnerPath,
30821
31397
  `#!/bin/sh
30822
31398
  cd ${shSingleQuote(cwd)}
@@ -30842,13 +31418,13 @@ cd ${shSingleQuote(cwd)}
30842
31418
  }
30843
31419
  }
30844
31420
  function trySpawnShellScriptLogRedirectWin(command, env, cwd, signal) {
30845
- const tmpRoot = fs16.mkdtempSync(path22.join(tmpdir2(), "ba-devsrv-sh-"));
30846
- const logPath = path22.join(tmpRoot, "combined.log");
30847
- const runnerPath = path22.join(tmpRoot, "_run.bat");
31421
+ const tmpRoot = fs18.mkdtempSync(path24.join(tmpdir2(), "ba-devsrv-sh-"));
31422
+ const logPath = path24.join(tmpRoot, "combined.log");
31423
+ const runnerPath = path24.join(tmpRoot, "_run.bat");
30848
31424
  const q = (p) => `"${p.replace(/"/g, '""')}"`;
30849
31425
  const com = process.env.ComSpec || "cmd.exe";
30850
31426
  try {
30851
- fs16.writeFileSync(
31427
+ fs18.writeFileSync(
30852
31428
  runnerPath,
30853
31429
  `@ECHO OFF\r
30854
31430
  CD /D ${q(cwd)}\r
@@ -31530,30 +32106,30 @@ function createOnBridgeIdentified(opts) {
31530
32106
  }
31531
32107
 
31532
32108
  // src/skills/discover-local-agent-skills.ts
31533
- import fs17 from "node:fs";
31534
- import path23 from "node:path";
32109
+ import fs19 from "node:fs";
32110
+ import path25 from "node:path";
31535
32111
  var SKILL_DISCOVERY_ROOTS = [".agents/skills", ".claude/skills", ".cursor/skills", "skills"];
31536
32112
  function discoverLocalSkills(cwd) {
31537
32113
  const out = [];
31538
32114
  const seenKeys = /* @__PURE__ */ new Set();
31539
32115
  for (const rel of SKILL_DISCOVERY_ROOTS) {
31540
- const base = path23.join(cwd, rel);
31541
- if (!fs17.existsSync(base) || !fs17.statSync(base).isDirectory()) continue;
32116
+ const base = path25.join(cwd, rel);
32117
+ if (!fs19.existsSync(base) || !fs19.statSync(base).isDirectory()) continue;
31542
32118
  let entries = [];
31543
32119
  try {
31544
- entries = fs17.readdirSync(base);
32120
+ entries = fs19.readdirSync(base);
31545
32121
  } catch {
31546
32122
  continue;
31547
32123
  }
31548
32124
  for (const name of entries) {
31549
- const dir = path23.join(base, name);
32125
+ const dir = path25.join(base, name);
31550
32126
  try {
31551
- if (!fs17.statSync(dir).isDirectory()) continue;
32127
+ if (!fs19.statSync(dir).isDirectory()) continue;
31552
32128
  } catch {
31553
32129
  continue;
31554
32130
  }
31555
- const skillMd = path23.join(dir, "SKILL.md");
31556
- if (!fs17.existsSync(skillMd)) continue;
32131
+ const skillMd = path25.join(dir, "SKILL.md");
32132
+ if (!fs19.existsSync(skillMd)) continue;
31557
32133
  const key = `${rel}/${name}`;
31558
32134
  if (seenKeys.has(key)) continue;
31559
32135
  seenKeys.add(key);
@@ -31565,23 +32141,23 @@ function discoverLocalSkills(cwd) {
31565
32141
  function discoverSkillLayoutRoots(cwd) {
31566
32142
  const roots = [];
31567
32143
  for (const rel of SKILL_DISCOVERY_ROOTS) {
31568
- const base = path23.join(cwd, rel);
31569
- if (!fs17.existsSync(base) || !fs17.statSync(base).isDirectory()) continue;
32144
+ const base = path25.join(cwd, rel);
32145
+ if (!fs19.existsSync(base) || !fs19.statSync(base).isDirectory()) continue;
31570
32146
  let entries = [];
31571
32147
  try {
31572
- entries = fs17.readdirSync(base);
32148
+ entries = fs19.readdirSync(base);
31573
32149
  } catch {
31574
32150
  continue;
31575
32151
  }
31576
32152
  const skills2 = [];
31577
32153
  for (const name of entries) {
31578
- const dir = path23.join(base, name);
32154
+ const dir = path25.join(base, name);
31579
32155
  try {
31580
- if (!fs17.statSync(dir).isDirectory()) continue;
32156
+ if (!fs19.statSync(dir).isDirectory()) continue;
31581
32157
  } catch {
31582
32158
  continue;
31583
32159
  }
31584
- if (!fs17.existsSync(path23.join(dir, "SKILL.md"))) continue;
32160
+ if (!fs19.existsSync(path25.join(dir, "SKILL.md"))) continue;
31585
32161
  const relPath = `${rel}/${name}`.replace(/\\/g, "/");
31586
32162
  skills2.push({ name, relPath });
31587
32163
  }
@@ -31726,12 +32302,12 @@ var handleAgentConfigMessage = (msg, deps) => {
31726
32302
  };
31727
32303
 
31728
32304
  // src/agents/acp/from-bridge/handle-bridge-prompt.ts
31729
- import * as path25 from "node:path";
32305
+ import * as path27 from "node:path";
31730
32306
  import { execFile as execFile5 } from "node:child_process";
31731
32307
  import { promisify as promisify5 } from "node:util";
31732
32308
 
31733
32309
  // src/git/bridge-queue-key.ts
31734
- import * as path24 from "node:path";
32310
+ import * as path26 from "node:path";
31735
32311
  import { createHash } from "node:crypto";
31736
32312
  function normalizeCanonicalGitUrl(url2) {
31737
32313
  let s = url2.trim();
@@ -31759,11 +32335,11 @@ function canonicalUrlToRepoIdSync(url2) {
31759
32335
  return createHash("sha256").update(normalized).digest("hex").slice(0, 32);
31760
32336
  }
31761
32337
  function fallbackRepoIdFromPath(absPath) {
31762
- return createHash("sha256").update(path24.resolve(absPath)).digest("hex").slice(0, 32);
32338
+ return createHash("sha256").update(path26.resolve(absPath)).digest("hex").slice(0, 32);
31763
32339
  }
31764
32340
  async function resolveBridgeQueueBindFields(options) {
31765
32341
  const { effectiveCwd, worktreePaths, primaryRepoRoots, log: log2 } = options;
31766
- const cwdAbs = worktreePaths.length > 0 ? path24.resolve(worktreePaths[0]) : path24.resolve(effectiveCwd);
32342
+ const cwdAbs = worktreePaths.length > 0 ? path26.resolve(worktreePaths[0]) : path26.resolve(effectiveCwd);
31767
32343
  if (!primaryRepoRoots.length) {
31768
32344
  log2("[Bridge service] Prompt queue bind skipped: no Git repository roots under the working directory.");
31769
32345
  return null;
@@ -31828,7 +32404,7 @@ function handleBridgePrompt(msg, deps) {
31828
32404
  };
31829
32405
  async function preambleAndPrompt(resolvedCwd) {
31830
32406
  const s = getWs();
31831
- const effectiveCwd = path25.resolve(resolvedCwd ?? getBridgeWorkspaceDirectory());
32407
+ const effectiveCwd = path27.resolve(resolvedCwd ?? getBridgeWorkspaceDirectory());
31832
32408
  const worktreePaths = sessionWorktreeManager.getWorktreePathsForSession(sessionId) ?? [];
31833
32409
  const repoRoots = await resolveSnapshotRepoRoots({
31834
32410
  worktreePaths,
@@ -31953,15 +32529,15 @@ var handleSkillCallMessage = (msg, { getWs, log: log2 }) => {
31953
32529
  };
31954
32530
 
31955
32531
  // src/files/list-dir.ts
31956
- import fs18 from "node:fs";
31957
- import path27 from "node:path";
32532
+ import fs20 from "node:fs";
32533
+ import path29 from "node:path";
31958
32534
 
31959
32535
  // src/files/ensure-under-cwd.ts
31960
- import path26 from "node:path";
32536
+ import path28 from "node:path";
31961
32537
  function ensureUnderCwd(relativePath, cwd = getBridgeWorkspaceDirectory()) {
31962
- const normalized = path26.normalize(relativePath).replace(/^(\.\/)+/, "");
31963
- const resolved = path26.resolve(cwd, normalized);
31964
- if (!resolved.startsWith(cwd + path26.sep) && resolved !== cwd) {
32538
+ const normalized = path28.normalize(relativePath).replace(/^(\.\/)+/, "");
32539
+ const resolved = path28.resolve(cwd, normalized);
32540
+ if (!resolved.startsWith(cwd + path28.sep) && resolved !== cwd) {
31965
32541
  return null;
31966
32542
  }
31967
32543
  return resolved;
@@ -31975,7 +32551,7 @@ async function listDirAsync(relativePath) {
31975
32551
  return { error: "Path is outside working directory" };
31976
32552
  }
31977
32553
  try {
31978
- const names = await fs18.promises.readdir(resolved, { withFileTypes: true });
32554
+ const names = await fs20.promises.readdir(resolved, { withFileTypes: true });
31979
32555
  const visible = names.filter((d) => !d.name.startsWith("."));
31980
32556
  const entries = [];
31981
32557
  for (let i = 0; i < visible.length; i++) {
@@ -31983,12 +32559,12 @@ async function listDirAsync(relativePath) {
31983
32559
  await yieldToEventLoop();
31984
32560
  }
31985
32561
  const d = visible[i];
31986
- const entryPath = path27.join(relativePath || ".", d.name).replace(/\\/g, "/");
31987
- const fullPath = path27.join(resolved, d.name);
32562
+ const entryPath = path29.join(relativePath || ".", d.name).replace(/\\/g, "/");
32563
+ const fullPath = path29.join(resolved, d.name);
31988
32564
  let isDir = d.isDirectory();
31989
32565
  if (d.isSymbolicLink()) {
31990
32566
  try {
31991
- const targetStat = await fs18.promises.stat(fullPath);
32567
+ const targetStat = await fs20.promises.stat(fullPath);
31992
32568
  isDir = targetStat.isDirectory();
31993
32569
  } catch {
31994
32570
  isDir = false;
@@ -32013,25 +32589,25 @@ async function listDirAsync(relativePath) {
32013
32589
  }
32014
32590
 
32015
32591
  // src/files/read-file.ts
32016
- import fs19 from "node:fs";
32592
+ import fs21 from "node:fs";
32017
32593
  import { StringDecoder } from "node:string_decoder";
32018
32594
  function resolveFilePath(relativePath) {
32019
32595
  const resolved = ensureUnderCwd(relativePath, getBridgeWorkspaceDirectory());
32020
32596
  if (!resolved) return { error: "Path is outside working directory" };
32021
32597
  let real;
32022
32598
  try {
32023
- real = fs19.realpathSync(resolved);
32599
+ real = fs21.realpathSync(resolved);
32024
32600
  } catch {
32025
32601
  real = resolved;
32026
32602
  }
32027
- const stat2 = fs19.statSync(real);
32603
+ const stat2 = fs21.statSync(real);
32028
32604
  if (!stat2.isFile()) return { error: "Not a file" };
32029
32605
  return real;
32030
32606
  }
32031
32607
  var LINE_CHUNK_SIZE = 64 * 1024;
32032
32608
  function readFileRange(filePath, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
32033
- const fileSize = fs19.statSync(filePath).size;
32034
- const fd = fs19.openSync(filePath, "r");
32609
+ const fileSize = fs21.statSync(filePath).size;
32610
+ const fd = fs21.openSync(filePath, "r");
32035
32611
  const bufSize = 64 * 1024;
32036
32612
  const buf = Buffer.alloc(bufSize);
32037
32613
  const decoder = new StringDecoder("utf8");
@@ -32044,7 +32620,7 @@ function readFileRange(filePath, startLine, endLine, lineOffsetIn, lineChunkSize
32044
32620
  let line0Accum = "";
32045
32621
  try {
32046
32622
  let bytesRead;
32047
- while (!done && (bytesRead = fs19.readSync(fd, buf, 0, bufSize, null)) > 0) {
32623
+ while (!done && (bytesRead = fs21.readSync(fd, buf, 0, bufSize, null)) > 0) {
32048
32624
  const text = partial2 + decoder.write(buf.subarray(0, bytesRead));
32049
32625
  partial2 = "";
32050
32626
  let lineStart = 0;
@@ -32179,7 +32755,7 @@ function readFileRange(filePath, startLine, endLine, lineOffsetIn, lineChunkSize
32179
32755
  }
32180
32756
  return { content: resultLines.join("\n"), size: fileSize };
32181
32757
  } finally {
32182
- fs19.closeSync(fd);
32758
+ fs21.closeSync(fd);
32183
32759
  }
32184
32760
  }
32185
32761
  function readFile2(relativePath, startLine, endLine, lineOffset, lineChunkSize = LINE_CHUNK_SIZE) {
@@ -32190,8 +32766,8 @@ function readFile2(relativePath, startLine, endLine, lineOffset, lineChunkSize =
32190
32766
  if (hasRange) {
32191
32767
  return readFileRange(result, startLine, endLine, lineOffset, lineChunkSize);
32192
32768
  }
32193
- const stat2 = fs19.statSync(result);
32194
- const raw = fs19.readFileSync(result, "utf8");
32769
+ const stat2 = fs21.statSync(result);
32770
+ const raw = fs21.readFileSync(result, "utf8");
32195
32771
  const lines = raw.split(/\r?\n/);
32196
32772
  return { content: raw, totalLines: lines.length, size: stat2.size };
32197
32773
  } catch (err) {
@@ -32301,10 +32877,10 @@ function handleSkillLayoutRequest(msg, deps) {
32301
32877
  }
32302
32878
 
32303
32879
  // src/skills/install-remote-skills.ts
32304
- import fs20 from "node:fs";
32305
- import path28 from "node:path";
32880
+ import fs22 from "node:fs";
32881
+ import path30 from "node:path";
32306
32882
  function installRemoteSkills(cwd, targetDir, items) {
32307
- const installed = [];
32883
+ const installed2 = [];
32308
32884
  if (!Array.isArray(items)) {
32309
32885
  return { success: false, error: "Invalid items" };
32310
32886
  }
@@ -32313,24 +32889,24 @@ function installRemoteSkills(cwd, targetDir, items) {
32313
32889
  if (typeof item.sourceId !== "string" || typeof item.skillName !== "string" || typeof item.versionHash !== "string" || !Array.isArray(item.files)) {
32314
32890
  continue;
32315
32891
  }
32316
- const skillDir = path28.join(cwd, targetDir, item.skillName);
32892
+ const skillDir = path30.join(cwd, targetDir, item.skillName);
32317
32893
  for (const f of item.files) {
32318
32894
  if (typeof f.path !== "string" || !f.text && !f.base64) continue;
32319
- const dest = path28.join(skillDir, f.path);
32320
- fs20.mkdirSync(path28.dirname(dest), { recursive: true });
32895
+ const dest = path30.join(skillDir, f.path);
32896
+ fs22.mkdirSync(path30.dirname(dest), { recursive: true });
32321
32897
  if (f.text !== void 0) {
32322
- fs20.writeFileSync(dest, f.text, "utf8");
32898
+ fs22.writeFileSync(dest, f.text, "utf8");
32323
32899
  } else if (f.base64) {
32324
- fs20.writeFileSync(dest, Buffer.from(f.base64, "base64"));
32900
+ fs22.writeFileSync(dest, Buffer.from(f.base64, "base64"));
32325
32901
  }
32326
32902
  }
32327
- installed.push({
32903
+ installed2.push({
32328
32904
  sourceId: item.sourceId,
32329
32905
  skillName: item.skillName,
32330
32906
  versionHash: item.versionHash
32331
32907
  });
32332
32908
  }
32333
- return { success: true, installed };
32909
+ return { success: true, installed: installed2 };
32334
32910
  } catch (e) {
32335
32911
  return { success: false, error: e instanceof Error ? e.message : String(e) };
32336
32912
  }
@@ -32367,7 +32943,8 @@ var handleSessionGitRequestMessage = (msg, deps) => {
32367
32943
  if (typeof msg.id !== "string") return;
32368
32944
  const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
32369
32945
  const action = msg.action;
32370
- if (!sessionId || action !== "status" && action !== "push" && action !== "commit") return;
32946
+ if (!sessionId || action !== "status" && action !== "push" && action !== "commit" && action !== "list_changes")
32947
+ return;
32371
32948
  void (async () => {
32372
32949
  const ws = deps.getWs();
32373
32950
  const reply = (payload) => sendResult(ws, msg.id, payload);
@@ -32381,6 +32958,24 @@ var handleSessionGitRequestMessage = (msg, deps) => {
32381
32958
  });
32382
32959
  return;
32383
32960
  }
32961
+ if (action === "list_changes") {
32962
+ const repoRel = typeof msg.changesRepoRelPath === "string" ? msg.changesRepoRelPath.trim() : "";
32963
+ const view = msg.changesView === "commit" ? "commit" : "working";
32964
+ const commitSha = typeof msg.changesCommitSha === "string" ? msg.changesCommitSha.trim() : "";
32965
+ if (view === "commit") {
32966
+ if (!repoRel || !commitSha) {
32967
+ reply({ ok: false, error: "changesRepoRelPath and changesCommitSha are required for commit view" });
32968
+ return;
32969
+ }
32970
+ }
32971
+ const opts = repoRel && view === "commit" && commitSha ? { repoRelPath: repoRel, basis: { kind: "commit", sha: commitSha } } : repoRel ? { repoRelPath: repoRel, basis: { kind: "working" } } : void 0;
32972
+ const repos = await deps.sessionWorktreeManager.getSessionWorkingTreeChangeDetails(sessionId, opts);
32973
+ reply({
32974
+ ok: true,
32975
+ repos
32976
+ });
32977
+ return;
32978
+ }
32384
32979
  if (action === "push") {
32385
32980
  const pushRes = await deps.sessionWorktreeManager.pushSessionUpstream(sessionId);
32386
32981
  if (!pushRes.ok) {
@@ -32447,7 +33042,7 @@ var handleSessionDiscardedMessage = (msg, deps) => {
32447
33042
  };
32448
33043
 
32449
33044
  // src/bridge/routing/handlers/revert-turn-snapshot.ts
32450
- import * as fs21 from "node:fs";
33045
+ import * as fs23 from "node:fs";
32451
33046
  var handleRevertTurnSnapshotMessage = (msg, deps) => {
32452
33047
  const id = typeof msg.id === "string" ? msg.id : "";
32453
33048
  const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
@@ -32459,7 +33054,7 @@ var handleRevertTurnSnapshotMessage = (msg, deps) => {
32459
33054
  if (!s) return;
32460
33055
  const agentBase = sessionWorktreeManager.getAgentCwdForSession(sessionId) ?? getBridgeWorkspaceDirectory();
32461
33056
  const file2 = snapshotFilePath(agentBase, turnId);
32462
- if (!fs21.existsSync(file2)) {
33057
+ if (!fs23.existsSync(file2)) {
32463
33058
  sendWsMessage(s, {
32464
33059
  type: "revert_turn_snapshot_result",
32465
33060
  id,
@@ -32788,6 +33383,7 @@ async function createBridgeConnection(options) {
32788
33383
 
32789
33384
  // src/run-bridge.ts
32790
33385
  async function runBridge(options) {
33386
+ installBridgeProcessResilience();
32791
33387
  const { apiUrl, workspaceId, authToken, refreshToken, bridgeName, justAuthenticated, worktreesRootAbs } = options;
32792
33388
  const firehoseServerUrl = options.firehoseServerUrl ?? options.proxyServerUrl;
32793
33389
  const hasAuth = workspaceId && authToken;