@buildautomaton/cli 0.1.38 → 0.1.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -973,7 +973,7 @@ var require_command = __commonJS({
973
973
  "../../node_modules/.pnpm/commander@12.1.0/node_modules/commander/lib/command.js"(exports) {
974
974
  var EventEmitter2 = __require("node:events").EventEmitter;
975
975
  var childProcess2 = __require("node:child_process");
976
- var path43 = __require("node:path");
976
+ var path46 = __require("node:path");
977
977
  var fs41 = __require("node:fs");
978
978
  var process8 = __require("node:process");
979
979
  var { Argument: Argument2, humanReadableArgName } = require_argument();
@@ -1906,9 +1906,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
1906
1906
  let launchWithNode = false;
1907
1907
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
1908
1908
  function findFile(baseDir, baseName) {
1909
- const localBin = path43.resolve(baseDir, baseName);
1909
+ const localBin = path46.resolve(baseDir, baseName);
1910
1910
  if (fs41.existsSync(localBin)) return localBin;
1911
- if (sourceExt.includes(path43.extname(baseName))) return void 0;
1911
+ if (sourceExt.includes(path46.extname(baseName))) return void 0;
1912
1912
  const foundExt = sourceExt.find(
1913
1913
  (ext) => fs41.existsSync(`${localBin}${ext}`)
1914
1914
  );
@@ -1926,17 +1926,17 @@ Expecting one of '${allowedValues.join("', '")}'`);
1926
1926
  } catch (err) {
1927
1927
  resolvedScriptPath = this._scriptPath;
1928
1928
  }
1929
- executableDir = path43.resolve(
1930
- path43.dirname(resolvedScriptPath),
1929
+ executableDir = path46.resolve(
1930
+ path46.dirname(resolvedScriptPath),
1931
1931
  executableDir
1932
1932
  );
1933
1933
  }
1934
1934
  if (executableDir) {
1935
1935
  let localFile = findFile(executableDir, executableFile);
1936
1936
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
1937
- const legacyName = path43.basename(
1937
+ const legacyName = path46.basename(
1938
1938
  this._scriptPath,
1939
- path43.extname(this._scriptPath)
1939
+ path46.extname(this._scriptPath)
1940
1940
  );
1941
1941
  if (legacyName !== this._name) {
1942
1942
  localFile = findFile(
@@ -1947,7 +1947,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1947
1947
  }
1948
1948
  executableFile = localFile || executableFile;
1949
1949
  }
1950
- launchWithNode = sourceExt.includes(path43.extname(executableFile));
1950
+ launchWithNode = sourceExt.includes(path46.extname(executableFile));
1951
1951
  let proc;
1952
1952
  if (process8.platform !== "win32") {
1953
1953
  if (launchWithNode) {
@@ -2787,7 +2787,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2787
2787
  * @return {Command}
2788
2788
  */
2789
2789
  nameFromFilename(filename) {
2790
- this._name = path43.basename(filename, path43.extname(filename));
2790
+ this._name = path46.basename(filename, path46.extname(filename));
2791
2791
  return this;
2792
2792
  }
2793
2793
  /**
@@ -2801,9 +2801,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
2801
2801
  * @param {string} [path]
2802
2802
  * @return {(string|null|Command)}
2803
2803
  */
2804
- executableDir(path44) {
2805
- if (path44 === void 0) return this._executableDir;
2806
- this._executableDir = path44;
2804
+ executableDir(path47) {
2805
+ if (path47 === void 0) return this._executableDir;
2806
+ this._executableDir = path47;
2807
2807
  return this;
2808
2808
  }
2809
2809
  /**
@@ -7061,8 +7061,8 @@ var init_parseUtil = __esm({
7061
7061
  init_errors();
7062
7062
  init_en();
7063
7063
  makeIssue = (params) => {
7064
- const { data, path: path43, errorMaps, issueData } = params;
7065
- const fullPath = [...path43, ...issueData.path || []];
7064
+ const { data, path: path46, errorMaps, issueData } = params;
7065
+ const fullPath = [...path46, ...issueData.path || []];
7066
7066
  const fullIssue = {
7067
7067
  ...issueData,
7068
7068
  path: fullPath
@@ -7370,11 +7370,11 @@ var init_types = __esm({
7370
7370
  init_parseUtil();
7371
7371
  init_util();
7372
7372
  ParseInputLazyPath = class {
7373
- constructor(parent, value, path43, key) {
7373
+ constructor(parent, value, path46, key) {
7374
7374
  this._cachedPath = [];
7375
7375
  this.parent = parent;
7376
7376
  this.data = value;
7377
- this._path = path43;
7377
+ this._path = path46;
7378
7378
  this._key = key;
7379
7379
  }
7380
7380
  get path() {
@@ -11529,10 +11529,10 @@ var require_src2 = __commonJS({
11529
11529
  var fs_1 = __require("fs");
11530
11530
  var debug_1 = __importDefault(require_src());
11531
11531
  var log2 = debug_1.default("@kwsites/file-exists");
11532
- function check2(path43, isFile, isDirectory) {
11533
- log2(`checking %s`, path43);
11532
+ function check2(path46, isFile, isDirectory) {
11533
+ log2(`checking %s`, path46);
11534
11534
  try {
11535
- const stat3 = fs_1.statSync(path43);
11535
+ const stat3 = fs_1.statSync(path46);
11536
11536
  if (stat3.isFile() && isFile) {
11537
11537
  log2(`[OK] path represents a file`);
11538
11538
  return true;
@@ -11552,8 +11552,8 @@ var require_src2 = __commonJS({
11552
11552
  throw e;
11553
11553
  }
11554
11554
  }
11555
- function exists2(path43, type = exports.READABLE) {
11556
- return check2(path43, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
11555
+ function exists2(path46, type = exports.READABLE) {
11556
+ return check2(path46, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
11557
11557
  }
11558
11558
  exports.exists = exists2;
11559
11559
  exports.FILE = 1;
@@ -11850,10 +11850,10 @@ function assignProp(target, prop, value) {
11850
11850
  configurable: true
11851
11851
  });
11852
11852
  }
11853
- function getElementAtPath(obj, path43) {
11854
- if (!path43)
11853
+ function getElementAtPath(obj, path46) {
11854
+ if (!path46)
11855
11855
  return obj;
11856
- return path43.reduce((acc, key) => acc?.[key], obj);
11856
+ return path46.reduce((acc, key) => acc?.[key], obj);
11857
11857
  }
11858
11858
  function promiseAllObject(promisesObj) {
11859
11859
  const keys = Object.keys(promisesObj);
@@ -12102,11 +12102,11 @@ function aborted(x, startIndex = 0) {
12102
12102
  }
12103
12103
  return false;
12104
12104
  }
12105
- function prefixIssues(path43, issues) {
12105
+ function prefixIssues(path46, issues) {
12106
12106
  return issues.map((iss) => {
12107
12107
  var _a2;
12108
12108
  (_a2 = iss).path ?? (_a2.path = []);
12109
- iss.path.unshift(path43);
12109
+ iss.path.unshift(path46);
12110
12110
  return iss;
12111
12111
  });
12112
12112
  }
@@ -12295,7 +12295,7 @@ function treeifyError(error40, _mapper) {
12295
12295
  return issue2.message;
12296
12296
  };
12297
12297
  const result = { errors: [] };
12298
- const processError = (error41, path43 = []) => {
12298
+ const processError = (error41, path46 = []) => {
12299
12299
  var _a2, _b;
12300
12300
  for (const issue2 of error41.issues) {
12301
12301
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -12305,7 +12305,7 @@ function treeifyError(error40, _mapper) {
12305
12305
  } else if (issue2.code === "invalid_element") {
12306
12306
  processError({ issues: issue2.issues }, issue2.path);
12307
12307
  } else {
12308
- const fullpath = [...path43, ...issue2.path];
12308
+ const fullpath = [...path46, ...issue2.path];
12309
12309
  if (fullpath.length === 0) {
12310
12310
  result.errors.push(mapper(issue2));
12311
12311
  continue;
@@ -12335,9 +12335,9 @@ function treeifyError(error40, _mapper) {
12335
12335
  processError(error40);
12336
12336
  return result;
12337
12337
  }
12338
- function toDotPath(path43) {
12338
+ function toDotPath(path46) {
12339
12339
  const segs = [];
12340
- for (const seg of path43) {
12340
+ for (const seg of path46) {
12341
12341
  if (typeof seg === "number")
12342
12342
  segs.push(`[${seg}]`);
12343
12343
  else if (typeof seg === "symbol")
@@ -24800,8 +24800,8 @@ var init_acp = __esm({
24800
24800
  this.#requestHandler = requestHandler;
24801
24801
  this.#notificationHandler = notificationHandler;
24802
24802
  this.#stream = stream;
24803
- this.#closedPromise = new Promise((resolve20) => {
24804
- this.#abortController.signal.addEventListener("abort", () => resolve20());
24803
+ this.#closedPromise = new Promise((resolve22) => {
24804
+ this.#abortController.signal.addEventListener("abort", () => resolve22());
24805
24805
  });
24806
24806
  this.#receive();
24807
24807
  }
@@ -24950,8 +24950,8 @@ var init_acp = __esm({
24950
24950
  }
24951
24951
  async sendRequest(method, params) {
24952
24952
  const id = this.#nextRequestId++;
24953
- const responsePromise = new Promise((resolve20, reject) => {
24954
- this.#pendingResponses.set(id, { resolve: resolve20, reject });
24953
+ const responsePromise = new Promise((resolve22, reject) => {
24954
+ this.#pendingResponses.set(id, { resolve: resolve22, reject });
24955
24955
  });
24956
24956
  await this.#sendMessage({ jsonrpc: "2.0", id, method, params });
24957
24957
  return responsePromise;
@@ -25064,7 +25064,7 @@ var {
25064
25064
  } = import_index.default;
25065
25065
 
25066
25066
  // src/cli-version.ts
25067
- var CLI_VERSION = "0.1.38".length > 0 ? "0.1.38" : "0.0.0-dev";
25067
+ var CLI_VERSION = "0.1.40".length > 0 ? "0.1.40" : "0.0.0-dev";
25068
25068
 
25069
25069
  // src/cli/defaults.ts
25070
25070
  var DEFAULT_API_URL = process.env.BUILDAUTOMATON_API_URL ?? "https://api.buildautomaton.com";
@@ -25072,7 +25072,7 @@ var DEFAULT_FIREHOSE_URL = "https://buildautomaton-firehose.fly.dev";
25072
25072
 
25073
25073
  // src/cli/run-cli-action.ts
25074
25074
  import * as fs40 from "node:fs";
25075
- import * as path42 from "node:path";
25075
+ import * as path45 from "node:path";
25076
25076
 
25077
25077
  // src/cli-log-level.ts
25078
25078
  var verbosity = "info";
@@ -25542,9 +25542,9 @@ function attachWebSocketClientPing(ws, intervalMs) {
25542
25542
  return clear;
25543
25543
  }
25544
25544
  function buildCliWebSocketClientOptions(wsUrl) {
25545
- const wsOptions = { perMessageDeflate: false, family: 4 };
25545
+ const wsOptions = { perMessageDeflate: false };
25546
25546
  if (wsUrl.startsWith("wss://")) {
25547
- wsOptions.agent = new https.Agent({ rejectUnauthorized: false, family: 4 });
25547
+ wsOptions.agent = new https.Agent({ rejectUnauthorized: false });
25548
25548
  }
25549
25549
  return wsOptions;
25550
25550
  }
@@ -25579,11 +25579,47 @@ function safeSendWebSocketBinary(ws, data) {
25579
25579
  }
25580
25580
  }
25581
25581
 
25582
+ // src/connection/parse-compact-heartbeat-ack.ts
25583
+ function tryParseCompactHeartbeatAck(raw) {
25584
+ let str = null;
25585
+ if (typeof raw === "string") {
25586
+ str = raw;
25587
+ } else if (Buffer.isBuffer(raw)) {
25588
+ str = raw.toString("utf8");
25589
+ } else if (raw instanceof ArrayBuffer) {
25590
+ str = Buffer.from(raw).toString("utf8");
25591
+ } else {
25592
+ return null;
25593
+ }
25594
+ if (str.length > 64 || !str.includes('"ha"')) return null;
25595
+ try {
25596
+ const parsed = JSON.parse(str);
25597
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
25598
+ const o = parsed;
25599
+ if (o.t !== "ha") return null;
25600
+ const s = o.s;
25601
+ if (typeof s !== "number" || !Number.isFinite(s)) return null;
25602
+ return Math.trunc(s);
25603
+ } catch {
25604
+ return null;
25605
+ }
25606
+ }
25607
+
25582
25608
  // src/connection/create-ws-bridge.ts
25583
25609
  var BRIDGE_AUTH_ERROR_HEADER = "x-bridge-auth-error";
25584
25610
  var BRIDGE_AUTH_ERROR_TOKEN_INVALID = "token_invalid";
25585
25611
  function createWsBridge(options) {
25586
- const { url: url2, onMessage, onOpen, onClose, onError: onError2, onAuthInvalid, clientPingIntervalMs } = options;
25612
+ const {
25613
+ url: url2,
25614
+ onMessage,
25615
+ onOpen,
25616
+ onClose,
25617
+ onError: onError2,
25618
+ onAuthInvalid,
25619
+ clientPingIntervalMs,
25620
+ onCompactHeartbeatAck,
25621
+ isActiveSocket
25622
+ } = options;
25587
25623
  applyCliOutboundNetworkPreferences();
25588
25624
  const ws = new wrapper_default(url2, buildCliWebSocketClientOptions(url2));
25589
25625
  let clearClientPing = null;
@@ -25607,7 +25643,13 @@ function createWsBridge(options) {
25607
25643
  onOpen?.();
25608
25644
  });
25609
25645
  ws.on("message", (raw) => {
25646
+ const ackSeq = tryParseCompactHeartbeatAck(raw);
25647
+ if (ackSeq != null) {
25648
+ onCompactHeartbeatAck?.(ackSeq);
25649
+ return;
25650
+ }
25610
25651
  setImmediate(() => {
25652
+ if (isActiveSocket && !isActiveSocket(ws)) return;
25611
25653
  try {
25612
25654
  let data;
25613
25655
  if (typeof raw === "string") {
@@ -25618,9 +25660,9 @@ function createWsBridge(options) {
25618
25660
  } else {
25619
25661
  data = raw;
25620
25662
  }
25621
- onMessage?.(data);
25663
+ onMessage?.(data, ws);
25622
25664
  } catch {
25623
- onMessage?.(raw);
25665
+ onMessage?.(raw, ws);
25624
25666
  }
25625
25667
  });
25626
25668
  });
@@ -26079,14 +26121,14 @@ var baseOpen = async (options) => {
26079
26121
  }
26080
26122
  const subprocess = childProcess.spawn(command, cliArguments, childProcessOptions);
26081
26123
  if (options.wait) {
26082
- return new Promise((resolve20, reject) => {
26124
+ return new Promise((resolve22, reject) => {
26083
26125
  subprocess.once("error", reject);
26084
26126
  subprocess.once("close", (exitCode) => {
26085
26127
  if (!options.allowNonzeroExitCode && exitCode > 0) {
26086
26128
  reject(new Error(`Exited with code ${exitCode}`));
26087
26129
  return;
26088
26130
  }
26089
- resolve20(subprocess);
26131
+ resolve22(subprocess);
26090
26132
  });
26091
26133
  });
26092
26134
  }
@@ -26527,6 +26569,61 @@ function scheduleMainBridgeReconnect(state, connect, log2, closeMeta) {
26527
26569
  });
26528
26570
  }
26529
26571
 
26572
+ // src/connection/reconnect/duplicate-bridge-connection-detect.ts
26573
+ var BRIDGE_SUPERSEDED_CLOSE_REASON_SNIPPET = "superseded";
26574
+ var DUPLICATE_BRIDGE_SHORT_SESSION_MS = 25e3;
26575
+ var DUPLICATE_BRIDGE_FLAP_MIN_COUNT = 2;
26576
+ var DUPLICATE_BRIDGE_FLAP_WINDOW_MS = 9e4;
26577
+ var DUPLICATE_BRIDGE_STABLE_SESSION_MS = BRIDGE_APP_HEARTBEAT_INTERVAL_MS * BRIDGE_HEARTBEAT_MISSED_ACKS_BEFORE_RECONNECT + 5e3;
26578
+ var DUPLICATE_BRIDGE_WARNING_MESSAGE = "[Bridge service] It looks like two bridges are using the same token and disconnecting each other. Stop any duplicate @buildautomaton/cli process for this workspace/token (only one bridge should run).";
26579
+ function createEmptyDuplicateBridgeFlapTracker() {
26580
+ return {
26581
+ sessionOpenedAtMs: null,
26582
+ shortDisconnectAtMs: []
26583
+ };
26584
+ }
26585
+ function isSupersededByNewBridgeClose(code, reason) {
26586
+ if (code !== 1e3) return false;
26587
+ return reason.toLowerCase().includes(BRIDGE_SUPERSEDED_CLOSE_REASON_SNIPPET);
26588
+ }
26589
+ function pruneShortDisconnects(tracker, nowMs) {
26590
+ const cutoff = nowMs - DUPLICATE_BRIDGE_FLAP_WINDOW_MS;
26591
+ tracker.shortDisconnectAtMs = tracker.shortDisconnectAtMs.filter((t) => t >= cutoff);
26592
+ }
26593
+ function recordBridgeSessionOpened(tracker, nowMs) {
26594
+ tracker.sessionOpenedAtMs = nowMs;
26595
+ }
26596
+ function evaluateDuplicateBridgeDisconnect(tracker, nowMs, code, reason) {
26597
+ if (isSupersededByNewBridgeClose(code, reason)) {
26598
+ return { kind: "warn", trigger: "superseded_close" };
26599
+ }
26600
+ const openedAt = tracker.sessionOpenedAtMs;
26601
+ tracker.sessionOpenedAtMs = null;
26602
+ if (openedAt == null) {
26603
+ return { kind: "none" };
26604
+ }
26605
+ const sessionMs = nowMs - openedAt;
26606
+ if (sessionMs >= DUPLICATE_BRIDGE_STABLE_SESSION_MS) {
26607
+ tracker.shortDisconnectAtMs = [];
26608
+ return { kind: "none" };
26609
+ }
26610
+ if (sessionMs < DUPLICATE_BRIDGE_SHORT_SESSION_MS) {
26611
+ pruneShortDisconnects(tracker, nowMs);
26612
+ tracker.shortDisconnectAtMs.push(nowMs);
26613
+ if (tracker.shortDisconnectAtMs.length >= DUPLICATE_BRIDGE_FLAP_MIN_COUNT) {
26614
+ return { kind: "warn", trigger: "rapid_short_disconnects" };
26615
+ }
26616
+ }
26617
+ return { kind: "none" };
26618
+ }
26619
+ function logDuplicateBridgeWarning(log2, result) {
26620
+ if (result.kind !== "warn") return;
26621
+ try {
26622
+ log2(DUPLICATE_BRIDGE_WARNING_MESSAGE);
26623
+ } catch {
26624
+ }
26625
+ }
26626
+
26530
26627
  // src/connection/reconnect/firehose-reconnect.ts
26531
26628
  function beginFirehoseDeferredDisconnect(ctx, code, reason, log2) {
26532
26629
  beginTieredSilentReconnectDisconnect({
@@ -26575,8 +26672,8 @@ function runPendingAuth(options) {
26575
26672
  let hasOpenedBrowser = false;
26576
26673
  let resolved = false;
26577
26674
  let resolveAuth;
26578
- const authPromise = new Promise((resolve20) => {
26579
- resolveAuth = resolve20;
26675
+ const authPromise = new Promise((resolve22) => {
26676
+ resolveAuth = resolve22;
26580
26677
  });
26581
26678
  let reconnectAttempt = 0;
26582
26679
  const signInQuiet = createEmptyReconnectQuietSlot();
@@ -26727,7 +26824,7 @@ import sqliteWasm from "node-sqlite3-wasm";
26727
26824
 
26728
26825
  // src/runtime/yield-to-event-loop.ts
26729
26826
  function yieldToEventLoop() {
26730
- return new Promise((resolve20) => setImmediate(resolve20));
26827
+ return new Promise((resolve22) => setImmediate(resolve22));
26731
26828
  }
26732
26829
 
26733
26830
  // src/sqlite/cli-sqlite-paths.ts
@@ -27570,9 +27667,9 @@ function parseChangeSummaryJson(raw, allowedPaths, options) {
27570
27667
  const rawPath = typeof o.path === "string" ? o.path.trim() : "";
27571
27668
  const summary = typeof o.summary === "string" ? o.summary.trim() : "";
27572
27669
  if (!rawPath || !summary) continue;
27573
- const path43 = skip ? normalizeRepoRelativePath(rawPath) || rawPath : resolveChangeSummaryPathAgainstAllowed(rawPath, allowedPaths);
27574
- if (!path43) continue;
27575
- rows.push({ path: path43, summary: clampSummaryToAtMostTwoLines(summary) });
27670
+ const path46 = skip ? normalizeRepoRelativePath(rawPath) || rawPath : resolveChangeSummaryPathAgainstAllowed(rawPath, allowedPaths);
27671
+ if (!path46) continue;
27672
+ rows.push({ path: path46, summary: clampSummaryToAtMostTwoLines(summary) });
27576
27673
  }
27577
27674
  return rows;
27578
27675
  }
@@ -27866,8 +27963,8 @@ function pathspec(...paths) {
27866
27963
  cache.set(key, paths);
27867
27964
  return key;
27868
27965
  }
27869
- function isPathSpec(path43) {
27870
- return path43 instanceof String && cache.has(path43);
27966
+ function isPathSpec(path46) {
27967
+ return path46 instanceof String && cache.has(path46);
27871
27968
  }
27872
27969
  function toPaths(pathSpec) {
27873
27970
  return cache.get(pathSpec) || [];
@@ -27956,8 +28053,8 @@ function toLinesWithContent(input = "", trimmed2 = true, separator = "\n") {
27956
28053
  function forEachLineWithContent(input, callback) {
27957
28054
  return toLinesWithContent(input, true).map((line) => callback(line));
27958
28055
  }
27959
- function folderExists(path43) {
27960
- return (0, import_file_exists.exists)(path43, import_file_exists.FOLDER);
28056
+ function folderExists(path46) {
28057
+ return (0, import_file_exists.exists)(path46, import_file_exists.FOLDER);
27961
28058
  }
27962
28059
  function append(target, item) {
27963
28060
  if (Array.isArray(target)) {
@@ -28361,8 +28458,8 @@ function checkIsRepoRootTask() {
28361
28458
  commands,
28362
28459
  format: "utf-8",
28363
28460
  onError,
28364
- parser(path43) {
28365
- return /^\.(git)?$/.test(path43.trim());
28461
+ parser(path46) {
28462
+ return /^\.(git)?$/.test(path46.trim());
28366
28463
  }
28367
28464
  };
28368
28465
  }
@@ -28796,11 +28893,11 @@ function parseGrep(grep) {
28796
28893
  const paths = /* @__PURE__ */ new Set();
28797
28894
  const results = {};
28798
28895
  forEachLineWithContent(grep, (input) => {
28799
- const [path43, line, preview] = input.split(NULL);
28800
- paths.add(path43);
28801
- (results[path43] = results[path43] || []).push({
28896
+ const [path46, line, preview] = input.split(NULL);
28897
+ paths.add(path46);
28898
+ (results[path46] = results[path46] || []).push({
28802
28899
  line: asNumber(line),
28803
- path: path43,
28900
+ path: path46,
28804
28901
  preview
28805
28902
  });
28806
28903
  });
@@ -29565,14 +29662,14 @@ var init_hash_object = __esm2({
29565
29662
  init_task();
29566
29663
  }
29567
29664
  });
29568
- function parseInit(bare, path43, text) {
29665
+ function parseInit(bare, path46, text) {
29569
29666
  const response = String(text).trim();
29570
29667
  let result;
29571
29668
  if (result = initResponseRegex.exec(response)) {
29572
- return new InitSummary(bare, path43, false, result[1]);
29669
+ return new InitSummary(bare, path46, false, result[1]);
29573
29670
  }
29574
29671
  if (result = reInitResponseRegex.exec(response)) {
29575
- return new InitSummary(bare, path43, true, result[1]);
29672
+ return new InitSummary(bare, path46, true, result[1]);
29576
29673
  }
29577
29674
  let gitDir = "";
29578
29675
  const tokens = response.split(" ");
@@ -29583,7 +29680,7 @@ function parseInit(bare, path43, text) {
29583
29680
  break;
29584
29681
  }
29585
29682
  }
29586
- return new InitSummary(bare, path43, /^re/i.test(response), gitDir);
29683
+ return new InitSummary(bare, path46, /^re/i.test(response), gitDir);
29587
29684
  }
29588
29685
  var InitSummary;
29589
29686
  var initResponseRegex;
@@ -29592,9 +29689,9 @@ var init_InitSummary = __esm2({
29592
29689
  "src/lib/responses/InitSummary.ts"() {
29593
29690
  "use strict";
29594
29691
  InitSummary = class {
29595
- constructor(bare, path43, existing, gitDir) {
29692
+ constructor(bare, path46, existing, gitDir) {
29596
29693
  this.bare = bare;
29597
- this.path = path43;
29694
+ this.path = path46;
29598
29695
  this.existing = existing;
29599
29696
  this.gitDir = gitDir;
29600
29697
  }
@@ -29606,7 +29703,7 @@ var init_InitSummary = __esm2({
29606
29703
  function hasBareCommand(command) {
29607
29704
  return command.includes(bareCommand);
29608
29705
  }
29609
- function initTask(bare = false, path43, customArgs) {
29706
+ function initTask(bare = false, path46, customArgs) {
29610
29707
  const commands = ["init", ...customArgs];
29611
29708
  if (bare && !hasBareCommand(commands)) {
29612
29709
  commands.splice(1, 0, bareCommand);
@@ -29615,7 +29712,7 @@ function initTask(bare = false, path43, customArgs) {
29615
29712
  commands,
29616
29713
  format: "utf-8",
29617
29714
  parser(text) {
29618
- return parseInit(commands.includes("--bare"), path43, text);
29715
+ return parseInit(commands.includes("--bare"), path46, text);
29619
29716
  }
29620
29717
  };
29621
29718
  }
@@ -30431,12 +30528,12 @@ var init_FileStatusSummary = __esm2({
30431
30528
  "use strict";
30432
30529
  fromPathRegex = /^(.+)\0(.+)$/;
30433
30530
  FileStatusSummary = class {
30434
- constructor(path43, index, working_dir) {
30435
- this.path = path43;
30531
+ constructor(path46, index, working_dir) {
30532
+ this.path = path46;
30436
30533
  this.index = index;
30437
30534
  this.working_dir = working_dir;
30438
30535
  if (index === "R" || working_dir === "R") {
30439
- const detail = fromPathRegex.exec(path43) || [null, path43, path43];
30536
+ const detail = fromPathRegex.exec(path46) || [null, path46, path46];
30440
30537
  this.from = detail[2] || "";
30441
30538
  this.path = detail[1] || "";
30442
30539
  }
@@ -30467,14 +30564,14 @@ function splitLine(result, lineStr) {
30467
30564
  default:
30468
30565
  return;
30469
30566
  }
30470
- function data(index, workingDir, path43) {
30567
+ function data(index, workingDir, path46) {
30471
30568
  const raw = `${index}${workingDir}`;
30472
30569
  const handler = parsers6.get(raw);
30473
30570
  if (handler) {
30474
- handler(result, path43);
30571
+ handler(result, path46);
30475
30572
  }
30476
30573
  if (raw !== "##" && raw !== "!!") {
30477
- result.files.push(new FileStatusSummary(path43, index, workingDir));
30574
+ result.files.push(new FileStatusSummary(path46, index, workingDir));
30478
30575
  }
30479
30576
  }
30480
30577
  }
@@ -30783,9 +30880,9 @@ var init_simple_git_api = __esm2({
30783
30880
  next
30784
30881
  );
30785
30882
  }
30786
- hashObject(path43, write) {
30883
+ hashObject(path46, write) {
30787
30884
  return this._runTask(
30788
- hashObjectTask(path43, write === true),
30885
+ hashObjectTask(path46, write === true),
30789
30886
  trailingFunctionArgument(arguments)
30790
30887
  );
30791
30888
  }
@@ -31138,8 +31235,8 @@ var init_branch = __esm2({
31138
31235
  }
31139
31236
  });
31140
31237
  function toPath(input) {
31141
- const path43 = input.trim().replace(/^["']|["']$/g, "");
31142
- return path43 && normalize(path43);
31238
+ const path46 = input.trim().replace(/^["']|["']$/g, "");
31239
+ return path46 && normalize(path46);
31143
31240
  }
31144
31241
  var parseCheckIgnore;
31145
31242
  var init_CheckIgnore = __esm2({
@@ -31453,8 +31550,8 @@ __export2(sub_module_exports, {
31453
31550
  subModuleTask: () => subModuleTask,
31454
31551
  updateSubModuleTask: () => updateSubModuleTask
31455
31552
  });
31456
- function addSubModuleTask(repo, path43) {
31457
- return subModuleTask(["add", repo, path43]);
31553
+ function addSubModuleTask(repo, path46) {
31554
+ return subModuleTask(["add", repo, path46]);
31458
31555
  }
31459
31556
  function initSubModuleTask(customArgs) {
31460
31557
  return subModuleTask(["init", ...customArgs]);
@@ -31787,8 +31884,8 @@ var require_git = __commonJS2({
31787
31884
  }
31788
31885
  return this._runTask(straightThroughStringTask2(command, this._trimmed), next);
31789
31886
  };
31790
- Git2.prototype.submoduleAdd = function(repo, path43, then) {
31791
- return this._runTask(addSubModuleTask2(repo, path43), trailingFunctionArgument2(arguments));
31887
+ Git2.prototype.submoduleAdd = function(repo, path46, then) {
31888
+ return this._runTask(addSubModuleTask2(repo, path46), trailingFunctionArgument2(arguments));
31792
31889
  };
31793
31890
  Git2.prototype.submoduleUpdate = function(args, then) {
31794
31891
  return this._runTask(
@@ -32425,8 +32522,8 @@ async function runGitTask(fn) {
32425
32522
  }
32426
32523
  async function yieldToEventLoop2() {
32427
32524
  throwIfGitShutdownRequested();
32428
- await new Promise((resolve20) => {
32429
- setImmediate(resolve20);
32525
+ await new Promise((resolve22) => {
32526
+ setImmediate(resolve22);
32430
32527
  });
32431
32528
  throwIfGitShutdownRequested();
32432
32529
  }
@@ -32751,9 +32848,9 @@ async function collectTurnGitDiffFromPreTurnSnapshot(options) {
32751
32848
  // src/agents/acp/put-summarize-change-summaries.ts
32752
32849
  async function putEncryptedChangeSummaryRows(params) {
32753
32850
  const base = params.apiBaseUrl.replace(/\/+$/, "");
32754
- const entries = params.rows.map(({ path: path43, summary }) => {
32851
+ const entries = params.rows.map(({ path: path46, summary }) => {
32755
32852
  const enc = params.e2ee.encryptFields({ summary }, ["summary"]);
32756
- return { path: path43, summary: JSON.stringify(enc) };
32853
+ return { path: path46, summary: JSON.stringify(enc) };
32757
32854
  });
32758
32855
  const res = await fetch(
32759
32856
  `${base}/api/sessions/${encodeURIComponent(params.sessionId)}/follow-ups/summarize-changes`,
@@ -33756,11 +33853,11 @@ async function sendAcpPromptViaTransport(transport, ctx, sessionId, promptText,
33756
33853
  // src/agents/acp/clients/sdk/sdk-stdio-permission-request-handshake.ts
33757
33854
  function awaitSdkStdioPermissionRequestHandshake(params) {
33758
33855
  const { requestId, paramsRecord, pending, onRequest } = params;
33759
- return new Promise((resolve20) => {
33760
- pending.set(requestId, { resolve: resolve20, params: paramsRecord });
33856
+ return new Promise((resolve22) => {
33857
+ pending.set(requestId, { resolve: resolve22, params: paramsRecord });
33761
33858
  if (onRequest == null) {
33762
33859
  pending.delete(requestId);
33763
- resolve20({ outcome: { outcome: "denied" } });
33860
+ resolve22({ outcome: { outcome: "denied" } });
33764
33861
  return;
33765
33862
  }
33766
33863
  try {
@@ -33826,7 +33923,7 @@ async function createSdkStdioAcpClient(options) {
33826
33923
  child.once("close", (code, signal) => {
33827
33924
  onAgentSubprocessExit?.({ code, signal });
33828
33925
  });
33829
- return new Promise((resolve20, reject) => {
33926
+ return new Promise((resolve22, reject) => {
33830
33927
  let initSettled = false;
33831
33928
  const settleReject = (err) => {
33832
33929
  if (initSettled) return;
@@ -33840,7 +33937,7 @@ async function createSdkStdioAcpClient(options) {
33840
33937
  const settleResolve = (handle) => {
33841
33938
  if (initSettled) return;
33842
33939
  initSettled = true;
33843
- resolve20(handle);
33940
+ resolve22(handle);
33844
33941
  };
33845
33942
  child.on("error", (err) => {
33846
33943
  settleReject(new Error(formatSpawnError(err, command[0])));
@@ -34131,7 +34228,7 @@ async function createCursorAcpClient(options) {
34131
34228
  logDebug,
34132
34229
  getStderrText: () => stderrCapture.getText()
34133
34230
  };
34134
- return new Promise((resolve20, reject) => {
34231
+ return new Promise((resolve22, reject) => {
34135
34232
  child.on("error", (err) => {
34136
34233
  child.kill();
34137
34234
  reject(new Error(formatSpawnError2(err, command[0])));
@@ -34307,7 +34404,7 @@ async function createCursorAcpClient(options) {
34307
34404
  clientInfo: { name: "buildautomaton-cli", version: "0.1.0" }
34308
34405
  });
34309
34406
  const sessionId = established.sessionId;
34310
- resolve20({
34407
+ resolve22({
34311
34408
  sessionId,
34312
34409
  async sendPrompt(prompt, options2) {
34313
34410
  const imgs = options2?.images?.map((im) => ({ type: "image", mimeType: im.mimeType, data: im.dataBase64 }));
@@ -35553,266 +35650,65 @@ async function createAcpManager(options) {
35553
35650
  };
35554
35651
  }
35555
35652
 
35556
- // src/worktrees/session-worktree-manager.ts
35557
- import * as path26 from "node:path";
35558
- import os8 from "node:os";
35559
-
35560
- // src/worktrees/prepare-new-session-worktrees.ts
35561
- import * as fs18 from "node:fs";
35562
- import * as path21 from "node:path";
35563
-
35564
- // src/git/worktrees/worktree-add.ts
35565
- async function gitWorktreeAddBranch(mainRepoPath, worktreePath, branch) {
35566
- const mainGit = cliSimpleGit(mainRepoPath);
35567
- await mainGit.raw(["worktree", "add", "-b", branch, worktreePath, "HEAD"]);
35568
- }
35569
-
35570
- // src/worktrees/worktree-layout-file.ts
35571
- import * as fs17 from "node:fs";
35572
- import * as path20 from "node:path";
35573
- import os7 from "node:os";
35574
- var LAYOUT_FILENAME = "worktree-launcher-layout.json";
35575
- function defaultWorktreeLayoutPath() {
35576
- return path20.join(os7.homedir(), ".buildautomaton", LAYOUT_FILENAME);
35577
- }
35578
- function normalizeLoadedLayout(raw) {
35579
- if (raw && typeof raw === "object" && "launcherCwds" in raw) {
35580
- const j = raw;
35581
- if (Array.isArray(j.launcherCwds)) return { launcherCwds: j.launcherCwds };
35582
- }
35583
- return { launcherCwds: [] };
35584
- }
35585
- function loadWorktreeLayout() {
35586
- try {
35587
- const p = defaultWorktreeLayoutPath();
35588
- if (!fs17.existsSync(p)) return { launcherCwds: [] };
35589
- const raw = JSON.parse(fs17.readFileSync(p, "utf8"));
35590
- return normalizeLoadedLayout(raw);
35591
- } catch {
35592
- return { launcherCwds: [] };
35593
- }
35594
- }
35595
- function saveWorktreeLayout(layout) {
35596
- try {
35597
- const dir = path20.dirname(defaultWorktreeLayoutPath());
35598
- fs17.mkdirSync(dir, { recursive: true });
35599
- fs17.writeFileSync(defaultWorktreeLayoutPath(), JSON.stringify(layout, null, 2), "utf8");
35600
- } catch {
35601
- }
35602
- }
35603
- function baseNameSafe(pathString) {
35604
- return path20.basename(pathString).replace(/[^a-zA-Z0-9._-]+/g, "-") || "cwd";
35605
- }
35606
- function getLauncherDirNameIfPresent(layout, bridgeRootPath2) {
35607
- const norm = path20.resolve(bridgeRootPath2);
35608
- const existing = layout.launcherCwds.find((e) => path20.resolve(e.absolutePath) === norm);
35609
- return existing?.dirName;
35610
- }
35611
- function allocateDirNameForLauncherCwd(layout, bridgeRootPath2) {
35612
- const existing = getLauncherDirNameIfPresent(layout, bridgeRootPath2);
35613
- if (existing) return existing;
35614
- const norm = path20.resolve(bridgeRootPath2);
35615
- const base = baseNameSafe(norm);
35616
- const used = new Set(layout.launcherCwds.map((e) => e.dirName));
35617
- let name = base;
35618
- let n = 2;
35619
- while (used.has(name)) {
35620
- name = `${base}-${n}`;
35621
- n += 1;
35622
- }
35623
- layout.launcherCwds.push({ absolutePath: norm, dirName: name });
35624
- saveWorktreeLayout(layout);
35625
- return name;
35626
- }
35627
-
35628
- // src/worktrees/prepare-new-session-worktrees.ts
35629
- async function prepareNewSessionWorktrees(options) {
35630
- const { worktreesRootPath, bridgeRoot, sessionId, layout, log: log2 } = options;
35631
- const bridgeResolved = path21.resolve(bridgeRoot);
35632
- const cwdKey = allocateDirNameForLauncherCwd(layout, bridgeResolved);
35633
- const bridgeKeyDir = path21.join(worktreesRootPath, cwdKey);
35634
- const sessionDir = path21.join(bridgeKeyDir, sessionId);
35635
- const repos = await discoverGitReposUnderRoot(bridgeResolved);
35636
- if (repos.length === 0) {
35637
- log2("[worktrees] No Git repositories under bridge root; skipping worktree creation.");
35638
- return null;
35639
- }
35640
- const branch = `session-${sessionId}`;
35641
- const worktreePaths = [];
35642
- fs18.mkdirSync(sessionDir, { recursive: true });
35643
- for (const repo of repos) {
35644
- let rel = path21.relative(bridgeResolved, repo.absolutePath);
35645
- if (rel.startsWith("..") || path21.isAbsolute(rel)) continue;
35646
- const relNorm = rel === "" ? "." : rel;
35647
- const wtPath = relNorm === "." ? sessionDir : path21.join(sessionDir, relNorm);
35648
- if (relNorm !== ".") {
35649
- fs18.mkdirSync(path21.dirname(wtPath), { recursive: true });
35650
- }
35651
- try {
35652
- await gitWorktreeAddBranch(repo.absolutePath, wtPath, branch);
35653
- log2(`[worktrees] Added worktree ${wtPath} (branch ${branch}).`);
35654
- worktreePaths.push(wtPath);
35655
- } catch (e) {
35656
- log2(
35657
- `[worktrees] Worktree add failed for ${repo.absolutePath}: ${e instanceof Error ? e.message : String(e)}`
35658
- );
35659
- }
35660
- }
35661
- if (worktreePaths.length === 0) return null;
35662
- return {
35663
- worktreePaths,
35664
- sessionParentPath: sessionDir,
35665
- workingTreeRelRoot: sessionDir
35666
- };
35667
- }
35668
-
35669
- // src/git/branches/rename-branch.ts
35670
- async function gitRenameCurrentBranch(repoDir, newName) {
35671
- const g = cliSimpleGit(repoDir);
35672
- await g.raw(["branch", "-m", newName]);
35673
- }
35674
-
35675
- // src/worktrees/rename-session-worktree-branches.ts
35676
- async function renameSessionWorktreeBranches(paths, newBranch, log2) {
35677
- const safe = newBranch.replace(/[^a-zA-Z0-9/_-]+/g, "-").slice(0, 80) || "session-branch";
35678
- for (const wt of paths) {
35679
- try {
35680
- await gitRenameCurrentBranch(wt, safe);
35681
- log2(`[worktrees] Renamed branch in ${wt} \u2192 ${safe}`);
35682
- } catch (e) {
35683
- log2(
35684
- `[worktrees] Branch rename failed in ${wt}: ${e instanceof Error ? e.message : String(e)}`
35685
- );
35686
- }
35687
- }
35688
- }
35689
-
35690
- // src/worktrees/remove-session-worktrees.ts
35691
- import * as fs21 from "node:fs";
35692
-
35693
- // src/git/worktrees/worktree-remove.ts
35694
- import * as fs20 from "node:fs";
35695
-
35696
- // src/git/worktrees/resolve-main-repo-from-git-file.ts
35697
- import * as fs19 from "node:fs";
35698
- import * as path22 from "node:path";
35699
- function resolveMainRepoFromWorktreeGitFile(wt) {
35700
- const gitDirFile = path22.join(wt, ".git");
35701
- if (!fs19.existsSync(gitDirFile) || !fs19.statSync(gitDirFile).isFile()) return "";
35702
- const first2 = fs19.readFileSync(gitDirFile, "utf8").trim();
35703
- const m = first2.match(/^gitdir:\s*(.+)$/im);
35704
- if (!m) return "";
35705
- const gitWorktreePath = path22.resolve(wt, m[1].trim());
35706
- const gitDir = path22.dirname(path22.dirname(gitWorktreePath));
35707
- return path22.dirname(gitDir);
35708
- }
35653
+ // src/git/changes/types.ts
35654
+ var MAX_PATCH_CHARS = 35e4;
35709
35655
 
35710
- // src/git/worktrees/worktree-remove.ts
35711
- async function gitWorktreeRemoveForce(worktreePath) {
35712
- const mainRepo = resolveMainRepoFromWorktreeGitFile(worktreePath);
35713
- if (mainRepo) {
35714
- await cliSimpleGit(mainRepo).raw(["worktree", "remove", "--force", worktreePath]);
35715
- } else {
35716
- fs20.rmSync(worktreePath, { recursive: true, force: true });
35717
- }
35656
+ // src/git/changes/lib/repo-format.ts
35657
+ function posixJoinDirFile(dir, file2) {
35658
+ const d = dir === "." || dir === "" ? "" : dir.replace(/\\/g, "/").replace(/\/+$/, "");
35659
+ const f = file2.replace(/\\/g, "/").replace(/^\/+/, "");
35660
+ return d ? `${d}/${f}` : f;
35718
35661
  }
35719
-
35720
- // src/worktrees/remove-session-worktrees.ts
35721
- async function removeSessionWorktrees(paths, log2) {
35722
- for (const wt of paths) {
35662
+ function formatRepoShortTitle(remoteUrl, repoRelPath) {
35663
+ const u = remoteUrl.trim();
35664
+ if (u) {
35723
35665
  try {
35724
- await gitWorktreeRemoveForce(wt);
35725
- log2(`[worktrees] Removed worktree ${wt}`);
35726
- } catch (e) {
35727
- log2(`[worktrees] Remove failed for ${wt}: ${e instanceof Error ? e.message : String(e)}`);
35728
- try {
35729
- fs21.rmSync(wt, { recursive: true, force: true });
35730
- } catch {
35666
+ if (u.startsWith("git@")) {
35667
+ const colon = u.indexOf(":");
35668
+ if (colon > 0) {
35669
+ const pathPart = u.slice(colon + 1).replace(/\.git$/i, "").replace(/\/+$/, "");
35670
+ if (pathPart.includes("/")) return pathPart;
35671
+ }
35672
+ } else {
35673
+ const parsed = new URL(u);
35674
+ const p = parsed.pathname.replace(/^\//, "").replace(/\.git$/i, "");
35675
+ const parts = p.split("/").filter(Boolean);
35676
+ if (parts.length >= 2) {
35677
+ return `${parts[parts.length - 2]}/${parts[parts.length - 1]}`;
35678
+ }
35679
+ if (parts.length === 1) return parts[0];
35731
35680
  }
35681
+ } catch {
35732
35682
  }
35733
35683
  }
35734
- }
35735
-
35736
- // src/git/changes/lib/parse-git-status.ts
35737
- function parseNameStatusLines(lines) {
35738
- const m = /* @__PURE__ */ new Map();
35739
- for (const line of lines) {
35740
- if (!line.trim()) continue;
35741
- const tabParts = line.split(" ");
35742
- if (tabParts.length < 2) continue;
35743
- const status = tabParts[0].trim();
35744
- const code = status[0];
35745
- if (code === "A") {
35746
- m.set(tabParts[tabParts.length - 1], "added");
35747
- } else if (code === "D") {
35748
- m.set(tabParts[tabParts.length - 1], "removed");
35749
- } else if (code === "R" || code === "C") {
35750
- if (tabParts.length >= 3) m.set(tabParts[tabParts.length - 1], "modified");
35751
- } else if (code === "M" || code === "U" || code === "T") {
35752
- m.set(tabParts[tabParts.length - 1], "modified");
35753
- }
35754
- }
35755
- return m;
35756
- }
35757
- function parseNumstatFirstLine(line) {
35758
- const parts = line.split(" ");
35759
- if (parts.length < 3) return null;
35760
- const [a, d] = parts;
35761
- const additions = a === "-" ? 0 : parseInt(String(a), 10) || 0;
35762
- const deletions = d === "-" ? 0 : parseInt(String(d), 10) || 0;
35763
- return { additions, deletions };
35764
- }
35765
- function parseNumstat(lines) {
35766
- const m = /* @__PURE__ */ new Map();
35767
- for (const line of lines) {
35768
- if (!line.trim()) continue;
35769
- const parts = line.split(" ");
35770
- if (parts.length < 3) continue;
35771
- const [a, d, p] = parts;
35772
- const additions = a === "-" ? 0 : parseInt(String(a), 10) || 0;
35773
- const deletions = d === "-" ? 0 : parseInt(String(d), 10) || 0;
35774
- m.set(p, { additions, deletions });
35684
+ if (repoRelPath && repoRelPath !== ".") {
35685
+ const segments = repoRelPath.split("/").filter(Boolean);
35686
+ const last2 = segments[segments.length - 1];
35687
+ if (last2) return last2;
35775
35688
  }
35776
- return m;
35689
+ return "Repository";
35777
35690
  }
35778
- async function numstatFromGitNoIndex(g, pathInRepo) {
35779
- const devNull = process.platform === "win32" ? "NUL" : "/dev/null";
35691
+ function formatRemoteDisplayLabel(remoteUrl) {
35692
+ const u = remoteUrl.trim();
35693
+ if (!u) return "";
35694
+ let hostPath = u;
35780
35695
  try {
35781
- const out = await g.raw(["diff", "--numstat", "--no-index", "--", devNull, pathInRepo]);
35782
- const first2 = String(out).split("\n").find((l) => l.trim()) ?? "";
35783
- return parseNumstatFirstLine(first2);
35696
+ if (u.startsWith("git@")) {
35697
+ const rest = u.slice("git@".length);
35698
+ const slash = rest.indexOf(":");
35699
+ if (slash > 0) hostPath = `${rest.slice(0, slash)}/${rest.slice(slash + 1)}`;
35700
+ } else {
35701
+ const parsed = new URL(u);
35702
+ hostPath = `${parsed.hostname}${parsed.pathname}`.replace(/\/\.git$/i, "").replace(/\.git$/i, "");
35703
+ }
35784
35704
  } catch {
35785
- return null;
35786
- }
35787
- }
35788
-
35789
- // src/git/changes/lib/working-tree-changed-path-count.ts
35790
- function workingTreeChangedPathCount(nameStatusLines, numstatLines, untrackedLines) {
35791
- const kindByPath = parseNameStatusLines(nameStatusLines);
35792
- const numByPath = parseNumstat(numstatLines);
35793
- const paths = /* @__PURE__ */ new Set([...kindByPath.keys(), ...numByPath.keys()]);
35794
- for (const p of untrackedLines.map((s) => s.trim()).filter(Boolean)) {
35795
- paths.add(p);
35705
+ hostPath = u.replace(/^https?:\/\//i, "").replace(/\.git$/i, "");
35796
35706
  }
35797
- return paths.size;
35707
+ return `origin \xB7 ${hostPath}`;
35798
35708
  }
35799
35709
 
35800
- // src/git/changes/count-working-tree-changed-files.ts
35801
- async function countWorkingTreeChangedFilesForRepo(repoGitCwd) {
35802
- return runGitTask(async () => {
35803
- const g = cliSimpleGit(repoGitCwd);
35804
- const [nameStatusRaw, numstatRaw, untrackedRaw] = await Promise.all([
35805
- g.raw(["diff", "--name-status", "HEAD"]).catch(() => ""),
35806
- g.raw(["diff", "HEAD", "--numstat"]).catch(() => ""),
35807
- g.raw(["ls-files", "--others", "--exclude-standard"]).catch(() => "")
35808
- ]);
35809
- return workingTreeChangedPathCount(
35810
- String(nameStatusRaw).split("\n"),
35811
- String(numstatRaw).split("\n"),
35812
- String(untrackedRaw).split("\n")
35813
- );
35814
- });
35815
- }
35710
+ // src/git/changes/get-working-tree-change-repo-details.ts
35711
+ import * as path21 from "node:path";
35816
35712
 
35817
35713
  // src/git/commits/resolve-remote-tracking.ts
35818
35714
  async function tryConfigGet(g, key) {
@@ -35912,96 +35808,6 @@ async function commitsAheadOfRemoteTracking(repoDir) {
35912
35808
  }
35913
35809
  }
35914
35810
 
35915
- // src/git/status/working-tree-status.ts
35916
- async function getRepoWorkingTreeStatus(repoDir) {
35917
- return runGitTask(async () => {
35918
- const uncommittedFileCount = await countWorkingTreeChangedFilesForRepo(repoDir);
35919
- const hasUncommittedChanges = uncommittedFileCount > 0;
35920
- const ahead = await commitsAheadOfRemoteTracking(repoDir);
35921
- return { hasUncommittedChanges, hasUnpushedCommits: ahead > 0, uncommittedFileCount };
35922
- });
35923
- }
35924
- async function aggregateSessionPathsWorkingTreeStatus(paths) {
35925
- let hasUncommittedChanges = false;
35926
- let hasUnpushedCommits = false;
35927
- let uncommittedFileCount = 0;
35928
- await forEachWithGitYield(paths, async (p) => {
35929
- const s = await getRepoWorkingTreeStatus(p);
35930
- uncommittedFileCount += s.uncommittedFileCount;
35931
- if (s.hasUncommittedChanges) hasUncommittedChanges = true;
35932
- if (s.hasUnpushedCommits) hasUnpushedCommits = true;
35933
- });
35934
- return { hasUncommittedChanges, hasUnpushedCommits, uncommittedFileCount };
35935
- }
35936
- async function pushAheadOfUpstreamForPaths(paths) {
35937
- await forEachWithGitYield(paths, async (p) => {
35938
- const g = cliSimpleGit(p);
35939
- const ahead = await commitsAheadOfRemoteTracking(p);
35940
- if (ahead <= 0) return;
35941
- await g.push();
35942
- });
35943
- }
35944
-
35945
- // src/git/changes/types.ts
35946
- var MAX_PATCH_CHARS = 35e4;
35947
-
35948
- // src/git/changes/lib/repo-format.ts
35949
- function posixJoinDirFile(dir, file2) {
35950
- const d = dir === "." || dir === "" ? "" : dir.replace(/\\/g, "/").replace(/\/+$/, "");
35951
- const f = file2.replace(/\\/g, "/").replace(/^\/+/, "");
35952
- return d ? `${d}/${f}` : f;
35953
- }
35954
- function formatRepoShortTitle(remoteUrl, repoRelPath) {
35955
- const u = remoteUrl.trim();
35956
- if (u) {
35957
- try {
35958
- if (u.startsWith("git@")) {
35959
- const colon = u.indexOf(":");
35960
- if (colon > 0) {
35961
- const pathPart = u.slice(colon + 1).replace(/\.git$/i, "").replace(/\/+$/, "");
35962
- if (pathPart.includes("/")) return pathPart;
35963
- }
35964
- } else {
35965
- const parsed = new URL(u);
35966
- const p = parsed.pathname.replace(/^\//, "").replace(/\.git$/i, "");
35967
- const parts = p.split("/").filter(Boolean);
35968
- if (parts.length >= 2) {
35969
- return `${parts[parts.length - 2]}/${parts[parts.length - 1]}`;
35970
- }
35971
- if (parts.length === 1) return parts[0];
35972
- }
35973
- } catch {
35974
- }
35975
- }
35976
- if (repoRelPath && repoRelPath !== ".") {
35977
- const segments = repoRelPath.split("/").filter(Boolean);
35978
- const last2 = segments[segments.length - 1];
35979
- if (last2) return last2;
35980
- }
35981
- return "Repository";
35982
- }
35983
- function formatRemoteDisplayLabel(remoteUrl) {
35984
- const u = remoteUrl.trim();
35985
- if (!u) return "";
35986
- let hostPath = u;
35987
- try {
35988
- if (u.startsWith("git@")) {
35989
- const rest = u.slice("git@".length);
35990
- const slash = rest.indexOf(":");
35991
- if (slash > 0) hostPath = `${rest.slice(0, slash)}/${rest.slice(slash + 1)}`;
35992
- } else {
35993
- const parsed = new URL(u);
35994
- hostPath = `${parsed.hostname}${parsed.pathname}`.replace(/\/\.git$/i, "").replace(/\.git$/i, "");
35995
- }
35996
- } catch {
35997
- hostPath = u.replace(/^https?:\/\//i, "").replace(/\.git$/i, "");
35998
- }
35999
- return `origin \xB7 ${hostPath}`;
36000
- }
36001
-
36002
- // src/git/changes/get-working-tree-change-repo-details.ts
36003
- import * as path24 from "node:path";
36004
-
36005
35811
  // src/git/commits/lib/parse-log-lines.ts
36006
35812
  function parseLogShaDateSubjectLines(raw) {
36007
35813
  const out = [];
@@ -36072,6 +35878,59 @@ async function listRecentCommits(repoDir, limitInput) {
36072
35878
  }
36073
35879
  }
36074
35880
 
35881
+ // src/git/changes/lib/parse-git-status.ts
35882
+ function parseNameStatusLines(lines) {
35883
+ const m = /* @__PURE__ */ new Map();
35884
+ for (const line of lines) {
35885
+ if (!line.trim()) continue;
35886
+ const tabParts = line.split(" ");
35887
+ if (tabParts.length < 2) continue;
35888
+ const status = tabParts[0].trim();
35889
+ const code = status[0];
35890
+ if (code === "A") {
35891
+ m.set(tabParts[tabParts.length - 1], "added");
35892
+ } else if (code === "D") {
35893
+ m.set(tabParts[tabParts.length - 1], "removed");
35894
+ } else if (code === "R" || code === "C") {
35895
+ if (tabParts.length >= 3) m.set(tabParts[tabParts.length - 1], "modified");
35896
+ } else if (code === "M" || code === "U" || code === "T") {
35897
+ m.set(tabParts[tabParts.length - 1], "modified");
35898
+ }
35899
+ }
35900
+ return m;
35901
+ }
35902
+ function parseNumstatFirstLine(line) {
35903
+ const parts = line.split(" ");
35904
+ if (parts.length < 3) return null;
35905
+ const [a, d] = parts;
35906
+ const additions = a === "-" ? 0 : parseInt(String(a), 10) || 0;
35907
+ const deletions = d === "-" ? 0 : parseInt(String(d), 10) || 0;
35908
+ return { additions, deletions };
35909
+ }
35910
+ function parseNumstat(lines) {
35911
+ const m = /* @__PURE__ */ new Map();
35912
+ for (const line of lines) {
35913
+ if (!line.trim()) continue;
35914
+ const parts = line.split(" ");
35915
+ if (parts.length < 3) continue;
35916
+ const [a, d, p] = parts;
35917
+ const additions = a === "-" ? 0 : parseInt(String(a), 10) || 0;
35918
+ const deletions = d === "-" ? 0 : parseInt(String(d), 10) || 0;
35919
+ m.set(p, { additions, deletions });
35920
+ }
35921
+ return m;
35922
+ }
35923
+ async function numstatFromGitNoIndex(g, pathInRepo) {
35924
+ const devNull = process.platform === "win32" ? "NUL" : "/dev/null";
35925
+ try {
35926
+ const out = await g.raw(["diff", "--numstat", "--no-index", "--", devNull, pathInRepo]);
35927
+ const first2 = String(out).split("\n").find((l) => l.trim()) ?? "";
35928
+ return parseNumstatFirstLine(first2);
35929
+ } catch {
35930
+ return null;
35931
+ }
35932
+ }
35933
+
36075
35934
  // src/git/changes/lib/patch-truncate.ts
36076
35935
  function truncatePatch(s) {
36077
35936
  if (s.length <= MAX_PATCH_CHARS) return s;
@@ -36137,8 +35996,8 @@ async function listChangedFilesForCommit(repoGitCwd, repoRelPath, commitSha) {
36137
35996
  }
36138
35997
 
36139
35998
  // src/git/changes/list-changed-files-for-repo.ts
36140
- import * as fs23 from "node:fs";
36141
- import * as path23 from "node:path";
35999
+ import * as fs18 from "node:fs";
36000
+ import * as path20 from "node:path";
36142
36001
 
36143
36002
  // src/git/changes/lib/count-lines.ts
36144
36003
  import { createReadStream } from "node:fs";
@@ -36162,7 +36021,7 @@ async function countTextFileLines(filePath) {
36162
36021
  }
36163
36022
 
36164
36023
  // src/git/changes/hydrate-patch.ts
36165
- import * as fs22 from "node:fs";
36024
+ import * as fs17 from "node:fs";
36166
36025
  var UNIFIED_HUNK_HEADER_RE = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
36167
36026
  var MAX_HYDRATE_LINES_PER_GAP = 8e3;
36168
36027
  var MAX_HYDRATE_LINES_PER_FILE = 8e4;
@@ -36177,7 +36036,7 @@ async function readGitBlobLines(repoCwd, pathInRepo) {
36177
36036
  }
36178
36037
  async function readWorktreeFileLines(filePath) {
36179
36038
  try {
36180
- const raw = await fs22.promises.readFile(filePath, "utf8");
36039
+ const raw = await fs17.promises.readFile(filePath, "utf8");
36181
36040
  return raw.split(/\r?\n/);
36182
36041
  } catch {
36183
36042
  return null;
@@ -36302,7 +36161,7 @@ async function listChangedFilesForRepo(repoGitCwd, repoRelPath) {
36302
36161
  const rows = [];
36303
36162
  await forEachWithGitYield([...paths], async (pathInRepo) => {
36304
36163
  const relLauncher = posixJoinDirFile(repoRelPath, pathInRepo.replace(/\\/g, "/"));
36305
- const repoFilePath = path23.join(repoGitCwd, pathInRepo);
36164
+ const repoFilePath = path20.join(repoGitCwd, pathInRepo);
36306
36165
  const nums = numByPath.get(pathInRepo);
36307
36166
  let additions = nums?.additions ?? 0;
36308
36167
  let deletions = nums?.deletions ?? 0;
@@ -36315,7 +36174,7 @@ async function listChangedFilesForRepo(repoGitCwd, repoRelPath) {
36315
36174
  deletions = fromGit.deletions;
36316
36175
  } else {
36317
36176
  try {
36318
- const st = await fs23.promises.stat(repoFilePath);
36177
+ const st = await fs18.promises.stat(repoFilePath);
36319
36178
  if (st.isFile()) additions = await countTextFileLines(repoFilePath);
36320
36179
  else additions = 0;
36321
36180
  } catch {
@@ -36341,7 +36200,7 @@ async function listChangedFilesForRepo(repoGitCwd, repoRelPath) {
36341
36200
  } else {
36342
36201
  pathInRepo = row.pathRelLauncher;
36343
36202
  }
36344
- const filePath = path23.join(repoGitCwd, pathInRepo);
36203
+ const filePath = path20.join(repoGitCwd, pathInRepo);
36345
36204
  let patch = await unifiedDiffForFile(repoGitCwd, pathInRepo, row.change);
36346
36205
  if (patch) {
36347
36206
  patch = await hydrateUnifiedPatchWithFileContext(patch, filePath, repoGitCwd, pathInRepo, row.change);
@@ -36357,8 +36216,8 @@ function normRepoRel(p) {
36357
36216
  return x === "" ? "." : x;
36358
36217
  }
36359
36218
  async function getWorkingTreeChangeRepoDetails(options) {
36360
- const bridgeRoot = path24.resolve(getBridgeRoot());
36361
- const sessionWtRoot = options.sessionWorktreeRootPath ? path24.resolve(options.sessionWorktreeRootPath) : null;
36219
+ const bridgeRoot = path21.resolve(getBridgeRoot());
36220
+ const sessionWtRoot = options.sessionWorktreeRootPath ? path21.resolve(options.sessionWorktreeRootPath) : null;
36362
36221
  const legacyNested = options.legacyRepoNestedSessionLayout === true;
36363
36222
  const out = [];
36364
36223
  const filter = options.repoFilterRelPath != null ? normRepoRel(options.repoFilterRelPath) : null;
@@ -36373,7 +36232,7 @@ async function getWorkingTreeChangeRepoDetails(options) {
36373
36232
  for (let i = 0; i < options.commitTargetPaths.length; i++) {
36374
36233
  if (i > 0) await yieldToEventLoop2();
36375
36234
  const target = options.commitTargetPaths[i];
36376
- const t = path24.resolve(target);
36235
+ const t = path21.resolve(target);
36377
36236
  if (!await isGitRepoDirectory(t)) continue;
36378
36237
  const g = cliSimpleGit(t);
36379
36238
  let branch = "HEAD";
@@ -36386,8 +36245,8 @@ async function getWorkingTreeChangeRepoDetails(options) {
36386
36245
  const remoteDisplay = formatRemoteDisplayLabel(remoteUrl);
36387
36246
  let repoRelPath;
36388
36247
  if (sessionWtRoot) {
36389
- const anchor = legacyNested ? path24.dirname(t) : t;
36390
- const relNorm = path24.relative(sessionWtRoot, anchor);
36248
+ const anchor = legacyNested ? path21.dirname(t) : t;
36249
+ const relNorm = path21.relative(sessionWtRoot, anchor);
36391
36250
  repoRelPath = relNorm === "" ? "." : relNorm.replace(/\\/g, "/");
36392
36251
  } else {
36393
36252
  let top = t;
@@ -36396,8 +36255,8 @@ async function getWorkingTreeChangeRepoDetails(options) {
36396
36255
  } catch {
36397
36256
  top = t;
36398
36257
  }
36399
- const rel = path24.relative(bridgeRoot, path24.resolve(top)).replace(/\\/g, "/") || ".";
36400
- repoRelPath = rel.startsWith("..") ? path24.basename(path24.resolve(top)) : rel;
36258
+ const rel = path21.relative(bridgeRoot, path21.resolve(top)).replace(/\\/g, "/") || ".";
36259
+ repoRelPath = rel.startsWith("..") ? path21.basename(path21.resolve(top)) : rel;
36401
36260
  }
36402
36261
  const norm = normRepoRel(repoRelPath === "" ? "." : repoRelPath);
36403
36262
  if (filter && norm !== filter) continue;
@@ -36429,6 +36288,64 @@ async function getWorkingTreeChangeRepoDetails(options) {
36429
36288
  return out;
36430
36289
  }
36431
36290
 
36291
+ // src/git/changes/lib/working-tree-changed-path-count.ts
36292
+ function workingTreeChangedPathCount(nameStatusLines, numstatLines, untrackedLines) {
36293
+ const kindByPath = parseNameStatusLines(nameStatusLines);
36294
+ const numByPath = parseNumstat(numstatLines);
36295
+ const paths = /* @__PURE__ */ new Set([...kindByPath.keys(), ...numByPath.keys()]);
36296
+ for (const p of untrackedLines.map((s) => s.trim()).filter(Boolean)) {
36297
+ paths.add(p);
36298
+ }
36299
+ return paths.size;
36300
+ }
36301
+
36302
+ // src/git/changes/count-working-tree-changed-files.ts
36303
+ async function countWorkingTreeChangedFilesForRepo(repoGitCwd) {
36304
+ return runGitTask(async () => {
36305
+ const g = cliSimpleGit(repoGitCwd);
36306
+ const [nameStatusRaw, numstatRaw, untrackedRaw] = await Promise.all([
36307
+ g.raw(["diff", "--name-status", "HEAD"]).catch(() => ""),
36308
+ g.raw(["diff", "HEAD", "--numstat"]).catch(() => ""),
36309
+ g.raw(["ls-files", "--others", "--exclude-standard"]).catch(() => "")
36310
+ ]);
36311
+ return workingTreeChangedPathCount(
36312
+ String(nameStatusRaw).split("\n"),
36313
+ String(numstatRaw).split("\n"),
36314
+ String(untrackedRaw).split("\n")
36315
+ );
36316
+ });
36317
+ }
36318
+
36319
+ // src/git/status/working-tree-status.ts
36320
+ async function getRepoWorkingTreeStatus(repoDir) {
36321
+ return runGitTask(async () => {
36322
+ const uncommittedFileCount = await countWorkingTreeChangedFilesForRepo(repoDir);
36323
+ const hasUncommittedChanges = uncommittedFileCount > 0;
36324
+ const ahead = await commitsAheadOfRemoteTracking(repoDir);
36325
+ return { hasUncommittedChanges, hasUnpushedCommits: ahead > 0, uncommittedFileCount };
36326
+ });
36327
+ }
36328
+ async function aggregateSessionPathsWorkingTreeStatus(paths) {
36329
+ let hasUncommittedChanges = false;
36330
+ let hasUnpushedCommits = false;
36331
+ let uncommittedFileCount = 0;
36332
+ await forEachWithGitYield(paths, async (p) => {
36333
+ const s = await getRepoWorkingTreeStatus(p);
36334
+ uncommittedFileCount += s.uncommittedFileCount;
36335
+ if (s.hasUncommittedChanges) hasUncommittedChanges = true;
36336
+ if (s.hasUnpushedCommits) hasUnpushedCommits = true;
36337
+ });
36338
+ return { hasUncommittedChanges, hasUnpushedCommits, uncommittedFileCount };
36339
+ }
36340
+ async function pushAheadOfUpstreamForPaths(paths) {
36341
+ await forEachWithGitYield(paths, async (p) => {
36342
+ const g = cliSimpleGit(p);
36343
+ const ahead = await commitsAheadOfRemoteTracking(p);
36344
+ if (ahead <= 0) return;
36345
+ await g.push();
36346
+ });
36347
+ }
36348
+
36432
36349
  // src/git/branches/commit-and-push.ts
36433
36350
  async function gitCommitAllIfDirty(repoDir, message, options) {
36434
36351
  const g = cliSimpleGit(repoDir);
@@ -36466,12 +36383,137 @@ async function commitSessionWorktrees(options) {
36466
36383
  }
36467
36384
  }
36468
36385
 
36386
+ // src/worktrees/remove-session-worktrees.ts
36387
+ import * as fs21 from "node:fs";
36388
+
36389
+ // src/git/worktrees/worktree-remove.ts
36390
+ import * as fs20 from "node:fs";
36391
+
36392
+ // src/git/worktrees/resolve-main-repo-from-git-file.ts
36393
+ import * as fs19 from "node:fs";
36394
+ import * as path22 from "node:path";
36395
+ function resolveMainRepoFromWorktreeGitFile(wt) {
36396
+ const gitDirFile = path22.join(wt, ".git");
36397
+ if (!fs19.existsSync(gitDirFile) || !fs19.statSync(gitDirFile).isFile()) return "";
36398
+ const first2 = fs19.readFileSync(gitDirFile, "utf8").trim();
36399
+ const m = first2.match(/^gitdir:\s*(.+)$/im);
36400
+ if (!m) return "";
36401
+ const gitWorktreePath = path22.resolve(wt, m[1].trim());
36402
+ const gitDir = path22.dirname(path22.dirname(gitWorktreePath));
36403
+ return path22.dirname(gitDir);
36404
+ }
36405
+
36406
+ // src/git/worktrees/worktree-remove.ts
36407
+ async function gitWorktreeRemoveForce(worktreePath) {
36408
+ const mainRepo = resolveMainRepoFromWorktreeGitFile(worktreePath);
36409
+ if (mainRepo) {
36410
+ await cliSimpleGit(mainRepo).raw(["worktree", "remove", "--force", worktreePath]);
36411
+ } else {
36412
+ fs20.rmSync(worktreePath, { recursive: true, force: true });
36413
+ }
36414
+ }
36415
+
36416
+ // src/worktrees/remove-session-worktrees.ts
36417
+ async function removeSessionWorktrees(paths, log2) {
36418
+ for (const wt of paths) {
36419
+ try {
36420
+ await gitWorktreeRemoveForce(wt);
36421
+ log2(`[worktrees] Removed worktree ${wt}`);
36422
+ } catch (e) {
36423
+ log2(`[worktrees] Remove failed for ${wt}: ${e instanceof Error ? e.message : String(e)}`);
36424
+ try {
36425
+ fs21.rmSync(wt, { recursive: true, force: true });
36426
+ } catch {
36427
+ }
36428
+ }
36429
+ }
36430
+ }
36431
+
36432
+ // src/git/branches/rename-branch.ts
36433
+ async function gitRenameCurrentBranch(repoDir, newName) {
36434
+ const g = cliSimpleGit(repoDir);
36435
+ await g.raw(["branch", "-m", newName]);
36436
+ }
36437
+
36438
+ // src/worktrees/rename-session-worktree-branches.ts
36439
+ async function renameSessionWorktreeBranches(paths, newBranch, log2) {
36440
+ const safe = newBranch.replace(/[^a-zA-Z0-9/_-]+/g, "-").slice(0, 80) || "session-branch";
36441
+ for (const wt of paths) {
36442
+ try {
36443
+ await gitRenameCurrentBranch(wt, safe);
36444
+ log2(`[worktrees] Renamed branch in ${wt} \u2192 ${safe}`);
36445
+ } catch (e) {
36446
+ log2(
36447
+ `[worktrees] Branch rename failed in ${wt}: ${e instanceof Error ? e.message : String(e)}`
36448
+ );
36449
+ }
36450
+ }
36451
+ }
36452
+
36453
+ // src/worktrees/worktree-layout-file.ts
36454
+ import * as fs22 from "node:fs";
36455
+ import * as path23 from "node:path";
36456
+ import os7 from "node:os";
36457
+ var LAYOUT_FILENAME = "worktree-launcher-layout.json";
36458
+ function defaultWorktreeLayoutPath() {
36459
+ return path23.join(os7.homedir(), ".buildautomaton", LAYOUT_FILENAME);
36460
+ }
36461
+ function normalizeLoadedLayout(raw) {
36462
+ if (raw && typeof raw === "object" && "launcherCwds" in raw) {
36463
+ const j = raw;
36464
+ if (Array.isArray(j.launcherCwds)) return { launcherCwds: j.launcherCwds };
36465
+ }
36466
+ return { launcherCwds: [] };
36467
+ }
36468
+ function loadWorktreeLayout() {
36469
+ try {
36470
+ const p = defaultWorktreeLayoutPath();
36471
+ if (!fs22.existsSync(p)) return { launcherCwds: [] };
36472
+ const raw = JSON.parse(fs22.readFileSync(p, "utf8"));
36473
+ return normalizeLoadedLayout(raw);
36474
+ } catch {
36475
+ return { launcherCwds: [] };
36476
+ }
36477
+ }
36478
+ function saveWorktreeLayout(layout) {
36479
+ try {
36480
+ const dir = path23.dirname(defaultWorktreeLayoutPath());
36481
+ fs22.mkdirSync(dir, { recursive: true });
36482
+ fs22.writeFileSync(defaultWorktreeLayoutPath(), JSON.stringify(layout, null, 2), "utf8");
36483
+ } catch {
36484
+ }
36485
+ }
36486
+ function baseNameSafe(pathString) {
36487
+ return path23.basename(pathString).replace(/[^a-zA-Z0-9._-]+/g, "-") || "cwd";
36488
+ }
36489
+ function getLauncherDirNameIfPresent(layout, bridgeRootPath2) {
36490
+ const norm = path23.resolve(bridgeRootPath2);
36491
+ const existing = layout.launcherCwds.find((e) => path23.resolve(e.absolutePath) === norm);
36492
+ return existing?.dirName;
36493
+ }
36494
+ function allocateDirNameForLauncherCwd(layout, bridgeRootPath2) {
36495
+ const existing = getLauncherDirNameIfPresent(layout, bridgeRootPath2);
36496
+ if (existing) return existing;
36497
+ const norm = path23.resolve(bridgeRootPath2);
36498
+ const base = baseNameSafe(norm);
36499
+ const used = new Set(layout.launcherCwds.map((e) => e.dirName));
36500
+ let name = base;
36501
+ let n = 2;
36502
+ while (used.has(name)) {
36503
+ name = `${base}-${n}`;
36504
+ n += 1;
36505
+ }
36506
+ layout.launcherCwds.push({ absolutePath: norm, dirName: name });
36507
+ saveWorktreeLayout(layout);
36508
+ return name;
36509
+ }
36510
+
36469
36511
  // src/worktrees/discover-session-worktree-on-disk.ts
36470
- import * as fs24 from "node:fs";
36471
- import * as path25 from "node:path";
36512
+ import * as fs23 from "node:fs";
36513
+ import * as path24 from "node:path";
36472
36514
  function isGitDir(dirPath) {
36473
36515
  try {
36474
- return fs24.existsSync(path25.join(dirPath, ".git"));
36516
+ return fs23.existsSync(path24.join(dirPath, ".git"));
36475
36517
  } catch {
36476
36518
  return false;
36477
36519
  }
@@ -36480,23 +36522,23 @@ function collectGitRepoRootsUnderDirectory(rootPath) {
36480
36522
  const out = [];
36481
36523
  const walk = (dir) => {
36482
36524
  if (isGitDir(dir)) {
36483
- out.push(path25.resolve(dir));
36525
+ out.push(path24.resolve(dir));
36484
36526
  return;
36485
36527
  }
36486
36528
  let entries;
36487
36529
  try {
36488
- entries = fs24.readdirSync(dir, { withFileTypes: true });
36530
+ entries = fs23.readdirSync(dir, { withFileTypes: true });
36489
36531
  } catch {
36490
36532
  return;
36491
36533
  }
36492
36534
  for (const e of entries) {
36493
36535
  if (e.name.startsWith(".")) continue;
36494
- const full = path25.join(dir, e.name);
36536
+ const full = path24.join(dir, e.name);
36495
36537
  if (!e.isDirectory()) continue;
36496
36538
  walk(full);
36497
36539
  }
36498
36540
  };
36499
- walk(path25.resolve(rootPath));
36541
+ walk(path24.resolve(rootPath));
36500
36542
  return [...new Set(out)];
36501
36543
  }
36502
36544
  function collectWorktreeRootsNamed(root, sessionId, maxDepth) {
@@ -36505,16 +36547,16 @@ function collectWorktreeRootsNamed(root, sessionId, maxDepth) {
36505
36547
  if (depth > maxDepth) return;
36506
36548
  let entries;
36507
36549
  try {
36508
- entries = fs24.readdirSync(dir, { withFileTypes: true });
36550
+ entries = fs23.readdirSync(dir, { withFileTypes: true });
36509
36551
  } catch {
36510
36552
  return;
36511
36553
  }
36512
36554
  for (const e of entries) {
36513
36555
  if (e.name.startsWith(".")) continue;
36514
- const full = path25.join(dir, e.name);
36556
+ const full = path24.join(dir, e.name);
36515
36557
  if (!e.isDirectory()) continue;
36516
36558
  if (e.name === sessionId) {
36517
- if (isGitDir(full)) out.push(path25.resolve(full));
36559
+ if (isGitDir(full)) out.push(path24.resolve(full));
36518
36560
  } else {
36519
36561
  walk(full, depth + 1);
36520
36562
  }
@@ -36526,14 +36568,14 @@ function collectWorktreeRootsNamed(root, sessionId, maxDepth) {
36526
36568
  function tryBindingFromSessionDirectory(sessionDir) {
36527
36569
  let st;
36528
36570
  try {
36529
- st = fs24.statSync(sessionDir);
36571
+ st = fs23.statSync(sessionDir);
36530
36572
  } catch {
36531
36573
  return null;
36532
36574
  }
36533
36575
  if (!st.isDirectory()) return null;
36534
36576
  const worktreePaths = collectGitRepoRootsUnderDirectory(sessionDir);
36535
36577
  if (worktreePaths.length === 0) return null;
36536
- const abs = path25.resolve(sessionDir);
36578
+ const abs = path24.resolve(sessionDir);
36537
36579
  return {
36538
36580
  sessionParentPath: abs,
36539
36581
  workingTreeRelRoot: abs,
@@ -36543,20 +36585,20 @@ function tryBindingFromSessionDirectory(sessionDir) {
36543
36585
  function discoverLegacyBindingAscendingFromCheckout(sessionId, checkoutPath) {
36544
36586
  const sid = sessionId.trim();
36545
36587
  if (!sid) return null;
36546
- const hintR = path25.resolve(checkoutPath);
36588
+ const hintR = path24.resolve(checkoutPath);
36547
36589
  let best = null;
36548
- let cur = path25.dirname(hintR);
36590
+ let cur = path24.dirname(hintR);
36549
36591
  for (let i = 0; i < 40; i++) {
36550
36592
  const paths = collectWorktreeRootsNamed(cur, sid, 24);
36551
- if (paths.some((p) => path25.resolve(p) === hintR)) {
36552
- const isolated = resolveIsolatedSessionParentPathFromCheckouts(paths) ?? path25.resolve(paths[0]);
36593
+ if (paths.some((p) => path24.resolve(p) === hintR)) {
36594
+ const isolated = resolveIsolatedSessionParentPathFromCheckouts(paths) ?? path24.resolve(paths[0]);
36553
36595
  best = {
36554
- sessionParentPath: path25.resolve(isolated),
36555
- workingTreeRelRoot: path25.resolve(cur),
36556
- repoCheckoutPaths: paths.map((p) => path25.resolve(p))
36596
+ sessionParentPath: path24.resolve(isolated),
36597
+ workingTreeRelRoot: path24.resolve(cur),
36598
+ repoCheckoutPaths: paths.map((p) => path24.resolve(p))
36557
36599
  };
36558
36600
  }
36559
- const next = path25.dirname(cur);
36601
+ const next = path24.dirname(cur);
36560
36602
  if (next === cur) break;
36561
36603
  cur = next;
36562
36604
  }
@@ -36564,33 +36606,33 @@ function discoverLegacyBindingAscendingFromCheckout(sessionId, checkoutPath) {
36564
36606
  }
36565
36607
  function discoverSessionWorktreeOnDisk(options) {
36566
36608
  const { sessionId, worktreesRootPath, layout, bridgeRoot } = options;
36567
- if (!sessionId.trim() || !fs24.existsSync(worktreesRootPath)) return null;
36609
+ if (!sessionId.trim() || !fs23.existsSync(worktreesRootPath)) return null;
36568
36610
  const preferredKey = getLauncherDirNameIfPresent(layout, bridgeRoot);
36569
36611
  const keys = [];
36570
36612
  if (preferredKey) keys.push(preferredKey);
36571
36613
  try {
36572
- for (const name of fs24.readdirSync(worktreesRootPath)) {
36614
+ for (const name of fs23.readdirSync(worktreesRootPath)) {
36573
36615
  if (name.startsWith(".")) continue;
36574
- const p = path25.join(worktreesRootPath, name);
36575
- if (!fs24.statSync(p).isDirectory()) continue;
36616
+ const p = path24.join(worktreesRootPath, name);
36617
+ if (!fs23.statSync(p).isDirectory()) continue;
36576
36618
  if (name !== preferredKey) keys.push(name);
36577
36619
  }
36578
36620
  } catch {
36579
36621
  return null;
36580
36622
  }
36581
36623
  for (const key of keys) {
36582
- const layoutRoot = path25.join(worktreesRootPath, key);
36583
- if (!fs24.existsSync(layoutRoot) || !fs24.statSync(layoutRoot).isDirectory()) continue;
36584
- const sessionDir = path25.join(layoutRoot, sessionId);
36624
+ const layoutRoot = path24.join(worktreesRootPath, key);
36625
+ if (!fs23.existsSync(layoutRoot) || !fs23.statSync(layoutRoot).isDirectory()) continue;
36626
+ const sessionDir = path24.join(layoutRoot, sessionId);
36585
36627
  const nested = tryBindingFromSessionDirectory(sessionDir);
36586
36628
  if (nested) return nested;
36587
36629
  const legacyPaths = collectWorktreeRootsNamed(layoutRoot, sessionId, 24);
36588
36630
  if (legacyPaths.length > 0) {
36589
- const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths) ?? path25.resolve(legacyPaths[0]);
36631
+ const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths) ?? path24.resolve(legacyPaths[0]);
36590
36632
  return {
36591
- sessionParentPath: path25.resolve(isolated),
36592
- workingTreeRelRoot: path25.resolve(layoutRoot),
36593
- repoCheckoutPaths: legacyPaths.map((p) => path25.resolve(p))
36633
+ sessionParentPath: path24.resolve(isolated),
36634
+ workingTreeRelRoot: path24.resolve(layoutRoot),
36635
+ repoCheckoutPaths: legacyPaths.map((p) => path24.resolve(p))
36594
36636
  };
36595
36637
  }
36596
36638
  }
@@ -36599,12 +36641,12 @@ function discoverSessionWorktreeOnDisk(options) {
36599
36641
  function discoverSessionWorktreesUnderSessionWorktreeRoot(sessionWorktreeRootPathOrHint, sessionId) {
36600
36642
  const sid = sessionId.trim();
36601
36643
  if (!sid) return null;
36602
- const hint = path25.resolve(sessionWorktreeRootPathOrHint);
36603
- const underHint = tryBindingFromSessionDirectory(path25.join(hint, sid));
36644
+ const hint = path24.resolve(sessionWorktreeRootPathOrHint);
36645
+ const underHint = tryBindingFromSessionDirectory(path24.join(hint, sid));
36604
36646
  if (underHint) return underHint;
36605
36647
  const direct = tryBindingFromSessionDirectory(hint);
36606
36648
  if (direct) {
36607
- if (path25.basename(hint) === sid && isGitDir(hint)) {
36649
+ if (path24.basename(hint) === sid && isGitDir(hint)) {
36608
36650
  const legacyFromCheckout = discoverLegacyBindingAscendingFromCheckout(sid, hint);
36609
36651
  if (legacyFromCheckout && legacyFromCheckout.repoCheckoutPaths.length > direct.repoCheckoutPaths.length) {
36610
36652
  return legacyFromCheckout;
@@ -36612,216 +36654,349 @@ function discoverSessionWorktreesUnderSessionWorktreeRoot(sessionWorktreeRootPat
36612
36654
  }
36613
36655
  return direct;
36614
36656
  }
36615
- if (path25.basename(hint) === sid && isGitDir(hint)) {
36657
+ if (path24.basename(hint) === sid && isGitDir(hint)) {
36616
36658
  const legacyFromCheckout = discoverLegacyBindingAscendingFromCheckout(sid, hint);
36617
36659
  if (legacyFromCheckout) return legacyFromCheckout;
36618
36660
  }
36619
36661
  let st;
36620
36662
  try {
36621
- st = fs24.statSync(hint);
36663
+ st = fs23.statSync(hint);
36622
36664
  } catch {
36623
36665
  return null;
36624
36666
  }
36625
36667
  if (!st.isDirectory()) return null;
36626
36668
  const legacyPaths = collectWorktreeRootsNamed(hint, sid, 24);
36627
36669
  if (legacyPaths.length === 0) return null;
36628
- const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths) ?? path25.resolve(legacyPaths[0]);
36670
+ const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths) ?? path24.resolve(legacyPaths[0]);
36629
36671
  return {
36630
- sessionParentPath: path25.resolve(isolated),
36672
+ sessionParentPath: path24.resolve(isolated),
36631
36673
  workingTreeRelRoot: hint,
36632
- repoCheckoutPaths: legacyPaths.map((p) => path25.resolve(p))
36674
+ repoCheckoutPaths: legacyPaths.map((p) => path24.resolve(p))
36633
36675
  };
36634
36676
  }
36635
36677
 
36636
- // src/worktrees/session-worktree-manager.ts
36678
+ // src/worktrees/manager/discover-session-binding.ts
36679
+ function discoverSessionBinding(params) {
36680
+ return discoverSessionWorktreeOnDisk({
36681
+ sessionId: params.sessionId,
36682
+ worktreesRootPath: params.worktreesRootPath,
36683
+ layout: params.layout,
36684
+ bridgeRoot: getBridgeRoot()
36685
+ });
36686
+ }
36687
+
36688
+ // src/worktrees/manager/resolve-isolated-session-parent-path.ts
36689
+ function resolveIsolatedSessionParentPath(sessionId, cache2, ensureRepoCheckoutPaths) {
36690
+ if (!sessionId) return null;
36691
+ const sid = sessionId.trim();
36692
+ const cached2 = cache2.getSessionParentPath(sid);
36693
+ if (cached2) return cached2;
36694
+ const paths = ensureRepoCheckoutPaths(sid);
36695
+ if (!paths?.length) return null;
36696
+ return resolveIsolatedSessionParentPathFromCheckouts(paths);
36697
+ }
36698
+ function ensureRepoCheckoutPathsForSession(sessionId, cache2, discover) {
36699
+ if (!sessionId?.trim()) return void 0;
36700
+ const sid = sessionId.trim();
36701
+ const cached2 = cache2.getRepoCheckoutPaths(sid);
36702
+ if (cached2?.length) return cached2;
36703
+ const disc = discover(sid);
36704
+ if (disc?.repoCheckoutPaths.length) {
36705
+ cache2.remember(sid, disc);
36706
+ return [...disc.repoCheckoutPaths];
36707
+ }
36708
+ return void 0;
36709
+ }
36710
+
36711
+ // src/worktrees/manager/resolve-commit-targets.ts
36712
+ function resolveCommitTargets(sessionId, cache2, discover) {
36713
+ const paths = cache2.getRepoCheckoutPathsRef(sessionId);
36714
+ if (paths?.length) return paths;
36715
+ const disc = discover(sessionId);
36716
+ if (disc?.repoCheckoutPaths.length) {
36717
+ cache2.remember(sessionId, disc);
36718
+ return disc.repoCheckoutPaths;
36719
+ }
36720
+ return [getBridgeRoot()];
36721
+ }
36722
+
36723
+ // src/worktrees/manager/parse-session-parent.ts
36637
36724
  function parseSessionParent(v) {
36638
36725
  if (v === "bridge_root" || v === "worktrees_root") return v;
36639
36726
  if (v === "session_worktrees_root") return "worktrees_root";
36640
36727
  return null;
36641
36728
  }
36642
- var SessionWorktreeManager = class {
36643
- worktreesRootPath;
36644
- log;
36729
+
36730
+ // src/worktrees/prepare-new-session-worktrees.ts
36731
+ import * as fs24 from "node:fs";
36732
+ import * as path25 from "node:path";
36733
+
36734
+ // src/git/worktrees/worktree-add.ts
36735
+ async function gitWorktreeAddBranch(mainRepoPath, worktreePath, branch, baseRef = "HEAD") {
36736
+ const mainGit = cliSimpleGit(mainRepoPath);
36737
+ const base = baseRef.trim() || "HEAD";
36738
+ await mainGit.raw(["worktree", "add", "-b", branch, worktreePath, base]);
36739
+ }
36740
+
36741
+ // src/worktrees/prepare-new-session-worktrees.ts
36742
+ function normalizeRepoRelPath(rel) {
36743
+ return rel === "" ? "." : rel.replace(/\\/g, "/");
36744
+ }
36745
+ function resolveBaseRefForRepo(relNorm, baseBranches) {
36746
+ if (!baseBranches) return "HEAD";
36747
+ const direct = baseBranches[relNorm]?.trim();
36748
+ if (direct) return direct;
36749
+ if (relNorm !== "." && baseBranches["."]?.trim()) return baseBranches["."].trim();
36750
+ return "HEAD";
36751
+ }
36752
+ async function prepareNewSessionWorktrees(options) {
36753
+ const { worktreesRootPath, bridgeRoot, sessionId, layout, log: log2, worktreeBaseBranches } = options;
36754
+ const bridgeResolved = path25.resolve(bridgeRoot);
36755
+ const cwdKey = allocateDirNameForLauncherCwd(layout, bridgeResolved);
36756
+ const bridgeKeyDir = path25.join(worktreesRootPath, cwdKey);
36757
+ const sessionDir = path25.join(bridgeKeyDir, sessionId);
36758
+ const repos = await discoverGitReposUnderRoot(bridgeResolved);
36759
+ if (repos.length === 0) {
36760
+ log2("[worktrees] No Git repositories under bridge root; skipping worktree creation.");
36761
+ return null;
36762
+ }
36763
+ const branch = `session-${sessionId}`;
36764
+ const worktreePaths = [];
36765
+ fs24.mkdirSync(sessionDir, { recursive: true });
36766
+ for (const repo of repos) {
36767
+ let rel = path25.relative(bridgeResolved, repo.absolutePath);
36768
+ if (rel.startsWith("..") || path25.isAbsolute(rel)) continue;
36769
+ const relNorm = normalizeRepoRelPath(rel === "" ? "." : rel);
36770
+ const wtPath = relNorm === "." ? sessionDir : path25.join(sessionDir, relNorm);
36771
+ if (relNorm !== ".") {
36772
+ fs24.mkdirSync(path25.dirname(wtPath), { recursive: true });
36773
+ }
36774
+ const baseRef = resolveBaseRefForRepo(relNorm, worktreeBaseBranches);
36775
+ try {
36776
+ await gitWorktreeAddBranch(repo.absolutePath, wtPath, branch, baseRef);
36777
+ log2(`[worktrees] Added worktree ${wtPath} (branch ${branch}, base ${baseRef}).`);
36778
+ worktreePaths.push(wtPath);
36779
+ } catch (e) {
36780
+ log2(
36781
+ `[worktrees] Worktree add failed for ${repo.absolutePath}: ${e instanceof Error ? e.message : String(e)}`
36782
+ );
36783
+ }
36784
+ }
36785
+ if (worktreePaths.length === 0) return null;
36786
+ return {
36787
+ worktreePaths,
36788
+ sessionParentPath: sessionDir,
36789
+ workingTreeRelRoot: sessionDir
36790
+ };
36791
+ }
36792
+
36793
+ // src/worktrees/manager/prepare-and-remember-session-worktrees.ts
36794
+ async function prepareAndRememberSessionWorktrees(params) {
36795
+ const prep = await prepareNewSessionWorktrees({
36796
+ worktreesRootPath: params.worktreesRootPath,
36797
+ bridgeRoot: getBridgeRoot(),
36798
+ sessionId: params.sessionId,
36799
+ layout: params.layout,
36800
+ log: params.log,
36801
+ ...params.worktreeBaseBranches && Object.keys(params.worktreeBaseBranches).length > 0 ? { worktreeBaseBranches: params.worktreeBaseBranches } : {}
36802
+ });
36803
+ if (!prep) return void 0;
36804
+ params.cache.remember(params.sessionId, {
36805
+ sessionParentPath: prep.sessionParentPath,
36806
+ workingTreeRelRoot: prep.workingTreeRelRoot,
36807
+ repoCheckoutPaths: prep.worktreePaths
36808
+ });
36809
+ return params.cache.getSessionParentPath(params.sessionId);
36810
+ }
36811
+
36812
+ // src/worktrees/manager/resolve-existing-session-parent-path.ts
36813
+ function resolveExistingSessionParentPath(sessionId, cache2, discover) {
36814
+ const cached2 = cache2.getSessionParentPath(sessionId);
36815
+ if (cached2) return cached2;
36816
+ const disc = discover();
36817
+ if (disc) {
36818
+ cache2.remember(sessionId, disc);
36819
+ return cache2.getSessionParentPath(sessionId);
36820
+ }
36821
+ return void 0;
36822
+ }
36823
+
36824
+ // src/worktrees/manager/resolve-explicit-session-parent-path.ts
36825
+ import * as path26 from "node:path";
36826
+ function resolveExplicitSessionParentPath(params) {
36827
+ const resolved = path26.resolve(params.parentPathRaw);
36828
+ if (parseSessionParent(params.sessionParent) !== "worktrees_root") {
36829
+ return resolved;
36830
+ }
36831
+ const rememberAndReturn = (binding) => {
36832
+ params.cache.remember(params.sessionId, binding);
36833
+ return params.cache.getSessionParentPath(params.sessionId) ?? resolved;
36834
+ };
36835
+ const diskFirst = params.discover();
36836
+ if (diskFirst) return rememberAndReturn(diskFirst);
36837
+ const fromRoot = discoverSessionWorktreesUnderSessionWorktreeRoot(resolved, params.sessionId);
36838
+ if (fromRoot) return rememberAndReturn(fromRoot);
36839
+ let cur = resolved;
36840
+ for (let i = 0; i < 16; i++) {
36841
+ const tryRoot = discoverSessionWorktreesUnderSessionWorktreeRoot(cur, params.sessionId);
36842
+ if (tryRoot) return rememberAndReturn(tryRoot);
36843
+ const next = path26.dirname(cur);
36844
+ if (next === cur) break;
36845
+ cur = next;
36846
+ }
36847
+ return resolved;
36848
+ }
36849
+
36850
+ // src/worktrees/manager/resolve-session-parent-path-for-prompt.ts
36851
+ async function resolveSessionParentPathForPrompt(params) {
36852
+ const { sessionId, cache: cache2, worktreesRootPath, layout, log: log2, discover, opts } = params;
36853
+ if (!sessionId) return void 0;
36854
+ const sid = sessionId.trim();
36855
+ const parentPathRaw = opts.sessionParentPath?.trim();
36856
+ if (parentPathRaw) {
36857
+ return resolveExplicitSessionParentPath({
36858
+ sessionId: sid,
36859
+ sessionParent: opts.sessionParent,
36860
+ parentPathRaw,
36861
+ cache: cache2,
36862
+ discover: () => discover(sid)
36863
+ });
36864
+ }
36865
+ const parentKind = parseSessionParent(opts.sessionParent);
36866
+ if (parentKind === "bridge_root") {
36867
+ return void 0;
36868
+ }
36869
+ if (parentKind === "worktrees_root") {
36870
+ if (!opts.isNewSession) {
36871
+ return resolveExistingSessionParentPath(sid, cache2, () => discover(sid));
36872
+ }
36873
+ return prepareAndRememberSessionWorktrees({
36874
+ cache: cache2,
36875
+ sessionId: sid,
36876
+ worktreesRootPath,
36877
+ layout,
36878
+ log: log2,
36879
+ ...opts.worktreeBaseBranches ? { worktreeBaseBranches: opts.worktreeBaseBranches } : {}
36880
+ });
36881
+ }
36882
+ if (!opts.isNewSession) {
36883
+ return resolveExistingSessionParentPath(sid, cache2, () => discover(sid));
36884
+ }
36885
+ return prepareAndRememberSessionWorktrees({
36886
+ cache: cache2,
36887
+ sessionId: sid,
36888
+ worktreesRootPath,
36889
+ layout,
36890
+ log: log2
36891
+ });
36892
+ }
36893
+
36894
+ // src/worktrees/manager/session-worktree-cache.ts
36895
+ import * as path27 from "node:path";
36896
+ var SessionWorktreeCache = class {
36645
36897
  sessionRepoCheckoutPaths = /* @__PURE__ */ new Map();
36646
36898
  sessionParentPathBySession = /* @__PURE__ */ new Map();
36647
36899
  sessionWorkingTreeRelRootBySession = /* @__PURE__ */ new Map();
36648
- layout;
36649
- constructor(options) {
36650
- this.worktreesRootPath = options.worktreesRootPath;
36651
- this.log = options.log;
36652
- this.layout = loadWorktreeLayout();
36653
- }
36654
- rememberSessionWorktrees(sessionId, binding) {
36655
- const paths = binding.repoCheckoutPaths.map((p) => path26.resolve(p));
36900
+ remember(sessionId, binding) {
36901
+ const paths = binding.repoCheckoutPaths.map((p) => path27.resolve(p));
36656
36902
  this.sessionRepoCheckoutPaths.set(sessionId, paths);
36657
- this.sessionParentPathBySession.set(sessionId, path26.resolve(binding.sessionParentPath));
36658
- this.sessionWorkingTreeRelRootBySession.set(sessionId, path26.resolve(binding.workingTreeRelRoot));
36903
+ this.sessionParentPathBySession.set(sessionId, path27.resolve(binding.sessionParentPath));
36904
+ this.sessionWorkingTreeRelRootBySession.set(sessionId, path27.resolve(binding.workingTreeRelRoot));
36905
+ }
36906
+ clearSession(sessionId) {
36907
+ const paths = this.sessionRepoCheckoutPaths.get(sessionId);
36908
+ this.sessionRepoCheckoutPaths.delete(sessionId);
36909
+ this.sessionParentPathBySession.delete(sessionId);
36910
+ this.sessionWorkingTreeRelRootBySession.delete(sessionId);
36911
+ return paths;
36659
36912
  }
36660
- sessionParentPathAfterRemember(sessionId) {
36913
+ getSessionParentPath(sessionId) {
36661
36914
  return this.sessionParentPathBySession.get(sessionId);
36662
36915
  }
36663
- tryDiscoverFromDisk(sessionId) {
36664
- return discoverSessionWorktreeOnDisk({
36665
- sessionId,
36666
- worktreesRootPath: this.worktreesRootPath,
36667
- layout: this.layout,
36668
- bridgeRoot: getBridgeRoot()
36669
- });
36916
+ getWorkingTreeRelRoot(sessionId) {
36917
+ return this.sessionWorkingTreeRelRootBySession.get(sessionId) ?? null;
36918
+ }
36919
+ hasSession(sessionId) {
36920
+ return this.sessionParentPathBySession.has(sessionId);
36921
+ }
36922
+ getRepoCheckoutPaths(sessionId) {
36923
+ const paths = this.sessionRepoCheckoutPaths.get(sessionId);
36924
+ return paths?.length ? [...paths] : void 0;
36925
+ }
36926
+ getRepoCheckoutPathsRef(sessionId) {
36927
+ const paths = this.sessionRepoCheckoutPaths.get(sessionId);
36928
+ return paths?.length ? paths : void 0;
36670
36929
  }
36671
36930
  isLegacyNestedLayout(sessionId) {
36672
36931
  const parent = this.sessionParentPathBySession.get(sessionId);
36673
36932
  const relRoot = this.sessionWorkingTreeRelRootBySession.get(sessionId);
36674
36933
  if (!parent || !relRoot) return false;
36675
- return path26.resolve(parent) !== path26.resolve(relRoot);
36934
+ return path27.resolve(parent) !== path27.resolve(relRoot);
36935
+ }
36936
+ };
36937
+
36938
+ // src/worktrees/manager/manager.ts
36939
+ var SessionWorktreeManager = class {
36940
+ worktreesRootPath;
36941
+ log;
36942
+ cache = new SessionWorktreeCache();
36943
+ layout;
36944
+ constructor(options) {
36945
+ this.worktreesRootPath = options.worktreesRootPath;
36946
+ this.log = options.log;
36947
+ this.layout = loadWorktreeLayout();
36948
+ }
36949
+ discover(sessionId) {
36950
+ return discoverSessionBinding({
36951
+ sessionId,
36952
+ worktreesRootPath: this.worktreesRootPath,
36953
+ layout: this.layout
36954
+ });
36676
36955
  }
36677
- /**
36678
- * Session parent path for `worktrees_root`: the per-session directory (new layout) or primary checkout (legacy).
36679
- */
36680
36956
  getIsolatedSessionParentPathForSession(sessionId) {
36681
- if (!sessionId) return null;
36682
- const sid = sessionId.trim();
36683
- const cached2 = this.sessionParentPathBySession.get(sid);
36684
- if (cached2) return path26.resolve(cached2);
36685
- const paths = this.ensureRepoCheckoutPathsForSession(sid) ?? this.getRepoCheckoutPathsForSession(sid);
36686
- if (!paths?.length) return null;
36687
- return resolveIsolatedSessionParentPathFromCheckouts(paths);
36688
- }
36689
- /**
36690
- * Resolved **session parent path** for the agent: session directory in worktrees mode,
36691
- * or `undefined` meaning use {@link getBridgeRoot}.
36692
- */
36693
- async resolveSessionParentPathForPrompt(sessionId, opts) {
36694
- if (!sessionId) return void 0;
36695
- const sid = sessionId.trim();
36696
- const parentPathRaw = opts.sessionParentPath?.trim();
36697
- if (parentPathRaw) {
36698
- const resolved = path26.resolve(parentPathRaw);
36699
- if (sid && parseSessionParent(opts.sessionParent) === "worktrees_root") {
36700
- const diskFirst = this.tryDiscoverFromDisk(sid);
36701
- if (diskFirst) {
36702
- this.rememberSessionWorktrees(sid, diskFirst);
36703
- return this.sessionParentPathAfterRemember(sid);
36704
- }
36705
- const fromRoot = discoverSessionWorktreesUnderSessionWorktreeRoot(resolved, sid);
36706
- if (fromRoot) {
36707
- this.rememberSessionWorktrees(sid, fromRoot);
36708
- return this.sessionParentPathAfterRemember(sid);
36709
- }
36710
- let cur = resolved;
36711
- for (let i = 0; i < 16; i++) {
36712
- const tryRoot = discoverSessionWorktreesUnderSessionWorktreeRoot(cur, sid);
36713
- if (tryRoot) {
36714
- this.rememberSessionWorktrees(sid, tryRoot);
36715
- return this.sessionParentPathAfterRemember(sid);
36716
- }
36717
- const next = path26.dirname(cur);
36718
- if (next === cur) break;
36719
- cur = next;
36720
- }
36721
- }
36722
- return resolved;
36723
- }
36724
- const parentKind = parseSessionParent(opts.sessionParent);
36725
- if (parentKind === "bridge_root") {
36726
- return void 0;
36727
- }
36728
- if (parentKind === "worktrees_root") {
36729
- if (!opts.isNewSession) {
36730
- const cached2 = this.sessionParentPathAfterRemember(sid);
36731
- if (cached2) return cached2;
36732
- const disc = this.tryDiscoverFromDisk(sid);
36733
- if (disc) {
36734
- this.rememberSessionWorktrees(sid, disc);
36735
- return this.sessionParentPathAfterRemember(sid);
36736
- }
36737
- return void 0;
36738
- }
36739
- const prep2 = await prepareNewSessionWorktrees({
36740
- worktreesRootPath: this.worktreesRootPath,
36741
- bridgeRoot: getBridgeRoot(),
36742
- sessionId: sid,
36743
- layout: this.layout,
36744
- log: this.log
36745
- });
36746
- if (!prep2) return void 0;
36747
- this.rememberSessionWorktrees(sid, {
36748
- sessionParentPath: prep2.sessionParentPath,
36749
- workingTreeRelRoot: prep2.workingTreeRelRoot,
36750
- repoCheckoutPaths: prep2.worktreePaths
36751
- });
36752
- return this.sessionParentPathAfterRemember(sid);
36753
- }
36754
- if (!opts.isNewSession) {
36755
- const cached2 = this.sessionParentPathAfterRemember(sid);
36756
- if (cached2) return cached2;
36757
- const disc = this.tryDiscoverFromDisk(sid);
36758
- if (disc) {
36759
- this.rememberSessionWorktrees(sid, disc);
36760
- return this.sessionParentPathAfterRemember(sid);
36761
- }
36762
- return void 0;
36763
- }
36764
- const prep = await prepareNewSessionWorktrees({
36957
+ return resolveIsolatedSessionParentPath(
36958
+ sessionId,
36959
+ this.cache,
36960
+ (sid) => this.ensureRepoCheckoutPathsForSession(sid)
36961
+ );
36962
+ }
36963
+ resolveSessionParentPathForPrompt(sessionId, opts) {
36964
+ return resolveSessionParentPathForPrompt({
36965
+ sessionId,
36966
+ cache: this.cache,
36765
36967
  worktreesRootPath: this.worktreesRootPath,
36766
- bridgeRoot: getBridgeRoot(),
36767
- sessionId: sid,
36768
36968
  layout: this.layout,
36769
- log: this.log
36770
- });
36771
- if (!prep) return void 0;
36772
- this.rememberSessionWorktrees(sid, {
36773
- sessionParentPath: prep.sessionParentPath,
36774
- workingTreeRelRoot: prep.workingTreeRelRoot,
36775
- repoCheckoutPaths: prep.worktreePaths
36969
+ log: this.log,
36970
+ discover: (sid) => this.discover(sid),
36971
+ opts
36776
36972
  });
36777
- return this.sessionParentPathAfterRemember(sid);
36778
36973
  }
36779
36974
  async renameSessionBranch(sessionId, newBranch) {
36780
- const paths = this.sessionRepoCheckoutPaths.get(sessionId);
36975
+ const paths = this.cache.getRepoCheckoutPathsRef(sessionId);
36781
36976
  if (!paths?.length) return;
36782
36977
  await renameSessionWorktreeBranches(paths, newBranch, this.log);
36783
36978
  }
36784
- /** True when this session uses an isolated worktree layout (not the bridge root). */
36785
36979
  usesWorktreeSession(sessionId) {
36786
36980
  if (!sessionId) return false;
36787
- return this.sessionParentPathBySession.has(sessionId);
36981
+ return this.cache.hasSession(sessionId);
36788
36982
  }
36789
- /** Per-repo git checkout directories for this session (for snapshots, commits, change lists). */
36790
36983
  getRepoCheckoutPathsForSession(sessionId) {
36791
36984
  if (!sessionId) return void 0;
36792
- const paths = this.sessionRepoCheckoutPaths.get(sessionId);
36793
- return paths?.length ? [...paths] : void 0;
36985
+ return this.cache.getRepoCheckoutPaths(sessionId);
36794
36986
  }
36795
- /**
36796
- * Same paths as {@link getRepoCheckoutPathsForSession}, but loads from disk into memory when the CLI
36797
- * restarted or maps were not yet populated (avoids discovering every repo under the worktrees root).
36798
- */
36799
36987
  ensureRepoCheckoutPathsForSession(sessionId) {
36800
- if (!sessionId?.trim()) return void 0;
36801
- const sid = sessionId.trim();
36802
- const cached2 = this.sessionRepoCheckoutPaths.get(sid);
36803
- if (cached2?.length) return [...cached2];
36804
- const disc = this.tryDiscoverFromDisk(sid);
36805
- if (disc?.repoCheckoutPaths.length) {
36806
- this.rememberSessionWorktrees(sid, disc);
36807
- return [...disc.repoCheckoutPaths];
36808
- }
36809
- return void 0;
36988
+ return ensureRepoCheckoutPathsForSession(sessionId, this.cache, (sid) => this.discover(sid));
36810
36989
  }
36811
- /** Session parent directory when in worktrees mode; null otherwise (same as {@link getIsolatedSessionParentPathForSession} path). */
36812
36990
  getSessionWorktreeRootForSession(sessionId) {
36813
36991
  return this.getIsolatedSessionParentPathForSession(sessionId);
36814
36992
  }
36815
36993
  async removeSessionWorktrees(sessionId) {
36816
- const paths = this.sessionRepoCheckoutPaths.get(sessionId);
36817
- this.sessionRepoCheckoutPaths.delete(sessionId);
36818
- this.sessionParentPathBySession.delete(sessionId);
36819
- this.sessionWorkingTreeRelRootBySession.delete(sessionId);
36994
+ const paths = this.cache.clearSession(sessionId);
36820
36995
  if (!paths?.length) return;
36821
36996
  await removeSessionWorktrees(paths, this.log);
36822
36997
  }
36823
36998
  async commitSession(params) {
36824
- const paths = this.sessionRepoCheckoutPaths.get(params.sessionId);
36999
+ const paths = this.cache.getRepoCheckoutPathsRef(params.sessionId);
36825
37000
  const targets = paths?.length ? paths : [getBridgeRoot()];
36826
37001
  return commitSessionWorktrees({
36827
37002
  paths: targets,
@@ -36830,28 +37005,17 @@ var SessionWorktreeManager = class {
36830
37005
  push: params.push
36831
37006
  });
36832
37007
  }
36833
- resolveCommitTargets(sessionId) {
36834
- const paths = this.sessionRepoCheckoutPaths.get(sessionId);
36835
- if (paths?.length) return paths;
36836
- const disc = this.tryDiscoverFromDisk(sessionId);
36837
- if (disc?.repoCheckoutPaths.length) {
36838
- this.rememberSessionWorktrees(sessionId, disc);
36839
- return disc.repoCheckoutPaths;
36840
- }
36841
- return [getBridgeRoot()];
36842
- }
36843
37008
  async getSessionWorkingTreeStatus(sessionId) {
36844
- return aggregateSessionPathsWorkingTreeStatus(this.resolveCommitTargets(sessionId));
37009
+ return aggregateSessionPathsWorkingTreeStatus(
37010
+ resolveCommitTargets(sessionId, this.cache, (sid) => this.discover(sid))
37011
+ );
36845
37012
  }
36846
- /** Per-repo changed files vs HEAD (or a single commit vs parent) for the same git roots used for commit/push. */
36847
37013
  async getSessionWorkingTreeChangeDetails(sessionId, opts) {
36848
- const targets = this.resolveCommitTargets(sessionId);
36849
- const sessionWorkingTreeRelRoot = this.sessionWorkingTreeRelRootBySession.get(sessionId) ?? null;
36850
- const legacyNested = this.isLegacyNestedLayout(sessionId);
37014
+ const targets = resolveCommitTargets(sessionId, this.cache, (sid) => this.discover(sid));
36851
37015
  return getWorkingTreeChangeRepoDetails({
36852
37016
  commitTargetPaths: targets,
36853
- sessionWorktreeRootPath: sessionWorkingTreeRelRoot,
36854
- legacyRepoNestedSessionLayout: legacyNested,
37017
+ sessionWorktreeRootPath: this.cache.getWorkingTreeRelRoot(sessionId),
37018
+ legacyRepoNestedSessionLayout: this.cache.isLegacyNestedLayout(sessionId),
36855
37019
  repoFilterRelPath: opts?.repoRelPath?.trim() ? opts.repoRelPath.trim() : null,
36856
37020
  basis: opts?.basis,
36857
37021
  recentCommitsLimit: opts?.recentCommitsLimit
@@ -36859,7 +37023,9 @@ var SessionWorktreeManager = class {
36859
37023
  }
36860
37024
  async pushSessionUpstream(sessionId) {
36861
37025
  try {
36862
- await pushAheadOfUpstreamForPaths(this.resolveCommitTargets(sessionId));
37026
+ await pushAheadOfUpstreamForPaths(
37027
+ resolveCommitTargets(sessionId, this.cache, (sid) => this.discover(sid))
37028
+ );
36863
37029
  return { ok: true };
36864
37030
  } catch (e) {
36865
37031
  const err = e instanceof Error ? e.message : String(e);
@@ -36867,27 +37033,31 @@ var SessionWorktreeManager = class {
36867
37033
  }
36868
37034
  }
36869
37035
  };
37036
+
37037
+ // src/worktrees/manager/default-worktrees-root-path.ts
37038
+ import * as path28 from "node:path";
37039
+ import os8 from "node:os";
36870
37040
  function defaultWorktreesRootPath() {
36871
- return path26.join(os8.homedir(), ".buildautomaton", "worktrees");
37041
+ return path28.join(os8.homedir(), ".buildautomaton", "worktrees");
36872
37042
  }
36873
37043
 
36874
37044
  // src/files/watch-file-index.ts
36875
37045
  import { watch } from "node:fs";
36876
- import path31 from "node:path";
37046
+ import path33 from "node:path";
36877
37047
 
36878
37048
  // src/files/index/paths.ts
36879
- import path27 from "node:path";
37049
+ import path29 from "node:path";
36880
37050
  import crypto2 from "node:crypto";
36881
37051
  function getCwdHashForFileIndex(resolvedCwd) {
36882
- return crypto2.createHash("sha256").update(path27.resolve(resolvedCwd)).digest("hex").slice(0, INDEX_HASH_LEN);
37052
+ return crypto2.createHash("sha256").update(path29.resolve(resolvedCwd)).digest("hex").slice(0, INDEX_HASH_LEN);
36883
37053
  }
36884
37054
 
36885
37055
  // src/files/index/build-file-index.ts
36886
- import path29 from "node:path";
37056
+ import path31 from "node:path";
36887
37057
 
36888
37058
  // src/files/index/walk-workspace-tree.ts
36889
37059
  import fs25 from "node:fs";
36890
- import path28 from "node:path";
37060
+ import path30 from "node:path";
36891
37061
  var DEPENDENCY_INSTALL_DIR_NAMES = /* @__PURE__ */ new Set([
36892
37062
  "node_modules",
36893
37063
  "bower_components",
@@ -36916,18 +37086,18 @@ async function walkWorkspaceTreeAsync(dir, baseDir, onFile, state) {
36916
37086
  if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
36917
37087
  }
36918
37088
  state.n++;
36919
- const full = path28.join(dir, name);
37089
+ const full = path30.join(dir, name);
36920
37090
  let stat3;
36921
37091
  try {
36922
37092
  stat3 = await fs25.promises.stat(full);
36923
37093
  } catch {
36924
37094
  continue;
36925
37095
  }
36926
- const relative5 = path28.relative(baseDir, full).replace(/\\/g, "/");
37096
+ const relative6 = path30.relative(baseDir, full).replace(/\\/g, "/");
36927
37097
  if (stat3.isDirectory()) {
36928
37098
  await walkWorkspaceTreeAsync(full, baseDir, onFile, state);
36929
37099
  } else if (stat3.isFile()) {
36930
- onFile(relative5);
37100
+ onFile(relative6);
36931
37101
  }
36932
37102
  }
36933
37103
  }
@@ -37025,7 +37195,7 @@ async function collectWorkspacePathsAsync(resolved) {
37025
37195
  }
37026
37196
  async function buildFileIndexAsync(cwd) {
37027
37197
  return withFileIndexSqliteLock(async () => {
37028
- const resolved = path29.resolve(cwd);
37198
+ const resolved = path31.resolve(cwd);
37029
37199
  await yieldToEventLoop();
37030
37200
  assertNotShutdown();
37031
37201
  const paths = await collectWorkspacePathsAsync(resolved);
@@ -37037,7 +37207,7 @@ async function buildFileIndexAsync(cwd) {
37037
37207
  }
37038
37208
 
37039
37209
  // src/files/index/ensure-file-index.ts
37040
- import path30 from "node:path";
37210
+ import path32 from "node:path";
37041
37211
 
37042
37212
  // src/files/index/search-file-index.ts
37043
37213
  function escapeLikePattern(fragment) {
@@ -37089,7 +37259,7 @@ async function searchBridgeFilePathsAsync(resolvedCwd, query, limit = 100) {
37089
37259
 
37090
37260
  // src/files/index/ensure-file-index.ts
37091
37261
  async function ensureFileIndexAsync(cwd) {
37092
- const resolved = path30.resolve(cwd);
37262
+ const resolved = path32.resolve(cwd);
37093
37263
  if (await bridgeFileIndexIsPopulated(resolved)) {
37094
37264
  return { fromCache: true, pathCount: await bridgeFileIndexPathCount(resolved) };
37095
37265
  }
@@ -37133,7 +37303,7 @@ function createFsWatcher(resolved, schedule) {
37133
37303
  }
37134
37304
  }
37135
37305
  function startFileIndexWatcher(cwd = getBridgeRoot()) {
37136
- const resolved = path31.resolve(cwd);
37306
+ const resolved = path33.resolve(cwd);
37137
37307
  void buildFileIndexAsync(resolved).catch((e) => {
37138
37308
  if (e instanceof CliSqliteInterrupted) return;
37139
37309
  console.error("[file-index] Initial index build failed:", e);
@@ -37163,7 +37333,7 @@ function startFileIndexWatcher(cwd = getBridgeRoot()) {
37163
37333
  }
37164
37334
 
37165
37335
  // src/connection/create-bridge-connection.ts
37166
- import * as path41 from "node:path";
37336
+ import * as path44 from "node:path";
37167
37337
 
37168
37338
  // src/dev-servers/manager/dev-server-manager.ts
37169
37339
  import { rm as rm2 } from "node:fs/promises";
@@ -37185,15 +37355,15 @@ function sendDevServerStatus(getWs, serverId, status, options) {
37185
37355
 
37186
37356
  // src/dev-servers/process/terminate-child-process.ts
37187
37357
  async function sigtermAndWaitForExit(proc, graceMs, log2, shortId) {
37188
- const exited = new Promise((resolve20) => {
37189
- proc.once("exit", () => resolve20());
37358
+ const exited = new Promise((resolve22) => {
37359
+ proc.once("exit", () => resolve22());
37190
37360
  });
37191
37361
  log2(`[dev-server] Sending SIGTERM to ${shortId} (pid=${proc.pid ?? "?"}).`);
37192
37362
  try {
37193
37363
  proc.kill("SIGTERM");
37194
37364
  } catch {
37195
37365
  }
37196
- await Promise.race([exited, new Promise((resolve20) => setTimeout(resolve20, graceMs))]);
37366
+ await Promise.race([exited, new Promise((resolve22) => setTimeout(resolve22, graceMs))]);
37197
37367
  }
37198
37368
  function forceKillChild(proc, log2, shortId, graceMs) {
37199
37369
  log2(
@@ -37473,10 +37643,10 @@ function trySpawnShellTruePiped(command, env, cwd, devNullFd, signal) {
37473
37643
  import { spawn as spawn7 } from "node:child_process";
37474
37644
  import fs29 from "node:fs";
37475
37645
  import { tmpdir } from "node:os";
37476
- import path32 from "node:path";
37646
+ import path34 from "node:path";
37477
37647
  function trySpawnMergedLogFile(command, env, cwd, signal) {
37478
- const tmpRoot = fs29.mkdtempSync(path32.join(tmpdir(), "ba-devsrv-log-"));
37479
- const logPath = path32.join(tmpRoot, "combined.log");
37648
+ const tmpRoot = fs29.mkdtempSync(path34.join(tmpdir(), "ba-devsrv-log-"));
37649
+ const logPath = path34.join(tmpRoot, "combined.log");
37480
37650
  let logFd;
37481
37651
  try {
37482
37652
  logFd = fs29.openSync(logPath, "a");
@@ -37520,15 +37690,15 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
37520
37690
  import { spawn as spawn8 } from "node:child_process";
37521
37691
  import fs30 from "node:fs";
37522
37692
  import { tmpdir as tmpdir2 } from "node:os";
37523
- import path33 from "node:path";
37693
+ import path35 from "node:path";
37524
37694
  function shSingleQuote(s) {
37525
37695
  return `'${s.replace(/'/g, `'\\''`)}'`;
37526
37696
  }
37527
37697
  function trySpawnShellScriptLogRedirectUnix(command, env, cwd, signal) {
37528
- const tmpRoot = fs30.mkdtempSync(path33.join(tmpdir2(), "ba-devsrv-sh-"));
37529
- const logPath = path33.join(tmpRoot, "combined.log");
37530
- const innerPath = path33.join(tmpRoot, "_cmd.sh");
37531
- const runnerPath = path33.join(tmpRoot, "_run.sh");
37698
+ const tmpRoot = fs30.mkdtempSync(path35.join(tmpdir2(), "ba-devsrv-sh-"));
37699
+ const logPath = path35.join(tmpRoot, "combined.log");
37700
+ const innerPath = path35.join(tmpRoot, "_cmd.sh");
37701
+ const runnerPath = path35.join(tmpRoot, "_run.sh");
37532
37702
  try {
37533
37703
  fs30.writeFileSync(innerPath, `#!/bin/sh
37534
37704
  ${command}
@@ -37559,9 +37729,9 @@ cd ${shSingleQuote(cwd)}
37559
37729
  }
37560
37730
  }
37561
37731
  function trySpawnShellScriptLogRedirectWin(command, env, cwd, signal) {
37562
- const tmpRoot = fs30.mkdtempSync(path33.join(tmpdir2(), "ba-devsrv-sh-"));
37563
- const logPath = path33.join(tmpRoot, "combined.log");
37564
- const runnerPath = path33.join(tmpRoot, "_run.bat");
37732
+ const tmpRoot = fs30.mkdtempSync(path35.join(tmpdir2(), "ba-devsrv-sh-"));
37733
+ const logPath = path35.join(tmpRoot, "combined.log");
37734
+ const runnerPath = path35.join(tmpRoot, "_run.bat");
37565
37735
  const q = (p) => `"${p.replace(/"/g, '""')}"`;
37566
37736
  const com = process.env.ComSpec || "cmd.exe";
37567
37737
  try {
@@ -38074,7 +38244,7 @@ async function proxyToLocal(request) {
38074
38244
  };
38075
38245
  const maxAttempts = isIdempotentProxyMethod(request.method) ? LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS.length + 1 : 1;
38076
38246
  for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
38077
- const once = await new Promise((resolve20) => {
38247
+ const once = await new Promise((resolve22) => {
38078
38248
  const req = mod.request(opts, (res) => {
38079
38249
  const chunks = [];
38080
38250
  res.on("data", (c) => chunks.push(c));
@@ -38085,7 +38255,7 @@ async function proxyToLocal(request) {
38085
38255
  if (typeof v === "string") headers[k] = v;
38086
38256
  else if (Array.isArray(v) && v[0]) headers[k] = v[0];
38087
38257
  }
38088
- resolve20({
38258
+ resolve22({
38089
38259
  id: request.id,
38090
38260
  statusCode: res.statusCode ?? 0,
38091
38261
  headers,
@@ -38094,7 +38264,7 @@ async function proxyToLocal(request) {
38094
38264
  });
38095
38265
  });
38096
38266
  req.on("error", (err) => {
38097
- resolve20({
38267
+ resolve22({
38098
38268
  id: request.id,
38099
38269
  statusCode: 0,
38100
38270
  headers: {},
@@ -38503,13 +38673,13 @@ function createOnBridgeIdentified(opts) {
38503
38673
 
38504
38674
  // src/skills/discover-local-agent-skills.ts
38505
38675
  import fs31 from "node:fs";
38506
- import path34 from "node:path";
38676
+ import path36 from "node:path";
38507
38677
  var SKILL_DISCOVERY_ROOTS = [".agents/skills", ".claude/skills", ".cursor/skills", "skills"];
38508
38678
  function discoverLocalSkills(cwd) {
38509
38679
  const out = [];
38510
38680
  const seenKeys = /* @__PURE__ */ new Set();
38511
38681
  for (const rel of SKILL_DISCOVERY_ROOTS) {
38512
- const base = path34.join(cwd, rel);
38682
+ const base = path36.join(cwd, rel);
38513
38683
  if (!fs31.existsSync(base) || !fs31.statSync(base).isDirectory()) continue;
38514
38684
  let entries = [];
38515
38685
  try {
@@ -38518,13 +38688,13 @@ function discoverLocalSkills(cwd) {
38518
38688
  continue;
38519
38689
  }
38520
38690
  for (const name of entries) {
38521
- const dir = path34.join(base, name);
38691
+ const dir = path36.join(base, name);
38522
38692
  try {
38523
38693
  if (!fs31.statSync(dir).isDirectory()) continue;
38524
38694
  } catch {
38525
38695
  continue;
38526
38696
  }
38527
- const skillMd = path34.join(dir, "SKILL.md");
38697
+ const skillMd = path36.join(dir, "SKILL.md");
38528
38698
  if (!fs31.existsSync(skillMd)) continue;
38529
38699
  const key = `${rel}/${name}`;
38530
38700
  if (seenKeys.has(key)) continue;
@@ -38537,7 +38707,7 @@ function discoverLocalSkills(cwd) {
38537
38707
  function discoverSkillLayoutRoots(cwd) {
38538
38708
  const roots = [];
38539
38709
  for (const rel of SKILL_DISCOVERY_ROOTS) {
38540
- const base = path34.join(cwd, rel);
38710
+ const base = path36.join(cwd, rel);
38541
38711
  if (!fs31.existsSync(base) || !fs31.statSync(base).isDirectory()) continue;
38542
38712
  let entries = [];
38543
38713
  try {
@@ -38547,13 +38717,13 @@ function discoverSkillLayoutRoots(cwd) {
38547
38717
  }
38548
38718
  const skills2 = [];
38549
38719
  for (const name of entries) {
38550
- const dir = path34.join(base, name);
38720
+ const dir = path36.join(base, name);
38551
38721
  try {
38552
38722
  if (!fs31.statSync(dir).isDirectory()) continue;
38553
38723
  } catch {
38554
38724
  continue;
38555
38725
  }
38556
- if (!fs31.existsSync(path34.join(dir, "SKILL.md"))) continue;
38726
+ if (!fs31.existsSync(path36.join(dir, "SKILL.md"))) continue;
38557
38727
  const relPath = `${rel}/${name}`.replace(/\\/g, "/");
38558
38728
  skills2.push({ name, relPath });
38559
38729
  }
@@ -38676,7 +38846,9 @@ var API_TO_BRIDGE_MESSAGE_TYPES = [
38676
38846
  "file_browser_search",
38677
38847
  "skill_layout_request",
38678
38848
  "install_skills",
38679
- "refresh_local_skills"
38849
+ "refresh_local_skills",
38850
+ "bridge_git_context_request",
38851
+ "list_repo_branches_request"
38680
38852
  ];
38681
38853
  var API_TO_BRIDGE_TYPE_SET = new Set(API_TO_BRIDGE_MESSAGE_TYPES);
38682
38854
  function parseApiToBridgeMessage(data, log2) {
@@ -38760,9 +38932,6 @@ var handleAgentConfigMessage = (msg, deps) => {
38760
38932
  handleBridgeAgentConfig(msg, deps);
38761
38933
  };
38762
38934
 
38763
- // src/prompt-turn-queue/runner.ts
38764
- import fs32 from "node:fs";
38765
-
38766
38935
  // src/prompt-turn-queue/client-report.ts
38767
38936
  function sendPromptQueueClientReport(ws, queues) {
38768
38937
  if (!ws) return false;
@@ -38832,8 +39001,36 @@ async function mergeServerQueueSnapshot(queueKey, serverTurns) {
38832
39001
  return { queueKey, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), turns };
38833
39002
  }
38834
39003
 
38835
- // src/prompt-turn-queue/runner.ts
38836
- var runIdToQueueKey = /* @__PURE__ */ new Map();
39004
+ // src/prompt-turn-queue/runner/dispatch-local-prompt.ts
39005
+ function dispatchLocalPrompt(next, deps) {
39006
+ const pl = next.payload;
39007
+ const rawParent = pl["sessionParent"];
39008
+ const sessionParent = rawParent === "bridge_root" || rawParent === "worktrees_root" ? rawParent : void 0;
39009
+ const rawParentPath = pl["sessionParentPath"];
39010
+ const sessionParentPath = typeof rawParentPath === "string" && rawParentPath.trim() !== "" ? rawParentPath.trim() : void 0;
39011
+ const rawBaseBranches = pl["worktreeBaseBranches"];
39012
+ const worktreeBaseBranches = rawBaseBranches != null && typeof rawBaseBranches === "object" && !Array.isArray(rawBaseBranches) ? rawBaseBranches : void 0;
39013
+ const msg = {
39014
+ type: "prompt",
39015
+ sessionId: next.sessionId,
39016
+ runId: next.turnId,
39017
+ prompt: pl.prompt,
39018
+ mode: typeof pl.mode === "string" ? pl.mode : "agent",
39019
+ isNewSession: pl.isNewSession === true,
39020
+ ...sessionParent ? { sessionParent } : {},
39021
+ ...sessionParentPath ? { sessionParentPath } : {},
39022
+ ...worktreeBaseBranches && Object.keys(worktreeBaseBranches).length > 0 ? { worktreeBaseBranches } : {},
39023
+ ...typeof pl.followUpCatalogPromptId === "string" ? { followUpCatalogPromptId: pl.followUpCatalogPromptId } : {},
39024
+ ...Array.isArray(pl.sessionChangeSummaryFilePaths) ? { sessionChangeSummaryFilePaths: pl.sessionChangeSummaryFilePaths } : {},
39025
+ ...Array.isArray(pl.sessionChangeSummaryFileSnapshots) ? { sessionChangeSummaryFileSnapshots: pl.sessionChangeSummaryFileSnapshots } : {},
39026
+ ...typeof pl.agentType === "string" && pl.agentType.trim() ? { agentType: pl.agentType.trim() } : {},
39027
+ ...pl.agentConfig != null && typeof pl.agentConfig === "object" && !Array.isArray(pl.agentConfig) && Object.keys(pl.agentConfig).length > 0 ? { agentConfig: pl.agentConfig } : {},
39028
+ ...Array.isArray(pl.attachments) && pl.attachments.length > 0 ? { attachments: pl.attachments } : {}
39029
+ };
39030
+ handleBridgePrompt(msg, deps);
39031
+ }
39032
+
39033
+ // src/prompt-turn-queue/runner/queue-selection.ts
38837
39034
  function isRunnableServerState(s) {
38838
39035
  return s === "queued" || s === "requeued" || s === "requeued_with_revert";
38839
39036
  }
@@ -38852,6 +39049,28 @@ function pickNextRunnableTurn(turns) {
38852
39049
  function hasRunningTurn(turns) {
38853
39050
  return turns.some((t) => t.lastClientState === "running");
38854
39051
  }
39052
+
39053
+ // src/prompt-turn-queue/runner/run-id-queue-key-map.ts
39054
+ var runIdToQueueKey = /* @__PURE__ */ new Map();
39055
+ function getRunIdQueueKey(runId) {
39056
+ return runIdToQueueKey.get(runId);
39057
+ }
39058
+ function setRunIdQueueKey(runId, queueKey) {
39059
+ runIdToQueueKey.set(runId, queueKey);
39060
+ }
39061
+ function deleteRunIdQueueKey(runId) {
39062
+ const queueKey = runIdToQueueKey.get(runId);
39063
+ runIdToQueueKey.delete(runId);
39064
+ return queueKey;
39065
+ }
39066
+ function syncRunningTurnQueueKeys(turns, queueKey) {
39067
+ for (const running of turns.filter((t) => t.lastClientState === "running")) {
39068
+ runIdToQueueKey.set(running.turnId, queueKey);
39069
+ }
39070
+ }
39071
+
39072
+ // src/prompt-turn-queue/runner/run-local-revert-before-queued-prompt.ts
39073
+ import fs32 from "node:fs";
38855
39074
  async function runLocalRevertBeforeQueuedPrompt(next, deps) {
38856
39075
  if (next.serverState !== "requeued_with_revert") return true;
38857
39076
  const sid = next.sessionId;
@@ -38873,30 +39092,23 @@ async function runLocalRevertBeforeQueuedPrompt(next, deps) {
38873
39092
  }
38874
39093
  return res.ok;
38875
39094
  }
38876
- function dispatchLocalPrompt(next, deps) {
38877
- const pl = next.payload;
38878
- const rawParent = pl["sessionParent"];
38879
- const sessionParent = rawParent === "bridge_root" || rawParent === "worktrees_root" ? rawParent : void 0;
38880
- const rawParentPath = pl["sessionParentPath"];
38881
- const sessionParentPath = typeof rawParentPath === "string" && rawParentPath.trim() !== "" ? rawParentPath.trim() : void 0;
38882
- const msg = {
38883
- type: "prompt",
38884
- sessionId: next.sessionId,
38885
- runId: next.turnId,
38886
- prompt: pl.prompt,
38887
- mode: typeof pl.mode === "string" ? pl.mode : "agent",
38888
- isNewSession: pl.isNewSession === true,
38889
- ...sessionParent ? { sessionParent } : {},
38890
- ...sessionParentPath ? { sessionParentPath } : {},
38891
- ...typeof pl.followUpCatalogPromptId === "string" ? { followUpCatalogPromptId: pl.followUpCatalogPromptId } : {},
38892
- ...Array.isArray(pl.sessionChangeSummaryFilePaths) ? { sessionChangeSummaryFilePaths: pl.sessionChangeSummaryFilePaths } : {},
38893
- ...Array.isArray(pl.sessionChangeSummaryFileSnapshots) ? { sessionChangeSummaryFileSnapshots: pl.sessionChangeSummaryFileSnapshots } : {},
38894
- ...typeof pl.agentType === "string" && pl.agentType.trim() ? { agentType: pl.agentType.trim() } : {},
38895
- ...pl.agentConfig != null && typeof pl.agentConfig === "object" && !Array.isArray(pl.agentConfig) && Object.keys(pl.agentConfig).length > 0 ? { agentConfig: pl.agentConfig } : {},
38896
- ...Array.isArray(pl.attachments) && pl.attachments.length > 0 ? { attachments: pl.attachments } : {}
38897
- };
38898
- handleBridgePrompt(msg, deps);
39095
+
39096
+ // src/prompt-turn-queue/runner/finalize-prompt-turn-on-bridge.ts
39097
+ async function finalizePromptTurnOnBridge(getWs, runId, success2, opts) {
39098
+ if (!runId) return false;
39099
+ const queueKey = deleteRunIdQueueKey(runId);
39100
+ if (!queueKey) return false;
39101
+ const f = await readPersistedQueue(queueKey);
39102
+ if (!f) return false;
39103
+ const t = f.turns.find((x) => x.turnId === runId);
39104
+ if (!t) return false;
39105
+ t.lastClientState = opts?.terminalClientState ?? (success2 ? "stopped" : "failed");
39106
+ await writePersistedQueue(f);
39107
+ sendPromptQueueClientReport(getWs(), { [queueKey]: [{ turnId: runId, clientState: t.lastClientState }] });
39108
+ return true;
38899
39109
  }
39110
+
39111
+ // src/prompt-turn-queue/runner/apply-prompt-queue-state-from-server.ts
38900
39112
  async function applyPromptQueueStateFromServer(msg, deps) {
38901
39113
  const raw = msg.queues;
38902
39114
  if (!raw || typeof raw !== "object") return;
@@ -38910,9 +39122,7 @@ async function applyPromptQueueStateFromServer(msg, deps) {
38910
39122
  if (!Array.isArray(serverTurns)) continue;
38911
39123
  const file2 = await readPersistedQueue(queueKey);
38912
39124
  if (!file2) continue;
38913
- for (const running of file2.turns.filter((t) => t.lastClientState === "running")) {
38914
- runIdToQueueKey.set(running.turnId, queueKey);
38915
- }
39125
+ syncRunningTurnQueueKeys(file2.turns, queueKey);
38916
39126
  }
38917
39127
  for (const [queueKey, serverTurns] of Object.entries(raw)) {
38918
39128
  if (!Array.isArray(serverTurns)) continue;
@@ -38957,7 +39167,7 @@ async function applyPromptQueueStateFromServer(msg, deps) {
38957
39167
  }
38958
39168
  next.lastClientState = "running";
38959
39169
  await writePersistedQueue(file2);
38960
- runIdToQueueKey.set(next.turnId, queueKey);
39170
+ setRunIdQueueKey(next.turnId, queueKey);
38961
39171
  startedThisTick.add(next.turnId);
38962
39172
  report[queueKey] = [{ turnId: next.turnId, clientState: "running" }];
38963
39173
  }
@@ -38970,24 +39180,10 @@ async function applyPromptQueueStateFromServer(msg, deps) {
38970
39180
  if (!file2) continue;
38971
39181
  const running = file2.turns.find((t) => t.lastClientState === "running");
38972
39182
  if (!running || !startedThisTick.has(running.turnId)) continue;
38973
- if (runIdToQueueKey.get(running.turnId) !== queueKey) continue;
39183
+ if (getRunIdQueueKey(running.turnId) !== queueKey) continue;
38974
39184
  dispatchLocalPrompt(running, deps);
38975
39185
  }
38976
39186
  }
38977
- async function finalizePromptTurnOnBridge(getWs, runId, success2, opts) {
38978
- if (!runId) return false;
38979
- const queueKey = runIdToQueueKey.get(runId);
38980
- runIdToQueueKey.delete(runId);
38981
- if (!queueKey) return false;
38982
- const f = await readPersistedQueue(queueKey);
38983
- if (!f) return false;
38984
- const t = f.turns.find((x) => x.turnId === runId);
38985
- if (!t) return false;
38986
- t.lastClientState = opts?.terminalClientState ?? (success2 ? "stopped" : "failed");
38987
- await writePersistedQueue(f);
38988
- sendPromptQueueClientReport(getWs(), { [queueKey]: [{ turnId: runId, clientState: t.lastClientState }] });
38989
- return true;
38990
- }
38991
39187
 
38992
39188
  // src/agents/acp/from-bridge/bridge-prompt-wiring.ts
38993
39189
  function createBridgePromptSenders(deps, getWs) {
@@ -39034,6 +39230,91 @@ function createBridgePromptSenders(deps, getWs) {
39034
39230
  return { sendBridgeMessage, sendResult: sendResult2, sendSessionUpdate };
39035
39231
  }
39036
39232
 
39233
+ // src/agents/acp/from-bridge/handle-bridge-prompt/parse-bridge-attachments.ts
39234
+ function parseBridgeAttachments(msg) {
39235
+ const raw = msg.attachments;
39236
+ if (!Array.isArray(raw)) return [];
39237
+ const out = [];
39238
+ for (const x of raw) {
39239
+ if (x === null || typeof x !== "object" || Array.isArray(x)) continue;
39240
+ const o = x;
39241
+ const id = typeof o.attachmentId === "string" ? o.attachmentId.trim() : "";
39242
+ if (!id) continue;
39243
+ const mt = typeof o.mimeType === "string" && o.mimeType.trim() ? o.mimeType.trim() : "application/octet-stream";
39244
+ out.push({ attachmentId: id, mimeType: mt });
39245
+ }
39246
+ return out;
39247
+ }
39248
+
39249
+ // src/agents/acp/from-bridge/handle-bridge-prompt/parse-worktree-base-branches.ts
39250
+ function parseWorktreeBaseBranches(msg) {
39251
+ const raw = msg.worktreeBaseBranches;
39252
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
39253
+ const out = {};
39254
+ for (const [k, v] of Object.entries(raw)) {
39255
+ if (typeof k !== "string" || typeof v !== "string") continue;
39256
+ const rel = k.trim();
39257
+ const branch = v.trim();
39258
+ if (rel && branch) out[rel] = branch;
39259
+ }
39260
+ return Object.keys(out).length > 0 ? out : void 0;
39261
+ }
39262
+
39263
+ // src/agents/acp/change-summary/decrypt-change-summary-file-input.ts
39264
+ function decryptChangeSummaryFileInput(row, e2ee) {
39265
+ if (!e2ee) return row;
39266
+ for (const field of ["path", "patchContent", "oldText", "newText"]) {
39267
+ const raw = row[field];
39268
+ if (typeof raw !== "string" || raw.trim() === "") continue;
39269
+ let o;
39270
+ try {
39271
+ o = JSON.parse(raw);
39272
+ } catch {
39273
+ continue;
39274
+ }
39275
+ if (!isE2eeEnvelope(o.ee)) continue;
39276
+ try {
39277
+ const d = e2ee.decryptMessage(o);
39278
+ const out = {
39279
+ path: typeof d.path === "string" ? d.path : row.path
39280
+ };
39281
+ if (d.directoryRemoved === true) out.directoryRemoved = true;
39282
+ else if (row.directoryRemoved === true) out.directoryRemoved = true;
39283
+ if (typeof d.patchContent === "string") out.patchContent = d.patchContent;
39284
+ else if (typeof row.patchContent === "string" && row.patchContent !== raw) out.patchContent = row.patchContent;
39285
+ if (typeof d.oldText === "string") out.oldText = d.oldText;
39286
+ else if (typeof row.oldText === "string") out.oldText = row.oldText;
39287
+ if (typeof d.newText === "string") out.newText = d.newText;
39288
+ else if (typeof row.newText === "string") out.newText = row.newText;
39289
+ return out;
39290
+ } catch {
39291
+ return row;
39292
+ }
39293
+ }
39294
+ return row;
39295
+ }
39296
+
39297
+ // src/agents/acp/change-summary/resolve-change-summary-prompt-for-agent.ts
39298
+ function hasSummarizePayload(f) {
39299
+ return f.directoryRemoved === true || f.patchContent != null && f.patchContent.trim() !== "" || f.oldText != null && f.oldText.trim() !== "" || f.newText != null && f.newText.trim() !== "";
39300
+ }
39301
+ function resolveChangeSummaryPromptForAgent(params) {
39302
+ const isBuiltin = params.followUpCatalogPromptId === BUILTIN_SESSION_CHANGE_SUMMARY_FOLLOW_UP_CATALOG_PROMPT_ID;
39303
+ const snaps = params.sessionChangeSummaryFileSnapshots;
39304
+ if (!isBuiltin || !snaps || snaps.length === 0) {
39305
+ return { promptText: params.bridgePromptText, sessionChangeSummaryFilePaths: void 0 };
39306
+ }
39307
+ const decrypted = dedupeSessionFileChangesByPath(snaps.map((row) => decryptChangeSummaryFileInput(row, params.e2ee)));
39308
+ const withPayload = decrypted.filter(hasSummarizePayload);
39309
+ if (withPayload.length === 0) {
39310
+ return { promptText: params.bridgePromptText, sessionChangeSummaryFilePaths: void 0 };
39311
+ }
39312
+ return {
39313
+ promptText: buildSessionChangeSummaryPrompt(withPayload),
39314
+ sessionChangeSummaryFilePaths: withPayload.map((f) => f.path)
39315
+ };
39316
+ }
39317
+
39037
39318
  // src/agents/acp/from-bridge/bridge-prompt-preamble.ts
39038
39319
  import { execFile as execFile8 } from "node:child_process";
39039
39320
  import { promisify as promisify8 } from "node:util";
@@ -39086,9 +39367,9 @@ function parseChangeSummarySnapshots(raw) {
39086
39367
  for (const item of raw) {
39087
39368
  if (!item || typeof item !== "object") continue;
39088
39369
  const o = item;
39089
- const path43 = typeof o.path === "string" && o.path.trim() !== "" ? o.path.trim() : "";
39090
- if (!path43) continue;
39091
- const row = { path: path43 };
39370
+ const path46 = typeof o.path === "string" && o.path.trim() !== "" ? o.path.trim() : "";
39371
+ if (!path46) continue;
39372
+ const row = { path: path46 };
39092
39373
  if (typeof o.patchContent === "string") row.patchContent = o.patchContent;
39093
39374
  if (typeof o.oldText === "string") row.oldText = o.oldText;
39094
39375
  if (typeof o.newText === "string") row.newText = o.newText;
@@ -39105,76 +39386,73 @@ function parseFollowUpFieldsFromPromptMessage(msg) {
39105
39386
  return { followUpCatalogPromptId, sessionChangeSummaryFilePaths, sessionChangeSummaryFileSnapshots };
39106
39387
  }
39107
39388
 
39108
- // src/agents/acp/change-summary/decrypt-change-summary-file-input.ts
39109
- function decryptChangeSummaryFileInput(row, e2ee) {
39110
- if (!e2ee) return row;
39111
- for (const field of ["path", "patchContent", "oldText", "newText"]) {
39112
- const raw = row[field];
39113
- if (typeof raw !== "string" || raw.trim() === "") continue;
39114
- let o;
39115
- try {
39116
- o = JSON.parse(raw);
39117
- } catch {
39118
- continue;
39119
- }
39120
- if (!isE2eeEnvelope(o.ee)) continue;
39121
- try {
39122
- const d = e2ee.decryptMessage(o);
39123
- const out = {
39124
- path: typeof d.path === "string" ? d.path : row.path
39125
- };
39126
- if (d.directoryRemoved === true) out.directoryRemoved = true;
39127
- else if (row.directoryRemoved === true) out.directoryRemoved = true;
39128
- if (typeof d.patchContent === "string") out.patchContent = d.patchContent;
39129
- else if (typeof row.patchContent === "string" && row.patchContent !== raw) out.patchContent = row.patchContent;
39130
- if (typeof d.oldText === "string") out.oldText = d.oldText;
39131
- else if (typeof row.oldText === "string") out.oldText = row.oldText;
39132
- if (typeof d.newText === "string") out.newText = d.newText;
39133
- else if (typeof row.newText === "string") out.newText = row.newText;
39134
- return out;
39135
- } catch {
39136
- return row;
39137
- }
39138
- }
39139
- return row;
39140
- }
39141
-
39142
- // src/agents/acp/change-summary/resolve-change-summary-prompt-for-agent.ts
39143
- function hasSummarizePayload(f) {
39144
- return f.directoryRemoved === true || f.patchContent != null && f.patchContent.trim() !== "" || f.oldText != null && f.oldText.trim() !== "" || f.newText != null && f.newText.trim() !== "";
39145
- }
39146
- function resolveChangeSummaryPromptForAgent(params) {
39147
- const isBuiltin = params.followUpCatalogPromptId === BUILTIN_SESSION_CHANGE_SUMMARY_FOLLOW_UP_CATALOG_PROMPT_ID;
39148
- const snaps = params.sessionChangeSummaryFileSnapshots;
39149
- if (!isBuiltin || !snaps || snaps.length === 0) {
39150
- return { promptText: params.bridgePromptText, sessionChangeSummaryFilePaths: void 0 };
39151
- }
39152
- const decrypted = dedupeSessionFileChangesByPath(snaps.map((row) => decryptChangeSummaryFileInput(row, params.e2ee)));
39153
- const withPayload = decrypted.filter(hasSummarizePayload);
39154
- if (withPayload.length === 0) {
39155
- return { promptText: params.bridgePromptText, sessionChangeSummaryFilePaths: void 0 };
39389
+ // src/agents/acp/from-bridge/handle-bridge-prompt/run-preamble-and-prompt.ts
39390
+ async function runPreambleAndPrompt(params) {
39391
+ const {
39392
+ deps,
39393
+ msg,
39394
+ getWs,
39395
+ log: log2,
39396
+ sessionWorktreeManager,
39397
+ sessionId,
39398
+ runId,
39399
+ promptText,
39400
+ attachments,
39401
+ mode,
39402
+ agentType,
39403
+ agentId,
39404
+ agentConfig,
39405
+ resolvedCwd,
39406
+ senders: { sendResult: sendResult2, sendSessionUpdate }
39407
+ } = params;
39408
+ const effectiveCwd = resolveSessionParentPathForAgentProcess(resolvedCwd);
39409
+ await runBridgePromptPreamble({
39410
+ getWs,
39411
+ log: log2,
39412
+ sessionWorktreeManager,
39413
+ sessionId,
39414
+ runId,
39415
+ effectiveCwd
39416
+ });
39417
+ const {
39418
+ followUpCatalogPromptId,
39419
+ sessionChangeSummaryFilePaths: pathsFromBridge,
39420
+ sessionChangeSummaryFileSnapshots
39421
+ } = parseFollowUpFieldsFromPromptMessage(msg);
39422
+ const { promptText: resolvedPromptText, sessionChangeSummaryFilePaths } = resolveChangeSummaryPromptForAgent({
39423
+ followUpCatalogPromptId,
39424
+ sessionChangeSummaryFileSnapshots,
39425
+ bridgePromptText: promptText,
39426
+ e2ee: deps.e2ee
39427
+ });
39428
+ if (sessionChangeSummaryFileSnapshots && sessionChangeSummaryFileSnapshots.length > 0 && resolvedPromptText === promptText) {
39429
+ deps.log(
39430
+ "[Agent] Change-summary snapshots were present but the prompt was not rebuilt (decrypt failed or empty payloads); sending the bridge prompt as-is."
39431
+ );
39156
39432
  }
39157
- return {
39158
- promptText: buildSessionChangeSummaryPrompt(withPayload),
39159
- sessionChangeSummaryFilePaths: withPayload.map((f) => f.path)
39160
- };
39433
+ const pathsForUpload = sessionChangeSummaryFilePaths ?? pathsFromBridge;
39434
+ deps.acpManager.handlePrompt({
39435
+ promptText: resolvedPromptText,
39436
+ promptId: msg.id,
39437
+ sessionId,
39438
+ runId,
39439
+ mode,
39440
+ agentType,
39441
+ agentId,
39442
+ agentConfig,
39443
+ sessionParentPath: effectiveCwd,
39444
+ sendResult: sendResult2,
39445
+ sendSessionUpdate,
39446
+ followUpCatalogPromptId,
39447
+ sessionChangeSummaryFilePaths: pathsForUpload,
39448
+ cloudApiBaseUrl: deps.cloudApiBaseUrl,
39449
+ getCloudAccessToken: deps.getCloudAccessToken,
39450
+ e2ee: deps.e2ee,
39451
+ ...attachments.length > 0 ? { attachments } : {}
39452
+ });
39161
39453
  }
39162
39454
 
39163
- // src/agents/acp/from-bridge/handle-bridge-prompt.ts
39164
- function parseBridgeAttachments(msg) {
39165
- const raw = msg.attachments;
39166
- if (!Array.isArray(raw)) return [];
39167
- const out = [];
39168
- for (const x of raw) {
39169
- if (x === null || typeof x !== "object" || Array.isArray(x)) continue;
39170
- const o = x;
39171
- const id = typeof o.attachmentId === "string" ? o.attachmentId.trim() : "";
39172
- if (!id) continue;
39173
- const mt = typeof o.mimeType === "string" && o.mimeType.trim() ? o.mimeType.trim() : "application/octet-stream";
39174
- out.push({ attachmentId: id, mimeType: mt });
39175
- }
39176
- return out;
39177
- }
39455
+ // src/agents/acp/from-bridge/handle-bridge-prompt/handle-bridge-prompt.ts
39178
39456
  function handleBridgePrompt(msg, deps) {
39179
39457
  const { getWs, log: log2, acpManager, sessionWorktreeManager } = deps;
39180
39458
  const rawPrompt = msg.prompt;
@@ -39183,12 +39461,12 @@ function handleBridgePrompt(msg, deps) {
39183
39461
  const sessionId = msg.sessionId;
39184
39462
  const runId = typeof msg.runId === "string" ? msg.runId : void 0;
39185
39463
  const promptId = typeof msg.id === "string" ? msg.id : void 0;
39186
- const { sendBridgeMessage, sendResult: sendResult2, sendSessionUpdate } = createBridgePromptSenders(deps, getWs);
39464
+ const senders = createBridgePromptSenders(deps, getWs);
39187
39465
  if (!promptText.trim() && attachments.length === 0) {
39188
39466
  log2(
39189
39467
  `[Bridge service] Prompt ignored: empty or missing prompt text (session ${typeof msg.sessionId === "string" ? msg.sessionId.slice(0, 8) : "\u2014"}\u2026, run ${typeof msg.runId === "string" ? msg.runId.slice(0, 8) : "\u2014"}\u2026).`
39190
39468
  );
39191
- sendBridgeMessage(
39469
+ senders.sendBridgeMessage(
39192
39470
  {
39193
39471
  type: "prompt_result",
39194
39472
  ...promptId ? { id: promptId } : {},
@@ -39210,57 +39488,50 @@ function handleBridgePrompt(msg, deps) {
39210
39488
  const agentId = typeof rawAgentId === "string" && rawAgentId.trim() !== "" ? rawAgentId.trim() : null;
39211
39489
  const mode = typeof msg.mode === "string" && msg.mode.trim() ? msg.mode.trim() : void 0;
39212
39490
  const agentConfig = msg.agentConfig != null && typeof msg.agentConfig === "object" && !Array.isArray(msg.agentConfig) ? msg.agentConfig : void 0;
39491
+ const worktreeBaseBranches = parseWorktreeBaseBranches(msg);
39213
39492
  acpManager.logPromptReceivedFromBridge({ agentType, mode });
39214
- async function preambleAndPrompt(resolvedCwd) {
39215
- const effectiveCwd = resolveSessionParentPathForAgentProcess(resolvedCwd);
39216
- await runBridgePromptPreamble({
39493
+ void sessionWorktreeManager.resolveSessionParentPathForPrompt(sessionId, {
39494
+ isNewSession,
39495
+ sessionParent,
39496
+ sessionParentPath,
39497
+ ...worktreeBaseBranches ? { worktreeBaseBranches } : {}
39498
+ }).then(
39499
+ (cwd) => runPreambleAndPrompt({
39500
+ deps,
39501
+ msg,
39217
39502
  getWs,
39218
39503
  log: log2,
39219
39504
  sessionWorktreeManager,
39220
39505
  sessionId,
39221
39506
  runId,
39222
- effectiveCwd
39223
- });
39224
- const {
39225
- followUpCatalogPromptId,
39226
- sessionChangeSummaryFilePaths: pathsFromBridge,
39227
- sessionChangeSummaryFileSnapshots
39228
- } = parseFollowUpFieldsFromPromptMessage(msg);
39229
- const { promptText: resolvedPromptText, sessionChangeSummaryFilePaths } = resolveChangeSummaryPromptForAgent({
39230
- followUpCatalogPromptId,
39231
- sessionChangeSummaryFileSnapshots,
39232
- bridgePromptText: promptText,
39233
- e2ee: deps.e2ee
39234
- });
39235
- if (sessionChangeSummaryFileSnapshots && sessionChangeSummaryFileSnapshots.length > 0 && resolvedPromptText === promptText) {
39236
- deps.log(
39237
- "[Agent] Change-summary snapshots were present but the prompt was not rebuilt (decrypt failed or empty payloads); sending the bridge prompt as-is."
39238
- );
39239
- }
39240
- const pathsForUpload = sessionChangeSummaryFilePaths ?? pathsFromBridge;
39241
- acpManager.handlePrompt({
39242
- promptText: resolvedPromptText,
39243
- promptId: msg.id,
39507
+ promptText,
39508
+ attachments,
39509
+ mode,
39510
+ agentType,
39511
+ agentId,
39512
+ agentConfig,
39513
+ resolvedCwd: cwd,
39514
+ senders
39515
+ })
39516
+ ).catch((err) => {
39517
+ log2(`[Agent] Session parent path resolve failed: ${err instanceof Error ? err.message : String(err)}`);
39518
+ void runPreambleAndPrompt({
39519
+ deps,
39520
+ msg,
39521
+ getWs,
39522
+ log: log2,
39523
+ sessionWorktreeManager,
39244
39524
  sessionId,
39245
39525
  runId,
39526
+ promptText,
39527
+ attachments,
39246
39528
  mode,
39247
39529
  agentType,
39248
39530
  agentId,
39249
39531
  agentConfig,
39250
- sessionParentPath: effectiveCwd,
39251
- sendResult: sendResult2,
39252
- sendSessionUpdate,
39253
- followUpCatalogPromptId,
39254
- sessionChangeSummaryFilePaths: pathsForUpload,
39255
- cloudApiBaseUrl: deps.cloudApiBaseUrl,
39256
- getCloudAccessToken: deps.getCloudAccessToken,
39257
- e2ee: deps.e2ee,
39258
- ...attachments.length > 0 ? { attachments } : {}
39532
+ resolvedCwd: void 0,
39533
+ senders
39259
39534
  });
39260
- }
39261
- void sessionWorktreeManager.resolveSessionParentPathForPrompt(sessionId, { isNewSession, sessionParent, sessionParentPath }).then((cwd) => preambleAndPrompt(cwd)).catch((err) => {
39262
- log2(`[Agent] Session parent path resolve failed: ${err instanceof Error ? err.message : String(err)}`);
39263
- void preambleAndPrompt(void 0);
39264
39535
  });
39265
39536
  }
39266
39537
 
@@ -39308,8 +39579,8 @@ function randomSecret() {
39308
39579
  }
39309
39580
  return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
39310
39581
  }
39311
- async function requestPreviewApi(port, secret, method, path43, body) {
39312
- const url2 = `http://127.0.0.1:${port}${path43}`;
39582
+ async function requestPreviewApi(port, secret, method, path46, body) {
39583
+ const url2 = `http://127.0.0.1:${port}${path46}`;
39313
39584
  const headers = {
39314
39585
  [PREVIEW_SECRET_HEADER]: secret,
39315
39586
  "Content-Type": "application/json"
@@ -39321,7 +39592,7 @@ async function requestPreviewApi(port, secret, method, path43, body) {
39321
39592
  });
39322
39593
  const data = await res.json().catch(() => ({}));
39323
39594
  if (!res.ok) {
39324
- throw new Error(data?.error ?? `Preview API ${method} ${path43}: ${res.status}`);
39595
+ throw new Error(data?.error ?? `Preview API ${method} ${path46}: ${res.status}`);
39325
39596
  }
39326
39597
  return data;
39327
39598
  }
@@ -39489,11 +39760,11 @@ var handleSkillCallMessage = (msg, { getWs, log: log2 }) => {
39489
39760
  import fs34 from "node:fs";
39490
39761
 
39491
39762
  // src/files/ensure-under-cwd.ts
39492
- import path35 from "node:path";
39763
+ import path37 from "node:path";
39493
39764
  function ensureUnderCwd(relativePath, cwd = getBridgeRoot()) {
39494
- const normalized = path35.normalize(relativePath).replace(/^(\.\/)+/, "");
39495
- const resolved = path35.resolve(cwd, normalized);
39496
- if (!resolved.startsWith(cwd + path35.sep) && resolved !== cwd) {
39765
+ const normalized = path37.normalize(relativePath).replace(/^(\.\/)+/, "");
39766
+ const resolved = path37.resolve(cwd, normalized);
39767
+ if (!resolved.startsWith(cwd + path37.sep) && resolved !== cwd) {
39497
39768
  return null;
39498
39769
  }
39499
39770
  return resolved;
@@ -39503,11 +39774,11 @@ function ensureUnderCwd(relativePath, cwd = getBridgeRoot()) {
39503
39774
  var LIST_DIR_YIELD_EVERY = 256;
39504
39775
 
39505
39776
  // src/files/list-dir/map-dir-entry.ts
39506
- import path36 from "node:path";
39777
+ import path38 from "node:path";
39507
39778
  import fs33 from "node:fs";
39508
39779
  async function mapDirEntry(d, relativePath, resolved) {
39509
- const entryPath = path36.join(relativePath || ".", d.name).replace(/\\/g, "/");
39510
- const fullPath = path36.join(resolved, d.name);
39780
+ const entryPath = path38.join(relativePath || ".", d.name).replace(/\\/g, "/");
39781
+ const fullPath = path38.join(resolved, d.name);
39511
39782
  let isDir = d.isDirectory();
39512
39783
  if (d.isSymbolicLink()) {
39513
39784
  try {
@@ -39858,13 +40129,13 @@ function resolveFileBrowserSessionParent(sessionWorktreeManager, sessionId) {
39858
40129
  }
39859
40130
 
39860
40131
  // src/files/handle-file-browser-search.ts
39861
- import path37 from "node:path";
40132
+ import path39 from "node:path";
39862
40133
  var SEARCH_LIMIT = 100;
39863
40134
  function handleFileBrowserSearch(msg, socket, e2ee, sessionWorktreeManager) {
39864
40135
  void (async () => {
39865
40136
  await yieldToEventLoop();
39866
40137
  const q = typeof msg.q === "string" ? msg.q : "";
39867
- const sessionParentPath = path37.resolve(
40138
+ const sessionParentPath = path39.resolve(
39868
40139
  sessionWorktreeManager != null ? resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : getBridgeRoot()
39869
40140
  );
39870
40141
  if (!await bridgeFileIndexIsPopulated(sessionParentPath)) {
@@ -39984,7 +40255,7 @@ function handleSkillLayoutRequest(msg, deps) {
39984
40255
 
39985
40256
  // src/skills/install-remote-skills.ts
39986
40257
  import fs38 from "node:fs";
39987
- import path38 from "node:path";
40258
+ import path40 from "node:path";
39988
40259
  function installRemoteSkills(cwd, targetDir, items) {
39989
40260
  const installed2 = [];
39990
40261
  if (!Array.isArray(items)) {
@@ -39995,11 +40266,11 @@ function installRemoteSkills(cwd, targetDir, items) {
39995
40266
  if (typeof item.sourceId !== "string" || typeof item.skillName !== "string" || typeof item.versionHash !== "string" || !Array.isArray(item.files)) {
39996
40267
  continue;
39997
40268
  }
39998
- const skillDir = path38.join(cwd, targetDir, item.skillName);
40269
+ const skillDir = path40.join(cwd, targetDir, item.skillName);
39999
40270
  for (const f of item.files) {
40000
40271
  if (typeof f.path !== "string" || !f.text && !f.base64) continue;
40001
- const dest = path38.join(skillDir, f.path);
40002
- fs38.mkdirSync(path38.dirname(dest), { recursive: true });
40272
+ const dest = path40.join(skillDir, f.path);
40273
+ fs38.mkdirSync(path40.dirname(dest), { recursive: true });
40003
40274
  if (f.text !== void 0) {
40004
40275
  fs38.writeFileSync(dest, f.text, "utf8");
40005
40276
  } else if (f.base64) {
@@ -40215,6 +40486,148 @@ var handleDevServersConfig = (msg, deps) => {
40215
40486
  deps.devServerManager?.applyConfig(devServers ?? []);
40216
40487
  };
40217
40488
 
40489
+ // src/git/bridge-git-context.ts
40490
+ import * as path41 from "node:path";
40491
+
40492
+ // src/git/branches/get-current-branch.ts
40493
+ async function getCurrentBranch(repoPath) {
40494
+ try {
40495
+ const git = cliSimpleGit(repoPath);
40496
+ const branch = await git.revparse(["--abbrev-ref", "HEAD"]);
40497
+ const trimmed2 = typeof branch === "string" ? branch.trim() : "";
40498
+ if (!trimmed2 || trimmed2 === "HEAD") return null;
40499
+ return trimmed2;
40500
+ } catch {
40501
+ return null;
40502
+ }
40503
+ }
40504
+
40505
+ // src/git/branches/list-repo-branch-refs.ts
40506
+ function normalizeBranchRef(raw) {
40507
+ const trimmed2 = raw.trim();
40508
+ if (!trimmed2 || trimmed2 === "HEAD") return null;
40509
+ if (trimmed2.startsWith("refs/heads/")) return trimmed2.slice("refs/heads/".length);
40510
+ if (trimmed2.startsWith("refs/remotes/")) return trimmed2.slice("refs/remotes/".length);
40511
+ return trimmed2;
40512
+ }
40513
+ async function listRepoBranchRefs(repoPath) {
40514
+ try {
40515
+ const git = cliSimpleGit(repoPath);
40516
+ const out = await git.raw([
40517
+ "for-each-ref",
40518
+ "--sort=-committerdate",
40519
+ "--format=%(refname:short)",
40520
+ "refs/heads",
40521
+ "refs/remotes"
40522
+ ]);
40523
+ const lines = out.split("\n").map((line) => normalizeBranchRef(line)).filter((line) => Boolean(line));
40524
+ const seen = /* @__PURE__ */ new Set();
40525
+ const ordered = [];
40526
+ for (const name of lines) {
40527
+ if (seen.has(name)) continue;
40528
+ seen.add(name);
40529
+ ordered.push(name);
40530
+ }
40531
+ return ordered;
40532
+ } catch {
40533
+ return [];
40534
+ }
40535
+ }
40536
+
40537
+ // src/git/bridge-git-context.ts
40538
+ function folderNameForRelPath(relPath, bridgeRoot) {
40539
+ if (relPath === "." || relPath === "") {
40540
+ return path41.basename(path41.resolve(bridgeRoot)) || "repo";
40541
+ }
40542
+ return path41.basename(relPath) || relPath;
40543
+ }
40544
+ async function discoverGitReposForBridgeContext(bridgeRoot) {
40545
+ const root = path41.resolve(bridgeRoot);
40546
+ if (await isGitRepoDirectory(root)) {
40547
+ const remoteUrl = await getRemoteOriginUrl(root);
40548
+ return [{ absolutePath: root, remoteUrl }];
40549
+ }
40550
+ const [deep, shallow] = await Promise.all([discoverGitReposUnderRoot(root), discoverGitRepos(root)]);
40551
+ const byPath = /* @__PURE__ */ new Map();
40552
+ for (const repo of [...deep, ...shallow]) {
40553
+ byPath.set(path41.resolve(repo.absolutePath), repo);
40554
+ }
40555
+ return [...byPath.values()];
40556
+ }
40557
+ async function getBridgeGitContext(bridgeRoot = getBridgeRoot()) {
40558
+ const bridgeResolved = path41.resolve(bridgeRoot);
40559
+ const repos = await discoverGitReposForBridgeContext(bridgeResolved);
40560
+ const rows = [];
40561
+ for (const repo of repos) {
40562
+ let rel = path41.relative(bridgeResolved, repo.absolutePath);
40563
+ if (rel.startsWith("..") || path41.isAbsolute(rel)) continue;
40564
+ const relPath = rel === "" ? "." : rel.replace(/\\/g, "/");
40565
+ const currentBranch = await getCurrentBranch(repo.absolutePath);
40566
+ const remoteUrl = repo.remoteUrl.trim() || null;
40567
+ rows.push({
40568
+ relPath,
40569
+ folderName: folderNameForRelPath(relPath, bridgeResolved),
40570
+ currentBranch,
40571
+ remoteUrl
40572
+ });
40573
+ }
40574
+ rows.sort((a, b) => a.relPath.localeCompare(b.relPath));
40575
+ return rows;
40576
+ }
40577
+ async function listRepoBranchesForBridge(repoRelPath, bridgeRoot = getBridgeRoot()) {
40578
+ const bridgeResolved = path41.resolve(bridgeRoot);
40579
+ const rel = repoRelPath.trim() === "." ? "" : repoRelPath.trim().replace(/\\/g, "/");
40580
+ const repoPath = rel === "" ? bridgeResolved : path41.join(bridgeResolved, rel);
40581
+ const resolved = path41.resolve(repoPath);
40582
+ if (!resolved.startsWith(bridgeResolved + path41.sep) && resolved !== bridgeResolved) {
40583
+ return [];
40584
+ }
40585
+ return listRepoBranchRefs(resolved);
40586
+ }
40587
+
40588
+ // src/routing/handlers/bridge-git-context-messages.ts
40589
+ function handleBridgeGitContextRequestMessage(msg, getWs) {
40590
+ void (async () => {
40591
+ const socket = getWs();
40592
+ if (!socket) return;
40593
+ try {
40594
+ const repos = await getBridgeGitContext();
40595
+ sendWsMessage(socket, { type: "bridge_git_context_response", id: msg.id, repos });
40596
+ } catch (e) {
40597
+ sendWsMessage(socket, {
40598
+ type: "bridge_git_context_response",
40599
+ id: msg.id,
40600
+ error: e instanceof Error ? e.message : String(e)
40601
+ });
40602
+ }
40603
+ })();
40604
+ }
40605
+ function handleListRepoBranchesRequestMessage(msg, getWs) {
40606
+ void (async () => {
40607
+ const socket = getWs();
40608
+ if (!socket) return;
40609
+ const repoRelPath = typeof msg.repoRelPath === "string" ? msg.repoRelPath.trim() : "";
40610
+ if (!repoRelPath) {
40611
+ sendWsMessage(socket, {
40612
+ type: "list_repo_branches_response",
40613
+ id: msg.id,
40614
+ error: "repoRelPath required"
40615
+ });
40616
+ return;
40617
+ }
40618
+ try {
40619
+ const branches = await listRepoBranchesForBridge(repoRelPath);
40620
+ sendWsMessage(socket, { type: "list_repo_branches_response", id: msg.id, branches });
40621
+ } catch (e) {
40622
+ sendWsMessage(socket, {
40623
+ type: "list_repo_branches_response",
40624
+ id: msg.id,
40625
+ error: e instanceof Error ? e.message : String(e)
40626
+ });
40627
+ }
40628
+ })();
40629
+ }
40630
+
40218
40631
  // src/routing/dispatch-bridge-message.ts
40219
40632
  function dispatchBridgeMessage(msg, deps) {
40220
40633
  switch (msg.type) {
@@ -40278,6 +40691,12 @@ function dispatchBridgeMessage(msg, deps) {
40278
40691
  case "refresh_local_skills":
40279
40692
  handleRefreshLocalSkills(msg, deps);
40280
40693
  break;
40694
+ case "bridge_git_context_request":
40695
+ handleBridgeGitContextRequestMessage(msg, deps.getWs);
40696
+ break;
40697
+ case "list_repo_branches_request":
40698
+ handleListRepoBranchesRequestMessage(msg, deps.getWs);
40699
+ break;
40281
40700
  }
40282
40701
  }
40283
40702
 
@@ -40290,8 +40709,10 @@ function normalizeInboundBridgeWebSocketJson(data) {
40290
40709
  }
40291
40710
  return data;
40292
40711
  }
40293
- function handleBridgeMessage(data, deps) {
40294
- if (!deps.getWs()) return;
40712
+ function handleBridgeMessage(data, deps, sourceWs) {
40713
+ const active = deps.getWs();
40714
+ if (!active) return;
40715
+ if (sourceWs != null && sourceWs !== active) return;
40295
40716
  const msg = parseApiToBridgeMessage(normalizeInboundBridgeWebSocketJson(data), deps.log);
40296
40717
  if (!msg) return;
40297
40718
  dispatchBridgeMessage(msg, deps);
@@ -40343,6 +40764,7 @@ function createMainBridgeWebSocketLifecycle(params) {
40343
40764
  } = params;
40344
40765
  let authRefreshInFlight = false;
40345
40766
  function handleOpen() {
40767
+ recordBridgeSessionOpened(state.duplicateBridgeFlap, Date.now());
40346
40768
  const logOpenAsPostRefreshReconnect = state.logBridgeOpenAsReconnect;
40347
40769
  clearMainBridgeReconnectQuietOnOpen(state, logFn);
40348
40770
  state.reconnectAttempt = 0;
@@ -40377,6 +40799,15 @@ function createMainBridgeWebSocketLifecycle(params) {
40377
40799
  state.currentWs = null;
40378
40800
  if (was) was.removeAllListeners();
40379
40801
  const willReconnect = !state.closedByUser;
40802
+ if (willReconnect) {
40803
+ const duplicateResult = evaluateDuplicateBridgeDisconnect(
40804
+ state.duplicateBridgeFlap,
40805
+ Date.now(),
40806
+ code,
40807
+ reason
40808
+ );
40809
+ logDuplicateBridgeWarning(logFn, duplicateResult);
40810
+ }
40380
40811
  beginMainBridgeDeferredDisconnect(state, code, reason, logFn, willReconnect);
40381
40812
  if (willReconnect) {
40382
40813
  state.lastReconnectCloseMeta = { code, reason };
@@ -40426,6 +40857,10 @@ function createMainBridgeWebSocketLifecycle(params) {
40426
40857
  state.currentWs = createWsBridge({
40427
40858
  url: url2,
40428
40859
  clientPingIntervalMs: CLI_WEBSOCKET_CLIENT_PING_MS,
40860
+ isActiveSocket: (socket) => state.currentWs === socket,
40861
+ onCompactHeartbeatAck: (seq) => {
40862
+ messageDeps.onBridgeHeartbeatAck?.(seq);
40863
+ },
40429
40864
  onAuthInvalid: () => {
40430
40865
  if (authRefreshInFlight) return;
40431
40866
  void (async () => {
@@ -40479,9 +40914,9 @@ function createMainBridgeWebSocketLifecycle(params) {
40479
40914
  } catch {
40480
40915
  }
40481
40916
  },
40482
- onMessage: (data) => {
40917
+ onMessage: (data, sourceWs) => {
40483
40918
  try {
40484
- handleBridgeMessage(data, messageDeps);
40919
+ handleBridgeMessage(data, messageDeps, sourceWs);
40485
40920
  } catch {
40486
40921
  }
40487
40922
  }
@@ -40649,10 +41084,10 @@ function listCliAgentCapabilityCacheForWorkspace(db, workspaceId) {
40649
41084
  }
40650
41085
 
40651
41086
  // src/agents/capabilities/warmup-agent-capabilities-on-connect.ts
40652
- import * as path40 from "node:path";
41087
+ import * as path43 from "node:path";
40653
41088
 
40654
41089
  // src/agents/capabilities/probe-one-agent-type-for-capabilities.ts
40655
- import * as path39 from "node:path";
41090
+ import * as path42 from "node:path";
40656
41091
  async function probeOneAgentTypeForCapabilities(params) {
40657
41092
  const { agentType, cwd, workspaceId, log: log2, reportAgentCapabilities, bridgeReport = true } = params;
40658
41093
  if (isCliImmediateShutdownRequested()) return false;
@@ -40692,7 +41127,7 @@ async function probeOneAgentTypeForCapabilities(params) {
40692
41127
  if (isCliImmediateShutdownRequested()) return false;
40693
41128
  handle = await resolved.createClient({
40694
41129
  command: resolved.command,
40695
- cwd: path39.resolve(cwd),
41130
+ cwd: path42.resolve(cwd),
40696
41131
  backendAgentType: agentType,
40697
41132
  sessionMode: "agent",
40698
41133
  persistedAcpSessionId: null,
@@ -40766,7 +41201,7 @@ async function probeAgentCapabilitiesForDetectedTypes(params) {
40766
41201
  async function warmupAgentCapabilitiesOnConnect(params) {
40767
41202
  const { workspaceId, log: log2, getWs } = params;
40768
41203
  if (isCliImmediateShutdownRequested()) return;
40769
- const cwd = path40.resolve(getBridgeRoot());
41204
+ const cwd = path43.resolve(getBridgeRoot());
40770
41205
  async function sendBatchFromCache() {
40771
41206
  const socket = getWs();
40772
41207
  if (!socket || socket.readyState !== wrapper_default.OPEN) return;
@@ -40846,6 +41281,7 @@ async function createBridgeConnection(options) {
40846
41281
  currentWs: null,
40847
41282
  mainQuiet: createEmptyReconnectQuietSlot(),
40848
41283
  mainOutage: createEmptyReconnectOutageTracker(),
41284
+ duplicateBridgeFlap: createEmptyDuplicateBridgeFlapTracker(),
40849
41285
  firehoseHandle: null,
40850
41286
  lastFirehoseParams: null,
40851
41287
  firehoseReconnectTimeout: null,
@@ -40933,8 +41369,8 @@ async function createBridgeConnection(options) {
40933
41369
  getCloudAccessToken: () => tokens.accessToken
40934
41370
  };
40935
41371
  const identifyReportedPaths = {
40936
- bridgeRootPath: path41.resolve(getBridgeRoot()),
40937
- worktreesRootPath: path41.resolve(worktreesRootPath)
41372
+ bridgeRootPath: path44.resolve(getBridgeRoot()),
41373
+ worktreesRootPath: path44.resolve(worktreesRootPath)
40938
41374
  };
40939
41375
  const { connect } = createMainBridgeWebSocketLifecycle({
40940
41376
  state,
@@ -41188,7 +41624,7 @@ async function runCliAction(program2, opts) {
41188
41624
  const firehoseServerUrl = opts.firehoseUrl ?? opts.proxyUrl ?? process.env.BUILDAUTOMATON_FIREHOSE_URL ?? process.env.BUILDAUTOMATON_PROXY_URL ?? DEFAULT_FIREHOSE_URL;
41189
41625
  const bridgeRootOpt = (opts.bridgeRoot && typeof opts.bridgeRoot === "string" && opts.bridgeRoot.trim() ? opts.bridgeRoot.trim() : null) ?? (opts.cwd && typeof opts.cwd === "string" && opts.cwd.trim() ? opts.cwd.trim() : null);
41190
41626
  if (bridgeRootOpt) {
41191
- const resolvedBridgeRoot = path42.resolve(process.cwd(), bridgeRootOpt);
41627
+ const resolvedBridgeRoot = path45.resolve(process.cwd(), bridgeRootOpt);
41192
41628
  try {
41193
41629
  const st = fs40.statSync(resolvedBridgeRoot);
41194
41630
  if (!st.isDirectory()) {
@@ -41210,7 +41646,7 @@ async function runCliAction(program2, opts) {
41210
41646
  );
41211
41647
  let worktreesRootPath;
41212
41648
  if (opts.worktreesRoot && opts.worktreesRoot.trim()) {
41213
- worktreesRootPath = path42.resolve(opts.worktreesRoot.trim());
41649
+ worktreesRootPath = path45.resolve(opts.worktreesRoot.trim());
41214
41650
  }
41215
41651
  const e2eCertificates = opts.e2eeCertificatesDir?.trim() ? await loadOrCreateE2eCertificates(opts.e2eeCertificatesDir.trim()) : void 0;
41216
41652
  if (e2eCertificates) {