@buildautomaton/cli 0.1.45 → 0.1.47

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 path46 = __require("node:path");
977
- var fs41 = __require("node:fs");
976
+ var path48 = __require("node:path");
977
+ var fs42 = __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 = path46.resolve(baseDir, baseName);
1910
- if (fs41.existsSync(localBin)) return localBin;
1911
- if (sourceExt.includes(path46.extname(baseName))) return void 0;
1909
+ const localBin = path48.resolve(baseDir, baseName);
1910
+ if (fs42.existsSync(localBin)) return localBin;
1911
+ if (sourceExt.includes(path48.extname(baseName))) return void 0;
1912
1912
  const foundExt = sourceExt.find(
1913
- (ext) => fs41.existsSync(`${localBin}${ext}`)
1913
+ (ext) => fs42.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 = fs41.realpathSync(this._scriptPath);
1925
+ resolvedScriptPath = fs42.realpathSync(this._scriptPath);
1926
1926
  } catch (err) {
1927
1927
  resolvedScriptPath = this._scriptPath;
1928
1928
  }
1929
- executableDir = path46.resolve(
1930
- path46.dirname(resolvedScriptPath),
1929
+ executableDir = path48.resolve(
1930
+ path48.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 = path46.basename(
1937
+ const legacyName = path48.basename(
1938
1938
  this._scriptPath,
1939
- path46.extname(this._scriptPath)
1939
+ path48.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(path46.extname(executableFile));
1950
+ launchWithNode = sourceExt.includes(path48.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 = path46.basename(filename, path46.extname(filename));
2790
+ this._name = path48.basename(filename, path48.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(path47) {
2805
- if (path47 === void 0) return this._executableDir;
2806
- this._executableDir = path47;
2804
+ executableDir(path49) {
2805
+ if (path49 === void 0) return this._executableDir;
2806
+ this._executableDir = path49;
2807
2807
  return this;
2808
2808
  }
2809
2809
  /**
@@ -5233,7 +5233,7 @@ var require_websocket = __commonJS({
5233
5233
  "use strict";
5234
5234
  var EventEmitter2 = __require("events");
5235
5235
  var https2 = __require("https");
5236
- var http = __require("http");
5236
+ var http2 = __require("http");
5237
5237
  var net = __require("net");
5238
5238
  var tls = __require("tls");
5239
5239
  var { randomBytes: randomBytes3, createHash: createHash3 } = __require("crypto");
@@ -5767,7 +5767,7 @@ var require_websocket = __commonJS({
5767
5767
  }
5768
5768
  const defaultPort = isSecure ? 443 : 80;
5769
5769
  const key = randomBytes3(16).toString("base64");
5770
- const request = isSecure ? https2.request : http.request;
5770
+ const request = isSecure ? https2.request : http2.request;
5771
5771
  const protocolSet = /* @__PURE__ */ new Set();
5772
5772
  let perMessageDeflate;
5773
5773
  opts.createConnection = opts.createConnection || (isSecure ? tlsConnect : netConnect);
@@ -6261,7 +6261,7 @@ var require_websocket_server = __commonJS({
6261
6261
  "../../node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/websocket-server.js"(exports, module) {
6262
6262
  "use strict";
6263
6263
  var EventEmitter2 = __require("events");
6264
- var http = __require("http");
6264
+ var http2 = __require("http");
6265
6265
  var { Duplex } = __require("stream");
6266
6266
  var { createHash: createHash3 } = __require("crypto");
6267
6267
  var extension = require_extension();
@@ -6336,8 +6336,8 @@ var require_websocket_server = __commonJS({
6336
6336
  );
6337
6337
  }
6338
6338
  if (options.port != null) {
6339
- this._server = http.createServer((req, res) => {
6340
- const body = http.STATUS_CODES[426];
6339
+ this._server = http2.createServer((req, res) => {
6340
+ const body = http2.STATUS_CODES[426];
6341
6341
  res.writeHead(426, {
6342
6342
  "Content-Length": body.length,
6343
6343
  "Content-Type": "text/plain"
@@ -6624,7 +6624,7 @@ var require_websocket_server = __commonJS({
6624
6624
  this.destroy();
6625
6625
  }
6626
6626
  function abortHandshake(socket, code, message, headers) {
6627
- message = message || http.STATUS_CODES[code];
6627
+ message = message || http2.STATUS_CODES[code];
6628
6628
  headers = {
6629
6629
  Connection: "close",
6630
6630
  "Content-Type": "text/html",
@@ -6633,7 +6633,7 @@ var require_websocket_server = __commonJS({
6633
6633
  };
6634
6634
  socket.once("finish", socket.destroy);
6635
6635
  socket.end(
6636
- `HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r
6636
+ `HTTP/1.1 ${code} ${http2.STATUS_CODES[code]}\r
6637
6637
  ` + Object.keys(headers).map((h) => `${h}: ${headers[h]}`).join("\r\n") + "\r\n\r\n" + message
6638
6638
  );
6639
6639
  }
@@ -7061,8 +7061,8 @@ var init_parseUtil = __esm({
7061
7061
  init_errors();
7062
7062
  init_en();
7063
7063
  makeIssue = (params) => {
7064
- const { data, path: path46, errorMaps, issueData } = params;
7065
- const fullPath = [...path46, ...issueData.path || []];
7064
+ const { data, path: path48, errorMaps, issueData } = params;
7065
+ const fullPath = [...path48, ...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, path46, key) {
7373
+ constructor(parent, value, path48, key) {
7374
7374
  this._cachedPath = [];
7375
7375
  this.parent = parent;
7376
7376
  this.data = value;
7377
- this._path = path46;
7377
+ this._path = path48;
7378
7378
  this._key = key;
7379
7379
  }
7380
7380
  get path() {
@@ -11099,15 +11099,15 @@ function assignProp(target, prop, value) {
11099
11099
  configurable: true
11100
11100
  });
11101
11101
  }
11102
- function getElementAtPath(obj, path46) {
11103
- if (!path46)
11102
+ function getElementAtPath(obj, path48) {
11103
+ if (!path48)
11104
11104
  return obj;
11105
- return path46.reduce((acc, key) => acc?.[key], obj);
11105
+ return path48.reduce((acc, key) => acc?.[key], obj);
11106
11106
  }
11107
11107
  function promiseAllObject(promisesObj) {
11108
11108
  const keys = Object.keys(promisesObj);
11109
- const promises5 = keys.map((key) => promisesObj[key]);
11110
- return Promise.all(promises5).then((results) => {
11109
+ const promises6 = keys.map((key) => promisesObj[key]);
11110
+ return Promise.all(promises6).then((results) => {
11111
11111
  const resolvedObj = {};
11112
11112
  for (let i = 0; i < keys.length; i++) {
11113
11113
  resolvedObj[keys[i]] = results[i];
@@ -11351,11 +11351,11 @@ function aborted(x, startIndex = 0) {
11351
11351
  }
11352
11352
  return false;
11353
11353
  }
11354
- function prefixIssues(path46, issues) {
11354
+ function prefixIssues(path48, issues) {
11355
11355
  return issues.map((iss) => {
11356
11356
  var _a2;
11357
11357
  (_a2 = iss).path ?? (_a2.path = []);
11358
- iss.path.unshift(path46);
11358
+ iss.path.unshift(path48);
11359
11359
  return iss;
11360
11360
  });
11361
11361
  }
@@ -11544,7 +11544,7 @@ function treeifyError(error40, _mapper) {
11544
11544
  return issue2.message;
11545
11545
  };
11546
11546
  const result = { errors: [] };
11547
- const processError = (error41, path46 = []) => {
11547
+ const processError = (error41, path48 = []) => {
11548
11548
  var _a2, _b;
11549
11549
  for (const issue2 of error41.issues) {
11550
11550
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -11554,7 +11554,7 @@ function treeifyError(error40, _mapper) {
11554
11554
  } else if (issue2.code === "invalid_element") {
11555
11555
  processError({ issues: issue2.issues }, issue2.path);
11556
11556
  } else {
11557
- const fullpath = [...path46, ...issue2.path];
11557
+ const fullpath = [...path48, ...issue2.path];
11558
11558
  if (fullpath.length === 0) {
11559
11559
  result.errors.push(mapper(issue2));
11560
11560
  continue;
@@ -11584,9 +11584,9 @@ function treeifyError(error40, _mapper) {
11584
11584
  processError(error40);
11585
11585
  return result;
11586
11586
  }
11587
- function toDotPath(path46) {
11587
+ function toDotPath(path48) {
11588
11588
  const segs = [];
11589
- for (const seg of path46) {
11589
+ for (const seg of path48) {
11590
11590
  if (typeof seg === "number")
11591
11591
  segs.push(`[${seg}]`);
11592
11592
  else if (typeof seg === "symbol")
@@ -25069,10 +25069,10 @@ var require_src2 = __commonJS({
25069
25069
  var fs_1 = __require("fs");
25070
25070
  var debug_1 = __importDefault(require_src());
25071
25071
  var log2 = debug_1.default("@kwsites/file-exists");
25072
- function check2(path46, isFile, isDirectory) {
25073
- log2(`checking %s`, path46);
25072
+ function check2(path48, isFile, isDirectory) {
25073
+ log2(`checking %s`, path48);
25074
25074
  try {
25075
- const stat3 = fs_1.statSync(path46);
25075
+ const stat3 = fs_1.statSync(path48);
25076
25076
  if (stat3.isFile() && isFile) {
25077
25077
  log2(`[OK] path represents a file`);
25078
25078
  return true;
@@ -25092,8 +25092,8 @@ var require_src2 = __commonJS({
25092
25092
  throw e;
25093
25093
  }
25094
25094
  }
25095
- function exists2(path46, type = exports.READABLE) {
25096
- return check2(path46, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
25095
+ function exists2(path48, type = exports.READABLE) {
25096
+ return check2(path48, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
25097
25097
  }
25098
25098
  exports.exists = exists2;
25099
25099
  exports.FILE = 1;
@@ -25174,15 +25174,15 @@ var {
25174
25174
  } = import_index.default;
25175
25175
 
25176
25176
  // src/cli-version.ts
25177
- var CLI_VERSION = "0.1.45".length > 0 ? "0.1.45" : "0.0.0-dev";
25177
+ var CLI_VERSION = "0.1.47".length > 0 ? "0.1.47" : "0.0.0-dev";
25178
25178
 
25179
25179
  // src/cli/defaults.ts
25180
25180
  var DEFAULT_API_URL = process.env.BUILDAUTOMATON_API_URL ?? "https://api.buildautomaton.com";
25181
25181
  var DEFAULT_FIREHOSE_URL = "https://buildautomaton-firehose.fly.dev";
25182
25182
 
25183
25183
  // src/cli/run-cli-action.ts
25184
- import * as fs40 from "node:fs";
25185
- import * as path45 from "node:path";
25184
+ import * as fs41 from "node:fs";
25185
+ import * as path47 from "node:path";
25186
25186
 
25187
25187
  // src/cli-log-level.ts
25188
25188
  var verbosity = "info";
@@ -27479,6 +27479,7 @@ var AcpPromptRoutingRegistry = class {
27479
27479
  function createAcpManagerContext(options) {
27480
27480
  return {
27481
27481
  log: options.log,
27482
+ getBridgeAccessPort: options.getBridgeAccessPort,
27482
27483
  reportAgentCapabilities: options.reportAgentCapabilities,
27483
27484
  backendFallbackAgentType: null,
27484
27485
  acpAgents: /* @__PURE__ */ new Map(),
@@ -27818,9 +27819,9 @@ function parseChangeSummaryJson(raw, allowedPaths, options) {
27818
27819
  const rawPath = typeof o.path === "string" ? o.path.trim() : "";
27819
27820
  const summary = typeof o.summary === "string" ? o.summary.trim() : "";
27820
27821
  if (!rawPath || !summary) continue;
27821
- const path46 = skip ? normalizeRepoRelativePath(rawPath) || rawPath : resolveChangeSummaryPathAgainstAllowed(rawPath, allowedPaths);
27822
- if (!path46) continue;
27823
- rows.push({ path: path46, summary: clampSummaryToAtMostTwoLines(summary) });
27822
+ const path48 = skip ? normalizeRepoRelativePath(rawPath) || rawPath : resolveChangeSummaryPathAgainstAllowed(rawPath, allowedPaths);
27823
+ if (!path48) continue;
27824
+ rows.push({ path: path48, summary: clampSummaryToAtMostTwoLines(summary) });
27824
27825
  }
27825
27826
  return rows;
27826
27827
  }
@@ -28751,7 +28752,7 @@ function createSdkStdioSessionContext(options) {
28751
28752
  return {
28752
28753
  cwd: options.cwd,
28753
28754
  onFileChange: options.onFileChange,
28754
- mcpServers: [],
28755
+ mcpServers: options.mcpServers ?? [],
28755
28756
  persistedAcpSessionId: options.persistedAcpSessionId,
28756
28757
  agentLabel: "ACP",
28757
28758
  suppressLoadReplay: suppressLoadReplayRef,
@@ -29194,6 +29195,7 @@ async function createSdkStdioAcpClient(options) {
29194
29195
  });
29195
29196
  const sessionCtx = createSdkStdioSessionContext({
29196
29197
  cwd,
29198
+ mcpServers: options.mcpServers,
29197
29199
  persistedAcpSessionId,
29198
29200
  backendAgentType,
29199
29201
  agentConfig,
@@ -29325,7 +29327,7 @@ function createCursorAcpSessionContext(options) {
29325
29327
  return {
29326
29328
  cwd: options.cwd,
29327
29329
  onFileChange: options.onFileChange,
29328
- mcpServers: [],
29330
+ mcpServers: options.mcpServers ?? [],
29329
29331
  persistedAcpSessionId: options.persistedAcpSessionId,
29330
29332
  agentLabel: "Cursor",
29331
29333
  suppressLoadReplay: suppressLoadReplayRef,
@@ -29751,6 +29753,7 @@ async function createCursorAcpClient(options) {
29751
29753
  });
29752
29754
  const sessionCtx = createCursorAcpSessionContext({
29753
29755
  cwd,
29756
+ mcpServers: options.mcpServers,
29754
29757
  persistedAcpSessionId,
29755
29758
  backendAgentType,
29756
29759
  agentConfig: options.agentConfig,
@@ -30709,6 +30712,30 @@ function computeAcpAgentKey(preferredAgentType, mode, agentConfig) {
30709
30712
  return `${resolved.label}::${fullCmd.join("\0")}${cacheConfig}`;
30710
30713
  }
30711
30714
 
30715
+ // src/mcp/bridge-mcp-constants.ts
30716
+ var BRIDGE_MCP_SERVER_NAME = "buildautomaton-bridge";
30717
+
30718
+ // src/mcp/resolve-bridge-mcp-server-path.ts
30719
+ import { dirname as dirname4, join as join3 } from "node:path";
30720
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
30721
+ function resolveBridgeMcpServerScriptPath() {
30722
+ const cliDir = dirname4(fileURLToPath4(import.meta.url));
30723
+ return join3(cliDir, "bridge-mcp-server.js");
30724
+ }
30725
+
30726
+ // src/mcp/build-acp-mcp-servers.ts
30727
+ function buildAcpMcpServers(bridgeAccessPort) {
30728
+ if (!Number.isFinite(bridgeAccessPort) || bridgeAccessPort <= 0) return [];
30729
+ return [
30730
+ {
30731
+ name: BRIDGE_MCP_SERVER_NAME,
30732
+ command: process.execPath,
30733
+ args: [resolveBridgeMcpServerScriptPath(), String(bridgeAccessPort)],
30734
+ env: []
30735
+ }
30736
+ ];
30737
+ }
30738
+
30712
30739
  // src/agents/acp/ensure-acp-client.ts
30713
30740
  function invalidateAcpClientState(state) {
30714
30741
  state.clientEpoch += 1;
@@ -30740,11 +30767,13 @@ async function ensureAcpClient(options) {
30740
30767
  sessionParentPath,
30741
30768
  resolveRouting,
30742
30769
  cloudSessionId,
30770
+ bridgeAccessPort,
30743
30771
  reportAgentCapabilities,
30744
30772
  sendSessionUpdate,
30745
30773
  sendRequest,
30746
30774
  log: log2
30747
30775
  } = options;
30776
+ const mcpServers = buildAcpMcpServers(bridgeAccessPort ?? 0);
30748
30777
  state.latestAgentConfigForBridgeHooks = agentConfig != null && typeof agentConfig === "object" && !Array.isArray(agentConfig) ? agentConfig : null;
30749
30778
  const targetSessionParentPath = resolveSessionParentPathForAgentProcess(sessionParentPath);
30750
30779
  if (state.acpStartPromise && !state.acpHandle) {
@@ -30860,7 +30889,8 @@ async function ensureAcpClient(options) {
30860
30889
  state.lastAcpStartError = "Agent subprocess exited";
30861
30890
  },
30862
30891
  ...hooks,
30863
- cwd: targetSessionParentPath
30892
+ cwd: targetSessionParentPath,
30893
+ mcpServers
30864
30894
  }).then(async (h) => {
30865
30895
  if (spawnEpoch !== state.clientEpoch || isCliImmediateShutdownRequested()) {
30866
30896
  try {
@@ -30951,8 +30981,8 @@ function pathspec(...paths) {
30951
30981
  cache.set(key, paths);
30952
30982
  return key;
30953
30983
  }
30954
- function isPathSpec(path46) {
30955
- return path46 instanceof String && cache.has(path46);
30984
+ function isPathSpec(path48) {
30985
+ return path48 instanceof String && cache.has(path48);
30956
30986
  }
30957
30987
  function toPaths(pathSpec) {
30958
30988
  return cache.get(pathSpec) || [];
@@ -31041,8 +31071,8 @@ function toLinesWithContent(input = "", trimmed2 = true, separator = "\n") {
31041
31071
  function forEachLineWithContent(input, callback) {
31042
31072
  return toLinesWithContent(input, true).map((line) => callback(line));
31043
31073
  }
31044
- function folderExists(path46) {
31045
- return (0, import_file_exists.exists)(path46, import_file_exists.FOLDER);
31074
+ function folderExists(path48) {
31075
+ return (0, import_file_exists.exists)(path48, import_file_exists.FOLDER);
31046
31076
  }
31047
31077
  function append(target, item) {
31048
31078
  if (Array.isArray(target)) {
@@ -31446,8 +31476,8 @@ function checkIsRepoRootTask() {
31446
31476
  commands,
31447
31477
  format: "utf-8",
31448
31478
  onError,
31449
- parser(path46) {
31450
- return /^\.(git)?$/.test(path46.trim());
31479
+ parser(path48) {
31480
+ return /^\.(git)?$/.test(path48.trim());
31451
31481
  }
31452
31482
  };
31453
31483
  }
@@ -31881,11 +31911,11 @@ function parseGrep(grep) {
31881
31911
  const paths = /* @__PURE__ */ new Set();
31882
31912
  const results = {};
31883
31913
  forEachLineWithContent(grep, (input) => {
31884
- const [path46, line, preview] = input.split(NULL);
31885
- paths.add(path46);
31886
- (results[path46] = results[path46] || []).push({
31914
+ const [path48, line, preview] = input.split(NULL);
31915
+ paths.add(path48);
31916
+ (results[path48] = results[path48] || []).push({
31887
31917
  line: asNumber(line),
31888
- path: path46,
31918
+ path: path48,
31889
31919
  preview
31890
31920
  });
31891
31921
  });
@@ -32650,14 +32680,14 @@ var init_hash_object = __esm2({
32650
32680
  init_task();
32651
32681
  }
32652
32682
  });
32653
- function parseInit(bare, path46, text) {
32683
+ function parseInit(bare, path48, text) {
32654
32684
  const response = String(text).trim();
32655
32685
  let result;
32656
32686
  if (result = initResponseRegex.exec(response)) {
32657
- return new InitSummary(bare, path46, false, result[1]);
32687
+ return new InitSummary(bare, path48, false, result[1]);
32658
32688
  }
32659
32689
  if (result = reInitResponseRegex.exec(response)) {
32660
- return new InitSummary(bare, path46, true, result[1]);
32690
+ return new InitSummary(bare, path48, true, result[1]);
32661
32691
  }
32662
32692
  let gitDir = "";
32663
32693
  const tokens = response.split(" ");
@@ -32668,7 +32698,7 @@ function parseInit(bare, path46, text) {
32668
32698
  break;
32669
32699
  }
32670
32700
  }
32671
- return new InitSummary(bare, path46, /^re/i.test(response), gitDir);
32701
+ return new InitSummary(bare, path48, /^re/i.test(response), gitDir);
32672
32702
  }
32673
32703
  var InitSummary;
32674
32704
  var initResponseRegex;
@@ -32677,9 +32707,9 @@ var init_InitSummary = __esm2({
32677
32707
  "src/lib/responses/InitSummary.ts"() {
32678
32708
  "use strict";
32679
32709
  InitSummary = class {
32680
- constructor(bare, path46, existing, gitDir) {
32710
+ constructor(bare, path48, existing, gitDir) {
32681
32711
  this.bare = bare;
32682
- this.path = path46;
32712
+ this.path = path48;
32683
32713
  this.existing = existing;
32684
32714
  this.gitDir = gitDir;
32685
32715
  }
@@ -32691,7 +32721,7 @@ var init_InitSummary = __esm2({
32691
32721
  function hasBareCommand(command) {
32692
32722
  return command.includes(bareCommand);
32693
32723
  }
32694
- function initTask(bare = false, path46, customArgs) {
32724
+ function initTask(bare = false, path48, customArgs) {
32695
32725
  const commands = ["init", ...customArgs];
32696
32726
  if (bare && !hasBareCommand(commands)) {
32697
32727
  commands.splice(1, 0, bareCommand);
@@ -32700,7 +32730,7 @@ function initTask(bare = false, path46, customArgs) {
32700
32730
  commands,
32701
32731
  format: "utf-8",
32702
32732
  parser(text) {
32703
- return parseInit(commands.includes("--bare"), path46, text);
32733
+ return parseInit(commands.includes("--bare"), path48, text);
32704
32734
  }
32705
32735
  };
32706
32736
  }
@@ -33516,12 +33546,12 @@ var init_FileStatusSummary = __esm2({
33516
33546
  "use strict";
33517
33547
  fromPathRegex = /^(.+)\0(.+)$/;
33518
33548
  FileStatusSummary = class {
33519
- constructor(path46, index, working_dir) {
33520
- this.path = path46;
33549
+ constructor(path48, index, working_dir) {
33550
+ this.path = path48;
33521
33551
  this.index = index;
33522
33552
  this.working_dir = working_dir;
33523
33553
  if (index === "R" || working_dir === "R") {
33524
- const detail = fromPathRegex.exec(path46) || [null, path46, path46];
33554
+ const detail = fromPathRegex.exec(path48) || [null, path48, path48];
33525
33555
  this.from = detail[2] || "";
33526
33556
  this.path = detail[1] || "";
33527
33557
  }
@@ -33552,14 +33582,14 @@ function splitLine(result, lineStr) {
33552
33582
  default:
33553
33583
  return;
33554
33584
  }
33555
- function data(index, workingDir, path46) {
33585
+ function data(index, workingDir, path48) {
33556
33586
  const raw = `${index}${workingDir}`;
33557
33587
  const handler = parsers6.get(raw);
33558
33588
  if (handler) {
33559
- handler(result, path46);
33589
+ handler(result, path48);
33560
33590
  }
33561
33591
  if (raw !== "##" && raw !== "!!") {
33562
- result.files.push(new FileStatusSummary(path46, index, workingDir));
33592
+ result.files.push(new FileStatusSummary(path48, index, workingDir));
33563
33593
  }
33564
33594
  }
33565
33595
  }
@@ -33868,9 +33898,9 @@ var init_simple_git_api = __esm2({
33868
33898
  next
33869
33899
  );
33870
33900
  }
33871
- hashObject(path46, write) {
33901
+ hashObject(path48, write) {
33872
33902
  return this._runTask(
33873
- hashObjectTask(path46, write === true),
33903
+ hashObjectTask(path48, write === true),
33874
33904
  trailingFunctionArgument(arguments)
33875
33905
  );
33876
33906
  }
@@ -34223,8 +34253,8 @@ var init_branch = __esm2({
34223
34253
  }
34224
34254
  });
34225
34255
  function toPath(input) {
34226
- const path46 = input.trim().replace(/^["']|["']$/g, "");
34227
- return path46 && normalize3(path46);
34256
+ const path48 = input.trim().replace(/^["']|["']$/g, "");
34257
+ return path48 && normalize3(path48);
34228
34258
  }
34229
34259
  var parseCheckIgnore;
34230
34260
  var init_CheckIgnore = __esm2({
@@ -34538,8 +34568,8 @@ __export2(sub_module_exports, {
34538
34568
  subModuleTask: () => subModuleTask,
34539
34569
  updateSubModuleTask: () => updateSubModuleTask
34540
34570
  });
34541
- function addSubModuleTask(repo, path46) {
34542
- return subModuleTask(["add", repo, path46]);
34571
+ function addSubModuleTask(repo, path48) {
34572
+ return subModuleTask(["add", repo, path48]);
34543
34573
  }
34544
34574
  function initSubModuleTask(customArgs) {
34545
34575
  return subModuleTask(["init", ...customArgs]);
@@ -34872,8 +34902,8 @@ var require_git = __commonJS2({
34872
34902
  }
34873
34903
  return this._runTask(straightThroughStringTask2(command, this._trimmed), next);
34874
34904
  };
34875
- Git2.prototype.submoduleAdd = function(repo, path46, then) {
34876
- return this._runTask(addSubModuleTask2(repo, path46), trailingFunctionArgument2(arguments));
34905
+ Git2.prototype.submoduleAdd = function(repo, path48, then) {
34906
+ return this._runTask(addSubModuleTask2(repo, path48), trailingFunctionArgument2(arguments));
34877
34907
  };
34878
34908
  Git2.prototype.submoduleUpdate = function(args, then) {
34879
34909
  return this._runTask(
@@ -35836,12 +35866,12 @@ async function collectTurnGitDiffFromPreTurnSnapshot(options) {
35836
35866
  // src/agents/acp/put-summarize-change-summaries.ts
35837
35867
  async function putEncryptedChangeSummaryRows(params) {
35838
35868
  const base = params.apiBaseUrl.replace(/\/+$/, "");
35839
- const entries = params.rows.map(({ path: path46, summary }) => {
35869
+ const entries = params.rows.map(({ path: path48, summary }) => {
35840
35870
  const enc = params.e2ee.encryptFields({ summary }, ["summary"]);
35841
- return { path: path46, summary: JSON.stringify(enc) };
35871
+ return { path: path48, summary: JSON.stringify(enc) };
35842
35872
  });
35843
35873
  const res = await fetch(
35844
- `${base}/api/sessions/${encodeURIComponent(params.sessionId)}/follow-ups/summarize-changes`,
35874
+ `${base}/internal/sessions/${encodeURIComponent(params.sessionId)}/follow-ups/summarize-changes`,
35845
35875
  {
35846
35876
  method: "PUT",
35847
35877
  headers: {
@@ -35980,8 +36010,8 @@ async function fetchSessionAttachmentPayloadsForAgent(params) {
35980
36010
  for (const a of attachments) {
35981
36011
  const id = typeof a.attachmentId === "string" ? a.attachmentId.trim() : "";
35982
36012
  if (!id) continue;
35983
- const metaUrl = `${base}/api/sessions/${encodeURIComponent(sessionId)}/attachments/${encodeURIComponent(id)}/meta`;
35984
- const blobUrl = `${base}/api/sessions/${encodeURIComponent(sessionId)}/attachments/${encodeURIComponent(id)}/blob`;
36013
+ const metaUrl = `${base}/internal/sessions/${encodeURIComponent(sessionId)}/attachments/${encodeURIComponent(id)}/meta`;
36014
+ const blobUrl = `${base}/internal/sessions/${encodeURIComponent(sessionId)}/attachments/${encodeURIComponent(id)}/blob`;
35985
36015
  const metaRes = await fetch(metaUrl, { headers: { Authorization: `Bearer ${token}` } });
35986
36016
  if (!metaRes.ok) {
35987
36017
  const t = await metaRes.text().catch(() => "");
@@ -36029,6 +36059,106 @@ async function fetchSessionAttachmentPayloadsForAgent(params) {
36029
36059
  return { ok: true, images: out };
36030
36060
  }
36031
36061
 
36062
+ // src/agents/acp/build-forked-session-agent-prompt.ts
36063
+ function buildForkedSessionAgentPrompt(userPrompt, childSessionId, parentSessionId) {
36064
+ const childRef = childSessionId.trim();
36065
+ const parentRef = parentSessionId.trim();
36066
+ return [
36067
+ "You are working in a new session that was forked from a prior session.",
36068
+ `This forked session id: ${childRef}`,
36069
+ `Parent session id: ${parentRef}`,
36070
+ "",
36071
+ "The parent session history is available through MCP tools on the buildautomaton-bridge server:",
36072
+ "- list_parent_session_prompts \u2014 list each user prompt / turn in the parent session",
36073
+ "- get_parent_session_turn_transcript \u2014 full transcript for one turn (including tool calls); requires turnId",
36074
+ "",
36075
+ "When calling these tools, always pass this forked session id as sessionId (not the parent session id).",
36076
+ "",
36077
+ "Use these tools when you need prior context instead of guessing.",
36078
+ "",
36079
+ "User request:",
36080
+ userPrompt.trim()
36081
+ ].join("\n");
36082
+ }
36083
+
36084
+ // src/mcp/tools/session-history/fetch-parent-session-cloud.ts
36085
+ function internalApiBase(cloudApiBaseUrl) {
36086
+ return cloudApiBaseUrl.replace(/\/+$/, "");
36087
+ }
36088
+ async function fetchCloudSessionMeta(params) {
36089
+ const token = params.getCloudAccessToken();
36090
+ if (!token) return { ok: false, error: "Missing cloud access token." };
36091
+ const url2 = `${internalApiBase(params.cloudApiBaseUrl)}/internal/sessions/${encodeURIComponent(params.sessionId)}/meta`;
36092
+ const res = await fetch(url2, { headers: { Authorization: `Bearer ${token}` } });
36093
+ if (!res.ok) {
36094
+ const t = await res.text().catch(() => "");
36095
+ return { ok: false, error: `Session meta fetch failed (${res.status}): ${t.slice(0, 200)}` };
36096
+ }
36097
+ const data = await res.json().catch(() => null);
36098
+ if (!data) return { ok: false, error: "Invalid session meta response." };
36099
+ return { ok: true, data };
36100
+ }
36101
+ async function fetchCloudParentSessionPrompts(params) {
36102
+ const token = params.getCloudAccessToken();
36103
+ if (!token) return { ok: false, error: "Missing cloud access token." };
36104
+ const url2 = `${internalApiBase(params.cloudApiBaseUrl)}/internal/sessions/${encodeURIComponent(params.childSessionId)}/parent/prompts`;
36105
+ const res = await fetch(url2, { headers: { Authorization: `Bearer ${token}` } });
36106
+ if (!res.ok) {
36107
+ const t = await res.text().catch(() => "");
36108
+ return { ok: false, error: `Parent prompts fetch failed (${res.status}): ${t.slice(0, 200)}` };
36109
+ }
36110
+ const data = await res.json().catch(() => null);
36111
+ if (!data) return { ok: false, error: "Invalid parent prompts response." };
36112
+ return { ok: true, data };
36113
+ }
36114
+ async function fetchCloudParentTurnTranscript(params) {
36115
+ const token = params.getCloudAccessToken();
36116
+ if (!token) return { ok: false, error: "Missing cloud access token." };
36117
+ const url2 = `${internalApiBase(params.cloudApiBaseUrl)}/internal/sessions/${encodeURIComponent(params.childSessionId)}/parent/turns/${encodeURIComponent(params.turnId)}/transcript`;
36118
+ const res = await fetch(url2, { headers: { Authorization: `Bearer ${token}` } });
36119
+ if (!res.ok) {
36120
+ const t = await res.text().catch(() => "");
36121
+ return { ok: false, error: `Transcript fetch failed (${res.status}): ${t.slice(0, 200)}` };
36122
+ }
36123
+ const data = await res.json().catch(() => null);
36124
+ const rows = Array.isArray(data?.transcript) ? data.transcript : [];
36125
+ const lines = rows.map((row) => {
36126
+ const kind = typeof row.kind === "string" ? row.kind : "event";
36127
+ const payload = typeof row.payload === "string" ? row.payload : JSON.stringify(row.payload ?? "");
36128
+ return `[${kind}] ${payload}`;
36129
+ });
36130
+ return { ok: true, text: lines.join("\n") };
36131
+ }
36132
+ async function resolveParentSessionIdForChild(params) {
36133
+ const meta = await fetchCloudSessionMeta({
36134
+ sessionId: params.childSessionId,
36135
+ cloudApiBaseUrl: params.cloudApiBaseUrl,
36136
+ getCloudAccessToken: params.getCloudAccessToken
36137
+ });
36138
+ if (!meta.ok) return meta;
36139
+ const parent = meta.data.parentSessionId?.trim() ?? "";
36140
+ if (!parent) {
36141
+ return {
36142
+ ok: false,
36143
+ error: `Session ${params.childSessionId} has no parent session history. It was not forked from another session (no parent_session_id). Pass the forked child session id as sessionId, not the parent session id.`
36144
+ };
36145
+ }
36146
+ return { ok: true, parentSessionId: parent };
36147
+ }
36148
+
36149
+ // src/agents/acp/enrich-forked-session-prompt-for-agent.ts
36150
+ async function enrichForkedSessionPromptForAgent(params) {
36151
+ const meta = await fetchCloudSessionMeta({
36152
+ sessionId: params.sessionId,
36153
+ cloudApiBaseUrl: params.cloudApiBaseUrl,
36154
+ getCloudAccessToken: params.getCloudAccessToken
36155
+ });
36156
+ if (!meta.ok) return params.promptText;
36157
+ const parentSessionId = meta.data.parentSessionId?.trim() ?? "";
36158
+ if (!parentSessionId) return params.promptText;
36159
+ return buildForkedSessionAgentPrompt(params.promptText, params.sessionId, parentSessionId);
36160
+ }
36161
+
36032
36162
  // src/agents/acp/send-prompt-to-agent.ts
36033
36163
  async function sendPromptToAgent(options) {
36034
36164
  const {
@@ -36050,6 +36180,15 @@ async function sendPromptToAgent(options) {
36050
36180
  attachments
36051
36181
  } = options;
36052
36182
  try {
36183
+ let agentPromptText = promptText;
36184
+ if (sessionId && cloudApiBaseUrl && getCloudAccessToken) {
36185
+ agentPromptText = await enrichForkedSessionPromptForAgent({
36186
+ sessionId,
36187
+ promptText,
36188
+ cloudApiBaseUrl,
36189
+ getCloudAccessToken
36190
+ });
36191
+ }
36053
36192
  let sendOpts = {};
36054
36193
  if (attachments && attachments.length > 0) {
36055
36194
  if (!sessionId || !cloudApiBaseUrl || !getCloudAccessToken) {
@@ -36088,7 +36227,7 @@ async function sendPromptToAgent(options) {
36088
36227
  }
36089
36228
  sendOpts = { images: resolved.images };
36090
36229
  }
36091
- const result = await handle.sendPrompt(promptText, sendOpts);
36230
+ const result = await handle.sendPrompt(agentPromptText, sendOpts);
36092
36231
  if (sessionId && runId && sendSessionUpdate && agentCwd && result.success) {
36093
36232
  await collectTurnGitDiffFromPreTurnSnapshot({
36094
36233
  sessionId,
@@ -36212,6 +36351,7 @@ function handlePrompt(ctx, opts) {
36212
36351
  sessionParentPath,
36213
36352
  resolveRouting: () => ctx.promptRouting.resolveRouting(activeAcpSessionAgentKey),
36214
36353
  cloudSessionId: sessionId,
36354
+ bridgeAccessPort: ctx.getBridgeAccessPort(),
36215
36355
  sendSessionUpdate,
36216
36356
  sendRequest: sendSessionUpdate,
36217
36357
  log: ctx.log,
@@ -36298,6 +36438,7 @@ function logPromptReceivedFromBridge(ctx, opts) {
36298
36438
  async function createAcpManager(options) {
36299
36439
  const ctx = createAcpManagerContext({
36300
36440
  log: options.log,
36441
+ getBridgeAccessPort: options.getBridgeAccessPort,
36301
36442
  reportAgentCapabilities: options.reportAgentCapabilities
36302
36443
  });
36303
36444
  return {
@@ -36321,7 +36462,7 @@ async function createAcpManager(options) {
36321
36462
  // src/git/changes/types.ts
36322
36463
  var MAX_PATCH_CHARS = 35e4;
36323
36464
 
36324
- // src/git/changes/lib/repo-format.ts
36465
+ // src/git/changes/paths/repo-format.ts
36325
36466
  function posixJoinDirFile(dir, file2) {
36326
36467
  const d = dir === "." || dir === "" ? "" : dir.replace(/\\/g, "/").replace(/\/+$/, "");
36327
36468
  const f = file2.replace(/\\/g, "/").replace(/^\/+/, "");
@@ -36376,7 +36517,7 @@ function formatRemoteDisplayLabel(remoteUrl) {
36376
36517
  }
36377
36518
 
36378
36519
  // src/git/changes/get-working-tree-change-repo-details.ts
36379
- import * as path21 from "node:path";
36520
+ import * as path23 from "node:path";
36380
36521
 
36381
36522
  // src/git/commits/resolve-remote-tracking.ts
36382
36523
  async function tryConfigGet(g, key) {
@@ -36546,9 +36687,232 @@ async function listRecentCommits(repoDir, limitInput) {
36546
36687
  }
36547
36688
  }
36548
36689
 
36549
- // src/git/changes/lib/parse-git-status.ts
36550
- function parseNameStatusLines(lines) {
36551
- const m = /* @__PURE__ */ new Map();
36690
+ // src/git/changes/parse/normalize-git-diff-path.ts
36691
+ function normalizeGitDiffPath(path48) {
36692
+ return path48.replace(/\\/g, "/").replace(/\/ +/g, "/").trim();
36693
+ }
36694
+
36695
+ // src/git/changes/parse/expand-git-rename-numstat-path.ts
36696
+ function expandGitRenameNumstatPath(rawPath) {
36697
+ const open2 = rawPath.indexOf("{");
36698
+ const arrow = rawPath.indexOf("=>");
36699
+ const close = rawPath.lastIndexOf("}");
36700
+ if (open2 === -1 || arrow === -1 || close === -1 || open2 > arrow || arrow > close) {
36701
+ return null;
36702
+ }
36703
+ const prefix = rawPath.slice(0, open2);
36704
+ const suffix = rawPath.slice(close + 1);
36705
+ const oldPart = rawPath.slice(open2 + 1, arrow).trim();
36706
+ const newPart = rawPath.slice(arrow + 2, close).trim();
36707
+ return {
36708
+ oldPath: normalizeGitDiffPath(`${prefix}${oldPart}${suffix}`),
36709
+ newPath: normalizeGitDiffPath(`${prefix}${newPart}${suffix}`)
36710
+ };
36711
+ }
36712
+ function isGitRenameNumstatPath(rawPath) {
36713
+ return expandGitRenameNumstatPath(rawPath) != null;
36714
+ }
36715
+
36716
+ // src/git/changes/rows/build-changed-file-row.ts
36717
+ function buildChangedFileRow(options) {
36718
+ const pathInRepo = normalizeGitDiffPath(options.pathInRepo);
36719
+ const relLauncher = posixJoinDirFile(options.repoRelPath, pathInRepo);
36720
+ const additions = options.numEntry?.additions ?? 0;
36721
+ const deletions = options.numEntry?.deletions ?? 0;
36722
+ let change = options.nameEntry?.change ?? "modified";
36723
+ let movedFromPathRelLauncher;
36724
+ let movedFromPathInRepo;
36725
+ const oldPathInRepo = options.nameEntry?.oldPathInRepo ?? options.numEntry?.oldPathInRepo;
36726
+ if (change === "moved" && oldPathInRepo) {
36727
+ movedFromPathInRepo = normalizeGitDiffPath(oldPathInRepo);
36728
+ movedFromPathRelLauncher = posixJoinDirFile(options.repoRelPath, movedFromPathInRepo);
36729
+ } else if (oldPathInRepo && change === "modified") {
36730
+ change = "moved";
36731
+ movedFromPathInRepo = normalizeGitDiffPath(oldPathInRepo);
36732
+ movedFromPathRelLauncher = posixJoinDirFile(options.repoRelPath, movedFromPathInRepo);
36733
+ }
36734
+ if (options.untracked && !options.nameEntry) {
36735
+ change = "added";
36736
+ } else if (!options.nameEntry && options.numEntry && change !== "moved") {
36737
+ if (additions > 0 && deletions === 0) change = "added";
36738
+ else if (deletions > 0 && additions === 0) change = "removed";
36739
+ else change = "modified";
36740
+ }
36741
+ return {
36742
+ pathRelLauncher: relLauncher,
36743
+ additions,
36744
+ deletions,
36745
+ change,
36746
+ ...movedFromPathRelLauncher ? { movedFromPathRelLauncher } : {},
36747
+ ...movedFromPathInRepo ? { movedFromPathInRepo } : {}
36748
+ };
36749
+ }
36750
+
36751
+ // src/git/changes/listing/build-changed-file-rows.ts
36752
+ async function buildChangedFileRowsFromPaths(options) {
36753
+ const rows = [];
36754
+ await forEachWithGitYield([...options.paths], async (pathInRepo) => {
36755
+ if (isGitRenameNumstatPath(pathInRepo)) return;
36756
+ if (options.movedSourcePaths.has(pathInRepo)) return;
36757
+ const nameEntry = options.nameByPath.get(pathInRepo);
36758
+ const numEntry = options.numByPath.get(pathInRepo);
36759
+ const row = buildChangedFileRow({
36760
+ pathInRepo,
36761
+ repoRelPath: options.repoRelPath,
36762
+ nameEntry,
36763
+ numEntry,
36764
+ untracked: options.untrackedSet?.has(pathInRepo)
36765
+ });
36766
+ if (options.applyUntrackedStats) {
36767
+ await options.applyUntrackedStats(row, pathInRepo);
36768
+ }
36769
+ rows.push(row);
36770
+ });
36771
+ return rows;
36772
+ }
36773
+
36774
+ // src/git/changes/patch/patch-truncate.ts
36775
+ function truncatePatch(s) {
36776
+ if (s.length <= MAX_PATCH_CHARS) return s;
36777
+ return `${s.slice(0, MAX_PATCH_CHARS)}
36778
+
36779
+ \u2026 (diff truncated)`;
36780
+ }
36781
+
36782
+ // src/git/changes/patch/unified-diff-for-file.ts
36783
+ function patchTextFromGitDiffOutput(raw) {
36784
+ if (raw.trim() === "") return void 0;
36785
+ return normalizePatchContent(raw);
36786
+ }
36787
+ async function unifiedDiffForFile(repoCwd, pathInRepo, change, movedFromPathInRepo) {
36788
+ const g = cliSimpleGit(repoCwd);
36789
+ const renameArgs = ["-M", "--find-renames"];
36790
+ const args = change === "moved" && movedFromPathInRepo ? ["diff", "--no-color", ...renameArgs, "HEAD", "--", movedFromPathInRepo, pathInRepo] : change === "removed" ? ["diff", "--no-color", ...renameArgs, "HEAD", "--", pathInRepo] : ["diff", "--no-color", ...renameArgs, "HEAD", "--", pathInRepo];
36791
+ const raw = await g.raw([...args]).catch(() => "");
36792
+ const patch = patchTextFromGitDiffOutput(String(raw));
36793
+ return patch ? truncatePatch(patch) : void 0;
36794
+ }
36795
+ async function unifiedDiffForFileInRange(repoCwd, range, pathInRepo, change, movedFromPathInRepo) {
36796
+ const g = cliSimpleGit(repoCwd);
36797
+ const renameArgs = ["-M", "--find-renames"];
36798
+ const args = change === "moved" && movedFromPathInRepo ? ["diff", "--no-color", ...renameArgs, "-U20000", range, "--", movedFromPathInRepo, pathInRepo] : ["diff", "--no-color", ...renameArgs, "-U20000", range, "--", pathInRepo];
36799
+ const raw = await g.raw([...args]).catch(() => "");
36800
+ const patch = patchTextFromGitDiffOutput(String(raw));
36801
+ return patch ? truncatePatch(patch) : void 0;
36802
+ }
36803
+
36804
+ // src/git/changes/total-lines/resolve-changed-file-total-lines.ts
36805
+ import * as fs17 from "node:fs";
36806
+ import * as path20 from "node:path";
36807
+
36808
+ // src/git/changes/lines/count-lines.ts
36809
+ import { createReadStream } from "node:fs";
36810
+ import * as readline2 from "node:readline";
36811
+ function countLinesInText(text) {
36812
+ return splitTextIntoDiffLines(text).length;
36813
+ }
36814
+ async function countTextFileLines(filePath) {
36815
+ let bytes = 0;
36816
+ const maxBytes = 512e3;
36817
+ let lines = 0;
36818
+ const stream = createReadStream(filePath, { encoding: "utf8" });
36819
+ const rl = readline2.createInterface({ input: stream, crlfDelay: Infinity });
36820
+ for await (const _line of rl) {
36821
+ lines += 1;
36822
+ bytes += Buffer.byteLength(String(_line), "utf8") + 1;
36823
+ if (bytes > maxBytes) {
36824
+ rl.close();
36825
+ stream.destroy();
36826
+ return lines;
36827
+ }
36828
+ }
36829
+ return lines;
36830
+ }
36831
+
36832
+ // src/git/changes/total-lines/resolve-changed-file-total-lines.ts
36833
+ var MAX_BYTES = 512e3;
36834
+ async function countLinesInGitRef(repoGitCwd, ref, pathInRepo) {
36835
+ const spec = `${ref}:${pathInRepo}`;
36836
+ const g = cliSimpleGit(repoGitCwd);
36837
+ try {
36838
+ await g.raw(["cat-file", "-e", spec]);
36839
+ } catch {
36840
+ return null;
36841
+ }
36842
+ try {
36843
+ const raw = String(await g.raw(["show", spec]));
36844
+ const capped = raw.length > MAX_BYTES ? raw.slice(0, MAX_BYTES) : raw;
36845
+ return countLinesInText(capped);
36846
+ } catch {
36847
+ return null;
36848
+ }
36849
+ }
36850
+ async function resolveWorkingTreeFileTotalLines(options) {
36851
+ if (options.isBinary) return null;
36852
+ const filePath = path20.join(options.repoGitCwd, options.pathInRepo);
36853
+ if (options.change === "removed") {
36854
+ return countLinesInGitRef(options.repoGitCwd, "HEAD", options.pathInRepo);
36855
+ }
36856
+ if (options.change === "moved" || options.change === "modified") {
36857
+ try {
36858
+ const st = await fs17.promises.stat(filePath);
36859
+ if (!st.isFile()) return null;
36860
+ return await countTextFileLines(filePath);
36861
+ } catch {
36862
+ return null;
36863
+ }
36864
+ }
36865
+ return null;
36866
+ }
36867
+ async function resolveCommitFileTotalLines(options) {
36868
+ if (options.isBinary) return null;
36869
+ if (options.change === "removed") {
36870
+ return countLinesInGitRef(options.repoGitCwd, options.parentSha, options.pathInRepo);
36871
+ }
36872
+ if (options.change === "moved" || options.change === "modified") {
36873
+ return countLinesInGitRef(options.repoGitCwd, options.commitSha, options.pathInRepo);
36874
+ }
36875
+ return null;
36876
+ }
36877
+
36878
+ // src/git/changes/listing/path-in-repo-from-launcher.ts
36879
+ function pathInRepoFromLauncher(pathRelLauncher, repoRelPath) {
36880
+ const normRel = repoRelPath === "." || repoRelPath === "" ? "." : repoRelPath;
36881
+ if (normRel === ".") return pathRelLauncher;
36882
+ if (pathRelLauncher.startsWith(`${normRel}/`)) {
36883
+ return pathRelLauncher.slice(normRel.length + 1);
36884
+ }
36885
+ return pathRelLauncher;
36886
+ }
36887
+ function normalizeRepoRelPath(repoRelPath) {
36888
+ return repoRelPath === "." || repoRelPath === "" ? "." : repoRelPath;
36889
+ }
36890
+
36891
+ // src/git/changes/listing/enrich-commit-file-rows.ts
36892
+ async function enrichCommitFileRows(options) {
36893
+ await forEachWithGitYield(options.rows, async (row) => {
36894
+ const pathInRepo = pathInRepoFromLauncher(row.pathRelLauncher, options.repoRelPath);
36895
+ row.patchContent = await unifiedDiffForFileInRange(
36896
+ options.repoGitCwd,
36897
+ options.range,
36898
+ pathInRepo,
36899
+ row.change,
36900
+ row.movedFromPathInRepo
36901
+ );
36902
+ row.totalLines = await resolveCommitFileTotalLines({
36903
+ repoGitCwd: options.repoGitCwd,
36904
+ pathInRepo,
36905
+ change: row.change,
36906
+ commitSha: options.commitSha,
36907
+ parentSha: options.parentSha,
36908
+ isBinary: options.binaryByPath.has(pathInRepo)
36909
+ });
36910
+ });
36911
+ }
36912
+
36913
+ // src/git/changes/parse/parse-name-status-entries.ts
36914
+ function parseNameStatusEntries(lines) {
36915
+ const entries = [];
36552
36916
  for (const line of lines) {
36553
36917
  if (!line.trim()) continue;
36554
36918
  const tabParts = line.split(" ");
@@ -36556,58 +36920,143 @@ function parseNameStatusLines(lines) {
36556
36920
  const status = tabParts[0].trim();
36557
36921
  const code = status[0];
36558
36922
  if (code === "A") {
36559
- m.set(tabParts[tabParts.length - 1], "added");
36923
+ entries.push({ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]), change: "added" });
36560
36924
  } else if (code === "D") {
36561
- m.set(tabParts[tabParts.length - 1], "removed");
36925
+ entries.push({ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]), change: "removed" });
36562
36926
  } else if (code === "R" || code === "C") {
36563
- if (tabParts.length >= 3) m.set(tabParts[tabParts.length - 1], "modified");
36927
+ if (tabParts.length >= 3) {
36928
+ entries.push({
36929
+ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]),
36930
+ oldPathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 2]),
36931
+ change: "moved"
36932
+ });
36933
+ }
36564
36934
  } else if (code === "M" || code === "U" || code === "T") {
36565
- m.set(tabParts[tabParts.length - 1], "modified");
36935
+ entries.push({ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]), change: "modified" });
36566
36936
  }
36567
36937
  }
36938
+ return entries;
36939
+ }
36940
+ function parseNameStatusLines(lines) {
36941
+ const m = /* @__PURE__ */ new Map();
36942
+ for (const entry of parseNameStatusEntries(lines)) {
36943
+ m.set(entry.pathInRepo, entry.change);
36944
+ }
36568
36945
  return m;
36569
36946
  }
36570
- function parseNumstatFirstLine(line) {
36947
+
36948
+ // src/git/changes/parse/parse-numstat-entries.ts
36949
+ function parseNumstatCounts(parts) {
36950
+ const [a, d] = parts;
36951
+ return {
36952
+ additions: a === "-" ? 0 : parseInt(String(a), 10) || 0,
36953
+ deletions: d === "-" ? 0 : parseInt(String(d), 10) || 0
36954
+ };
36955
+ }
36956
+ function parseNumstatLine(line) {
36571
36957
  const parts = line.split(" ");
36572
36958
  if (parts.length < 3) return null;
36573
- const [a, d] = parts;
36574
- const additions = a === "-" ? 0 : parseInt(String(a), 10) || 0;
36575
- const deletions = d === "-" ? 0 : parseInt(String(d), 10) || 0;
36576
- return { additions, deletions };
36959
+ const rawPath = parts[parts.length - 1];
36960
+ const { additions, deletions } = parseNumstatCounts(parts);
36961
+ const expanded = expandGitRenameNumstatPath(rawPath);
36962
+ const pathInRepo = normalizeGitDiffPath(expanded?.newPath ?? rawPath);
36963
+ return {
36964
+ pathInRepo,
36965
+ additions,
36966
+ deletions,
36967
+ ...expanded ? { oldPathInRepo: expanded.oldPath } : {}
36968
+ };
36577
36969
  }
36578
- function parseNumstat(lines) {
36970
+ function parseNumstatEntries(lines) {
36579
36971
  const m = /* @__PURE__ */ new Map();
36580
36972
  for (const line of lines) {
36581
36973
  if (!line.trim()) continue;
36582
36974
  const parts = line.split(" ");
36583
36975
  if (parts.length < 3) continue;
36584
- const [a, d, p] = parts;
36585
- const additions = a === "-" ? 0 : parseInt(String(a), 10) || 0;
36586
- const deletions = d === "-" ? 0 : parseInt(String(d), 10) || 0;
36587
- m.set(p, { additions, deletions });
36976
+ const rawPath = parts[parts.length - 1];
36977
+ const expanded = expandGitRenameNumstatPath(rawPath);
36978
+ const entry = parseNumstatLine(line);
36979
+ if (!entry) continue;
36980
+ const existing = m.get(entry.pathInRepo);
36981
+ if (!existing || expanded) {
36982
+ m.set(entry.pathInRepo, entry);
36983
+ }
36588
36984
  }
36589
36985
  return m;
36590
36986
  }
36987
+ function parseNumstat(lines) {
36988
+ const m = /* @__PURE__ */ new Map();
36989
+ for (const [path48, entry] of parseNumstatEntries(lines)) {
36990
+ m.set(path48, { additions: entry.additions, deletions: entry.deletions });
36991
+ }
36992
+ return m;
36993
+ }
36994
+
36995
+ // src/git/changes/parse/numstat-from-git-no-index.ts
36591
36996
  async function numstatFromGitNoIndex(g, pathInRepo) {
36592
36997
  const devNull = process.platform === "win32" ? "NUL" : "/dev/null";
36593
36998
  try {
36594
36999
  const out = await g.raw(["diff", "--numstat", "--no-index", "--", devNull, pathInRepo]);
36595
37000
  const first2 = String(out).split("\n").find((l) => l.trim()) ?? "";
36596
- return parseNumstatFirstLine(first2);
37001
+ const parsed = parseNumstatLine(first2);
37002
+ if (!parsed) return null;
37003
+ return { additions: parsed.additions, deletions: parsed.deletions };
36597
37004
  } catch {
36598
37005
  return null;
36599
37006
  }
36600
37007
  }
36601
37008
 
36602
- // src/git/changes/lib/patch-truncate.ts
36603
- function truncatePatch(s) {
36604
- if (s.length <= MAX_PATCH_CHARS) return s;
36605
- return `${s.slice(0, MAX_PATCH_CHARS)}
37009
+ // src/git/changes/rows/collect-moved-source-paths.ts
37010
+ function collectMovedSourcePaths(nameEntries, numByPath) {
37011
+ const sources = /* @__PURE__ */ new Set();
37012
+ for (const entry of nameEntries) {
37013
+ if (entry.change === "moved" && entry.oldPathInRepo) sources.add(entry.oldPathInRepo);
37014
+ }
37015
+ for (const entry of numByPath.values()) {
37016
+ if (entry.oldPathInRepo) sources.add(entry.oldPathInRepo);
37017
+ }
37018
+ return sources;
37019
+ }
36606
37020
 
36607
- \u2026 (diff truncated)`;
37021
+ // src/git/changes/numstat/is-binary-numstat-line.ts
37022
+ function isBinaryNumstatLine(line) {
37023
+ const parts = line.split(" ");
37024
+ if (parts.length < 3) return false;
37025
+ return parts[0] === "-" || parts[1] === "-";
37026
+ }
37027
+
37028
+ // src/git/changes/listing/collect-binary-paths-from-numstat.ts
37029
+ function collectBinaryPathsFromNumstat(numstatRaw) {
37030
+ const binaryByPath = /* @__PURE__ */ new Set();
37031
+ for (const line of numstatRaw.split("\n")) {
37032
+ if (!line.trim()) continue;
37033
+ const parts = line.split(" ");
37034
+ if (parts.length < 3) continue;
37035
+ const rawPath = parts[parts.length - 1];
37036
+ const expanded = expandGitRenameNumstatPath(rawPath);
37037
+ if (isBinaryNumstatLine(line)) binaryByPath.add(expanded?.newPath ?? rawPath);
37038
+ }
37039
+ return binaryByPath;
36608
37040
  }
36609
37041
 
36610
- // src/git/commits/list-changed-files-for-commit.ts
37042
+ // src/git/changes/listing/parse-diff-output.ts
37043
+ function parseDiffOutput(nameStatusRaw, numstatRaw) {
37044
+ const nameEntries = parseNameStatusEntries(nameStatusRaw.split("\n"));
37045
+ const nameByPath = new Map(nameEntries.map((entry) => [entry.pathInRepo, entry]));
37046
+ const numByPath = parseNumstatEntries(numstatRaw.split("\n"));
37047
+ const movedSourcePaths = collectMovedSourcePaths(nameEntries, numByPath);
37048
+ const binaryByPath = collectBinaryPathsFromNumstat(numstatRaw);
37049
+ return { nameEntries, nameByPath, numByPath, movedSourcePaths, binaryByPath };
37050
+ }
37051
+ function collectChangedPaths(options) {
37052
+ const paths = /* @__PURE__ */ new Set();
37053
+ for (const entry of options.nameEntries) paths.add(entry.pathInRepo);
37054
+ for (const pathInRepo of options.numByPath.keys()) paths.add(pathInRepo);
37055
+ for (const pathInRepo of options.untracked ?? []) paths.add(pathInRepo);
37056
+ return paths;
37057
+ }
37058
+
37059
+ // src/git/changes/listing/parent-for-commit-diff.ts
36611
37060
  var EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
36612
37061
  async function parentForCommitDiff(g, sha) {
36613
37062
  try {
@@ -36620,76 +37069,112 @@ async function parentForCommitDiff(g, sha) {
36620
37069
  }
36621
37070
  }
36622
37071
  }
37072
+
37073
+ // src/git/changes/listing/rename-diff-args.ts
37074
+ var RENAME_DIFF_ARGS = ["-M", "--find-renames"];
37075
+
37076
+ // src/git/changes/rows/pick-preferred-changed-file-row.ts
37077
+ function rowRank(row) {
37078
+ if (row.change === "moved") return 3;
37079
+ if (row.change === "modified") return 2;
37080
+ if (row.change === "added") return 1;
37081
+ return 0;
37082
+ }
37083
+ function pickPreferredChangedFileRow(a, b) {
37084
+ if (rowRank(a) !== rowRank(b)) return rowRank(a) > rowRank(b) ? a : b;
37085
+ const aHasPatch = Boolean(a.patchContent?.trim());
37086
+ const bHasPatch = Boolean(b.patchContent?.trim());
37087
+ if (aHasPatch !== bHasPatch) return aHasPatch ? a : b;
37088
+ const aHasMoveMeta = Boolean(a.movedFromPathInRepo || a.movedFromPathRelLauncher);
37089
+ const bHasMoveMeta = Boolean(b.movedFromPathInRepo || b.movedFromPathRelLauncher);
37090
+ if (aHasMoveMeta !== bHasMoveMeta) return aHasMoveMeta ? a : b;
37091
+ return a;
37092
+ }
37093
+
37094
+ // src/git/changes/rows/dedupe-changed-file-rows.ts
37095
+ function dedupeChangedFileRows(rows) {
37096
+ const byPath = /* @__PURE__ */ new Map();
37097
+ for (const row of rows) {
37098
+ const key = normalizeGitDiffPath(row.pathRelLauncher);
37099
+ const normalizedRow = key === row.pathRelLauncher ? row : { ...row, pathRelLauncher: key };
37100
+ const existing = byPath.get(key);
37101
+ if (!existing) {
37102
+ byPath.set(key, normalizedRow);
37103
+ continue;
37104
+ }
37105
+ const preferRow = pickPreferredChangedFileRow(existing, normalizedRow);
37106
+ byPath.set(key, { ...preferRow, pathRelLauncher: key });
37107
+ }
37108
+ return [...byPath.values()];
37109
+ }
37110
+
37111
+ // src/git/changes/listing/list-changed-files-for-commit.ts
36623
37112
  async function listChangedFilesForCommit(repoGitCwd, repoRelPath, commitSha) {
36624
37113
  const g = cliSimpleGit(repoGitCwd);
36625
37114
  const parent = await parentForCommitDiff(g, commitSha);
36626
37115
  const range = `${parent}..${commitSha}`;
36627
37116
  const [nameStatusRaw, numstatRaw] = await Promise.all([
36628
- g.raw(["diff", "--name-status", range]).catch(() => ""),
36629
- g.raw(["diff", "--numstat", range]).catch(() => "")
37117
+ g.raw(["diff", ...RENAME_DIFF_ARGS, "--name-status", range]).catch(() => ""),
37118
+ g.raw(["diff", ...RENAME_DIFF_ARGS, "--numstat", range]).catch(() => "")
36630
37119
  ]);
36631
- const kindByPath = parseNameStatusLines(String(nameStatusRaw).split("\n"));
36632
- const numByPath = parseNumstat(String(numstatRaw).split("\n"));
36633
- const paths = new Set([...kindByPath.keys(), ...numByPath.keys()].filter(Boolean));
36634
- const rows = [];
36635
- const normRel = repoRelPath === "." || repoRelPath === "" ? "." : repoRelPath;
36636
- await forEachWithGitYield([...paths], async (pathInRepo) => {
36637
- const relLauncher = posixJoinDirFile(normRel, pathInRepo.replace(/\\/g, "/"));
36638
- const nums = numByPath.get(pathInRepo);
36639
- let additions = nums?.additions ?? 0;
36640
- let deletions = nums?.deletions ?? 0;
36641
- let change = kindByPath.get(pathInRepo) ?? "modified";
36642
- if (!kindByPath.has(pathInRepo) && nums) {
36643
- if (additions > 0 && deletions === 0) change = "added";
36644
- else if (deletions > 0 && additions === 0) change = "removed";
36645
- else change = "modified";
36646
- }
36647
- rows.push({ pathRelLauncher: relLauncher, additions, deletions, change });
36648
- });
36649
- await forEachWithGitYield(rows, async (row) => {
36650
- let pathInRepo;
36651
- if (normRel === ".") {
36652
- pathInRepo = row.pathRelLauncher;
36653
- } else if (row.pathRelLauncher.startsWith(`${normRel}/`)) {
36654
- pathInRepo = row.pathRelLauncher.slice(normRel.length + 1);
36655
- } else {
36656
- pathInRepo = row.pathRelLauncher;
36657
- }
36658
- const raw = await g.raw(["diff", "-U20000", range, "--", pathInRepo]).catch(() => "");
36659
- const t = String(raw).trim();
36660
- row.patchContent = t ? truncatePatch(t) : void 0;
37120
+ const parsed = parseDiffOutput(String(nameStatusRaw), String(numstatRaw));
37121
+ const normRel = normalizeRepoRelPath(repoRelPath);
37122
+ const paths = collectChangedPaths({
37123
+ nameEntries: parsed.nameEntries,
37124
+ numByPath: parsed.numByPath
36661
37125
  });
36662
- rows.sort((a, b) => a.pathRelLauncher.localeCompare(b.pathRelLauncher));
36663
- return rows;
36664
- }
36665
-
36666
- // src/git/changes/list-changed-files-for-repo.ts
37126
+ let rows = await buildChangedFileRowsFromPaths({
37127
+ paths,
37128
+ repoRelPath: normRel,
37129
+ nameByPath: parsed.nameByPath,
37130
+ numByPath: parsed.numByPath,
37131
+ movedSourcePaths: parsed.movedSourcePaths
37132
+ });
37133
+ rows = dedupeChangedFileRows(rows);
37134
+ await enrichCommitFileRows({
37135
+ rows,
37136
+ repoGitCwd,
37137
+ repoRelPath: normRel,
37138
+ range,
37139
+ commitSha,
37140
+ parentSha: parent,
37141
+ binaryByPath: parsed.binaryByPath
37142
+ });
37143
+ const finalRows = dedupeChangedFileRows(rows);
37144
+ finalRows.sort((a, b) => a.pathRelLauncher.localeCompare(b.pathRelLauncher));
37145
+ return finalRows;
37146
+ }
37147
+
37148
+ // src/git/changes/listing/apply-untracked-file-stats.ts
36667
37149
  import * as fs18 from "node:fs";
36668
- import * as path20 from "node:path";
36669
-
36670
- // src/git/changes/lib/count-lines.ts
36671
- import { createReadStream } from "node:fs";
36672
- import * as readline2 from "node:readline";
36673
- async function countTextFileLines(filePath) {
36674
- let bytes = 0;
36675
- const maxBytes = 512e3;
36676
- let lines = 0;
36677
- const stream = createReadStream(filePath, { encoding: "utf8" });
36678
- const rl = readline2.createInterface({ input: stream, crlfDelay: Infinity });
36679
- for await (const _line of rl) {
36680
- lines += 1;
36681
- bytes += Buffer.byteLength(String(_line), "utf8") + 1;
36682
- if (bytes > maxBytes) {
36683
- rl.close();
36684
- stream.destroy();
36685
- return lines;
37150
+ import * as path21 from "node:path";
37151
+ function createUntrackedStatsApplier(options) {
37152
+ return async (row, pathInRepo) => {
37153
+ if (!options.untrackedSet.has(pathInRepo)) return;
37154
+ if (options.nameByPath.has(pathInRepo)) return;
37155
+ if (row.change === "moved") return;
37156
+ const repoFilePath = path21.join(options.repoGitCwd, pathInRepo);
37157
+ const fromGit = await numstatFromGitNoIndex(options.g, pathInRepo);
37158
+ if (fromGit) {
37159
+ row.additions = fromGit.additions;
37160
+ row.deletions = fromGit.deletions;
37161
+ return;
36686
37162
  }
36687
- }
36688
- return lines;
37163
+ try {
37164
+ const st = await fs18.promises.stat(repoFilePath);
37165
+ row.additions = st.isFile() ? await countTextFileLines(repoFilePath) : 0;
37166
+ } catch {
37167
+ row.additions = 0;
37168
+ }
37169
+ row.deletions = 0;
37170
+ };
36689
37171
  }
36690
37172
 
36691
- // src/git/changes/hydrate-patch.ts
36692
- import * as fs17 from "node:fs";
37173
+ // src/git/changes/listing/enrich-working-tree-file-rows.ts
37174
+ import * as path22 from "node:path";
37175
+
37176
+ // src/git/changes/patch/hydrate-patch.ts
37177
+ import * as fs19 from "node:fs";
36693
37178
  var UNIFIED_HUNK_HEADER_RE = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
36694
37179
  var MAX_HYDRATE_LINES_PER_GAP = 8e3;
36695
37180
  var MAX_HYDRATE_LINES_PER_FILE = 8e4;
@@ -36704,7 +37189,7 @@ async function readGitBlobLines(repoCwd, pathInRepo) {
36704
37189
  }
36705
37190
  async function readWorktreeFileLines(filePath) {
36706
37191
  try {
36707
- const raw = await fs17.promises.readFile(filePath, "utf8");
37192
+ const raw = await fs19.promises.readFile(filePath, "utf8");
36708
37193
  return raw.split(/\r?\n/);
36709
37194
  } catch {
36710
37195
  return null;
@@ -36804,82 +37289,70 @@ async function hydrateUnifiedPatchWithFileContext(patch, filePath, repoGitCwd, p
36804
37289
  return truncatePatch(out.join("\n"));
36805
37290
  }
36806
37291
 
36807
- // src/git/changes/unified-diff-for-file.ts
36808
- function patchTextFromGitDiffOutput(raw) {
36809
- if (raw.trim() === "") return void 0;
36810
- return normalizePatchContent(raw);
36811
- }
36812
- async function unifiedDiffForFile(repoCwd, pathInRepo, change) {
36813
- const g = cliSimpleGit(repoCwd);
36814
- const args = change === "added" ? ["diff", "--no-color", "HEAD", "--", pathInRepo] : change === "removed" ? ["diff", "--no-color", "HEAD", "--", pathInRepo] : ["diff", "--no-color", "HEAD", "--", pathInRepo];
36815
- const raw = await g.raw([...args]).catch(() => "");
36816
- const patch = patchTextFromGitDiffOutput(String(raw));
36817
- return patch ? truncatePatch(patch) : void 0;
37292
+ // src/git/changes/listing/enrich-working-tree-file-rows.ts
37293
+ async function enrichWorkingTreeFileRows(options) {
37294
+ await forEachWithGitYield(options.rows, async (row) => {
37295
+ const pathInRepo = pathInRepoFromLauncher(row.pathRelLauncher, options.repoRelPath);
37296
+ const filePath = path22.join(options.repoGitCwd, pathInRepo);
37297
+ const hydrateKind = row.change === "moved" ? "modified" : row.change;
37298
+ let patch = await unifiedDiffForFile(options.repoGitCwd, pathInRepo, row.change, row.movedFromPathInRepo);
37299
+ if (patch) {
37300
+ patch = await hydrateUnifiedPatchWithFileContext(
37301
+ patch,
37302
+ filePath,
37303
+ options.repoGitCwd,
37304
+ pathInRepo,
37305
+ hydrateKind
37306
+ );
37307
+ }
37308
+ row.patchContent = patch;
37309
+ row.totalLines = await resolveWorkingTreeFileTotalLines({
37310
+ repoGitCwd: options.repoGitCwd,
37311
+ pathInRepo,
37312
+ change: row.change,
37313
+ isBinary: options.binaryByPath.has(pathInRepo)
37314
+ });
37315
+ });
36818
37316
  }
36819
37317
 
36820
- // src/git/changes/list-changed-files-for-repo.ts
37318
+ // src/git/changes/listing/list-changed-files-for-repo.ts
36821
37319
  async function listChangedFilesForRepo(repoGitCwd, repoRelPath) {
36822
37320
  const g = cliSimpleGit(repoGitCwd);
36823
37321
  const [nameStatusRaw, numstatRaw, untrackedRaw] = await Promise.all([
36824
- g.raw(["diff", "--name-status", "HEAD"]).catch(() => ""),
36825
- g.raw(["diff", "HEAD", "--numstat"]).catch(() => ""),
37322
+ g.raw(["diff", ...RENAME_DIFF_ARGS, "--name-status", "HEAD"]).catch(() => ""),
37323
+ g.raw(["diff", ...RENAME_DIFF_ARGS, "HEAD", "--numstat"]).catch(() => ""),
36826
37324
  g.raw(["ls-files", "--others", "--exclude-standard"]).catch(() => "")
36827
37325
  ]);
36828
- const kindByPath = parseNameStatusLines(String(nameStatusRaw).split("\n"));
36829
- const numByPath = parseNumstat(String(numstatRaw).split("\n"));
36830
- const paths = /* @__PURE__ */ new Set([...kindByPath.keys(), ...numByPath.keys()]);
37326
+ const parsed = parseDiffOutput(String(nameStatusRaw), String(numstatRaw));
36831
37327
  const untracked = String(untrackedRaw).split("\n").map((s) => s.trim()).filter(Boolean);
36832
- for (const p of untracked) paths.add(p);
36833
- const rows = [];
36834
- await forEachWithGitYield([...paths], async (pathInRepo) => {
36835
- const relLauncher = posixJoinDirFile(repoRelPath, pathInRepo.replace(/\\/g, "/"));
36836
- const repoFilePath = path20.join(repoGitCwd, pathInRepo);
36837
- const nums = numByPath.get(pathInRepo);
36838
- let additions = nums?.additions ?? 0;
36839
- let deletions = nums?.deletions ?? 0;
36840
- let change = kindByPath.get(pathInRepo) ?? "modified";
36841
- if (untracked.includes(pathInRepo) && !kindByPath.has(pathInRepo)) {
36842
- change = "added";
36843
- const fromGit = await numstatFromGitNoIndex(g, pathInRepo);
36844
- if (fromGit) {
36845
- additions = fromGit.additions;
36846
- deletions = fromGit.deletions;
36847
- } else {
36848
- try {
36849
- const st = await fs18.promises.stat(repoFilePath);
36850
- if (st.isFile()) additions = await countTextFileLines(repoFilePath);
36851
- else additions = 0;
36852
- } catch {
36853
- additions = 0;
36854
- }
36855
- deletions = 0;
36856
- }
36857
- }
36858
- if (!kindByPath.has(pathInRepo) && nums) {
36859
- if (additions > 0 && deletions === 0) change = "added";
36860
- else if (deletions > 0 && additions === 0) change = "removed";
36861
- else change = "modified";
36862
- }
36863
- rows.push({ pathRelLauncher: relLauncher, additions, deletions, change });
37328
+ const untrackedSet = new Set(untracked);
37329
+ const paths = collectChangedPaths({
37330
+ nameEntries: parsed.nameEntries,
37331
+ numByPath: parsed.numByPath,
37332
+ untracked
36864
37333
  });
36865
- const normRel = repoRelPath === "." || repoRelPath === "" ? "." : repoRelPath;
36866
- await forEachWithGitYield(rows, async (row) => {
36867
- let pathInRepo;
36868
- if (normRel === ".") {
36869
- pathInRepo = row.pathRelLauncher;
36870
- } else if (row.pathRelLauncher.startsWith(`${normRel}/`)) {
36871
- pathInRepo = row.pathRelLauncher.slice(normRel.length + 1);
36872
- } else {
36873
- pathInRepo = row.pathRelLauncher;
36874
- }
36875
- const filePath = path20.join(repoGitCwd, pathInRepo);
36876
- let patch = await unifiedDiffForFile(repoGitCwd, pathInRepo, row.change);
36877
- if (patch) {
36878
- patch = await hydrateUnifiedPatchWithFileContext(patch, filePath, repoGitCwd, pathInRepo, row.change);
36879
- }
36880
- row.patchContent = patch;
37334
+ let rows = await buildChangedFileRowsFromPaths({
37335
+ paths,
37336
+ repoRelPath,
37337
+ nameByPath: parsed.nameByPath,
37338
+ numByPath: parsed.numByPath,
37339
+ movedSourcePaths: parsed.movedSourcePaths,
37340
+ untrackedSet,
37341
+ applyUntrackedStats: createUntrackedStatsApplier({
37342
+ g,
37343
+ repoGitCwd,
37344
+ untrackedSet,
37345
+ nameByPath: parsed.nameByPath
37346
+ })
36881
37347
  });
36882
- return rows;
37348
+ rows = dedupeChangedFileRows(rows);
37349
+ await enrichWorkingTreeFileRows({
37350
+ rows,
37351
+ repoGitCwd,
37352
+ repoRelPath,
37353
+ binaryByPath: parsed.binaryByPath
37354
+ });
37355
+ return dedupeChangedFileRows(rows);
36883
37356
  }
36884
37357
 
36885
37358
  // src/git/changes/get-working-tree-change-repo-details.ts
@@ -36888,8 +37361,8 @@ function normRepoRel(p) {
36888
37361
  return x === "" ? "." : x;
36889
37362
  }
36890
37363
  async function getWorkingTreeChangeRepoDetails(options) {
36891
- const bridgeRoot = path21.resolve(getBridgeRoot());
36892
- const sessionWtRoot = options.sessionWorktreeRootPath ? path21.resolve(options.sessionWorktreeRootPath) : null;
37364
+ const bridgeRoot = path23.resolve(getBridgeRoot());
37365
+ const sessionWtRoot = options.sessionWorktreeRootPath ? path23.resolve(options.sessionWorktreeRootPath) : null;
36893
37366
  const legacyNested = options.legacyRepoNestedSessionLayout === true;
36894
37367
  const out = [];
36895
37368
  const filter = options.repoFilterRelPath != null ? normRepoRel(options.repoFilterRelPath) : null;
@@ -36904,7 +37377,7 @@ async function getWorkingTreeChangeRepoDetails(options) {
36904
37377
  for (let i = 0; i < options.commitTargetPaths.length; i++) {
36905
37378
  if (i > 0) await yieldToEventLoop2();
36906
37379
  const target = options.commitTargetPaths[i];
36907
- const t = path21.resolve(target);
37380
+ const t = path23.resolve(target);
36908
37381
  if (!await isGitRepoDirectory(t)) continue;
36909
37382
  const g = cliSimpleGit(t);
36910
37383
  let branch = "HEAD";
@@ -36917,8 +37390,8 @@ async function getWorkingTreeChangeRepoDetails(options) {
36917
37390
  const remoteDisplay = formatRemoteDisplayLabel(remoteUrl);
36918
37391
  let repoRelPath;
36919
37392
  if (sessionWtRoot) {
36920
- const anchor = legacyNested ? path21.dirname(t) : t;
36921
- const relNorm = path21.relative(sessionWtRoot, anchor);
37393
+ const anchor = legacyNested ? path23.dirname(t) : t;
37394
+ const relNorm = path23.relative(sessionWtRoot, anchor);
36922
37395
  repoRelPath = relNorm === "" ? "." : relNorm.replace(/\\/g, "/");
36923
37396
  } else {
36924
37397
  let top = t;
@@ -36927,8 +37400,8 @@ async function getWorkingTreeChangeRepoDetails(options) {
36927
37400
  } catch {
36928
37401
  top = t;
36929
37402
  }
36930
- const rel = path21.relative(bridgeRoot, path21.resolve(top)).replace(/\\/g, "/") || ".";
36931
- repoRelPath = rel.startsWith("..") ? path21.basename(path21.resolve(top)) : rel;
37403
+ const rel = path23.relative(bridgeRoot, path23.resolve(top)).replace(/\\/g, "/") || ".";
37404
+ repoRelPath = rel.startsWith("..") ? path23.basename(path23.resolve(top)) : rel;
36932
37405
  }
36933
37406
  const norm = normRepoRel(repoRelPath === "" ? "." : repoRelPath);
36934
37407
  if (filter && norm !== filter) continue;
@@ -36960,7 +37433,7 @@ async function getWorkingTreeChangeRepoDetails(options) {
36960
37433
  return out;
36961
37434
  }
36962
37435
 
36963
- // src/git/changes/lib/working-tree-changed-path-count.ts
37436
+ // src/git/changes/status/working-tree-changed-path-count.ts
36964
37437
  function workingTreeChangedPathCount(nameStatusLines, numstatLines, untrackedLines) {
36965
37438
  const kindByPath = parseNameStatusLines(nameStatusLines);
36966
37439
  const numByPath = parseNumstat(numstatLines);
@@ -37056,23 +37529,23 @@ async function commitSessionWorktrees(options) {
37056
37529
  }
37057
37530
 
37058
37531
  // src/worktrees/remove-session-worktrees.ts
37059
- import * as fs21 from "node:fs";
37532
+ import * as fs22 from "node:fs";
37060
37533
 
37061
37534
  // src/git/worktrees/worktree-remove.ts
37062
- import * as fs20 from "node:fs";
37535
+ import * as fs21 from "node:fs";
37063
37536
 
37064
37537
  // src/git/worktrees/resolve-main-repo-from-git-file.ts
37065
- import * as fs19 from "node:fs";
37066
- import * as path22 from "node:path";
37538
+ import * as fs20 from "node:fs";
37539
+ import * as path24 from "node:path";
37067
37540
  function resolveMainRepoFromWorktreeGitFile(wt) {
37068
- const gitDirFile = path22.join(wt, ".git");
37069
- if (!fs19.existsSync(gitDirFile) || !fs19.statSync(gitDirFile).isFile()) return "";
37070
- const first2 = fs19.readFileSync(gitDirFile, "utf8").trim();
37541
+ const gitDirFile = path24.join(wt, ".git");
37542
+ if (!fs20.existsSync(gitDirFile) || !fs20.statSync(gitDirFile).isFile()) return "";
37543
+ const first2 = fs20.readFileSync(gitDirFile, "utf8").trim();
37071
37544
  const m = first2.match(/^gitdir:\s*(.+)$/im);
37072
37545
  if (!m) return "";
37073
- const gitWorktreePath = path22.resolve(wt, m[1].trim());
37074
- const gitDir = path22.dirname(path22.dirname(gitWorktreePath));
37075
- return path22.dirname(gitDir);
37546
+ const gitWorktreePath = path24.resolve(wt, m[1].trim());
37547
+ const gitDir = path24.dirname(path24.dirname(gitWorktreePath));
37548
+ return path24.dirname(gitDir);
37076
37549
  }
37077
37550
 
37078
37551
  // src/git/worktrees/worktree-remove.ts
@@ -37081,7 +37554,7 @@ async function gitWorktreeRemoveForce(worktreePath) {
37081
37554
  if (mainRepo) {
37082
37555
  await cliSimpleGit(mainRepo).raw(["worktree", "remove", "--force", worktreePath]);
37083
37556
  } else {
37084
- fs20.rmSync(worktreePath, { recursive: true, force: true });
37557
+ fs21.rmSync(worktreePath, { recursive: true, force: true });
37085
37558
  }
37086
37559
  }
37087
37560
 
@@ -37094,7 +37567,7 @@ async function removeSessionWorktrees(paths, log2) {
37094
37567
  } catch (e) {
37095
37568
  log2(`[worktrees] Remove failed for ${wt}: ${e instanceof Error ? e.message : String(e)}`);
37096
37569
  try {
37097
- fs21.rmSync(wt, { recursive: true, force: true });
37570
+ fs22.rmSync(wt, { recursive: true, force: true });
37098
37571
  } catch {
37099
37572
  }
37100
37573
  }
@@ -37123,12 +37596,12 @@ async function renameSessionWorktreeBranches(paths, newBranch, log2) {
37123
37596
  }
37124
37597
 
37125
37598
  // src/worktrees/worktree-layout-file.ts
37126
- import * as fs22 from "node:fs";
37127
- import * as path23 from "node:path";
37599
+ import * as fs23 from "node:fs";
37600
+ import * as path25 from "node:path";
37128
37601
  import os7 from "node:os";
37129
37602
  var LAYOUT_FILENAME = "worktree-launcher-layout.json";
37130
37603
  function defaultWorktreeLayoutPath() {
37131
- return path23.join(os7.homedir(), ".buildautomaton", LAYOUT_FILENAME);
37604
+ return path25.join(os7.homedir(), ".buildautomaton", LAYOUT_FILENAME);
37132
37605
  }
37133
37606
  function normalizeLoadedLayout(raw) {
37134
37607
  if (raw && typeof raw === "object" && "launcherCwds" in raw) {
@@ -37140,8 +37613,8 @@ function normalizeLoadedLayout(raw) {
37140
37613
  function loadWorktreeLayout() {
37141
37614
  try {
37142
37615
  const p = defaultWorktreeLayoutPath();
37143
- if (!fs22.existsSync(p)) return { launcherCwds: [] };
37144
- const raw = JSON.parse(fs22.readFileSync(p, "utf8"));
37616
+ if (!fs23.existsSync(p)) return { launcherCwds: [] };
37617
+ const raw = JSON.parse(fs23.readFileSync(p, "utf8"));
37145
37618
  return normalizeLoadedLayout(raw);
37146
37619
  } catch {
37147
37620
  return { launcherCwds: [] };
@@ -37149,24 +37622,24 @@ function loadWorktreeLayout() {
37149
37622
  }
37150
37623
  function saveWorktreeLayout(layout) {
37151
37624
  try {
37152
- const dir = path23.dirname(defaultWorktreeLayoutPath());
37153
- fs22.mkdirSync(dir, { recursive: true });
37154
- fs22.writeFileSync(defaultWorktreeLayoutPath(), JSON.stringify(layout, null, 2), "utf8");
37625
+ const dir = path25.dirname(defaultWorktreeLayoutPath());
37626
+ fs23.mkdirSync(dir, { recursive: true });
37627
+ fs23.writeFileSync(defaultWorktreeLayoutPath(), JSON.stringify(layout, null, 2), "utf8");
37155
37628
  } catch {
37156
37629
  }
37157
37630
  }
37158
37631
  function baseNameSafe(pathString) {
37159
- return path23.basename(pathString).replace(/[^a-zA-Z0-9._-]+/g, "-") || "cwd";
37632
+ return path25.basename(pathString).replace(/[^a-zA-Z0-9._-]+/g, "-") || "cwd";
37160
37633
  }
37161
37634
  function getLauncherDirNameIfPresent(layout, bridgeRootPath2) {
37162
- const norm = path23.resolve(bridgeRootPath2);
37163
- const existing = layout.launcherCwds.find((e) => path23.resolve(e.absolutePath) === norm);
37635
+ const norm = path25.resolve(bridgeRootPath2);
37636
+ const existing = layout.launcherCwds.find((e) => path25.resolve(e.absolutePath) === norm);
37164
37637
  return existing?.dirName;
37165
37638
  }
37166
37639
  function allocateDirNameForLauncherCwd(layout, bridgeRootPath2) {
37167
37640
  const existing = getLauncherDirNameIfPresent(layout, bridgeRootPath2);
37168
37641
  if (existing) return existing;
37169
- const norm = path23.resolve(bridgeRootPath2);
37642
+ const norm = path25.resolve(bridgeRootPath2);
37170
37643
  const base = baseNameSafe(norm);
37171
37644
  const used = new Set(layout.launcherCwds.map((e) => e.dirName));
37172
37645
  let name = base;
@@ -37181,11 +37654,11 @@ function allocateDirNameForLauncherCwd(layout, bridgeRootPath2) {
37181
37654
  }
37182
37655
 
37183
37656
  // src/worktrees/discover-session-worktree-on-disk.ts
37184
- import * as fs23 from "node:fs";
37185
- import * as path24 from "node:path";
37657
+ import * as fs24 from "node:fs";
37658
+ import * as path26 from "node:path";
37186
37659
  function isGitDir(dirPath) {
37187
37660
  try {
37188
- return fs23.existsSync(path24.join(dirPath, ".git"));
37661
+ return fs24.existsSync(path26.join(dirPath, ".git"));
37189
37662
  } catch {
37190
37663
  return false;
37191
37664
  }
@@ -37194,23 +37667,23 @@ function collectGitRepoRootsUnderDirectory(rootPath) {
37194
37667
  const out = [];
37195
37668
  const walk = (dir) => {
37196
37669
  if (isGitDir(dir)) {
37197
- out.push(path24.resolve(dir));
37670
+ out.push(path26.resolve(dir));
37198
37671
  return;
37199
37672
  }
37200
37673
  let entries;
37201
37674
  try {
37202
- entries = fs23.readdirSync(dir, { withFileTypes: true });
37675
+ entries = fs24.readdirSync(dir, { withFileTypes: true });
37203
37676
  } catch {
37204
37677
  return;
37205
37678
  }
37206
37679
  for (const e of entries) {
37207
37680
  if (e.name.startsWith(".")) continue;
37208
- const full = path24.join(dir, e.name);
37681
+ const full = path26.join(dir, e.name);
37209
37682
  if (!e.isDirectory()) continue;
37210
37683
  walk(full);
37211
37684
  }
37212
37685
  };
37213
- walk(path24.resolve(rootPath));
37686
+ walk(path26.resolve(rootPath));
37214
37687
  return [...new Set(out)];
37215
37688
  }
37216
37689
  function collectWorktreeRootsNamed(root, sessionId, maxDepth) {
@@ -37219,16 +37692,16 @@ function collectWorktreeRootsNamed(root, sessionId, maxDepth) {
37219
37692
  if (depth > maxDepth) return;
37220
37693
  let entries;
37221
37694
  try {
37222
- entries = fs23.readdirSync(dir, { withFileTypes: true });
37695
+ entries = fs24.readdirSync(dir, { withFileTypes: true });
37223
37696
  } catch {
37224
37697
  return;
37225
37698
  }
37226
37699
  for (const e of entries) {
37227
37700
  if (e.name.startsWith(".")) continue;
37228
- const full = path24.join(dir, e.name);
37701
+ const full = path26.join(dir, e.name);
37229
37702
  if (!e.isDirectory()) continue;
37230
37703
  if (e.name === sessionId) {
37231
- if (isGitDir(full)) out.push(path24.resolve(full));
37704
+ if (isGitDir(full)) out.push(path26.resolve(full));
37232
37705
  } else {
37233
37706
  walk(full, depth + 1);
37234
37707
  }
@@ -37240,14 +37713,14 @@ function collectWorktreeRootsNamed(root, sessionId, maxDepth) {
37240
37713
  function tryBindingFromSessionDirectory(sessionDir) {
37241
37714
  let st;
37242
37715
  try {
37243
- st = fs23.statSync(sessionDir);
37716
+ st = fs24.statSync(sessionDir);
37244
37717
  } catch {
37245
37718
  return null;
37246
37719
  }
37247
37720
  if (!st.isDirectory()) return null;
37248
37721
  const worktreePaths = collectGitRepoRootsUnderDirectory(sessionDir);
37249
37722
  if (worktreePaths.length === 0) return null;
37250
- const abs = path24.resolve(sessionDir);
37723
+ const abs = path26.resolve(sessionDir);
37251
37724
  return {
37252
37725
  sessionParentPath: abs,
37253
37726
  workingTreeRelRoot: abs,
@@ -37257,20 +37730,20 @@ function tryBindingFromSessionDirectory(sessionDir) {
37257
37730
  function discoverLegacyBindingAscendingFromCheckout(sessionId, checkoutPath) {
37258
37731
  const sid = sessionId.trim();
37259
37732
  if (!sid) return null;
37260
- const hintR = path24.resolve(checkoutPath);
37733
+ const hintR = path26.resolve(checkoutPath);
37261
37734
  let best = null;
37262
- let cur = path24.dirname(hintR);
37735
+ let cur = path26.dirname(hintR);
37263
37736
  for (let i = 0; i < 40; i++) {
37264
37737
  const paths = collectWorktreeRootsNamed(cur, sid, 24);
37265
- if (paths.some((p) => path24.resolve(p) === hintR)) {
37266
- const isolated = resolveIsolatedSessionParentPathFromCheckouts(paths) ?? path24.resolve(paths[0]);
37738
+ if (paths.some((p) => path26.resolve(p) === hintR)) {
37739
+ const isolated = resolveIsolatedSessionParentPathFromCheckouts(paths) ?? path26.resolve(paths[0]);
37267
37740
  best = {
37268
- sessionParentPath: path24.resolve(isolated),
37269
- workingTreeRelRoot: path24.resolve(cur),
37270
- repoCheckoutPaths: paths.map((p) => path24.resolve(p))
37741
+ sessionParentPath: path26.resolve(isolated),
37742
+ workingTreeRelRoot: path26.resolve(cur),
37743
+ repoCheckoutPaths: paths.map((p) => path26.resolve(p))
37271
37744
  };
37272
37745
  }
37273
- const next = path24.dirname(cur);
37746
+ const next = path26.dirname(cur);
37274
37747
  if (next === cur) break;
37275
37748
  cur = next;
37276
37749
  }
@@ -37278,33 +37751,33 @@ function discoverLegacyBindingAscendingFromCheckout(sessionId, checkoutPath) {
37278
37751
  }
37279
37752
  function discoverSessionWorktreeOnDisk(options) {
37280
37753
  const { sessionId, worktreesRootPath, layout, bridgeRoot } = options;
37281
- if (!sessionId.trim() || !fs23.existsSync(worktreesRootPath)) return null;
37754
+ if (!sessionId.trim() || !fs24.existsSync(worktreesRootPath)) return null;
37282
37755
  const preferredKey = getLauncherDirNameIfPresent(layout, bridgeRoot);
37283
37756
  const keys = [];
37284
37757
  if (preferredKey) keys.push(preferredKey);
37285
37758
  try {
37286
- for (const name of fs23.readdirSync(worktreesRootPath)) {
37759
+ for (const name of fs24.readdirSync(worktreesRootPath)) {
37287
37760
  if (name.startsWith(".")) continue;
37288
- const p = path24.join(worktreesRootPath, name);
37289
- if (!fs23.statSync(p).isDirectory()) continue;
37761
+ const p = path26.join(worktreesRootPath, name);
37762
+ if (!fs24.statSync(p).isDirectory()) continue;
37290
37763
  if (name !== preferredKey) keys.push(name);
37291
37764
  }
37292
37765
  } catch {
37293
37766
  return null;
37294
37767
  }
37295
37768
  for (const key of keys) {
37296
- const layoutRoot = path24.join(worktreesRootPath, key);
37297
- if (!fs23.existsSync(layoutRoot) || !fs23.statSync(layoutRoot).isDirectory()) continue;
37298
- const sessionDir = path24.join(layoutRoot, sessionId);
37769
+ const layoutRoot = path26.join(worktreesRootPath, key);
37770
+ if (!fs24.existsSync(layoutRoot) || !fs24.statSync(layoutRoot).isDirectory()) continue;
37771
+ const sessionDir = path26.join(layoutRoot, sessionId);
37299
37772
  const nested = tryBindingFromSessionDirectory(sessionDir);
37300
37773
  if (nested) return nested;
37301
37774
  const legacyPaths = collectWorktreeRootsNamed(layoutRoot, sessionId, 24);
37302
37775
  if (legacyPaths.length > 0) {
37303
- const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths) ?? path24.resolve(legacyPaths[0]);
37776
+ const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths) ?? path26.resolve(legacyPaths[0]);
37304
37777
  return {
37305
- sessionParentPath: path24.resolve(isolated),
37306
- workingTreeRelRoot: path24.resolve(layoutRoot),
37307
- repoCheckoutPaths: legacyPaths.map((p) => path24.resolve(p))
37778
+ sessionParentPath: path26.resolve(isolated),
37779
+ workingTreeRelRoot: path26.resolve(layoutRoot),
37780
+ repoCheckoutPaths: legacyPaths.map((p) => path26.resolve(p))
37308
37781
  };
37309
37782
  }
37310
37783
  }
@@ -37313,12 +37786,12 @@ function discoverSessionWorktreeOnDisk(options) {
37313
37786
  function discoverSessionWorktreesUnderSessionWorktreeRoot(sessionWorktreeRootPathOrHint, sessionId) {
37314
37787
  const sid = sessionId.trim();
37315
37788
  if (!sid) return null;
37316
- const hint = path24.resolve(sessionWorktreeRootPathOrHint);
37317
- const underHint = tryBindingFromSessionDirectory(path24.join(hint, sid));
37789
+ const hint = path26.resolve(sessionWorktreeRootPathOrHint);
37790
+ const underHint = tryBindingFromSessionDirectory(path26.join(hint, sid));
37318
37791
  if (underHint) return underHint;
37319
37792
  const direct = tryBindingFromSessionDirectory(hint);
37320
37793
  if (direct) {
37321
- if (path24.basename(hint) === sid && isGitDir(hint)) {
37794
+ if (path26.basename(hint) === sid && isGitDir(hint)) {
37322
37795
  const legacyFromCheckout = discoverLegacyBindingAscendingFromCheckout(sid, hint);
37323
37796
  if (legacyFromCheckout && legacyFromCheckout.repoCheckoutPaths.length > direct.repoCheckoutPaths.length) {
37324
37797
  return legacyFromCheckout;
@@ -37326,24 +37799,24 @@ function discoverSessionWorktreesUnderSessionWorktreeRoot(sessionWorktreeRootPat
37326
37799
  }
37327
37800
  return direct;
37328
37801
  }
37329
- if (path24.basename(hint) === sid && isGitDir(hint)) {
37802
+ if (path26.basename(hint) === sid && isGitDir(hint)) {
37330
37803
  const legacyFromCheckout = discoverLegacyBindingAscendingFromCheckout(sid, hint);
37331
37804
  if (legacyFromCheckout) return legacyFromCheckout;
37332
37805
  }
37333
37806
  let st;
37334
37807
  try {
37335
- st = fs23.statSync(hint);
37808
+ st = fs24.statSync(hint);
37336
37809
  } catch {
37337
37810
  return null;
37338
37811
  }
37339
37812
  if (!st.isDirectory()) return null;
37340
37813
  const legacyPaths = collectWorktreeRootsNamed(hint, sid, 24);
37341
37814
  if (legacyPaths.length === 0) return null;
37342
- const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths) ?? path24.resolve(legacyPaths[0]);
37815
+ const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths) ?? path26.resolve(legacyPaths[0]);
37343
37816
  return {
37344
- sessionParentPath: path24.resolve(isolated),
37817
+ sessionParentPath: path26.resolve(isolated),
37345
37818
  workingTreeRelRoot: hint,
37346
- repoCheckoutPaths: legacyPaths.map((p) => path24.resolve(p))
37819
+ repoCheckoutPaths: legacyPaths.map((p) => path26.resolve(p))
37347
37820
  };
37348
37821
  }
37349
37822
 
@@ -37400,8 +37873,8 @@ function parseSessionParent(v) {
37400
37873
  }
37401
37874
 
37402
37875
  // src/worktrees/prepare-new-session-worktrees.ts
37403
- import * as fs24 from "node:fs";
37404
- import * as path25 from "node:path";
37876
+ import * as fs25 from "node:fs";
37877
+ import * as path27 from "node:path";
37405
37878
 
37406
37879
  // src/git/worktrees/worktree-add.ts
37407
37880
  async function gitWorktreeAddBranch(mainRepoPath, worktreePath, branch, baseRef = "HEAD") {
@@ -37411,7 +37884,7 @@ async function gitWorktreeAddBranch(mainRepoPath, worktreePath, branch, baseRef
37411
37884
  }
37412
37885
 
37413
37886
  // src/worktrees/prepare-new-session-worktrees.ts
37414
- function normalizeRepoRelPath(rel) {
37887
+ function normalizeRepoRelPath2(rel) {
37415
37888
  return rel === "" ? "." : rel.replace(/\\/g, "/");
37416
37889
  }
37417
37890
  function resolveBaseRefForRepo(relNorm, baseBranches) {
@@ -37423,10 +37896,10 @@ function resolveBaseRefForRepo(relNorm, baseBranches) {
37423
37896
  }
37424
37897
  async function prepareNewSessionWorktrees(options) {
37425
37898
  const { worktreesRootPath, bridgeRoot, sessionId, layout, log: log2, worktreeBaseBranches } = options;
37426
- const bridgeResolved = path25.resolve(bridgeRoot);
37899
+ const bridgeResolved = path27.resolve(bridgeRoot);
37427
37900
  const cwdKey = allocateDirNameForLauncherCwd(layout, bridgeResolved);
37428
- const bridgeKeyDir = path25.join(worktreesRootPath, cwdKey);
37429
- const sessionDir = path25.join(bridgeKeyDir, sessionId);
37901
+ const bridgeKeyDir = path27.join(worktreesRootPath, cwdKey);
37902
+ const sessionDir = path27.join(bridgeKeyDir, sessionId);
37430
37903
  const repos = await discoverGitReposUnderRoot(bridgeResolved);
37431
37904
  if (repos.length === 0) {
37432
37905
  log2("[worktrees] No Git repositories under bridge root; skipping worktree creation.");
@@ -37434,14 +37907,14 @@ async function prepareNewSessionWorktrees(options) {
37434
37907
  }
37435
37908
  const branch = `session-${sessionId}`;
37436
37909
  const worktreePaths = [];
37437
- fs24.mkdirSync(sessionDir, { recursive: true });
37910
+ fs25.mkdirSync(sessionDir, { recursive: true });
37438
37911
  for (const repo of repos) {
37439
- let rel = path25.relative(bridgeResolved, repo.absolutePath);
37440
- if (rel.startsWith("..") || path25.isAbsolute(rel)) continue;
37441
- const relNorm = normalizeRepoRelPath(rel === "" ? "." : rel);
37442
- const wtPath = relNorm === "." ? sessionDir : path25.join(sessionDir, relNorm);
37912
+ let rel = path27.relative(bridgeResolved, repo.absolutePath);
37913
+ if (rel.startsWith("..") || path27.isAbsolute(rel)) continue;
37914
+ const relNorm = normalizeRepoRelPath2(rel === "" ? "." : rel);
37915
+ const wtPath = relNorm === "." ? sessionDir : path27.join(sessionDir, relNorm);
37443
37916
  if (relNorm !== ".") {
37444
- fs24.mkdirSync(path25.dirname(wtPath), { recursive: true });
37917
+ fs25.mkdirSync(path27.dirname(wtPath), { recursive: true });
37445
37918
  }
37446
37919
  const baseRef = resolveBaseRefForRepo(relNorm, worktreeBaseBranches);
37447
37920
  try {
@@ -37494,9 +37967,9 @@ function resolveExistingSessionParentPath(sessionId, cache2, discover) {
37494
37967
  }
37495
37968
 
37496
37969
  // src/worktrees/manager/resolve-explicit-session-parent-path.ts
37497
- import * as path26 from "node:path";
37970
+ import * as path28 from "node:path";
37498
37971
  function resolveExplicitSessionParentPath(params) {
37499
- const resolved = path26.resolve(params.parentPathRaw);
37972
+ const resolved = path28.resolve(params.parentPathRaw);
37500
37973
  if (parseSessionParent(params.sessionParent) !== "worktrees_root") {
37501
37974
  return resolved;
37502
37975
  }
@@ -37512,7 +37985,7 @@ function resolveExplicitSessionParentPath(params) {
37512
37985
  for (let i = 0; i < 16; i++) {
37513
37986
  const tryRoot = discoverSessionWorktreesUnderSessionWorktreeRoot(cur, params.sessionId);
37514
37987
  if (tryRoot) return rememberAndReturn(tryRoot);
37515
- const next = path26.dirname(cur);
37988
+ const next = path28.dirname(cur);
37516
37989
  if (next === cur) break;
37517
37990
  cur = next;
37518
37991
  }
@@ -37564,16 +38037,16 @@ async function resolveSessionParentPathForPrompt(params) {
37564
38037
  }
37565
38038
 
37566
38039
  // src/worktrees/manager/session-worktree-cache.ts
37567
- import * as path27 from "node:path";
38040
+ import * as path29 from "node:path";
37568
38041
  var SessionWorktreeCache = class {
37569
38042
  sessionRepoCheckoutPaths = /* @__PURE__ */ new Map();
37570
38043
  sessionParentPathBySession = /* @__PURE__ */ new Map();
37571
38044
  sessionWorkingTreeRelRootBySession = /* @__PURE__ */ new Map();
37572
38045
  remember(sessionId, binding) {
37573
- const paths = binding.repoCheckoutPaths.map((p) => path27.resolve(p));
38046
+ const paths = binding.repoCheckoutPaths.map((p) => path29.resolve(p));
37574
38047
  this.sessionRepoCheckoutPaths.set(sessionId, paths);
37575
- this.sessionParentPathBySession.set(sessionId, path27.resolve(binding.sessionParentPath));
37576
- this.sessionWorkingTreeRelRootBySession.set(sessionId, path27.resolve(binding.workingTreeRelRoot));
38048
+ this.sessionParentPathBySession.set(sessionId, path29.resolve(binding.sessionParentPath));
38049
+ this.sessionWorkingTreeRelRootBySession.set(sessionId, path29.resolve(binding.workingTreeRelRoot));
37577
38050
  }
37578
38051
  clearSession(sessionId) {
37579
38052
  const paths = this.sessionRepoCheckoutPaths.get(sessionId);
@@ -37603,7 +38076,7 @@ var SessionWorktreeCache = class {
37603
38076
  const parent = this.sessionParentPathBySession.get(sessionId);
37604
38077
  const relRoot = this.sessionWorkingTreeRelRootBySession.get(sessionId);
37605
38078
  if (!parent || !relRoot) return false;
37606
- return path27.resolve(parent) !== path27.resolve(relRoot);
38079
+ return path29.resolve(parent) !== path29.resolve(relRoot);
37607
38080
  }
37608
38081
  };
37609
38082
 
@@ -37707,29 +38180,29 @@ var SessionWorktreeManager = class {
37707
38180
  };
37708
38181
 
37709
38182
  // src/worktrees/manager/default-worktrees-root-path.ts
37710
- import * as path28 from "node:path";
38183
+ import * as path30 from "node:path";
37711
38184
  import os8 from "node:os";
37712
38185
  function defaultWorktreesRootPath() {
37713
- return path28.join(os8.homedir(), ".buildautomaton", "worktrees");
38186
+ return path30.join(os8.homedir(), ".buildautomaton", "worktrees");
37714
38187
  }
37715
38188
 
37716
38189
  // src/files/watch-file-index.ts
37717
38190
  import { watch } from "node:fs";
37718
- import path33 from "node:path";
38191
+ import path35 from "node:path";
37719
38192
 
37720
38193
  // src/files/index/paths.ts
37721
- import path29 from "node:path";
38194
+ import path31 from "node:path";
37722
38195
  import crypto2 from "node:crypto";
37723
38196
  function getCwdHashForFileIndex(resolvedCwd) {
37724
- return crypto2.createHash("sha256").update(path29.resolve(resolvedCwd)).digest("hex").slice(0, INDEX_HASH_LEN);
38197
+ return crypto2.createHash("sha256").update(path31.resolve(resolvedCwd)).digest("hex").slice(0, INDEX_HASH_LEN);
37725
38198
  }
37726
38199
 
37727
38200
  // src/files/index/build-file-index.ts
37728
- import path31 from "node:path";
38201
+ import path33 from "node:path";
37729
38202
 
37730
38203
  // src/files/index/walk-workspace-tree.ts
37731
- import fs25 from "node:fs";
37732
- import path30 from "node:path";
38204
+ import fs26 from "node:fs";
38205
+ import path32 from "node:path";
37733
38206
  var DEPENDENCY_INSTALL_DIR_NAMES = /* @__PURE__ */ new Set([
37734
38207
  "node_modules",
37735
38208
  "bower_components",
@@ -37746,7 +38219,7 @@ function shouldSkipWorkspaceWalkEntry(name) {
37746
38219
  async function walkWorkspaceTreeAsync(dir, baseDir, onFile, state) {
37747
38220
  let names;
37748
38221
  try {
37749
- names = await fs25.promises.readdir(dir);
38222
+ names = await fs26.promises.readdir(dir);
37750
38223
  } catch {
37751
38224
  return;
37752
38225
  }
@@ -37758,14 +38231,14 @@ async function walkWorkspaceTreeAsync(dir, baseDir, onFile, state) {
37758
38231
  if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
37759
38232
  }
37760
38233
  state.n++;
37761
- const full = path30.join(dir, name);
38234
+ const full = path32.join(dir, name);
37762
38235
  let stat3;
37763
38236
  try {
37764
- stat3 = await fs25.promises.stat(full);
38237
+ stat3 = await fs26.promises.stat(full);
37765
38238
  } catch {
37766
38239
  continue;
37767
38240
  }
37768
- const relative6 = path30.relative(baseDir, full).replace(/\\/g, "/");
38241
+ const relative6 = path32.relative(baseDir, full).replace(/\\/g, "/");
37769
38242
  if (stat3.isDirectory()) {
37770
38243
  await walkWorkspaceTreeAsync(full, baseDir, onFile, state);
37771
38244
  } else if (stat3.isFile()) {
@@ -37778,7 +38251,7 @@ function createWalkYieldState() {
37778
38251
  }
37779
38252
 
37780
38253
  // src/files/index/file-index-sqlite-lock.ts
37781
- import fs26 from "node:fs";
38254
+ import fs27 from "node:fs";
37782
38255
  function isSqliteCorruptError(e) {
37783
38256
  const msg = e instanceof Error ? e.message : String(e);
37784
38257
  return msg.includes("malformed") || msg.includes("database disk image is malformed") || msg.includes("corrupt");
@@ -37792,7 +38265,7 @@ function withFileIndexSqliteLock(fn) {
37792
38265
  if (!isSqliteCorruptError(e)) throw e;
37793
38266
  closeAllCliSqliteConnections();
37794
38267
  try {
37795
- fs26.unlinkSync(getCliSqlitePath());
38268
+ fs27.unlinkSync(getCliSqlitePath());
37796
38269
  } catch {
37797
38270
  }
37798
38271
  chain = Promise.resolve();
@@ -37867,7 +38340,7 @@ async function collectWorkspacePathsAsync(resolved) {
37867
38340
  }
37868
38341
  async function buildFileIndexAsync(cwd) {
37869
38342
  return withFileIndexSqliteLock(async () => {
37870
- const resolved = path31.resolve(cwd);
38343
+ const resolved = path33.resolve(cwd);
37871
38344
  await yieldToEventLoop();
37872
38345
  assertNotShutdown();
37873
38346
  const paths = await collectWorkspacePathsAsync(resolved);
@@ -37879,7 +38352,7 @@ async function buildFileIndexAsync(cwd) {
37879
38352
  }
37880
38353
 
37881
38354
  // src/files/index/ensure-file-index.ts
37882
- import path32 from "node:path";
38355
+ import path34 from "node:path";
37883
38356
 
37884
38357
  // src/files/index/search-file-index.ts
37885
38358
  function escapeLikePattern(fragment) {
@@ -37931,7 +38404,7 @@ async function searchBridgeFilePathsAsync(resolvedCwd, query, limit = 100) {
37931
38404
 
37932
38405
  // src/files/index/ensure-file-index.ts
37933
38406
  async function ensureFileIndexAsync(cwd) {
37934
- const resolved = path32.resolve(cwd);
38407
+ const resolved = path34.resolve(cwd);
37935
38408
  if (await bridgeFileIndexIsPopulated(resolved)) {
37936
38409
  return { fromCache: true, pathCount: await bridgeFileIndexPathCount(resolved) };
37937
38410
  }
@@ -37975,7 +38448,7 @@ function createFsWatcher(resolved, schedule) {
37975
38448
  }
37976
38449
  }
37977
38450
  function startFileIndexWatcher(cwd = getBridgeRoot()) {
37978
- const resolved = path33.resolve(cwd);
38451
+ const resolved = path35.resolve(cwd);
37979
38452
  void buildFileIndexAsync(resolved).catch((e) => {
37980
38453
  if (e instanceof CliSqliteInterrupted) return;
37981
38454
  console.error("[file-index] Initial index build failed:", e);
@@ -38005,7 +38478,7 @@ function startFileIndexWatcher(cwd = getBridgeRoot()) {
38005
38478
  }
38006
38479
 
38007
38480
  // src/connection/create-bridge-connection.ts
38008
- import * as path44 from "node:path";
38481
+ import * as path46 from "node:path";
38009
38482
 
38010
38483
  // src/dev-servers/manager/dev-server-manager.ts
38011
38484
  import { rm as rm2 } from "node:fs/promises";
@@ -38049,7 +38522,7 @@ function forceKillChild(proc, log2, shortId, graceMs) {
38049
38522
  }
38050
38523
 
38051
38524
  // src/dev-servers/process/wire-dev-server-child-process.ts
38052
- import fs27 from "node:fs";
38525
+ import fs28 from "node:fs";
38053
38526
 
38054
38527
  // src/dev-servers/manager/forward-pipe.ts
38055
38528
  function forwardChildPipe(childReadable, terminal, onData) {
@@ -38085,7 +38558,7 @@ function wireDevServerChildProcess(d) {
38085
38558
  d.setPollInterval(void 0);
38086
38559
  return;
38087
38560
  }
38088
- fs27.readFile(d.mergedLogPath, (err, buf) => {
38561
+ fs28.readFile(d.mergedLogPath, (err, buf) => {
38089
38562
  if (err || (d.getSpawnGeneration() ?? 0) !== d.scheduledGen) return;
38090
38563
  if (buf.length <= d.mergedReadPos.value) return;
38091
38564
  const chunk = Buffer.from(buf.subarray(d.mergedReadPos.value));
@@ -38123,7 +38596,7 @@ ${errTail}` : ""}`);
38123
38596
  d.sendStatus(code === 0 || code == null ? "stopped" : "error", detail, tails);
38124
38597
  };
38125
38598
  if (mergedPath) {
38126
- fs27.readFile(mergedPath, (err, buf) => {
38599
+ fs28.readFile(mergedPath, (err, buf) => {
38127
38600
  if (!err && buf.length > d.mergedReadPos.value) {
38128
38601
  const chunk = Buffer.from(buf.subarray(d.mergedReadPos.value));
38129
38602
  if (chunk.length > 0) {
@@ -38225,13 +38698,13 @@ function parseDevServerDefs(servers) {
38225
38698
  }
38226
38699
 
38227
38700
  // src/dev-servers/manager/shell-spawn/utils.ts
38228
- import fs28 from "node:fs";
38701
+ import fs29 from "node:fs";
38229
38702
  function isSpawnEbadf(e) {
38230
38703
  return typeof e === "object" && e !== null && "code" in e && e.code === "EBADF";
38231
38704
  }
38232
38705
  function rmDirQuiet(dir) {
38233
38706
  try {
38234
- fs28.rmSync(dir, { recursive: true, force: true });
38707
+ fs29.rmSync(dir, { recursive: true, force: true });
38235
38708
  } catch {
38236
38709
  }
38237
38710
  }
@@ -38239,7 +38712,7 @@ var cachedDevNullReadFd;
38239
38712
  function devNullReadFd() {
38240
38713
  if (cachedDevNullReadFd === void 0) {
38241
38714
  const devPath = process.platform === "win32" ? "nul" : "/dev/null";
38242
- cachedDevNullReadFd = fs28.openSync(devPath, "r");
38715
+ cachedDevNullReadFd = fs29.openSync(devPath, "r");
38243
38716
  }
38244
38717
  return cachedDevNullReadFd;
38245
38718
  }
@@ -38313,15 +38786,15 @@ function trySpawnShellTruePiped(command, env, cwd, devNullFd, signal) {
38313
38786
 
38314
38787
  // src/dev-servers/manager/shell-spawn/try-spawn-merged-log-file.ts
38315
38788
  import { spawn as spawn7 } from "node:child_process";
38316
- import fs29 from "node:fs";
38789
+ import fs30 from "node:fs";
38317
38790
  import { tmpdir } from "node:os";
38318
- import path34 from "node:path";
38791
+ import path36 from "node:path";
38319
38792
  function trySpawnMergedLogFile(command, env, cwd, signal) {
38320
- const tmpRoot = fs29.mkdtempSync(path34.join(tmpdir(), "ba-devsrv-log-"));
38321
- const logPath = path34.join(tmpRoot, "combined.log");
38793
+ const tmpRoot = fs30.mkdtempSync(path36.join(tmpdir(), "ba-devsrv-log-"));
38794
+ const logPath = path36.join(tmpRoot, "combined.log");
38322
38795
  let logFd;
38323
38796
  try {
38324
- logFd = fs29.openSync(logPath, "a");
38797
+ logFd = fs30.openSync(logPath, "a");
38325
38798
  } catch {
38326
38799
  rmDirQuiet(tmpRoot);
38327
38800
  return null;
@@ -38340,7 +38813,7 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
38340
38813
  } else {
38341
38814
  proc = spawn7("/bin/sh", ["-c", command], { env, cwd, stdio, ...signal ? { signal } : {} });
38342
38815
  }
38343
- fs29.closeSync(logFd);
38816
+ fs30.closeSync(logFd);
38344
38817
  return {
38345
38818
  proc,
38346
38819
  pipedStdoutStderr: true,
@@ -38349,7 +38822,7 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
38349
38822
  };
38350
38823
  } catch (e) {
38351
38824
  try {
38352
- fs29.closeSync(logFd);
38825
+ fs30.closeSync(logFd);
38353
38826
  } catch {
38354
38827
  }
38355
38828
  rmDirQuiet(tmpRoot);
@@ -38360,22 +38833,22 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
38360
38833
 
38361
38834
  // src/dev-servers/manager/shell-spawn/try-spawn-shell-script-log-redirect.ts
38362
38835
  import { spawn as spawn8 } from "node:child_process";
38363
- import fs30 from "node:fs";
38836
+ import fs31 from "node:fs";
38364
38837
  import { tmpdir as tmpdir2 } from "node:os";
38365
- import path35 from "node:path";
38838
+ import path37 from "node:path";
38366
38839
  function shSingleQuote(s) {
38367
38840
  return `'${s.replace(/'/g, `'\\''`)}'`;
38368
38841
  }
38369
38842
  function trySpawnShellScriptLogRedirectUnix(command, env, cwd, signal) {
38370
- const tmpRoot = fs30.mkdtempSync(path35.join(tmpdir2(), "ba-devsrv-sh-"));
38371
- const logPath = path35.join(tmpRoot, "combined.log");
38372
- const innerPath = path35.join(tmpRoot, "_cmd.sh");
38373
- const runnerPath = path35.join(tmpRoot, "_run.sh");
38843
+ const tmpRoot = fs31.mkdtempSync(path37.join(tmpdir2(), "ba-devsrv-sh-"));
38844
+ const logPath = path37.join(tmpRoot, "combined.log");
38845
+ const innerPath = path37.join(tmpRoot, "_cmd.sh");
38846
+ const runnerPath = path37.join(tmpRoot, "_run.sh");
38374
38847
  try {
38375
- fs30.writeFileSync(innerPath, `#!/bin/sh
38848
+ fs31.writeFileSync(innerPath, `#!/bin/sh
38376
38849
  ${command}
38377
38850
  `);
38378
- fs30.writeFileSync(
38851
+ fs31.writeFileSync(
38379
38852
  runnerPath,
38380
38853
  `#!/bin/sh
38381
38854
  cd ${shSingleQuote(cwd)}
@@ -38401,13 +38874,13 @@ cd ${shSingleQuote(cwd)}
38401
38874
  }
38402
38875
  }
38403
38876
  function trySpawnShellScriptLogRedirectWin(command, env, cwd, signal) {
38404
- const tmpRoot = fs30.mkdtempSync(path35.join(tmpdir2(), "ba-devsrv-sh-"));
38405
- const logPath = path35.join(tmpRoot, "combined.log");
38406
- const runnerPath = path35.join(tmpRoot, "_run.bat");
38877
+ const tmpRoot = fs31.mkdtempSync(path37.join(tmpdir2(), "ba-devsrv-sh-"));
38878
+ const logPath = path37.join(tmpRoot, "combined.log");
38879
+ const runnerPath = path37.join(tmpRoot, "_run.bat");
38407
38880
  const q = (p) => `"${p.replace(/"/g, '""')}"`;
38408
38881
  const com = process.env.ComSpec || "cmd.exe";
38409
38882
  try {
38410
- fs30.writeFileSync(
38883
+ fs31.writeFileSync(
38411
38884
  runnerPath,
38412
38885
  `@ECHO OFF\r
38413
38886
  CD /D ${q(cwd)}\r
@@ -39344,30 +39817,30 @@ function createOnBridgeIdentified(opts) {
39344
39817
  }
39345
39818
 
39346
39819
  // src/skills/discover-local-agent-skills.ts
39347
- import fs31 from "node:fs";
39348
- import path36 from "node:path";
39820
+ import fs32 from "node:fs";
39821
+ import path38 from "node:path";
39349
39822
  var SKILL_DISCOVERY_ROOTS = [".agents/skills", ".claude/skills", ".cursor/skills", "skills"];
39350
39823
  function discoverLocalSkills(cwd) {
39351
39824
  const out = [];
39352
39825
  const seenKeys = /* @__PURE__ */ new Set();
39353
39826
  for (const rel of SKILL_DISCOVERY_ROOTS) {
39354
- const base = path36.join(cwd, rel);
39355
- if (!fs31.existsSync(base) || !fs31.statSync(base).isDirectory()) continue;
39827
+ const base = path38.join(cwd, rel);
39828
+ if (!fs32.existsSync(base) || !fs32.statSync(base).isDirectory()) continue;
39356
39829
  let entries = [];
39357
39830
  try {
39358
- entries = fs31.readdirSync(base);
39831
+ entries = fs32.readdirSync(base);
39359
39832
  } catch {
39360
39833
  continue;
39361
39834
  }
39362
39835
  for (const name of entries) {
39363
- const dir = path36.join(base, name);
39836
+ const dir = path38.join(base, name);
39364
39837
  try {
39365
- if (!fs31.statSync(dir).isDirectory()) continue;
39838
+ if (!fs32.statSync(dir).isDirectory()) continue;
39366
39839
  } catch {
39367
39840
  continue;
39368
39841
  }
39369
- const skillMd = path36.join(dir, "SKILL.md");
39370
- if (!fs31.existsSync(skillMd)) continue;
39842
+ const skillMd = path38.join(dir, "SKILL.md");
39843
+ if (!fs32.existsSync(skillMd)) continue;
39371
39844
  const key = `${rel}/${name}`;
39372
39845
  if (seenKeys.has(key)) continue;
39373
39846
  seenKeys.add(key);
@@ -39379,23 +39852,23 @@ function discoverLocalSkills(cwd) {
39379
39852
  function discoverSkillLayoutRoots(cwd) {
39380
39853
  const roots = [];
39381
39854
  for (const rel of SKILL_DISCOVERY_ROOTS) {
39382
- const base = path36.join(cwd, rel);
39383
- if (!fs31.existsSync(base) || !fs31.statSync(base).isDirectory()) continue;
39855
+ const base = path38.join(cwd, rel);
39856
+ if (!fs32.existsSync(base) || !fs32.statSync(base).isDirectory()) continue;
39384
39857
  let entries = [];
39385
39858
  try {
39386
- entries = fs31.readdirSync(base);
39859
+ entries = fs32.readdirSync(base);
39387
39860
  } catch {
39388
39861
  continue;
39389
39862
  }
39390
39863
  const skills2 = [];
39391
39864
  for (const name of entries) {
39392
- const dir = path36.join(base, name);
39865
+ const dir = path38.join(base, name);
39393
39866
  try {
39394
- if (!fs31.statSync(dir).isDirectory()) continue;
39867
+ if (!fs32.statSync(dir).isDirectory()) continue;
39395
39868
  } catch {
39396
39869
  continue;
39397
39870
  }
39398
- if (!fs31.existsSync(path36.join(dir, "SKILL.md"))) continue;
39871
+ if (!fs32.existsSync(path38.join(dir, "SKILL.md"))) continue;
39399
39872
  const relPath = `${rel}/${name}`.replace(/\\/g, "/");
39400
39873
  skills2.push({ name, relPath });
39401
39874
  }
@@ -39751,7 +40224,7 @@ function syncRunningTurnQueueKeys(turns, queueKey) {
39751
40224
  }
39752
40225
 
39753
40226
  // src/prompt-turn-queue/runner/run-local-revert-before-queued-prompt.ts
39754
- import fs32 from "node:fs";
40227
+ import fs33 from "node:fs";
39755
40228
  async function runLocalRevertBeforeQueuedPrompt(next, deps) {
39756
40229
  if (next.serverState !== "requeued_with_revert") return true;
39757
40230
  const sid = next.sessionId;
@@ -39760,7 +40233,7 @@ async function runLocalRevertBeforeQueuedPrompt(next, deps) {
39760
40233
  const agentBase = deps.sessionWorktreeManager.getSessionWorktreeRootForSession(sid) ?? getBridgeRoot();
39761
40234
  const file2 = snapshotFilePath(agentBase, tid);
39762
40235
  try {
39763
- await fs32.promises.access(file2, fs32.constants.F_OK);
40236
+ await fs33.promises.access(file2, fs33.constants.F_OK);
39764
40237
  } catch {
39765
40238
  deps.log(
39766
40239
  `[Queue] requeued_with_revert: no pre-turn snapshot for ${tid.slice(0, 8)}\u2026; continuing without revert.`
@@ -40048,9 +40521,9 @@ function parseChangeSummarySnapshots(raw) {
40048
40521
  for (const item of raw) {
40049
40522
  if (!item || typeof item !== "object") continue;
40050
40523
  const o = item;
40051
- const path46 = typeof o.path === "string" && o.path.trim() !== "" ? o.path.trim() : "";
40052
- if (!path46) continue;
40053
- const row = { path: path46 };
40524
+ const path48 = typeof o.path === "string" && o.path.trim() !== "" ? o.path.trim() : "";
40525
+ if (!path48) continue;
40526
+ const row = { path: path48 };
40054
40527
  if (typeof o.patchContent === "string") row.patchContent = o.patchContent;
40055
40528
  if (typeof o.oldText === "string") row.oldText = o.oldText;
40056
40529
  if (typeof o.newText === "string") row.newText = o.newText;
@@ -40260,8 +40733,8 @@ function randomSecret() {
40260
40733
  }
40261
40734
  return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
40262
40735
  }
40263
- async function requestPreviewApi(port, secret, method, path46, body) {
40264
- const url2 = `http://127.0.0.1:${port}${path46}`;
40736
+ async function requestPreviewApi(port, secret, method, path48, body) {
40737
+ const url2 = `http://127.0.0.1:${port}${path48}`;
40265
40738
  const headers = {
40266
40739
  [PREVIEW_SECRET_HEADER]: secret,
40267
40740
  "Content-Type": "application/json"
@@ -40273,7 +40746,7 @@ async function requestPreviewApi(port, secret, method, path46, body) {
40273
40746
  });
40274
40747
  const data = await res.json().catch(() => ({}));
40275
40748
  if (!res.ok) {
40276
- throw new Error(data?.error ?? `Preview API ${method} ${path46}: ${res.status}`);
40749
+ throw new Error(data?.error ?? `Preview API ${method} ${path48}: ${res.status}`);
40277
40750
  }
40278
40751
  return data;
40279
40752
  }
@@ -40438,14 +40911,14 @@ var handleSkillCallMessage = (msg, { getWs, log: log2 }) => {
40438
40911
  };
40439
40912
 
40440
40913
  // src/files/list-dir/index.ts
40441
- import fs34 from "node:fs";
40914
+ import fs35 from "node:fs";
40442
40915
 
40443
40916
  // src/files/ensure-under-cwd.ts
40444
- import path37 from "node:path";
40917
+ import path39 from "node:path";
40445
40918
  function ensureUnderCwd(relativePath, cwd = getBridgeRoot()) {
40446
- const normalized = path37.normalize(relativePath).replace(/^(\.\/)+/, "");
40447
- const resolved = path37.resolve(cwd, normalized);
40448
- if (!resolved.startsWith(cwd + path37.sep) && resolved !== cwd) {
40919
+ const normalized = path39.normalize(relativePath).replace(/^(\.\/)+/, "");
40920
+ const resolved = path39.resolve(cwd, normalized);
40921
+ if (!resolved.startsWith(cwd + path39.sep) && resolved !== cwd) {
40449
40922
  return null;
40450
40923
  }
40451
40924
  return resolved;
@@ -40455,15 +40928,15 @@ function ensureUnderCwd(relativePath, cwd = getBridgeRoot()) {
40455
40928
  var LIST_DIR_YIELD_EVERY = 256;
40456
40929
 
40457
40930
  // src/files/list-dir/map-dir-entry.ts
40458
- import path38 from "node:path";
40459
- import fs33 from "node:fs";
40931
+ import path40 from "node:path";
40932
+ import fs34 from "node:fs";
40460
40933
  async function mapDirEntry(d, relativePath, resolved) {
40461
- const entryPath = path38.join(relativePath || ".", d.name).replace(/\\/g, "/");
40462
- const fullPath = path38.join(resolved, d.name);
40934
+ const entryPath = path40.join(relativePath || ".", d.name).replace(/\\/g, "/");
40935
+ const fullPath = path40.join(resolved, d.name);
40463
40936
  let isDir = d.isDirectory();
40464
40937
  if (d.isSymbolicLink()) {
40465
40938
  try {
40466
- const targetStat = await fs33.promises.stat(fullPath);
40939
+ const targetStat = await fs34.promises.stat(fullPath);
40467
40940
  isDir = targetStat.isDirectory();
40468
40941
  } catch {
40469
40942
  isDir = false;
@@ -40493,7 +40966,7 @@ async function listDirAsync(relativePath, sessionParentPath = getBridgeRoot()) {
40493
40966
  return { error: "Path is outside working directory" };
40494
40967
  }
40495
40968
  try {
40496
- const names = await fs34.promises.readdir(resolved, { withFileTypes: true });
40969
+ const names = await fs35.promises.readdir(resolved, { withFileTypes: true });
40497
40970
  const entries = [];
40498
40971
  for (let i = 0; i < names.length; i++) {
40499
40972
  if (i > 0 && i % LIST_DIR_YIELD_EVERY === 0) {
@@ -40513,18 +40986,18 @@ var LINE_CHUNK_SIZE = 64 * 1024;
40513
40986
  var READ_RANGE_YIELD_EVERY_BYTES = 256 * 1024;
40514
40987
 
40515
40988
  // src/files/read-file/resolve-file-path.ts
40516
- import fs35 from "node:fs";
40989
+ import fs36 from "node:fs";
40517
40990
  async function resolveFilePathAsync(relativePath, sessionParentPath = getBridgeRoot()) {
40518
40991
  const resolved = ensureUnderCwd(relativePath, sessionParentPath);
40519
40992
  if (!resolved) return { error: "Path is outside working directory" };
40520
40993
  let real;
40521
40994
  try {
40522
- real = await fs35.promises.realpath(resolved);
40995
+ real = await fs36.promises.realpath(resolved);
40523
40996
  } catch {
40524
40997
  real = resolved;
40525
40998
  }
40526
40999
  try {
40527
- const stat3 = await fs35.promises.stat(real);
41000
+ const stat3 = await fs36.promises.stat(real);
40528
41001
  if (!stat3.isFile()) return { error: "Not a file" };
40529
41002
  return real;
40530
41003
  } catch (err) {
@@ -40533,11 +41006,11 @@ async function resolveFilePathAsync(relativePath, sessionParentPath = getBridgeR
40533
41006
  }
40534
41007
 
40535
41008
  // src/files/read-file/read-file-range-async.ts
40536
- import fs36 from "node:fs";
41009
+ import fs37 from "node:fs";
40537
41010
  import { StringDecoder } from "node:string_decoder";
40538
41011
  async function readFileRangeAsync(filePath, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
40539
- const fileSize = (await fs36.promises.stat(filePath)).size;
40540
- const fd = await fs36.promises.open(filePath, "r");
41012
+ const fileSize = (await fs37.promises.stat(filePath)).size;
41013
+ const fd = await fs37.promises.open(filePath, "r");
40541
41014
  const bufSize = 64 * 1024;
40542
41015
  const buf = Buffer.alloc(bufSize);
40543
41016
  const decoder = new StringDecoder("utf8");
@@ -40699,11 +41172,11 @@ async function readFileRangeAsync(filePath, startLine, endLine, lineOffsetIn, li
40699
41172
  }
40700
41173
 
40701
41174
  // src/files/read-file/read-file-buffer-full-async.ts
40702
- import fs37 from "node:fs";
41175
+ import fs38 from "node:fs";
40703
41176
  var READ_CHUNK_BYTES = 256 * 1024;
40704
41177
  async function readFileBufferFullAsync(filePath) {
40705
- const stat3 = await fs37.promises.stat(filePath);
40706
- const fd = await fs37.promises.open(filePath, "r");
41178
+ const stat3 = await fs38.promises.stat(filePath);
41179
+ const fd = await fs38.promises.open(filePath, "r");
40707
41180
  const chunks = [];
40708
41181
  let position = 0;
40709
41182
  let bytesSinceYield = 0;
@@ -40810,13 +41283,13 @@ function resolveFileBrowserSessionParent(sessionWorktreeManager, sessionId) {
40810
41283
  }
40811
41284
 
40812
41285
  // src/files/handle-file-browser-search.ts
40813
- import path39 from "node:path";
41286
+ import path41 from "node:path";
40814
41287
  var SEARCH_LIMIT = 100;
40815
41288
  function handleFileBrowserSearch(msg, socket, e2ee, sessionWorktreeManager) {
40816
41289
  void (async () => {
40817
41290
  await yieldToEventLoop();
40818
41291
  const q = typeof msg.q === "string" ? msg.q : "";
40819
- const sessionParentPath = path39.resolve(
41292
+ const sessionParentPath = path41.resolve(
40820
41293
  sessionWorktreeManager != null ? resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : getBridgeRoot()
40821
41294
  );
40822
41295
  if (!await bridgeFileIndexIsPopulated(sessionParentPath)) {
@@ -40935,8 +41408,8 @@ function handleSkillLayoutRequest(msg, deps) {
40935
41408
  }
40936
41409
 
40937
41410
  // src/skills/install-remote-skills.ts
40938
- import fs38 from "node:fs";
40939
- import path40 from "node:path";
41411
+ import fs39 from "node:fs";
41412
+ import path42 from "node:path";
40940
41413
  function installRemoteSkills(cwd, targetDir, items) {
40941
41414
  const installed2 = [];
40942
41415
  if (!Array.isArray(items)) {
@@ -40947,15 +41420,15 @@ function installRemoteSkills(cwd, targetDir, items) {
40947
41420
  if (typeof item.sourceId !== "string" || typeof item.skillName !== "string" || typeof item.versionHash !== "string" || !Array.isArray(item.files)) {
40948
41421
  continue;
40949
41422
  }
40950
- const skillDir = path40.join(cwd, targetDir, item.skillName);
41423
+ const skillDir = path42.join(cwd, targetDir, item.skillName);
40951
41424
  for (const f of item.files) {
40952
41425
  if (typeof f.path !== "string" || !f.text && !f.base64) continue;
40953
- const dest = path40.join(skillDir, f.path);
40954
- fs38.mkdirSync(path40.dirname(dest), { recursive: true });
41426
+ const dest = path42.join(skillDir, f.path);
41427
+ fs39.mkdirSync(path42.dirname(dest), { recursive: true });
40955
41428
  if (f.text !== void 0) {
40956
- fs38.writeFileSync(dest, f.text, "utf8");
41429
+ fs39.writeFileSync(dest, f.text, "utf8");
40957
41430
  } else if (f.base64) {
40958
- fs38.writeFileSync(dest, Buffer.from(f.base64, "base64"));
41431
+ fs39.writeFileSync(dest, Buffer.from(f.base64, "base64"));
40959
41432
  }
40960
41433
  }
40961
41434
  installed2.push({
@@ -41113,7 +41586,7 @@ var handleSessionDiscardedMessage = (msg, deps) => {
41113
41586
  };
41114
41587
 
41115
41588
  // src/routing/handlers/revert-turn-snapshot.ts
41116
- import * as fs39 from "node:fs";
41589
+ import * as fs40 from "node:fs";
41117
41590
  var handleRevertTurnSnapshotMessage = (msg, deps) => {
41118
41591
  const id = typeof msg.id === "string" ? msg.id : "";
41119
41592
  const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
@@ -41126,7 +41599,7 @@ var handleRevertTurnSnapshotMessage = (msg, deps) => {
41126
41599
  const agentBase = sessionWorktreeManager.getSessionWorktreeRootForSession(sessionId) ?? getBridgeRoot();
41127
41600
  const file2 = snapshotFilePath(agentBase, turnId);
41128
41601
  try {
41129
- await fs39.promises.access(file2, fs39.constants.F_OK);
41602
+ await fs40.promises.access(file2, fs40.constants.F_OK);
41130
41603
  } catch {
41131
41604
  sendWsMessage(s, {
41132
41605
  type: "revert_turn_snapshot_result",
@@ -41168,7 +41641,7 @@ var handleDevServersConfig = (msg, deps) => {
41168
41641
  };
41169
41642
 
41170
41643
  // src/git/bridge-git-context.ts
41171
- import * as path41 from "node:path";
41644
+ import * as path43 from "node:path";
41172
41645
 
41173
41646
  // src/git/branches/get-current-branch.ts
41174
41647
  async function getCurrentBranch(repoPath) {
@@ -41218,12 +41691,12 @@ async function listRepoBranchRefs(repoPath) {
41218
41691
  // src/git/bridge-git-context.ts
41219
41692
  function folderNameForRelPath(relPath, bridgeRoot) {
41220
41693
  if (relPath === "." || relPath === "") {
41221
- return path41.basename(path41.resolve(bridgeRoot)) || "repo";
41694
+ return path43.basename(path43.resolve(bridgeRoot)) || "repo";
41222
41695
  }
41223
- return path41.basename(relPath) || relPath;
41696
+ return path43.basename(relPath) || relPath;
41224
41697
  }
41225
41698
  async function discoverGitReposForBridgeContext(bridgeRoot) {
41226
- const root = path41.resolve(bridgeRoot);
41699
+ const root = path43.resolve(bridgeRoot);
41227
41700
  if (await isGitRepoDirectory(root)) {
41228
41701
  const remoteUrl = await getRemoteOriginUrl(root);
41229
41702
  return [{ absolutePath: root, remoteUrl }];
@@ -41231,17 +41704,17 @@ async function discoverGitReposForBridgeContext(bridgeRoot) {
41231
41704
  const [deep, shallow] = await Promise.all([discoverGitReposUnderRoot(root), discoverGitRepos(root)]);
41232
41705
  const byPath = /* @__PURE__ */ new Map();
41233
41706
  for (const repo of [...deep, ...shallow]) {
41234
- byPath.set(path41.resolve(repo.absolutePath), repo);
41707
+ byPath.set(path43.resolve(repo.absolutePath), repo);
41235
41708
  }
41236
41709
  return [...byPath.values()];
41237
41710
  }
41238
41711
  async function getBridgeGitContext(bridgeRoot = getBridgeRoot()) {
41239
- const bridgeResolved = path41.resolve(bridgeRoot);
41712
+ const bridgeResolved = path43.resolve(bridgeRoot);
41240
41713
  const repos = await discoverGitReposForBridgeContext(bridgeResolved);
41241
41714
  const rows = [];
41242
41715
  for (const repo of repos) {
41243
- let rel = path41.relative(bridgeResolved, repo.absolutePath);
41244
- if (rel.startsWith("..") || path41.isAbsolute(rel)) continue;
41716
+ let rel = path43.relative(bridgeResolved, repo.absolutePath);
41717
+ if (rel.startsWith("..") || path43.isAbsolute(rel)) continue;
41245
41718
  const relPath = rel === "" ? "." : rel.replace(/\\/g, "/");
41246
41719
  const currentBranch = await getCurrentBranch(repo.absolutePath);
41247
41720
  const remoteUrl = repo.remoteUrl.trim() || null;
@@ -41256,11 +41729,11 @@ async function getBridgeGitContext(bridgeRoot = getBridgeRoot()) {
41256
41729
  return rows;
41257
41730
  }
41258
41731
  async function listRepoBranchesForBridge(repoRelPath, bridgeRoot = getBridgeRoot()) {
41259
- const bridgeResolved = path41.resolve(bridgeRoot);
41732
+ const bridgeResolved = path43.resolve(bridgeRoot);
41260
41733
  const rel = repoRelPath.trim() === "." ? "" : repoRelPath.trim().replace(/\\/g, "/");
41261
- const repoPath = rel === "" ? bridgeResolved : path41.join(bridgeResolved, rel);
41262
- const resolved = path41.resolve(repoPath);
41263
- if (!resolved.startsWith(bridgeResolved + path41.sep) && resolved !== bridgeResolved) {
41734
+ const repoPath = rel === "" ? bridgeResolved : path43.join(bridgeResolved, rel);
41735
+ const resolved = path43.resolve(repoPath);
41736
+ if (!resolved.startsWith(bridgeResolved + path43.sep) && resolved !== bridgeResolved) {
41264
41737
  return [];
41265
41738
  }
41266
41739
  return listRepoBranchRefs(resolved);
@@ -41765,10 +42238,10 @@ function listCliAgentCapabilityCacheForWorkspace(db, workspaceId) {
41765
42238
  }
41766
42239
 
41767
42240
  // src/agents/capabilities/warmup-agent-capabilities-on-connect.ts
41768
- import * as path43 from "node:path";
42241
+ import * as path45 from "node:path";
41769
42242
 
41770
42243
  // src/agents/capabilities/probe-one-agent-type-for-capabilities.ts
41771
- import * as path42 from "node:path";
42244
+ import * as path44 from "node:path";
41772
42245
  async function probeOneAgentTypeForCapabilities(params) {
41773
42246
  const { agentType, cwd, workspaceId, log: log2, reportAgentCapabilities, bridgeReport = true, shouldContinue } = params;
41774
42247
  const canContinue = () => shouldContinue?.() !== false;
@@ -41806,7 +42279,7 @@ async function probeOneAgentTypeForCapabilities(params) {
41806
42279
  if (!canContinue()) return false;
41807
42280
  handle = await resolved.createClient({
41808
42281
  command: resolved.command,
41809
- cwd: path42.resolve(cwd),
42282
+ cwd: path44.resolve(cwd),
41810
42283
  backendAgentType: agentType,
41811
42284
  sessionMode: "agent",
41812
42285
  persistedAcpSessionId: null,
@@ -41884,7 +42357,7 @@ async function warmupAgentCapabilitiesOnConnect(params) {
41884
42357
  const { workspaceId, log: log2, getWs } = params;
41885
42358
  const isCurrent = beginAgentCapabilityWarmupRun();
41886
42359
  if (!isCurrent()) return;
41887
- const cwd = path43.resolve(getBridgeRoot());
42360
+ const cwd = path45.resolve(getBridgeRoot());
41888
42361
  async function sendBatchFromCache() {
41889
42362
  if (!isCurrent()) return;
41890
42363
  const socket = getWs();
@@ -41950,6 +42423,232 @@ async function warmupAgentCapabilitiesOnConnect(params) {
41950
42423
  })();
41951
42424
  }
41952
42425
 
42426
+ // src/mcp/tools/session-history/fork-session-mcp-tools.ts
42427
+ var LIST_PARENT_SESSION_PROMPTS_TOOL = "list_parent_session_prompts";
42428
+ var GET_PARENT_SESSION_TURN_TRANSCRIPT_TOOL = "get_parent_session_turn_transcript";
42429
+ var SESSION_ID_SCHEMA = {
42430
+ type: "string",
42431
+ description: "Id of this forked (child) session \u2014 not the parent session id. Required on every tool call."
42432
+ };
42433
+ var FORK_SESSION_MCP_TOOL_DEFINITIONS = [
42434
+ {
42435
+ name: LIST_PARENT_SESSION_PROMPTS_TOOL,
42436
+ description: "List user prompts (main turns) from the parent session a fork was created from. Requires sessionId. Returns an error if the session has no parent history (not a fork).",
42437
+ inputSchema: {
42438
+ type: "object",
42439
+ properties: { sessionId: SESSION_ID_SCHEMA },
42440
+ required: ["sessionId"],
42441
+ additionalProperties: false
42442
+ }
42443
+ },
42444
+ {
42445
+ name: GET_PARENT_SESSION_TURN_TRANSCRIPT_TOOL,
42446
+ description: "Full transcript for one parent-session turn, including tool calls and agent messages. Requires sessionId and turnId. Returns an error if the session has no parent history (not a fork).",
42447
+ inputSchema: {
42448
+ type: "object",
42449
+ properties: {
42450
+ sessionId: SESSION_ID_SCHEMA,
42451
+ turnId: { type: "string", description: "Turn id from list_parent_session_prompts" }
42452
+ },
42453
+ required: ["sessionId", "turnId"],
42454
+ additionalProperties: false
42455
+ }
42456
+ }
42457
+ ];
42458
+ function textResult(text, isError = false) {
42459
+ return { content: [{ type: "text", text }], ...isError ? { isError: true } : {} };
42460
+ }
42461
+ function isForkSessionMcpToolName(name) {
42462
+ return name === LIST_PARENT_SESSION_PROMPTS_TOOL || name === GET_PARENT_SESSION_TURN_TRANSCRIPT_TOOL;
42463
+ }
42464
+ async function callForkSessionMcpTool(ctx, cloud, name, args) {
42465
+ const childSessionId = ctx.sessionId.trim();
42466
+ if (!childSessionId) {
42467
+ return textResult("sessionId is required.", true);
42468
+ }
42469
+ const parentResolved = await resolveParentSessionIdForChild({
42470
+ childSessionId,
42471
+ cloudApiBaseUrl: cloud.cloudApiBaseUrl,
42472
+ getCloudAccessToken: cloud.getCloudAccessToken
42473
+ });
42474
+ if (!parentResolved.ok) {
42475
+ return textResult(parentResolved.error, true);
42476
+ }
42477
+ const parentSessionId = parentResolved.parentSessionId;
42478
+ if (name === LIST_PARENT_SESSION_PROMPTS_TOOL) {
42479
+ const detail = await fetchCloudParentSessionPrompts({
42480
+ childSessionId,
42481
+ cloudApiBaseUrl: cloud.cloudApiBaseUrl,
42482
+ getCloudAccessToken: cloud.getCloudAccessToken
42483
+ });
42484
+ if (!detail.ok) return textResult(detail.error, true);
42485
+ const turns = Array.isArray(detail.data.turns) ? detail.data.turns : [];
42486
+ const payload = turns.map((t) => ({
42487
+ turnId: typeof t.turnId === "string" ? t.turnId : "",
42488
+ title: typeof t.title === "string" ? t.title : null,
42489
+ status: typeof t.status === "string" ? t.status : null,
42490
+ promptText: typeof t.promptText === "string" ? t.promptText : null
42491
+ }));
42492
+ return textResult(JSON.stringify({ parentSessionId, turns: payload }, null, 2));
42493
+ }
42494
+ if (name === GET_PARENT_SESSION_TURN_TRANSCRIPT_TOOL) {
42495
+ const turnId = typeof args.turnId === "string" ? args.turnId.trim() : "";
42496
+ if (!turnId) return textResult("turnId is required.", true);
42497
+ const transcript = await fetchCloudParentTurnTranscript({
42498
+ childSessionId,
42499
+ turnId,
42500
+ cloudApiBaseUrl: cloud.cloudApiBaseUrl,
42501
+ getCloudAccessToken: cloud.getCloudAccessToken,
42502
+ e2ee: ctx.e2ee
42503
+ });
42504
+ if (!transcript.ok) return textResult(transcript.error, true);
42505
+ return textResult(transcript.text);
42506
+ }
42507
+ return textResult(`Unknown fork session tool: ${name}`, true);
42508
+ }
42509
+
42510
+ // src/mcp/bridge-mcp-tools.ts
42511
+ function requireCloud(shared) {
42512
+ const cloudApiBaseUrl = shared.cloudApiBaseUrl?.trim() ?? "";
42513
+ const getCloudAccessToken = shared.getCloudAccessToken;
42514
+ if (!cloudApiBaseUrl || !getCloudAccessToken) return null;
42515
+ return { cloudApiBaseUrl, getCloudAccessToken };
42516
+ }
42517
+ function createBridgeMcpToolRegistry(shared) {
42518
+ const cloud = requireCloud(shared);
42519
+ return {
42520
+ listTools: async () => [...FORK_SESSION_MCP_TOOL_DEFINITIONS],
42521
+ callTool: async (name, args) => {
42522
+ if (isForkSessionMcpToolName(name)) {
42523
+ if (!cloud) {
42524
+ return {
42525
+ content: [{ type: "text", text: "Bridge MCP tools require cloud API credentials." }],
42526
+ isError: true
42527
+ };
42528
+ }
42529
+ const sessionId = typeof args.sessionId === "string" ? args.sessionId.trim() : "";
42530
+ if (!sessionId) {
42531
+ return {
42532
+ content: [{ type: "text", text: "sessionId is required." }],
42533
+ isError: true
42534
+ };
42535
+ }
42536
+ return callForkSessionMcpTool({ ...shared, sessionId }, cloud, name, args);
42537
+ }
42538
+ throw new Error(`Unknown bridge MCP tool: ${name}`);
42539
+ }
42540
+ };
42541
+ }
42542
+
42543
+ // src/mcp/bridge-access/start-server.ts
42544
+ import * as http from "node:http";
42545
+
42546
+ // src/mcp/bridge-access/read-json-body.ts
42547
+ function readJsonBody(req) {
42548
+ return new Promise((resolve22, reject) => {
42549
+ const chunks = [];
42550
+ req.on("data", (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
42551
+ req.on("end", () => {
42552
+ try {
42553
+ const raw = Buffer.concat(chunks).toString("utf8").trim();
42554
+ if (!raw) {
42555
+ resolve22({});
42556
+ return;
42557
+ }
42558
+ const parsed = JSON.parse(raw);
42559
+ resolve22(parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {});
42560
+ } catch (e) {
42561
+ reject(e);
42562
+ }
42563
+ });
42564
+ req.on("error", reject);
42565
+ });
42566
+ }
42567
+
42568
+ // src/mcp/bridge-access/send-json-response.ts
42569
+ function sendJsonResponse(res, status, body) {
42570
+ res.writeHead(status, { "Content-Type": "application/json" });
42571
+ res.end(JSON.stringify(body));
42572
+ }
42573
+
42574
+ // src/mcp/bridge-access/handle-request.ts
42575
+ function createBridgeAccessRequestHandler(registry2) {
42576
+ return (req, res) => {
42577
+ void (async () => {
42578
+ if (req.method !== "POST") {
42579
+ sendJsonResponse(res, 405, { ok: false, error: "Method not allowed" });
42580
+ return;
42581
+ }
42582
+ try {
42583
+ const body = await readJsonBody(req);
42584
+ if (req.url === "/tools/list") {
42585
+ const tools = await Promise.resolve(registry2.listTools());
42586
+ sendJsonResponse(res, 200, { ok: true, data: tools });
42587
+ return;
42588
+ }
42589
+ if (req.url === "/tools/call") {
42590
+ const sessionId = typeof body.sessionId === "string" ? body.sessionId.trim() : "";
42591
+ if (!sessionId) {
42592
+ sendJsonResponse(res, 400, { ok: false, error: "sessionId is required" });
42593
+ return;
42594
+ }
42595
+ const name = typeof body.name === "string" ? body.name.trim() : "";
42596
+ const args = body.args && typeof body.args === "object" && !Array.isArray(body.args) ? body.args : {};
42597
+ if (!name) {
42598
+ sendJsonResponse(res, 400, { ok: false, error: "name is required" });
42599
+ return;
42600
+ }
42601
+ const data = await registry2.callTool(name, { ...args, sessionId });
42602
+ sendJsonResponse(res, 200, { ok: true, data });
42603
+ return;
42604
+ }
42605
+ sendJsonResponse(res, 404, { ok: false, error: "Not found" });
42606
+ } catch (err) {
42607
+ const message = err instanceof Error ? err.message : String(err);
42608
+ sendJsonResponse(res, 500, { ok: false, error: message });
42609
+ }
42610
+ })();
42611
+ };
42612
+ }
42613
+
42614
+ // src/mcp/bridge-access/start-server.ts
42615
+ function startBridgeAccessServer(registry2) {
42616
+ const server = http.createServer(createBridgeAccessRequestHandler(registry2));
42617
+ return new Promise((resolve22, reject) => {
42618
+ server.once("error", reject);
42619
+ server.listen(0, "127.0.0.1", () => {
42620
+ const addr = server.address();
42621
+ if (!addr || typeof addr === "string") {
42622
+ reject(new Error("Bridge access server did not bind"));
42623
+ return;
42624
+ }
42625
+ resolve22({
42626
+ port: addr.port,
42627
+ close: () => new Promise((closeResolve, closeReject) => {
42628
+ server.close((err) => err ? closeReject(err) : closeResolve());
42629
+ })
42630
+ });
42631
+ });
42632
+ });
42633
+ }
42634
+
42635
+ // src/mcp/bridge-access/create-state.ts
42636
+ async function createBridgeAccessState(options = {}) {
42637
+ const registry2 = createBridgeMcpToolRegistry({
42638
+ cloudApiBaseUrl: options.cloudApiBaseUrl,
42639
+ getCloudAccessToken: options.getCloudAccessToken,
42640
+ e2ee: options.e2ee
42641
+ });
42642
+ const server = await startBridgeAccessServer(registry2);
42643
+ return {
42644
+ getPort: () => server.port,
42645
+ getRegistry: () => registry2,
42646
+ close: async () => {
42647
+ await server.close();
42648
+ }
42649
+ };
42650
+ }
42651
+
41953
42652
  // src/connection/create-bridge-connection.ts
41954
42653
  async function createBridgeConnection(options) {
41955
42654
  const { apiUrl, workspaceId, justAuthenticated, onAuthInvalid, persistTokens } = options;
@@ -42012,12 +42711,18 @@ async function createBridgeConnection(options) {
42012
42711
  worktreesRootPath,
42013
42712
  log: logFn
42014
42713
  });
42714
+ const e2ee = options.e2eCertificate ? createCliE2eeRuntime(options.e2eCertificate) : void 0;
42715
+ const bridgeAccess = await createBridgeAccessState({
42716
+ cloudApiBaseUrl: apiUrl,
42717
+ getCloudAccessToken: () => tokens.accessToken,
42718
+ e2ee
42719
+ });
42015
42720
  const acpManager = await createAcpManager({
42016
42721
  log: logFn,
42722
+ getBridgeAccessPort: () => bridgeAccess.getPort(),
42017
42723
  reportAgentCapabilities: sendAgentCapabilitiesToBridge
42018
42724
  });
42019
42725
  logFn("CLI running. Press Ctrl+C to exit.");
42020
- const e2ee = options.e2eCertificate ? createCliE2eeRuntime(options.e2eCertificate) : void 0;
42021
42726
  const devServerManager = new DevServerManager({ getWs, log: logFn, getBridgeRoot, e2ee });
42022
42727
  const bridgeHeartbeat = createBridgeHeartbeatController({ getWs, log: logFn });
42023
42728
  const baseOnBridgeIdentified = createOnBridgeIdentified({
@@ -42057,8 +42762,8 @@ async function createBridgeConnection(options) {
42057
42762
  getCloudAccessToken: () => tokens.accessToken
42058
42763
  };
42059
42764
  const identifyReportedPaths = {
42060
- bridgeRootPath: path44.resolve(getBridgeRoot()),
42061
- worktreesRootPath: path44.resolve(worktreesRootPath)
42765
+ bridgeRootPath: path46.resolve(getBridgeRoot()),
42766
+ worktreesRootPath: path46.resolve(worktreesRootPath)
42062
42767
  };
42063
42768
  const { connect } = createMainBridgeWebSocketLifecycle({
42064
42769
  state,
@@ -42083,6 +42788,7 @@ async function createBridgeConnection(options) {
42083
42788
  stopFileIndexWatcher();
42084
42789
  bridgeHeartbeat.stop();
42085
42790
  await closeBridgeConnection(state, acpManager, devServerManager, logFn);
42791
+ await bridgeAccess.close();
42086
42792
  }
42087
42793
  };
42088
42794
  }
@@ -42312,9 +43018,9 @@ async function runCliAction(program2, opts) {
42312
43018
  const firehoseServerUrl = opts.firehoseUrl ?? opts.proxyUrl ?? process.env.BUILDAUTOMATON_FIREHOSE_URL ?? process.env.BUILDAUTOMATON_PROXY_URL ?? DEFAULT_FIREHOSE_URL;
42313
43019
  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);
42314
43020
  if (bridgeRootOpt) {
42315
- const resolvedBridgeRoot = path45.resolve(process.cwd(), bridgeRootOpt);
43021
+ const resolvedBridgeRoot = path47.resolve(process.cwd(), bridgeRootOpt);
42316
43022
  try {
42317
- const st = fs40.statSync(resolvedBridgeRoot);
43023
+ const st = fs41.statSync(resolvedBridgeRoot);
42318
43024
  if (!st.isDirectory()) {
42319
43025
  console.error(`Bridge root is not a directory: ${resolvedBridgeRoot}`);
42320
43026
  process.exit(1);
@@ -42334,7 +43040,7 @@ async function runCliAction(program2, opts) {
42334
43040
  );
42335
43041
  let worktreesRootPath;
42336
43042
  if (opts.worktreesRoot && opts.worktreesRoot.trim()) {
42337
- worktreesRootPath = path45.resolve(opts.worktreesRoot.trim());
43043
+ worktreesRootPath = path47.resolve(opts.worktreesRoot.trim());
42338
43044
  }
42339
43045
  const e2eCertificates = opts.e2eeCertificatesDir?.trim() ? await loadOrCreateE2eCertificates(opts.e2eeCertificatesDir.trim()) : void 0;
42340
43046
  if (e2eCertificates) {