@buildautomaton/cli 0.1.48 → 0.1.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -4175,8 +4175,8 @@ var init_parseUtil = __esm({
4175
4175
  init_errors();
4176
4176
  init_en();
4177
4177
  makeIssue = (params) => {
4178
- const { data, path: path46, errorMaps, issueData } = params;
4179
- const fullPath = [...path46, ...issueData.path || []];
4178
+ const { data, path: path53, errorMaps, issueData } = params;
4179
+ const fullPath = [...path53, ...issueData.path || []];
4180
4180
  const fullIssue = {
4181
4181
  ...issueData,
4182
4182
  path: fullPath
@@ -4484,11 +4484,11 @@ var init_types = __esm({
4484
4484
  init_parseUtil();
4485
4485
  init_util();
4486
4486
  ParseInputLazyPath = class {
4487
- constructor(parent, value, path46, key) {
4487
+ constructor(parent, value, path53, key) {
4488
4488
  this._cachedPath = [];
4489
4489
  this.parent = parent;
4490
4490
  this.data = value;
4491
- this._path = path46;
4491
+ this._path = path53;
4492
4492
  this._key = key;
4493
4493
  }
4494
4494
  get path() {
@@ -8103,15 +8103,15 @@ function assignProp(target, prop, value) {
8103
8103
  configurable: true
8104
8104
  });
8105
8105
  }
8106
- function getElementAtPath(obj, path46) {
8107
- if (!path46)
8106
+ function getElementAtPath(obj, path53) {
8107
+ if (!path53)
8108
8108
  return obj;
8109
- return path46.reduce((acc, key) => acc?.[key], obj);
8109
+ return path53.reduce((acc, key) => acc?.[key], obj);
8110
8110
  }
8111
8111
  function promiseAllObject(promisesObj) {
8112
8112
  const keys = Object.keys(promisesObj);
8113
- const promises6 = keys.map((key) => promisesObj[key]);
8114
- return Promise.all(promises6).then((results) => {
8113
+ const promises11 = keys.map((key) => promisesObj[key]);
8114
+ return Promise.all(promises11).then((results) => {
8115
8115
  const resolvedObj = {};
8116
8116
  for (let i = 0; i < keys.length; i++) {
8117
8117
  resolvedObj[keys[i]] = results[i];
@@ -8355,11 +8355,11 @@ function aborted(x, startIndex = 0) {
8355
8355
  }
8356
8356
  return false;
8357
8357
  }
8358
- function prefixIssues(path46, issues) {
8358
+ function prefixIssues(path53, issues) {
8359
8359
  return issues.map((iss) => {
8360
8360
  var _a2;
8361
8361
  (_a2 = iss).path ?? (_a2.path = []);
8362
- iss.path.unshift(path46);
8362
+ iss.path.unshift(path53);
8363
8363
  return iss;
8364
8364
  });
8365
8365
  }
@@ -8548,7 +8548,7 @@ function treeifyError(error40, _mapper) {
8548
8548
  return issue2.message;
8549
8549
  };
8550
8550
  const result = { errors: [] };
8551
- const processError = (error41, path46 = []) => {
8551
+ const processError = (error41, path53 = []) => {
8552
8552
  var _a2, _b;
8553
8553
  for (const issue2 of error41.issues) {
8554
8554
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -8558,7 +8558,7 @@ function treeifyError(error40, _mapper) {
8558
8558
  } else if (issue2.code === "invalid_element") {
8559
8559
  processError({ issues: issue2.issues }, issue2.path);
8560
8560
  } else {
8561
- const fullpath = [...path46, ...issue2.path];
8561
+ const fullpath = [...path53, ...issue2.path];
8562
8562
  if (fullpath.length === 0) {
8563
8563
  result.errors.push(mapper(issue2));
8564
8564
  continue;
@@ -8588,9 +8588,9 @@ function treeifyError(error40, _mapper) {
8588
8588
  processError(error40);
8589
8589
  return result;
8590
8590
  }
8591
- function toDotPath(path46) {
8591
+ function toDotPath(path53) {
8592
8592
  const segs = [];
8593
- for (const seg of path46) {
8593
+ for (const seg of path53) {
8594
8594
  if (typeof seg === "number")
8595
8595
  segs.push(`[${seg}]`);
8596
8596
  else if (typeof seg === "symbol")
@@ -21053,8 +21053,8 @@ var init_acp = __esm({
21053
21053
  this.#requestHandler = requestHandler;
21054
21054
  this.#notificationHandler = notificationHandler;
21055
21055
  this.#stream = stream;
21056
- this.#closedPromise = new Promise((resolve20) => {
21057
- this.#abortController.signal.addEventListener("abort", () => resolve20());
21056
+ this.#closedPromise = new Promise((resolve26) => {
21057
+ this.#abortController.signal.addEventListener("abort", () => resolve26());
21058
21058
  });
21059
21059
  this.#receive();
21060
21060
  }
@@ -21203,8 +21203,8 @@ var init_acp = __esm({
21203
21203
  }
21204
21204
  async sendRequest(method, params) {
21205
21205
  const id = this.#nextRequestId++;
21206
- const responsePromise = new Promise((resolve20, reject) => {
21207
- this.#pendingResponses.set(id, { resolve: resolve20, reject });
21206
+ const responsePromise = new Promise((resolve26, reject) => {
21207
+ this.#pendingResponses.set(id, { resolve: resolve26, reject });
21208
21208
  });
21209
21209
  await this.#sendMessage({ jsonrpc: "2.0", id, method, params });
21210
21210
  return responsePromise;
@@ -22073,10 +22073,10 @@ var require_src2 = __commonJS({
22073
22073
  var fs_1 = __require("fs");
22074
22074
  var debug_1 = __importDefault(require_src());
22075
22075
  var log2 = debug_1.default("@kwsites/file-exists");
22076
- function check2(path46, isFile, isDirectory) {
22077
- log2(`checking %s`, path46);
22076
+ function check2(path53, isFile, isDirectory) {
22077
+ log2(`checking %s`, path53);
22078
22078
  try {
22079
- const stat2 = fs_1.statSync(path46);
22079
+ const stat2 = fs_1.statSync(path53);
22080
22080
  if (stat2.isFile() && isFile) {
22081
22081
  log2(`[OK] path represents a file`);
22082
22082
  return true;
@@ -22096,8 +22096,8 @@ var require_src2 = __commonJS({
22096
22096
  throw e;
22097
22097
  }
22098
22098
  }
22099
- function exists2(path46, type = exports.READABLE) {
22100
- return check2(path46, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
22099
+ function exists2(path53, type = exports.READABLE) {
22100
+ return check2(path53, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
22101
22101
  }
22102
22102
  exports.exists = exists2;
22103
22103
  exports.FILE = 1;
@@ -22446,10 +22446,10 @@ async function killChildProcessTreeGracefully(child, graceMs = ACP_PROCESS_TREE_
22446
22446
  }
22447
22447
  await killProcessTree(pid, "SIGTERM");
22448
22448
  if (graceMs <= 0) return;
22449
- const exited = new Promise((resolve20) => {
22450
- child.once("exit", () => resolve20());
22449
+ const exited = new Promise((resolve26) => {
22450
+ child.once("exit", () => resolve26());
22451
22451
  });
22452
- await Promise.race([exited, new Promise((resolve20) => setTimeout(resolve20, graceMs))]);
22452
+ await Promise.race([exited, new Promise((resolve26) => setTimeout(resolve26, graceMs))]);
22453
22453
  if (child.exitCode == null && child.signalCode == null) {
22454
22454
  await killProcessTree(pid, "SIGKILL");
22455
22455
  }
@@ -22466,8 +22466,8 @@ async function gracefulAcpSubprocessDisconnect(params) {
22466
22466
  await transport.closeSession?.(sessionId);
22467
22467
  } catch {
22468
22468
  }
22469
- await new Promise((resolve20) => {
22470
- const timer = setTimeout(resolve20, ACP_GRACEFUL_SESSION_CLOSE_WAIT_MS);
22469
+ await new Promise((resolve26) => {
22470
+ const timer = setTimeout(resolve26, ACP_GRACEFUL_SESSION_CLOSE_WAIT_MS);
22471
22471
  timer.unref?.();
22472
22472
  });
22473
22473
  }
@@ -22733,6 +22733,238 @@ var WebSocketMessageSchema = external_exports.object({
22733
22733
  error: external_exports.string().optional()
22734
22734
  });
22735
22735
 
22736
+ // ../types/src/integrations/link-detection/linear.ts
22737
+ function detectLinearLink(url2) {
22738
+ const linearMatch = url2.match(
22739
+ /https?:\/\/(?:app\.)?linear\.app\/([^/]+)\/issue\/([A-Z]+-\d+)/i
22740
+ );
22741
+ if (linearMatch) {
22742
+ return {
22743
+ url: url2,
22744
+ integrationType: "linear",
22745
+ resourceType: "work-item",
22746
+ resourceId: linearMatch[2],
22747
+ workspaceId: linearMatch[1]
22748
+ };
22749
+ }
22750
+ return null;
22751
+ }
22752
+
22753
+ // ../types/src/integrations/link-detection/github.ts
22754
+ function detectGitHubLink(url2) {
22755
+ const githubIssueMatch = url2.match(
22756
+ /https?:\/\/github\.com\/([^/]+)\/([^/]+)\/issues\/(\d+)/i
22757
+ );
22758
+ if (githubIssueMatch) {
22759
+ return {
22760
+ url: url2,
22761
+ integrationType: "github",
22762
+ resourceType: "issue",
22763
+ resourceId: githubIssueMatch[3],
22764
+ owner: githubIssueMatch[1],
22765
+ repo: githubIssueMatch[2]
22766
+ };
22767
+ }
22768
+ const githubPRMatch = url2.match(
22769
+ /https?:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/i
22770
+ );
22771
+ if (githubPRMatch) {
22772
+ return {
22773
+ url: url2,
22774
+ integrationType: "github",
22775
+ resourceType: "pull-request",
22776
+ resourceId: githubPRMatch[3],
22777
+ owner: githubPRMatch[1],
22778
+ repo: githubPRMatch[2]
22779
+ };
22780
+ }
22781
+ return null;
22782
+ }
22783
+
22784
+ // ../types/src/integrations/link-detection/jira.ts
22785
+ function detectJIRALink(url2) {
22786
+ const jiraMatch = url2.match(
22787
+ /https?:\/\/([^/]+)\.atlassian\.net\/(?:browse|jira\/software\/projects\/[^/]+\/issues)\/([A-Z]+-\d+)/i
22788
+ );
22789
+ if (jiraMatch) {
22790
+ return {
22791
+ url: url2,
22792
+ integrationType: "jira",
22793
+ resourceType: "issue",
22794
+ resourceId: jiraMatch[2],
22795
+ projectKey: jiraMatch[2].split("-")[0]
22796
+ };
22797
+ }
22798
+ return null;
22799
+ }
22800
+
22801
+ // ../types/src/integrations/link-detection/notion.ts
22802
+ function formatNotionPageId(hex) {
22803
+ if (hex.length !== 32) return hex;
22804
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
22805
+ }
22806
+ function detectNotionLink(url2) {
22807
+ const standardMatch = url2.match(
22808
+ /https?:\/\/(?:www\.)?notion\.(?:so|site)\/(?:[^/#?]+\/)?(?:[^/#?-]+-)?([a-f0-9]{32})/i
22809
+ );
22810
+ if (standardMatch) {
22811
+ return {
22812
+ url: url2,
22813
+ integrationType: "notion",
22814
+ resourceType: "page",
22815
+ resourceId: formatNotionPageId(standardMatch[1])
22816
+ };
22817
+ }
22818
+ const siteMatch = url2.match(
22819
+ /https?:\/\/([^.]+)\.notion\.(?:so|site)\/(?:[^/#?-]+-)?([a-f0-9]{32})/i
22820
+ );
22821
+ if (siteMatch) {
22822
+ return {
22823
+ url: url2,
22824
+ integrationType: "notion",
22825
+ resourceType: "page",
22826
+ resourceId: formatNotionPageId(siteMatch[2]),
22827
+ notionWorkspaceId: siteMatch[1]
22828
+ };
22829
+ }
22830
+ return null;
22831
+ }
22832
+
22833
+ // ../types/src/integrations/link-detection/slack.ts
22834
+ function detectSlackLink(url2) {
22835
+ const archiveThreadMatch = url2.match(
22836
+ /https?:\/\/(?:([^./]+)\.)?slack\.com\/archives\/([A-Z0-9]+)\/p(\d+)(?:\?.*)?$/i
22837
+ );
22838
+ if (archiveThreadMatch) {
22839
+ return {
22840
+ url: url2,
22841
+ integrationType: "slack",
22842
+ resourceType: "thread",
22843
+ resourceId: `${archiveThreadMatch[2]}-${archiveThreadMatch[3]}`,
22844
+ isChannel: false
22845
+ };
22846
+ }
22847
+ const appThreadMatch = url2.match(
22848
+ /https?:\/\/app\.slack\.com\/client\/([^/]+)\/([A-Z0-9]+)\/thread\/[A-Z0-9]+-(\d+)/i
22849
+ );
22850
+ if (appThreadMatch) {
22851
+ return {
22852
+ url: url2,
22853
+ integrationType: "slack",
22854
+ resourceType: "thread",
22855
+ resourceId: `${appThreadMatch[2]}-${appThreadMatch[3]}`,
22856
+ isChannel: false
22857
+ };
22858
+ }
22859
+ const channelOnlyMatch = url2.match(
22860
+ /https?:\/\/(?:app\.)?(?:([^./]+)\.)?slack\.com\/archives\/([A-Z0-9]+)\/?(\?.*)?$/i
22861
+ );
22862
+ if (channelOnlyMatch && !url2.includes("/p")) {
22863
+ return {
22864
+ url: url2,
22865
+ integrationType: "slack",
22866
+ resourceType: "channel",
22867
+ resourceId: channelOnlyMatch[2],
22868
+ isChannel: true
22869
+ };
22870
+ }
22871
+ return null;
22872
+ }
22873
+
22874
+ // ../types/src/integrations/link-detection/zoom.ts
22875
+ function detectZoomLink(url2) {
22876
+ try {
22877
+ const u = new URL(url2);
22878
+ const host = u.hostname.toLowerCase();
22879
+ if (!host.endsWith("zoom.us") && !host.endsWith("zoom.com")) return null;
22880
+ const pathParts = u.pathname.split("/").filter(Boolean);
22881
+ if (pathParts.length >= 2 && (pathParts[0] === "j" || pathParts[0] === "s" || pathParts[0] === "my")) {
22882
+ const meetingId = pathParts[1].replace(/\s/g, "");
22883
+ if (meetingId.length >= 9) {
22884
+ return {
22885
+ url: url2,
22886
+ integrationType: "zoom",
22887
+ resourceType: "meeting",
22888
+ resourceId: meetingId,
22889
+ zoomMeetingId: meetingId
22890
+ };
22891
+ }
22892
+ }
22893
+ } catch {
22894
+ }
22895
+ return null;
22896
+ }
22897
+
22898
+ // ../types/src/integrations/link-detection/asana.ts
22899
+ var ASANA_TASK_GID = /\d{10,}/;
22900
+ function parseAsanaTaskGid(segment) {
22901
+ const trimmed2 = segment.trim();
22902
+ if (!ASANA_TASK_GID.test(trimmed2)) return null;
22903
+ return trimmed2;
22904
+ }
22905
+ function detectAsanaLink(url2) {
22906
+ const v1ProjectMatch = url2.match(
22907
+ /https?:\/\/app\.asana\.com\/1\/[^/]+\/project\/([^/?#]+)\/task\/([^/?#]+)/i
22908
+ );
22909
+ if (v1ProjectMatch) {
22910
+ const taskGid = parseAsanaTaskGid(v1ProjectMatch[2]);
22911
+ if (!taskGid) return null;
22912
+ return {
22913
+ url: url2,
22914
+ integrationType: "asana",
22915
+ resourceType: "task",
22916
+ resourceId: taskGid,
22917
+ asanaProjectGid: v1ProjectMatch[1]
22918
+ };
22919
+ }
22920
+ const v1TaskMatch = url2.match(/https?:\/\/app\.asana\.com\/1\/[^/]+(?:\/[^/]+)*\/task\/([^/?#]+)/i);
22921
+ if (v1TaskMatch) {
22922
+ const taskGid = parseAsanaTaskGid(v1TaskMatch[1]);
22923
+ if (!taskGid) return null;
22924
+ return {
22925
+ url: url2,
22926
+ integrationType: "asana",
22927
+ resourceType: "task",
22928
+ resourceId: taskGid
22929
+ };
22930
+ }
22931
+ const legacyMatch = url2.match(/https?:\/\/app\.asana\.com\/0\/([^/?#]+)\/([^/?#]+)/i);
22932
+ if (legacyMatch) {
22933
+ const projectGid = legacyMatch[1];
22934
+ const taskGid = parseAsanaTaskGid(legacyMatch[2]);
22935
+ if (!taskGid) return null;
22936
+ if (projectGid === "inbox" || projectGid === "my_tasks") {
22937
+ return null;
22938
+ }
22939
+ return {
22940
+ url: url2,
22941
+ integrationType: "asana",
22942
+ resourceType: "task",
22943
+ resourceId: taskGid,
22944
+ asanaProjectGid: projectGid === "0" ? void 0 : projectGid
22945
+ };
22946
+ }
22947
+ return null;
22948
+ }
22949
+
22950
+ // ../types/src/integrations/link-detection/index.ts
22951
+ function detectIntegrationLink(url2) {
22952
+ return detectLinearLink(url2) || detectGitHubLink(url2) || detectJIRALink(url2) || detectNotionLink(url2) || detectSlackLink(url2) || detectZoomLink(url2) || detectAsanaLink(url2);
22953
+ }
22954
+ function extractIntegrationLinks(text) {
22955
+ const urlRegex = /https?:\/\/[^\s<>"{}|\\^`[\]]+/gi;
22956
+ const urls = text.match(urlRegex) || [];
22957
+ const links = [];
22958
+ for (const url2 of urls) {
22959
+ const detected = detectIntegrationLink(url2);
22960
+ if (detected) links.push(detected);
22961
+ }
22962
+ return links;
22963
+ }
22964
+ function extractFetchableIntegrationLinks(text) {
22965
+ return extractIntegrationLinks(text).filter((link) => !(link.integrationType === "slack" && link.isChannel));
22966
+ }
22967
+
22736
22968
  // ../types/src/checkpoints.ts
22737
22969
  init_zod();
22738
22970
  var CheckpointKindSchema = external_exports.enum(["daily", "weekly", "overall"]);
@@ -22940,9 +23172,9 @@ function parseChangeSummaryJson(raw, allowedPaths, options) {
22940
23172
  const rawPath = typeof o.path === "string" ? o.path.trim() : "";
22941
23173
  const summary = typeof o.summary === "string" ? o.summary.trim() : "";
22942
23174
  if (!rawPath || !summary) continue;
22943
- const path46 = skip ? normalizeRepoRelativePath(rawPath) || rawPath : resolveChangeSummaryPathAgainstAllowed(rawPath, allowedPaths);
22944
- if (!path46) continue;
22945
- rows.push({ path: path46, summary: clampSummaryToAtMostTwoLines(summary) });
23175
+ const path53 = skip ? normalizeRepoRelativePath(rawPath) || rawPath : resolveChangeSummaryPathAgainstAllowed(rawPath, allowedPaths);
23176
+ if (!path53) continue;
23177
+ rows.push({ path: path53, summary: clampSummaryToAtMostTwoLines(summary) });
22946
23178
  }
22947
23179
  return rows;
22948
23180
  }
@@ -23160,6 +23392,16 @@ var GitRepoMetaSchema = external_exports.object({
23160
23392
  updatedAt: external_exports.string()
23161
23393
  });
23162
23394
 
23395
+ // ../types/src/mcp/bridge-mcp-constants.ts
23396
+ var GET_INTEGRATION_CONTENT_TOOL = "get_integration_content";
23397
+ var LIST_PARENT_SESSION_PROMPTS_TOOL = "list_parent_session_prompts";
23398
+ var GET_PARENT_SESSION_TURN_TRANSCRIPT_TOOL = "get_parent_session_turn_transcript";
23399
+ var BRIDGE_MCP_TOOL_LABELS = {
23400
+ [GET_INTEGRATION_CONTENT_TOOL]: "Fetch external ticket content",
23401
+ [LIST_PARENT_SESSION_PROMPTS_TOOL]: "List parent session prompts",
23402
+ [GET_PARENT_SESSION_TURN_TRANSCRIPT_TOOL]: "Read parent session transcript"
23403
+ };
23404
+
23163
23405
  // ../types/src/claude-code-permission-mode.ts
23164
23406
  var CLAUDE_CODE_PERMISSION_MODES = [
23165
23407
  "default",
@@ -23386,11 +23628,11 @@ function createSdkStdioExtNotificationHandler(options) {
23386
23628
  // src/agents/acp/clients/sdk/sdk-stdio-permission-request-handshake.ts
23387
23629
  function awaitSdkStdioPermissionRequestHandshake(params) {
23388
23630
  const { requestId, paramsRecord, pending, onRequest } = params;
23389
- return new Promise((resolve20) => {
23390
- pending.set(requestId, { resolve: resolve20, params: paramsRecord });
23631
+ return new Promise((resolve26) => {
23632
+ pending.set(requestId, { resolve: resolve26, params: paramsRecord });
23391
23633
  if (onRequest == null) {
23392
23634
  pending.delete(requestId);
23393
- resolve20({ outcome: { outcome: "denied" } });
23635
+ resolve26({ outcome: { outcome: "denied" } });
23394
23636
  return;
23395
23637
  }
23396
23638
  try {
@@ -23934,10 +24176,10 @@ function createSdkStdioInitSettle(child) {
23934
24176
  forceAcpSubprocessDisconnect(child);
23935
24177
  reject(err);
23936
24178
  },
23937
- settleResolve(resolve20, handle) {
24179
+ settleResolve(resolve26, handle) {
23938
24180
  if (initSettled) return;
23939
24181
  initSettled = true;
23940
- resolve20(handle);
24182
+ resolve26(handle);
23941
24183
  }
23942
24184
  };
23943
24185
  }
@@ -24006,7 +24248,7 @@ async function createSdkStdioAcpClient(options) {
24006
24248
  onFileChange,
24007
24249
  stderrCapture
24008
24250
  });
24009
- return new Promise((resolve20, reject) => {
24251
+ return new Promise((resolve26, reject) => {
24010
24252
  const init = createSdkStdioInitSettle(child);
24011
24253
  child.on("error", (err) => {
24012
24254
  init.settleReject(reject, new Error(formatSpawnError(err, command[0])));
@@ -24031,7 +24273,7 @@ async function createSdkStdioAcpClient(options) {
24031
24273
  protocolVersion: PROTOCOL_VERSION2
24032
24274
  });
24033
24275
  init.settleResolve(
24034
- resolve20,
24276
+ resolve26,
24035
24277
  createSdkStdioHandle({
24036
24278
  child,
24037
24279
  sessionId: established.sessionId,
@@ -24139,7 +24381,7 @@ async function proxyToLocal(request) {
24139
24381
  };
24140
24382
  const maxAttempts = isIdempotentProxyMethod(request.method) ? LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS.length + 1 : 1;
24141
24383
  for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
24142
- const once = await new Promise((resolve20) => {
24384
+ const once = await new Promise((resolve26) => {
24143
24385
  const req = mod.request(opts, (res) => {
24144
24386
  const chunks = [];
24145
24387
  res.on("data", (c) => chunks.push(c));
@@ -24150,7 +24392,7 @@ async function proxyToLocal(request) {
24150
24392
  if (typeof v === "string") headers[k] = v;
24151
24393
  else if (Array.isArray(v) && v[0]) headers[k] = v[0];
24152
24394
  }
24153
- resolve20({
24395
+ resolve26({
24154
24396
  id: request.id,
24155
24397
  statusCode: res.statusCode ?? 0,
24156
24398
  headers,
@@ -24159,7 +24401,7 @@ async function proxyToLocal(request) {
24159
24401
  });
24160
24402
  });
24161
24403
  req.on("error", (err) => {
24162
- resolve20({
24404
+ resolve26({
24163
24405
  id: request.id,
24164
24406
  statusCode: 0,
24165
24407
  headers: {},
@@ -24241,8 +24483,8 @@ function randomSecret() {
24241
24483
  }
24242
24484
  return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
24243
24485
  }
24244
- async function requestPreviewApi(port, secret, method, path46, body) {
24245
- const url2 = `http://127.0.0.1:${port}${path46}`;
24486
+ async function requestPreviewApi(port, secret, method, path53, body) {
24487
+ const url2 = `http://127.0.0.1:${port}${path53}`;
24246
24488
  const headers = {
24247
24489
  [PREVIEW_SECRET_HEADER]: secret,
24248
24490
  "Content-Type": "application/json"
@@ -24254,7 +24496,7 @@ async function requestPreviewApi(port, secret, method, path46, body) {
24254
24496
  });
24255
24497
  const data = await res.json().catch(() => ({}));
24256
24498
  if (!res.ok) {
24257
- throw new Error(data?.error ?? `Preview API ${method} ${path46}: ${res.status}`);
24499
+ throw new Error(data?.error ?? `Preview API ${method} ${path53}: ${res.status}`);
24258
24500
  }
24259
24501
  return data;
24260
24502
  }
@@ -24469,7 +24711,7 @@ function installBridgeProcessResilience() {
24469
24711
  }
24470
24712
 
24471
24713
  // src/cli-version.ts
24472
- var CLI_VERSION = "0.1.48".length > 0 ? "0.1.48" : "0.0.0-dev";
24714
+ var CLI_VERSION = "0.1.50".length > 0 ? "0.1.50" : "0.0.0-dev";
24473
24715
 
24474
24716
  // src/connection/heartbeat/constants.ts
24475
24717
  var BRIDGE_APP_HEARTBEAT_INTERVAL_MS = 1e4;
@@ -24910,14 +25152,14 @@ var baseOpen = async (options) => {
24910
25152
  }
24911
25153
  const subprocess = childProcess.spawn(command, cliArguments, childProcessOptions);
24912
25154
  if (options.wait) {
24913
- return new Promise((resolve20, reject) => {
25155
+ return new Promise((resolve26, reject) => {
24914
25156
  subprocess.once("error", reject);
24915
25157
  subprocess.once("close", (exitCode) => {
24916
25158
  if (!options.allowNonzeroExitCode && exitCode > 0) {
24917
25159
  reject(new Error(`Exited with code ${exitCode}`));
24918
25160
  return;
24919
25161
  }
24920
- resolve20(subprocess);
25162
+ resolve26(subprocess);
24921
25163
  });
24922
25164
  });
24923
25165
  }
@@ -25461,8 +25703,8 @@ function runPendingAuth(options) {
25461
25703
  let hasOpenedBrowser = false;
25462
25704
  let resolved = false;
25463
25705
  let resolveAuth;
25464
- const authPromise = new Promise((resolve20) => {
25465
- resolveAuth = resolve20;
25706
+ const authPromise = new Promise((resolve26) => {
25707
+ resolveAuth = resolve26;
25466
25708
  });
25467
25709
  let reconnectAttempt = 0;
25468
25710
  const signInQuiet = createEmptyReconnectQuietSlot();
@@ -25613,7 +25855,7 @@ import sqliteWasm from "node-sqlite3-wasm";
25613
25855
 
25614
25856
  // src/runtime/yield-to-event-loop.ts
25615
25857
  function yieldToEventLoop() {
25616
- return new Promise((resolve20) => setImmediate(resolve20));
25858
+ return new Promise((resolve26) => setImmediate(resolve26));
25617
25859
  }
25618
25860
 
25619
25861
  // src/sqlite/cli-sqlite-paths.ts
@@ -26777,7 +27019,7 @@ async function createCursorAcpClient(options) {
26777
27019
  onFileChange,
26778
27020
  stderrCapture
26779
27021
  });
26780
- return new Promise((resolve20, reject) => {
27022
+ return new Promise((resolve26, reject) => {
26781
27023
  child.on("error", (err) => {
26782
27024
  killChildProcessTree(child, "SIGKILL");
26783
27025
  reject(new Error(formatSpawnError(err, command[0])));
@@ -26789,7 +27031,7 @@ async function createCursorAcpClient(options) {
26789
27031
  sessionCtx,
26790
27032
  incomingDeps: { dbgFs, sessionCtx, onSessionUpdate, onRequest }
26791
27033
  });
26792
- resolve20(
27034
+ resolve26(
26793
27035
  createCursorAcpHandle({
26794
27036
  child,
26795
27037
  sessionId: established.sessionId,
@@ -27727,7 +27969,7 @@ function computeAcpAgentKey(preferredAgentType, mode, agentConfig) {
27727
27969
  }
27728
27970
 
27729
27971
  // src/mcp/bridge-mcp-constants.ts
27730
- var BRIDGE_MCP_SERVER_NAME = "buildautomaton-bridge";
27972
+ var BRIDGE_MCP_SERVER_NAME2 = "buildautomaton-bridge";
27731
27973
 
27732
27974
  // src/mcp/resolve-bridge-mcp-server-path.ts
27733
27975
  import { dirname as dirname4, join as join2 } from "node:path";
@@ -27742,7 +27984,7 @@ function buildAcpMcpServers(bridgeAccessPort) {
27742
27984
  if (!Number.isFinite(bridgeAccessPort) || bridgeAccessPort <= 0) return [];
27743
27985
  return [
27744
27986
  {
27745
- name: BRIDGE_MCP_SERVER_NAME,
27987
+ name: BRIDGE_MCP_SERVER_NAME2,
27746
27988
  command: process.execPath,
27747
27989
  args: [resolveBridgeMcpServerScriptPath(), String(bridgeAccessPort)],
27748
27990
  env: []
@@ -27995,8 +28237,8 @@ function pathspec(...paths) {
27995
28237
  cache.set(key, paths);
27996
28238
  return key;
27997
28239
  }
27998
- function isPathSpec(path46) {
27999
- return path46 instanceof String && cache.has(path46);
28240
+ function isPathSpec(path53) {
28241
+ return path53 instanceof String && cache.has(path53);
28000
28242
  }
28001
28243
  function toPaths(pathSpec) {
28002
28244
  return cache.get(pathSpec) || [];
@@ -28085,8 +28327,8 @@ function toLinesWithContent(input = "", trimmed2 = true, separator = "\n") {
28085
28327
  function forEachLineWithContent(input, callback) {
28086
28328
  return toLinesWithContent(input, true).map((line) => callback(line));
28087
28329
  }
28088
- function folderExists(path46) {
28089
- return (0, import_file_exists.exists)(path46, import_file_exists.FOLDER);
28330
+ function folderExists(path53) {
28331
+ return (0, import_file_exists.exists)(path53, import_file_exists.FOLDER);
28090
28332
  }
28091
28333
  function append(target, item) {
28092
28334
  if (Array.isArray(target)) {
@@ -28490,8 +28732,8 @@ function checkIsRepoRootTask() {
28490
28732
  commands,
28491
28733
  format: "utf-8",
28492
28734
  onError,
28493
- parser(path46) {
28494
- return /^\.(git)?$/.test(path46.trim());
28735
+ parser(path53) {
28736
+ return /^\.(git)?$/.test(path53.trim());
28495
28737
  }
28496
28738
  };
28497
28739
  }
@@ -28925,11 +29167,11 @@ function parseGrep(grep) {
28925
29167
  const paths = /* @__PURE__ */ new Set();
28926
29168
  const results = {};
28927
29169
  forEachLineWithContent(grep, (input) => {
28928
- const [path46, line, preview] = input.split(NULL);
28929
- paths.add(path46);
28930
- (results[path46] = results[path46] || []).push({
29170
+ const [path53, line, preview] = input.split(NULL);
29171
+ paths.add(path53);
29172
+ (results[path53] = results[path53] || []).push({
28931
29173
  line: asNumber(line),
28932
- path: path46,
29174
+ path: path53,
28933
29175
  preview
28934
29176
  });
28935
29177
  });
@@ -29694,14 +29936,14 @@ var init_hash_object = __esm2({
29694
29936
  init_task();
29695
29937
  }
29696
29938
  });
29697
- function parseInit(bare, path46, text) {
29939
+ function parseInit(bare, path53, text) {
29698
29940
  const response = String(text).trim();
29699
29941
  let result;
29700
29942
  if (result = initResponseRegex.exec(response)) {
29701
- return new InitSummary(bare, path46, false, result[1]);
29943
+ return new InitSummary(bare, path53, false, result[1]);
29702
29944
  }
29703
29945
  if (result = reInitResponseRegex.exec(response)) {
29704
- return new InitSummary(bare, path46, true, result[1]);
29946
+ return new InitSummary(bare, path53, true, result[1]);
29705
29947
  }
29706
29948
  let gitDir = "";
29707
29949
  const tokens = response.split(" ");
@@ -29712,7 +29954,7 @@ function parseInit(bare, path46, text) {
29712
29954
  break;
29713
29955
  }
29714
29956
  }
29715
- return new InitSummary(bare, path46, /^re/i.test(response), gitDir);
29957
+ return new InitSummary(bare, path53, /^re/i.test(response), gitDir);
29716
29958
  }
29717
29959
  var InitSummary;
29718
29960
  var initResponseRegex;
@@ -29721,9 +29963,9 @@ var init_InitSummary = __esm2({
29721
29963
  "src/lib/responses/InitSummary.ts"() {
29722
29964
  "use strict";
29723
29965
  InitSummary = class {
29724
- constructor(bare, path46, existing, gitDir) {
29966
+ constructor(bare, path53, existing, gitDir) {
29725
29967
  this.bare = bare;
29726
- this.path = path46;
29968
+ this.path = path53;
29727
29969
  this.existing = existing;
29728
29970
  this.gitDir = gitDir;
29729
29971
  }
@@ -29735,7 +29977,7 @@ var init_InitSummary = __esm2({
29735
29977
  function hasBareCommand(command) {
29736
29978
  return command.includes(bareCommand);
29737
29979
  }
29738
- function initTask(bare = false, path46, customArgs) {
29980
+ function initTask(bare = false, path53, customArgs) {
29739
29981
  const commands = ["init", ...customArgs];
29740
29982
  if (bare && !hasBareCommand(commands)) {
29741
29983
  commands.splice(1, 0, bareCommand);
@@ -29744,7 +29986,7 @@ function initTask(bare = false, path46, customArgs) {
29744
29986
  commands,
29745
29987
  format: "utf-8",
29746
29988
  parser(text) {
29747
- return parseInit(commands.includes("--bare"), path46, text);
29989
+ return parseInit(commands.includes("--bare"), path53, text);
29748
29990
  }
29749
29991
  };
29750
29992
  }
@@ -30560,12 +30802,12 @@ var init_FileStatusSummary = __esm2({
30560
30802
  "use strict";
30561
30803
  fromPathRegex = /^(.+)\0(.+)$/;
30562
30804
  FileStatusSummary = class {
30563
- constructor(path46, index, working_dir) {
30564
- this.path = path46;
30805
+ constructor(path53, index, working_dir) {
30806
+ this.path = path53;
30565
30807
  this.index = index;
30566
30808
  this.working_dir = working_dir;
30567
30809
  if (index === "R" || working_dir === "R") {
30568
- const detail = fromPathRegex.exec(path46) || [null, path46, path46];
30810
+ const detail = fromPathRegex.exec(path53) || [null, path53, path53];
30569
30811
  this.from = detail[2] || "";
30570
30812
  this.path = detail[1] || "";
30571
30813
  }
@@ -30596,14 +30838,14 @@ function splitLine(result, lineStr) {
30596
30838
  default:
30597
30839
  return;
30598
30840
  }
30599
- function data(index, workingDir, path46) {
30841
+ function data(index, workingDir, path53) {
30600
30842
  const raw = `${index}${workingDir}`;
30601
30843
  const handler = parsers6.get(raw);
30602
30844
  if (handler) {
30603
- handler(result, path46);
30845
+ handler(result, path53);
30604
30846
  }
30605
30847
  if (raw !== "##" && raw !== "!!") {
30606
- result.files.push(new FileStatusSummary(path46, index, workingDir));
30848
+ result.files.push(new FileStatusSummary(path53, index, workingDir));
30607
30849
  }
30608
30850
  }
30609
30851
  }
@@ -30912,9 +31154,9 @@ var init_simple_git_api = __esm2({
30912
31154
  next
30913
31155
  );
30914
31156
  }
30915
- hashObject(path46, write) {
31157
+ hashObject(path53, write) {
30916
31158
  return this._runTask(
30917
- hashObjectTask(path46, write === true),
31159
+ hashObjectTask(path53, write === true),
30918
31160
  trailingFunctionArgument(arguments)
30919
31161
  );
30920
31162
  }
@@ -31267,8 +31509,8 @@ var init_branch = __esm2({
31267
31509
  }
31268
31510
  });
31269
31511
  function toPath(input) {
31270
- const path46 = input.trim().replace(/^["']|["']$/g, "");
31271
- return path46 && normalize3(path46);
31512
+ const path53 = input.trim().replace(/^["']|["']$/g, "");
31513
+ return path53 && normalize3(path53);
31272
31514
  }
31273
31515
  var parseCheckIgnore;
31274
31516
  var init_CheckIgnore = __esm2({
@@ -31582,8 +31824,8 @@ __export2(sub_module_exports, {
31582
31824
  subModuleTask: () => subModuleTask,
31583
31825
  updateSubModuleTask: () => updateSubModuleTask
31584
31826
  });
31585
- function addSubModuleTask(repo, path46) {
31586
- return subModuleTask(["add", repo, path46]);
31827
+ function addSubModuleTask(repo, path53) {
31828
+ return subModuleTask(["add", repo, path53]);
31587
31829
  }
31588
31830
  function initSubModuleTask(customArgs) {
31589
31831
  return subModuleTask(["init", ...customArgs]);
@@ -31916,8 +32158,8 @@ var require_git = __commonJS2({
31916
32158
  }
31917
32159
  return this._runTask(straightThroughStringTask2(command, this._trimmed), next);
31918
32160
  };
31919
- Git2.prototype.submoduleAdd = function(repo, path46, then) {
31920
- return this._runTask(addSubModuleTask2(repo, path46), trailingFunctionArgument2(arguments));
32161
+ Git2.prototype.submoduleAdd = function(repo, path53, then) {
32162
+ return this._runTask(addSubModuleTask2(repo, path53), trailingFunctionArgument2(arguments));
31921
32163
  };
31922
32164
  Git2.prototype.submoduleUpdate = function(args, then) {
31923
32165
  return this._runTask(
@@ -32554,14 +32796,14 @@ async function runGitTask(fn) {
32554
32796
  }
32555
32797
  async function yieldToEventLoop2() {
32556
32798
  throwIfGitShutdownRequested();
32557
- await new Promise((resolve20) => {
32558
- setImmediate(resolve20);
32799
+ await new Promise((resolve26) => {
32800
+ setImmediate(resolve26);
32559
32801
  });
32560
32802
  throwIfGitShutdownRequested();
32561
32803
  }
32562
32804
  async function forEachWithGitYield(items, fn) {
32563
32805
  for (let i = 0; i < items.length; i++) {
32564
- if (i > 0 || items.length > 1) await yieldToEventLoop2();
32806
+ await yieldToEventLoop2();
32565
32807
  await fn(items[i], i);
32566
32808
  }
32567
32809
  }
@@ -32880,9 +33122,9 @@ async function collectTurnGitDiffFromPreTurnSnapshot(options) {
32880
33122
  // src/agents/acp/put-summarize-change-summaries.ts
32881
33123
  async function putEncryptedChangeSummaryRows(params) {
32882
33124
  const base = params.apiBaseUrl.replace(/\/+$/, "");
32883
- const entries = params.rows.map(({ path: path46, summary }) => {
33125
+ const entries = params.rows.map(({ path: path53, summary }) => {
32884
33126
  const enc = params.e2ee.encryptFields({ summary }, ["summary"]);
32885
- return { path: path46, summary: JSON.stringify(enc) };
33127
+ return { path: path53, summary: JSON.stringify(enc) };
32886
33128
  });
32887
33129
  const res = await fetch(
32888
33130
  `${base}/internal/sessions/${encodeURIComponent(params.sessionId)}/follow-ups/summarize-changes`,
@@ -33129,7 +33371,15 @@ async function fetchInternalApiJson(params) {
33129
33371
  const token = params.getCloudAccessToken();
33130
33372
  if (!token) return { ok: false, error: "Missing cloud access token." };
33131
33373
  const url2 = `${internalApiBase(params.cloudApiBaseUrl)}${params.path}`;
33132
- const res = await fetch(url2, { headers: { Authorization: `Bearer ${token}` } });
33374
+ const method = params.method ?? "GET";
33375
+ const res = await fetch(url2, {
33376
+ method,
33377
+ headers: {
33378
+ Authorization: `Bearer ${token}`,
33379
+ ...params.body != null ? { "Content-Type": "application/json" } : {}
33380
+ },
33381
+ ...params.body != null ? { body: JSON.stringify(params.body) } : {}
33382
+ });
33133
33383
  if (!res.ok) {
33134
33384
  const t = await res.text().catch(() => "");
33135
33385
  return { ok: false, error: `${params.errorLabel} (${res.status}): ${t.slice(0, 200)}` };
@@ -33312,6 +33562,39 @@ async function enrichForkedSessionPromptForAgent(params) {
33312
33562
  return buildForkedSessionAgentPrompt(params.promptText, params.sessionId, parentSessionId);
33313
33563
  }
33314
33564
 
33565
+ // src/agents/acp/build-integration-content-agent-prompt-note.ts
33566
+ function buildIntegrationContentAgentPromptNote(sessionId) {
33567
+ const sessionRef = sessionId.trim();
33568
+ return [
33569
+ "The user message includes link(s) to external integration resources (Linear, GitHub, Jira, Notion, Slack thread, Zoom, or Asana).",
33570
+ "Use the get_integration_content MCP tool on the buildautomaton-bridge server to fetch the full content of each link.",
33571
+ `When calling the tool, pass sessionId "${sessionRef}" and the URL as url.`,
33572
+ "By default the tool links the fetched URL as this session external ticket when the session has none yet. Pass associateAsSessionTicket: false when fetching additional links for context only (after the primary ticket is linked, or when you do not want association).",
33573
+ "If the tool reports unsupported_url, do not retry with the same URL. If it reports integration_reconnect_required, ask the user to reconnect the integration in workspace settings. If it reports temporarily_unavailable, you may retry later."
33574
+ ].join("\n");
33575
+ }
33576
+ function injectIntegrationContentAgentPromptNote(agentPrompt, sessionId) {
33577
+ const note = buildIntegrationContentAgentPromptNote(sessionId);
33578
+ const marker = "\nUser request:\n";
33579
+ const idx = agentPrompt.indexOf(marker);
33580
+ if (idx >= 0) {
33581
+ return `${agentPrompt.slice(0, idx)}
33582
+ ${note}${agentPrompt.slice(idx)}`;
33583
+ }
33584
+ return `${note}
33585
+
33586
+ ${agentPrompt.trim()}`;
33587
+ }
33588
+
33589
+ // src/agents/acp/enrich-integration-content-prompt-for-agent.ts
33590
+ function enrichIntegrationContentPromptForAgent(params) {
33591
+ if (!params.sessionId.trim()) return params.agentPromptText;
33592
+ if (extractFetchableIntegrationLinks(params.originalPromptText).length === 0) {
33593
+ return params.agentPromptText;
33594
+ }
33595
+ return injectIntegrationContentAgentPromptNote(params.agentPromptText, params.sessionId);
33596
+ }
33597
+
33315
33598
  // src/agents/acp/send-prompt-to-agent.ts
33316
33599
  async function sendPromptToAgent(options) {
33317
33600
  const {
@@ -33322,7 +33605,7 @@ async function sendPromptToAgent(options) {
33322
33605
  runId,
33323
33606
  agentType,
33324
33607
  agentCwd,
33325
- sendResult: sendResult2,
33608
+ sendResult,
33326
33609
  sendSessionUpdate,
33327
33610
  log: log2,
33328
33611
  followUpCatalogPromptId,
@@ -33342,10 +33625,17 @@ async function sendPromptToAgent(options) {
33342
33625
  getCloudAccessToken
33343
33626
  });
33344
33627
  }
33628
+ if (sessionId) {
33629
+ agentPromptText = enrichIntegrationContentPromptForAgent({
33630
+ sessionId,
33631
+ originalPromptText: promptText,
33632
+ agentPromptText
33633
+ });
33634
+ }
33345
33635
  let sendOpts = {};
33346
33636
  if (attachments && attachments.length > 0) {
33347
33637
  if (!sessionId || !cloudApiBaseUrl || !getCloudAccessToken) {
33348
- sendResult2({
33638
+ sendResult({
33349
33639
  type: "prompt_result",
33350
33640
  id: promptId,
33351
33641
  ...sessionId ? { sessionId } : {},
@@ -33366,7 +33656,7 @@ async function sendPromptToAgent(options) {
33366
33656
  log: log2
33367
33657
  });
33368
33658
  if (!resolved.ok) {
33369
- sendResult2({
33659
+ sendResult({
33370
33660
  type: "prompt_result",
33371
33661
  id: promptId,
33372
33662
  ...sessionId ? { sessionId } : {},
@@ -33405,7 +33695,7 @@ async function sendPromptToAgent(options) {
33405
33695
  const errStr = typeof result.error === "string" ? result.error : void 0;
33406
33696
  const resultStop = typeof result.stopReason === "string" ? result.stopReason.trim() : "";
33407
33697
  const cancelledByAgent = resultStop.toLowerCase() === "cancelled" || errStr != null && isUserEndedSessionTurnErrorText(errStr);
33408
- sendResult2({
33698
+ sendResult({
33409
33699
  type: "prompt_result",
33410
33700
  id: promptId,
33411
33701
  ...sessionId ? { sessionId } : {},
@@ -33427,7 +33717,7 @@ async function sendPromptToAgent(options) {
33427
33717
  } catch (err) {
33428
33718
  const errMsg = err instanceof Error ? err.message : String(err);
33429
33719
  log2(`[Agent] Send failed: ${errMsg}`);
33430
- sendResult2({
33720
+ sendResult({
33431
33721
  type: "prompt_result",
33432
33722
  id: promptId,
33433
33723
  ...sessionId ? { sessionId } : {},
@@ -33461,7 +33751,7 @@ function handlePrompt(ctx, opts) {
33461
33751
  agentType,
33462
33752
  agentConfig,
33463
33753
  sessionParentPath,
33464
- sendResult: sendResult2,
33754
+ sendResult,
33465
33755
  sendSessionUpdate,
33466
33756
  followUpCatalogPromptId,
33467
33757
  sessionChangeSummaryFilePaths,
@@ -33478,7 +33768,7 @@ function handlePrompt(ctx, opts) {
33478
33768
  const activeRunId = runId;
33479
33769
  const acpAgentKey = computeAcpAgentKey(preferredForPrompt, mode, agentConfig ?? null);
33480
33770
  if (!acpAgentKey) {
33481
- sendResult2({
33771
+ sendResult({
33482
33772
  type: "prompt_result",
33483
33773
  id: promptId,
33484
33774
  ...sessionId ? { sessionId } : {},
@@ -33515,7 +33805,7 @@ function handlePrompt(ctx, opts) {
33515
33805
  const evaluated = Boolean(preferredForPrompt && errMsg.trim());
33516
33806
  const suggestsAuth = evaluated ? localAgentErrorSuggestsAuth(preferredForPrompt, errMsg) : false;
33517
33807
  const auth = suggestsAuth && preferredForPrompt ? { agentAuthRequired: true, agentType: preferredForPrompt } : {};
33518
- sendResult2({
33808
+ sendResult({
33519
33809
  type: "prompt_result",
33520
33810
  id: promptId,
33521
33811
  ...sessionId ? { sessionId } : {},
@@ -33535,7 +33825,7 @@ function handlePrompt(ctx, opts) {
33535
33825
  await handle.cancel?.();
33536
33826
  } catch {
33537
33827
  }
33538
- sendResult2({
33828
+ sendResult({
33539
33829
  type: "prompt_result",
33540
33830
  id: promptId,
33541
33831
  ...sessionId ? { sessionId } : {},
@@ -33556,7 +33846,7 @@ function handlePrompt(ctx, opts) {
33556
33846
  runId: activeRunId,
33557
33847
  agentType: preferredForPrompt,
33558
33848
  agentCwd: resolveSessionParentPathForAgentProcess(sessionParentPath),
33559
- sendResult: sendResult2,
33849
+ sendResult,
33560
33850
  sendSessionUpdate,
33561
33851
  log: ctx.log,
33562
33852
  followUpCatalogPromptId,
@@ -33612,6 +33902,563 @@ async function createAcpManager(options) {
33612
33902
  };
33613
33903
  }
33614
33904
 
33905
+ // src/worktrees/worktree-layout-file.ts
33906
+ import * as fs16 from "node:fs";
33907
+ import * as path19 from "node:path";
33908
+ import os7 from "node:os";
33909
+ var LAYOUT_FILENAME = "worktree-launcher-layout.json";
33910
+ function defaultWorktreeLayoutPath() {
33911
+ return path19.join(os7.homedir(), ".buildautomaton", LAYOUT_FILENAME);
33912
+ }
33913
+ function normalizeLoadedLayout(raw) {
33914
+ if (raw && typeof raw === "object" && "launcherCwds" in raw) {
33915
+ const j = raw;
33916
+ if (Array.isArray(j.launcherCwds)) return { launcherCwds: j.launcherCwds };
33917
+ }
33918
+ return { launcherCwds: [] };
33919
+ }
33920
+ function loadWorktreeLayout() {
33921
+ try {
33922
+ const p = defaultWorktreeLayoutPath();
33923
+ if (!fs16.existsSync(p)) return { launcherCwds: [] };
33924
+ const raw = JSON.parse(fs16.readFileSync(p, "utf8"));
33925
+ return normalizeLoadedLayout(raw);
33926
+ } catch {
33927
+ return { launcherCwds: [] };
33928
+ }
33929
+ }
33930
+ function saveWorktreeLayout(layout) {
33931
+ try {
33932
+ const dir = path19.dirname(defaultWorktreeLayoutPath());
33933
+ fs16.mkdirSync(dir, { recursive: true });
33934
+ fs16.writeFileSync(defaultWorktreeLayoutPath(), JSON.stringify(layout, null, 2), "utf8");
33935
+ } catch {
33936
+ }
33937
+ }
33938
+ function baseNameSafe(pathString) {
33939
+ return path19.basename(pathString).replace(/[^a-zA-Z0-9._-]+/g, "-") || "cwd";
33940
+ }
33941
+ function getLauncherDirNameIfPresent(layout, bridgeRootPath2) {
33942
+ const norm = path19.resolve(bridgeRootPath2);
33943
+ const existing = layout.launcherCwds.find((e) => path19.resolve(e.absolutePath) === norm);
33944
+ return existing?.dirName;
33945
+ }
33946
+ function allocateDirNameForLauncherCwd(layout, bridgeRootPath2) {
33947
+ const existing = getLauncherDirNameIfPresent(layout, bridgeRootPath2);
33948
+ if (existing) return existing;
33949
+ const norm = path19.resolve(bridgeRootPath2);
33950
+ const base = baseNameSafe(norm);
33951
+ const used = new Set(layout.launcherCwds.map((e) => e.dirName));
33952
+ let name = base;
33953
+ let n = 2;
33954
+ while (used.has(name)) {
33955
+ name = `${base}-${n}`;
33956
+ n += 1;
33957
+ }
33958
+ layout.launcherCwds.push({ absolutePath: norm, dirName: name });
33959
+ saveWorktreeLayout(layout);
33960
+ return name;
33961
+ }
33962
+
33963
+ // src/worktrees/discovery/discover-session-worktree-on-disk.ts
33964
+ import * as fs20 from "node:fs";
33965
+ import * as path24 from "node:path";
33966
+
33967
+ // src/worktrees/discovery/collect-worktree-paths.ts
33968
+ import * as fs18 from "node:fs";
33969
+ import * as path21 from "node:path";
33970
+
33971
+ // src/worktrees/discovery/disk-walk-constants.ts
33972
+ var DISK_WALK_YIELD_EVERY = 64;
33973
+ var LEGACY_WALK_MAX_DEPTH = 12;
33974
+ var SKIP_DISK_WALK_DIR_NAMES = /* @__PURE__ */ new Set([
33975
+ "node_modules",
33976
+ "bower_components",
33977
+ "vendor",
33978
+ "Pods",
33979
+ "Carthage",
33980
+ "DerivedData",
33981
+ ".yarn",
33982
+ ".pnpm-store",
33983
+ "dist",
33984
+ "build",
33985
+ ".next",
33986
+ "coverage",
33987
+ ".turbo",
33988
+ ".cache",
33989
+ "target",
33990
+ "__pycache__"
33991
+ ]);
33992
+ function shouldSkipDiskWalkEntry(name) {
33993
+ return name.startsWith(".") || SKIP_DISK_WALK_DIR_NAMES.has(name);
33994
+ }
33995
+
33996
+ // src/worktrees/discovery/disk-walk-utils.ts
33997
+ import * as fs17 from "node:fs";
33998
+ import * as path20 from "node:path";
33999
+ async function yieldDuringDiskWalk(state) {
34000
+ state.entries++;
34001
+ if (state.entries % DISK_WALK_YIELD_EVERY !== 0) return true;
34002
+ if (isCliImmediateShutdownRequested()) return false;
34003
+ await yieldToEventLoop();
34004
+ return !isCliImmediateShutdownRequested();
34005
+ }
34006
+ async function isGitDir(dirPath) {
34007
+ try {
34008
+ await fs17.promises.access(path20.join(dirPath, ".git"));
34009
+ return true;
34010
+ } catch {
34011
+ return false;
34012
+ }
34013
+ }
34014
+
34015
+ // src/worktrees/discovery/collect-worktree-paths.ts
34016
+ async function collectGitRepoRootsUnderDirectory(rootPath) {
34017
+ const out = [];
34018
+ const state = { entries: 0 };
34019
+ const walk = async (dir) => {
34020
+ if (!await yieldDuringDiskWalk(state)) return;
34021
+ if (await isGitDir(dir)) {
34022
+ out.push(path21.resolve(dir));
34023
+ return;
34024
+ }
34025
+ let entries;
34026
+ try {
34027
+ entries = await fs18.promises.readdir(dir, { withFileTypes: true });
34028
+ } catch {
34029
+ return;
34030
+ }
34031
+ for (const e of entries) {
34032
+ if (shouldSkipDiskWalkEntry(e.name)) continue;
34033
+ const full = path21.join(dir, e.name);
34034
+ if (!e.isDirectory()) continue;
34035
+ await walk(full);
34036
+ }
34037
+ };
34038
+ await walk(path21.resolve(rootPath));
34039
+ return { paths: [...new Set(out)], entriesVisited: state.entries };
34040
+ }
34041
+ async function collectWorktreeRootsNamed(root, sessionId, maxDepth = LEGACY_WALK_MAX_DEPTH) {
34042
+ const out = [];
34043
+ const state = { entries: 0 };
34044
+ const walk = async (dir, depth) => {
34045
+ if (depth > maxDepth) return;
34046
+ if (!await yieldDuringDiskWalk(state)) return;
34047
+ let entries;
34048
+ try {
34049
+ entries = await fs18.promises.readdir(dir, { withFileTypes: true });
34050
+ } catch {
34051
+ return;
34052
+ }
34053
+ for (const e of entries) {
34054
+ if (shouldSkipDiskWalkEntry(e.name)) continue;
34055
+ const full = path21.join(dir, e.name);
34056
+ if (!e.isDirectory()) continue;
34057
+ if (e.name === sessionId) {
34058
+ if (await isGitDir(full)) out.push(path21.resolve(full));
34059
+ continue;
34060
+ }
34061
+ if (await isGitDir(full)) continue;
34062
+ await walk(full, depth + 1);
34063
+ }
34064
+ };
34065
+ await walk(root, 0);
34066
+ return { paths: out, entriesVisited: state.entries };
34067
+ }
34068
+
34069
+ // src/worktrees/discovery/layout-keys-for-bridge-root.ts
34070
+ import * as path22 from "node:path";
34071
+ function layoutKeysForBridgeRoot(layout, bridgeRoot, keysOnDisk) {
34072
+ const bridge = path22.resolve(bridgeRoot);
34073
+ const preferred = getLauncherDirNameIfPresent(layout, bridgeRoot);
34074
+ const relevant = layout.launcherCwds.filter((e) => {
34075
+ const entry = path22.resolve(e.absolutePath);
34076
+ return bridge === entry || bridge.startsWith(entry + path22.sep) || entry.startsWith(bridge + path22.sep);
34077
+ }).sort((a, b) => path22.resolve(b.absolutePath).length - path22.resolve(a.absolutePath).length).map((e) => e.dirName).filter((name) => keysOnDisk.includes(name));
34078
+ const ordered = [];
34079
+ const seen = /* @__PURE__ */ new Set();
34080
+ const add = (k) => {
34081
+ if (seen.has(k) || !keysOnDisk.includes(k)) return;
34082
+ seen.add(k);
34083
+ ordered.push(k);
34084
+ };
34085
+ if (preferred) add(preferred);
34086
+ for (const k of relevant) add(k);
34087
+ if (relevant.length > 0) {
34088
+ return ordered;
34089
+ }
34090
+ for (const k of keysOnDisk) add(k);
34091
+ return ordered;
34092
+ }
34093
+
34094
+ // src/worktrees/discovery/try-binding-from-session-directory.ts
34095
+ import * as fs19 from "node:fs";
34096
+ import * as path23 from "node:path";
34097
+ async function tryBindingFromSessionDirectory(sessionDir) {
34098
+ let st;
34099
+ try {
34100
+ st = await fs19.promises.stat(sessionDir);
34101
+ } catch {
34102
+ return null;
34103
+ }
34104
+ if (!st.isDirectory()) return null;
34105
+ const { paths: worktreePaths } = await collectGitRepoRootsUnderDirectory(sessionDir);
34106
+ if (worktreePaths.length === 0) return null;
34107
+ const abs = path23.resolve(sessionDir);
34108
+ return {
34109
+ sessionParentPath: abs,
34110
+ workingTreeRelRoot: abs,
34111
+ repoCheckoutPaths: worktreePaths
34112
+ };
34113
+ }
34114
+
34115
+ // src/worktrees/discovery/discover-session-worktree-on-disk.ts
34116
+ async function discoverSessionWorktreeOnDisk(options) {
34117
+ const { sessionId, worktreesRootPath, layout, bridgeRoot } = options;
34118
+ const sid = sessionId.trim();
34119
+ if (!sid) return null;
34120
+ try {
34121
+ await fs20.promises.access(worktreesRootPath);
34122
+ } catch {
34123
+ return null;
34124
+ }
34125
+ const preferredKey = getLauncherDirNameIfPresent(layout, bridgeRoot);
34126
+ const keysOnDisk = [];
34127
+ if (preferredKey) keysOnDisk.push(preferredKey);
34128
+ try {
34129
+ for (const name of await fs20.promises.readdir(worktreesRootPath)) {
34130
+ if (name.startsWith(".")) continue;
34131
+ const p = path24.join(worktreesRootPath, name);
34132
+ let st;
34133
+ try {
34134
+ st = await fs20.promises.stat(p);
34135
+ } catch {
34136
+ continue;
34137
+ }
34138
+ if (!st.isDirectory()) continue;
34139
+ if (!keysOnDisk.includes(name)) keysOnDisk.push(name);
34140
+ }
34141
+ } catch {
34142
+ return null;
34143
+ }
34144
+ const keys = layoutKeysForBridgeRoot(layout, bridgeRoot, keysOnDisk);
34145
+ for (const key of keys) {
34146
+ if (isCliImmediateShutdownRequested()) return null;
34147
+ await yieldToEventLoop();
34148
+ const layoutRoot = path24.join(worktreesRootPath, key);
34149
+ let layoutSt;
34150
+ try {
34151
+ layoutSt = await fs20.promises.stat(layoutRoot);
34152
+ } catch {
34153
+ continue;
34154
+ }
34155
+ if (!layoutSt.isDirectory()) continue;
34156
+ const sessionDir = path24.join(layoutRoot, sid);
34157
+ const nested = await tryBindingFromSessionDirectory(sessionDir);
34158
+ if (nested) return nested;
34159
+ const legacy = await collectWorktreeRootsNamed(layoutRoot, sid);
34160
+ if (legacy.paths.length > 0) {
34161
+ const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacy.paths) ?? path24.resolve(legacy.paths[0]);
34162
+ return {
34163
+ sessionParentPath: path24.resolve(isolated),
34164
+ workingTreeRelRoot: path24.resolve(layoutRoot),
34165
+ repoCheckoutPaths: legacy.paths.map((p) => path24.resolve(p))
34166
+ };
34167
+ }
34168
+ }
34169
+ return null;
34170
+ }
34171
+
34172
+ // src/worktrees/discovery/discover-session-worktrees-under-session-worktree-root.ts
34173
+ import * as fs21 from "node:fs";
34174
+ import * as path26 from "node:path";
34175
+
34176
+ // src/worktrees/discovery/discover-legacy-binding-ascending-from-checkout.ts
34177
+ import * as path25 from "node:path";
34178
+ async function discoverLegacyBindingAscendingFromCheckout(sessionId, checkoutPath) {
34179
+ const sid = sessionId.trim();
34180
+ if (!sid) return null;
34181
+ const hintR = path25.resolve(checkoutPath);
34182
+ let best = null;
34183
+ let cur = path25.dirname(hintR);
34184
+ for (let i = 0; i < 40; i++) {
34185
+ if (isCliImmediateShutdownRequested()) return best;
34186
+ await yieldToEventLoop();
34187
+ const paths = await collectWorktreeRootsNamed(cur, sid);
34188
+ if (paths.paths.some((p) => path25.resolve(p) === hintR)) {
34189
+ const isolated = resolveIsolatedSessionParentPathFromCheckouts(paths.paths) ?? path25.resolve(paths.paths[0]);
34190
+ best = {
34191
+ sessionParentPath: path25.resolve(isolated),
34192
+ workingTreeRelRoot: path25.resolve(cur),
34193
+ repoCheckoutPaths: paths.paths.map((p) => path25.resolve(p))
34194
+ };
34195
+ }
34196
+ const next = path25.dirname(cur);
34197
+ if (next === cur) break;
34198
+ cur = next;
34199
+ }
34200
+ return best;
34201
+ }
34202
+
34203
+ // src/worktrees/discovery/discover-session-worktrees-under-session-worktree-root.ts
34204
+ async function discoverSessionWorktreesUnderSessionWorktreeRoot(sessionWorktreeRootPathOrHint, sessionId) {
34205
+ const sid = sessionId.trim();
34206
+ if (!sid) return null;
34207
+ const hint = path26.resolve(sessionWorktreeRootPathOrHint);
34208
+ const underHint = await tryBindingFromSessionDirectory(path26.join(hint, sid));
34209
+ if (underHint) return underHint;
34210
+ const direct = await tryBindingFromSessionDirectory(hint);
34211
+ if (direct) {
34212
+ if (path26.basename(hint) === sid && await isGitDir(hint)) {
34213
+ const legacyFromCheckout = await discoverLegacyBindingAscendingFromCheckout(sid, hint);
34214
+ if (legacyFromCheckout && legacyFromCheckout.repoCheckoutPaths.length > direct.repoCheckoutPaths.length) {
34215
+ return legacyFromCheckout;
34216
+ }
34217
+ }
34218
+ return direct;
34219
+ }
34220
+ if (path26.basename(hint) === sid && await isGitDir(hint)) {
34221
+ const legacyFromCheckout = await discoverLegacyBindingAscendingFromCheckout(sid, hint);
34222
+ if (legacyFromCheckout) return legacyFromCheckout;
34223
+ }
34224
+ let st;
34225
+ try {
34226
+ st = await fs21.promises.stat(hint);
34227
+ } catch {
34228
+ return null;
34229
+ }
34230
+ if (!st.isDirectory()) return null;
34231
+ const legacyPaths = await collectWorktreeRootsNamed(hint, sid);
34232
+ if (legacyPaths.paths.length === 0) return null;
34233
+ const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths.paths) ?? path26.resolve(legacyPaths.paths[0]);
34234
+ return {
34235
+ sessionParentPath: path26.resolve(isolated),
34236
+ workingTreeRelRoot: hint,
34237
+ repoCheckoutPaths: legacyPaths.paths.map((p) => path26.resolve(p))
34238
+ };
34239
+ }
34240
+
34241
+ // src/worktrees/manager/discover-session-binding.ts
34242
+ var discoverInflightBySessionId = /* @__PURE__ */ new Map();
34243
+ function discoverSessionBindingAsync(params) {
34244
+ const sid = params.sessionId.trim();
34245
+ if (!sid) return Promise.resolve(null);
34246
+ const existing = discoverInflightBySessionId.get(sid);
34247
+ if (existing) return existing;
34248
+ const promise2 = discoverSessionWorktreeOnDisk({
34249
+ sessionId: sid,
34250
+ worktreesRootPath: params.worktreesRootPath,
34251
+ layout: params.layout,
34252
+ bridgeRoot: getBridgeRoot()
34253
+ }).finally(() => {
34254
+ if (discoverInflightBySessionId.get(sid) === promise2) {
34255
+ discoverInflightBySessionId.delete(sid);
34256
+ }
34257
+ });
34258
+ discoverInflightBySessionId.set(sid, promise2);
34259
+ return promise2;
34260
+ }
34261
+
34262
+ // src/worktrees/manager/resolve-isolated-session-parent-path.ts
34263
+ function resolveIsolatedSessionParentPath(sessionId, cache2, ensureRepoCheckoutPaths) {
34264
+ if (!sessionId) return null;
34265
+ const sid = sessionId.trim();
34266
+ const cached2 = cache2.getSessionParentPath(sid);
34267
+ if (cached2) return cached2;
34268
+ const paths = ensureRepoCheckoutPaths(sid);
34269
+ if (!paths?.length) return null;
34270
+ return resolveIsolatedSessionParentPathFromCheckouts(paths);
34271
+ }
34272
+ function ensureRepoCheckoutPathsForSession(sessionId, cache2) {
34273
+ if (!sessionId?.trim()) return void 0;
34274
+ const cached2 = cache2.getRepoCheckoutPaths(sessionId.trim());
34275
+ return cached2?.length ? [...cached2] : void 0;
34276
+ }
34277
+ async function ensureRepoCheckoutPathsForSessionAsync(sessionId, cache2, discover) {
34278
+ if (!sessionId?.trim()) return void 0;
34279
+ const sid = sessionId.trim();
34280
+ const cached2 = cache2.getRepoCheckoutPaths(sid);
34281
+ if (cached2?.length) return cached2;
34282
+ const disc = await discover(sid);
34283
+ if (disc?.repoCheckoutPaths.length) {
34284
+ cache2.remember(sid, disc);
34285
+ return [...disc.repoCheckoutPaths];
34286
+ }
34287
+ return void 0;
34288
+ }
34289
+
34290
+ // src/worktrees/manager/parse-session-parent.ts
34291
+ function parseSessionParent(v) {
34292
+ if (v === "bridge_root" || v === "worktrees_root") return v;
34293
+ if (v === "session_worktrees_root") return "worktrees_root";
34294
+ return null;
34295
+ }
34296
+
34297
+ // src/worktrees/prepare-new-session-worktrees.ts
34298
+ import * as fs22 from "node:fs";
34299
+ import * as path27 from "node:path";
34300
+
34301
+ // src/git/worktrees/worktree-add.ts
34302
+ async function gitWorktreeAddBranch(mainRepoPath, worktreePath, branch, baseRef = "HEAD") {
34303
+ const mainGit = cliSimpleGit(mainRepoPath);
34304
+ const base = baseRef.trim() || "HEAD";
34305
+ await mainGit.raw(["worktree", "add", "-b", branch, worktreePath, base]);
34306
+ }
34307
+
34308
+ // src/worktrees/prepare-new-session-worktrees.ts
34309
+ function normalizeRepoRelPath(rel) {
34310
+ return rel === "" ? "." : rel.replace(/\\/g, "/");
34311
+ }
34312
+ function resolveBaseRefForRepo(relNorm, baseBranches) {
34313
+ if (!baseBranches) return "HEAD";
34314
+ const direct = baseBranches[relNorm]?.trim();
34315
+ if (direct) return direct;
34316
+ if (relNorm !== "." && baseBranches["."]?.trim()) return baseBranches["."].trim();
34317
+ return "HEAD";
34318
+ }
34319
+ async function prepareNewSessionWorktrees(options) {
34320
+ const { worktreesRootPath, bridgeRoot, sessionId, layout, log: log2, worktreeBaseBranches } = options;
34321
+ const bridgeResolved = path27.resolve(bridgeRoot);
34322
+ const cwdKey = allocateDirNameForLauncherCwd(layout, bridgeResolved);
34323
+ const bridgeKeyDir = path27.join(worktreesRootPath, cwdKey);
34324
+ const sessionDir = path27.join(bridgeKeyDir, sessionId);
34325
+ const repos = await discoverGitReposUnderRoot(bridgeResolved);
34326
+ if (repos.length === 0) {
34327
+ log2("[worktrees] No Git repositories under bridge root; skipping worktree creation.");
34328
+ return null;
34329
+ }
34330
+ const branch = `session-${sessionId}`;
34331
+ const worktreePaths = [];
34332
+ fs22.mkdirSync(sessionDir, { recursive: true });
34333
+ for (const repo of repos) {
34334
+ let rel = path27.relative(bridgeResolved, repo.absolutePath);
34335
+ if (rel.startsWith("..") || path27.isAbsolute(rel)) continue;
34336
+ const relNorm = normalizeRepoRelPath(rel === "" ? "." : rel);
34337
+ const wtPath = relNorm === "." ? sessionDir : path27.join(sessionDir, relNorm);
34338
+ if (relNorm !== ".") {
34339
+ fs22.mkdirSync(path27.dirname(wtPath), { recursive: true });
34340
+ }
34341
+ const baseRef = resolveBaseRefForRepo(relNorm, worktreeBaseBranches);
34342
+ try {
34343
+ await gitWorktreeAddBranch(repo.absolutePath, wtPath, branch, baseRef);
34344
+ log2(`[worktrees] Added worktree ${wtPath} (branch ${branch}, base ${baseRef}).`);
34345
+ worktreePaths.push(wtPath);
34346
+ } catch (e) {
34347
+ log2(
34348
+ `[worktrees] Worktree add failed for ${repo.absolutePath}: ${e instanceof Error ? e.message : String(e)}`
34349
+ );
34350
+ }
34351
+ }
34352
+ if (worktreePaths.length === 0) return null;
34353
+ return {
34354
+ worktreePaths,
34355
+ sessionParentPath: sessionDir,
34356
+ workingTreeRelRoot: sessionDir
34357
+ };
34358
+ }
34359
+
34360
+ // src/worktrees/manager/prepare-and-remember-session-worktrees.ts
34361
+ async function prepareAndRememberSessionWorktrees(params) {
34362
+ const prep = await prepareNewSessionWorktrees({
34363
+ worktreesRootPath: params.worktreesRootPath,
34364
+ bridgeRoot: getBridgeRoot(),
34365
+ sessionId: params.sessionId,
34366
+ layout: params.layout,
34367
+ log: params.log,
34368
+ ...params.worktreeBaseBranches && Object.keys(params.worktreeBaseBranches).length > 0 ? { worktreeBaseBranches: params.worktreeBaseBranches } : {}
34369
+ });
34370
+ if (!prep) return void 0;
34371
+ params.cache.remember(params.sessionId, {
34372
+ sessionParentPath: prep.sessionParentPath,
34373
+ workingTreeRelRoot: prep.workingTreeRelRoot,
34374
+ repoCheckoutPaths: prep.worktreePaths
34375
+ });
34376
+ return params.cache.getSessionParentPath(params.sessionId);
34377
+ }
34378
+
34379
+ // src/worktrees/manager/resolve-existing-session-parent-path.ts
34380
+ async function resolveExistingSessionParentPath(sessionId, cache2, discover) {
34381
+ const cached2 = cache2.getSessionParentPath(sessionId);
34382
+ if (cached2) return cached2;
34383
+ const disc = await discover();
34384
+ if (disc) {
34385
+ cache2.remember(sessionId, disc);
34386
+ return cache2.getSessionParentPath(sessionId);
34387
+ }
34388
+ return void 0;
34389
+ }
34390
+
34391
+ // src/worktrees/manager/resolve-explicit-session-parent-path.ts
34392
+ import * as path28 from "node:path";
34393
+ async function resolveExplicitSessionParentPath(params) {
34394
+ const resolved = path28.resolve(params.parentPathRaw);
34395
+ if (parseSessionParent(params.sessionParent) !== "worktrees_root") {
34396
+ return resolved;
34397
+ }
34398
+ const rememberAndReturn = (binding) => {
34399
+ params.cache.remember(params.sessionId, binding);
34400
+ return params.cache.getSessionParentPath(params.sessionId) ?? resolved;
34401
+ };
34402
+ const diskFirst = await params.discover();
34403
+ if (diskFirst) return rememberAndReturn(diskFirst);
34404
+ const fromRoot = await discoverSessionWorktreesUnderSessionWorktreeRoot(resolved, params.sessionId);
34405
+ if (fromRoot) return rememberAndReturn(fromRoot);
34406
+ let cur = resolved;
34407
+ for (let i = 0; i < 16; i++) {
34408
+ await yieldToEventLoop();
34409
+ const tryRoot = await discoverSessionWorktreesUnderSessionWorktreeRoot(cur, params.sessionId);
34410
+ if (tryRoot) return rememberAndReturn(tryRoot);
34411
+ const next = path28.dirname(cur);
34412
+ if (next === cur) break;
34413
+ cur = next;
34414
+ }
34415
+ return resolved;
34416
+ }
34417
+
34418
+ // src/worktrees/manager/resolve-session-parent-path-for-prompt.ts
34419
+ async function resolveSessionParentPathForPrompt(params) {
34420
+ const { sessionId, cache: cache2, worktreesRootPath, layout, log: log2, discover, opts } = params;
34421
+ if (!sessionId) return void 0;
34422
+ const sid = sessionId.trim();
34423
+ const parentPathRaw = opts.sessionParentPath?.trim();
34424
+ if (parentPathRaw) {
34425
+ return resolveExplicitSessionParentPath({
34426
+ sessionId: sid,
34427
+ sessionParent: opts.sessionParent,
34428
+ parentPathRaw,
34429
+ cache: cache2,
34430
+ discover: () => discover(sid)
34431
+ });
34432
+ }
34433
+ const parentKind = parseSessionParent(opts.sessionParent);
34434
+ if (parentKind === "bridge_root") {
34435
+ return void 0;
34436
+ }
34437
+ if (parentKind === "worktrees_root") {
34438
+ if (!opts.isNewSession) {
34439
+ return resolveExistingSessionParentPath(sid, cache2, () => discover(sid));
34440
+ }
34441
+ return prepareAndRememberSessionWorktrees({
34442
+ cache: cache2,
34443
+ sessionId: sid,
34444
+ worktreesRootPath,
34445
+ layout,
34446
+ log: log2,
34447
+ ...opts.worktreeBaseBranches ? { worktreeBaseBranches: opts.worktreeBaseBranches } : {}
34448
+ });
34449
+ }
34450
+ if (!opts.isNewSession) {
34451
+ return resolveExistingSessionParentPath(sid, cache2, () => discover(sid));
34452
+ }
34453
+ return prepareAndRememberSessionWorktrees({
34454
+ cache: cache2,
34455
+ sessionId: sid,
34456
+ worktreesRootPath,
34457
+ layout,
34458
+ log: log2
34459
+ });
34460
+ }
34461
+
33615
34462
  // src/git/changes/types.ts
33616
34463
  var MAX_PATCH_CHARS = 35e4;
33617
34464
 
@@ -33669,8 +34516,11 @@ function formatRemoteDisplayLabel(remoteUrl) {
33669
34516
  return `origin \xB7 ${hostPath}`;
33670
34517
  }
33671
34518
 
33672
- // src/git/changes/get-working-tree-change-repo-details.ts
33673
- import * as path22 from "node:path";
34519
+ // src/git/changes/repo/get-working-tree-change-repo-details.ts
34520
+ import * as path33 from "node:path";
34521
+
34522
+ // src/git/changes/repo/build-working-tree-change-repo-detail.ts
34523
+ import * as path32 from "node:path";
33674
34524
 
33675
34525
  // src/git/commits/resolve-remote-tracking.ts
33676
34526
  async function tryConfigGet(g, key) {
@@ -33841,8 +34691,8 @@ async function listRecentCommits(repoDir, limitInput) {
33841
34691
  }
33842
34692
 
33843
34693
  // src/git/changes/parse/normalize-git-diff-path.ts
33844
- function normalizeGitDiffPath(path46) {
33845
- return path46.replace(/\\/g, "/").replace(/\/ +/g, "/").trim();
34694
+ function normalizeGitDiffPath(path53) {
34695
+ return path53.replace(/\\/g, "/").replace(/\/ +/g, "/").trim();
33846
34696
  }
33847
34697
 
33848
34698
  // src/git/changes/parse/expand-git-rename-numstat-path.ts
@@ -33955,8 +34805,8 @@ async function unifiedDiffForFileInRange(repoCwd, range, pathInRepo, change, mov
33955
34805
  }
33956
34806
 
33957
34807
  // src/git/changes/total-lines/resolve-changed-file-total-lines.ts
33958
- import * as fs16 from "node:fs";
33959
- import * as path19 from "node:path";
34808
+ import * as fs23 from "node:fs";
34809
+ import * as path29 from "node:path";
33960
34810
 
33961
34811
  // src/git/changes/lines/count-lines.ts
33962
34812
  import { createReadStream } from "node:fs";
@@ -34002,13 +34852,13 @@ async function countLinesInGitRef(repoGitCwd, ref, pathInRepo) {
34002
34852
  }
34003
34853
  async function resolveWorkingTreeFileTotalLines(options) {
34004
34854
  if (options.isBinary) return null;
34005
- const filePath = path19.join(options.repoGitCwd, options.pathInRepo);
34855
+ const filePath = path29.join(options.repoGitCwd, options.pathInRepo);
34006
34856
  if (options.change === "removed") {
34007
34857
  return countLinesInGitRef(options.repoGitCwd, "HEAD", options.pathInRepo);
34008
34858
  }
34009
34859
  if (options.change === "moved" || options.change === "modified") {
34010
34860
  try {
34011
- const st = await fs16.promises.stat(filePath);
34861
+ const st = await fs23.promises.stat(filePath);
34012
34862
  if (!st.isFile()) return null;
34013
34863
  return await countTextFileLines(filePath);
34014
34864
  } catch {
@@ -34037,7 +34887,7 @@ function pathInRepoFromLauncher(pathRelLauncher, repoRelPath) {
34037
34887
  }
34038
34888
  return pathRelLauncher;
34039
34889
  }
34040
- function normalizeRepoRelPath(repoRelPath) {
34890
+ function normalizeRepoRelPath2(repoRelPath) {
34041
34891
  return repoRelPath === "." || repoRelPath === "" ? "." : repoRelPath;
34042
34892
  }
34043
34893
 
@@ -34139,8 +34989,8 @@ function parseNumstatEntries(lines) {
34139
34989
  }
34140
34990
  function parseNumstat(lines) {
34141
34991
  const m = /* @__PURE__ */ new Map();
34142
- for (const [path46, entry] of parseNumstatEntries(lines)) {
34143
- m.set(path46, { additions: entry.additions, deletions: entry.deletions });
34992
+ for (const [path53, entry] of parseNumstatEntries(lines)) {
34993
+ m.set(path53, { additions: entry.additions, deletions: entry.deletions });
34144
34994
  }
34145
34995
  return m;
34146
34996
  }
@@ -34271,7 +35121,7 @@ async function listChangedFilesForCommit(repoGitCwd, repoRelPath, commitSha) {
34271
35121
  g.raw(["diff", ...RENAME_DIFF_ARGS, "--numstat", range]).catch(() => "")
34272
35122
  ]);
34273
35123
  const parsed = parseDiffOutput(String(nameStatusRaw), String(numstatRaw));
34274
- const normRel = normalizeRepoRelPath(repoRelPath);
35124
+ const normRel = normalizeRepoRelPath2(repoRelPath);
34275
35125
  const paths = collectChangedPaths({
34276
35126
  nameEntries: parsed.nameEntries,
34277
35127
  numByPath: parsed.numByPath
@@ -34299,14 +35149,14 @@ async function listChangedFilesForCommit(repoGitCwd, repoRelPath, commitSha) {
34299
35149
  }
34300
35150
 
34301
35151
  // src/git/changes/listing/apply-untracked-file-stats.ts
34302
- import * as fs17 from "node:fs";
34303
- import * as path20 from "node:path";
35152
+ import * as fs24 from "node:fs";
35153
+ import * as path30 from "node:path";
34304
35154
  function createUntrackedStatsApplier(options) {
34305
35155
  return async (row, pathInRepo) => {
34306
35156
  if (!options.untrackedSet.has(pathInRepo)) return;
34307
35157
  if (options.nameByPath.has(pathInRepo)) return;
34308
35158
  if (row.change === "moved") return;
34309
- const repoFilePath = path20.join(options.repoGitCwd, pathInRepo);
35159
+ const repoFilePath = path30.join(options.repoGitCwd, pathInRepo);
34310
35160
  const fromGit = await numstatFromGitNoIndex(options.g, pathInRepo);
34311
35161
  if (fromGit) {
34312
35162
  row.additions = fromGit.additions;
@@ -34314,7 +35164,7 @@ function createUntrackedStatsApplier(options) {
34314
35164
  return;
34315
35165
  }
34316
35166
  try {
34317
- const st = await fs17.promises.stat(repoFilePath);
35167
+ const st = await fs24.promises.stat(repoFilePath);
34318
35168
  row.additions = st.isFile() ? await countTextFileLines(repoFilePath) : 0;
34319
35169
  } catch {
34320
35170
  row.additions = 0;
@@ -34324,13 +35174,75 @@ function createUntrackedStatsApplier(options) {
34324
35174
  }
34325
35175
 
34326
35176
  // src/git/changes/listing/enrich-working-tree-file-rows.ts
34327
- import * as path21 from "node:path";
35177
+ import * as path31 from "node:path";
34328
35178
 
34329
- // src/git/changes/patch/hydrate-patch.ts
34330
- import * as fs18 from "node:fs";
35179
+ // src/git/changes/patch/hydrate/append-injected-context-lines.ts
35180
+ async function appendInjectedContextLines(out, inject, maybeYield, injectedTotal) {
35181
+ for (const t of inject) {
35182
+ out.push(` ${t}`);
35183
+ injectedTotal.value++;
35184
+ await maybeYield();
35185
+ }
35186
+ }
35187
+
35188
+ // src/git/changes/patch/hydrate/constants.ts
34331
35189
  var UNIFIED_HUNK_HEADER_RE = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
34332
35190
  var MAX_HYDRATE_LINES_PER_GAP = 8e3;
34333
35191
  var MAX_HYDRATE_LINES_PER_FILE = 8e4;
35192
+ var HYDRATE_YIELD_EVERY_LINES = 512;
35193
+
35194
+ // src/git/changes/patch/hydrate/collect-gap-inject-lines.ts
35195
+ async function collectGapInjectLines(options) {
35196
+ if (options.injectedTotal >= MAX_HYDRATE_LINES_PER_FILE) return null;
35197
+ const remaining = MAX_HYDRATE_LINES_PER_FILE - options.injectedTotal;
35198
+ if (options.gapNewEnd >= options.gapNewStart && options.change !== "removed") {
35199
+ const nNew = options.gapNewEnd - options.gapNewStart + 1;
35200
+ if (options.gapOldEnd < options.gapOldStart || options.gapOldEnd - options.gapOldStart + 1 === nNew) {
35201
+ const cap = Math.min(nNew, MAX_HYDRATE_LINES_PER_GAP, remaining);
35202
+ const dl = await options.diskLines();
35203
+ if (dl && cap > 0) {
35204
+ return dl.slice(options.gapNewStart - 1, options.gapNewStart - 1 + cap);
35205
+ }
35206
+ }
35207
+ return null;
35208
+ }
35209
+ if (options.gapOldEnd >= options.gapOldStart && options.change === "removed") {
35210
+ const nOld = options.gapOldEnd - options.gapOldStart + 1;
35211
+ const cap = Math.min(nOld, MAX_HYDRATE_LINES_PER_GAP, remaining);
35212
+ const bl = await options.blobLines();
35213
+ if (bl && cap > 0) {
35214
+ return bl.slice(options.gapOldStart - 1, options.gapOldStart - 1 + cap);
35215
+ }
35216
+ }
35217
+ return null;
35218
+ }
35219
+
35220
+ // src/git/changes/patch/hydrate/consume-unified-hunk-body.ts
35221
+ async function consumeUnifiedHunkBody(options) {
35222
+ let i = options.startIndex;
35223
+ let oldConsumed = 0;
35224
+ let newConsumed = 0;
35225
+ while (i < options.all.length) {
35226
+ await options.maybeYield();
35227
+ const bl = options.all[i];
35228
+ if (UNIFIED_HUNK_HEADER_RE.test(bl)) break;
35229
+ options.out.push(bl);
35230
+ i++;
35231
+ if (bl.startsWith("\\")) continue;
35232
+ const ch = bl[0];
35233
+ if (ch === " ") {
35234
+ oldConsumed++;
35235
+ newConsumed++;
35236
+ } else if (ch === "-") {
35237
+ oldConsumed++;
35238
+ } else if (ch === "+") {
35239
+ newConsumed++;
35240
+ }
35241
+ }
35242
+ return { nextIndex: i, oldConsumed, newConsumed };
35243
+ }
35244
+
35245
+ // src/git/changes/patch/hydrate/read-git-blob-lines.ts
34334
35246
  async function readGitBlobLines(repoCwd, pathInRepo) {
34335
35247
  try {
34336
35248
  const rel = pathInRepo.replace(/\\/g, "/");
@@ -34340,35 +35252,66 @@ async function readGitBlobLines(repoCwd, pathInRepo) {
34340
35252
  return null;
34341
35253
  }
34342
35254
  }
35255
+
35256
+ // src/git/changes/patch/hydrate/read-worktree-file-lines.ts
35257
+ import * as fs25 from "node:fs";
34343
35258
  async function readWorktreeFileLines(filePath) {
34344
35259
  try {
34345
- const raw = await fs18.promises.readFile(filePath, "utf8");
35260
+ const raw = await fs25.promises.readFile(filePath, "utf8");
34346
35261
  return raw.split(/\r?\n/);
34347
35262
  } catch {
34348
35263
  return null;
34349
35264
  }
34350
35265
  }
34351
- async function hydrateUnifiedPatchWithFileContext(patch, filePath, repoGitCwd, pathInRepo, change) {
34352
- if (!patch.trim() || patch.includes("Binary files")) return patch;
34353
- const all = patch.split("\n");
34354
- const out = [];
34355
- let prevOldEnd = 0;
34356
- let prevNewEnd = 0;
34357
- let injectedTotal = 0;
34358
- let i = 0;
35266
+
35267
+ // src/git/changes/patch/hydrate/create-hydrate-source-lines.ts
35268
+ function createHydrateSourceLines(options) {
34359
35269
  let blobCache;
34360
35270
  let diskCache;
34361
35271
  const blobLines = async () => {
34362
35272
  if (blobCache !== void 0) return blobCache;
34363
- blobCache = await readGitBlobLines(repoGitCwd, pathInRepo);
35273
+ blobCache = await readGitBlobLines(options.repoGitCwd, options.pathInRepo);
34364
35274
  return blobCache;
34365
35275
  };
34366
35276
  const diskLines = async () => {
34367
35277
  if (diskCache !== void 0) return diskCache;
34368
- diskCache = await readWorktreeFileLines(filePath);
35278
+ diskCache = await readWorktreeFileLines(options.filePath);
34369
35279
  return diskCache;
34370
35280
  };
35281
+ return { blobLines, diskLines };
35282
+ }
35283
+
35284
+ // src/git/changes/patch/hydrate/create-hydrate-yield.ts
35285
+ function createHydrateYield() {
35286
+ let processedLines = 0;
35287
+ return async () => {
35288
+ processedLines++;
35289
+ if (processedLines % HYDRATE_YIELD_EVERY_LINES === 0) {
35290
+ await yieldToEventLoop();
35291
+ }
35292
+ };
35293
+ }
35294
+
35295
+ // src/git/changes/patch/hydrate/update-hunk-line-ends.ts
35296
+ function updateHunkLineEnds(options) {
35297
+ const prevOldEnd = options.oldStart > 0 ? options.oldStart + options.oldConsumed - 1 : 0;
35298
+ const prevNewEnd = options.newStart > 0 ? options.newStart + options.newConsumed - 1 : 0;
35299
+ return { prevOldEnd, prevNewEnd };
35300
+ }
35301
+
35302
+ // src/git/changes/patch/hydrate/hydrate-unified-patch-with-file-context.ts
35303
+ async function hydrateUnifiedPatchWithFileContext(patch, filePath, repoGitCwd, pathInRepo, change) {
35304
+ if (!patch.trim() || patch.includes("Binary files")) return patch;
35305
+ const all = patch.split("\n");
35306
+ const out = [];
35307
+ let prevOldEnd = 0;
35308
+ let prevNewEnd = 0;
35309
+ const injectedTotal = { value: 0 };
35310
+ let i = 0;
35311
+ const maybeYield = createHydrateYield();
35312
+ const { blobLines, diskLines } = createHydrateSourceLines({ repoGitCwd, pathInRepo, filePath });
34371
35313
  while (i < all.length) {
35314
+ await maybeYield();
34372
35315
  const line = all[i];
34373
35316
  const hm = line.match(UNIFIED_HUNK_HEADER_RE);
34374
35317
  if (!hm) {
@@ -34378,66 +35321,31 @@ async function hydrateUnifiedPatchWithFileContext(patch, filePath, repoGitCwd, p
34378
35321
  }
34379
35322
  const oldStart = parseInt(hm[1], 10) || 0;
34380
35323
  const newStart = parseInt(hm[3], 10) || 0;
34381
- const gapOldStart = prevOldEnd + 1;
34382
- const gapOldEnd = oldStart - 1;
34383
- const gapNewStart = prevNewEnd + 1;
34384
- const gapNewEnd = newStart - 1;
34385
- if (injectedTotal < MAX_HYDRATE_LINES_PER_FILE) {
34386
- let inject = null;
34387
- if (gapNewEnd >= gapNewStart && change !== "removed") {
34388
- const nNew = gapNewEnd - gapNewStart + 1;
34389
- if (gapOldEnd < gapOldStart || gapOldEnd - gapOldStart + 1 === nNew) {
34390
- const cap = Math.min(nNew, MAX_HYDRATE_LINES_PER_GAP, MAX_HYDRATE_LINES_PER_FILE - injectedTotal);
34391
- const dl = await diskLines();
34392
- if (dl && cap > 0) {
34393
- inject = dl.slice(gapNewStart - 1, gapNewStart - 1 + cap);
34394
- }
34395
- }
34396
- } else if (gapOldEnd >= gapOldStart && change === "removed") {
34397
- const nOld = gapOldEnd - gapOldStart + 1;
34398
- const cap = Math.min(nOld, MAX_HYDRATE_LINES_PER_GAP, MAX_HYDRATE_LINES_PER_FILE - injectedTotal);
34399
- const bl = await blobLines();
34400
- if (bl && cap > 0) {
34401
- inject = bl.slice(gapOldStart - 1, gapOldStart - 1 + cap);
34402
- }
34403
- }
34404
- if (inject && inject.length > 0) {
34405
- for (const t of inject) {
34406
- out.push(` ${t}`);
34407
- injectedTotal++;
34408
- }
34409
- }
35324
+ const inject = await collectGapInjectLines({
35325
+ change,
35326
+ gapOldStart: prevOldEnd + 1,
35327
+ gapOldEnd: oldStart - 1,
35328
+ gapNewStart: prevNewEnd + 1,
35329
+ gapNewEnd: newStart - 1,
35330
+ injectedTotal: injectedTotal.value,
35331
+ blobLines,
35332
+ diskLines
35333
+ });
35334
+ if (inject && inject.length > 0) {
35335
+ await appendInjectedContextLines(out, inject, maybeYield, injectedTotal);
34410
35336
  }
34411
35337
  out.push(line);
34412
35338
  i++;
34413
- let oldConsumed = 0;
34414
- let newConsumed = 0;
34415
- while (i < all.length) {
34416
- const bl = all[i];
34417
- if (UNIFIED_HUNK_HEADER_RE.test(bl)) break;
34418
- out.push(bl);
34419
- i++;
34420
- if (bl.startsWith("\\")) continue;
34421
- const ch = bl[0];
34422
- if (ch === " ") {
34423
- oldConsumed++;
34424
- newConsumed++;
34425
- } else if (ch === "-") {
34426
- oldConsumed++;
34427
- } else if (ch === "+") {
34428
- newConsumed++;
34429
- }
34430
- }
34431
- if (oldStart > 0) {
34432
- prevOldEnd = oldStart + oldConsumed - 1;
34433
- } else {
34434
- prevOldEnd = 0;
34435
- }
34436
- if (newStart > 0) {
34437
- prevNewEnd = newStart + newConsumed - 1;
34438
- } else {
34439
- prevNewEnd = 0;
34440
- }
35339
+ const body = await consumeUnifiedHunkBody({ all, startIndex: i, out, maybeYield });
35340
+ i = body.nextIndex;
35341
+ const ends = updateHunkLineEnds({
35342
+ oldStart,
35343
+ newStart,
35344
+ oldConsumed: body.oldConsumed,
35345
+ newConsumed: body.newConsumed
35346
+ });
35347
+ prevOldEnd = ends.prevOldEnd;
35348
+ prevNewEnd = ends.prevNewEnd;
34441
35349
  }
34442
35350
  return truncatePatch(out.join("\n"));
34443
35351
  }
@@ -34446,7 +35354,7 @@ async function hydrateUnifiedPatchWithFileContext(patch, filePath, repoGitCwd, p
34446
35354
  async function enrichWorkingTreeFileRows(options) {
34447
35355
  await forEachWithGitYield(options.rows, async (row) => {
34448
35356
  const pathInRepo = pathInRepoFromLauncher(row.pathRelLauncher, options.repoRelPath);
34449
- const filePath = path21.join(options.repoGitCwd, pathInRepo);
35357
+ const filePath = path31.join(options.repoGitCwd, pathInRepo);
34450
35358
  const hydrateKind = row.change === "moved" ? "modified" : row.change;
34451
35359
  let patch = await unifiedDiffForFile(options.repoGitCwd, pathInRepo, row.change, row.movedFromPathInRepo);
34452
35360
  if (patch) {
@@ -34508,14 +35416,76 @@ async function listChangedFilesForRepo(repoGitCwd, repoRelPath) {
34508
35416
  return dedupeChangedFileRows(rows);
34509
35417
  }
34510
35418
 
34511
- // src/git/changes/get-working-tree-change-repo-details.ts
35419
+ // src/git/changes/repo/norm-repo-rel-path.ts
34512
35420
  function normRepoRel(p) {
34513
35421
  const x = p.replace(/\\/g, "/").trim();
34514
35422
  return x === "" ? "." : x;
34515
35423
  }
35424
+
35425
+ // src/git/changes/repo/build-working-tree-change-repo-detail.ts
35426
+ async function buildWorkingTreeChangeRepoDetail(options) {
35427
+ const t = path32.resolve(options.targetPath);
35428
+ if (!await isGitRepoDirectory(t)) return null;
35429
+ const g = cliSimpleGit(t);
35430
+ let branch = "HEAD";
35431
+ try {
35432
+ await yieldToEventLoop2();
35433
+ branch = (await g.raw(["rev-parse", "--abbrev-ref", "HEAD"])).trim() || "HEAD";
35434
+ } catch {
35435
+ branch = "HEAD";
35436
+ }
35437
+ await yieldToEventLoop2();
35438
+ const remoteUrl = await getRemoteOriginUrl(t);
35439
+ const remoteDisplay = formatRemoteDisplayLabel(remoteUrl);
35440
+ const sessionWtRoot = options.sessionWorktreeRootPath ? path32.resolve(options.sessionWorktreeRootPath) : null;
35441
+ const legacyNested = options.legacyRepoNestedSessionLayout;
35442
+ let repoRelPath;
35443
+ if (sessionWtRoot) {
35444
+ const anchor = legacyNested ? path32.dirname(t) : t;
35445
+ const relNorm = path32.relative(sessionWtRoot, anchor);
35446
+ repoRelPath = relNorm === "" ? "." : relNorm.replace(/\\/g, "/");
35447
+ } else {
35448
+ let top = t;
35449
+ try {
35450
+ await yieldToEventLoop2();
35451
+ top = (await g.raw(["rev-parse", "--show-toplevel"])).trim();
35452
+ } catch {
35453
+ top = t;
35454
+ }
35455
+ const rel = path32.relative(options.bridgeRoot, path32.resolve(top)).replace(/\\/g, "/") || ".";
35456
+ repoRelPath = rel.startsWith("..") ? path32.basename(path32.resolve(top)) : rel;
35457
+ }
35458
+ const norm = normRepoRel(repoRelPath === "" ? "." : repoRelPath);
35459
+ if (options.repoFilterRelPath && norm !== options.repoFilterRelPath) return null;
35460
+ const repoDisplayName = formatRepoShortTitle(remoteUrl, norm === "." ? "." : norm);
35461
+ const relForList = norm === "." ? "." : norm;
35462
+ const files = options.basis.kind === "commit" ? await listChangedFilesForCommit(t, relForList, options.basis.sha.trim()) : await listChangedFilesForRepo(t, relForList);
35463
+ const st = await g.status();
35464
+ const hasUncommittedChanges = (st.files?.length ?? 0) > 0;
35465
+ const [unpushedCommits, recentCommitList] = await Promise.all([
35466
+ listUnpushedCommits(t),
35467
+ listRecentCommits(t, options.recentCommitsLimit)
35468
+ ]);
35469
+ return {
35470
+ repoRelPath: norm,
35471
+ repoDisplayName,
35472
+ branch,
35473
+ remoteUrl,
35474
+ remoteDisplay,
35475
+ files,
35476
+ hasUncommittedChanges,
35477
+ unpushedCommits,
35478
+ recentCommits: recentCommitList.commits,
35479
+ recentCommitsHasMore: recentCommitList.hasMore,
35480
+ changesView: options.basis.kind === "commit" ? "commit" : "working",
35481
+ changesCommitSha: options.basis.kind === "commit" ? options.basis.sha.trim() : null
35482
+ };
35483
+ }
35484
+
35485
+ // src/git/changes/repo/get-working-tree-change-repo-details.ts
34516
35486
  async function getWorkingTreeChangeRepoDetails(options) {
34517
- const bridgeRoot = path22.resolve(getBridgeRoot());
34518
- const sessionWtRoot = options.sessionWorktreeRootPath ? path22.resolve(options.sessionWorktreeRootPath) : null;
35487
+ const bridgeRoot = path33.resolve(getBridgeRoot());
35488
+ const sessionWtRoot = options.sessionWorktreeRootPath ? path33.resolve(options.sessionWorktreeRootPath) : null;
34519
35489
  const legacyNested = options.legacyRepoNestedSessionLayout === true;
34520
35490
  const out = [];
34521
35491
  const filter = options.repoFilterRelPath != null ? normRepoRel(options.repoFilterRelPath) : null;
@@ -34528,59 +35498,18 @@ async function getWorkingTreeChangeRepoDetails(options) {
34528
35498
  }
34529
35499
  const basis = filter == null && basisInput.kind === "commit" ? { kind: "working" } : basisInput;
34530
35500
  for (let i = 0; i < options.commitTargetPaths.length; i++) {
34531
- if (i > 0) await yieldToEventLoop2();
34532
- const target = options.commitTargetPaths[i];
34533
- const t = path22.resolve(target);
34534
- if (!await isGitRepoDirectory(t)) continue;
34535
- const g = cliSimpleGit(t);
34536
- let branch = "HEAD";
34537
- try {
34538
- branch = (await g.raw(["rev-parse", "--abbrev-ref", "HEAD"])).trim() || "HEAD";
34539
- } catch {
34540
- branch = "HEAD";
34541
- }
34542
- const remoteUrl = await getRemoteOriginUrl(t);
34543
- const remoteDisplay = formatRemoteDisplayLabel(remoteUrl);
34544
- let repoRelPath;
34545
- if (sessionWtRoot) {
34546
- const anchor = legacyNested ? path22.dirname(t) : t;
34547
- const relNorm = path22.relative(sessionWtRoot, anchor);
34548
- repoRelPath = relNorm === "" ? "." : relNorm.replace(/\\/g, "/");
34549
- } else {
34550
- let top = t;
34551
- try {
34552
- top = (await g.raw(["rev-parse", "--show-toplevel"])).trim();
34553
- } catch {
34554
- top = t;
34555
- }
34556
- const rel = path22.relative(bridgeRoot, path22.resolve(top)).replace(/\\/g, "/") || ".";
34557
- repoRelPath = rel.startsWith("..") ? path22.basename(path22.resolve(top)) : rel;
34558
- }
34559
- const norm = normRepoRel(repoRelPath === "" ? "." : repoRelPath);
34560
- if (filter && norm !== filter) continue;
34561
- const repoDisplayName = formatRepoShortTitle(remoteUrl, norm === "." ? "." : norm);
34562
- const relForList = norm === "." ? "." : norm;
34563
- const files = basis.kind === "commit" ? await listChangedFilesForCommit(t, relForList, basis.sha.trim()) : await listChangedFilesForRepo(t, relForList);
34564
- const st = await g.status();
34565
- const hasUncommittedChanges = (st.files?.length ?? 0) > 0;
34566
- const [unpushedCommits, recentCommitList] = await Promise.all([
34567
- listUnpushedCommits(t),
34568
- listRecentCommits(t, options.recentCommitsLimit)
34569
- ]);
34570
- out.push({
34571
- repoRelPath: norm,
34572
- repoDisplayName,
34573
- branch,
34574
- remoteUrl,
34575
- remoteDisplay,
34576
- files,
34577
- hasUncommittedChanges,
34578
- unpushedCommits,
34579
- recentCommits: recentCommitList.commits,
34580
- recentCommitsHasMore: recentCommitList.hasMore,
34581
- changesView: basis.kind === "commit" ? "commit" : "working",
34582
- changesCommitSha: basis.kind === "commit" ? basis.sha.trim() : null
34583
- });
35501
+ await yieldToEventLoop2();
35502
+ const detail = await buildWorkingTreeChangeRepoDetail({
35503
+ targetPath: options.commitTargetPaths[i],
35504
+ bridgeRoot,
35505
+ sessionWorktreeRootPath: sessionWtRoot,
35506
+ legacyRepoNestedSessionLayout: legacyNested,
35507
+ repoFilterRelPath: filter,
35508
+ basis,
35509
+ recentCommitsLimit: options.recentCommitsLimit
35510
+ });
35511
+ if (!detail) continue;
35512
+ out.push(detail);
34584
35513
  if (filter) return out;
34585
35514
  }
34586
35515
  return out;
@@ -34682,23 +35611,23 @@ async function commitSessionWorktrees(options) {
34682
35611
  }
34683
35612
 
34684
35613
  // src/worktrees/remove-session-worktrees.ts
34685
- import * as fs21 from "node:fs";
35614
+ import * as fs28 from "node:fs";
34686
35615
 
34687
35616
  // src/git/worktrees/worktree-remove.ts
34688
- import * as fs20 from "node:fs";
35617
+ import * as fs27 from "node:fs";
34689
35618
 
34690
35619
  // src/git/worktrees/resolve-main-repo-from-git-file.ts
34691
- import * as fs19 from "node:fs";
34692
- import * as path23 from "node:path";
35620
+ import * as fs26 from "node:fs";
35621
+ import * as path34 from "node:path";
34693
35622
  function resolveMainRepoFromWorktreeGitFile(wt) {
34694
- const gitDirFile = path23.join(wt, ".git");
34695
- if (!fs19.existsSync(gitDirFile) || !fs19.statSync(gitDirFile).isFile()) return "";
34696
- const first2 = fs19.readFileSync(gitDirFile, "utf8").trim();
35623
+ const gitDirFile = path34.join(wt, ".git");
35624
+ if (!fs26.existsSync(gitDirFile) || !fs26.statSync(gitDirFile).isFile()) return "";
35625
+ const first2 = fs26.readFileSync(gitDirFile, "utf8").trim();
34697
35626
  const m = first2.match(/^gitdir:\s*(.+)$/im);
34698
35627
  if (!m) return "";
34699
- const gitWorktreePath = path23.resolve(wt, m[1].trim());
34700
- const gitDir = path23.dirname(path23.dirname(gitWorktreePath));
34701
- return path23.dirname(gitDir);
35628
+ const gitWorktreePath = path34.resolve(wt, m[1].trim());
35629
+ const gitDir = path34.dirname(path34.dirname(gitWorktreePath));
35630
+ return path34.dirname(gitDir);
34702
35631
  }
34703
35632
 
34704
35633
  // src/git/worktrees/worktree-remove.ts
@@ -34707,7 +35636,7 @@ async function gitWorktreeRemoveForce(worktreePath) {
34707
35636
  if (mainRepo) {
34708
35637
  await cliSimpleGit(mainRepo).raw(["worktree", "remove", "--force", worktreePath]);
34709
35638
  } else {
34710
- fs20.rmSync(worktreePath, { recursive: true, force: true });
35639
+ fs27.rmSync(worktreePath, { recursive: true, force: true });
34711
35640
  }
34712
35641
  }
34713
35642
 
@@ -34720,7 +35649,7 @@ async function removeSessionWorktrees(paths, log2) {
34720
35649
  } catch (e) {
34721
35650
  log2(`[worktrees] Remove failed for ${wt}: ${e instanceof Error ? e.message : String(e)}`);
34722
35651
  try {
34723
- fs21.rmSync(wt, { recursive: true, force: true });
35652
+ fs28.rmSync(wt, { recursive: true, force: true });
34724
35653
  } catch {
34725
35654
  }
34726
35655
  }
@@ -34748,458 +35677,86 @@ async function renameSessionWorktreeBranches(paths, newBranch, log2) {
34748
35677
  }
34749
35678
  }
34750
35679
 
34751
- // src/worktrees/worktree-layout-file.ts
34752
- import * as fs22 from "node:fs";
34753
- import * as path24 from "node:path";
34754
- import os7 from "node:os";
34755
- var LAYOUT_FILENAME = "worktree-launcher-layout.json";
34756
- function defaultWorktreeLayoutPath() {
34757
- return path24.join(os7.homedir(), ".buildautomaton", LAYOUT_FILENAME);
34758
- }
34759
- function normalizeLoadedLayout(raw) {
34760
- if (raw && typeof raw === "object" && "launcherCwds" in raw) {
34761
- const j = raw;
34762
- if (Array.isArray(j.launcherCwds)) return { launcherCwds: j.launcherCwds };
34763
- }
34764
- return { launcherCwds: [] };
34765
- }
34766
- function loadWorktreeLayout() {
34767
- try {
34768
- const p = defaultWorktreeLayoutPath();
34769
- if (!fs22.existsSync(p)) return { launcherCwds: [] };
34770
- const raw = JSON.parse(fs22.readFileSync(p, "utf8"));
34771
- return normalizeLoadedLayout(raw);
34772
- } catch {
34773
- return { launcherCwds: [] };
34774
- }
34775
- }
34776
- function saveWorktreeLayout(layout) {
34777
- try {
34778
- const dir = path24.dirname(defaultWorktreeLayoutPath());
34779
- fs22.mkdirSync(dir, { recursive: true });
34780
- fs22.writeFileSync(defaultWorktreeLayoutPath(), JSON.stringify(layout, null, 2), "utf8");
34781
- } catch {
34782
- }
34783
- }
34784
- function baseNameSafe(pathString) {
34785
- return path24.basename(pathString).replace(/[^a-zA-Z0-9._-]+/g, "-") || "cwd";
34786
- }
34787
- function getLauncherDirNameIfPresent(layout, bridgeRootPath2) {
34788
- const norm = path24.resolve(bridgeRootPath2);
34789
- const existing = layout.launcherCwds.find((e) => path24.resolve(e.absolutePath) === norm);
34790
- return existing?.dirName;
34791
- }
34792
- function allocateDirNameForLauncherCwd(layout, bridgeRootPath2) {
34793
- const existing = getLauncherDirNameIfPresent(layout, bridgeRootPath2);
34794
- if (existing) return existing;
34795
- const norm = path24.resolve(bridgeRootPath2);
34796
- const base = baseNameSafe(norm);
34797
- const used = new Set(layout.launcherCwds.map((e) => e.dirName));
34798
- let name = base;
34799
- let n = 2;
34800
- while (used.has(name)) {
34801
- name = `${base}-${n}`;
34802
- n += 1;
34803
- }
34804
- layout.launcherCwds.push({ absolutePath: norm, dirName: name });
34805
- saveWorktreeLayout(layout);
34806
- return name;
34807
- }
34808
-
34809
- // src/worktrees/discover-session-worktree-on-disk.ts
34810
- import * as fs23 from "node:fs";
34811
- import * as path25 from "node:path";
34812
- function isGitDir(dirPath) {
34813
- try {
34814
- return fs23.existsSync(path25.join(dirPath, ".git"));
34815
- } catch {
34816
- return false;
34817
- }
34818
- }
34819
- function collectGitRepoRootsUnderDirectory(rootPath) {
34820
- const out = [];
34821
- const walk = (dir) => {
34822
- if (isGitDir(dir)) {
34823
- out.push(path25.resolve(dir));
34824
- return;
34825
- }
34826
- let entries;
34827
- try {
34828
- entries = fs23.readdirSync(dir, { withFileTypes: true });
34829
- } catch {
34830
- return;
34831
- }
34832
- for (const e of entries) {
34833
- if (e.name.startsWith(".")) continue;
34834
- const full = path25.join(dir, e.name);
34835
- if (!e.isDirectory()) continue;
34836
- walk(full);
34837
- }
34838
- };
34839
- walk(path25.resolve(rootPath));
34840
- return [...new Set(out)];
34841
- }
34842
- function collectWorktreeRootsNamed(root, sessionId, maxDepth) {
34843
- const out = [];
34844
- const walk = (dir, depth) => {
34845
- if (depth > maxDepth) return;
34846
- let entries;
34847
- try {
34848
- entries = fs23.readdirSync(dir, { withFileTypes: true });
34849
- } catch {
34850
- return;
34851
- }
34852
- for (const e of entries) {
34853
- if (e.name.startsWith(".")) continue;
34854
- const full = path25.join(dir, e.name);
34855
- if (!e.isDirectory()) continue;
34856
- if (e.name === sessionId) {
34857
- if (isGitDir(full)) out.push(path25.resolve(full));
34858
- } else {
34859
- walk(full, depth + 1);
34860
- }
34861
- }
34862
- };
34863
- walk(root, 0);
34864
- return out;
34865
- }
34866
- function tryBindingFromSessionDirectory(sessionDir) {
34867
- let st;
34868
- try {
34869
- st = fs23.statSync(sessionDir);
34870
- } catch {
34871
- return null;
34872
- }
34873
- if (!st.isDirectory()) return null;
34874
- const worktreePaths = collectGitRepoRootsUnderDirectory(sessionDir);
34875
- if (worktreePaths.length === 0) return null;
34876
- const abs = path25.resolve(sessionDir);
34877
- return {
34878
- sessionParentPath: abs,
34879
- workingTreeRelRoot: abs,
34880
- repoCheckoutPaths: worktreePaths
34881
- };
34882
- }
34883
- function discoverLegacyBindingAscendingFromCheckout(sessionId, checkoutPath) {
34884
- const sid = sessionId.trim();
34885
- if (!sid) return null;
34886
- const hintR = path25.resolve(checkoutPath);
34887
- let best = null;
34888
- let cur = path25.dirname(hintR);
34889
- for (let i = 0; i < 40; i++) {
34890
- const paths = collectWorktreeRootsNamed(cur, sid, 24);
34891
- if (paths.some((p) => path25.resolve(p) === hintR)) {
34892
- const isolated = resolveIsolatedSessionParentPathFromCheckouts(paths) ?? path25.resolve(paths[0]);
34893
- best = {
34894
- sessionParentPath: path25.resolve(isolated),
34895
- workingTreeRelRoot: path25.resolve(cur),
34896
- repoCheckoutPaths: paths.map((p) => path25.resolve(p))
34897
- };
34898
- }
34899
- const next = path25.dirname(cur);
34900
- if (next === cur) break;
34901
- cur = next;
34902
- }
34903
- return best;
34904
- }
34905
- function discoverSessionWorktreeOnDisk(options) {
34906
- const { sessionId, worktreesRootPath, layout, bridgeRoot } = options;
34907
- if (!sessionId.trim() || !fs23.existsSync(worktreesRootPath)) return null;
34908
- const preferredKey = getLauncherDirNameIfPresent(layout, bridgeRoot);
34909
- const keys = [];
34910
- if (preferredKey) keys.push(preferredKey);
34911
- try {
34912
- for (const name of fs23.readdirSync(worktreesRootPath)) {
34913
- if (name.startsWith(".")) continue;
34914
- const p = path25.join(worktreesRootPath, name);
34915
- if (!fs23.statSync(p).isDirectory()) continue;
34916
- if (name !== preferredKey) keys.push(name);
34917
- }
34918
- } catch {
34919
- return null;
34920
- }
34921
- for (const key of keys) {
34922
- const layoutRoot = path25.join(worktreesRootPath, key);
34923
- if (!fs23.existsSync(layoutRoot) || !fs23.statSync(layoutRoot).isDirectory()) continue;
34924
- const sessionDir = path25.join(layoutRoot, sessionId);
34925
- const nested = tryBindingFromSessionDirectory(sessionDir);
34926
- if (nested) return nested;
34927
- const legacyPaths = collectWorktreeRootsNamed(layoutRoot, sessionId, 24);
34928
- if (legacyPaths.length > 0) {
34929
- const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths) ?? path25.resolve(legacyPaths[0]);
34930
- return {
34931
- sessionParentPath: path25.resolve(isolated),
34932
- workingTreeRelRoot: path25.resolve(layoutRoot),
34933
- repoCheckoutPaths: legacyPaths.map((p) => path25.resolve(p))
34934
- };
34935
- }
34936
- }
34937
- return null;
34938
- }
34939
- function discoverSessionWorktreesUnderSessionWorktreeRoot(sessionWorktreeRootPathOrHint, sessionId) {
34940
- const sid = sessionId.trim();
34941
- if (!sid) return null;
34942
- const hint = path25.resolve(sessionWorktreeRootPathOrHint);
34943
- const underHint = tryBindingFromSessionDirectory(path25.join(hint, sid));
34944
- if (underHint) return underHint;
34945
- const direct = tryBindingFromSessionDirectory(hint);
34946
- if (direct) {
34947
- if (path25.basename(hint) === sid && isGitDir(hint)) {
34948
- const legacyFromCheckout = discoverLegacyBindingAscendingFromCheckout(sid, hint);
34949
- if (legacyFromCheckout && legacyFromCheckout.repoCheckoutPaths.length > direct.repoCheckoutPaths.length) {
34950
- return legacyFromCheckout;
34951
- }
34952
- }
34953
- return direct;
34954
- }
34955
- if (path25.basename(hint) === sid && isGitDir(hint)) {
34956
- const legacyFromCheckout = discoverLegacyBindingAscendingFromCheckout(sid, hint);
34957
- if (legacyFromCheckout) return legacyFromCheckout;
34958
- }
34959
- let st;
34960
- try {
34961
- st = fs23.statSync(hint);
34962
- } catch {
34963
- return null;
34964
- }
34965
- if (!st.isDirectory()) return null;
34966
- const legacyPaths = collectWorktreeRootsNamed(hint, sid, 24);
34967
- if (legacyPaths.length === 0) return null;
34968
- const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths) ?? path25.resolve(legacyPaths[0]);
35680
+ // src/worktrees/manager/resolve-commit-targets.ts
35681
+ function bridgeRootBinding(bridgeRoot) {
34969
35682
  return {
34970
- sessionParentPath: path25.resolve(isolated),
34971
- workingTreeRelRoot: hint,
34972
- repoCheckoutPaths: legacyPaths.map((p) => path25.resolve(p))
35683
+ sessionParentPath: bridgeRoot,
35684
+ workingTreeRelRoot: bridgeRoot,
35685
+ repoCheckoutPaths: [bridgeRoot]
34973
35686
  };
34974
35687
  }
34975
-
34976
- // src/worktrees/manager/discover-session-binding.ts
34977
- function discoverSessionBinding(params) {
34978
- return discoverSessionWorktreeOnDisk({
34979
- sessionId: params.sessionId,
34980
- worktreesRootPath: params.worktreesRootPath,
34981
- layout: params.layout,
34982
- bridgeRoot: getBridgeRoot()
34983
- });
34984
- }
34985
-
34986
- // src/worktrees/manager/resolve-isolated-session-parent-path.ts
34987
- function resolveIsolatedSessionParentPath(sessionId, cache2, ensureRepoCheckoutPaths) {
34988
- if (!sessionId) return null;
34989
- const sid = sessionId.trim();
34990
- const cached2 = cache2.getSessionParentPath(sid);
34991
- if (cached2) return cached2;
34992
- const paths = ensureRepoCheckoutPaths(sid);
34993
- if (!paths?.length) return null;
34994
- return resolveIsolatedSessionParentPathFromCheckouts(paths);
34995
- }
34996
- function ensureRepoCheckoutPathsForSession(sessionId, cache2, discover) {
34997
- if (!sessionId?.trim()) return void 0;
35688
+ async function resolveCommitTargetsAsync(sessionId, cache2, discover) {
34998
35689
  const sid = sessionId.trim();
34999
- const cached2 = cache2.getRepoCheckoutPaths(sid);
35000
- if (cached2?.length) return cached2;
35001
- const disc = discover(sid);
35002
- if (disc?.repoCheckoutPaths.length) {
35003
- cache2.remember(sid, disc);
35004
- return [...disc.repoCheckoutPaths];
35005
- }
35006
- return void 0;
35007
- }
35008
-
35009
- // src/worktrees/manager/resolve-commit-targets.ts
35010
- function resolveCommitTargets(sessionId, cache2, discover) {
35011
- const paths = cache2.getRepoCheckoutPathsRef(sessionId);
35690
+ const paths = cache2.getRepoCheckoutPathsRef(sid);
35012
35691
  if (paths?.length) return paths;
35013
- const disc = discover(sessionId);
35692
+ const disc = await discover(sid);
35014
35693
  if (disc?.repoCheckoutPaths.length) {
35015
- cache2.remember(sessionId, disc);
35694
+ cache2.remember(sid, disc);
35016
35695
  return disc.repoCheckoutPaths;
35017
35696
  }
35018
- return [getBridgeRoot()];
35697
+ const bridgeRoot = getBridgeRoot();
35698
+ cache2.remember(sid, bridgeRootBinding(bridgeRoot));
35699
+ return [bridgeRoot];
35019
35700
  }
35020
35701
 
35021
- // src/worktrees/manager/parse-session-parent.ts
35022
- function parseSessionParent(v) {
35023
- if (v === "bridge_root" || v === "worktrees_root") return v;
35024
- if (v === "session_worktrees_root") return "worktrees_root";
35025
- return null;
35026
- }
35027
-
35028
- // src/worktrees/prepare-new-session-worktrees.ts
35029
- import * as fs24 from "node:fs";
35030
- import * as path26 from "node:path";
35031
-
35032
- // src/git/worktrees/worktree-add.ts
35033
- async function gitWorktreeAddBranch(mainRepoPath, worktreePath, branch, baseRef = "HEAD") {
35034
- const mainGit = cliSimpleGit(mainRepoPath);
35035
- const base = baseRef.trim() || "HEAD";
35036
- await mainGit.raw(["worktree", "add", "-b", branch, worktreePath, base]);
35037
- }
35038
-
35039
- // src/worktrees/prepare-new-session-worktrees.ts
35040
- function normalizeRepoRelPath2(rel) {
35041
- return rel === "" ? "." : rel.replace(/\\/g, "/");
35042
- }
35043
- function resolveBaseRefForRepo(relNorm, baseBranches) {
35044
- if (!baseBranches) return "HEAD";
35045
- const direct = baseBranches[relNorm]?.trim();
35046
- if (direct) return direct;
35047
- if (relNorm !== "." && baseBranches["."]?.trim()) return baseBranches["."].trim();
35048
- return "HEAD";
35702
+ // src/worktrees/manager/session-git-ops.ts
35703
+ async function commitSessionWorktree(cache2, params) {
35704
+ const paths = cache2.getRepoCheckoutPathsRef(params.sessionId);
35705
+ const targets = paths?.length ? paths : [getBridgeRoot()];
35706
+ return commitSessionWorktrees({
35707
+ paths: targets,
35708
+ branch: params.branch,
35709
+ message: params.message,
35710
+ push: params.push
35711
+ });
35049
35712
  }
35050
- async function prepareNewSessionWorktrees(options) {
35051
- const { worktreesRootPath, bridgeRoot, sessionId, layout, log: log2, worktreeBaseBranches } = options;
35052
- const bridgeResolved = path26.resolve(bridgeRoot);
35053
- const cwdKey = allocateDirNameForLauncherCwd(layout, bridgeResolved);
35054
- const bridgeKeyDir = path26.join(worktreesRootPath, cwdKey);
35055
- const sessionDir = path26.join(bridgeKeyDir, sessionId);
35056
- const repos = await discoverGitReposUnderRoot(bridgeResolved);
35057
- if (repos.length === 0) {
35058
- log2("[worktrees] No Git repositories under bridge root; skipping worktree creation.");
35059
- return null;
35060
- }
35061
- const branch = `session-${sessionId}`;
35062
- const worktreePaths = [];
35063
- fs24.mkdirSync(sessionDir, { recursive: true });
35064
- for (const repo of repos) {
35065
- let rel = path26.relative(bridgeResolved, repo.absolutePath);
35066
- if (rel.startsWith("..") || path26.isAbsolute(rel)) continue;
35067
- const relNorm = normalizeRepoRelPath2(rel === "" ? "." : rel);
35068
- const wtPath = relNorm === "." ? sessionDir : path26.join(sessionDir, relNorm);
35069
- if (relNorm !== ".") {
35070
- fs24.mkdirSync(path26.dirname(wtPath), { recursive: true });
35071
- }
35072
- const baseRef = resolveBaseRefForRepo(relNorm, worktreeBaseBranches);
35073
- try {
35074
- await gitWorktreeAddBranch(repo.absolutePath, wtPath, branch, baseRef);
35075
- log2(`[worktrees] Added worktree ${wtPath} (branch ${branch}, base ${baseRef}).`);
35076
- worktreePaths.push(wtPath);
35077
- } catch (e) {
35078
- log2(
35079
- `[worktrees] Worktree add failed for ${repo.absolutePath}: ${e instanceof Error ? e.message : String(e)}`
35080
- );
35081
- }
35082
- }
35083
- if (worktreePaths.length === 0) return null;
35084
- return {
35085
- worktreePaths,
35086
- sessionParentPath: sessionDir,
35087
- workingTreeRelRoot: sessionDir
35088
- };
35713
+ async function getSessionWorkingTreeStatus(cache2, sessionId, discover) {
35714
+ return aggregateSessionPathsWorkingTreeStatus(
35715
+ await resolveCommitTargetsAsync(sessionId, cache2, discover)
35716
+ );
35089
35717
  }
35090
-
35091
- // src/worktrees/manager/prepare-and-remember-session-worktrees.ts
35092
- async function prepareAndRememberSessionWorktrees(params) {
35093
- const prep = await prepareNewSessionWorktrees({
35094
- worktreesRootPath: params.worktreesRootPath,
35095
- bridgeRoot: getBridgeRoot(),
35096
- sessionId: params.sessionId,
35097
- layout: params.layout,
35098
- log: params.log,
35099
- ...params.worktreeBaseBranches && Object.keys(params.worktreeBaseBranches).length > 0 ? { worktreeBaseBranches: params.worktreeBaseBranches } : {}
35718
+ async function getSessionWorkingTreeChangeDetails(cache2, sessionId, discover, opts) {
35719
+ const targets = await resolveCommitTargetsAsync(sessionId, cache2, discover);
35720
+ return getWorkingTreeChangeRepoDetails({
35721
+ commitTargetPaths: targets,
35722
+ sessionWorktreeRootPath: cache2.getWorkingTreeRelRoot(sessionId),
35723
+ legacyRepoNestedSessionLayout: cache2.isLegacyNestedLayout(sessionId),
35724
+ repoFilterRelPath: opts?.repoRelPath?.trim() ? opts.repoRelPath.trim() : null,
35725
+ basis: opts?.basis,
35726
+ recentCommitsLimit: opts?.recentCommitsLimit
35100
35727
  });
35101
- if (!prep) return void 0;
35102
- params.cache.remember(params.sessionId, {
35103
- sessionParentPath: prep.sessionParentPath,
35104
- workingTreeRelRoot: prep.workingTreeRelRoot,
35105
- repoCheckoutPaths: prep.worktreePaths
35106
- });
35107
- return params.cache.getSessionParentPath(params.sessionId);
35108
35728
  }
35109
-
35110
- // src/worktrees/manager/resolve-existing-session-parent-path.ts
35111
- function resolveExistingSessionParentPath(sessionId, cache2, discover) {
35112
- const cached2 = cache2.getSessionParentPath(sessionId);
35113
- if (cached2) return cached2;
35114
- const disc = discover();
35115
- if (disc) {
35116
- cache2.remember(sessionId, disc);
35117
- return cache2.getSessionParentPath(sessionId);
35729
+ async function pushSessionUpstream(cache2, sessionId, discover) {
35730
+ try {
35731
+ await pushAheadOfUpstreamForPaths(await resolveCommitTargetsAsync(sessionId, cache2, discover));
35732
+ return { ok: true };
35733
+ } catch (e) {
35734
+ const err = e instanceof Error ? e.message : String(e);
35735
+ return { ok: false, error: err };
35118
35736
  }
35119
- return void 0;
35120
35737
  }
35121
-
35122
- // src/worktrees/manager/resolve-explicit-session-parent-path.ts
35123
- import * as path27 from "node:path";
35124
- function resolveExplicitSessionParentPath(params) {
35125
- const resolved = path27.resolve(params.parentPathRaw);
35126
- if (parseSessionParent(params.sessionParent) !== "worktrees_root") {
35127
- return resolved;
35128
- }
35129
- const rememberAndReturn = (binding) => {
35130
- params.cache.remember(params.sessionId, binding);
35131
- return params.cache.getSessionParentPath(params.sessionId) ?? resolved;
35132
- };
35133
- const diskFirst = params.discover();
35134
- if (diskFirst) return rememberAndReturn(diskFirst);
35135
- const fromRoot = discoverSessionWorktreesUnderSessionWorktreeRoot(resolved, params.sessionId);
35136
- if (fromRoot) return rememberAndReturn(fromRoot);
35137
- let cur = resolved;
35138
- for (let i = 0; i < 16; i++) {
35139
- const tryRoot = discoverSessionWorktreesUnderSessionWorktreeRoot(cur, params.sessionId);
35140
- if (tryRoot) return rememberAndReturn(tryRoot);
35141
- const next = path27.dirname(cur);
35142
- if (next === cur) break;
35143
- cur = next;
35144
- }
35145
- return resolved;
35738
+ async function removeSessionWorktreeCheckouts(cache2, sessionId, log2) {
35739
+ const paths = cache2.clearSession(sessionId);
35740
+ if (!paths?.length) return;
35741
+ await removeSessionWorktrees(paths, log2);
35146
35742
  }
35147
-
35148
- // src/worktrees/manager/resolve-session-parent-path-for-prompt.ts
35149
- async function resolveSessionParentPathForPrompt(params) {
35150
- const { sessionId, cache: cache2, worktreesRootPath, layout, log: log2, discover, opts } = params;
35151
- if (!sessionId) return void 0;
35152
- const sid = sessionId.trim();
35153
- const parentPathRaw = opts.sessionParentPath?.trim();
35154
- if (parentPathRaw) {
35155
- return resolveExplicitSessionParentPath({
35156
- sessionId: sid,
35157
- sessionParent: opts.sessionParent,
35158
- parentPathRaw,
35159
- cache: cache2,
35160
- discover: () => discover(sid)
35161
- });
35162
- }
35163
- const parentKind = parseSessionParent(opts.sessionParent);
35164
- if (parentKind === "bridge_root") {
35165
- return void 0;
35166
- }
35167
- if (parentKind === "worktrees_root") {
35168
- if (!opts.isNewSession) {
35169
- return resolveExistingSessionParentPath(sid, cache2, () => discover(sid));
35170
- }
35171
- return prepareAndRememberSessionWorktrees({
35172
- cache: cache2,
35173
- sessionId: sid,
35174
- worktreesRootPath,
35175
- layout,
35176
- log: log2,
35177
- ...opts.worktreeBaseBranches ? { worktreeBaseBranches: opts.worktreeBaseBranches } : {}
35178
- });
35179
- }
35180
- if (!opts.isNewSession) {
35181
- return resolveExistingSessionParentPath(sid, cache2, () => discover(sid));
35182
- }
35183
- return prepareAndRememberSessionWorktrees({
35184
- cache: cache2,
35185
- sessionId: sid,
35186
- worktreesRootPath,
35187
- layout,
35188
- log: log2
35189
- });
35743
+ async function renameSessionWorktreeBranch(cache2, sessionId, newBranch, log2) {
35744
+ const paths = cache2.getRepoCheckoutPathsRef(sessionId);
35745
+ if (!paths?.length) return;
35746
+ await renameSessionWorktreeBranches(paths, newBranch, log2);
35190
35747
  }
35191
35748
 
35192
35749
  // src/worktrees/manager/session-worktree-cache.ts
35193
- import * as path28 from "node:path";
35750
+ import * as path35 from "node:path";
35194
35751
  var SessionWorktreeCache = class {
35195
35752
  sessionRepoCheckoutPaths = /* @__PURE__ */ new Map();
35196
35753
  sessionParentPathBySession = /* @__PURE__ */ new Map();
35197
35754
  sessionWorkingTreeRelRootBySession = /* @__PURE__ */ new Map();
35198
35755
  remember(sessionId, binding) {
35199
- const paths = binding.repoCheckoutPaths.map((p) => path28.resolve(p));
35756
+ const paths = binding.repoCheckoutPaths.map((p) => path35.resolve(p));
35200
35757
  this.sessionRepoCheckoutPaths.set(sessionId, paths);
35201
- this.sessionParentPathBySession.set(sessionId, path28.resolve(binding.sessionParentPath));
35202
- this.sessionWorkingTreeRelRootBySession.set(sessionId, path28.resolve(binding.workingTreeRelRoot));
35758
+ this.sessionParentPathBySession.set(sessionId, path35.resolve(binding.sessionParentPath));
35759
+ this.sessionWorkingTreeRelRootBySession.set(sessionId, path35.resolve(binding.workingTreeRelRoot));
35203
35760
  }
35204
35761
  clearSession(sessionId) {
35205
35762
  const paths = this.sessionRepoCheckoutPaths.get(sessionId);
@@ -35229,7 +35786,7 @@ var SessionWorktreeCache = class {
35229
35786
  const parent = this.sessionParentPathBySession.get(sessionId);
35230
35787
  const relRoot = this.sessionWorkingTreeRelRootBySession.get(sessionId);
35231
35788
  if (!parent || !relRoot) return false;
35232
- return path28.resolve(parent) !== path28.resolve(relRoot);
35789
+ return path35.resolve(parent) !== path35.resolve(relRoot);
35233
35790
  }
35234
35791
  };
35235
35792
 
@@ -35244,8 +35801,8 @@ var SessionWorktreeManager = class {
35244
35801
  this.log = options.log;
35245
35802
  this.layout = loadWorktreeLayout();
35246
35803
  }
35247
- discover(sessionId) {
35248
- return discoverSessionBinding({
35804
+ discoverAsync(sessionId) {
35805
+ return discoverSessionBindingAsync({
35249
35806
  sessionId,
35250
35807
  worktreesRootPath: this.worktreesRootPath,
35251
35808
  layout: this.layout
@@ -35265,14 +35822,12 @@ var SessionWorktreeManager = class {
35265
35822
  worktreesRootPath: this.worktreesRootPath,
35266
35823
  layout: this.layout,
35267
35824
  log: this.log,
35268
- discover: (sid) => this.discover(sid),
35825
+ discover: (sid) => this.discoverAsync(sid),
35269
35826
  opts
35270
35827
  });
35271
35828
  }
35272
35829
  async renameSessionBranch(sessionId, newBranch) {
35273
- const paths = this.cache.getRepoCheckoutPathsRef(sessionId);
35274
- if (!paths?.length) return;
35275
- await renameSessionWorktreeBranches(paths, newBranch, this.log);
35830
+ await renameSessionWorktreeBranch(this.cache, sessionId, newBranch, this.log);
35276
35831
  }
35277
35832
  usesWorktreeSession(sessionId) {
35278
35833
  if (!sessionId) return false;
@@ -35283,79 +35838,55 @@ var SessionWorktreeManager = class {
35283
35838
  return this.cache.getRepoCheckoutPaths(sessionId);
35284
35839
  }
35285
35840
  ensureRepoCheckoutPathsForSession(sessionId) {
35286
- return ensureRepoCheckoutPathsForSession(sessionId, this.cache, (sid) => this.discover(sid));
35841
+ return ensureRepoCheckoutPathsForSession(sessionId, this.cache);
35842
+ }
35843
+ async ensureRepoCheckoutPathsForSessionAsync(sessionId) {
35844
+ return ensureRepoCheckoutPathsForSessionAsync(sessionId, this.cache, (sid) => this.discoverAsync(sid));
35287
35845
  }
35288
35846
  getSessionWorktreeRootForSession(sessionId) {
35289
35847
  return this.getIsolatedSessionParentPathForSession(sessionId);
35290
35848
  }
35291
35849
  async removeSessionWorktrees(sessionId) {
35292
- const paths = this.cache.clearSession(sessionId);
35293
- if (!paths?.length) return;
35294
- await removeSessionWorktrees(paths, this.log);
35850
+ await removeSessionWorktreeCheckouts(this.cache, sessionId, this.log);
35295
35851
  }
35296
35852
  async commitSession(params) {
35297
- const paths = this.cache.getRepoCheckoutPathsRef(params.sessionId);
35298
- const targets = paths?.length ? paths : [getBridgeRoot()];
35299
- return commitSessionWorktrees({
35300
- paths: targets,
35301
- branch: params.branch,
35302
- message: params.message,
35303
- push: params.push
35304
- });
35853
+ return commitSessionWorktree(this.cache, params);
35305
35854
  }
35306
35855
  async getSessionWorkingTreeStatus(sessionId) {
35307
- return aggregateSessionPathsWorkingTreeStatus(
35308
- resolveCommitTargets(sessionId, this.cache, (sid) => this.discover(sid))
35309
- );
35856
+ return getSessionWorkingTreeStatus(this.cache, sessionId, (sid) => this.discoverAsync(sid));
35310
35857
  }
35311
35858
  async getSessionWorkingTreeChangeDetails(sessionId, opts) {
35312
- const targets = resolveCommitTargets(sessionId, this.cache, (sid) => this.discover(sid));
35313
- return getWorkingTreeChangeRepoDetails({
35314
- commitTargetPaths: targets,
35315
- sessionWorktreeRootPath: this.cache.getWorkingTreeRelRoot(sessionId),
35316
- legacyRepoNestedSessionLayout: this.cache.isLegacyNestedLayout(sessionId),
35317
- repoFilterRelPath: opts?.repoRelPath?.trim() ? opts.repoRelPath.trim() : null,
35318
- basis: opts?.basis,
35319
- recentCommitsLimit: opts?.recentCommitsLimit
35320
- });
35859
+ return getSessionWorkingTreeChangeDetails(this.cache, sessionId, (sid) => this.discoverAsync(sid), opts);
35321
35860
  }
35322
35861
  async pushSessionUpstream(sessionId) {
35323
- try {
35324
- await pushAheadOfUpstreamForPaths(
35325
- resolveCommitTargets(sessionId, this.cache, (sid) => this.discover(sid))
35326
- );
35327
- return { ok: true };
35328
- } catch (e) {
35329
- const err = e instanceof Error ? e.message : String(e);
35330
- return { ok: false, error: err };
35331
- }
35862
+ return pushSessionUpstream(this.cache, sessionId, (sid) => this.discoverAsync(sid));
35332
35863
  }
35333
35864
  };
35334
35865
 
35335
35866
  // src/worktrees/manager/default-worktrees-root-path.ts
35336
- import * as path29 from "node:path";
35867
+ import * as path36 from "node:path";
35337
35868
  import os8 from "node:os";
35338
35869
  function defaultWorktreesRootPath() {
35339
- return path29.join(os8.homedir(), ".buildautomaton", "worktrees");
35870
+ return path36.join(os8.homedir(), ".buildautomaton", "worktrees");
35340
35871
  }
35341
35872
 
35342
35873
  // src/files/watch-file-index.ts
35343
35874
  import { watch } from "node:fs";
35344
- import path34 from "node:path";
35875
+ import path41 from "node:path";
35345
35876
 
35346
35877
  // src/files/index/paths.ts
35347
- import path30 from "node:path";
35878
+ import path37 from "node:path";
35348
35879
  import crypto2 from "node:crypto";
35349
35880
  function getCwdHashForFileIndex(resolvedCwd) {
35350
- return crypto2.createHash("sha256").update(path30.resolve(resolvedCwd)).digest("hex").slice(0, INDEX_HASH_LEN);
35881
+ return crypto2.createHash("sha256").update(path37.resolve(resolvedCwd)).digest("hex").slice(0, INDEX_HASH_LEN);
35351
35882
  }
35352
35883
 
35353
35884
  // src/files/index/build-file-index.ts
35354
- import path32 from "node:path";
35885
+ import path39 from "node:path";
35355
35886
 
35356
35887
  // src/files/index/walk-workspace-tree.ts
35357
- import fs25 from "node:fs";
35358
- import path31 from "node:path";
35888
+ import fs29 from "node:fs";
35889
+ import path38 from "node:path";
35359
35890
  var DEPENDENCY_INSTALL_DIR_NAMES = /* @__PURE__ */ new Set([
35360
35891
  "node_modules",
35361
35892
  "bower_components",
@@ -35372,7 +35903,7 @@ function shouldSkipWorkspaceWalkEntry(name) {
35372
35903
  async function walkWorkspaceTreeAsync(dir, baseDir, onFile, state) {
35373
35904
  let names;
35374
35905
  try {
35375
- names = await fs25.promises.readdir(dir);
35906
+ names = await fs29.promises.readdir(dir);
35376
35907
  } catch {
35377
35908
  return;
35378
35909
  }
@@ -35384,14 +35915,14 @@ async function walkWorkspaceTreeAsync(dir, baseDir, onFile, state) {
35384
35915
  if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
35385
35916
  }
35386
35917
  state.n++;
35387
- const full = path31.join(dir, name);
35918
+ const full = path38.join(dir, name);
35388
35919
  let stat2;
35389
35920
  try {
35390
- stat2 = await fs25.promises.stat(full);
35921
+ stat2 = await fs29.promises.stat(full);
35391
35922
  } catch {
35392
35923
  continue;
35393
35924
  }
35394
- const relative6 = path31.relative(baseDir, full).replace(/\\/g, "/");
35925
+ const relative6 = path38.relative(baseDir, full).replace(/\\/g, "/");
35395
35926
  if (stat2.isDirectory()) {
35396
35927
  await walkWorkspaceTreeAsync(full, baseDir, onFile, state);
35397
35928
  } else if (stat2.isFile()) {
@@ -35404,7 +35935,7 @@ function createWalkYieldState() {
35404
35935
  }
35405
35936
 
35406
35937
  // src/files/index/file-index-sqlite-lock.ts
35407
- import fs26 from "node:fs";
35938
+ import fs30 from "node:fs";
35408
35939
  function isSqliteCorruptError(e) {
35409
35940
  const msg = e instanceof Error ? e.message : String(e);
35410
35941
  return msg.includes("malformed") || msg.includes("database disk image is malformed") || msg.includes("corrupt");
@@ -35418,7 +35949,7 @@ function withFileIndexSqliteLock(fn) {
35418
35949
  if (!isSqliteCorruptError(e)) throw e;
35419
35950
  closeAllCliSqliteConnections();
35420
35951
  try {
35421
- fs26.unlinkSync(getCliSqlitePath());
35952
+ fs30.unlinkSync(getCliSqlitePath());
35422
35953
  } catch {
35423
35954
  }
35424
35955
  chain = Promise.resolve();
@@ -35493,7 +36024,7 @@ async function collectWorkspacePathsAsync(resolved) {
35493
36024
  }
35494
36025
  async function buildFileIndexAsync(cwd) {
35495
36026
  return withFileIndexSqliteLock(async () => {
35496
- const resolved = path32.resolve(cwd);
36027
+ const resolved = path39.resolve(cwd);
35497
36028
  await yieldToEventLoop();
35498
36029
  assertNotShutdown();
35499
36030
  const paths = await collectWorkspacePathsAsync(resolved);
@@ -35505,7 +36036,7 @@ async function buildFileIndexAsync(cwd) {
35505
36036
  }
35506
36037
 
35507
36038
  // src/files/index/ensure-file-index.ts
35508
- import path33 from "node:path";
36039
+ import path40 from "node:path";
35509
36040
 
35510
36041
  // src/files/index/search-file-index.ts
35511
36042
  function escapeLikePattern(fragment) {
@@ -35557,7 +36088,7 @@ async function searchBridgeFilePathsAsync(resolvedCwd, query, limit = 100) {
35557
36088
 
35558
36089
  // src/files/index/ensure-file-index.ts
35559
36090
  async function ensureFileIndexAsync(cwd) {
35560
- const resolved = path33.resolve(cwd);
36091
+ const resolved = path40.resolve(cwd);
35561
36092
  if (await bridgeFileIndexIsPopulated(resolved)) {
35562
36093
  return { fromCache: true, pathCount: await bridgeFileIndexPathCount(resolved) };
35563
36094
  }
@@ -35601,7 +36132,7 @@ function createFsWatcher(resolved, schedule) {
35601
36132
  }
35602
36133
  }
35603
36134
  function startFileIndexWatcher(cwd = getBridgeRoot()) {
35604
- const resolved = path34.resolve(cwd);
36135
+ const resolved = path41.resolve(cwd);
35605
36136
  void buildFileIndexAsync(resolved).catch((e) => {
35606
36137
  if (e instanceof CliSqliteInterrupted) return;
35607
36138
  console.error("[file-index] Initial index build failed:", e);
@@ -35631,7 +36162,7 @@ function startFileIndexWatcher(cwd = getBridgeRoot()) {
35631
36162
  }
35632
36163
 
35633
36164
  // src/connection/create-bridge-connection.ts
35634
- import * as path45 from "node:path";
36165
+ import * as path52 from "node:path";
35635
36166
 
35636
36167
  // src/dev-servers/manager/dev-server-manager.ts
35637
36168
  import { rm as rm2 } from "node:fs/promises";
@@ -35653,15 +36184,15 @@ function sendDevServerStatus(getWs, serverId, status, options) {
35653
36184
 
35654
36185
  // src/dev-servers/process/terminate-child-process.ts
35655
36186
  async function sigtermAndWaitForExit(proc, graceMs, log2, shortId) {
35656
- const exited = new Promise((resolve20) => {
35657
- proc.once("exit", () => resolve20());
36187
+ const exited = new Promise((resolve26) => {
36188
+ proc.once("exit", () => resolve26());
35658
36189
  });
35659
36190
  log2(`[dev-server] Sending SIGTERM to ${shortId} (pid=${proc.pid ?? "?"}).`);
35660
36191
  try {
35661
36192
  proc.kill("SIGTERM");
35662
36193
  } catch {
35663
36194
  }
35664
- await Promise.race([exited, new Promise((resolve20) => setTimeout(resolve20, graceMs))]);
36195
+ await Promise.race([exited, new Promise((resolve26) => setTimeout(resolve26, graceMs))]);
35665
36196
  }
35666
36197
  function forceKillChild(proc, log2, shortId, graceMs) {
35667
36198
  log2(
@@ -35675,7 +36206,7 @@ function forceKillChild(proc, log2, shortId, graceMs) {
35675
36206
  }
35676
36207
 
35677
36208
  // src/dev-servers/process/wire-dev-server-child-process.ts
35678
- import fs27 from "node:fs";
36209
+ import fs31 from "node:fs";
35679
36210
 
35680
36211
  // src/dev-servers/manager/forward-pipe.ts
35681
36212
  function forwardChildPipe(childReadable, terminal, onData) {
@@ -35711,7 +36242,7 @@ function wireDevServerChildProcess(d) {
35711
36242
  d.setPollInterval(void 0);
35712
36243
  return;
35713
36244
  }
35714
- fs27.readFile(d.mergedLogPath, (err, buf) => {
36245
+ fs31.readFile(d.mergedLogPath, (err, buf) => {
35715
36246
  if (err || (d.getSpawnGeneration() ?? 0) !== d.scheduledGen) return;
35716
36247
  if (buf.length <= d.mergedReadPos.value) return;
35717
36248
  const chunk = Buffer.from(buf.subarray(d.mergedReadPos.value));
@@ -35749,7 +36280,7 @@ ${errTail}` : ""}`);
35749
36280
  d.sendStatus(code === 0 || code == null ? "stopped" : "error", detail, tails);
35750
36281
  };
35751
36282
  if (mergedPath) {
35752
- fs27.readFile(mergedPath, (err, buf) => {
36283
+ fs31.readFile(mergedPath, (err, buf) => {
35753
36284
  if (!err && buf.length > d.mergedReadPos.value) {
35754
36285
  const chunk = Buffer.from(buf.subarray(d.mergedReadPos.value));
35755
36286
  if (chunk.length > 0) {
@@ -35851,13 +36382,13 @@ function parseDevServerDefs(servers) {
35851
36382
  }
35852
36383
 
35853
36384
  // src/dev-servers/manager/shell-spawn/utils.ts
35854
- import fs28 from "node:fs";
36385
+ import fs32 from "node:fs";
35855
36386
  function isSpawnEbadf(e) {
35856
36387
  return typeof e === "object" && e !== null && "code" in e && e.code === "EBADF";
35857
36388
  }
35858
36389
  function rmDirQuiet(dir) {
35859
36390
  try {
35860
- fs28.rmSync(dir, { recursive: true, force: true });
36391
+ fs32.rmSync(dir, { recursive: true, force: true });
35861
36392
  } catch {
35862
36393
  }
35863
36394
  }
@@ -35865,7 +36396,7 @@ var cachedDevNullReadFd;
35865
36396
  function devNullReadFd() {
35866
36397
  if (cachedDevNullReadFd === void 0) {
35867
36398
  const devPath = process.platform === "win32" ? "nul" : "/dev/null";
35868
- cachedDevNullReadFd = fs28.openSync(devPath, "r");
36399
+ cachedDevNullReadFd = fs32.openSync(devPath, "r");
35869
36400
  }
35870
36401
  return cachedDevNullReadFd;
35871
36402
  }
@@ -35939,15 +36470,15 @@ function trySpawnShellTruePiped(command, env, cwd, devNullFd, signal) {
35939
36470
 
35940
36471
  // src/dev-servers/manager/shell-spawn/try-spawn-merged-log-file.ts
35941
36472
  import { spawn as spawn8 } from "node:child_process";
35942
- import fs29 from "node:fs";
36473
+ import fs33 from "node:fs";
35943
36474
  import { tmpdir } from "node:os";
35944
- import path35 from "node:path";
36475
+ import path42 from "node:path";
35945
36476
  function trySpawnMergedLogFile(command, env, cwd, signal) {
35946
- const tmpRoot = fs29.mkdtempSync(path35.join(tmpdir(), "ba-devsrv-log-"));
35947
- const logPath = path35.join(tmpRoot, "combined.log");
36477
+ const tmpRoot = fs33.mkdtempSync(path42.join(tmpdir(), "ba-devsrv-log-"));
36478
+ const logPath = path42.join(tmpRoot, "combined.log");
35948
36479
  let logFd;
35949
36480
  try {
35950
- logFd = fs29.openSync(logPath, "a");
36481
+ logFd = fs33.openSync(logPath, "a");
35951
36482
  } catch {
35952
36483
  rmDirQuiet(tmpRoot);
35953
36484
  return null;
@@ -35966,7 +36497,7 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
35966
36497
  } else {
35967
36498
  proc = spawn8("/bin/sh", ["-c", command], { env, cwd, stdio, ...signal ? { signal } : {} });
35968
36499
  }
35969
- fs29.closeSync(logFd);
36500
+ fs33.closeSync(logFd);
35970
36501
  return {
35971
36502
  proc,
35972
36503
  pipedStdoutStderr: true,
@@ -35975,7 +36506,7 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
35975
36506
  };
35976
36507
  } catch (e) {
35977
36508
  try {
35978
- fs29.closeSync(logFd);
36509
+ fs33.closeSync(logFd);
35979
36510
  } catch {
35980
36511
  }
35981
36512
  rmDirQuiet(tmpRoot);
@@ -35986,22 +36517,22 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
35986
36517
 
35987
36518
  // src/dev-servers/manager/shell-spawn/try-spawn-shell-script-log-redirect.ts
35988
36519
  import { spawn as spawn9 } from "node:child_process";
35989
- import fs30 from "node:fs";
36520
+ import fs34 from "node:fs";
35990
36521
  import { tmpdir as tmpdir2 } from "node:os";
35991
- import path36 from "node:path";
36522
+ import path43 from "node:path";
35992
36523
  function shSingleQuote(s) {
35993
36524
  return `'${s.replace(/'/g, `'\\''`)}'`;
35994
36525
  }
35995
36526
  function trySpawnShellScriptLogRedirectUnix(command, env, cwd, signal) {
35996
- const tmpRoot = fs30.mkdtempSync(path36.join(tmpdir2(), "ba-devsrv-sh-"));
35997
- const logPath = path36.join(tmpRoot, "combined.log");
35998
- const innerPath = path36.join(tmpRoot, "_cmd.sh");
35999
- const runnerPath = path36.join(tmpRoot, "_run.sh");
36527
+ const tmpRoot = fs34.mkdtempSync(path43.join(tmpdir2(), "ba-devsrv-sh-"));
36528
+ const logPath = path43.join(tmpRoot, "combined.log");
36529
+ const innerPath = path43.join(tmpRoot, "_cmd.sh");
36530
+ const runnerPath = path43.join(tmpRoot, "_run.sh");
36000
36531
  try {
36001
- fs30.writeFileSync(innerPath, `#!/bin/sh
36532
+ fs34.writeFileSync(innerPath, `#!/bin/sh
36002
36533
  ${command}
36003
36534
  `);
36004
- fs30.writeFileSync(
36535
+ fs34.writeFileSync(
36005
36536
  runnerPath,
36006
36537
  `#!/bin/sh
36007
36538
  cd ${shSingleQuote(cwd)}
@@ -36027,13 +36558,13 @@ cd ${shSingleQuote(cwd)}
36027
36558
  }
36028
36559
  }
36029
36560
  function trySpawnShellScriptLogRedirectWin(command, env, cwd, signal) {
36030
- const tmpRoot = fs30.mkdtempSync(path36.join(tmpdir2(), "ba-devsrv-sh-"));
36031
- const logPath = path36.join(tmpRoot, "combined.log");
36032
- const runnerPath = path36.join(tmpRoot, "_run.bat");
36561
+ const tmpRoot = fs34.mkdtempSync(path43.join(tmpdir2(), "ba-devsrv-sh-"));
36562
+ const logPath = path43.join(tmpRoot, "combined.log");
36563
+ const runnerPath = path43.join(tmpRoot, "_run.bat");
36033
36564
  const q = (p) => `"${p.replace(/"/g, '""')}"`;
36034
36565
  const com = process.env.ComSpec || "cmd.exe";
36035
36566
  try {
36036
- fs30.writeFileSync(
36567
+ fs34.writeFileSync(
36037
36568
  runnerPath,
36038
36569
  `@ECHO OFF\r
36039
36570
  CD /D ${q(cwd)}\r
@@ -36802,30 +37333,30 @@ function createOnBridgeIdentified(opts) {
36802
37333
  }
36803
37334
 
36804
37335
  // src/skills/discover-local-agent-skills.ts
36805
- import fs31 from "node:fs";
36806
- import path37 from "node:path";
37336
+ import fs35 from "node:fs";
37337
+ import path44 from "node:path";
36807
37338
  var SKILL_DISCOVERY_ROOTS = [".agents/skills", ".claude/skills", ".cursor/skills", "skills"];
36808
37339
  function discoverLocalSkills(cwd) {
36809
37340
  const out = [];
36810
37341
  const seenKeys = /* @__PURE__ */ new Set();
36811
37342
  for (const rel of SKILL_DISCOVERY_ROOTS) {
36812
- const base = path37.join(cwd, rel);
36813
- if (!fs31.existsSync(base) || !fs31.statSync(base).isDirectory()) continue;
37343
+ const base = path44.join(cwd, rel);
37344
+ if (!fs35.existsSync(base) || !fs35.statSync(base).isDirectory()) continue;
36814
37345
  let entries = [];
36815
37346
  try {
36816
- entries = fs31.readdirSync(base);
37347
+ entries = fs35.readdirSync(base);
36817
37348
  } catch {
36818
37349
  continue;
36819
37350
  }
36820
37351
  for (const name of entries) {
36821
- const dir = path37.join(base, name);
37352
+ const dir = path44.join(base, name);
36822
37353
  try {
36823
- if (!fs31.statSync(dir).isDirectory()) continue;
37354
+ if (!fs35.statSync(dir).isDirectory()) continue;
36824
37355
  } catch {
36825
37356
  continue;
36826
37357
  }
36827
- const skillMd = path37.join(dir, "SKILL.md");
36828
- if (!fs31.existsSync(skillMd)) continue;
37358
+ const skillMd = path44.join(dir, "SKILL.md");
37359
+ if (!fs35.existsSync(skillMd)) continue;
36829
37360
  const key = `${rel}/${name}`;
36830
37361
  if (seenKeys.has(key)) continue;
36831
37362
  seenKeys.add(key);
@@ -36837,23 +37368,23 @@ function discoverLocalSkills(cwd) {
36837
37368
  function discoverSkillLayoutRoots(cwd) {
36838
37369
  const roots = [];
36839
37370
  for (const rel of SKILL_DISCOVERY_ROOTS) {
36840
- const base = path37.join(cwd, rel);
36841
- if (!fs31.existsSync(base) || !fs31.statSync(base).isDirectory()) continue;
37371
+ const base = path44.join(cwd, rel);
37372
+ if (!fs35.existsSync(base) || !fs35.statSync(base).isDirectory()) continue;
36842
37373
  let entries = [];
36843
37374
  try {
36844
- entries = fs31.readdirSync(base);
37375
+ entries = fs35.readdirSync(base);
36845
37376
  } catch {
36846
37377
  continue;
36847
37378
  }
36848
37379
  const skills2 = [];
36849
37380
  for (const name of entries) {
36850
- const dir = path37.join(base, name);
37381
+ const dir = path44.join(base, name);
36851
37382
  try {
36852
- if (!fs31.statSync(dir).isDirectory()) continue;
37383
+ if (!fs35.statSync(dir).isDirectory()) continue;
36853
37384
  } catch {
36854
37385
  continue;
36855
37386
  }
36856
- if (!fs31.existsSync(path37.join(dir, "SKILL.md"))) continue;
37387
+ if (!fs35.existsSync(path44.join(dir, "SKILL.md"))) continue;
36857
37388
  const relPath = `${rel}/${name}`.replace(/\\/g, "/");
36858
37389
  skills2.push({ name, relPath });
36859
37390
  }
@@ -37209,7 +37740,7 @@ function syncRunningTurnQueueKeys(turns, queueKey) {
37209
37740
  }
37210
37741
 
37211
37742
  // src/prompt-turn-queue/runner/run-local-revert-before-queued-prompt.ts
37212
- import fs32 from "node:fs";
37743
+ import fs36 from "node:fs";
37213
37744
  async function runLocalRevertBeforeQueuedPrompt(next, deps) {
37214
37745
  if (next.serverState !== "requeued_with_revert") return true;
37215
37746
  const sid = next.sessionId;
@@ -37218,7 +37749,7 @@ async function runLocalRevertBeforeQueuedPrompt(next, deps) {
37218
37749
  const agentBase = deps.sessionWorktreeManager.getSessionWorktreeRootForSession(sid) ?? getBridgeRoot();
37219
37750
  const file2 = snapshotFilePath(agentBase, tid);
37220
37751
  try {
37221
- await fs32.promises.access(file2, fs32.constants.F_OK);
37752
+ await fs36.promises.access(file2, fs36.constants.F_OK);
37222
37753
  } catch {
37223
37754
  deps.log(
37224
37755
  `[Queue] requeued_with_revert: no pre-turn snapshot for ${tid.slice(0, 8)}\u2026; continuing without revert.`
@@ -37333,7 +37864,7 @@ function createBridgePromptSenders(deps, getWs) {
37333
37864
  sendWsMessage(s, wire);
37334
37865
  return true;
37335
37866
  };
37336
- const sendResult2 = (result) => {
37867
+ const sendResult = (result) => {
37337
37868
  const skipEncryptForChangeSummaryFollowUp = result.type === "prompt_result" && result.followUpCatalogPromptId === BUILTIN_SESSION_CHANGE_SUMMARY_FOLLOW_UP_CATALOG_PROMPT_ID;
37338
37869
  const encryptedFields = result.type === "prompt_result" && !skipEncryptForChangeSummaryFollowUp ? ["output", "error"] : [];
37339
37870
  sendBridgeMessage(result, encryptedFields);
@@ -37366,7 +37897,7 @@ function createBridgePromptSenders(deps, getWs) {
37366
37897
  ]) : payload;
37367
37898
  sendWsMessage(s, wire);
37368
37899
  };
37369
- return { sendBridgeMessage, sendResult: sendResult2, sendSessionUpdate };
37900
+ return { sendBridgeMessage, sendResult, sendSessionUpdate };
37370
37901
  }
37371
37902
 
37372
37903
  // src/agents/acp/from-bridge/handle-bridge-prompt/parse-bridge-attachments.ts
@@ -37470,7 +38001,7 @@ async function readGitBranch(cwd) {
37470
38001
  async function runBridgePromptPreamble(params) {
37471
38002
  const { getWs, log: log2, sessionWorktreeManager, sessionId, runId, effectiveCwd } = params;
37472
38003
  const s = getWs();
37473
- const repoCheckoutPaths = sessionWorktreeManager.ensureRepoCheckoutPathsForSession(sessionId);
38004
+ const repoCheckoutPaths = await sessionWorktreeManager.ensureRepoCheckoutPathsForSessionAsync(sessionId);
37474
38005
  const repoRoots = await resolveSnapshotRepoRoots({
37475
38006
  worktreePaths: repoCheckoutPaths,
37476
38007
  fallbackCwd: effectiveCwd,
@@ -37506,9 +38037,9 @@ function parseChangeSummarySnapshots(raw) {
37506
38037
  for (const item of raw) {
37507
38038
  if (!item || typeof item !== "object") continue;
37508
38039
  const o = item;
37509
- const path46 = typeof o.path === "string" && o.path.trim() !== "" ? o.path.trim() : "";
37510
- if (!path46) continue;
37511
- const row = { path: path46 };
38040
+ const path53 = typeof o.path === "string" && o.path.trim() !== "" ? o.path.trim() : "";
38041
+ if (!path53) continue;
38042
+ const row = { path: path53 };
37512
38043
  if (typeof o.patchContent === "string") row.patchContent = o.patchContent;
37513
38044
  if (typeof o.oldText === "string") row.oldText = o.oldText;
37514
38045
  if (typeof o.newText === "string") row.newText = o.newText;
@@ -37542,7 +38073,7 @@ async function runPreambleAndPrompt(params) {
37542
38073
  agentId,
37543
38074
  agentConfig,
37544
38075
  resolvedCwd,
37545
- senders: { sendResult: sendResult2, sendSessionUpdate }
38076
+ senders: { sendResult, sendSessionUpdate }
37546
38077
  } = params;
37547
38078
  const effectiveCwd = resolveSessionParentPathForAgentProcess(resolvedCwd);
37548
38079
  await runBridgePromptPreamble({
@@ -37580,7 +38111,7 @@ async function runPreambleAndPrompt(params) {
37580
38111
  agentId,
37581
38112
  agentConfig,
37582
38113
  sessionParentPath: effectiveCwd,
37583
- sendResult: sendResult2,
38114
+ sendResult,
37584
38115
  sendSessionUpdate,
37585
38116
  followUpCatalogPromptId,
37586
38117
  sessionChangeSummaryFilePaths: pathsForUpload,
@@ -37722,14 +38253,14 @@ var handleSkillCallMessage = (msg, { getWs, log: log2 }) => {
37722
38253
  };
37723
38254
 
37724
38255
  // src/files/list-dir/index.ts
37725
- import fs34 from "node:fs";
38256
+ import fs38 from "node:fs";
37726
38257
 
37727
38258
  // src/files/ensure-under-cwd.ts
37728
- import path38 from "node:path";
38259
+ import path45 from "node:path";
37729
38260
  function ensureUnderCwd(relativePath, cwd = getBridgeRoot()) {
37730
- const normalized = path38.normalize(relativePath).replace(/^(\.\/)+/, "");
37731
- const resolved = path38.resolve(cwd, normalized);
37732
- if (!resolved.startsWith(cwd + path38.sep) && resolved !== cwd) {
38261
+ const normalized = path45.normalize(relativePath).replace(/^(\.\/)+/, "");
38262
+ const resolved = path45.resolve(cwd, normalized);
38263
+ if (!resolved.startsWith(cwd + path45.sep) && resolved !== cwd) {
37733
38264
  return null;
37734
38265
  }
37735
38266
  return resolved;
@@ -37739,15 +38270,15 @@ function ensureUnderCwd(relativePath, cwd = getBridgeRoot()) {
37739
38270
  var LIST_DIR_YIELD_EVERY = 256;
37740
38271
 
37741
38272
  // src/files/list-dir/map-dir-entry.ts
37742
- import path39 from "node:path";
37743
- import fs33 from "node:fs";
38273
+ import path46 from "node:path";
38274
+ import fs37 from "node:fs";
37744
38275
  async function mapDirEntry(d, relativePath, resolved) {
37745
- const entryPath = path39.join(relativePath || ".", d.name).replace(/\\/g, "/");
37746
- const fullPath = path39.join(resolved, d.name);
38276
+ const entryPath = path46.join(relativePath || ".", d.name).replace(/\\/g, "/");
38277
+ const fullPath = path46.join(resolved, d.name);
37747
38278
  let isDir = d.isDirectory();
37748
38279
  if (d.isSymbolicLink()) {
37749
38280
  try {
37750
- const targetStat = await fs33.promises.stat(fullPath);
38281
+ const targetStat = await fs37.promises.stat(fullPath);
37751
38282
  isDir = targetStat.isDirectory();
37752
38283
  } catch {
37753
38284
  isDir = false;
@@ -37777,7 +38308,7 @@ async function listDirAsync(relativePath, sessionParentPath = getBridgeRoot()) {
37777
38308
  return { error: "Path is outside working directory" };
37778
38309
  }
37779
38310
  try {
37780
- const names = await fs34.promises.readdir(resolved, { withFileTypes: true });
38311
+ const names = await fs38.promises.readdir(resolved, { withFileTypes: true });
37781
38312
  const entries = [];
37782
38313
  for (let i = 0; i < names.length; i++) {
37783
38314
  if (i > 0 && i % LIST_DIR_YIELD_EVERY === 0) {
@@ -37797,18 +38328,18 @@ var LINE_CHUNK_SIZE = 64 * 1024;
37797
38328
  var READ_RANGE_YIELD_EVERY_BYTES = 256 * 1024;
37798
38329
 
37799
38330
  // src/files/read-file/resolve-file-path.ts
37800
- import fs35 from "node:fs";
38331
+ import fs39 from "node:fs";
37801
38332
  async function resolveFilePathAsync(relativePath, sessionParentPath = getBridgeRoot()) {
37802
38333
  const resolved = ensureUnderCwd(relativePath, sessionParentPath);
37803
38334
  if (!resolved) return { error: "Path is outside working directory" };
37804
38335
  let real;
37805
38336
  try {
37806
- real = await fs35.promises.realpath(resolved);
38337
+ real = await fs39.promises.realpath(resolved);
37807
38338
  } catch {
37808
38339
  real = resolved;
37809
38340
  }
37810
38341
  try {
37811
- const stat2 = await fs35.promises.stat(real);
38342
+ const stat2 = await fs39.promises.stat(real);
37812
38343
  if (!stat2.isFile()) return { error: "Not a file" };
37813
38344
  return real;
37814
38345
  } catch (err) {
@@ -37817,11 +38348,11 @@ async function resolveFilePathAsync(relativePath, sessionParentPath = getBridgeR
37817
38348
  }
37818
38349
 
37819
38350
  // src/files/read-file/read-file-range-async.ts
37820
- import fs36 from "node:fs";
38351
+ import fs40 from "node:fs";
37821
38352
  import { StringDecoder } from "node:string_decoder";
37822
38353
  async function readFileRangeAsync(filePath, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
37823
- const fileSize = (await fs36.promises.stat(filePath)).size;
37824
- const fd = await fs36.promises.open(filePath, "r");
38354
+ const fileSize = (await fs40.promises.stat(filePath)).size;
38355
+ const fd = await fs40.promises.open(filePath, "r");
37825
38356
  const bufSize = 64 * 1024;
37826
38357
  const buf = Buffer.alloc(bufSize);
37827
38358
  const decoder = new StringDecoder("utf8");
@@ -37983,11 +38514,11 @@ async function readFileRangeAsync(filePath, startLine, endLine, lineOffsetIn, li
37983
38514
  }
37984
38515
 
37985
38516
  // src/files/read-file/read-file-buffer-full-async.ts
37986
- import fs37 from "node:fs";
38517
+ import fs41 from "node:fs";
37987
38518
  var READ_CHUNK_BYTES = 256 * 1024;
37988
38519
  async function readFileBufferFullAsync(filePath) {
37989
- const stat2 = await fs37.promises.stat(filePath);
37990
- const fd = await fs37.promises.open(filePath, "r");
38520
+ const stat2 = await fs41.promises.stat(filePath);
38521
+ const fd = await fs41.promises.open(filePath, "r");
37991
38522
  const chunks = [];
37992
38523
  let position = 0;
37993
38524
  let bytesSinceYield = 0;
@@ -38083,10 +38614,10 @@ async function readFileAsync(relativePath, startLine, endLine, lineOffset, lineC
38083
38614
  }
38084
38615
 
38085
38616
  // src/files/resolve-file-browser-session-parent.ts
38086
- function resolveFileBrowserSessionParent(sessionWorktreeManager, sessionId) {
38617
+ async function resolveFileBrowserSessionParent(sessionWorktreeManager, sessionId) {
38087
38618
  const sid = sessionId?.trim();
38088
38619
  if (sid) {
38089
- sessionWorktreeManager.ensureRepoCheckoutPathsForSession(sid);
38620
+ await sessionWorktreeManager.ensureRepoCheckoutPathsForSessionAsync(sid);
38090
38621
  const worktreeRoot = sessionWorktreeManager.getSessionWorktreeRootForSession(sid);
38091
38622
  if (worktreeRoot) return worktreeRoot;
38092
38623
  }
@@ -38094,14 +38625,14 @@ function resolveFileBrowserSessionParent(sessionWorktreeManager, sessionId) {
38094
38625
  }
38095
38626
 
38096
38627
  // src/files/handle-file-browser-search.ts
38097
- import path40 from "node:path";
38628
+ import path47 from "node:path";
38098
38629
  var SEARCH_LIMIT = 100;
38099
38630
  function handleFileBrowserSearch(msg, socket, e2ee, sessionWorktreeManager) {
38100
38631
  void (async () => {
38101
38632
  await yieldToEventLoop();
38102
38633
  const q = typeof msg.q === "string" ? msg.q : "";
38103
- const sessionParentPath = path40.resolve(
38104
- sessionWorktreeManager != null ? resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : getBridgeRoot()
38634
+ const sessionParentPath = path47.resolve(
38635
+ sessionWorktreeManager != null ? await resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : getBridgeRoot()
38105
38636
  );
38106
38637
  if (!await bridgeFileIndexIsPopulated(sessionParentPath)) {
38107
38638
  const payload2 = {
@@ -38139,7 +38670,7 @@ function handleFileBrowserRequest(msg, socket, e2ee, sessionWorktreeManager) {
38139
38670
  void (async () => {
38140
38671
  const reqPath = msg.path.replace(/^\/+/, "") || ".";
38141
38672
  const op = msg.op === "read" ? "read" : "list";
38142
- const sessionParentPath = sessionWorktreeManager != null ? resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : void 0;
38673
+ const sessionParentPath = sessionWorktreeManager != null ? await resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : void 0;
38143
38674
  if (op === "list") {
38144
38675
  const result = await listDirAsync(reqPath, sessionParentPath);
38145
38676
  if ("error" in result) {
@@ -38219,8 +38750,8 @@ function handleSkillLayoutRequest(msg, deps) {
38219
38750
  }
38220
38751
 
38221
38752
  // src/skills/install-remote-skills.ts
38222
- import fs38 from "node:fs";
38223
- import path41 from "node:path";
38753
+ import fs42 from "node:fs";
38754
+ import path48 from "node:path";
38224
38755
  function installRemoteSkills(cwd, targetDir, items) {
38225
38756
  const installed2 = [];
38226
38757
  if (!Array.isArray(items)) {
@@ -38231,15 +38762,15 @@ function installRemoteSkills(cwd, targetDir, items) {
38231
38762
  if (typeof item.sourceId !== "string" || typeof item.skillName !== "string" || typeof item.versionHash !== "string" || !Array.isArray(item.files)) {
38232
38763
  continue;
38233
38764
  }
38234
- const skillDir = path41.join(cwd, targetDir, item.skillName);
38765
+ const skillDir = path48.join(cwd, targetDir, item.skillName);
38235
38766
  for (const f of item.files) {
38236
38767
  if (typeof f.path !== "string" || !f.text && !f.base64) continue;
38237
- const dest = path41.join(skillDir, f.path);
38238
- fs38.mkdirSync(path41.dirname(dest), { recursive: true });
38768
+ const dest = path48.join(skillDir, f.path);
38769
+ fs42.mkdirSync(path48.dirname(dest), { recursive: true });
38239
38770
  if (f.text !== void 0) {
38240
- fs38.writeFileSync(dest, f.text, "utf8");
38771
+ fs42.writeFileSync(dest, f.text, "utf8");
38241
38772
  } else if (f.base64) {
38242
- fs38.writeFileSync(dest, Buffer.from(f.base64, "base64"));
38773
+ fs42.writeFileSync(dest, Buffer.from(f.base64, "base64"));
38243
38774
  }
38244
38775
  }
38245
38776
  installed2.push({
@@ -38280,12 +38811,116 @@ var handleRefreshLocalSkills = (_msg, deps) => {
38280
38811
  deps.sendLocalSkillsReport?.();
38281
38812
  };
38282
38813
 
38283
- // src/routing/handlers/session-git-request.ts
38284
- function sendResult(ws, id, payload, e2ee, encryptedFields = []) {
38814
+ // src/routing/handlers/git/handle-session-git-commit.ts
38815
+ async function handleSessionGitCommitAction(deps, sessionId, msg, reply) {
38816
+ const branch = typeof msg.branch === "string" ? msg.branch : "";
38817
+ const message = typeof msg.message === "string" ? msg.message : "";
38818
+ const pushAfterCommit = msg.pushAfterCommit === true;
38819
+ if (!branch.trim() || !message.trim()) {
38820
+ reply({ ok: false, error: "branch and message are required for commit" });
38821
+ return;
38822
+ }
38823
+ const commitRes = await deps.sessionWorktreeManager.commitSession({
38824
+ sessionId,
38825
+ branch: branch.trim(),
38826
+ message: message.trim(),
38827
+ push: pushAfterCommit
38828
+ });
38829
+ if (!commitRes.ok) {
38830
+ reply({ ok: false, error: commitRes.error ?? "Commit failed" });
38831
+ return;
38832
+ }
38833
+ const st = await deps.sessionWorktreeManager.getSessionWorkingTreeStatus(sessionId);
38834
+ reply({
38835
+ ok: true,
38836
+ hasUncommittedChanges: st.hasUncommittedChanges,
38837
+ hasUnpushedCommits: st.hasUnpushedCommits,
38838
+ uncommittedFileCount: st.uncommittedFileCount
38839
+ });
38840
+ }
38841
+
38842
+ // src/routing/handlers/git/coalesce-list-changes.ts
38843
+ var listChangesInflight = /* @__PURE__ */ new Map();
38844
+ function listChangesInflightKey(sessionId, opts) {
38845
+ const basis = opts?.basis;
38846
+ return [
38847
+ sessionId,
38848
+ opts?.repoRelPath ?? "",
38849
+ basis?.kind === "commit" ? `commit:${basis.sha}` : "working",
38850
+ String(opts?.recentCommitsLimit ?? "")
38851
+ ].join("|");
38852
+ }
38853
+ function getSessionWorkingTreeChangeDetailsCoalesced(sessionWorktreeManager, sessionId, opts) {
38854
+ const key = listChangesInflightKey(sessionId, opts);
38855
+ const existing = listChangesInflight.get(key);
38856
+ if (existing) return existing;
38857
+ const promise2 = sessionWorktreeManager.getSessionWorkingTreeChangeDetails(sessionId, opts).finally(() => {
38858
+ if (listChangesInflight.get(key) === promise2) listChangesInflight.delete(key);
38859
+ });
38860
+ listChangesInflight.set(key, promise2);
38861
+ return promise2;
38862
+ }
38863
+
38864
+ // src/routing/handlers/git/handle-session-git-list-changes.ts
38865
+ async function handleSessionGitListChangesAction(deps, msg, reply) {
38866
+ const repoRel = typeof msg.changesRepoRelPath === "string" ? msg.changesRepoRelPath.trim() : "";
38867
+ const view = msg.changesView === "commit" ? "commit" : "working";
38868
+ const commitSha = typeof msg.changesCommitSha === "string" ? msg.changesCommitSha.trim() : "";
38869
+ if (view === "commit") {
38870
+ if (!repoRel || !commitSha) {
38871
+ reply({ ok: false, error: "changesRepoRelPath and changesCommitSha are required for commit view" });
38872
+ return;
38873
+ }
38874
+ }
38875
+ const recentCommitsLimit = typeof msg.changesRecentCommitsLimit === "number" || typeof msg.changesRecentCommitsLimit === "string" ? msg.changesRecentCommitsLimit : void 0;
38876
+ const opts = repoRel && view === "commit" && commitSha ? {
38877
+ repoRelPath: repoRel,
38878
+ basis: { kind: "commit", sha: commitSha },
38879
+ recentCommitsLimit
38880
+ } : repoRel ? { repoRelPath: repoRel, basis: { kind: "working" }, recentCommitsLimit } : recentCommitsLimit !== void 0 ? { recentCommitsLimit } : void 0;
38881
+ const repos = await getSessionWorkingTreeChangeDetailsCoalesced(
38882
+ deps.sessionWorktreeManager,
38883
+ msg.sessionId,
38884
+ opts
38885
+ );
38886
+ reply({ ok: true, repos }, ["repos"]);
38887
+ }
38888
+
38889
+ // src/routing/handlers/git/handle-session-git-push.ts
38890
+ async function handleSessionGitPushAction(deps, sessionId, reply) {
38891
+ const pushRes = await deps.sessionWorktreeManager.pushSessionUpstream(sessionId);
38892
+ if (!pushRes.ok) {
38893
+ reply({ ok: false, error: pushRes.error ?? "Push failed" });
38894
+ return;
38895
+ }
38896
+ const st = await deps.sessionWorktreeManager.getSessionWorkingTreeStatus(sessionId);
38897
+ reply({
38898
+ ok: true,
38899
+ hasUncommittedChanges: st.hasUncommittedChanges,
38900
+ hasUnpushedCommits: st.hasUnpushedCommits,
38901
+ uncommittedFileCount: st.uncommittedFileCount
38902
+ });
38903
+ }
38904
+
38905
+ // src/routing/handlers/git/handle-session-git-status.ts
38906
+ async function handleSessionGitStatusAction(deps, sessionId, reply) {
38907
+ const r = await deps.sessionWorktreeManager.getSessionWorkingTreeStatus(sessionId);
38908
+ reply({
38909
+ ok: true,
38910
+ hasUncommittedChanges: r.hasUncommittedChanges,
38911
+ hasUnpushedCommits: r.hasUnpushedCommits,
38912
+ uncommittedFileCount: r.uncommittedFileCount
38913
+ });
38914
+ }
38915
+
38916
+ // src/routing/handlers/git/send-session-git-result.ts
38917
+ function sendSessionGitResult(ws, id, payload, e2ee, encryptedFields = []) {
38285
38918
  if (!ws) return;
38286
38919
  const message = { type: "session_git_result", id, ...payload };
38287
38920
  sendWsMessage(ws, e2ee && encryptedFields.length > 0 ? e2ee.encryptFields(message, encryptedFields) : message);
38288
38921
  }
38922
+
38923
+ // src/routing/handlers/git/session-git-request.ts
38289
38924
  var handleSessionGitRequestMessage = (msg, deps) => {
38290
38925
  if (typeof msg.id !== "string") return;
38291
38926
  const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
@@ -38294,80 +38929,21 @@ var handleSessionGitRequestMessage = (msg, deps) => {
38294
38929
  return;
38295
38930
  void (async () => {
38296
38931
  const ws = deps.getWs();
38297
- const reply = (payload, encryptedFields = []) => sendResult(ws, msg.id, payload, deps.e2ee, encryptedFields);
38932
+ const reply = (payload, encryptedFields = []) => sendSessionGitResult(ws, msg.id, payload, deps.e2ee, encryptedFields);
38298
38933
  try {
38299
38934
  if (action === "status") {
38300
- const r = await deps.sessionWorktreeManager.getSessionWorkingTreeStatus(sessionId);
38301
- reply({
38302
- ok: true,
38303
- hasUncommittedChanges: r.hasUncommittedChanges,
38304
- hasUnpushedCommits: r.hasUnpushedCommits,
38305
- uncommittedFileCount: r.uncommittedFileCount
38306
- });
38935
+ await handleSessionGitStatusAction(deps, sessionId, reply);
38307
38936
  return;
38308
38937
  }
38309
38938
  if (action === "list_changes") {
38310
- const repoRel = typeof msg.changesRepoRelPath === "string" ? msg.changesRepoRelPath.trim() : "";
38311
- const view = msg.changesView === "commit" ? "commit" : "working";
38312
- const commitSha = typeof msg.changesCommitSha === "string" ? msg.changesCommitSha.trim() : "";
38313
- if (view === "commit") {
38314
- if (!repoRel || !commitSha) {
38315
- reply({ ok: false, error: "changesRepoRelPath and changesCommitSha are required for commit view" });
38316
- return;
38317
- }
38318
- }
38319
- const recentCommitsLimit = typeof msg.changesRecentCommitsLimit === "number" || typeof msg.changesRecentCommitsLimit === "string" ? msg.changesRecentCommitsLimit : void 0;
38320
- const opts = repoRel && view === "commit" && commitSha ? {
38321
- repoRelPath: repoRel,
38322
- basis: { kind: "commit", sha: commitSha },
38323
- recentCommitsLimit
38324
- } : repoRel ? { repoRelPath: repoRel, basis: { kind: "working" }, recentCommitsLimit } : recentCommitsLimit !== void 0 ? { recentCommitsLimit } : void 0;
38325
- const repos = await deps.sessionWorktreeManager.getSessionWorkingTreeChangeDetails(sessionId, opts);
38326
- reply({
38327
- ok: true,
38328
- repos
38329
- }, ["repos"]);
38939
+ await handleSessionGitListChangesAction(deps, { ...msg, sessionId }, reply);
38330
38940
  return;
38331
38941
  }
38332
38942
  if (action === "push") {
38333
- const pushRes = await deps.sessionWorktreeManager.pushSessionUpstream(sessionId);
38334
- if (!pushRes.ok) {
38335
- reply({ ok: false, error: pushRes.error ?? "Push failed" });
38336
- return;
38337
- }
38338
- const st2 = await deps.sessionWorktreeManager.getSessionWorkingTreeStatus(sessionId);
38339
- reply({
38340
- ok: true,
38341
- hasUncommittedChanges: st2.hasUncommittedChanges,
38342
- hasUnpushedCommits: st2.hasUnpushedCommits,
38343
- uncommittedFileCount: st2.uncommittedFileCount
38344
- });
38345
- return;
38346
- }
38347
- const branch = typeof msg.branch === "string" ? msg.branch : "";
38348
- const message = typeof msg.message === "string" ? msg.message : "";
38349
- const pushAfterCommit = msg.pushAfterCommit === true;
38350
- if (!branch.trim() || !message.trim()) {
38351
- reply({ ok: false, error: "branch and message are required for commit" });
38943
+ await handleSessionGitPushAction(deps, sessionId, reply);
38352
38944
  return;
38353
38945
  }
38354
- const commitRes = await deps.sessionWorktreeManager.commitSession({
38355
- sessionId,
38356
- branch: branch.trim(),
38357
- message: message.trim(),
38358
- push: pushAfterCommit
38359
- });
38360
- if (!commitRes.ok) {
38361
- reply({ ok: false, error: commitRes.error ?? "Commit failed" });
38362
- return;
38363
- }
38364
- const st = await deps.sessionWorktreeManager.getSessionWorkingTreeStatus(sessionId);
38365
- reply({
38366
- ok: true,
38367
- hasUncommittedChanges: st.hasUncommittedChanges,
38368
- hasUnpushedCommits: st.hasUnpushedCommits,
38369
- uncommittedFileCount: st.uncommittedFileCount
38370
- });
38946
+ await handleSessionGitCommitAction(deps, sessionId, msg, reply);
38371
38947
  } catch (e) {
38372
38948
  reply({ ok: false, error: e instanceof Error ? e.message : String(e) });
38373
38949
  }
@@ -38397,7 +38973,7 @@ var handleSessionDiscardedMessage = (msg, deps) => {
38397
38973
  };
38398
38974
 
38399
38975
  // src/routing/handlers/revert-turn-snapshot.ts
38400
- import * as fs39 from "node:fs";
38976
+ import * as fs43 from "node:fs";
38401
38977
  var handleRevertTurnSnapshotMessage = (msg, deps) => {
38402
38978
  const id = typeof msg.id === "string" ? msg.id : "";
38403
38979
  const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
@@ -38410,7 +38986,7 @@ var handleRevertTurnSnapshotMessage = (msg, deps) => {
38410
38986
  const agentBase = sessionWorktreeManager.getSessionWorktreeRootForSession(sessionId) ?? getBridgeRoot();
38411
38987
  const file2 = snapshotFilePath(agentBase, turnId);
38412
38988
  try {
38413
- await fs39.promises.access(file2, fs39.constants.F_OK);
38989
+ await fs43.promises.access(file2, fs43.constants.F_OK);
38414
38990
  } catch {
38415
38991
  sendWsMessage(s, {
38416
38992
  type: "revert_turn_snapshot_result",
@@ -38452,7 +39028,7 @@ var handleDevServersConfig = (msg, deps) => {
38452
39028
  };
38453
39029
 
38454
39030
  // src/git/bridge-git-context.ts
38455
- import * as path42 from "node:path";
39031
+ import * as path49 from "node:path";
38456
39032
 
38457
39033
  // src/git/branches/get-current-branch.ts
38458
39034
  async function getCurrentBranch(repoPath) {
@@ -38502,12 +39078,12 @@ async function listRepoBranchRefs(repoPath) {
38502
39078
  // src/git/bridge-git-context.ts
38503
39079
  function folderNameForRelPath(relPath, bridgeRoot) {
38504
39080
  if (relPath === "." || relPath === "") {
38505
- return path42.basename(path42.resolve(bridgeRoot)) || "repo";
39081
+ return path49.basename(path49.resolve(bridgeRoot)) || "repo";
38506
39082
  }
38507
- return path42.basename(relPath) || relPath;
39083
+ return path49.basename(relPath) || relPath;
38508
39084
  }
38509
39085
  async function discoverGitReposForBridgeContext(bridgeRoot) {
38510
- const root = path42.resolve(bridgeRoot);
39086
+ const root = path49.resolve(bridgeRoot);
38511
39087
  if (await isGitRepoDirectory(root)) {
38512
39088
  const remoteUrl = await getRemoteOriginUrl(root);
38513
39089
  return [{ absolutePath: root, remoteUrl }];
@@ -38515,17 +39091,17 @@ async function discoverGitReposForBridgeContext(bridgeRoot) {
38515
39091
  const [deep, shallow] = await Promise.all([discoverGitReposUnderRoot(root), discoverGitRepos(root)]);
38516
39092
  const byPath = /* @__PURE__ */ new Map();
38517
39093
  for (const repo of [...deep, ...shallow]) {
38518
- byPath.set(path42.resolve(repo.absolutePath), repo);
39094
+ byPath.set(path49.resolve(repo.absolutePath), repo);
38519
39095
  }
38520
39096
  return [...byPath.values()];
38521
39097
  }
38522
39098
  async function getBridgeGitContext(bridgeRoot = getBridgeRoot()) {
38523
- const bridgeResolved = path42.resolve(bridgeRoot);
39099
+ const bridgeResolved = path49.resolve(bridgeRoot);
38524
39100
  const repos = await discoverGitReposForBridgeContext(bridgeResolved);
38525
39101
  const rows = [];
38526
39102
  for (const repo of repos) {
38527
- let rel = path42.relative(bridgeResolved, repo.absolutePath);
38528
- if (rel.startsWith("..") || path42.isAbsolute(rel)) continue;
39103
+ let rel = path49.relative(bridgeResolved, repo.absolutePath);
39104
+ if (rel.startsWith("..") || path49.isAbsolute(rel)) continue;
38529
39105
  const relPath = rel === "" ? "." : rel.replace(/\\/g, "/");
38530
39106
  const currentBranch = await getCurrentBranch(repo.absolutePath);
38531
39107
  const remoteUrl = repo.remoteUrl.trim() || null;
@@ -38540,11 +39116,11 @@ async function getBridgeGitContext(bridgeRoot = getBridgeRoot()) {
38540
39116
  return rows;
38541
39117
  }
38542
39118
  async function listRepoBranchesForBridge(repoRelPath, bridgeRoot = getBridgeRoot()) {
38543
- const bridgeResolved = path42.resolve(bridgeRoot);
39119
+ const bridgeResolved = path49.resolve(bridgeRoot);
38544
39120
  const rel = repoRelPath.trim() === "." ? "" : repoRelPath.trim().replace(/\\/g, "/");
38545
- const repoPath = rel === "" ? bridgeResolved : path42.join(bridgeResolved, rel);
38546
- const resolved = path42.resolve(repoPath);
38547
- if (!resolved.startsWith(bridgeResolved + path42.sep) && resolved !== bridgeResolved) {
39121
+ const repoPath = rel === "" ? bridgeResolved : path49.join(bridgeResolved, rel);
39122
+ const resolved = path49.resolve(repoPath);
39123
+ if (!resolved.startsWith(bridgeResolved + path49.sep) && resolved !== bridgeResolved) {
38548
39124
  return [];
38549
39125
  }
38550
39126
  return listRepoBranchRefs(resolved);
@@ -39128,10 +39704,10 @@ function listCliAgentCapabilityCacheForWorkspace(db, workspaceId) {
39128
39704
  }
39129
39705
 
39130
39706
  // src/agents/capabilities/warmup-agent-capabilities-on-connect.ts
39131
- import * as path44 from "node:path";
39707
+ import * as path51 from "node:path";
39132
39708
 
39133
39709
  // src/agents/capabilities/probe-one-agent-type-for-capabilities.ts
39134
- import * as path43 from "node:path";
39710
+ import * as path50 from "node:path";
39135
39711
  async function probeOneAgentTypeForCapabilities(params) {
39136
39712
  const { agentType, cwd, workspaceId, log: log2, reportAgentCapabilities, bridgeReport = true, shouldContinue } = params;
39137
39713
  const canContinue = () => shouldContinue?.() !== false;
@@ -39169,7 +39745,7 @@ async function probeOneAgentTypeForCapabilities(params) {
39169
39745
  if (!canContinue()) return false;
39170
39746
  handle = await resolved.createClient({
39171
39747
  command: resolved.command,
39172
- cwd: path43.resolve(cwd),
39748
+ cwd: path50.resolve(cwd),
39173
39749
  backendAgentType: agentType,
39174
39750
  sessionMode: "agent",
39175
39751
  persistedAcpSessionId: null,
@@ -39247,7 +39823,7 @@ async function warmupAgentCapabilitiesOnConnect(params) {
39247
39823
  const { workspaceId, log: log2, getWs } = params;
39248
39824
  const isCurrent = beginAgentCapabilityWarmupRun();
39249
39825
  if (!isCurrent()) return;
39250
- const cwd = path44.resolve(getBridgeRoot());
39826
+ const cwd = path51.resolve(getBridgeRoot());
39251
39827
  async function sendBatchFromCache() {
39252
39828
  if (!isCurrent()) return;
39253
39829
  const socket = getWs();
@@ -39353,8 +39929,8 @@ async function callListParentSessionPromptsTool(ctx, cloud, childSessionId, pare
39353
39929
  }
39354
39930
 
39355
39931
  // src/mcp/tools/session-history/fork/tool-names.ts
39356
- var LIST_PARENT_SESSION_PROMPTS_TOOL = "list_parent_session_prompts";
39357
- var GET_PARENT_SESSION_TURN_TRANSCRIPT_TOOL = "get_parent_session_turn_transcript";
39932
+ var LIST_PARENT_SESSION_PROMPTS_TOOL2 = "list_parent_session_prompts";
39933
+ var GET_PARENT_SESSION_TURN_TRANSCRIPT_TOOL2 = "get_parent_session_turn_transcript";
39358
39934
 
39359
39935
  // src/mcp/tools/session-history/fork/call-fork-session-mcp-tool.ts
39360
39936
  async function callForkSessionMcpTool(ctx, cloud, name, args) {
@@ -39371,10 +39947,10 @@ async function callForkSessionMcpTool(ctx, cloud, name, args) {
39371
39947
  return mcpTextResult(parentResolved.error, true);
39372
39948
  }
39373
39949
  const parentSessionId = parentResolved.parentSessionId;
39374
- if (name === LIST_PARENT_SESSION_PROMPTS_TOOL) {
39950
+ if (name === LIST_PARENT_SESSION_PROMPTS_TOOL2) {
39375
39951
  return callListParentSessionPromptsTool(ctx, cloud, childSessionId, parentSessionId);
39376
39952
  }
39377
- if (name === GET_PARENT_SESSION_TURN_TRANSCRIPT_TOOL) {
39953
+ if (name === GET_PARENT_SESSION_TURN_TRANSCRIPT_TOOL2) {
39378
39954
  return callGetParentSessionTurnTranscriptTool(ctx, cloud, childSessionId, args);
39379
39955
  }
39380
39956
  return mcpTextResult(`Unknown fork session tool: ${name}`, true);
@@ -39387,7 +39963,7 @@ var SESSION_ID_SCHEMA = {
39387
39963
  };
39388
39964
  var FORK_SESSION_MCP_TOOL_DEFINITIONS = [
39389
39965
  {
39390
- name: LIST_PARENT_SESSION_PROMPTS_TOOL,
39966
+ name: LIST_PARENT_SESSION_PROMPTS_TOOL2,
39391
39967
  description: "List user prompts (main turns) from the parent session a fork was created from. Requires sessionId. Returns an error if the session has no parent history (not a fork).",
39392
39968
  inputSchema: {
39393
39969
  type: "object",
@@ -39397,7 +39973,7 @@ var FORK_SESSION_MCP_TOOL_DEFINITIONS = [
39397
39973
  }
39398
39974
  },
39399
39975
  {
39400
- name: GET_PARENT_SESSION_TURN_TRANSCRIPT_TOOL,
39976
+ name: GET_PARENT_SESSION_TURN_TRANSCRIPT_TOOL2,
39401
39977
  description: "Full transcript for one parent-session turn, including tool calls and agent messages. Requires sessionId and turnId. Returns an error if the session has no parent history (not a fork).",
39402
39978
  inputSchema: {
39403
39979
  type: "object",
@@ -39413,7 +39989,130 @@ var FORK_SESSION_MCP_TOOL_DEFINITIONS = [
39413
39989
 
39414
39990
  // src/mcp/tools/session-history/fork/is-fork-session-mcp-tool-name.ts
39415
39991
  function isForkSessionMcpToolName(name) {
39416
- return name === LIST_PARENT_SESSION_PROMPTS_TOOL || name === GET_PARENT_SESSION_TURN_TRANSCRIPT_TOOL;
39992
+ return name === LIST_PARENT_SESSION_PROMPTS_TOOL2 || name === GET_PARENT_SESSION_TURN_TRANSCRIPT_TOOL2;
39993
+ }
39994
+
39995
+ // src/mcp/tools/integration-content/tool-names.ts
39996
+ var GET_INTEGRATION_CONTENT_TOOL2 = "get_integration_content";
39997
+
39998
+ // src/mcp/tools/integration-content/cloud/fetch-integration-content.ts
39999
+ async function fetchCloudIntegrationContent(params) {
40000
+ return fetchInternalApiJson({
40001
+ ...params,
40002
+ path: `/internal/sessions/${encodeURIComponent(params.sessionId)}/integrations/content`,
40003
+ errorLabel: "Integration content fetch failed",
40004
+ method: "POST",
40005
+ body: {
40006
+ url: params.url,
40007
+ associateAsSessionTicket: params.associateAsSessionTicket !== false
40008
+ }
40009
+ });
40010
+ }
40011
+
40012
+ // src/mcp/tools/integration-content/format-integration-content-for-mcp.ts
40013
+ function formatIntegrationContentForMcp(result) {
40014
+ if (!result.ok) {
40015
+ const lines2 = [`status: ${result.code}`, `message: ${result.message}`];
40016
+ if (result.integrationType) lines2.push(`integrationType: ${result.integrationType}`);
40017
+ if (result.retryable) lines2.push("retryable: true");
40018
+ lines2.push(...formatSessionTicketAssociationLines(result.sessionTicketAssociation));
40019
+ return lines2.join("\n");
40020
+ }
40021
+ const lines = [
40022
+ formatIntegrationContentBody(result.integrationType, result.content),
40023
+ ...formatSessionTicketAssociationLines(result.sessionTicketAssociation)
40024
+ ];
40025
+ return lines.join("\n");
40026
+ }
40027
+ function formatSessionTicketAssociationLines(association) {
40028
+ if (association.status === "associated") {
40029
+ const title = association.externalTicket.displayTitle?.trim() || association.externalTicket.normalizedUrl;
40030
+ return ["", "--- session ticket ---", "status: associated", `displayTitle: ${title}`];
40031
+ }
40032
+ if (association.status === "failed") {
40033
+ return ["", "--- session ticket ---", "status: failed", `reason: ${association.reason}`];
40034
+ }
40035
+ return ["", "--- session ticket ---", `status: ${association.status}`, `reason: ${association.reason}`];
40036
+ }
40037
+ function formatIntegrationContentBody(integrationType, content) {
40038
+ const lines = [
40039
+ `integrationType: ${integrationType}`,
40040
+ `title: ${content.title}`,
40041
+ `url: ${content.url}`,
40042
+ "",
40043
+ "--- body ---",
40044
+ content.body
40045
+ ];
40046
+ if (content.comments?.length) {
40047
+ lines.push("", "--- comments ---");
40048
+ for (const comment of content.comments) {
40049
+ lines.push(`[${comment.createdAt}] ${comment.author}:`, comment.body, "");
40050
+ }
40051
+ }
40052
+ const threadMessages = content.threadMessages;
40053
+ if (threadMessages?.length) {
40054
+ lines.push("", "--- thread messages ---");
40055
+ for (const msg of threadMessages) {
40056
+ lines.push(`[${msg.ts}] ${msg.displayName}: ${msg.text}`);
40057
+ }
40058
+ }
40059
+ if (content.metadata && Object.keys(content.metadata).length > 0) {
40060
+ lines.push("", "--- metadata ---", JSON.stringify(content.metadata, null, 2));
40061
+ }
40062
+ return lines.join("\n");
40063
+ }
40064
+
40065
+ // src/mcp/tools/integration-content/call-integration-content-mcp-tool.ts
40066
+ async function callIntegrationContentMcpTool(ctx, cloud, name, args) {
40067
+ if (name !== GET_INTEGRATION_CONTENT_TOOL2) {
40068
+ return mcpTextResult(`Unknown integration content tool: ${name}`, true);
40069
+ }
40070
+ const sessionId = ctx.sessionId.trim();
40071
+ if (!sessionId) return mcpTextResult("sessionId is required.", true);
40072
+ const url2 = typeof args.url === "string" ? args.url.trim() : "";
40073
+ if (!url2) return mcpTextResult("url is required.", true);
40074
+ const associateAsSessionTicket = args.associateAsSessionTicket !== false;
40075
+ const fetched = await fetchCloudIntegrationContent({
40076
+ sessionId,
40077
+ url: url2,
40078
+ associateAsSessionTicket,
40079
+ cloudApiBaseUrl: cloud.cloudApiBaseUrl,
40080
+ getCloudAccessToken: cloud.getCloudAccessToken
40081
+ });
40082
+ if (!fetched.ok) return mcpTextResult(fetched.error, true);
40083
+ const text = formatIntegrationContentForMcp(fetched.data);
40084
+ const isError = !fetched.data.ok;
40085
+ return mcpTextResult(text, isError);
40086
+ }
40087
+
40088
+ // src/mcp/tools/integration-content/tool-definitions.ts
40089
+ var SESSION_ID_SCHEMA2 = {
40090
+ type: "string",
40091
+ description: "Id of the current session. Required on every tool call so the workspace integration credentials can be resolved."
40092
+ };
40093
+ var INTEGRATION_CONTENT_MCP_TOOL_DEFINITIONS = [
40094
+ {
40095
+ name: GET_INTEGRATION_CONTENT_TOOL2,
40096
+ description: "Fetch the full content of a supported external integration link (Linear, GitHub, Jira, Notion, Slack thread, Zoom meeting, Asana). Requires sessionId and url. By default, links the URL as this session external ticket when the session has none yet. Pass associateAsSessionTicket: false to fetch content only (e.g. additional context links). Returns an error when the URL is unsupported, the integration is not connected, or content is temporarily unavailable (retry may succeed).",
40097
+ inputSchema: {
40098
+ type: "object",
40099
+ properties: {
40100
+ sessionId: SESSION_ID_SCHEMA2,
40101
+ url: { type: "string", description: "External integration URL to fetch" },
40102
+ associateAsSessionTicket: {
40103
+ type: "boolean",
40104
+ description: "Defaults to true. When true and the session has no linked external ticket yet, links this URL as the session external ticket. Pass false when fetching additional links for context only."
40105
+ }
40106
+ },
40107
+ required: ["sessionId", "url"],
40108
+ additionalProperties: false
40109
+ }
40110
+ }
40111
+ ];
40112
+
40113
+ // src/mcp/tools/integration-content/index.ts
40114
+ function isIntegrationContentMcpToolName(name) {
40115
+ return name === GET_INTEGRATION_CONTENT_TOOL2;
39417
40116
  }
39418
40117
 
39419
40118
  // src/mcp/bridge-mcp-tools.ts
@@ -39426,7 +40125,7 @@ function requireCloud(shared) {
39426
40125
  function createBridgeMcpToolRegistry(shared) {
39427
40126
  const cloud = requireCloud(shared);
39428
40127
  return {
39429
- listTools: async () => [...FORK_SESSION_MCP_TOOL_DEFINITIONS],
40128
+ listTools: async () => [...FORK_SESSION_MCP_TOOL_DEFINITIONS, ...INTEGRATION_CONTENT_MCP_TOOL_DEFINITIONS],
39430
40129
  callTool: async (name, args) => {
39431
40130
  if (isForkSessionMcpToolName(name)) {
39432
40131
  if (!cloud) {
@@ -39444,6 +40143,22 @@ function createBridgeMcpToolRegistry(shared) {
39444
40143
  }
39445
40144
  return callForkSessionMcpTool({ ...shared, sessionId }, cloud, name, args);
39446
40145
  }
40146
+ if (isIntegrationContentMcpToolName(name)) {
40147
+ if (!cloud) {
40148
+ return {
40149
+ content: [{ type: "text", text: "Bridge MCP tools require cloud API credentials." }],
40150
+ isError: true
40151
+ };
40152
+ }
40153
+ const sessionId = typeof args.sessionId === "string" ? args.sessionId.trim() : "";
40154
+ if (!sessionId) {
40155
+ return {
40156
+ content: [{ type: "text", text: "sessionId is required." }],
40157
+ isError: true
40158
+ };
40159
+ }
40160
+ return callIntegrationContentMcpTool({ ...shared, sessionId }, cloud, name, args);
40161
+ }
39447
40162
  throw new Error(`Unknown bridge MCP tool: ${name}`);
39448
40163
  }
39449
40164
  };
@@ -39454,18 +40169,18 @@ import * as http from "node:http";
39454
40169
 
39455
40170
  // src/mcp/bridge-access/read-json-body.ts
39456
40171
  function readJsonBody(req) {
39457
- return new Promise((resolve20, reject) => {
40172
+ return new Promise((resolve26, reject) => {
39458
40173
  const chunks = [];
39459
40174
  req.on("data", (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
39460
40175
  req.on("end", () => {
39461
40176
  try {
39462
40177
  const raw = Buffer.concat(chunks).toString("utf8").trim();
39463
40178
  if (!raw) {
39464
- resolve20({});
40179
+ resolve26({});
39465
40180
  return;
39466
40181
  }
39467
40182
  const parsed = JSON.parse(raw);
39468
- resolve20(parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {});
40183
+ resolve26(parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {});
39469
40184
  } catch (e) {
39470
40185
  reject(e);
39471
40186
  }
@@ -39523,7 +40238,7 @@ function createBridgeAccessRequestHandler(registry2) {
39523
40238
  // src/mcp/bridge-access/start-server.ts
39524
40239
  function startBridgeAccessServer(registry2) {
39525
40240
  const server = http.createServer(createBridgeAccessRequestHandler(registry2));
39526
- return new Promise((resolve20, reject) => {
40241
+ return new Promise((resolve26, reject) => {
39527
40242
  server.once("error", reject);
39528
40243
  server.listen(0, "127.0.0.1", () => {
39529
40244
  const addr = server.address();
@@ -39531,7 +40246,7 @@ function startBridgeAccessServer(registry2) {
39531
40246
  reject(new Error("Bridge access server did not bind"));
39532
40247
  return;
39533
40248
  }
39534
- resolve20({
40249
+ resolve26({
39535
40250
  port: addr.port,
39536
40251
  close: () => new Promise((closeResolve, closeReject) => {
39537
40252
  server.close((err) => err ? closeReject(err) : closeResolve());
@@ -39671,8 +40386,8 @@ async function createBridgeConnection(options) {
39671
40386
  getCloudAccessToken: () => tokens.accessToken
39672
40387
  };
39673
40388
  const identifyReportedPaths = {
39674
- bridgeRootPath: path45.resolve(getBridgeRoot()),
39675
- worktreesRootPath: path45.resolve(worktreesRootPath)
40389
+ bridgeRootPath: path52.resolve(getBridgeRoot()),
40390
+ worktreesRootPath: path52.resolve(worktreesRootPath)
39676
40391
  };
39677
40392
  const { connect } = createMainBridgeWebSocketLifecycle({
39678
40393
  state,