@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/index.js CHANGED
@@ -2237,7 +2237,7 @@ var require_websocket = __commonJS({
2237
2237
  "use strict";
2238
2238
  var EventEmitter2 = __require("events");
2239
2239
  var https2 = __require("https");
2240
- var http = __require("http");
2240
+ var http2 = __require("http");
2241
2241
  var net = __require("net");
2242
2242
  var tls = __require("tls");
2243
2243
  var { randomBytes: randomBytes2, createHash: createHash2 } = __require("crypto");
@@ -2771,7 +2771,7 @@ var require_websocket = __commonJS({
2771
2771
  }
2772
2772
  const defaultPort = isSecure ? 443 : 80;
2773
2773
  const key = randomBytes2(16).toString("base64");
2774
- const request = isSecure ? https2.request : http.request;
2774
+ const request = isSecure ? https2.request : http2.request;
2775
2775
  const protocolSet = /* @__PURE__ */ new Set();
2776
2776
  let perMessageDeflate;
2777
2777
  opts.createConnection = opts.createConnection || (isSecure ? tlsConnect : netConnect);
@@ -3265,7 +3265,7 @@ var require_websocket_server = __commonJS({
3265
3265
  "../../node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/websocket-server.js"(exports, module) {
3266
3266
  "use strict";
3267
3267
  var EventEmitter2 = __require("events");
3268
- var http = __require("http");
3268
+ var http2 = __require("http");
3269
3269
  var { Duplex } = __require("stream");
3270
3270
  var { createHash: createHash2 } = __require("crypto");
3271
3271
  var extension = require_extension();
@@ -3340,8 +3340,8 @@ var require_websocket_server = __commonJS({
3340
3340
  );
3341
3341
  }
3342
3342
  if (options.port != null) {
3343
- this._server = http.createServer((req, res) => {
3344
- const body = http.STATUS_CODES[426];
3343
+ this._server = http2.createServer((req, res) => {
3344
+ const body = http2.STATUS_CODES[426];
3345
3345
  res.writeHead(426, {
3346
3346
  "Content-Length": body.length,
3347
3347
  "Content-Type": "text/plain"
@@ -3628,7 +3628,7 @@ var require_websocket_server = __commonJS({
3628
3628
  this.destroy();
3629
3629
  }
3630
3630
  function abortHandshake(socket, code, message, headers) {
3631
- message = message || http.STATUS_CODES[code];
3631
+ message = message || http2.STATUS_CODES[code];
3632
3632
  headers = {
3633
3633
  Connection: "close",
3634
3634
  "Content-Type": "text/html",
@@ -3637,7 +3637,7 @@ var require_websocket_server = __commonJS({
3637
3637
  };
3638
3638
  socket.once("finish", socket.destroy);
3639
3639
  socket.end(
3640
- `HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r
3640
+ `HTTP/1.1 ${code} ${http2.STATUS_CODES[code]}\r
3641
3641
  ` + Object.keys(headers).map((h) => `${h}: ${headers[h]}`).join("\r\n") + "\r\n\r\n" + message
3642
3642
  );
3643
3643
  }
@@ -4175,8 +4175,8 @@ var init_parseUtil = __esm({
4175
4175
  init_errors();
4176
4176
  init_en();
4177
4177
  makeIssue = (params) => {
4178
- const { data, path: path44, errorMaps, issueData } = params;
4179
- const fullPath = [...path44, ...issueData.path || []];
4178
+ const { data, path: path46, errorMaps, issueData } = params;
4179
+ const fullPath = [...path46, ...issueData.path || []];
4180
4180
  const fullIssue = {
4181
4181
  ...issueData,
4182
4182
  path: fullPath
@@ -4484,11 +4484,11 @@ var init_types = __esm({
4484
4484
  init_parseUtil();
4485
4485
  init_util();
4486
4486
  ParseInputLazyPath = class {
4487
- constructor(parent, value, path44, key) {
4487
+ constructor(parent, value, path46, key) {
4488
4488
  this._cachedPath = [];
4489
4489
  this.parent = parent;
4490
4490
  this.data = value;
4491
- this._path = path44;
4491
+ this._path = path46;
4492
4492
  this._key = key;
4493
4493
  }
4494
4494
  get path() {
@@ -8103,15 +8103,15 @@ function assignProp(target, prop, value) {
8103
8103
  configurable: true
8104
8104
  });
8105
8105
  }
8106
- function getElementAtPath(obj, path44) {
8107
- if (!path44)
8106
+ function getElementAtPath(obj, path46) {
8107
+ if (!path46)
8108
8108
  return obj;
8109
- return path44.reduce((acc, key) => acc?.[key], obj);
8109
+ return path46.reduce((acc, key) => acc?.[key], obj);
8110
8110
  }
8111
8111
  function promiseAllObject(promisesObj) {
8112
8112
  const keys = Object.keys(promisesObj);
8113
- const promises5 = keys.map((key) => promisesObj[key]);
8114
- return Promise.all(promises5).then((results) => {
8113
+ const promises6 = keys.map((key) => promisesObj[key]);
8114
+ return Promise.all(promises6).then((results) => {
8115
8115
  const resolvedObj = {};
8116
8116
  for (let i = 0; i < keys.length; i++) {
8117
8117
  resolvedObj[keys[i]] = results[i];
@@ -8355,11 +8355,11 @@ function aborted(x, startIndex = 0) {
8355
8355
  }
8356
8356
  return false;
8357
8357
  }
8358
- function prefixIssues(path44, issues) {
8358
+ function prefixIssues(path46, issues) {
8359
8359
  return issues.map((iss) => {
8360
8360
  var _a2;
8361
8361
  (_a2 = iss).path ?? (_a2.path = []);
8362
- iss.path.unshift(path44);
8362
+ iss.path.unshift(path46);
8363
8363
  return iss;
8364
8364
  });
8365
8365
  }
@@ -8548,7 +8548,7 @@ function treeifyError(error40, _mapper) {
8548
8548
  return issue2.message;
8549
8549
  };
8550
8550
  const result = { errors: [] };
8551
- const processError = (error41, path44 = []) => {
8551
+ const processError = (error41, path46 = []) => {
8552
8552
  var _a2, _b;
8553
8553
  for (const issue2 of error41.issues) {
8554
8554
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -8558,7 +8558,7 @@ function treeifyError(error40, _mapper) {
8558
8558
  } else if (issue2.code === "invalid_element") {
8559
8559
  processError({ issues: issue2.issues }, issue2.path);
8560
8560
  } else {
8561
- const fullpath = [...path44, ...issue2.path];
8561
+ const fullpath = [...path46, ...issue2.path];
8562
8562
  if (fullpath.length === 0) {
8563
8563
  result.errors.push(mapper(issue2));
8564
8564
  continue;
@@ -8588,9 +8588,9 @@ function treeifyError(error40, _mapper) {
8588
8588
  processError(error40);
8589
8589
  return result;
8590
8590
  }
8591
- function toDotPath(path44) {
8591
+ function toDotPath(path46) {
8592
8592
  const segs = [];
8593
- for (const seg of path44) {
8593
+ for (const seg of path46) {
8594
8594
  if (typeof seg === "number")
8595
8595
  segs.push(`[${seg}]`);
8596
8596
  else if (typeof seg === "symbol")
@@ -22073,10 +22073,10 @@ var require_src2 = __commonJS({
22073
22073
  var fs_1 = __require("fs");
22074
22074
  var debug_1 = __importDefault(require_src());
22075
22075
  var log2 = debug_1.default("@kwsites/file-exists");
22076
- function check2(path44, isFile, isDirectory) {
22077
- log2(`checking %s`, path44);
22076
+ function check2(path46, isFile, isDirectory) {
22077
+ log2(`checking %s`, path46);
22078
22078
  try {
22079
- const stat2 = fs_1.statSync(path44);
22079
+ const stat2 = fs_1.statSync(path46);
22080
22080
  if (stat2.isFile() && isFile) {
22081
22081
  log2(`[OK] path represents a file`);
22082
22082
  return true;
@@ -22096,8 +22096,8 @@ var require_src2 = __commonJS({
22096
22096
  throw e;
22097
22097
  }
22098
22098
  }
22099
- function exists2(path44, type = exports.READABLE) {
22100
- return check2(path44, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
22099
+ function exists2(path46, type = exports.READABLE) {
22100
+ return check2(path46, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
22101
22101
  }
22102
22102
  exports.exists = exists2;
22103
22103
  exports.FILE = 1;
@@ -22940,9 +22940,9 @@ function parseChangeSummaryJson(raw, allowedPaths, options) {
22940
22940
  const rawPath = typeof o.path === "string" ? o.path.trim() : "";
22941
22941
  const summary = typeof o.summary === "string" ? o.summary.trim() : "";
22942
22942
  if (!rawPath || !summary) continue;
22943
- const path44 = skip ? normalizeRepoRelativePath(rawPath) || rawPath : resolveChangeSummaryPathAgainstAllowed(rawPath, allowedPaths);
22944
- if (!path44) continue;
22945
- rows.push({ path: path44, summary: clampSummaryToAtMostTwoLines(summary) });
22943
+ const path46 = skip ? normalizeRepoRelativePath(rawPath) || rawPath : resolveChangeSummaryPathAgainstAllowed(rawPath, allowedPaths);
22944
+ if (!path46) continue;
22945
+ rows.push({ path: path46, summary: clampSummaryToAtMostTwoLines(summary) });
22946
22946
  }
22947
22947
  return rows;
22948
22948
  }
@@ -23553,7 +23553,7 @@ function createSdkStdioSessionContext(options) {
23553
23553
  return {
23554
23554
  cwd: options.cwd,
23555
23555
  onFileChange: options.onFileChange,
23556
- mcpServers: [],
23556
+ mcpServers: options.mcpServers ?? [],
23557
23557
  persistedAcpSessionId: options.persistedAcpSessionId,
23558
23558
  agentLabel: "ACP",
23559
23559
  suppressLoadReplay: suppressLoadReplayRef,
@@ -23996,6 +23996,7 @@ async function createSdkStdioAcpClient(options) {
23996
23996
  });
23997
23997
  const sessionCtx = createSdkStdioSessionContext({
23998
23998
  cwd,
23999
+ mcpServers: options.mcpServers,
23999
24000
  persistedAcpSessionId,
24000
24001
  backendAgentType,
24001
24002
  agentConfig,
@@ -24240,8 +24241,8 @@ function randomSecret() {
24240
24241
  }
24241
24242
  return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
24242
24243
  }
24243
- async function requestPreviewApi(port, secret, method, path44, body) {
24244
- const url2 = `http://127.0.0.1:${port}${path44}`;
24244
+ async function requestPreviewApi(port, secret, method, path46, body) {
24245
+ const url2 = `http://127.0.0.1:${port}${path46}`;
24245
24246
  const headers = {
24246
24247
  [PREVIEW_SECRET_HEADER]: secret,
24247
24248
  "Content-Type": "application/json"
@@ -24253,7 +24254,7 @@ async function requestPreviewApi(port, secret, method, path44, body) {
24253
24254
  });
24254
24255
  const data = await res.json().catch(() => ({}));
24255
24256
  if (!res.ok) {
24256
- throw new Error(data?.error ?? `Preview API ${method} ${path44}: ${res.status}`);
24257
+ throw new Error(data?.error ?? `Preview API ${method} ${path46}: ${res.status}`);
24257
24258
  }
24258
24259
  return data;
24259
24260
  }
@@ -24468,7 +24469,7 @@ function installBridgeProcessResilience() {
24468
24469
  }
24469
24470
 
24470
24471
  // src/cli-version.ts
24471
- var CLI_VERSION = "0.1.45".length > 0 ? "0.1.45" : "0.0.0-dev";
24472
+ var CLI_VERSION = "0.1.47".length > 0 ? "0.1.47" : "0.0.0-dev";
24472
24473
 
24473
24474
  // src/connection/heartbeat/constants.ts
24474
24475
  var BRIDGE_APP_HEARTBEAT_INTERVAL_MS = 1e4;
@@ -26157,6 +26158,7 @@ var AcpPromptRoutingRegistry = class {
26157
26158
  function createAcpManagerContext(options) {
26158
26159
  return {
26159
26160
  log: options.log,
26161
+ getBridgeAccessPort: options.getBridgeAccessPort,
26160
26162
  reportAgentCapabilities: options.reportAgentCapabilities,
26161
26163
  backendFallbackAgentType: null,
26162
26164
  acpAgents: /* @__PURE__ */ new Map(),
@@ -26339,7 +26341,7 @@ function createCursorAcpSessionContext(options) {
26339
26341
  return {
26340
26342
  cwd: options.cwd,
26341
26343
  onFileChange: options.onFileChange,
26342
- mcpServers: [],
26344
+ mcpServers: options.mcpServers ?? [],
26343
26345
  persistedAcpSessionId: options.persistedAcpSessionId,
26344
26346
  agentLabel: "Cursor",
26345
26347
  suppressLoadReplay: suppressLoadReplayRef,
@@ -26765,6 +26767,7 @@ async function createCursorAcpClient(options) {
26765
26767
  });
26766
26768
  const sessionCtx = createCursorAcpSessionContext({
26767
26769
  cwd,
26770
+ mcpServers: options.mcpServers,
26768
26771
  persistedAcpSessionId,
26769
26772
  backendAgentType,
26770
26773
  agentConfig: options.agentConfig,
@@ -27723,6 +27726,30 @@ function computeAcpAgentKey(preferredAgentType, mode, agentConfig) {
27723
27726
  return `${resolved.label}::${fullCmd.join("\0")}${cacheConfig}`;
27724
27727
  }
27725
27728
 
27729
+ // src/mcp/bridge-mcp-constants.ts
27730
+ var BRIDGE_MCP_SERVER_NAME = "buildautomaton-bridge";
27731
+
27732
+ // src/mcp/resolve-bridge-mcp-server-path.ts
27733
+ import { dirname as dirname4, join as join2 } from "node:path";
27734
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
27735
+ function resolveBridgeMcpServerScriptPath() {
27736
+ const cliDir = dirname4(fileURLToPath4(import.meta.url));
27737
+ return join2(cliDir, "bridge-mcp-server.js");
27738
+ }
27739
+
27740
+ // src/mcp/build-acp-mcp-servers.ts
27741
+ function buildAcpMcpServers(bridgeAccessPort) {
27742
+ if (!Number.isFinite(bridgeAccessPort) || bridgeAccessPort <= 0) return [];
27743
+ return [
27744
+ {
27745
+ name: BRIDGE_MCP_SERVER_NAME,
27746
+ command: process.execPath,
27747
+ args: [resolveBridgeMcpServerScriptPath(), String(bridgeAccessPort)],
27748
+ env: []
27749
+ }
27750
+ ];
27751
+ }
27752
+
27726
27753
  // src/agents/acp/ensure-acp-client.ts
27727
27754
  function invalidateAcpClientState(state) {
27728
27755
  state.clientEpoch += 1;
@@ -27754,11 +27781,13 @@ async function ensureAcpClient(options) {
27754
27781
  sessionParentPath,
27755
27782
  resolveRouting,
27756
27783
  cloudSessionId,
27784
+ bridgeAccessPort,
27757
27785
  reportAgentCapabilities,
27758
27786
  sendSessionUpdate,
27759
27787
  sendRequest,
27760
27788
  log: log2
27761
27789
  } = options;
27790
+ const mcpServers = buildAcpMcpServers(bridgeAccessPort ?? 0);
27762
27791
  state.latestAgentConfigForBridgeHooks = agentConfig != null && typeof agentConfig === "object" && !Array.isArray(agentConfig) ? agentConfig : null;
27763
27792
  const targetSessionParentPath = resolveSessionParentPathForAgentProcess(sessionParentPath);
27764
27793
  if (state.acpStartPromise && !state.acpHandle) {
@@ -27874,7 +27903,8 @@ async function ensureAcpClient(options) {
27874
27903
  state.lastAcpStartError = "Agent subprocess exited";
27875
27904
  },
27876
27905
  ...hooks,
27877
- cwd: targetSessionParentPath
27906
+ cwd: targetSessionParentPath,
27907
+ mcpServers
27878
27908
  }).then(async (h) => {
27879
27909
  if (spawnEpoch !== state.clientEpoch || isCliImmediateShutdownRequested()) {
27880
27910
  try {
@@ -27965,8 +27995,8 @@ function pathspec(...paths) {
27965
27995
  cache.set(key, paths);
27966
27996
  return key;
27967
27997
  }
27968
- function isPathSpec(path44) {
27969
- return path44 instanceof String && cache.has(path44);
27998
+ function isPathSpec(path46) {
27999
+ return path46 instanceof String && cache.has(path46);
27970
28000
  }
27971
28001
  function toPaths(pathSpec) {
27972
28002
  return cache.get(pathSpec) || [];
@@ -28055,8 +28085,8 @@ function toLinesWithContent(input = "", trimmed2 = true, separator = "\n") {
28055
28085
  function forEachLineWithContent(input, callback) {
28056
28086
  return toLinesWithContent(input, true).map((line) => callback(line));
28057
28087
  }
28058
- function folderExists(path44) {
28059
- return (0, import_file_exists.exists)(path44, import_file_exists.FOLDER);
28088
+ function folderExists(path46) {
28089
+ return (0, import_file_exists.exists)(path46, import_file_exists.FOLDER);
28060
28090
  }
28061
28091
  function append(target, item) {
28062
28092
  if (Array.isArray(target)) {
@@ -28460,8 +28490,8 @@ function checkIsRepoRootTask() {
28460
28490
  commands,
28461
28491
  format: "utf-8",
28462
28492
  onError,
28463
- parser(path44) {
28464
- return /^\.(git)?$/.test(path44.trim());
28493
+ parser(path46) {
28494
+ return /^\.(git)?$/.test(path46.trim());
28465
28495
  }
28466
28496
  };
28467
28497
  }
@@ -28895,11 +28925,11 @@ function parseGrep(grep) {
28895
28925
  const paths = /* @__PURE__ */ new Set();
28896
28926
  const results = {};
28897
28927
  forEachLineWithContent(grep, (input) => {
28898
- const [path44, line, preview] = input.split(NULL);
28899
- paths.add(path44);
28900
- (results[path44] = results[path44] || []).push({
28928
+ const [path46, line, preview] = input.split(NULL);
28929
+ paths.add(path46);
28930
+ (results[path46] = results[path46] || []).push({
28901
28931
  line: asNumber(line),
28902
- path: path44,
28932
+ path: path46,
28903
28933
  preview
28904
28934
  });
28905
28935
  });
@@ -29664,14 +29694,14 @@ var init_hash_object = __esm2({
29664
29694
  init_task();
29665
29695
  }
29666
29696
  });
29667
- function parseInit(bare, path44, text) {
29697
+ function parseInit(bare, path46, text) {
29668
29698
  const response = String(text).trim();
29669
29699
  let result;
29670
29700
  if (result = initResponseRegex.exec(response)) {
29671
- return new InitSummary(bare, path44, false, result[1]);
29701
+ return new InitSummary(bare, path46, false, result[1]);
29672
29702
  }
29673
29703
  if (result = reInitResponseRegex.exec(response)) {
29674
- return new InitSummary(bare, path44, true, result[1]);
29704
+ return new InitSummary(bare, path46, true, result[1]);
29675
29705
  }
29676
29706
  let gitDir = "";
29677
29707
  const tokens = response.split(" ");
@@ -29682,7 +29712,7 @@ function parseInit(bare, path44, text) {
29682
29712
  break;
29683
29713
  }
29684
29714
  }
29685
- return new InitSummary(bare, path44, /^re/i.test(response), gitDir);
29715
+ return new InitSummary(bare, path46, /^re/i.test(response), gitDir);
29686
29716
  }
29687
29717
  var InitSummary;
29688
29718
  var initResponseRegex;
@@ -29691,9 +29721,9 @@ var init_InitSummary = __esm2({
29691
29721
  "src/lib/responses/InitSummary.ts"() {
29692
29722
  "use strict";
29693
29723
  InitSummary = class {
29694
- constructor(bare, path44, existing, gitDir) {
29724
+ constructor(bare, path46, existing, gitDir) {
29695
29725
  this.bare = bare;
29696
- this.path = path44;
29726
+ this.path = path46;
29697
29727
  this.existing = existing;
29698
29728
  this.gitDir = gitDir;
29699
29729
  }
@@ -29705,7 +29735,7 @@ var init_InitSummary = __esm2({
29705
29735
  function hasBareCommand(command) {
29706
29736
  return command.includes(bareCommand);
29707
29737
  }
29708
- function initTask(bare = false, path44, customArgs) {
29738
+ function initTask(bare = false, path46, customArgs) {
29709
29739
  const commands = ["init", ...customArgs];
29710
29740
  if (bare && !hasBareCommand(commands)) {
29711
29741
  commands.splice(1, 0, bareCommand);
@@ -29714,7 +29744,7 @@ function initTask(bare = false, path44, customArgs) {
29714
29744
  commands,
29715
29745
  format: "utf-8",
29716
29746
  parser(text) {
29717
- return parseInit(commands.includes("--bare"), path44, text);
29747
+ return parseInit(commands.includes("--bare"), path46, text);
29718
29748
  }
29719
29749
  };
29720
29750
  }
@@ -30530,12 +30560,12 @@ var init_FileStatusSummary = __esm2({
30530
30560
  "use strict";
30531
30561
  fromPathRegex = /^(.+)\0(.+)$/;
30532
30562
  FileStatusSummary = class {
30533
- constructor(path44, index, working_dir) {
30534
- this.path = path44;
30563
+ constructor(path46, index, working_dir) {
30564
+ this.path = path46;
30535
30565
  this.index = index;
30536
30566
  this.working_dir = working_dir;
30537
30567
  if (index === "R" || working_dir === "R") {
30538
- const detail = fromPathRegex.exec(path44) || [null, path44, path44];
30568
+ const detail = fromPathRegex.exec(path46) || [null, path46, path46];
30539
30569
  this.from = detail[2] || "";
30540
30570
  this.path = detail[1] || "";
30541
30571
  }
@@ -30566,14 +30596,14 @@ function splitLine(result, lineStr) {
30566
30596
  default:
30567
30597
  return;
30568
30598
  }
30569
- function data(index, workingDir, path44) {
30599
+ function data(index, workingDir, path46) {
30570
30600
  const raw = `${index}${workingDir}`;
30571
30601
  const handler = parsers6.get(raw);
30572
30602
  if (handler) {
30573
- handler(result, path44);
30603
+ handler(result, path46);
30574
30604
  }
30575
30605
  if (raw !== "##" && raw !== "!!") {
30576
- result.files.push(new FileStatusSummary(path44, index, workingDir));
30606
+ result.files.push(new FileStatusSummary(path46, index, workingDir));
30577
30607
  }
30578
30608
  }
30579
30609
  }
@@ -30882,9 +30912,9 @@ var init_simple_git_api = __esm2({
30882
30912
  next
30883
30913
  );
30884
30914
  }
30885
- hashObject(path44, write) {
30915
+ hashObject(path46, write) {
30886
30916
  return this._runTask(
30887
- hashObjectTask(path44, write === true),
30917
+ hashObjectTask(path46, write === true),
30888
30918
  trailingFunctionArgument(arguments)
30889
30919
  );
30890
30920
  }
@@ -31237,8 +31267,8 @@ var init_branch = __esm2({
31237
31267
  }
31238
31268
  });
31239
31269
  function toPath(input) {
31240
- const path44 = input.trim().replace(/^["']|["']$/g, "");
31241
- return path44 && normalize3(path44);
31270
+ const path46 = input.trim().replace(/^["']|["']$/g, "");
31271
+ return path46 && normalize3(path46);
31242
31272
  }
31243
31273
  var parseCheckIgnore;
31244
31274
  var init_CheckIgnore = __esm2({
@@ -31552,8 +31582,8 @@ __export2(sub_module_exports, {
31552
31582
  subModuleTask: () => subModuleTask,
31553
31583
  updateSubModuleTask: () => updateSubModuleTask
31554
31584
  });
31555
- function addSubModuleTask(repo, path44) {
31556
- return subModuleTask(["add", repo, path44]);
31585
+ function addSubModuleTask(repo, path46) {
31586
+ return subModuleTask(["add", repo, path46]);
31557
31587
  }
31558
31588
  function initSubModuleTask(customArgs) {
31559
31589
  return subModuleTask(["init", ...customArgs]);
@@ -31886,8 +31916,8 @@ var require_git = __commonJS2({
31886
31916
  }
31887
31917
  return this._runTask(straightThroughStringTask2(command, this._trimmed), next);
31888
31918
  };
31889
- Git2.prototype.submoduleAdd = function(repo, path44, then) {
31890
- return this._runTask(addSubModuleTask2(repo, path44), trailingFunctionArgument2(arguments));
31919
+ Git2.prototype.submoduleAdd = function(repo, path46, then) {
31920
+ return this._runTask(addSubModuleTask2(repo, path46), trailingFunctionArgument2(arguments));
31891
31921
  };
31892
31922
  Git2.prototype.submoduleUpdate = function(args, then) {
31893
31923
  return this._runTask(
@@ -32850,12 +32880,12 @@ async function collectTurnGitDiffFromPreTurnSnapshot(options) {
32850
32880
  // src/agents/acp/put-summarize-change-summaries.ts
32851
32881
  async function putEncryptedChangeSummaryRows(params) {
32852
32882
  const base = params.apiBaseUrl.replace(/\/+$/, "");
32853
- const entries = params.rows.map(({ path: path44, summary }) => {
32883
+ const entries = params.rows.map(({ path: path46, summary }) => {
32854
32884
  const enc = params.e2ee.encryptFields({ summary }, ["summary"]);
32855
- return { path: path44, summary: JSON.stringify(enc) };
32885
+ return { path: path46, summary: JSON.stringify(enc) };
32856
32886
  });
32857
32887
  const res = await fetch(
32858
- `${base}/api/sessions/${encodeURIComponent(params.sessionId)}/follow-ups/summarize-changes`,
32888
+ `${base}/internal/sessions/${encodeURIComponent(params.sessionId)}/follow-ups/summarize-changes`,
32859
32889
  {
32860
32890
  method: "PUT",
32861
32891
  headers: {
@@ -33020,8 +33050,8 @@ async function fetchSessionAttachmentPayloadsForAgent(params) {
33020
33050
  for (const a of attachments) {
33021
33051
  const id = typeof a.attachmentId === "string" ? a.attachmentId.trim() : "";
33022
33052
  if (!id) continue;
33023
- const metaUrl = `${base}/api/sessions/${encodeURIComponent(sessionId)}/attachments/${encodeURIComponent(id)}/meta`;
33024
- const blobUrl = `${base}/api/sessions/${encodeURIComponent(sessionId)}/attachments/${encodeURIComponent(id)}/blob`;
33053
+ const metaUrl = `${base}/internal/sessions/${encodeURIComponent(sessionId)}/attachments/${encodeURIComponent(id)}/meta`;
33054
+ const blobUrl = `${base}/internal/sessions/${encodeURIComponent(sessionId)}/attachments/${encodeURIComponent(id)}/blob`;
33025
33055
  const metaRes = await fetch(metaUrl, { headers: { Authorization: `Bearer ${token}` } });
33026
33056
  if (!metaRes.ok) {
33027
33057
  const t = await metaRes.text().catch(() => "");
@@ -33069,6 +33099,106 @@ async function fetchSessionAttachmentPayloadsForAgent(params) {
33069
33099
  return { ok: true, images: out };
33070
33100
  }
33071
33101
 
33102
+ // src/agents/acp/build-forked-session-agent-prompt.ts
33103
+ function buildForkedSessionAgentPrompt(userPrompt, childSessionId, parentSessionId) {
33104
+ const childRef = childSessionId.trim();
33105
+ const parentRef = parentSessionId.trim();
33106
+ return [
33107
+ "You are working in a new session that was forked from a prior session.",
33108
+ `This forked session id: ${childRef}`,
33109
+ `Parent session id: ${parentRef}`,
33110
+ "",
33111
+ "The parent session history is available through MCP tools on the buildautomaton-bridge server:",
33112
+ "- list_parent_session_prompts \u2014 list each user prompt / turn in the parent session",
33113
+ "- get_parent_session_turn_transcript \u2014 full transcript for one turn (including tool calls); requires turnId",
33114
+ "",
33115
+ "When calling these tools, always pass this forked session id as sessionId (not the parent session id).",
33116
+ "",
33117
+ "Use these tools when you need prior context instead of guessing.",
33118
+ "",
33119
+ "User request:",
33120
+ userPrompt.trim()
33121
+ ].join("\n");
33122
+ }
33123
+
33124
+ // src/mcp/tools/session-history/fetch-parent-session-cloud.ts
33125
+ function internalApiBase(cloudApiBaseUrl) {
33126
+ return cloudApiBaseUrl.replace(/\/+$/, "");
33127
+ }
33128
+ async function fetchCloudSessionMeta(params) {
33129
+ const token = params.getCloudAccessToken();
33130
+ if (!token) return { ok: false, error: "Missing cloud access token." };
33131
+ const url2 = `${internalApiBase(params.cloudApiBaseUrl)}/internal/sessions/${encodeURIComponent(params.sessionId)}/meta`;
33132
+ const res = await fetch(url2, { headers: { Authorization: `Bearer ${token}` } });
33133
+ if (!res.ok) {
33134
+ const t = await res.text().catch(() => "");
33135
+ return { ok: false, error: `Session meta fetch failed (${res.status}): ${t.slice(0, 200)}` };
33136
+ }
33137
+ const data = await res.json().catch(() => null);
33138
+ if (!data) return { ok: false, error: "Invalid session meta response." };
33139
+ return { ok: true, data };
33140
+ }
33141
+ async function fetchCloudParentSessionPrompts(params) {
33142
+ const token = params.getCloudAccessToken();
33143
+ if (!token) return { ok: false, error: "Missing cloud access token." };
33144
+ const url2 = `${internalApiBase(params.cloudApiBaseUrl)}/internal/sessions/${encodeURIComponent(params.childSessionId)}/parent/prompts`;
33145
+ const res = await fetch(url2, { headers: { Authorization: `Bearer ${token}` } });
33146
+ if (!res.ok) {
33147
+ const t = await res.text().catch(() => "");
33148
+ return { ok: false, error: `Parent prompts fetch failed (${res.status}): ${t.slice(0, 200)}` };
33149
+ }
33150
+ const data = await res.json().catch(() => null);
33151
+ if (!data) return { ok: false, error: "Invalid parent prompts response." };
33152
+ return { ok: true, data };
33153
+ }
33154
+ async function fetchCloudParentTurnTranscript(params) {
33155
+ const token = params.getCloudAccessToken();
33156
+ if (!token) return { ok: false, error: "Missing cloud access token." };
33157
+ const url2 = `${internalApiBase(params.cloudApiBaseUrl)}/internal/sessions/${encodeURIComponent(params.childSessionId)}/parent/turns/${encodeURIComponent(params.turnId)}/transcript`;
33158
+ const res = await fetch(url2, { headers: { Authorization: `Bearer ${token}` } });
33159
+ if (!res.ok) {
33160
+ const t = await res.text().catch(() => "");
33161
+ return { ok: false, error: `Transcript fetch failed (${res.status}): ${t.slice(0, 200)}` };
33162
+ }
33163
+ const data = await res.json().catch(() => null);
33164
+ const rows = Array.isArray(data?.transcript) ? data.transcript : [];
33165
+ const lines = rows.map((row) => {
33166
+ const kind = typeof row.kind === "string" ? row.kind : "event";
33167
+ const payload = typeof row.payload === "string" ? row.payload : JSON.stringify(row.payload ?? "");
33168
+ return `[${kind}] ${payload}`;
33169
+ });
33170
+ return { ok: true, text: lines.join("\n") };
33171
+ }
33172
+ async function resolveParentSessionIdForChild(params) {
33173
+ const meta = await fetchCloudSessionMeta({
33174
+ sessionId: params.childSessionId,
33175
+ cloudApiBaseUrl: params.cloudApiBaseUrl,
33176
+ getCloudAccessToken: params.getCloudAccessToken
33177
+ });
33178
+ if (!meta.ok) return meta;
33179
+ const parent = meta.data.parentSessionId?.trim() ?? "";
33180
+ if (!parent) {
33181
+ return {
33182
+ ok: false,
33183
+ 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.`
33184
+ };
33185
+ }
33186
+ return { ok: true, parentSessionId: parent };
33187
+ }
33188
+
33189
+ // src/agents/acp/enrich-forked-session-prompt-for-agent.ts
33190
+ async function enrichForkedSessionPromptForAgent(params) {
33191
+ const meta = await fetchCloudSessionMeta({
33192
+ sessionId: params.sessionId,
33193
+ cloudApiBaseUrl: params.cloudApiBaseUrl,
33194
+ getCloudAccessToken: params.getCloudAccessToken
33195
+ });
33196
+ if (!meta.ok) return params.promptText;
33197
+ const parentSessionId = meta.data.parentSessionId?.trim() ?? "";
33198
+ if (!parentSessionId) return params.promptText;
33199
+ return buildForkedSessionAgentPrompt(params.promptText, params.sessionId, parentSessionId);
33200
+ }
33201
+
33072
33202
  // src/agents/acp/send-prompt-to-agent.ts
33073
33203
  async function sendPromptToAgent(options) {
33074
33204
  const {
@@ -33090,6 +33220,15 @@ async function sendPromptToAgent(options) {
33090
33220
  attachments
33091
33221
  } = options;
33092
33222
  try {
33223
+ let agentPromptText = promptText;
33224
+ if (sessionId && cloudApiBaseUrl && getCloudAccessToken) {
33225
+ agentPromptText = await enrichForkedSessionPromptForAgent({
33226
+ sessionId,
33227
+ promptText,
33228
+ cloudApiBaseUrl,
33229
+ getCloudAccessToken
33230
+ });
33231
+ }
33093
33232
  let sendOpts = {};
33094
33233
  if (attachments && attachments.length > 0) {
33095
33234
  if (!sessionId || !cloudApiBaseUrl || !getCloudAccessToken) {
@@ -33128,7 +33267,7 @@ async function sendPromptToAgent(options) {
33128
33267
  }
33129
33268
  sendOpts = { images: resolved.images };
33130
33269
  }
33131
- const result = await handle.sendPrompt(promptText, sendOpts);
33270
+ const result = await handle.sendPrompt(agentPromptText, sendOpts);
33132
33271
  if (sessionId && runId && sendSessionUpdate && agentCwd && result.success) {
33133
33272
  await collectTurnGitDiffFromPreTurnSnapshot({
33134
33273
  sessionId,
@@ -33252,6 +33391,7 @@ function handlePrompt(ctx, opts) {
33252
33391
  sessionParentPath,
33253
33392
  resolveRouting: () => ctx.promptRouting.resolveRouting(activeAcpSessionAgentKey),
33254
33393
  cloudSessionId: sessionId,
33394
+ bridgeAccessPort: ctx.getBridgeAccessPort(),
33255
33395
  sendSessionUpdate,
33256
33396
  sendRequest: sendSessionUpdate,
33257
33397
  log: ctx.log,
@@ -33338,6 +33478,7 @@ function logPromptReceivedFromBridge(ctx, opts) {
33338
33478
  async function createAcpManager(options) {
33339
33479
  const ctx = createAcpManagerContext({
33340
33480
  log: options.log,
33481
+ getBridgeAccessPort: options.getBridgeAccessPort,
33341
33482
  reportAgentCapabilities: options.reportAgentCapabilities
33342
33483
  });
33343
33484
  return {
@@ -33361,7 +33502,7 @@ async function createAcpManager(options) {
33361
33502
  // src/git/changes/types.ts
33362
33503
  var MAX_PATCH_CHARS = 35e4;
33363
33504
 
33364
- // src/git/changes/lib/repo-format.ts
33505
+ // src/git/changes/paths/repo-format.ts
33365
33506
  function posixJoinDirFile(dir, file2) {
33366
33507
  const d = dir === "." || dir === "" ? "" : dir.replace(/\\/g, "/").replace(/\/+$/, "");
33367
33508
  const f = file2.replace(/\\/g, "/").replace(/^\/+/, "");
@@ -33416,7 +33557,7 @@ function formatRemoteDisplayLabel(remoteUrl) {
33416
33557
  }
33417
33558
 
33418
33559
  // src/git/changes/get-working-tree-change-repo-details.ts
33419
- import * as path20 from "node:path";
33560
+ import * as path22 from "node:path";
33420
33561
 
33421
33562
  // src/git/commits/resolve-remote-tracking.ts
33422
33563
  async function tryConfigGet(g, key) {
@@ -33586,9 +33727,232 @@ async function listRecentCommits(repoDir, limitInput) {
33586
33727
  }
33587
33728
  }
33588
33729
 
33589
- // src/git/changes/lib/parse-git-status.ts
33590
- function parseNameStatusLines(lines) {
33591
- const m = /* @__PURE__ */ new Map();
33730
+ // src/git/changes/parse/normalize-git-diff-path.ts
33731
+ function normalizeGitDiffPath(path46) {
33732
+ return path46.replace(/\\/g, "/").replace(/\/ +/g, "/").trim();
33733
+ }
33734
+
33735
+ // src/git/changes/parse/expand-git-rename-numstat-path.ts
33736
+ function expandGitRenameNumstatPath(rawPath) {
33737
+ const open2 = rawPath.indexOf("{");
33738
+ const arrow = rawPath.indexOf("=>");
33739
+ const close = rawPath.lastIndexOf("}");
33740
+ if (open2 === -1 || arrow === -1 || close === -1 || open2 > arrow || arrow > close) {
33741
+ return null;
33742
+ }
33743
+ const prefix = rawPath.slice(0, open2);
33744
+ const suffix = rawPath.slice(close + 1);
33745
+ const oldPart = rawPath.slice(open2 + 1, arrow).trim();
33746
+ const newPart = rawPath.slice(arrow + 2, close).trim();
33747
+ return {
33748
+ oldPath: normalizeGitDiffPath(`${prefix}${oldPart}${suffix}`),
33749
+ newPath: normalizeGitDiffPath(`${prefix}${newPart}${suffix}`)
33750
+ };
33751
+ }
33752
+ function isGitRenameNumstatPath(rawPath) {
33753
+ return expandGitRenameNumstatPath(rawPath) != null;
33754
+ }
33755
+
33756
+ // src/git/changes/rows/build-changed-file-row.ts
33757
+ function buildChangedFileRow(options) {
33758
+ const pathInRepo = normalizeGitDiffPath(options.pathInRepo);
33759
+ const relLauncher = posixJoinDirFile(options.repoRelPath, pathInRepo);
33760
+ const additions = options.numEntry?.additions ?? 0;
33761
+ const deletions = options.numEntry?.deletions ?? 0;
33762
+ let change = options.nameEntry?.change ?? "modified";
33763
+ let movedFromPathRelLauncher;
33764
+ let movedFromPathInRepo;
33765
+ const oldPathInRepo = options.nameEntry?.oldPathInRepo ?? options.numEntry?.oldPathInRepo;
33766
+ if (change === "moved" && oldPathInRepo) {
33767
+ movedFromPathInRepo = normalizeGitDiffPath(oldPathInRepo);
33768
+ movedFromPathRelLauncher = posixJoinDirFile(options.repoRelPath, movedFromPathInRepo);
33769
+ } else if (oldPathInRepo && change === "modified") {
33770
+ change = "moved";
33771
+ movedFromPathInRepo = normalizeGitDiffPath(oldPathInRepo);
33772
+ movedFromPathRelLauncher = posixJoinDirFile(options.repoRelPath, movedFromPathInRepo);
33773
+ }
33774
+ if (options.untracked && !options.nameEntry) {
33775
+ change = "added";
33776
+ } else if (!options.nameEntry && options.numEntry && change !== "moved") {
33777
+ if (additions > 0 && deletions === 0) change = "added";
33778
+ else if (deletions > 0 && additions === 0) change = "removed";
33779
+ else change = "modified";
33780
+ }
33781
+ return {
33782
+ pathRelLauncher: relLauncher,
33783
+ additions,
33784
+ deletions,
33785
+ change,
33786
+ ...movedFromPathRelLauncher ? { movedFromPathRelLauncher } : {},
33787
+ ...movedFromPathInRepo ? { movedFromPathInRepo } : {}
33788
+ };
33789
+ }
33790
+
33791
+ // src/git/changes/listing/build-changed-file-rows.ts
33792
+ async function buildChangedFileRowsFromPaths(options) {
33793
+ const rows = [];
33794
+ await forEachWithGitYield([...options.paths], async (pathInRepo) => {
33795
+ if (isGitRenameNumstatPath(pathInRepo)) return;
33796
+ if (options.movedSourcePaths.has(pathInRepo)) return;
33797
+ const nameEntry = options.nameByPath.get(pathInRepo);
33798
+ const numEntry = options.numByPath.get(pathInRepo);
33799
+ const row = buildChangedFileRow({
33800
+ pathInRepo,
33801
+ repoRelPath: options.repoRelPath,
33802
+ nameEntry,
33803
+ numEntry,
33804
+ untracked: options.untrackedSet?.has(pathInRepo)
33805
+ });
33806
+ if (options.applyUntrackedStats) {
33807
+ await options.applyUntrackedStats(row, pathInRepo);
33808
+ }
33809
+ rows.push(row);
33810
+ });
33811
+ return rows;
33812
+ }
33813
+
33814
+ // src/git/changes/patch/patch-truncate.ts
33815
+ function truncatePatch(s) {
33816
+ if (s.length <= MAX_PATCH_CHARS) return s;
33817
+ return `${s.slice(0, MAX_PATCH_CHARS)}
33818
+
33819
+ \u2026 (diff truncated)`;
33820
+ }
33821
+
33822
+ // src/git/changes/patch/unified-diff-for-file.ts
33823
+ function patchTextFromGitDiffOutput(raw) {
33824
+ if (raw.trim() === "") return void 0;
33825
+ return normalizePatchContent(raw);
33826
+ }
33827
+ async function unifiedDiffForFile(repoCwd, pathInRepo, change, movedFromPathInRepo) {
33828
+ const g = cliSimpleGit(repoCwd);
33829
+ const renameArgs = ["-M", "--find-renames"];
33830
+ 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];
33831
+ const raw = await g.raw([...args]).catch(() => "");
33832
+ const patch = patchTextFromGitDiffOutput(String(raw));
33833
+ return patch ? truncatePatch(patch) : void 0;
33834
+ }
33835
+ async function unifiedDiffForFileInRange(repoCwd, range, pathInRepo, change, movedFromPathInRepo) {
33836
+ const g = cliSimpleGit(repoCwd);
33837
+ const renameArgs = ["-M", "--find-renames"];
33838
+ const args = change === "moved" && movedFromPathInRepo ? ["diff", "--no-color", ...renameArgs, "-U20000", range, "--", movedFromPathInRepo, pathInRepo] : ["diff", "--no-color", ...renameArgs, "-U20000", range, "--", pathInRepo];
33839
+ const raw = await g.raw([...args]).catch(() => "");
33840
+ const patch = patchTextFromGitDiffOutput(String(raw));
33841
+ return patch ? truncatePatch(patch) : void 0;
33842
+ }
33843
+
33844
+ // src/git/changes/total-lines/resolve-changed-file-total-lines.ts
33845
+ import * as fs16 from "node:fs";
33846
+ import * as path19 from "node:path";
33847
+
33848
+ // src/git/changes/lines/count-lines.ts
33849
+ import { createReadStream } from "node:fs";
33850
+ import * as readline2 from "node:readline";
33851
+ function countLinesInText(text) {
33852
+ return splitTextIntoDiffLines(text).length;
33853
+ }
33854
+ async function countTextFileLines(filePath) {
33855
+ let bytes = 0;
33856
+ const maxBytes = 512e3;
33857
+ let lines = 0;
33858
+ const stream = createReadStream(filePath, { encoding: "utf8" });
33859
+ const rl = readline2.createInterface({ input: stream, crlfDelay: Infinity });
33860
+ for await (const _line of rl) {
33861
+ lines += 1;
33862
+ bytes += Buffer.byteLength(String(_line), "utf8") + 1;
33863
+ if (bytes > maxBytes) {
33864
+ rl.close();
33865
+ stream.destroy();
33866
+ return lines;
33867
+ }
33868
+ }
33869
+ return lines;
33870
+ }
33871
+
33872
+ // src/git/changes/total-lines/resolve-changed-file-total-lines.ts
33873
+ var MAX_BYTES = 512e3;
33874
+ async function countLinesInGitRef(repoGitCwd, ref, pathInRepo) {
33875
+ const spec = `${ref}:${pathInRepo}`;
33876
+ const g = cliSimpleGit(repoGitCwd);
33877
+ try {
33878
+ await g.raw(["cat-file", "-e", spec]);
33879
+ } catch {
33880
+ return null;
33881
+ }
33882
+ try {
33883
+ const raw = String(await g.raw(["show", spec]));
33884
+ const capped = raw.length > MAX_BYTES ? raw.slice(0, MAX_BYTES) : raw;
33885
+ return countLinesInText(capped);
33886
+ } catch {
33887
+ return null;
33888
+ }
33889
+ }
33890
+ async function resolveWorkingTreeFileTotalLines(options) {
33891
+ if (options.isBinary) return null;
33892
+ const filePath = path19.join(options.repoGitCwd, options.pathInRepo);
33893
+ if (options.change === "removed") {
33894
+ return countLinesInGitRef(options.repoGitCwd, "HEAD", options.pathInRepo);
33895
+ }
33896
+ if (options.change === "moved" || options.change === "modified") {
33897
+ try {
33898
+ const st = await fs16.promises.stat(filePath);
33899
+ if (!st.isFile()) return null;
33900
+ return await countTextFileLines(filePath);
33901
+ } catch {
33902
+ return null;
33903
+ }
33904
+ }
33905
+ return null;
33906
+ }
33907
+ async function resolveCommitFileTotalLines(options) {
33908
+ if (options.isBinary) return null;
33909
+ if (options.change === "removed") {
33910
+ return countLinesInGitRef(options.repoGitCwd, options.parentSha, options.pathInRepo);
33911
+ }
33912
+ if (options.change === "moved" || options.change === "modified") {
33913
+ return countLinesInGitRef(options.repoGitCwd, options.commitSha, options.pathInRepo);
33914
+ }
33915
+ return null;
33916
+ }
33917
+
33918
+ // src/git/changes/listing/path-in-repo-from-launcher.ts
33919
+ function pathInRepoFromLauncher(pathRelLauncher, repoRelPath) {
33920
+ const normRel = repoRelPath === "." || repoRelPath === "" ? "." : repoRelPath;
33921
+ if (normRel === ".") return pathRelLauncher;
33922
+ if (pathRelLauncher.startsWith(`${normRel}/`)) {
33923
+ return pathRelLauncher.slice(normRel.length + 1);
33924
+ }
33925
+ return pathRelLauncher;
33926
+ }
33927
+ function normalizeRepoRelPath(repoRelPath) {
33928
+ return repoRelPath === "." || repoRelPath === "" ? "." : repoRelPath;
33929
+ }
33930
+
33931
+ // src/git/changes/listing/enrich-commit-file-rows.ts
33932
+ async function enrichCommitFileRows(options) {
33933
+ await forEachWithGitYield(options.rows, async (row) => {
33934
+ const pathInRepo = pathInRepoFromLauncher(row.pathRelLauncher, options.repoRelPath);
33935
+ row.patchContent = await unifiedDiffForFileInRange(
33936
+ options.repoGitCwd,
33937
+ options.range,
33938
+ pathInRepo,
33939
+ row.change,
33940
+ row.movedFromPathInRepo
33941
+ );
33942
+ row.totalLines = await resolveCommitFileTotalLines({
33943
+ repoGitCwd: options.repoGitCwd,
33944
+ pathInRepo,
33945
+ change: row.change,
33946
+ commitSha: options.commitSha,
33947
+ parentSha: options.parentSha,
33948
+ isBinary: options.binaryByPath.has(pathInRepo)
33949
+ });
33950
+ });
33951
+ }
33952
+
33953
+ // src/git/changes/parse/parse-name-status-entries.ts
33954
+ function parseNameStatusEntries(lines) {
33955
+ const entries = [];
33592
33956
  for (const line of lines) {
33593
33957
  if (!line.trim()) continue;
33594
33958
  const tabParts = line.split(" ");
@@ -33596,58 +33960,143 @@ function parseNameStatusLines(lines) {
33596
33960
  const status = tabParts[0].trim();
33597
33961
  const code = status[0];
33598
33962
  if (code === "A") {
33599
- m.set(tabParts[tabParts.length - 1], "added");
33963
+ entries.push({ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]), change: "added" });
33600
33964
  } else if (code === "D") {
33601
- m.set(tabParts[tabParts.length - 1], "removed");
33965
+ entries.push({ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]), change: "removed" });
33602
33966
  } else if (code === "R" || code === "C") {
33603
- if (tabParts.length >= 3) m.set(tabParts[tabParts.length - 1], "modified");
33967
+ if (tabParts.length >= 3) {
33968
+ entries.push({
33969
+ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]),
33970
+ oldPathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 2]),
33971
+ change: "moved"
33972
+ });
33973
+ }
33604
33974
  } else if (code === "M" || code === "U" || code === "T") {
33605
- m.set(tabParts[tabParts.length - 1], "modified");
33975
+ entries.push({ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]), change: "modified" });
33606
33976
  }
33607
33977
  }
33978
+ return entries;
33979
+ }
33980
+ function parseNameStatusLines(lines) {
33981
+ const m = /* @__PURE__ */ new Map();
33982
+ for (const entry of parseNameStatusEntries(lines)) {
33983
+ m.set(entry.pathInRepo, entry.change);
33984
+ }
33608
33985
  return m;
33609
33986
  }
33610
- function parseNumstatFirstLine(line) {
33987
+
33988
+ // src/git/changes/parse/parse-numstat-entries.ts
33989
+ function parseNumstatCounts(parts) {
33990
+ const [a, d] = parts;
33991
+ return {
33992
+ additions: a === "-" ? 0 : parseInt(String(a), 10) || 0,
33993
+ deletions: d === "-" ? 0 : parseInt(String(d), 10) || 0
33994
+ };
33995
+ }
33996
+ function parseNumstatLine(line) {
33611
33997
  const parts = line.split(" ");
33612
33998
  if (parts.length < 3) return null;
33613
- const [a, d] = parts;
33614
- const additions = a === "-" ? 0 : parseInt(String(a), 10) || 0;
33615
- const deletions = d === "-" ? 0 : parseInt(String(d), 10) || 0;
33616
- return { additions, deletions };
33999
+ const rawPath = parts[parts.length - 1];
34000
+ const { additions, deletions } = parseNumstatCounts(parts);
34001
+ const expanded = expandGitRenameNumstatPath(rawPath);
34002
+ const pathInRepo = normalizeGitDiffPath(expanded?.newPath ?? rawPath);
34003
+ return {
34004
+ pathInRepo,
34005
+ additions,
34006
+ deletions,
34007
+ ...expanded ? { oldPathInRepo: expanded.oldPath } : {}
34008
+ };
33617
34009
  }
33618
- function parseNumstat(lines) {
34010
+ function parseNumstatEntries(lines) {
33619
34011
  const m = /* @__PURE__ */ new Map();
33620
34012
  for (const line of lines) {
33621
34013
  if (!line.trim()) continue;
33622
34014
  const parts = line.split(" ");
33623
34015
  if (parts.length < 3) continue;
33624
- const [a, d, p] = parts;
33625
- const additions = a === "-" ? 0 : parseInt(String(a), 10) || 0;
33626
- const deletions = d === "-" ? 0 : parseInt(String(d), 10) || 0;
33627
- m.set(p, { additions, deletions });
34016
+ const rawPath = parts[parts.length - 1];
34017
+ const expanded = expandGitRenameNumstatPath(rawPath);
34018
+ const entry = parseNumstatLine(line);
34019
+ if (!entry) continue;
34020
+ const existing = m.get(entry.pathInRepo);
34021
+ if (!existing || expanded) {
34022
+ m.set(entry.pathInRepo, entry);
34023
+ }
33628
34024
  }
33629
34025
  return m;
33630
34026
  }
34027
+ function parseNumstat(lines) {
34028
+ const m = /* @__PURE__ */ new Map();
34029
+ for (const [path46, entry] of parseNumstatEntries(lines)) {
34030
+ m.set(path46, { additions: entry.additions, deletions: entry.deletions });
34031
+ }
34032
+ return m;
34033
+ }
34034
+
34035
+ // src/git/changes/parse/numstat-from-git-no-index.ts
33631
34036
  async function numstatFromGitNoIndex(g, pathInRepo) {
33632
34037
  const devNull = process.platform === "win32" ? "NUL" : "/dev/null";
33633
34038
  try {
33634
34039
  const out = await g.raw(["diff", "--numstat", "--no-index", "--", devNull, pathInRepo]);
33635
34040
  const first2 = String(out).split("\n").find((l) => l.trim()) ?? "";
33636
- return parseNumstatFirstLine(first2);
34041
+ const parsed = parseNumstatLine(first2);
34042
+ if (!parsed) return null;
34043
+ return { additions: parsed.additions, deletions: parsed.deletions };
33637
34044
  } catch {
33638
34045
  return null;
33639
34046
  }
33640
34047
  }
33641
34048
 
33642
- // src/git/changes/lib/patch-truncate.ts
33643
- function truncatePatch(s) {
33644
- if (s.length <= MAX_PATCH_CHARS) return s;
33645
- return `${s.slice(0, MAX_PATCH_CHARS)}
34049
+ // src/git/changes/rows/collect-moved-source-paths.ts
34050
+ function collectMovedSourcePaths(nameEntries, numByPath) {
34051
+ const sources = /* @__PURE__ */ new Set();
34052
+ for (const entry of nameEntries) {
34053
+ if (entry.change === "moved" && entry.oldPathInRepo) sources.add(entry.oldPathInRepo);
34054
+ }
34055
+ for (const entry of numByPath.values()) {
34056
+ if (entry.oldPathInRepo) sources.add(entry.oldPathInRepo);
34057
+ }
34058
+ return sources;
34059
+ }
33646
34060
 
33647
- \u2026 (diff truncated)`;
34061
+ // src/git/changes/numstat/is-binary-numstat-line.ts
34062
+ function isBinaryNumstatLine(line) {
34063
+ const parts = line.split(" ");
34064
+ if (parts.length < 3) return false;
34065
+ return parts[0] === "-" || parts[1] === "-";
34066
+ }
34067
+
34068
+ // src/git/changes/listing/collect-binary-paths-from-numstat.ts
34069
+ function collectBinaryPathsFromNumstat(numstatRaw) {
34070
+ const binaryByPath = /* @__PURE__ */ new Set();
34071
+ for (const line of numstatRaw.split("\n")) {
34072
+ if (!line.trim()) continue;
34073
+ const parts = line.split(" ");
34074
+ if (parts.length < 3) continue;
34075
+ const rawPath = parts[parts.length - 1];
34076
+ const expanded = expandGitRenameNumstatPath(rawPath);
34077
+ if (isBinaryNumstatLine(line)) binaryByPath.add(expanded?.newPath ?? rawPath);
34078
+ }
34079
+ return binaryByPath;
34080
+ }
34081
+
34082
+ // src/git/changes/listing/parse-diff-output.ts
34083
+ function parseDiffOutput(nameStatusRaw, numstatRaw) {
34084
+ const nameEntries = parseNameStatusEntries(nameStatusRaw.split("\n"));
34085
+ const nameByPath = new Map(nameEntries.map((entry) => [entry.pathInRepo, entry]));
34086
+ const numByPath = parseNumstatEntries(numstatRaw.split("\n"));
34087
+ const movedSourcePaths = collectMovedSourcePaths(nameEntries, numByPath);
34088
+ const binaryByPath = collectBinaryPathsFromNumstat(numstatRaw);
34089
+ return { nameEntries, nameByPath, numByPath, movedSourcePaths, binaryByPath };
34090
+ }
34091
+ function collectChangedPaths(options) {
34092
+ const paths = /* @__PURE__ */ new Set();
34093
+ for (const entry of options.nameEntries) paths.add(entry.pathInRepo);
34094
+ for (const pathInRepo of options.numByPath.keys()) paths.add(pathInRepo);
34095
+ for (const pathInRepo of options.untracked ?? []) paths.add(pathInRepo);
34096
+ return paths;
33648
34097
  }
33649
34098
 
33650
- // src/git/commits/list-changed-files-for-commit.ts
34099
+ // src/git/changes/listing/parent-for-commit-diff.ts
33651
34100
  var EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
33652
34101
  async function parentForCommitDiff(g, sha) {
33653
34102
  try {
@@ -33660,76 +34109,112 @@ async function parentForCommitDiff(g, sha) {
33660
34109
  }
33661
34110
  }
33662
34111
  }
34112
+
34113
+ // src/git/changes/listing/rename-diff-args.ts
34114
+ var RENAME_DIFF_ARGS = ["-M", "--find-renames"];
34115
+
34116
+ // src/git/changes/rows/pick-preferred-changed-file-row.ts
34117
+ function rowRank(row) {
34118
+ if (row.change === "moved") return 3;
34119
+ if (row.change === "modified") return 2;
34120
+ if (row.change === "added") return 1;
34121
+ return 0;
34122
+ }
34123
+ function pickPreferredChangedFileRow(a, b) {
34124
+ if (rowRank(a) !== rowRank(b)) return rowRank(a) > rowRank(b) ? a : b;
34125
+ const aHasPatch = Boolean(a.patchContent?.trim());
34126
+ const bHasPatch = Boolean(b.patchContent?.trim());
34127
+ if (aHasPatch !== bHasPatch) return aHasPatch ? a : b;
34128
+ const aHasMoveMeta = Boolean(a.movedFromPathInRepo || a.movedFromPathRelLauncher);
34129
+ const bHasMoveMeta = Boolean(b.movedFromPathInRepo || b.movedFromPathRelLauncher);
34130
+ if (aHasMoveMeta !== bHasMoveMeta) return aHasMoveMeta ? a : b;
34131
+ return a;
34132
+ }
34133
+
34134
+ // src/git/changes/rows/dedupe-changed-file-rows.ts
34135
+ function dedupeChangedFileRows(rows) {
34136
+ const byPath = /* @__PURE__ */ new Map();
34137
+ for (const row of rows) {
34138
+ const key = normalizeGitDiffPath(row.pathRelLauncher);
34139
+ const normalizedRow = key === row.pathRelLauncher ? row : { ...row, pathRelLauncher: key };
34140
+ const existing = byPath.get(key);
34141
+ if (!existing) {
34142
+ byPath.set(key, normalizedRow);
34143
+ continue;
34144
+ }
34145
+ const preferRow = pickPreferredChangedFileRow(existing, normalizedRow);
34146
+ byPath.set(key, { ...preferRow, pathRelLauncher: key });
34147
+ }
34148
+ return [...byPath.values()];
34149
+ }
34150
+
34151
+ // src/git/changes/listing/list-changed-files-for-commit.ts
33663
34152
  async function listChangedFilesForCommit(repoGitCwd, repoRelPath, commitSha) {
33664
34153
  const g = cliSimpleGit(repoGitCwd);
33665
34154
  const parent = await parentForCommitDiff(g, commitSha);
33666
34155
  const range = `${parent}..${commitSha}`;
33667
34156
  const [nameStatusRaw, numstatRaw] = await Promise.all([
33668
- g.raw(["diff", "--name-status", range]).catch(() => ""),
33669
- g.raw(["diff", "--numstat", range]).catch(() => "")
34157
+ g.raw(["diff", ...RENAME_DIFF_ARGS, "--name-status", range]).catch(() => ""),
34158
+ g.raw(["diff", ...RENAME_DIFF_ARGS, "--numstat", range]).catch(() => "")
33670
34159
  ]);
33671
- const kindByPath = parseNameStatusLines(String(nameStatusRaw).split("\n"));
33672
- const numByPath = parseNumstat(String(numstatRaw).split("\n"));
33673
- const paths = new Set([...kindByPath.keys(), ...numByPath.keys()].filter(Boolean));
33674
- const rows = [];
33675
- const normRel = repoRelPath === "." || repoRelPath === "" ? "." : repoRelPath;
33676
- await forEachWithGitYield([...paths], async (pathInRepo) => {
33677
- const relLauncher = posixJoinDirFile(normRel, pathInRepo.replace(/\\/g, "/"));
33678
- const nums = numByPath.get(pathInRepo);
33679
- let additions = nums?.additions ?? 0;
33680
- let deletions = nums?.deletions ?? 0;
33681
- let change = kindByPath.get(pathInRepo) ?? "modified";
33682
- if (!kindByPath.has(pathInRepo) && nums) {
33683
- if (additions > 0 && deletions === 0) change = "added";
33684
- else if (deletions > 0 && additions === 0) change = "removed";
33685
- else change = "modified";
33686
- }
33687
- rows.push({ pathRelLauncher: relLauncher, additions, deletions, change });
33688
- });
33689
- await forEachWithGitYield(rows, async (row) => {
33690
- let pathInRepo;
33691
- if (normRel === ".") {
33692
- pathInRepo = row.pathRelLauncher;
33693
- } else if (row.pathRelLauncher.startsWith(`${normRel}/`)) {
33694
- pathInRepo = row.pathRelLauncher.slice(normRel.length + 1);
33695
- } else {
33696
- pathInRepo = row.pathRelLauncher;
33697
- }
33698
- const raw = await g.raw(["diff", "-U20000", range, "--", pathInRepo]).catch(() => "");
33699
- const t = String(raw).trim();
33700
- row.patchContent = t ? truncatePatch(t) : void 0;
34160
+ const parsed = parseDiffOutput(String(nameStatusRaw), String(numstatRaw));
34161
+ const normRel = normalizeRepoRelPath(repoRelPath);
34162
+ const paths = collectChangedPaths({
34163
+ nameEntries: parsed.nameEntries,
34164
+ numByPath: parsed.numByPath
33701
34165
  });
33702
- rows.sort((a, b) => a.pathRelLauncher.localeCompare(b.pathRelLauncher));
33703
- return rows;
33704
- }
33705
-
33706
- // src/git/changes/list-changed-files-for-repo.ts
34166
+ let rows = await buildChangedFileRowsFromPaths({
34167
+ paths,
34168
+ repoRelPath: normRel,
34169
+ nameByPath: parsed.nameByPath,
34170
+ numByPath: parsed.numByPath,
34171
+ movedSourcePaths: parsed.movedSourcePaths
34172
+ });
34173
+ rows = dedupeChangedFileRows(rows);
34174
+ await enrichCommitFileRows({
34175
+ rows,
34176
+ repoGitCwd,
34177
+ repoRelPath: normRel,
34178
+ range,
34179
+ commitSha,
34180
+ parentSha: parent,
34181
+ binaryByPath: parsed.binaryByPath
34182
+ });
34183
+ const finalRows = dedupeChangedFileRows(rows);
34184
+ finalRows.sort((a, b) => a.pathRelLauncher.localeCompare(b.pathRelLauncher));
34185
+ return finalRows;
34186
+ }
34187
+
34188
+ // src/git/changes/listing/apply-untracked-file-stats.ts
33707
34189
  import * as fs17 from "node:fs";
33708
- import * as path19 from "node:path";
33709
-
33710
- // src/git/changes/lib/count-lines.ts
33711
- import { createReadStream } from "node:fs";
33712
- import * as readline2 from "node:readline";
33713
- async function countTextFileLines(filePath) {
33714
- let bytes = 0;
33715
- const maxBytes = 512e3;
33716
- let lines = 0;
33717
- const stream = createReadStream(filePath, { encoding: "utf8" });
33718
- const rl = readline2.createInterface({ input: stream, crlfDelay: Infinity });
33719
- for await (const _line of rl) {
33720
- lines += 1;
33721
- bytes += Buffer.byteLength(String(_line), "utf8") + 1;
33722
- if (bytes > maxBytes) {
33723
- rl.close();
33724
- stream.destroy();
33725
- return lines;
34190
+ import * as path20 from "node:path";
34191
+ function createUntrackedStatsApplier(options) {
34192
+ return async (row, pathInRepo) => {
34193
+ if (!options.untrackedSet.has(pathInRepo)) return;
34194
+ if (options.nameByPath.has(pathInRepo)) return;
34195
+ if (row.change === "moved") return;
34196
+ const repoFilePath = path20.join(options.repoGitCwd, pathInRepo);
34197
+ const fromGit = await numstatFromGitNoIndex(options.g, pathInRepo);
34198
+ if (fromGit) {
34199
+ row.additions = fromGit.additions;
34200
+ row.deletions = fromGit.deletions;
34201
+ return;
33726
34202
  }
33727
- }
33728
- return lines;
34203
+ try {
34204
+ const st = await fs17.promises.stat(repoFilePath);
34205
+ row.additions = st.isFile() ? await countTextFileLines(repoFilePath) : 0;
34206
+ } catch {
34207
+ row.additions = 0;
34208
+ }
34209
+ row.deletions = 0;
34210
+ };
33729
34211
  }
33730
34212
 
33731
- // src/git/changes/hydrate-patch.ts
33732
- import * as fs16 from "node:fs";
34213
+ // src/git/changes/listing/enrich-working-tree-file-rows.ts
34214
+ import * as path21 from "node:path";
34215
+
34216
+ // src/git/changes/patch/hydrate-patch.ts
34217
+ import * as fs18 from "node:fs";
33733
34218
  var UNIFIED_HUNK_HEADER_RE = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
33734
34219
  var MAX_HYDRATE_LINES_PER_GAP = 8e3;
33735
34220
  var MAX_HYDRATE_LINES_PER_FILE = 8e4;
@@ -33744,7 +34229,7 @@ async function readGitBlobLines(repoCwd, pathInRepo) {
33744
34229
  }
33745
34230
  async function readWorktreeFileLines(filePath) {
33746
34231
  try {
33747
- const raw = await fs16.promises.readFile(filePath, "utf8");
34232
+ const raw = await fs18.promises.readFile(filePath, "utf8");
33748
34233
  return raw.split(/\r?\n/);
33749
34234
  } catch {
33750
34235
  return null;
@@ -33844,82 +34329,70 @@ async function hydrateUnifiedPatchWithFileContext(patch, filePath, repoGitCwd, p
33844
34329
  return truncatePatch(out.join("\n"));
33845
34330
  }
33846
34331
 
33847
- // src/git/changes/unified-diff-for-file.ts
33848
- function patchTextFromGitDiffOutput(raw) {
33849
- if (raw.trim() === "") return void 0;
33850
- return normalizePatchContent(raw);
33851
- }
33852
- async function unifiedDiffForFile(repoCwd, pathInRepo, change) {
33853
- const g = cliSimpleGit(repoCwd);
33854
- const args = change === "added" ? ["diff", "--no-color", "HEAD", "--", pathInRepo] : change === "removed" ? ["diff", "--no-color", "HEAD", "--", pathInRepo] : ["diff", "--no-color", "HEAD", "--", pathInRepo];
33855
- const raw = await g.raw([...args]).catch(() => "");
33856
- const patch = patchTextFromGitDiffOutput(String(raw));
33857
- return patch ? truncatePatch(patch) : void 0;
34332
+ // src/git/changes/listing/enrich-working-tree-file-rows.ts
34333
+ async function enrichWorkingTreeFileRows(options) {
34334
+ await forEachWithGitYield(options.rows, async (row) => {
34335
+ const pathInRepo = pathInRepoFromLauncher(row.pathRelLauncher, options.repoRelPath);
34336
+ const filePath = path21.join(options.repoGitCwd, pathInRepo);
34337
+ const hydrateKind = row.change === "moved" ? "modified" : row.change;
34338
+ let patch = await unifiedDiffForFile(options.repoGitCwd, pathInRepo, row.change, row.movedFromPathInRepo);
34339
+ if (patch) {
34340
+ patch = await hydrateUnifiedPatchWithFileContext(
34341
+ patch,
34342
+ filePath,
34343
+ options.repoGitCwd,
34344
+ pathInRepo,
34345
+ hydrateKind
34346
+ );
34347
+ }
34348
+ row.patchContent = patch;
34349
+ row.totalLines = await resolveWorkingTreeFileTotalLines({
34350
+ repoGitCwd: options.repoGitCwd,
34351
+ pathInRepo,
34352
+ change: row.change,
34353
+ isBinary: options.binaryByPath.has(pathInRepo)
34354
+ });
34355
+ });
33858
34356
  }
33859
34357
 
33860
- // src/git/changes/list-changed-files-for-repo.ts
34358
+ // src/git/changes/listing/list-changed-files-for-repo.ts
33861
34359
  async function listChangedFilesForRepo(repoGitCwd, repoRelPath) {
33862
34360
  const g = cliSimpleGit(repoGitCwd);
33863
34361
  const [nameStatusRaw, numstatRaw, untrackedRaw] = await Promise.all([
33864
- g.raw(["diff", "--name-status", "HEAD"]).catch(() => ""),
33865
- g.raw(["diff", "HEAD", "--numstat"]).catch(() => ""),
34362
+ g.raw(["diff", ...RENAME_DIFF_ARGS, "--name-status", "HEAD"]).catch(() => ""),
34363
+ g.raw(["diff", ...RENAME_DIFF_ARGS, "HEAD", "--numstat"]).catch(() => ""),
33866
34364
  g.raw(["ls-files", "--others", "--exclude-standard"]).catch(() => "")
33867
34365
  ]);
33868
- const kindByPath = parseNameStatusLines(String(nameStatusRaw).split("\n"));
33869
- const numByPath = parseNumstat(String(numstatRaw).split("\n"));
33870
- const paths = /* @__PURE__ */ new Set([...kindByPath.keys(), ...numByPath.keys()]);
34366
+ const parsed = parseDiffOutput(String(nameStatusRaw), String(numstatRaw));
33871
34367
  const untracked = String(untrackedRaw).split("\n").map((s) => s.trim()).filter(Boolean);
33872
- for (const p of untracked) paths.add(p);
33873
- const rows = [];
33874
- await forEachWithGitYield([...paths], async (pathInRepo) => {
33875
- const relLauncher = posixJoinDirFile(repoRelPath, pathInRepo.replace(/\\/g, "/"));
33876
- const repoFilePath = path19.join(repoGitCwd, pathInRepo);
33877
- const nums = numByPath.get(pathInRepo);
33878
- let additions = nums?.additions ?? 0;
33879
- let deletions = nums?.deletions ?? 0;
33880
- let change = kindByPath.get(pathInRepo) ?? "modified";
33881
- if (untracked.includes(pathInRepo) && !kindByPath.has(pathInRepo)) {
33882
- change = "added";
33883
- const fromGit = await numstatFromGitNoIndex(g, pathInRepo);
33884
- if (fromGit) {
33885
- additions = fromGit.additions;
33886
- deletions = fromGit.deletions;
33887
- } else {
33888
- try {
33889
- const st = await fs17.promises.stat(repoFilePath);
33890
- if (st.isFile()) additions = await countTextFileLines(repoFilePath);
33891
- else additions = 0;
33892
- } catch {
33893
- additions = 0;
33894
- }
33895
- deletions = 0;
33896
- }
33897
- }
33898
- if (!kindByPath.has(pathInRepo) && nums) {
33899
- if (additions > 0 && deletions === 0) change = "added";
33900
- else if (deletions > 0 && additions === 0) change = "removed";
33901
- else change = "modified";
33902
- }
33903
- rows.push({ pathRelLauncher: relLauncher, additions, deletions, change });
34368
+ const untrackedSet = new Set(untracked);
34369
+ const paths = collectChangedPaths({
34370
+ nameEntries: parsed.nameEntries,
34371
+ numByPath: parsed.numByPath,
34372
+ untracked
33904
34373
  });
33905
- const normRel = repoRelPath === "." || repoRelPath === "" ? "." : repoRelPath;
33906
- await forEachWithGitYield(rows, async (row) => {
33907
- let pathInRepo;
33908
- if (normRel === ".") {
33909
- pathInRepo = row.pathRelLauncher;
33910
- } else if (row.pathRelLauncher.startsWith(`${normRel}/`)) {
33911
- pathInRepo = row.pathRelLauncher.slice(normRel.length + 1);
33912
- } else {
33913
- pathInRepo = row.pathRelLauncher;
33914
- }
33915
- const filePath = path19.join(repoGitCwd, pathInRepo);
33916
- let patch = await unifiedDiffForFile(repoGitCwd, pathInRepo, row.change);
33917
- if (patch) {
33918
- patch = await hydrateUnifiedPatchWithFileContext(patch, filePath, repoGitCwd, pathInRepo, row.change);
33919
- }
33920
- row.patchContent = patch;
34374
+ let rows = await buildChangedFileRowsFromPaths({
34375
+ paths,
34376
+ repoRelPath,
34377
+ nameByPath: parsed.nameByPath,
34378
+ numByPath: parsed.numByPath,
34379
+ movedSourcePaths: parsed.movedSourcePaths,
34380
+ untrackedSet,
34381
+ applyUntrackedStats: createUntrackedStatsApplier({
34382
+ g,
34383
+ repoGitCwd,
34384
+ untrackedSet,
34385
+ nameByPath: parsed.nameByPath
34386
+ })
33921
34387
  });
33922
- return rows;
34388
+ rows = dedupeChangedFileRows(rows);
34389
+ await enrichWorkingTreeFileRows({
34390
+ rows,
34391
+ repoGitCwd,
34392
+ repoRelPath,
34393
+ binaryByPath: parsed.binaryByPath
34394
+ });
34395
+ return dedupeChangedFileRows(rows);
33923
34396
  }
33924
34397
 
33925
34398
  // src/git/changes/get-working-tree-change-repo-details.ts
@@ -33928,8 +34401,8 @@ function normRepoRel(p) {
33928
34401
  return x === "" ? "." : x;
33929
34402
  }
33930
34403
  async function getWorkingTreeChangeRepoDetails(options) {
33931
- const bridgeRoot = path20.resolve(getBridgeRoot());
33932
- const sessionWtRoot = options.sessionWorktreeRootPath ? path20.resolve(options.sessionWorktreeRootPath) : null;
34404
+ const bridgeRoot = path22.resolve(getBridgeRoot());
34405
+ const sessionWtRoot = options.sessionWorktreeRootPath ? path22.resolve(options.sessionWorktreeRootPath) : null;
33933
34406
  const legacyNested = options.legacyRepoNestedSessionLayout === true;
33934
34407
  const out = [];
33935
34408
  const filter = options.repoFilterRelPath != null ? normRepoRel(options.repoFilterRelPath) : null;
@@ -33944,7 +34417,7 @@ async function getWorkingTreeChangeRepoDetails(options) {
33944
34417
  for (let i = 0; i < options.commitTargetPaths.length; i++) {
33945
34418
  if (i > 0) await yieldToEventLoop2();
33946
34419
  const target = options.commitTargetPaths[i];
33947
- const t = path20.resolve(target);
34420
+ const t = path22.resolve(target);
33948
34421
  if (!await isGitRepoDirectory(t)) continue;
33949
34422
  const g = cliSimpleGit(t);
33950
34423
  let branch = "HEAD";
@@ -33957,8 +34430,8 @@ async function getWorkingTreeChangeRepoDetails(options) {
33957
34430
  const remoteDisplay = formatRemoteDisplayLabel(remoteUrl);
33958
34431
  let repoRelPath;
33959
34432
  if (sessionWtRoot) {
33960
- const anchor = legacyNested ? path20.dirname(t) : t;
33961
- const relNorm = path20.relative(sessionWtRoot, anchor);
34433
+ const anchor = legacyNested ? path22.dirname(t) : t;
34434
+ const relNorm = path22.relative(sessionWtRoot, anchor);
33962
34435
  repoRelPath = relNorm === "" ? "." : relNorm.replace(/\\/g, "/");
33963
34436
  } else {
33964
34437
  let top = t;
@@ -33967,8 +34440,8 @@ async function getWorkingTreeChangeRepoDetails(options) {
33967
34440
  } catch {
33968
34441
  top = t;
33969
34442
  }
33970
- const rel = path20.relative(bridgeRoot, path20.resolve(top)).replace(/\\/g, "/") || ".";
33971
- repoRelPath = rel.startsWith("..") ? path20.basename(path20.resolve(top)) : rel;
34443
+ const rel = path22.relative(bridgeRoot, path22.resolve(top)).replace(/\\/g, "/") || ".";
34444
+ repoRelPath = rel.startsWith("..") ? path22.basename(path22.resolve(top)) : rel;
33972
34445
  }
33973
34446
  const norm = normRepoRel(repoRelPath === "" ? "." : repoRelPath);
33974
34447
  if (filter && norm !== filter) continue;
@@ -34000,7 +34473,7 @@ async function getWorkingTreeChangeRepoDetails(options) {
34000
34473
  return out;
34001
34474
  }
34002
34475
 
34003
- // src/git/changes/lib/working-tree-changed-path-count.ts
34476
+ // src/git/changes/status/working-tree-changed-path-count.ts
34004
34477
  function workingTreeChangedPathCount(nameStatusLines, numstatLines, untrackedLines) {
34005
34478
  const kindByPath = parseNameStatusLines(nameStatusLines);
34006
34479
  const numByPath = parseNumstat(numstatLines);
@@ -34096,23 +34569,23 @@ async function commitSessionWorktrees(options) {
34096
34569
  }
34097
34570
 
34098
34571
  // src/worktrees/remove-session-worktrees.ts
34099
- import * as fs20 from "node:fs";
34572
+ import * as fs21 from "node:fs";
34100
34573
 
34101
34574
  // src/git/worktrees/worktree-remove.ts
34102
- import * as fs19 from "node:fs";
34575
+ import * as fs20 from "node:fs";
34103
34576
 
34104
34577
  // src/git/worktrees/resolve-main-repo-from-git-file.ts
34105
- import * as fs18 from "node:fs";
34106
- import * as path21 from "node:path";
34578
+ import * as fs19 from "node:fs";
34579
+ import * as path23 from "node:path";
34107
34580
  function resolveMainRepoFromWorktreeGitFile(wt) {
34108
- const gitDirFile = path21.join(wt, ".git");
34109
- if (!fs18.existsSync(gitDirFile) || !fs18.statSync(gitDirFile).isFile()) return "";
34110
- const first2 = fs18.readFileSync(gitDirFile, "utf8").trim();
34581
+ const gitDirFile = path23.join(wt, ".git");
34582
+ if (!fs19.existsSync(gitDirFile) || !fs19.statSync(gitDirFile).isFile()) return "";
34583
+ const first2 = fs19.readFileSync(gitDirFile, "utf8").trim();
34111
34584
  const m = first2.match(/^gitdir:\s*(.+)$/im);
34112
34585
  if (!m) return "";
34113
- const gitWorktreePath = path21.resolve(wt, m[1].trim());
34114
- const gitDir = path21.dirname(path21.dirname(gitWorktreePath));
34115
- return path21.dirname(gitDir);
34586
+ const gitWorktreePath = path23.resolve(wt, m[1].trim());
34587
+ const gitDir = path23.dirname(path23.dirname(gitWorktreePath));
34588
+ return path23.dirname(gitDir);
34116
34589
  }
34117
34590
 
34118
34591
  // src/git/worktrees/worktree-remove.ts
@@ -34121,7 +34594,7 @@ async function gitWorktreeRemoveForce(worktreePath) {
34121
34594
  if (mainRepo) {
34122
34595
  await cliSimpleGit(mainRepo).raw(["worktree", "remove", "--force", worktreePath]);
34123
34596
  } else {
34124
- fs19.rmSync(worktreePath, { recursive: true, force: true });
34597
+ fs20.rmSync(worktreePath, { recursive: true, force: true });
34125
34598
  }
34126
34599
  }
34127
34600
 
@@ -34134,7 +34607,7 @@ async function removeSessionWorktrees(paths, log2) {
34134
34607
  } catch (e) {
34135
34608
  log2(`[worktrees] Remove failed for ${wt}: ${e instanceof Error ? e.message : String(e)}`);
34136
34609
  try {
34137
- fs20.rmSync(wt, { recursive: true, force: true });
34610
+ fs21.rmSync(wt, { recursive: true, force: true });
34138
34611
  } catch {
34139
34612
  }
34140
34613
  }
@@ -34163,12 +34636,12 @@ async function renameSessionWorktreeBranches(paths, newBranch, log2) {
34163
34636
  }
34164
34637
 
34165
34638
  // src/worktrees/worktree-layout-file.ts
34166
- import * as fs21 from "node:fs";
34167
- import * as path22 from "node:path";
34639
+ import * as fs22 from "node:fs";
34640
+ import * as path24 from "node:path";
34168
34641
  import os7 from "node:os";
34169
34642
  var LAYOUT_FILENAME = "worktree-launcher-layout.json";
34170
34643
  function defaultWorktreeLayoutPath() {
34171
- return path22.join(os7.homedir(), ".buildautomaton", LAYOUT_FILENAME);
34644
+ return path24.join(os7.homedir(), ".buildautomaton", LAYOUT_FILENAME);
34172
34645
  }
34173
34646
  function normalizeLoadedLayout(raw) {
34174
34647
  if (raw && typeof raw === "object" && "launcherCwds" in raw) {
@@ -34180,8 +34653,8 @@ function normalizeLoadedLayout(raw) {
34180
34653
  function loadWorktreeLayout() {
34181
34654
  try {
34182
34655
  const p = defaultWorktreeLayoutPath();
34183
- if (!fs21.existsSync(p)) return { launcherCwds: [] };
34184
- const raw = JSON.parse(fs21.readFileSync(p, "utf8"));
34656
+ if (!fs22.existsSync(p)) return { launcherCwds: [] };
34657
+ const raw = JSON.parse(fs22.readFileSync(p, "utf8"));
34185
34658
  return normalizeLoadedLayout(raw);
34186
34659
  } catch {
34187
34660
  return { launcherCwds: [] };
@@ -34189,24 +34662,24 @@ function loadWorktreeLayout() {
34189
34662
  }
34190
34663
  function saveWorktreeLayout(layout) {
34191
34664
  try {
34192
- const dir = path22.dirname(defaultWorktreeLayoutPath());
34193
- fs21.mkdirSync(dir, { recursive: true });
34194
- fs21.writeFileSync(defaultWorktreeLayoutPath(), JSON.stringify(layout, null, 2), "utf8");
34665
+ const dir = path24.dirname(defaultWorktreeLayoutPath());
34666
+ fs22.mkdirSync(dir, { recursive: true });
34667
+ fs22.writeFileSync(defaultWorktreeLayoutPath(), JSON.stringify(layout, null, 2), "utf8");
34195
34668
  } catch {
34196
34669
  }
34197
34670
  }
34198
34671
  function baseNameSafe(pathString) {
34199
- return path22.basename(pathString).replace(/[^a-zA-Z0-9._-]+/g, "-") || "cwd";
34672
+ return path24.basename(pathString).replace(/[^a-zA-Z0-9._-]+/g, "-") || "cwd";
34200
34673
  }
34201
34674
  function getLauncherDirNameIfPresent(layout, bridgeRootPath2) {
34202
- const norm = path22.resolve(bridgeRootPath2);
34203
- const existing = layout.launcherCwds.find((e) => path22.resolve(e.absolutePath) === norm);
34675
+ const norm = path24.resolve(bridgeRootPath2);
34676
+ const existing = layout.launcherCwds.find((e) => path24.resolve(e.absolutePath) === norm);
34204
34677
  return existing?.dirName;
34205
34678
  }
34206
34679
  function allocateDirNameForLauncherCwd(layout, bridgeRootPath2) {
34207
34680
  const existing = getLauncherDirNameIfPresent(layout, bridgeRootPath2);
34208
34681
  if (existing) return existing;
34209
- const norm = path22.resolve(bridgeRootPath2);
34682
+ const norm = path24.resolve(bridgeRootPath2);
34210
34683
  const base = baseNameSafe(norm);
34211
34684
  const used = new Set(layout.launcherCwds.map((e) => e.dirName));
34212
34685
  let name = base;
@@ -34221,11 +34694,11 @@ function allocateDirNameForLauncherCwd(layout, bridgeRootPath2) {
34221
34694
  }
34222
34695
 
34223
34696
  // src/worktrees/discover-session-worktree-on-disk.ts
34224
- import * as fs22 from "node:fs";
34225
- import * as path23 from "node:path";
34697
+ import * as fs23 from "node:fs";
34698
+ import * as path25 from "node:path";
34226
34699
  function isGitDir(dirPath) {
34227
34700
  try {
34228
- return fs22.existsSync(path23.join(dirPath, ".git"));
34701
+ return fs23.existsSync(path25.join(dirPath, ".git"));
34229
34702
  } catch {
34230
34703
  return false;
34231
34704
  }
@@ -34234,23 +34707,23 @@ function collectGitRepoRootsUnderDirectory(rootPath) {
34234
34707
  const out = [];
34235
34708
  const walk = (dir) => {
34236
34709
  if (isGitDir(dir)) {
34237
- out.push(path23.resolve(dir));
34710
+ out.push(path25.resolve(dir));
34238
34711
  return;
34239
34712
  }
34240
34713
  let entries;
34241
34714
  try {
34242
- entries = fs22.readdirSync(dir, { withFileTypes: true });
34715
+ entries = fs23.readdirSync(dir, { withFileTypes: true });
34243
34716
  } catch {
34244
34717
  return;
34245
34718
  }
34246
34719
  for (const e of entries) {
34247
34720
  if (e.name.startsWith(".")) continue;
34248
- const full = path23.join(dir, e.name);
34721
+ const full = path25.join(dir, e.name);
34249
34722
  if (!e.isDirectory()) continue;
34250
34723
  walk(full);
34251
34724
  }
34252
34725
  };
34253
- walk(path23.resolve(rootPath));
34726
+ walk(path25.resolve(rootPath));
34254
34727
  return [...new Set(out)];
34255
34728
  }
34256
34729
  function collectWorktreeRootsNamed(root, sessionId, maxDepth) {
@@ -34259,16 +34732,16 @@ function collectWorktreeRootsNamed(root, sessionId, maxDepth) {
34259
34732
  if (depth > maxDepth) return;
34260
34733
  let entries;
34261
34734
  try {
34262
- entries = fs22.readdirSync(dir, { withFileTypes: true });
34735
+ entries = fs23.readdirSync(dir, { withFileTypes: true });
34263
34736
  } catch {
34264
34737
  return;
34265
34738
  }
34266
34739
  for (const e of entries) {
34267
34740
  if (e.name.startsWith(".")) continue;
34268
- const full = path23.join(dir, e.name);
34741
+ const full = path25.join(dir, e.name);
34269
34742
  if (!e.isDirectory()) continue;
34270
34743
  if (e.name === sessionId) {
34271
- if (isGitDir(full)) out.push(path23.resolve(full));
34744
+ if (isGitDir(full)) out.push(path25.resolve(full));
34272
34745
  } else {
34273
34746
  walk(full, depth + 1);
34274
34747
  }
@@ -34280,14 +34753,14 @@ function collectWorktreeRootsNamed(root, sessionId, maxDepth) {
34280
34753
  function tryBindingFromSessionDirectory(sessionDir) {
34281
34754
  let st;
34282
34755
  try {
34283
- st = fs22.statSync(sessionDir);
34756
+ st = fs23.statSync(sessionDir);
34284
34757
  } catch {
34285
34758
  return null;
34286
34759
  }
34287
34760
  if (!st.isDirectory()) return null;
34288
34761
  const worktreePaths = collectGitRepoRootsUnderDirectory(sessionDir);
34289
34762
  if (worktreePaths.length === 0) return null;
34290
- const abs = path23.resolve(sessionDir);
34763
+ const abs = path25.resolve(sessionDir);
34291
34764
  return {
34292
34765
  sessionParentPath: abs,
34293
34766
  workingTreeRelRoot: abs,
@@ -34297,20 +34770,20 @@ function tryBindingFromSessionDirectory(sessionDir) {
34297
34770
  function discoverLegacyBindingAscendingFromCheckout(sessionId, checkoutPath) {
34298
34771
  const sid = sessionId.trim();
34299
34772
  if (!sid) return null;
34300
- const hintR = path23.resolve(checkoutPath);
34773
+ const hintR = path25.resolve(checkoutPath);
34301
34774
  let best = null;
34302
- let cur = path23.dirname(hintR);
34775
+ let cur = path25.dirname(hintR);
34303
34776
  for (let i = 0; i < 40; i++) {
34304
34777
  const paths = collectWorktreeRootsNamed(cur, sid, 24);
34305
- if (paths.some((p) => path23.resolve(p) === hintR)) {
34306
- const isolated = resolveIsolatedSessionParentPathFromCheckouts(paths) ?? path23.resolve(paths[0]);
34778
+ if (paths.some((p) => path25.resolve(p) === hintR)) {
34779
+ const isolated = resolveIsolatedSessionParentPathFromCheckouts(paths) ?? path25.resolve(paths[0]);
34307
34780
  best = {
34308
- sessionParentPath: path23.resolve(isolated),
34309
- workingTreeRelRoot: path23.resolve(cur),
34310
- repoCheckoutPaths: paths.map((p) => path23.resolve(p))
34781
+ sessionParentPath: path25.resolve(isolated),
34782
+ workingTreeRelRoot: path25.resolve(cur),
34783
+ repoCheckoutPaths: paths.map((p) => path25.resolve(p))
34311
34784
  };
34312
34785
  }
34313
- const next = path23.dirname(cur);
34786
+ const next = path25.dirname(cur);
34314
34787
  if (next === cur) break;
34315
34788
  cur = next;
34316
34789
  }
@@ -34318,33 +34791,33 @@ function discoverLegacyBindingAscendingFromCheckout(sessionId, checkoutPath) {
34318
34791
  }
34319
34792
  function discoverSessionWorktreeOnDisk(options) {
34320
34793
  const { sessionId, worktreesRootPath, layout, bridgeRoot } = options;
34321
- if (!sessionId.trim() || !fs22.existsSync(worktreesRootPath)) return null;
34794
+ if (!sessionId.trim() || !fs23.existsSync(worktreesRootPath)) return null;
34322
34795
  const preferredKey = getLauncherDirNameIfPresent(layout, bridgeRoot);
34323
34796
  const keys = [];
34324
34797
  if (preferredKey) keys.push(preferredKey);
34325
34798
  try {
34326
- for (const name of fs22.readdirSync(worktreesRootPath)) {
34799
+ for (const name of fs23.readdirSync(worktreesRootPath)) {
34327
34800
  if (name.startsWith(".")) continue;
34328
- const p = path23.join(worktreesRootPath, name);
34329
- if (!fs22.statSync(p).isDirectory()) continue;
34801
+ const p = path25.join(worktreesRootPath, name);
34802
+ if (!fs23.statSync(p).isDirectory()) continue;
34330
34803
  if (name !== preferredKey) keys.push(name);
34331
34804
  }
34332
34805
  } catch {
34333
34806
  return null;
34334
34807
  }
34335
34808
  for (const key of keys) {
34336
- const layoutRoot = path23.join(worktreesRootPath, key);
34337
- if (!fs22.existsSync(layoutRoot) || !fs22.statSync(layoutRoot).isDirectory()) continue;
34338
- const sessionDir = path23.join(layoutRoot, sessionId);
34809
+ const layoutRoot = path25.join(worktreesRootPath, key);
34810
+ if (!fs23.existsSync(layoutRoot) || !fs23.statSync(layoutRoot).isDirectory()) continue;
34811
+ const sessionDir = path25.join(layoutRoot, sessionId);
34339
34812
  const nested = tryBindingFromSessionDirectory(sessionDir);
34340
34813
  if (nested) return nested;
34341
34814
  const legacyPaths = collectWorktreeRootsNamed(layoutRoot, sessionId, 24);
34342
34815
  if (legacyPaths.length > 0) {
34343
- const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths) ?? path23.resolve(legacyPaths[0]);
34816
+ const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths) ?? path25.resolve(legacyPaths[0]);
34344
34817
  return {
34345
- sessionParentPath: path23.resolve(isolated),
34346
- workingTreeRelRoot: path23.resolve(layoutRoot),
34347
- repoCheckoutPaths: legacyPaths.map((p) => path23.resolve(p))
34818
+ sessionParentPath: path25.resolve(isolated),
34819
+ workingTreeRelRoot: path25.resolve(layoutRoot),
34820
+ repoCheckoutPaths: legacyPaths.map((p) => path25.resolve(p))
34348
34821
  };
34349
34822
  }
34350
34823
  }
@@ -34353,12 +34826,12 @@ function discoverSessionWorktreeOnDisk(options) {
34353
34826
  function discoverSessionWorktreesUnderSessionWorktreeRoot(sessionWorktreeRootPathOrHint, sessionId) {
34354
34827
  const sid = sessionId.trim();
34355
34828
  if (!sid) return null;
34356
- const hint = path23.resolve(sessionWorktreeRootPathOrHint);
34357
- const underHint = tryBindingFromSessionDirectory(path23.join(hint, sid));
34829
+ const hint = path25.resolve(sessionWorktreeRootPathOrHint);
34830
+ const underHint = tryBindingFromSessionDirectory(path25.join(hint, sid));
34358
34831
  if (underHint) return underHint;
34359
34832
  const direct = tryBindingFromSessionDirectory(hint);
34360
34833
  if (direct) {
34361
- if (path23.basename(hint) === sid && isGitDir(hint)) {
34834
+ if (path25.basename(hint) === sid && isGitDir(hint)) {
34362
34835
  const legacyFromCheckout = discoverLegacyBindingAscendingFromCheckout(sid, hint);
34363
34836
  if (legacyFromCheckout && legacyFromCheckout.repoCheckoutPaths.length > direct.repoCheckoutPaths.length) {
34364
34837
  return legacyFromCheckout;
@@ -34366,24 +34839,24 @@ function discoverSessionWorktreesUnderSessionWorktreeRoot(sessionWorktreeRootPat
34366
34839
  }
34367
34840
  return direct;
34368
34841
  }
34369
- if (path23.basename(hint) === sid && isGitDir(hint)) {
34842
+ if (path25.basename(hint) === sid && isGitDir(hint)) {
34370
34843
  const legacyFromCheckout = discoverLegacyBindingAscendingFromCheckout(sid, hint);
34371
34844
  if (legacyFromCheckout) return legacyFromCheckout;
34372
34845
  }
34373
34846
  let st;
34374
34847
  try {
34375
- st = fs22.statSync(hint);
34848
+ st = fs23.statSync(hint);
34376
34849
  } catch {
34377
34850
  return null;
34378
34851
  }
34379
34852
  if (!st.isDirectory()) return null;
34380
34853
  const legacyPaths = collectWorktreeRootsNamed(hint, sid, 24);
34381
34854
  if (legacyPaths.length === 0) return null;
34382
- const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths) ?? path23.resolve(legacyPaths[0]);
34855
+ const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths) ?? path25.resolve(legacyPaths[0]);
34383
34856
  return {
34384
- sessionParentPath: path23.resolve(isolated),
34857
+ sessionParentPath: path25.resolve(isolated),
34385
34858
  workingTreeRelRoot: hint,
34386
- repoCheckoutPaths: legacyPaths.map((p) => path23.resolve(p))
34859
+ repoCheckoutPaths: legacyPaths.map((p) => path25.resolve(p))
34387
34860
  };
34388
34861
  }
34389
34862
 
@@ -34440,8 +34913,8 @@ function parseSessionParent(v) {
34440
34913
  }
34441
34914
 
34442
34915
  // src/worktrees/prepare-new-session-worktrees.ts
34443
- import * as fs23 from "node:fs";
34444
- import * as path24 from "node:path";
34916
+ import * as fs24 from "node:fs";
34917
+ import * as path26 from "node:path";
34445
34918
 
34446
34919
  // src/git/worktrees/worktree-add.ts
34447
34920
  async function gitWorktreeAddBranch(mainRepoPath, worktreePath, branch, baseRef = "HEAD") {
@@ -34451,7 +34924,7 @@ async function gitWorktreeAddBranch(mainRepoPath, worktreePath, branch, baseRef
34451
34924
  }
34452
34925
 
34453
34926
  // src/worktrees/prepare-new-session-worktrees.ts
34454
- function normalizeRepoRelPath(rel) {
34927
+ function normalizeRepoRelPath2(rel) {
34455
34928
  return rel === "" ? "." : rel.replace(/\\/g, "/");
34456
34929
  }
34457
34930
  function resolveBaseRefForRepo(relNorm, baseBranches) {
@@ -34463,10 +34936,10 @@ function resolveBaseRefForRepo(relNorm, baseBranches) {
34463
34936
  }
34464
34937
  async function prepareNewSessionWorktrees(options) {
34465
34938
  const { worktreesRootPath, bridgeRoot, sessionId, layout, log: log2, worktreeBaseBranches } = options;
34466
- const bridgeResolved = path24.resolve(bridgeRoot);
34939
+ const bridgeResolved = path26.resolve(bridgeRoot);
34467
34940
  const cwdKey = allocateDirNameForLauncherCwd(layout, bridgeResolved);
34468
- const bridgeKeyDir = path24.join(worktreesRootPath, cwdKey);
34469
- const sessionDir = path24.join(bridgeKeyDir, sessionId);
34941
+ const bridgeKeyDir = path26.join(worktreesRootPath, cwdKey);
34942
+ const sessionDir = path26.join(bridgeKeyDir, sessionId);
34470
34943
  const repos = await discoverGitReposUnderRoot(bridgeResolved);
34471
34944
  if (repos.length === 0) {
34472
34945
  log2("[worktrees] No Git repositories under bridge root; skipping worktree creation.");
@@ -34474,14 +34947,14 @@ async function prepareNewSessionWorktrees(options) {
34474
34947
  }
34475
34948
  const branch = `session-${sessionId}`;
34476
34949
  const worktreePaths = [];
34477
- fs23.mkdirSync(sessionDir, { recursive: true });
34950
+ fs24.mkdirSync(sessionDir, { recursive: true });
34478
34951
  for (const repo of repos) {
34479
- let rel = path24.relative(bridgeResolved, repo.absolutePath);
34480
- if (rel.startsWith("..") || path24.isAbsolute(rel)) continue;
34481
- const relNorm = normalizeRepoRelPath(rel === "" ? "." : rel);
34482
- const wtPath = relNorm === "." ? sessionDir : path24.join(sessionDir, relNorm);
34952
+ let rel = path26.relative(bridgeResolved, repo.absolutePath);
34953
+ if (rel.startsWith("..") || path26.isAbsolute(rel)) continue;
34954
+ const relNorm = normalizeRepoRelPath2(rel === "" ? "." : rel);
34955
+ const wtPath = relNorm === "." ? sessionDir : path26.join(sessionDir, relNorm);
34483
34956
  if (relNorm !== ".") {
34484
- fs23.mkdirSync(path24.dirname(wtPath), { recursive: true });
34957
+ fs24.mkdirSync(path26.dirname(wtPath), { recursive: true });
34485
34958
  }
34486
34959
  const baseRef = resolveBaseRefForRepo(relNorm, worktreeBaseBranches);
34487
34960
  try {
@@ -34534,9 +35007,9 @@ function resolveExistingSessionParentPath(sessionId, cache2, discover) {
34534
35007
  }
34535
35008
 
34536
35009
  // src/worktrees/manager/resolve-explicit-session-parent-path.ts
34537
- import * as path25 from "node:path";
35010
+ import * as path27 from "node:path";
34538
35011
  function resolveExplicitSessionParentPath(params) {
34539
- const resolved = path25.resolve(params.parentPathRaw);
35012
+ const resolved = path27.resolve(params.parentPathRaw);
34540
35013
  if (parseSessionParent(params.sessionParent) !== "worktrees_root") {
34541
35014
  return resolved;
34542
35015
  }
@@ -34552,7 +35025,7 @@ function resolveExplicitSessionParentPath(params) {
34552
35025
  for (let i = 0; i < 16; i++) {
34553
35026
  const tryRoot = discoverSessionWorktreesUnderSessionWorktreeRoot(cur, params.sessionId);
34554
35027
  if (tryRoot) return rememberAndReturn(tryRoot);
34555
- const next = path25.dirname(cur);
35028
+ const next = path27.dirname(cur);
34556
35029
  if (next === cur) break;
34557
35030
  cur = next;
34558
35031
  }
@@ -34604,16 +35077,16 @@ async function resolveSessionParentPathForPrompt(params) {
34604
35077
  }
34605
35078
 
34606
35079
  // src/worktrees/manager/session-worktree-cache.ts
34607
- import * as path26 from "node:path";
35080
+ import * as path28 from "node:path";
34608
35081
  var SessionWorktreeCache = class {
34609
35082
  sessionRepoCheckoutPaths = /* @__PURE__ */ new Map();
34610
35083
  sessionParentPathBySession = /* @__PURE__ */ new Map();
34611
35084
  sessionWorkingTreeRelRootBySession = /* @__PURE__ */ new Map();
34612
35085
  remember(sessionId, binding) {
34613
- const paths = binding.repoCheckoutPaths.map((p) => path26.resolve(p));
35086
+ const paths = binding.repoCheckoutPaths.map((p) => path28.resolve(p));
34614
35087
  this.sessionRepoCheckoutPaths.set(sessionId, paths);
34615
- this.sessionParentPathBySession.set(sessionId, path26.resolve(binding.sessionParentPath));
34616
- this.sessionWorkingTreeRelRootBySession.set(sessionId, path26.resolve(binding.workingTreeRelRoot));
35088
+ this.sessionParentPathBySession.set(sessionId, path28.resolve(binding.sessionParentPath));
35089
+ this.sessionWorkingTreeRelRootBySession.set(sessionId, path28.resolve(binding.workingTreeRelRoot));
34617
35090
  }
34618
35091
  clearSession(sessionId) {
34619
35092
  const paths = this.sessionRepoCheckoutPaths.get(sessionId);
@@ -34643,7 +35116,7 @@ var SessionWorktreeCache = class {
34643
35116
  const parent = this.sessionParentPathBySession.get(sessionId);
34644
35117
  const relRoot = this.sessionWorkingTreeRelRootBySession.get(sessionId);
34645
35118
  if (!parent || !relRoot) return false;
34646
- return path26.resolve(parent) !== path26.resolve(relRoot);
35119
+ return path28.resolve(parent) !== path28.resolve(relRoot);
34647
35120
  }
34648
35121
  };
34649
35122
 
@@ -34747,29 +35220,29 @@ var SessionWorktreeManager = class {
34747
35220
  };
34748
35221
 
34749
35222
  // src/worktrees/manager/default-worktrees-root-path.ts
34750
- import * as path27 from "node:path";
35223
+ import * as path29 from "node:path";
34751
35224
  import os8 from "node:os";
34752
35225
  function defaultWorktreesRootPath() {
34753
- return path27.join(os8.homedir(), ".buildautomaton", "worktrees");
35226
+ return path29.join(os8.homedir(), ".buildautomaton", "worktrees");
34754
35227
  }
34755
35228
 
34756
35229
  // src/files/watch-file-index.ts
34757
35230
  import { watch } from "node:fs";
34758
- import path32 from "node:path";
35231
+ import path34 from "node:path";
34759
35232
 
34760
35233
  // src/files/index/paths.ts
34761
- import path28 from "node:path";
35234
+ import path30 from "node:path";
34762
35235
  import crypto2 from "node:crypto";
34763
35236
  function getCwdHashForFileIndex(resolvedCwd) {
34764
- return crypto2.createHash("sha256").update(path28.resolve(resolvedCwd)).digest("hex").slice(0, INDEX_HASH_LEN);
35237
+ return crypto2.createHash("sha256").update(path30.resolve(resolvedCwd)).digest("hex").slice(0, INDEX_HASH_LEN);
34765
35238
  }
34766
35239
 
34767
35240
  // src/files/index/build-file-index.ts
34768
- import path30 from "node:path";
35241
+ import path32 from "node:path";
34769
35242
 
34770
35243
  // src/files/index/walk-workspace-tree.ts
34771
- import fs24 from "node:fs";
34772
- import path29 from "node:path";
35244
+ import fs25 from "node:fs";
35245
+ import path31 from "node:path";
34773
35246
  var DEPENDENCY_INSTALL_DIR_NAMES = /* @__PURE__ */ new Set([
34774
35247
  "node_modules",
34775
35248
  "bower_components",
@@ -34786,7 +35259,7 @@ function shouldSkipWorkspaceWalkEntry(name) {
34786
35259
  async function walkWorkspaceTreeAsync(dir, baseDir, onFile, state) {
34787
35260
  let names;
34788
35261
  try {
34789
- names = await fs24.promises.readdir(dir);
35262
+ names = await fs25.promises.readdir(dir);
34790
35263
  } catch {
34791
35264
  return;
34792
35265
  }
@@ -34798,14 +35271,14 @@ async function walkWorkspaceTreeAsync(dir, baseDir, onFile, state) {
34798
35271
  if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
34799
35272
  }
34800
35273
  state.n++;
34801
- const full = path29.join(dir, name);
35274
+ const full = path31.join(dir, name);
34802
35275
  let stat2;
34803
35276
  try {
34804
- stat2 = await fs24.promises.stat(full);
35277
+ stat2 = await fs25.promises.stat(full);
34805
35278
  } catch {
34806
35279
  continue;
34807
35280
  }
34808
- const relative6 = path29.relative(baseDir, full).replace(/\\/g, "/");
35281
+ const relative6 = path31.relative(baseDir, full).replace(/\\/g, "/");
34809
35282
  if (stat2.isDirectory()) {
34810
35283
  await walkWorkspaceTreeAsync(full, baseDir, onFile, state);
34811
35284
  } else if (stat2.isFile()) {
@@ -34818,7 +35291,7 @@ function createWalkYieldState() {
34818
35291
  }
34819
35292
 
34820
35293
  // src/files/index/file-index-sqlite-lock.ts
34821
- import fs25 from "node:fs";
35294
+ import fs26 from "node:fs";
34822
35295
  function isSqliteCorruptError(e) {
34823
35296
  const msg = e instanceof Error ? e.message : String(e);
34824
35297
  return msg.includes("malformed") || msg.includes("database disk image is malformed") || msg.includes("corrupt");
@@ -34832,7 +35305,7 @@ function withFileIndexSqliteLock(fn) {
34832
35305
  if (!isSqliteCorruptError(e)) throw e;
34833
35306
  closeAllCliSqliteConnections();
34834
35307
  try {
34835
- fs25.unlinkSync(getCliSqlitePath());
35308
+ fs26.unlinkSync(getCliSqlitePath());
34836
35309
  } catch {
34837
35310
  }
34838
35311
  chain = Promise.resolve();
@@ -34907,7 +35380,7 @@ async function collectWorkspacePathsAsync(resolved) {
34907
35380
  }
34908
35381
  async function buildFileIndexAsync(cwd) {
34909
35382
  return withFileIndexSqliteLock(async () => {
34910
- const resolved = path30.resolve(cwd);
35383
+ const resolved = path32.resolve(cwd);
34911
35384
  await yieldToEventLoop();
34912
35385
  assertNotShutdown();
34913
35386
  const paths = await collectWorkspacePathsAsync(resolved);
@@ -34919,7 +35392,7 @@ async function buildFileIndexAsync(cwd) {
34919
35392
  }
34920
35393
 
34921
35394
  // src/files/index/ensure-file-index.ts
34922
- import path31 from "node:path";
35395
+ import path33 from "node:path";
34923
35396
 
34924
35397
  // src/files/index/search-file-index.ts
34925
35398
  function escapeLikePattern(fragment) {
@@ -34971,7 +35444,7 @@ async function searchBridgeFilePathsAsync(resolvedCwd, query, limit = 100) {
34971
35444
 
34972
35445
  // src/files/index/ensure-file-index.ts
34973
35446
  async function ensureFileIndexAsync(cwd) {
34974
- const resolved = path31.resolve(cwd);
35447
+ const resolved = path33.resolve(cwd);
34975
35448
  if (await bridgeFileIndexIsPopulated(resolved)) {
34976
35449
  return { fromCache: true, pathCount: await bridgeFileIndexPathCount(resolved) };
34977
35450
  }
@@ -35015,7 +35488,7 @@ function createFsWatcher(resolved, schedule) {
35015
35488
  }
35016
35489
  }
35017
35490
  function startFileIndexWatcher(cwd = getBridgeRoot()) {
35018
- const resolved = path32.resolve(cwd);
35491
+ const resolved = path34.resolve(cwd);
35019
35492
  void buildFileIndexAsync(resolved).catch((e) => {
35020
35493
  if (e instanceof CliSqliteInterrupted) return;
35021
35494
  console.error("[file-index] Initial index build failed:", e);
@@ -35045,7 +35518,7 @@ function startFileIndexWatcher(cwd = getBridgeRoot()) {
35045
35518
  }
35046
35519
 
35047
35520
  // src/connection/create-bridge-connection.ts
35048
- import * as path43 from "node:path";
35521
+ import * as path45 from "node:path";
35049
35522
 
35050
35523
  // src/dev-servers/manager/dev-server-manager.ts
35051
35524
  import { rm as rm2 } from "node:fs/promises";
@@ -35089,7 +35562,7 @@ function forceKillChild(proc, log2, shortId, graceMs) {
35089
35562
  }
35090
35563
 
35091
35564
  // src/dev-servers/process/wire-dev-server-child-process.ts
35092
- import fs26 from "node:fs";
35565
+ import fs27 from "node:fs";
35093
35566
 
35094
35567
  // src/dev-servers/manager/forward-pipe.ts
35095
35568
  function forwardChildPipe(childReadable, terminal, onData) {
@@ -35125,7 +35598,7 @@ function wireDevServerChildProcess(d) {
35125
35598
  d.setPollInterval(void 0);
35126
35599
  return;
35127
35600
  }
35128
- fs26.readFile(d.mergedLogPath, (err, buf) => {
35601
+ fs27.readFile(d.mergedLogPath, (err, buf) => {
35129
35602
  if (err || (d.getSpawnGeneration() ?? 0) !== d.scheduledGen) return;
35130
35603
  if (buf.length <= d.mergedReadPos.value) return;
35131
35604
  const chunk = Buffer.from(buf.subarray(d.mergedReadPos.value));
@@ -35163,7 +35636,7 @@ ${errTail}` : ""}`);
35163
35636
  d.sendStatus(code === 0 || code == null ? "stopped" : "error", detail, tails);
35164
35637
  };
35165
35638
  if (mergedPath) {
35166
- fs26.readFile(mergedPath, (err, buf) => {
35639
+ fs27.readFile(mergedPath, (err, buf) => {
35167
35640
  if (!err && buf.length > d.mergedReadPos.value) {
35168
35641
  const chunk = Buffer.from(buf.subarray(d.mergedReadPos.value));
35169
35642
  if (chunk.length > 0) {
@@ -35265,13 +35738,13 @@ function parseDevServerDefs(servers) {
35265
35738
  }
35266
35739
 
35267
35740
  // src/dev-servers/manager/shell-spawn/utils.ts
35268
- import fs27 from "node:fs";
35741
+ import fs28 from "node:fs";
35269
35742
  function isSpawnEbadf(e) {
35270
35743
  return typeof e === "object" && e !== null && "code" in e && e.code === "EBADF";
35271
35744
  }
35272
35745
  function rmDirQuiet(dir) {
35273
35746
  try {
35274
- fs27.rmSync(dir, { recursive: true, force: true });
35747
+ fs28.rmSync(dir, { recursive: true, force: true });
35275
35748
  } catch {
35276
35749
  }
35277
35750
  }
@@ -35279,7 +35752,7 @@ var cachedDevNullReadFd;
35279
35752
  function devNullReadFd() {
35280
35753
  if (cachedDevNullReadFd === void 0) {
35281
35754
  const devPath = process.platform === "win32" ? "nul" : "/dev/null";
35282
- cachedDevNullReadFd = fs27.openSync(devPath, "r");
35755
+ cachedDevNullReadFd = fs28.openSync(devPath, "r");
35283
35756
  }
35284
35757
  return cachedDevNullReadFd;
35285
35758
  }
@@ -35353,15 +35826,15 @@ function trySpawnShellTruePiped(command, env, cwd, devNullFd, signal) {
35353
35826
 
35354
35827
  // src/dev-servers/manager/shell-spawn/try-spawn-merged-log-file.ts
35355
35828
  import { spawn as spawn8 } from "node:child_process";
35356
- import fs28 from "node:fs";
35829
+ import fs29 from "node:fs";
35357
35830
  import { tmpdir } from "node:os";
35358
- import path33 from "node:path";
35831
+ import path35 from "node:path";
35359
35832
  function trySpawnMergedLogFile(command, env, cwd, signal) {
35360
- const tmpRoot = fs28.mkdtempSync(path33.join(tmpdir(), "ba-devsrv-log-"));
35361
- const logPath = path33.join(tmpRoot, "combined.log");
35833
+ const tmpRoot = fs29.mkdtempSync(path35.join(tmpdir(), "ba-devsrv-log-"));
35834
+ const logPath = path35.join(tmpRoot, "combined.log");
35362
35835
  let logFd;
35363
35836
  try {
35364
- logFd = fs28.openSync(logPath, "a");
35837
+ logFd = fs29.openSync(logPath, "a");
35365
35838
  } catch {
35366
35839
  rmDirQuiet(tmpRoot);
35367
35840
  return null;
@@ -35380,7 +35853,7 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
35380
35853
  } else {
35381
35854
  proc = spawn8("/bin/sh", ["-c", command], { env, cwd, stdio, ...signal ? { signal } : {} });
35382
35855
  }
35383
- fs28.closeSync(logFd);
35856
+ fs29.closeSync(logFd);
35384
35857
  return {
35385
35858
  proc,
35386
35859
  pipedStdoutStderr: true,
@@ -35389,7 +35862,7 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
35389
35862
  };
35390
35863
  } catch (e) {
35391
35864
  try {
35392
- fs28.closeSync(logFd);
35865
+ fs29.closeSync(logFd);
35393
35866
  } catch {
35394
35867
  }
35395
35868
  rmDirQuiet(tmpRoot);
@@ -35400,22 +35873,22 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
35400
35873
 
35401
35874
  // src/dev-servers/manager/shell-spawn/try-spawn-shell-script-log-redirect.ts
35402
35875
  import { spawn as spawn9 } from "node:child_process";
35403
- import fs29 from "node:fs";
35876
+ import fs30 from "node:fs";
35404
35877
  import { tmpdir as tmpdir2 } from "node:os";
35405
- import path34 from "node:path";
35878
+ import path36 from "node:path";
35406
35879
  function shSingleQuote(s) {
35407
35880
  return `'${s.replace(/'/g, `'\\''`)}'`;
35408
35881
  }
35409
35882
  function trySpawnShellScriptLogRedirectUnix(command, env, cwd, signal) {
35410
- const tmpRoot = fs29.mkdtempSync(path34.join(tmpdir2(), "ba-devsrv-sh-"));
35411
- const logPath = path34.join(tmpRoot, "combined.log");
35412
- const innerPath = path34.join(tmpRoot, "_cmd.sh");
35413
- const runnerPath = path34.join(tmpRoot, "_run.sh");
35883
+ const tmpRoot = fs30.mkdtempSync(path36.join(tmpdir2(), "ba-devsrv-sh-"));
35884
+ const logPath = path36.join(tmpRoot, "combined.log");
35885
+ const innerPath = path36.join(tmpRoot, "_cmd.sh");
35886
+ const runnerPath = path36.join(tmpRoot, "_run.sh");
35414
35887
  try {
35415
- fs29.writeFileSync(innerPath, `#!/bin/sh
35888
+ fs30.writeFileSync(innerPath, `#!/bin/sh
35416
35889
  ${command}
35417
35890
  `);
35418
- fs29.writeFileSync(
35891
+ fs30.writeFileSync(
35419
35892
  runnerPath,
35420
35893
  `#!/bin/sh
35421
35894
  cd ${shSingleQuote(cwd)}
@@ -35441,13 +35914,13 @@ cd ${shSingleQuote(cwd)}
35441
35914
  }
35442
35915
  }
35443
35916
  function trySpawnShellScriptLogRedirectWin(command, env, cwd, signal) {
35444
- const tmpRoot = fs29.mkdtempSync(path34.join(tmpdir2(), "ba-devsrv-sh-"));
35445
- const logPath = path34.join(tmpRoot, "combined.log");
35446
- const runnerPath = path34.join(tmpRoot, "_run.bat");
35917
+ const tmpRoot = fs30.mkdtempSync(path36.join(tmpdir2(), "ba-devsrv-sh-"));
35918
+ const logPath = path36.join(tmpRoot, "combined.log");
35919
+ const runnerPath = path36.join(tmpRoot, "_run.bat");
35447
35920
  const q = (p) => `"${p.replace(/"/g, '""')}"`;
35448
35921
  const com = process.env.ComSpec || "cmd.exe";
35449
35922
  try {
35450
- fs29.writeFileSync(
35923
+ fs30.writeFileSync(
35451
35924
  runnerPath,
35452
35925
  `@ECHO OFF\r
35453
35926
  CD /D ${q(cwd)}\r
@@ -36216,30 +36689,30 @@ function createOnBridgeIdentified(opts) {
36216
36689
  }
36217
36690
 
36218
36691
  // src/skills/discover-local-agent-skills.ts
36219
- import fs30 from "node:fs";
36220
- import path35 from "node:path";
36692
+ import fs31 from "node:fs";
36693
+ import path37 from "node:path";
36221
36694
  var SKILL_DISCOVERY_ROOTS = [".agents/skills", ".claude/skills", ".cursor/skills", "skills"];
36222
36695
  function discoverLocalSkills(cwd) {
36223
36696
  const out = [];
36224
36697
  const seenKeys = /* @__PURE__ */ new Set();
36225
36698
  for (const rel of SKILL_DISCOVERY_ROOTS) {
36226
- const base = path35.join(cwd, rel);
36227
- if (!fs30.existsSync(base) || !fs30.statSync(base).isDirectory()) continue;
36699
+ const base = path37.join(cwd, rel);
36700
+ if (!fs31.existsSync(base) || !fs31.statSync(base).isDirectory()) continue;
36228
36701
  let entries = [];
36229
36702
  try {
36230
- entries = fs30.readdirSync(base);
36703
+ entries = fs31.readdirSync(base);
36231
36704
  } catch {
36232
36705
  continue;
36233
36706
  }
36234
36707
  for (const name of entries) {
36235
- const dir = path35.join(base, name);
36708
+ const dir = path37.join(base, name);
36236
36709
  try {
36237
- if (!fs30.statSync(dir).isDirectory()) continue;
36710
+ if (!fs31.statSync(dir).isDirectory()) continue;
36238
36711
  } catch {
36239
36712
  continue;
36240
36713
  }
36241
- const skillMd = path35.join(dir, "SKILL.md");
36242
- if (!fs30.existsSync(skillMd)) continue;
36714
+ const skillMd = path37.join(dir, "SKILL.md");
36715
+ if (!fs31.existsSync(skillMd)) continue;
36243
36716
  const key = `${rel}/${name}`;
36244
36717
  if (seenKeys.has(key)) continue;
36245
36718
  seenKeys.add(key);
@@ -36251,23 +36724,23 @@ function discoverLocalSkills(cwd) {
36251
36724
  function discoverSkillLayoutRoots(cwd) {
36252
36725
  const roots = [];
36253
36726
  for (const rel of SKILL_DISCOVERY_ROOTS) {
36254
- const base = path35.join(cwd, rel);
36255
- if (!fs30.existsSync(base) || !fs30.statSync(base).isDirectory()) continue;
36727
+ const base = path37.join(cwd, rel);
36728
+ if (!fs31.existsSync(base) || !fs31.statSync(base).isDirectory()) continue;
36256
36729
  let entries = [];
36257
36730
  try {
36258
- entries = fs30.readdirSync(base);
36731
+ entries = fs31.readdirSync(base);
36259
36732
  } catch {
36260
36733
  continue;
36261
36734
  }
36262
36735
  const skills2 = [];
36263
36736
  for (const name of entries) {
36264
- const dir = path35.join(base, name);
36737
+ const dir = path37.join(base, name);
36265
36738
  try {
36266
- if (!fs30.statSync(dir).isDirectory()) continue;
36739
+ if (!fs31.statSync(dir).isDirectory()) continue;
36267
36740
  } catch {
36268
36741
  continue;
36269
36742
  }
36270
- if (!fs30.existsSync(path35.join(dir, "SKILL.md"))) continue;
36743
+ if (!fs31.existsSync(path37.join(dir, "SKILL.md"))) continue;
36271
36744
  const relPath = `${rel}/${name}`.replace(/\\/g, "/");
36272
36745
  skills2.push({ name, relPath });
36273
36746
  }
@@ -36623,7 +37096,7 @@ function syncRunningTurnQueueKeys(turns, queueKey) {
36623
37096
  }
36624
37097
 
36625
37098
  // src/prompt-turn-queue/runner/run-local-revert-before-queued-prompt.ts
36626
- import fs31 from "node:fs";
37099
+ import fs32 from "node:fs";
36627
37100
  async function runLocalRevertBeforeQueuedPrompt(next, deps) {
36628
37101
  if (next.serverState !== "requeued_with_revert") return true;
36629
37102
  const sid = next.sessionId;
@@ -36632,7 +37105,7 @@ async function runLocalRevertBeforeQueuedPrompt(next, deps) {
36632
37105
  const agentBase = deps.sessionWorktreeManager.getSessionWorktreeRootForSession(sid) ?? getBridgeRoot();
36633
37106
  const file2 = snapshotFilePath(agentBase, tid);
36634
37107
  try {
36635
- await fs31.promises.access(file2, fs31.constants.F_OK);
37108
+ await fs32.promises.access(file2, fs32.constants.F_OK);
36636
37109
  } catch {
36637
37110
  deps.log(
36638
37111
  `[Queue] requeued_with_revert: no pre-turn snapshot for ${tid.slice(0, 8)}\u2026; continuing without revert.`
@@ -36920,9 +37393,9 @@ function parseChangeSummarySnapshots(raw) {
36920
37393
  for (const item of raw) {
36921
37394
  if (!item || typeof item !== "object") continue;
36922
37395
  const o = item;
36923
- const path44 = typeof o.path === "string" && o.path.trim() !== "" ? o.path.trim() : "";
36924
- if (!path44) continue;
36925
- const row = { path: path44 };
37396
+ const path46 = typeof o.path === "string" && o.path.trim() !== "" ? o.path.trim() : "";
37397
+ if (!path46) continue;
37398
+ const row = { path: path46 };
36926
37399
  if (typeof o.patchContent === "string") row.patchContent = o.patchContent;
36927
37400
  if (typeof o.oldText === "string") row.oldText = o.oldText;
36928
37401
  if (typeof o.newText === "string") row.newText = o.newText;
@@ -37136,14 +37609,14 @@ var handleSkillCallMessage = (msg, { getWs, log: log2 }) => {
37136
37609
  };
37137
37610
 
37138
37611
  // src/files/list-dir/index.ts
37139
- import fs33 from "node:fs";
37612
+ import fs34 from "node:fs";
37140
37613
 
37141
37614
  // src/files/ensure-under-cwd.ts
37142
- import path36 from "node:path";
37615
+ import path38 from "node:path";
37143
37616
  function ensureUnderCwd(relativePath, cwd = getBridgeRoot()) {
37144
- const normalized = path36.normalize(relativePath).replace(/^(\.\/)+/, "");
37145
- const resolved = path36.resolve(cwd, normalized);
37146
- if (!resolved.startsWith(cwd + path36.sep) && resolved !== cwd) {
37617
+ const normalized = path38.normalize(relativePath).replace(/^(\.\/)+/, "");
37618
+ const resolved = path38.resolve(cwd, normalized);
37619
+ if (!resolved.startsWith(cwd + path38.sep) && resolved !== cwd) {
37147
37620
  return null;
37148
37621
  }
37149
37622
  return resolved;
@@ -37153,15 +37626,15 @@ function ensureUnderCwd(relativePath, cwd = getBridgeRoot()) {
37153
37626
  var LIST_DIR_YIELD_EVERY = 256;
37154
37627
 
37155
37628
  // src/files/list-dir/map-dir-entry.ts
37156
- import path37 from "node:path";
37157
- import fs32 from "node:fs";
37629
+ import path39 from "node:path";
37630
+ import fs33 from "node:fs";
37158
37631
  async function mapDirEntry(d, relativePath, resolved) {
37159
- const entryPath = path37.join(relativePath || ".", d.name).replace(/\\/g, "/");
37160
- const fullPath = path37.join(resolved, d.name);
37632
+ const entryPath = path39.join(relativePath || ".", d.name).replace(/\\/g, "/");
37633
+ const fullPath = path39.join(resolved, d.name);
37161
37634
  let isDir = d.isDirectory();
37162
37635
  if (d.isSymbolicLink()) {
37163
37636
  try {
37164
- const targetStat = await fs32.promises.stat(fullPath);
37637
+ const targetStat = await fs33.promises.stat(fullPath);
37165
37638
  isDir = targetStat.isDirectory();
37166
37639
  } catch {
37167
37640
  isDir = false;
@@ -37191,7 +37664,7 @@ async function listDirAsync(relativePath, sessionParentPath = getBridgeRoot()) {
37191
37664
  return { error: "Path is outside working directory" };
37192
37665
  }
37193
37666
  try {
37194
- const names = await fs33.promises.readdir(resolved, { withFileTypes: true });
37667
+ const names = await fs34.promises.readdir(resolved, { withFileTypes: true });
37195
37668
  const entries = [];
37196
37669
  for (let i = 0; i < names.length; i++) {
37197
37670
  if (i > 0 && i % LIST_DIR_YIELD_EVERY === 0) {
@@ -37211,18 +37684,18 @@ var LINE_CHUNK_SIZE = 64 * 1024;
37211
37684
  var READ_RANGE_YIELD_EVERY_BYTES = 256 * 1024;
37212
37685
 
37213
37686
  // src/files/read-file/resolve-file-path.ts
37214
- import fs34 from "node:fs";
37687
+ import fs35 from "node:fs";
37215
37688
  async function resolveFilePathAsync(relativePath, sessionParentPath = getBridgeRoot()) {
37216
37689
  const resolved = ensureUnderCwd(relativePath, sessionParentPath);
37217
37690
  if (!resolved) return { error: "Path is outside working directory" };
37218
37691
  let real;
37219
37692
  try {
37220
- real = await fs34.promises.realpath(resolved);
37693
+ real = await fs35.promises.realpath(resolved);
37221
37694
  } catch {
37222
37695
  real = resolved;
37223
37696
  }
37224
37697
  try {
37225
- const stat2 = await fs34.promises.stat(real);
37698
+ const stat2 = await fs35.promises.stat(real);
37226
37699
  if (!stat2.isFile()) return { error: "Not a file" };
37227
37700
  return real;
37228
37701
  } catch (err) {
@@ -37231,11 +37704,11 @@ async function resolveFilePathAsync(relativePath, sessionParentPath = getBridgeR
37231
37704
  }
37232
37705
 
37233
37706
  // src/files/read-file/read-file-range-async.ts
37234
- import fs35 from "node:fs";
37707
+ import fs36 from "node:fs";
37235
37708
  import { StringDecoder } from "node:string_decoder";
37236
37709
  async function readFileRangeAsync(filePath, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
37237
- const fileSize = (await fs35.promises.stat(filePath)).size;
37238
- const fd = await fs35.promises.open(filePath, "r");
37710
+ const fileSize = (await fs36.promises.stat(filePath)).size;
37711
+ const fd = await fs36.promises.open(filePath, "r");
37239
37712
  const bufSize = 64 * 1024;
37240
37713
  const buf = Buffer.alloc(bufSize);
37241
37714
  const decoder = new StringDecoder("utf8");
@@ -37397,11 +37870,11 @@ async function readFileRangeAsync(filePath, startLine, endLine, lineOffsetIn, li
37397
37870
  }
37398
37871
 
37399
37872
  // src/files/read-file/read-file-buffer-full-async.ts
37400
- import fs36 from "node:fs";
37873
+ import fs37 from "node:fs";
37401
37874
  var READ_CHUNK_BYTES = 256 * 1024;
37402
37875
  async function readFileBufferFullAsync(filePath) {
37403
- const stat2 = await fs36.promises.stat(filePath);
37404
- const fd = await fs36.promises.open(filePath, "r");
37876
+ const stat2 = await fs37.promises.stat(filePath);
37877
+ const fd = await fs37.promises.open(filePath, "r");
37405
37878
  const chunks = [];
37406
37879
  let position = 0;
37407
37880
  let bytesSinceYield = 0;
@@ -37508,13 +37981,13 @@ function resolveFileBrowserSessionParent(sessionWorktreeManager, sessionId) {
37508
37981
  }
37509
37982
 
37510
37983
  // src/files/handle-file-browser-search.ts
37511
- import path38 from "node:path";
37984
+ import path40 from "node:path";
37512
37985
  var SEARCH_LIMIT = 100;
37513
37986
  function handleFileBrowserSearch(msg, socket, e2ee, sessionWorktreeManager) {
37514
37987
  void (async () => {
37515
37988
  await yieldToEventLoop();
37516
37989
  const q = typeof msg.q === "string" ? msg.q : "";
37517
- const sessionParentPath = path38.resolve(
37990
+ const sessionParentPath = path40.resolve(
37518
37991
  sessionWorktreeManager != null ? resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : getBridgeRoot()
37519
37992
  );
37520
37993
  if (!await bridgeFileIndexIsPopulated(sessionParentPath)) {
@@ -37633,8 +38106,8 @@ function handleSkillLayoutRequest(msg, deps) {
37633
38106
  }
37634
38107
 
37635
38108
  // src/skills/install-remote-skills.ts
37636
- import fs37 from "node:fs";
37637
- import path39 from "node:path";
38109
+ import fs38 from "node:fs";
38110
+ import path41 from "node:path";
37638
38111
  function installRemoteSkills(cwd, targetDir, items) {
37639
38112
  const installed2 = [];
37640
38113
  if (!Array.isArray(items)) {
@@ -37645,15 +38118,15 @@ function installRemoteSkills(cwd, targetDir, items) {
37645
38118
  if (typeof item.sourceId !== "string" || typeof item.skillName !== "string" || typeof item.versionHash !== "string" || !Array.isArray(item.files)) {
37646
38119
  continue;
37647
38120
  }
37648
- const skillDir = path39.join(cwd, targetDir, item.skillName);
38121
+ const skillDir = path41.join(cwd, targetDir, item.skillName);
37649
38122
  for (const f of item.files) {
37650
38123
  if (typeof f.path !== "string" || !f.text && !f.base64) continue;
37651
- const dest = path39.join(skillDir, f.path);
37652
- fs37.mkdirSync(path39.dirname(dest), { recursive: true });
38124
+ const dest = path41.join(skillDir, f.path);
38125
+ fs38.mkdirSync(path41.dirname(dest), { recursive: true });
37653
38126
  if (f.text !== void 0) {
37654
- fs37.writeFileSync(dest, f.text, "utf8");
38127
+ fs38.writeFileSync(dest, f.text, "utf8");
37655
38128
  } else if (f.base64) {
37656
- fs37.writeFileSync(dest, Buffer.from(f.base64, "base64"));
38129
+ fs38.writeFileSync(dest, Buffer.from(f.base64, "base64"));
37657
38130
  }
37658
38131
  }
37659
38132
  installed2.push({
@@ -37811,7 +38284,7 @@ var handleSessionDiscardedMessage = (msg, deps) => {
37811
38284
  };
37812
38285
 
37813
38286
  // src/routing/handlers/revert-turn-snapshot.ts
37814
- import * as fs38 from "node:fs";
38287
+ import * as fs39 from "node:fs";
37815
38288
  var handleRevertTurnSnapshotMessage = (msg, deps) => {
37816
38289
  const id = typeof msg.id === "string" ? msg.id : "";
37817
38290
  const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
@@ -37824,7 +38297,7 @@ var handleRevertTurnSnapshotMessage = (msg, deps) => {
37824
38297
  const agentBase = sessionWorktreeManager.getSessionWorktreeRootForSession(sessionId) ?? getBridgeRoot();
37825
38298
  const file2 = snapshotFilePath(agentBase, turnId);
37826
38299
  try {
37827
- await fs38.promises.access(file2, fs38.constants.F_OK);
38300
+ await fs39.promises.access(file2, fs39.constants.F_OK);
37828
38301
  } catch {
37829
38302
  sendWsMessage(s, {
37830
38303
  type: "revert_turn_snapshot_result",
@@ -37866,7 +38339,7 @@ var handleDevServersConfig = (msg, deps) => {
37866
38339
  };
37867
38340
 
37868
38341
  // src/git/bridge-git-context.ts
37869
- import * as path40 from "node:path";
38342
+ import * as path42 from "node:path";
37870
38343
 
37871
38344
  // src/git/branches/get-current-branch.ts
37872
38345
  async function getCurrentBranch(repoPath) {
@@ -37916,12 +38389,12 @@ async function listRepoBranchRefs(repoPath) {
37916
38389
  // src/git/bridge-git-context.ts
37917
38390
  function folderNameForRelPath(relPath, bridgeRoot) {
37918
38391
  if (relPath === "." || relPath === "") {
37919
- return path40.basename(path40.resolve(bridgeRoot)) || "repo";
38392
+ return path42.basename(path42.resolve(bridgeRoot)) || "repo";
37920
38393
  }
37921
- return path40.basename(relPath) || relPath;
38394
+ return path42.basename(relPath) || relPath;
37922
38395
  }
37923
38396
  async function discoverGitReposForBridgeContext(bridgeRoot) {
37924
- const root = path40.resolve(bridgeRoot);
38397
+ const root = path42.resolve(bridgeRoot);
37925
38398
  if (await isGitRepoDirectory(root)) {
37926
38399
  const remoteUrl = await getRemoteOriginUrl(root);
37927
38400
  return [{ absolutePath: root, remoteUrl }];
@@ -37929,17 +38402,17 @@ async function discoverGitReposForBridgeContext(bridgeRoot) {
37929
38402
  const [deep, shallow] = await Promise.all([discoverGitReposUnderRoot(root), discoverGitRepos(root)]);
37930
38403
  const byPath = /* @__PURE__ */ new Map();
37931
38404
  for (const repo of [...deep, ...shallow]) {
37932
- byPath.set(path40.resolve(repo.absolutePath), repo);
38405
+ byPath.set(path42.resolve(repo.absolutePath), repo);
37933
38406
  }
37934
38407
  return [...byPath.values()];
37935
38408
  }
37936
38409
  async function getBridgeGitContext(bridgeRoot = getBridgeRoot()) {
37937
- const bridgeResolved = path40.resolve(bridgeRoot);
38410
+ const bridgeResolved = path42.resolve(bridgeRoot);
37938
38411
  const repos = await discoverGitReposForBridgeContext(bridgeResolved);
37939
38412
  const rows = [];
37940
38413
  for (const repo of repos) {
37941
- let rel = path40.relative(bridgeResolved, repo.absolutePath);
37942
- if (rel.startsWith("..") || path40.isAbsolute(rel)) continue;
38414
+ let rel = path42.relative(bridgeResolved, repo.absolutePath);
38415
+ if (rel.startsWith("..") || path42.isAbsolute(rel)) continue;
37943
38416
  const relPath = rel === "" ? "." : rel.replace(/\\/g, "/");
37944
38417
  const currentBranch = await getCurrentBranch(repo.absolutePath);
37945
38418
  const remoteUrl = repo.remoteUrl.trim() || null;
@@ -37954,11 +38427,11 @@ async function getBridgeGitContext(bridgeRoot = getBridgeRoot()) {
37954
38427
  return rows;
37955
38428
  }
37956
38429
  async function listRepoBranchesForBridge(repoRelPath, bridgeRoot = getBridgeRoot()) {
37957
- const bridgeResolved = path40.resolve(bridgeRoot);
38430
+ const bridgeResolved = path42.resolve(bridgeRoot);
37958
38431
  const rel = repoRelPath.trim() === "." ? "" : repoRelPath.trim().replace(/\\/g, "/");
37959
- const repoPath = rel === "" ? bridgeResolved : path40.join(bridgeResolved, rel);
37960
- const resolved = path40.resolve(repoPath);
37961
- if (!resolved.startsWith(bridgeResolved + path40.sep) && resolved !== bridgeResolved) {
38432
+ const repoPath = rel === "" ? bridgeResolved : path42.join(bridgeResolved, rel);
38433
+ const resolved = path42.resolve(repoPath);
38434
+ if (!resolved.startsWith(bridgeResolved + path42.sep) && resolved !== bridgeResolved) {
37962
38435
  return [];
37963
38436
  }
37964
38437
  return listRepoBranchRefs(resolved);
@@ -38542,10 +39015,10 @@ function listCliAgentCapabilityCacheForWorkspace(db, workspaceId) {
38542
39015
  }
38543
39016
 
38544
39017
  // src/agents/capabilities/warmup-agent-capabilities-on-connect.ts
38545
- import * as path42 from "node:path";
39018
+ import * as path44 from "node:path";
38546
39019
 
38547
39020
  // src/agents/capabilities/probe-one-agent-type-for-capabilities.ts
38548
- import * as path41 from "node:path";
39021
+ import * as path43 from "node:path";
38549
39022
  async function probeOneAgentTypeForCapabilities(params) {
38550
39023
  const { agentType, cwd, workspaceId, log: log2, reportAgentCapabilities, bridgeReport = true, shouldContinue } = params;
38551
39024
  const canContinue = () => shouldContinue?.() !== false;
@@ -38583,7 +39056,7 @@ async function probeOneAgentTypeForCapabilities(params) {
38583
39056
  if (!canContinue()) return false;
38584
39057
  handle = await resolved.createClient({
38585
39058
  command: resolved.command,
38586
- cwd: path41.resolve(cwd),
39059
+ cwd: path43.resolve(cwd),
38587
39060
  backendAgentType: agentType,
38588
39061
  sessionMode: "agent",
38589
39062
  persistedAcpSessionId: null,
@@ -38661,7 +39134,7 @@ async function warmupAgentCapabilitiesOnConnect(params) {
38661
39134
  const { workspaceId, log: log2, getWs } = params;
38662
39135
  const isCurrent = beginAgentCapabilityWarmupRun();
38663
39136
  if (!isCurrent()) return;
38664
- const cwd = path42.resolve(getBridgeRoot());
39137
+ const cwd = path44.resolve(getBridgeRoot());
38665
39138
  async function sendBatchFromCache() {
38666
39139
  if (!isCurrent()) return;
38667
39140
  const socket = getWs();
@@ -38727,6 +39200,232 @@ async function warmupAgentCapabilitiesOnConnect(params) {
38727
39200
  })();
38728
39201
  }
38729
39202
 
39203
+ // src/mcp/tools/session-history/fork-session-mcp-tools.ts
39204
+ var LIST_PARENT_SESSION_PROMPTS_TOOL = "list_parent_session_prompts";
39205
+ var GET_PARENT_SESSION_TURN_TRANSCRIPT_TOOL = "get_parent_session_turn_transcript";
39206
+ var SESSION_ID_SCHEMA = {
39207
+ type: "string",
39208
+ description: "Id of this forked (child) session \u2014 not the parent session id. Required on every tool call."
39209
+ };
39210
+ var FORK_SESSION_MCP_TOOL_DEFINITIONS = [
39211
+ {
39212
+ name: LIST_PARENT_SESSION_PROMPTS_TOOL,
39213
+ 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).",
39214
+ inputSchema: {
39215
+ type: "object",
39216
+ properties: { sessionId: SESSION_ID_SCHEMA },
39217
+ required: ["sessionId"],
39218
+ additionalProperties: false
39219
+ }
39220
+ },
39221
+ {
39222
+ name: GET_PARENT_SESSION_TURN_TRANSCRIPT_TOOL,
39223
+ 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).",
39224
+ inputSchema: {
39225
+ type: "object",
39226
+ properties: {
39227
+ sessionId: SESSION_ID_SCHEMA,
39228
+ turnId: { type: "string", description: "Turn id from list_parent_session_prompts" }
39229
+ },
39230
+ required: ["sessionId", "turnId"],
39231
+ additionalProperties: false
39232
+ }
39233
+ }
39234
+ ];
39235
+ function textResult(text, isError = false) {
39236
+ return { content: [{ type: "text", text }], ...isError ? { isError: true } : {} };
39237
+ }
39238
+ function isForkSessionMcpToolName(name) {
39239
+ return name === LIST_PARENT_SESSION_PROMPTS_TOOL || name === GET_PARENT_SESSION_TURN_TRANSCRIPT_TOOL;
39240
+ }
39241
+ async function callForkSessionMcpTool(ctx, cloud, name, args) {
39242
+ const childSessionId = ctx.sessionId.trim();
39243
+ if (!childSessionId) {
39244
+ return textResult("sessionId is required.", true);
39245
+ }
39246
+ const parentResolved = await resolveParentSessionIdForChild({
39247
+ childSessionId,
39248
+ cloudApiBaseUrl: cloud.cloudApiBaseUrl,
39249
+ getCloudAccessToken: cloud.getCloudAccessToken
39250
+ });
39251
+ if (!parentResolved.ok) {
39252
+ return textResult(parentResolved.error, true);
39253
+ }
39254
+ const parentSessionId = parentResolved.parentSessionId;
39255
+ if (name === LIST_PARENT_SESSION_PROMPTS_TOOL) {
39256
+ const detail = await fetchCloudParentSessionPrompts({
39257
+ childSessionId,
39258
+ cloudApiBaseUrl: cloud.cloudApiBaseUrl,
39259
+ getCloudAccessToken: cloud.getCloudAccessToken
39260
+ });
39261
+ if (!detail.ok) return textResult(detail.error, true);
39262
+ const turns = Array.isArray(detail.data.turns) ? detail.data.turns : [];
39263
+ const payload = turns.map((t) => ({
39264
+ turnId: typeof t.turnId === "string" ? t.turnId : "",
39265
+ title: typeof t.title === "string" ? t.title : null,
39266
+ status: typeof t.status === "string" ? t.status : null,
39267
+ promptText: typeof t.promptText === "string" ? t.promptText : null
39268
+ }));
39269
+ return textResult(JSON.stringify({ parentSessionId, turns: payload }, null, 2));
39270
+ }
39271
+ if (name === GET_PARENT_SESSION_TURN_TRANSCRIPT_TOOL) {
39272
+ const turnId = typeof args.turnId === "string" ? args.turnId.trim() : "";
39273
+ if (!turnId) return textResult("turnId is required.", true);
39274
+ const transcript = await fetchCloudParentTurnTranscript({
39275
+ childSessionId,
39276
+ turnId,
39277
+ cloudApiBaseUrl: cloud.cloudApiBaseUrl,
39278
+ getCloudAccessToken: cloud.getCloudAccessToken,
39279
+ e2ee: ctx.e2ee
39280
+ });
39281
+ if (!transcript.ok) return textResult(transcript.error, true);
39282
+ return textResult(transcript.text);
39283
+ }
39284
+ return textResult(`Unknown fork session tool: ${name}`, true);
39285
+ }
39286
+
39287
+ // src/mcp/bridge-mcp-tools.ts
39288
+ function requireCloud(shared) {
39289
+ const cloudApiBaseUrl = shared.cloudApiBaseUrl?.trim() ?? "";
39290
+ const getCloudAccessToken = shared.getCloudAccessToken;
39291
+ if (!cloudApiBaseUrl || !getCloudAccessToken) return null;
39292
+ return { cloudApiBaseUrl, getCloudAccessToken };
39293
+ }
39294
+ function createBridgeMcpToolRegistry(shared) {
39295
+ const cloud = requireCloud(shared);
39296
+ return {
39297
+ listTools: async () => [...FORK_SESSION_MCP_TOOL_DEFINITIONS],
39298
+ callTool: async (name, args) => {
39299
+ if (isForkSessionMcpToolName(name)) {
39300
+ if (!cloud) {
39301
+ return {
39302
+ content: [{ type: "text", text: "Bridge MCP tools require cloud API credentials." }],
39303
+ isError: true
39304
+ };
39305
+ }
39306
+ const sessionId = typeof args.sessionId === "string" ? args.sessionId.trim() : "";
39307
+ if (!sessionId) {
39308
+ return {
39309
+ content: [{ type: "text", text: "sessionId is required." }],
39310
+ isError: true
39311
+ };
39312
+ }
39313
+ return callForkSessionMcpTool({ ...shared, sessionId }, cloud, name, args);
39314
+ }
39315
+ throw new Error(`Unknown bridge MCP tool: ${name}`);
39316
+ }
39317
+ };
39318
+ }
39319
+
39320
+ // src/mcp/bridge-access/start-server.ts
39321
+ import * as http from "node:http";
39322
+
39323
+ // src/mcp/bridge-access/read-json-body.ts
39324
+ function readJsonBody(req) {
39325
+ return new Promise((resolve20, reject) => {
39326
+ const chunks = [];
39327
+ req.on("data", (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
39328
+ req.on("end", () => {
39329
+ try {
39330
+ const raw = Buffer.concat(chunks).toString("utf8").trim();
39331
+ if (!raw) {
39332
+ resolve20({});
39333
+ return;
39334
+ }
39335
+ const parsed = JSON.parse(raw);
39336
+ resolve20(parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {});
39337
+ } catch (e) {
39338
+ reject(e);
39339
+ }
39340
+ });
39341
+ req.on("error", reject);
39342
+ });
39343
+ }
39344
+
39345
+ // src/mcp/bridge-access/send-json-response.ts
39346
+ function sendJsonResponse(res, status, body) {
39347
+ res.writeHead(status, { "Content-Type": "application/json" });
39348
+ res.end(JSON.stringify(body));
39349
+ }
39350
+
39351
+ // src/mcp/bridge-access/handle-request.ts
39352
+ function createBridgeAccessRequestHandler(registry2) {
39353
+ return (req, res) => {
39354
+ void (async () => {
39355
+ if (req.method !== "POST") {
39356
+ sendJsonResponse(res, 405, { ok: false, error: "Method not allowed" });
39357
+ return;
39358
+ }
39359
+ try {
39360
+ const body = await readJsonBody(req);
39361
+ if (req.url === "/tools/list") {
39362
+ const tools = await Promise.resolve(registry2.listTools());
39363
+ sendJsonResponse(res, 200, { ok: true, data: tools });
39364
+ return;
39365
+ }
39366
+ if (req.url === "/tools/call") {
39367
+ const sessionId = typeof body.sessionId === "string" ? body.sessionId.trim() : "";
39368
+ if (!sessionId) {
39369
+ sendJsonResponse(res, 400, { ok: false, error: "sessionId is required" });
39370
+ return;
39371
+ }
39372
+ const name = typeof body.name === "string" ? body.name.trim() : "";
39373
+ const args = body.args && typeof body.args === "object" && !Array.isArray(body.args) ? body.args : {};
39374
+ if (!name) {
39375
+ sendJsonResponse(res, 400, { ok: false, error: "name is required" });
39376
+ return;
39377
+ }
39378
+ const data = await registry2.callTool(name, { ...args, sessionId });
39379
+ sendJsonResponse(res, 200, { ok: true, data });
39380
+ return;
39381
+ }
39382
+ sendJsonResponse(res, 404, { ok: false, error: "Not found" });
39383
+ } catch (err) {
39384
+ const message = err instanceof Error ? err.message : String(err);
39385
+ sendJsonResponse(res, 500, { ok: false, error: message });
39386
+ }
39387
+ })();
39388
+ };
39389
+ }
39390
+
39391
+ // src/mcp/bridge-access/start-server.ts
39392
+ function startBridgeAccessServer(registry2) {
39393
+ const server = http.createServer(createBridgeAccessRequestHandler(registry2));
39394
+ return new Promise((resolve20, reject) => {
39395
+ server.once("error", reject);
39396
+ server.listen(0, "127.0.0.1", () => {
39397
+ const addr = server.address();
39398
+ if (!addr || typeof addr === "string") {
39399
+ reject(new Error("Bridge access server did not bind"));
39400
+ return;
39401
+ }
39402
+ resolve20({
39403
+ port: addr.port,
39404
+ close: () => new Promise((closeResolve, closeReject) => {
39405
+ server.close((err) => err ? closeReject(err) : closeResolve());
39406
+ })
39407
+ });
39408
+ });
39409
+ });
39410
+ }
39411
+
39412
+ // src/mcp/bridge-access/create-state.ts
39413
+ async function createBridgeAccessState(options = {}) {
39414
+ const registry2 = createBridgeMcpToolRegistry({
39415
+ cloudApiBaseUrl: options.cloudApiBaseUrl,
39416
+ getCloudAccessToken: options.getCloudAccessToken,
39417
+ e2ee: options.e2ee
39418
+ });
39419
+ const server = await startBridgeAccessServer(registry2);
39420
+ return {
39421
+ getPort: () => server.port,
39422
+ getRegistry: () => registry2,
39423
+ close: async () => {
39424
+ await server.close();
39425
+ }
39426
+ };
39427
+ }
39428
+
38730
39429
  // src/connection/create-bridge-connection.ts
38731
39430
  async function createBridgeConnection(options) {
38732
39431
  const { apiUrl, workspaceId, justAuthenticated, onAuthInvalid, persistTokens } = options;
@@ -38789,12 +39488,18 @@ async function createBridgeConnection(options) {
38789
39488
  worktreesRootPath,
38790
39489
  log: logFn
38791
39490
  });
39491
+ const e2ee = options.e2eCertificate ? createCliE2eeRuntime(options.e2eCertificate) : void 0;
39492
+ const bridgeAccess = await createBridgeAccessState({
39493
+ cloudApiBaseUrl: apiUrl,
39494
+ getCloudAccessToken: () => tokens.accessToken,
39495
+ e2ee
39496
+ });
38792
39497
  const acpManager = await createAcpManager({
38793
39498
  log: logFn,
39499
+ getBridgeAccessPort: () => bridgeAccess.getPort(),
38794
39500
  reportAgentCapabilities: sendAgentCapabilitiesToBridge
38795
39501
  });
38796
39502
  logFn("CLI running. Press Ctrl+C to exit.");
38797
- const e2ee = options.e2eCertificate ? createCliE2eeRuntime(options.e2eCertificate) : void 0;
38798
39503
  const devServerManager = new DevServerManager({ getWs, log: logFn, getBridgeRoot, e2ee });
38799
39504
  const bridgeHeartbeat = createBridgeHeartbeatController({ getWs, log: logFn });
38800
39505
  const baseOnBridgeIdentified = createOnBridgeIdentified({
@@ -38834,8 +39539,8 @@ async function createBridgeConnection(options) {
38834
39539
  getCloudAccessToken: () => tokens.accessToken
38835
39540
  };
38836
39541
  const identifyReportedPaths = {
38837
- bridgeRootPath: path43.resolve(getBridgeRoot()),
38838
- worktreesRootPath: path43.resolve(worktreesRootPath)
39542
+ bridgeRootPath: path45.resolve(getBridgeRoot()),
39543
+ worktreesRootPath: path45.resolve(worktreesRootPath)
38839
39544
  };
38840
39545
  const { connect } = createMainBridgeWebSocketLifecycle({
38841
39546
  state,
@@ -38860,6 +39565,7 @@ async function createBridgeConnection(options) {
38860
39565
  stopFileIndexWatcher();
38861
39566
  bridgeHeartbeat.stop();
38862
39567
  await closeBridgeConnection(state, acpManager, devServerManager, logFn);
39568
+ await bridgeAccess.close();
38863
39569
  }
38864
39570
  };
38865
39571
  }