@buildautomaton/cli 0.1.8 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -4065,8 +4065,8 @@ var init_parseUtil = __esm({
4065
4065
  init_errors();
4066
4066
  init_en();
4067
4067
  makeIssue = (params) => {
4068
- const { data, path: path24, errorMaps, issueData } = params;
4069
- const fullPath = [...path24, ...issueData.path || []];
4068
+ const { data, path: path29, errorMaps, issueData } = params;
4069
+ const fullPath = [...path29, ...issueData.path || []];
4070
4070
  const fullIssue = {
4071
4071
  ...issueData,
4072
4072
  path: fullPath
@@ -4374,11 +4374,11 @@ var init_types = __esm({
4374
4374
  init_parseUtil();
4375
4375
  init_util();
4376
4376
  ParseInputLazyPath = class {
4377
- constructor(parent, value, path24, key) {
4377
+ constructor(parent, value, path29, key) {
4378
4378
  this._cachedPath = [];
4379
4379
  this.parent = parent;
4380
4380
  this.data = value;
4381
- this._path = path24;
4381
+ this._path = path29;
4382
4382
  this._key = key;
4383
4383
  }
4384
4384
  get path() {
@@ -7993,10 +7993,10 @@ function assignProp(target, prop, value) {
7993
7993
  configurable: true
7994
7994
  });
7995
7995
  }
7996
- function getElementAtPath(obj, path24) {
7997
- if (!path24)
7996
+ function getElementAtPath(obj, path29) {
7997
+ if (!path29)
7998
7998
  return obj;
7999
- return path24.reduce((acc, key) => acc?.[key], obj);
7999
+ return path29.reduce((acc, key) => acc?.[key], obj);
8000
8000
  }
8001
8001
  function promiseAllObject(promisesObj) {
8002
8002
  const keys = Object.keys(promisesObj);
@@ -8245,11 +8245,11 @@ function aborted(x, startIndex = 0) {
8245
8245
  }
8246
8246
  return false;
8247
8247
  }
8248
- function prefixIssues(path24, issues) {
8248
+ function prefixIssues(path29, issues) {
8249
8249
  return issues.map((iss) => {
8250
8250
  var _a2;
8251
8251
  (_a2 = iss).path ?? (_a2.path = []);
8252
- iss.path.unshift(path24);
8252
+ iss.path.unshift(path29);
8253
8253
  return iss;
8254
8254
  });
8255
8255
  }
@@ -8438,7 +8438,7 @@ function treeifyError(error40, _mapper) {
8438
8438
  return issue2.message;
8439
8439
  };
8440
8440
  const result = { errors: [] };
8441
- const processError = (error41, path24 = []) => {
8441
+ const processError = (error41, path29 = []) => {
8442
8442
  var _a2, _b;
8443
8443
  for (const issue2 of error41.issues) {
8444
8444
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -8448,7 +8448,7 @@ function treeifyError(error40, _mapper) {
8448
8448
  } else if (issue2.code === "invalid_element") {
8449
8449
  processError({ issues: issue2.issues }, issue2.path);
8450
8450
  } else {
8451
- const fullpath = [...path24, ...issue2.path];
8451
+ const fullpath = [...path29, ...issue2.path];
8452
8452
  if (fullpath.length === 0) {
8453
8453
  result.errors.push(mapper(issue2));
8454
8454
  continue;
@@ -8478,9 +8478,9 @@ function treeifyError(error40, _mapper) {
8478
8478
  processError(error40);
8479
8479
  return result;
8480
8480
  }
8481
- function toDotPath(path24) {
8481
+ function toDotPath(path29) {
8482
8482
  const segs = [];
8483
- for (const seg of path24) {
8483
+ for (const seg of path29) {
8484
8484
  if (typeof seg === "number")
8485
8485
  segs.push(`[${seg}]`);
8486
8486
  else if (typeof seg === "symbol")
@@ -21963,10 +21963,10 @@ var require_src2 = __commonJS({
21963
21963
  var fs_1 = __require("fs");
21964
21964
  var debug_1 = __importDefault(require_src());
21965
21965
  var log2 = debug_1.default("@kwsites/file-exists");
21966
- function check2(path24, isFile, isDirectory) {
21967
- log2(`checking %s`, path24);
21966
+ function check2(path29, isFile, isDirectory) {
21967
+ log2(`checking %s`, path29);
21968
21968
  try {
21969
- const stat2 = fs_1.statSync(path24);
21969
+ const stat2 = fs_1.statSync(path29);
21970
21970
  if (stat2.isFile() && isFile) {
21971
21971
  log2(`[OK] path represents a file`);
21972
21972
  return true;
@@ -21986,8 +21986,8 @@ var require_src2 = __commonJS({
21986
21986
  throw e;
21987
21987
  }
21988
21988
  }
21989
- function exists2(path24, type = exports.READABLE) {
21990
- return check2(path24, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
21989
+ function exists2(path29, type = exports.READABLE) {
21990
+ return check2(path29, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
21991
21991
  }
21992
21992
  exports.exists = exists2;
21993
21993
  exports.FILE = 1;
@@ -22426,7 +22426,7 @@ function localAgentErrorSuggestsAuth(agentType, errorText) {
22426
22426
  return hints.some((re) => re.test(String(errorText)));
22427
22427
  }
22428
22428
 
22429
- // src/acp/clients/sdk-stdio-acp-client.ts
22429
+ // src/agents/acp/clients/sdk-stdio-acp-client.ts
22430
22430
  import { spawn } from "node:child_process";
22431
22431
  import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
22432
22432
  import { dirname } from "node:path";
@@ -22488,7 +22488,7 @@ function getBridgeWorkspaceDirectory() {
22488
22488
  return bridgeWorkspaceDirectory;
22489
22489
  }
22490
22490
 
22491
- // src/acp/safe-fs-path.ts
22491
+ // src/agents/acp/safe-fs-path.ts
22492
22492
  import * as path2 from "node:path";
22493
22493
  function resolveSafePathUnderCwd(cwd, filePath) {
22494
22494
  const trimmed2 = filePath.trim();
@@ -22506,7 +22506,7 @@ function toDisplayPathRelativeToCwd(cwd, absolutePath) {
22506
22506
  return rel.split(path2.sep).join("/");
22507
22507
  }
22508
22508
 
22509
- // src/acp/clients/agent-stderr-capture.ts
22509
+ // src/agents/acp/clients/agent-stderr-capture.ts
22510
22510
  var STDERR_CAPTURE_MAX = 48e3;
22511
22511
  function createStderrCapture(child) {
22512
22512
  const chunks = [];
@@ -22553,7 +22553,7 @@ function mergeErrorWithStderr(primary, stderrText) {
22553
22553
  ${s}`;
22554
22554
  }
22555
22555
 
22556
- // src/acp/clients/kiro-sdk-ext-notifications.ts
22556
+ // src/agents/acp/clients/kiro-sdk-ext-notifications.ts
22557
22557
  function createKiroSdkExtNotificationHandler(options) {
22558
22558
  const { onSessionUpdate } = options;
22559
22559
  return async (method, params) => {
@@ -22570,7 +22570,7 @@ function createKiroSdkExtNotificationHandler(options) {
22570
22570
  };
22571
22571
  }
22572
22572
 
22573
- // src/acp/clients/sdk-stdio-ext-notifications.ts
22573
+ // src/agents/acp/clients/sdk-stdio-ext-notifications.ts
22574
22574
  var noopExtNotification = async () => {
22575
22575
  };
22576
22576
  function createSdkStdioExtNotificationHandler(options) {
@@ -22583,7 +22583,7 @@ function createSdkStdioExtNotificationHandler(options) {
22583
22583
  }
22584
22584
  }
22585
22585
 
22586
- // src/acp/clients/sdk-stdio-acp-client.ts
22586
+ // src/agents/acp/clients/sdk-stdio-acp-client.ts
22587
22587
  function formatSpawnError(err, command) {
22588
22588
  if (err.code === "ENOENT") {
22589
22589
  return `Command "${command}" not found. Install the agent (e.g. Cursor CLI) or add it to PATH.`;
@@ -22951,8 +22951,8 @@ function randomSecret() {
22951
22951
  }
22952
22952
  return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
22953
22953
  }
22954
- async function requestPreviewApi(port, secret, method, path24, body) {
22955
- const url2 = `http://127.0.0.1:${port}${path24}`;
22954
+ async function requestPreviewApi(port, secret, method, path29, body) {
22955
+ const url2 = `http://127.0.0.1:${port}${path29}`;
22956
22956
  const headers = {
22957
22957
  [PREVIEW_SECRET_HEADER]: secret,
22958
22958
  "Content-Type": "application/json"
@@ -22964,7 +22964,7 @@ async function requestPreviewApi(port, secret, method, path24, body) {
22964
22964
  });
22965
22965
  const data = await res.json().catch(() => ({}));
22966
22966
  if (!res.ok) {
22967
- throw new Error(data?.error ?? `Preview API ${method} ${path24}: ${res.status}`);
22967
+ throw new Error(data?.error ?? `Preview API ${method} ${path29}: ${res.status}`);
22968
22968
  }
22969
22969
  return data;
22970
22970
  }
@@ -23514,13 +23514,64 @@ function runPendingAuth(options) {
23514
23514
  };
23515
23515
  }
23516
23516
 
23517
- // src/bridge/connection/build-bridge-url.ts
23518
- function buildBridgeUrl(apiUrl, workspaceId, authToken) {
23519
- const base = apiUrl.startsWith("https") ? apiUrl.replace(/^https/, "wss") : apiUrl.replace(/^http/, "ws");
23520
- const params = new URLSearchParams({ workspaceId, token: authToken });
23521
- return `${base}/ws/bridge?${params.toString()}`;
23517
+ // src/bridge/connection/close-bridge-connection.ts
23518
+ async function closeBridgeConnection(state, acpManager, devServerManager, log2) {
23519
+ const say = log2 ?? logImmediate;
23520
+ say("Cleaning up connections\u2026");
23521
+ await new Promise((resolve14) => setImmediate(resolve14));
23522
+ state.closedByUser = true;
23523
+ clearReconnectQuietTimer(state.mainQuiet);
23524
+ clearReconnectQuietTimer(state.firehoseQuiet);
23525
+ if (state.reconnectTimeout != null) {
23526
+ say("Cancelling bridge reconnect timer\u2026");
23527
+ clearTimeout(state.reconnectTimeout);
23528
+ state.reconnectTimeout = null;
23529
+ }
23530
+ if (state.firehoseReconnectTimeout != null) {
23531
+ say("Cancelling preview tunnel reconnect timer\u2026");
23532
+ clearTimeout(state.firehoseReconnectTimeout);
23533
+ state.firehoseReconnectTimeout = null;
23534
+ }
23535
+ if (state.firehoseHandle) {
23536
+ say("Closing preview tunnel (local HTTP proxy and dev logs)\u2026");
23537
+ state.firehoseHandle.close();
23538
+ state.firehoseHandle = null;
23539
+ }
23540
+ say("Disconnecting local agent\u2026");
23541
+ acpManager.disconnect();
23542
+ if (state.currentWs) {
23543
+ say("Closing bridge connection to the cloud\u2026");
23544
+ state.currentWs.removeAllListeners();
23545
+ const wsState = state.currentWs.readyState;
23546
+ if (wsState === 1 || wsState === 2) {
23547
+ state.currentWs.close();
23548
+ } else {
23549
+ state.currentWs.on("error", () => {
23550
+ });
23551
+ state.currentWs.on("close", () => {
23552
+ });
23553
+ }
23554
+ state.currentWs = null;
23555
+ }
23556
+ if (devServerManager) {
23557
+ say("Stopping local dev server processes\u2026");
23558
+ await devServerManager.shutdownAllGraceful();
23559
+ }
23560
+ say("Shutdown complete.");
23522
23561
  }
23523
23562
 
23563
+ // src/git/session-git-queue.ts
23564
+ import { execFile as execFile2 } from "node:child_process";
23565
+ import { readFile, stat } from "node:fs/promises";
23566
+ import { promisify as promisify2 } from "node:util";
23567
+ import * as path6 from "node:path";
23568
+
23569
+ // src/git/pre-turn-snapshot.ts
23570
+ import * as fs3 from "node:fs";
23571
+ import * as path5 from "node:path";
23572
+ import { execFile } from "node:child_process";
23573
+ import { promisify } from "node:util";
23574
+
23524
23575
  // src/git/discover-repos.ts
23525
23576
  import * as fs2 from "node:fs";
23526
23577
  import * as path4 from "node:path";
@@ -23562,8 +23613,8 @@ function pathspec(...paths) {
23562
23613
  cache.set(key, paths);
23563
23614
  return key;
23564
23615
  }
23565
- function isPathSpec(path24) {
23566
- return path24 instanceof String && cache.has(path24);
23616
+ function isPathSpec(path29) {
23617
+ return path29 instanceof String && cache.has(path29);
23567
23618
  }
23568
23619
  function toPaths(pathSpec) {
23569
23620
  return cache.get(pathSpec) || [];
@@ -23652,8 +23703,8 @@ function toLinesWithContent(input = "", trimmed2 = true, separator = "\n") {
23652
23703
  function forEachLineWithContent(input, callback) {
23653
23704
  return toLinesWithContent(input, true).map((line) => callback(line));
23654
23705
  }
23655
- function folderExists(path24) {
23656
- return (0, import_file_exists.exists)(path24, import_file_exists.FOLDER);
23706
+ function folderExists(path29) {
23707
+ return (0, import_file_exists.exists)(path29, import_file_exists.FOLDER);
23657
23708
  }
23658
23709
  function append(target, item) {
23659
23710
  if (Array.isArray(target)) {
@@ -24057,8 +24108,8 @@ function checkIsRepoRootTask() {
24057
24108
  commands,
24058
24109
  format: "utf-8",
24059
24110
  onError,
24060
- parser(path24) {
24061
- return /^\.(git)?$/.test(path24.trim());
24111
+ parser(path29) {
24112
+ return /^\.(git)?$/.test(path29.trim());
24062
24113
  }
24063
24114
  };
24064
24115
  }
@@ -24492,11 +24543,11 @@ function parseGrep(grep) {
24492
24543
  const paths = /* @__PURE__ */ new Set();
24493
24544
  const results = {};
24494
24545
  forEachLineWithContent(grep, (input) => {
24495
- const [path24, line, preview] = input.split(NULL);
24496
- paths.add(path24);
24497
- (results[path24] = results[path24] || []).push({
24546
+ const [path29, line, preview] = input.split(NULL);
24547
+ paths.add(path29);
24548
+ (results[path29] = results[path29] || []).push({
24498
24549
  line: asNumber(line),
24499
- path: path24,
24550
+ path: path29,
24500
24551
  preview
24501
24552
  });
24502
24553
  });
@@ -25261,14 +25312,14 @@ var init_hash_object = __esm2({
25261
25312
  init_task();
25262
25313
  }
25263
25314
  });
25264
- function parseInit(bare, path24, text) {
25315
+ function parseInit(bare, path29, text) {
25265
25316
  const response = String(text).trim();
25266
25317
  let result;
25267
25318
  if (result = initResponseRegex.exec(response)) {
25268
- return new InitSummary(bare, path24, false, result[1]);
25319
+ return new InitSummary(bare, path29, false, result[1]);
25269
25320
  }
25270
25321
  if (result = reInitResponseRegex.exec(response)) {
25271
- return new InitSummary(bare, path24, true, result[1]);
25322
+ return new InitSummary(bare, path29, true, result[1]);
25272
25323
  }
25273
25324
  let gitDir = "";
25274
25325
  const tokens = response.split(" ");
@@ -25279,7 +25330,7 @@ function parseInit(bare, path24, text) {
25279
25330
  break;
25280
25331
  }
25281
25332
  }
25282
- return new InitSummary(bare, path24, /^re/i.test(response), gitDir);
25333
+ return new InitSummary(bare, path29, /^re/i.test(response), gitDir);
25283
25334
  }
25284
25335
  var InitSummary;
25285
25336
  var initResponseRegex;
@@ -25288,9 +25339,9 @@ var init_InitSummary = __esm2({
25288
25339
  "src/lib/responses/InitSummary.ts"() {
25289
25340
  "use strict";
25290
25341
  InitSummary = class {
25291
- constructor(bare, path24, existing, gitDir) {
25342
+ constructor(bare, path29, existing, gitDir) {
25292
25343
  this.bare = bare;
25293
- this.path = path24;
25344
+ this.path = path29;
25294
25345
  this.existing = existing;
25295
25346
  this.gitDir = gitDir;
25296
25347
  }
@@ -25302,7 +25353,7 @@ var init_InitSummary = __esm2({
25302
25353
  function hasBareCommand(command) {
25303
25354
  return command.includes(bareCommand);
25304
25355
  }
25305
- function initTask(bare = false, path24, customArgs) {
25356
+ function initTask(bare = false, path29, customArgs) {
25306
25357
  const commands = ["init", ...customArgs];
25307
25358
  if (bare && !hasBareCommand(commands)) {
25308
25359
  commands.splice(1, 0, bareCommand);
@@ -25311,7 +25362,7 @@ function initTask(bare = false, path24, customArgs) {
25311
25362
  commands,
25312
25363
  format: "utf-8",
25313
25364
  parser(text) {
25314
- return parseInit(commands.includes("--bare"), path24, text);
25365
+ return parseInit(commands.includes("--bare"), path29, text);
25315
25366
  }
25316
25367
  };
25317
25368
  }
@@ -26127,12 +26178,12 @@ var init_FileStatusSummary = __esm2({
26127
26178
  "use strict";
26128
26179
  fromPathRegex = /^(.+)\0(.+)$/;
26129
26180
  FileStatusSummary = class {
26130
- constructor(path24, index, working_dir) {
26131
- this.path = path24;
26181
+ constructor(path29, index, working_dir) {
26182
+ this.path = path29;
26132
26183
  this.index = index;
26133
26184
  this.working_dir = working_dir;
26134
26185
  if (index === "R" || working_dir === "R") {
26135
- const detail = fromPathRegex.exec(path24) || [null, path24, path24];
26186
+ const detail = fromPathRegex.exec(path29) || [null, path29, path29];
26136
26187
  this.from = detail[2] || "";
26137
26188
  this.path = detail[1] || "";
26138
26189
  }
@@ -26163,14 +26214,14 @@ function splitLine(result, lineStr) {
26163
26214
  default:
26164
26215
  return;
26165
26216
  }
26166
- function data(index, workingDir, path24) {
26217
+ function data(index, workingDir, path29) {
26167
26218
  const raw = `${index}${workingDir}`;
26168
26219
  const handler = parsers6.get(raw);
26169
26220
  if (handler) {
26170
- handler(result, path24);
26221
+ handler(result, path29);
26171
26222
  }
26172
26223
  if (raw !== "##" && raw !== "!!") {
26173
- result.files.push(new FileStatusSummary(path24, index, workingDir));
26224
+ result.files.push(new FileStatusSummary(path29, index, workingDir));
26174
26225
  }
26175
26226
  }
26176
26227
  }
@@ -26479,9 +26530,9 @@ var init_simple_git_api = __esm2({
26479
26530
  next
26480
26531
  );
26481
26532
  }
26482
- hashObject(path24, write) {
26533
+ hashObject(path29, write) {
26483
26534
  return this._runTask(
26484
- hashObjectTask(path24, write === true),
26535
+ hashObjectTask(path29, write === true),
26485
26536
  trailingFunctionArgument(arguments)
26486
26537
  );
26487
26538
  }
@@ -26834,8 +26885,8 @@ var init_branch = __esm2({
26834
26885
  }
26835
26886
  });
26836
26887
  function toPath(input) {
26837
- const path24 = input.trim().replace(/^["']|["']$/g, "");
26838
- return path24 && normalize2(path24);
26888
+ const path29 = input.trim().replace(/^["']|["']$/g, "");
26889
+ return path29 && normalize2(path29);
26839
26890
  }
26840
26891
  var parseCheckIgnore;
26841
26892
  var init_CheckIgnore = __esm2({
@@ -27149,8 +27200,8 @@ __export2(sub_module_exports, {
27149
27200
  subModuleTask: () => subModuleTask,
27150
27201
  updateSubModuleTask: () => updateSubModuleTask
27151
27202
  });
27152
- function addSubModuleTask(repo, path24) {
27153
- return subModuleTask(["add", repo, path24]);
27203
+ function addSubModuleTask(repo, path29) {
27204
+ return subModuleTask(["add", repo, path29]);
27154
27205
  }
27155
27206
  function initSubModuleTask(customArgs) {
27156
27207
  return subModuleTask(["init", ...customArgs]);
@@ -27483,8 +27534,8 @@ var require_git = __commonJS2({
27483
27534
  }
27484
27535
  return this._runTask(straightThroughStringTask2(command, this._trimmed), next);
27485
27536
  };
27486
- Git2.prototype.submoduleAdd = function(repo, path24, then) {
27487
- return this._runTask(addSubModuleTask2(repo, path24), trailingFunctionArgument2(arguments));
27537
+ Git2.prototype.submoduleAdd = function(repo, path29, then) {
27538
+ return this._runTask(addSubModuleTask2(repo, path29), trailingFunctionArgument2(arguments));
27488
27539
  };
27489
27540
  Git2.prototype.submoduleUpdate = function(args, then) {
27490
27541
  return this._runTask(
@@ -28160,84 +28211,7 @@ async function discoverGitReposUnderRoot(rootAbs) {
28160
28211
  return out;
28161
28212
  }
28162
28213
 
28163
- // src/bridge/connection/report-git-repos.ts
28164
- function reportGitRepos(getWs, log2) {
28165
- setImmediate(() => {
28166
- discoverGitRepos().then((repos) => {
28167
- if (repos.length > 0) {
28168
- const socket = getWs();
28169
- if (socket) {
28170
- sendWsMessage(socket, {
28171
- type: "git_repos",
28172
- repos: repos.map((r) => ({ absolutePath: r.absolutePath, remoteUrl: r.remoteUrl }))
28173
- });
28174
- }
28175
- }
28176
- }).catch((err) => {
28177
- log2(
28178
- `[Bridge service] Git repository discovery failed: ${err instanceof Error ? err.message : String(err)}`
28179
- );
28180
- });
28181
- });
28182
- }
28183
-
28184
- // src/bridge/connection/close-bridge-connection.ts
28185
- async function closeBridgeConnection(state, acpManager, devServerManager, log2) {
28186
- const say = log2 ?? logImmediate;
28187
- say("Cleaning up connections\u2026");
28188
- await new Promise((resolve14) => setImmediate(resolve14));
28189
- state.closedByUser = true;
28190
- clearReconnectQuietTimer(state.mainQuiet);
28191
- clearReconnectQuietTimer(state.firehoseQuiet);
28192
- if (state.reconnectTimeout != null) {
28193
- say("Cancelling bridge reconnect timer\u2026");
28194
- clearTimeout(state.reconnectTimeout);
28195
- state.reconnectTimeout = null;
28196
- }
28197
- if (state.firehoseReconnectTimeout != null) {
28198
- say("Cancelling preview tunnel reconnect timer\u2026");
28199
- clearTimeout(state.firehoseReconnectTimeout);
28200
- state.firehoseReconnectTimeout = null;
28201
- }
28202
- if (state.firehoseHandle) {
28203
- say("Closing preview tunnel (local HTTP proxy and dev logs)\u2026");
28204
- state.firehoseHandle.close();
28205
- state.firehoseHandle = null;
28206
- }
28207
- say("Disconnecting local agent\u2026");
28208
- acpManager.disconnect();
28209
- if (state.currentWs) {
28210
- say("Closing bridge connection to the cloud\u2026");
28211
- state.currentWs.removeAllListeners();
28212
- const wsState = state.currentWs.readyState;
28213
- if (wsState === 1 || wsState === 2) {
28214
- state.currentWs.close();
28215
- } else {
28216
- state.currentWs.on("error", () => {
28217
- });
28218
- state.currentWs.on("close", () => {
28219
- });
28220
- }
28221
- state.currentWs = null;
28222
- }
28223
- if (devServerManager) {
28224
- say("Stopping local dev server processes\u2026");
28225
- await devServerManager.shutdownAllGraceful();
28226
- }
28227
- say("Shutdown complete.");
28228
- }
28229
-
28230
- // src/git/session-git-queue.ts
28231
- import { execFile as execFile2 } from "node:child_process";
28232
- import { readFile, stat } from "node:fs/promises";
28233
- import { promisify as promisify2 } from "node:util";
28234
- import * as path6 from "node:path";
28235
-
28236
28214
  // src/git/pre-turn-snapshot.ts
28237
- import * as fs3 from "node:fs";
28238
- import * as path5 from "node:path";
28239
- import { execFile } from "node:child_process";
28240
- import { promisify } from "node:util";
28241
28215
  var execFileAsync = promisify(execFile);
28242
28216
  function snapshotsDirForCwd(agentCwd) {
28243
28217
  return path5.join(agentCwd, ".buildautomaton", "snapshots");
@@ -28421,7 +28395,7 @@ async function collectTurnGitDiffFromPreTurnSnapshot(options) {
28421
28395
  }
28422
28396
  }
28423
28397
 
28424
- // src/acp/send-prompt-to-agent.ts
28398
+ // src/agents/acp/send-prompt-to-agent.ts
28425
28399
  async function sendPromptToAgent(options) {
28426
28400
  const {
28427
28401
  handle,
@@ -28431,7 +28405,7 @@ async function sendPromptToAgent(options) {
28431
28405
  runId,
28432
28406
  agentType,
28433
28407
  agentCwd,
28434
- sendResult,
28408
+ sendResult: sendResult2,
28435
28409
  sendSessionUpdate,
28436
28410
  log: log2
28437
28411
  } = options;
@@ -28455,7 +28429,7 @@ async function sendPromptToAgent(options) {
28455
28429
  });
28456
28430
  }
28457
28431
  const errStr = typeof result.error === "string" ? result.error : void 0;
28458
- sendResult({
28432
+ sendResult2({
28459
28433
  type: "prompt_result",
28460
28434
  id: promptId,
28461
28435
  ...sessionId ? { sessionId } : {},
@@ -28469,7 +28443,7 @@ async function sendPromptToAgent(options) {
28469
28443
  } catch (err) {
28470
28444
  const errMsg = err instanceof Error ? err.message : String(err);
28471
28445
  log2(`[Agent] Send failed: ${errMsg}`);
28472
- sendResult({
28446
+ sendResult2({
28473
28447
  type: "prompt_result",
28474
28448
  id: promptId,
28475
28449
  ...sessionId ? { sessionId } : {},
@@ -28481,7 +28455,7 @@ async function sendPromptToAgent(options) {
28481
28455
  }
28482
28456
  }
28483
28457
 
28484
- // src/acp/ensure-acp-client.ts
28458
+ // src/agents/acp/ensure-acp-client.ts
28485
28459
  import * as fs4 from "node:fs";
28486
28460
  import * as path9 from "node:path";
28487
28461
 
@@ -28495,7 +28469,7 @@ function errorMessage(err) {
28495
28469
  return String(err);
28496
28470
  }
28497
28471
 
28498
- // src/acp/clients/claude-code-acp-client.ts
28472
+ // src/agents/acp/clients/claude-code-acp-client.ts
28499
28473
  var claude_code_acp_client_exports = {};
28500
28474
  __export(claude_code_acp_client_exports, {
28501
28475
  BACKEND_LOCAL_AGENT_TYPE: () => BACKEND_LOCAL_AGENT_TYPE,
@@ -28506,7 +28480,7 @@ __export(claude_code_acp_client_exports, {
28506
28480
  import { execFile as execFile4 } from "node:child_process";
28507
28481
  import { promisify as promisify4 } from "node:util";
28508
28482
 
28509
- // src/acp/clients/detect-command-on-path.ts
28483
+ // src/agents/acp/clients/detect-command-on-path.ts
28510
28484
  import { execFile as execFile3 } from "node:child_process";
28511
28485
  import { promisify as promisify3 } from "node:util";
28512
28486
  var execFileAsync3 = promisify3(execFile3);
@@ -28519,7 +28493,7 @@ async function isCommandOnPath(command, timeoutMs = 4e3) {
28519
28493
  }
28520
28494
  }
28521
28495
 
28522
- // src/acp/clients/claude-code-acp-client.ts
28496
+ // src/agents/acp/clients/claude-code-acp-client.ts
28523
28497
  var execFileAsync4 = promisify4(execFile4);
28524
28498
  var BACKEND_LOCAL_AGENT_TYPE = "claude-code";
28525
28499
  async function detectLocalAgentPresence() {
@@ -28547,7 +28521,7 @@ async function createClaudeCodeAcpClient(options) {
28547
28521
  });
28548
28522
  }
28549
28523
 
28550
- // src/acp/clients/codex-acp-client.ts
28524
+ // src/agents/acp/clients/codex-acp-client.ts
28551
28525
  var codex_acp_client_exports = {};
28552
28526
  __export(codex_acp_client_exports, {
28553
28527
  BACKEND_LOCAL_AGENT_TYPE: () => BACKEND_LOCAL_AGENT_TYPE2,
@@ -28575,7 +28549,7 @@ async function createCodexAcpClient(options) {
28575
28549
  return createSdkStdioAcpClient({ ...options, command });
28576
28550
  }
28577
28551
 
28578
- // src/acp/clients/cursor-acp-client.ts
28552
+ // src/agents/acp/clients/cursor-acp-client.ts
28579
28553
  var cursor_acp_client_exports = {};
28580
28554
  __export(cursor_acp_client_exports, {
28581
28555
  BACKEND_LOCAL_AGENT_TYPE: () => BACKEND_LOCAL_AGENT_TYPE3,
@@ -28588,7 +28562,7 @@ import { dirname as dirname2 } from "node:path";
28588
28562
  import { spawn as spawn4 } from "node:child_process";
28589
28563
  import * as readline from "node:readline";
28590
28564
 
28591
- // src/acp/format-session-update-kind-for-log.ts
28565
+ // src/agents/acp/format-session-update-kind-for-log.ts
28592
28566
  var SESSION_UPDATE_KIND_LABELS = {
28593
28567
  tool_call: "Tool call",
28594
28568
  tool_call_update: "Tool call status",
@@ -28601,7 +28575,7 @@ function formatSessionUpdateKindForLog(kind) {
28601
28575
  return kind.split("_").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(" ");
28602
28576
  }
28603
28577
 
28604
- // src/acp/clients/cursor-acp-client.ts
28578
+ // src/agents/acp/clients/cursor-acp-client.ts
28605
28579
  var FS_READ_METHODS = /* @__PURE__ */ new Set(["fs/read_text_file", "fs/readTextFile"]);
28606
28580
  var FS_WRITE_METHODS = /* @__PURE__ */ new Set(["fs/write_text_file", "fs/writeTextFile"]);
28607
28581
  function formatSpawnError2(err, command) {
@@ -28922,7 +28896,7 @@ async function detectLocalAgentPresence3() {
28922
28896
  return isCommandOnPath("agent");
28923
28897
  }
28924
28898
 
28925
- // src/acp/clients/kiro-acp-client.ts
28899
+ // src/agents/acp/clients/kiro-acp-client.ts
28926
28900
  var kiro_acp_client_exports = {};
28927
28901
  __export(kiro_acp_client_exports, {
28928
28902
  BACKEND_LOCAL_AGENT_TYPE: () => BACKEND_LOCAL_AGENT_TYPE4,
@@ -28953,7 +28927,7 @@ async function createKiroAcpClient(options) {
28953
28927
  return createSdkStdioAcpClient({ ...options, command });
28954
28928
  }
28955
28929
 
28956
- // src/acp/resolve-agent-command.ts
28930
+ // src/agents/acp/resolve-agent-command.ts
28957
28931
  var AGENT_TYPE_DEFAULT_COMMANDS = {
28958
28932
  [BACKEND_LOCAL_AGENT_TYPE3]: ["agent", "acp"],
28959
28933
  [BACKEND_LOCAL_AGENT_TYPE2]: [...DEFAULT_CODEX_ACP_COMMAND],
@@ -29022,7 +28996,7 @@ function resolveAgentCommand(preferredAgentType) {
29022
28996
  };
29023
28997
  }
29024
28998
 
29025
- // src/acp/session-file-change-path-kind.ts
28999
+ // src/agents/acp/session-file-change-path-kind.ts
29026
29000
  import { execFileSync as execFileSync3 } from "node:child_process";
29027
29001
  import { existsSync, statSync } from "node:fs";
29028
29002
 
@@ -29043,7 +29017,7 @@ function getGitRepoRootSync(startDir) {
29043
29017
  }
29044
29018
  }
29045
29019
 
29046
- // src/acp/workspace-files.ts
29020
+ // src/agents/acp/workspace-files.ts
29047
29021
  import { execFileSync as execFileSync2 } from "node:child_process";
29048
29022
  import { readFileSync as readFileSync4 } from "node:fs";
29049
29023
  import * as path8 from "node:path";
@@ -29115,7 +29089,7 @@ function readGitHeadBlob(cwd, displayPath) {
29115
29089
  }
29116
29090
  }
29117
29091
 
29118
- // src/acp/session-file-change-path-kind.ts
29092
+ // src/agents/acp/session-file-change-path-kind.ts
29119
29093
  function gitHeadPathObjectType(cwd, displayPath) {
29120
29094
  if (!displayPath || displayPath.includes("..")) return null;
29121
29095
  const gitRoot = getGitRepoRootSync(cwd);
@@ -29147,7 +29121,7 @@ function getSessionFileChangeDirectoryFlags(cwd, displayPath) {
29147
29121
  return { isDirectory: false, directoryRemoved: false };
29148
29122
  }
29149
29123
 
29150
- // src/acp/hooks/bridge-on-file-change.ts
29124
+ // src/agents/acp/hooks/bridge-on-file-change.ts
29151
29125
  function createBridgeOnFileChange(opts) {
29152
29126
  const { routing, getSendSessionUpdate, log: log2 } = opts;
29153
29127
  return (evt) => {
@@ -29180,7 +29154,7 @@ function createBridgeOnFileChange(opts) {
29180
29154
  };
29181
29155
  }
29182
29156
 
29183
- // src/acp/hooks/bridge-on-request.ts
29157
+ // src/agents/acp/hooks/bridge-on-request.ts
29184
29158
  function createBridgeOnRequest(opts) {
29185
29159
  const { routing, getSendRequest, log: log2 } = opts;
29186
29160
  return (request) => {
@@ -29211,7 +29185,7 @@ function createBridgeOnRequest(opts) {
29211
29185
  };
29212
29186
  }
29213
29187
 
29214
- // src/acp/hooks/extract-acp-file-diffs-from-update/paths-and-text.ts
29188
+ // src/agents/acp/hooks/extract-acp-file-diffs-from-update/paths-and-text.ts
29215
29189
  import { fileURLToPath } from "node:url";
29216
29190
  function readOptionalTextField(v) {
29217
29191
  if (v === null || v === void 0) return "";
@@ -29237,7 +29211,7 @@ function extractDiffPath(o) {
29237
29211
  return null;
29238
29212
  }
29239
29213
 
29240
- // src/acp/hooks/extract-acp-file-diffs-from-update/push-diff.ts
29214
+ // src/agents/acp/hooks/extract-acp-file-diffs-from-update/push-diff.ts
29241
29215
  function pushDiffIfComplete(o, cwd, out) {
29242
29216
  const t = o.type;
29243
29217
  if (typeof t !== "string" || t.toLowerCase() !== "diff") return;
@@ -29250,7 +29224,7 @@ function pushDiffIfComplete(o, cwd, out) {
29250
29224
  out.push({ path: resolved.display, oldText, newText });
29251
29225
  }
29252
29226
 
29253
- // src/acp/hooks/extract-acp-file-diffs-from-update/walk-update-for-diffs.ts
29227
+ // src/agents/acp/hooks/extract-acp-file-diffs-from-update/walk-update-for-diffs.ts
29254
29228
  var NEST_KEYS = [
29255
29229
  "content",
29256
29230
  "contents",
@@ -29289,7 +29263,7 @@ function walkValue(value, cwd, depth, out) {
29289
29263
  }
29290
29264
  }
29291
29265
 
29292
- // src/acp/hooks/extract-acp-file-diffs-from-update/extract.ts
29266
+ // src/agents/acp/hooks/extract-acp-file-diffs-from-update/extract.ts
29293
29267
  function extractAcpFileDiffsFromUpdate(update, cwd) {
29294
29268
  if (!update || typeof update !== "object") return [];
29295
29269
  const u = update;
@@ -29303,7 +29277,7 @@ function extractAcpFileDiffsFromUpdate(update, cwd) {
29303
29277
  return [...byPath.values()];
29304
29278
  }
29305
29279
 
29306
- // src/acp/hooks/extract-tool-target-paths.ts
29280
+ // src/agents/acp/hooks/extract-tool-target-paths.ts
29307
29281
  function addPath(cwd, raw, out) {
29308
29282
  if (typeof raw !== "string") return;
29309
29283
  const trimmed2 = raw.trim();
@@ -29381,10 +29355,10 @@ function extractToolTargetDisplayPaths(update, cwd) {
29381
29355
  return [...out];
29382
29356
  }
29383
29357
 
29384
- // src/acp/hooks/bridge-on-session-update/constants.ts
29358
+ // src/agents/acp/hooks/bridge-on-session-update/constants.ts
29385
29359
  var PATH_SNAPSHOT_DEBOUNCE_MS = 500;
29386
29360
 
29387
- // src/acp/hooks/bridge-on-session-update/tool-key.ts
29361
+ // src/agents/acp/hooks/bridge-on-session-update/tool-key.ts
29388
29362
  function getToolCallId(params) {
29389
29363
  if (!params || typeof params !== "object") return "";
29390
29364
  const u = params;
@@ -29406,7 +29380,7 @@ function accumulateToolPaths(toolKey, paths, acc) {
29406
29380
  for (const p of paths) s.add(p);
29407
29381
  }
29408
29382
 
29409
- // src/acp/hooks/bridge-on-session-update/path-snapshot-tracker.ts
29383
+ // src/agents/acp/hooks/bridge-on-session-update/path-snapshot-tracker.ts
29410
29384
  var PathSnapshotTracker = class {
29411
29385
  lastSeenRunId;
29412
29386
  anonToolSeq = 0;
@@ -29528,7 +29502,7 @@ var PathSnapshotTracker = class {
29528
29502
  }
29529
29503
  };
29530
29504
 
29531
- // src/acp/hooks/bridge-on-session-update/send-structured-file-changes.ts
29505
+ // src/agents/acp/hooks/bridge-on-session-update/send-structured-file-changes.ts
29532
29506
  function sendExtractedDiffsAsSessionFileChanges(diffs, send, cwd, sessionId, runId, sentPaths, log2) {
29533
29507
  for (const d of diffs) {
29534
29508
  try {
@@ -29578,7 +29552,7 @@ function sendGitHeadVsWorkspaceForToolPaths(mergedPaths, sentPaths, send, cwd, s
29578
29552
  }
29579
29553
  }
29580
29554
 
29581
- // src/acp/hooks/bridge-on-session-update/create-bridge-on-session-update.ts
29555
+ // src/agents/acp/hooks/bridge-on-session-update/create-bridge-on-session-update.ts
29582
29556
  function createBridgeOnSessionUpdate(opts) {
29583
29557
  const { routing, getSendSessionUpdate, log: log2 } = opts;
29584
29558
  const pathTracker = new PathSnapshotTracker();
@@ -29652,7 +29626,7 @@ function createBridgeOnSessionUpdate(opts) {
29652
29626
  };
29653
29627
  }
29654
29628
 
29655
- // src/acp/hooks/build-acp-session-bridge-hooks.ts
29629
+ // src/agents/acp/hooks/build-acp-session-bridge-hooks.ts
29656
29630
  function buildAcpSessionBridgeHooks(opts) {
29657
29631
  return {
29658
29632
  onFileChange: createBridgeOnFileChange(opts),
@@ -29661,7 +29635,7 @@ function buildAcpSessionBridgeHooks(opts) {
29661
29635
  };
29662
29636
  }
29663
29637
 
29664
- // src/acp/ensure-acp-client.ts
29638
+ // src/agents/acp/ensure-acp-client.ts
29665
29639
  async function ensureAcpClient(options) {
29666
29640
  const { state, preferredAgentType, mode, cwd, routing, sendSessionUpdate, sendRequest, log: log2 } = options;
29667
29641
  const targetCwd = path9.resolve(
@@ -29747,7 +29721,7 @@ async function ensureAcpClient(options) {
29747
29721
  return state.acpStartPromise;
29748
29722
  }
29749
29723
 
29750
- // src/acp/create-acp-manager.ts
29724
+ // src/agents/acp/create-acp-manager.ts
29751
29725
  async function createAcpManager(options) {
29752
29726
  const { log: log2 } = options;
29753
29727
  const state = {
@@ -29783,7 +29757,7 @@ async function createAcpManager(options) {
29783
29757
  mode,
29784
29758
  agentType,
29785
29759
  cwd,
29786
- sendResult,
29760
+ sendResult: sendResult2,
29787
29761
  sendSessionUpdate
29788
29762
  } = opts;
29789
29763
  const preferredForPrompt = agentType ?? backendFallbackAgentType ?? null;
@@ -29807,7 +29781,7 @@ async function createAcpManager(options) {
29807
29781
  const evaluated = Boolean(preferredForPrompt && errMsg.trim());
29808
29782
  const suggestsAuth = evaluated ? localAgentErrorSuggestsAuth(preferredForPrompt, errMsg) : false;
29809
29783
  const auth = suggestsAuth && preferredForPrompt ? { agentAuthRequired: true, agentType: preferredForPrompt } : {};
29810
- sendResult({
29784
+ sendResult2({
29811
29785
  type: "prompt_result",
29812
29786
  id: promptId,
29813
29787
  ...sessionId ? { sessionId } : {},
@@ -29827,7 +29801,7 @@ async function createAcpManager(options) {
29827
29801
  await handle.cancel?.();
29828
29802
  } catch {
29829
29803
  }
29830
- sendResult({
29804
+ sendResult2({
29831
29805
  type: "prompt_result",
29832
29806
  id: promptId,
29833
29807
  ...sessionId ? { sessionId } : {},
@@ -29846,7 +29820,7 @@ async function createAcpManager(options) {
29846
29820
  runId,
29847
29821
  agentType: preferredForPrompt,
29848
29822
  agentCwd: cwd,
29849
- sendResult,
29823
+ sendResult: sendResult2,
29850
29824
  sendSessionUpdate,
29851
29825
  log: log2
29852
29826
  });
@@ -29894,1375 +29868,633 @@ async function createAcpManager(options) {
29894
29868
  };
29895
29869
  }
29896
29870
 
29897
- // src/bridge/routing/handlers/auth-token.ts
29898
- var handleAuthToken = (msg, { log: log2 }) => {
29899
- if (typeof msg.token !== "string") return;
29900
- log2("Received auth token. Save it for future runs:");
29901
- log2(` export BUILDAMATON_AUTH_TOKEN="${msg.token}"`);
29902
- };
29871
+ // src/worktrees/session-worktree-manager.ts
29872
+ import * as path13 from "node:path";
29873
+ import os3 from "node:os";
29903
29874
 
29904
- // src/bridge/routing/handlers/bridge-identified.ts
29905
- var handleBridgeIdentified = (msg, deps) => {
29906
- if (typeof msg.bridgeName !== "string") return;
29907
- deps.onBridgeIdentified(
29908
- msg
29909
- );
29910
- setImmediate(() => {
29911
- void (async () => {
29912
- try {
29913
- await deps.reportAutoDetectedAgents?.();
29914
- } catch (e) {
29915
- deps.log(
29916
- `[Bridge service] Auto-detect agents failed: ${e instanceof Error ? e.message : String(e)}`
29917
- );
29918
- }
29919
- })();
29920
- });
29921
- setImmediate(() => {
29922
- try {
29923
- deps.sendLocalSkillsReport?.();
29924
- } catch (e) {
29925
- deps.log(
29926
- `[Bridge service] Local skills report failed: ${e instanceof Error ? e.message : String(e)}`
29927
- );
29928
- }
29929
- });
29930
- };
29875
+ // src/worktrees/prepare-new-session-worktrees.ts
29876
+ import * as fs6 from "node:fs";
29877
+ import * as path11 from "node:path";
29931
29878
 
29932
- // src/acp/from-bridge/handle-bridge-agent-config.ts
29933
- function handleBridgeAgentConfig(msg, { acpManager }) {
29934
- if (!Array.isArray(msg.agents) || msg.agents.length === 0) return;
29935
- acpManager.setPreferredAgentType(msg.agents[0].type);
29879
+ // src/git/worktree-add.ts
29880
+ async function gitWorktreeAddBranch(mainRepoPath, worktreePath, branch) {
29881
+ const mainGit = simpleGit(mainRepoPath);
29882
+ await mainGit.raw(["worktree", "add", "-b", branch, worktreePath, "HEAD"]);
29936
29883
  }
29937
29884
 
29938
- // src/bridge/routing/handlers/agent-config.ts
29939
- var handleAgentConfigMessage = (msg, deps) => {
29940
- handleBridgeAgentConfig(msg, deps);
29941
- };
29942
-
29943
- // src/acp/from-bridge/handle-bridge-prompt.ts
29944
- import * as path11 from "node:path";
29945
- import { execFile as execFile5 } from "node:child_process";
29946
- import { promisify as promisify5 } from "node:util";
29947
-
29948
- // src/git/bridge-queue-key.ts
29885
+ // src/worktrees/worktree-layout-file.ts
29886
+ import * as fs5 from "node:fs";
29949
29887
  import * as path10 from "node:path";
29950
- import { createHash } from "node:crypto";
29951
- function normalizeCanonicalGitUrl(url2) {
29952
- let s = url2.trim();
29953
- if (!s) return s;
29954
- if (s.toLowerCase().endsWith(".git")) {
29955
- s = s.slice(0, -4);
29888
+ import os2 from "node:os";
29889
+ var LAYOUT_FILENAME = "worktree-launcher-layout.json";
29890
+ function defaultWorktreeLayoutPath() {
29891
+ return path10.join(os2.homedir(), ".buildautomaton", LAYOUT_FILENAME);
29892
+ }
29893
+ function normalizeLoadedLayout(raw) {
29894
+ if (raw && typeof raw === "object" && "launcherCwds" in raw) {
29895
+ const j = raw;
29896
+ if (Array.isArray(j.launcherCwds)) return { launcherCwds: j.launcherCwds };
29956
29897
  }
29957
- s = s.replace(/\/+$/, "");
29958
- const httpsMatch = /^(https?):\/\/([^/]+)(\/.*)?$/i.exec(s);
29959
- if (httpsMatch) {
29960
- const host = httpsMatch[2].toLowerCase();
29961
- const p = httpsMatch[3] ?? "";
29962
- s = `${httpsMatch[1].toLowerCase()}://${host}${p}`;
29963
- } else {
29964
- const sshMatch = /^git@([^:]+):(.+)$/i.exec(s);
29965
- if (sshMatch) {
29966
- const host = sshMatch[1].toLowerCase();
29967
- s = `git@${host}:${sshMatch[2]}`;
29968
- }
29898
+ return { launcherCwds: [] };
29899
+ }
29900
+ function loadWorktreeLayout() {
29901
+ try {
29902
+ const p = defaultWorktreeLayoutPath();
29903
+ if (!fs5.existsSync(p)) return { launcherCwds: [] };
29904
+ const raw = JSON.parse(fs5.readFileSync(p, "utf8"));
29905
+ return normalizeLoadedLayout(raw);
29906
+ } catch {
29907
+ return { launcherCwds: [] };
29969
29908
  }
29970
- return s;
29971
29909
  }
29972
- function canonicalUrlToRepoIdSync(url2) {
29973
- const normalized = normalizeCanonicalGitUrl(url2);
29974
- return createHash("sha256").update(normalized).digest("hex").slice(0, 32);
29910
+ function saveWorktreeLayout(layout) {
29911
+ try {
29912
+ const dir = path10.dirname(defaultWorktreeLayoutPath());
29913
+ fs5.mkdirSync(dir, { recursive: true });
29914
+ fs5.writeFileSync(defaultWorktreeLayoutPath(), JSON.stringify(layout, null, 2), "utf8");
29915
+ } catch {
29916
+ }
29975
29917
  }
29976
- function fallbackRepoIdFromPath(absPath) {
29977
- return createHash("sha256").update(path10.resolve(absPath)).digest("hex").slice(0, 32);
29918
+ function baseNameSafe(abs) {
29919
+ return path10.basename(abs).replace(/[^a-zA-Z0-9._-]+/g, "-") || "cwd";
29978
29920
  }
29979
- async function resolveBridgeQueueBindFields(options) {
29980
- const { effectiveCwd, worktreePaths, primaryRepoRoots, log: log2 } = options;
29981
- const cwdAbs = worktreePaths.length > 0 ? path10.resolve(worktreePaths[0]) : path10.resolve(effectiveCwd);
29982
- if (!primaryRepoRoots.length) {
29983
- log2("[Bridge service] Prompt queue bind skipped: no Git repository roots under the working directory.");
29984
- return null;
29921
+ function allocateDirNameForLauncherCwd(layout, launcherCwdAbs) {
29922
+ const norm = path10.resolve(launcherCwdAbs);
29923
+ const existing = layout.launcherCwds.find((e) => path10.resolve(e.absolutePath) === norm);
29924
+ if (existing) return existing.dirName;
29925
+ const base = baseNameSafe(norm);
29926
+ const used = new Set(layout.launcherCwds.map((e) => e.dirName));
29927
+ let name = base;
29928
+ let n = 2;
29929
+ while (used.has(name)) {
29930
+ name = `${base}-${n}`;
29931
+ n += 1;
29985
29932
  }
29986
- let primaryRoot = primaryRepoRoots[0];
29987
- let remote = await getRemoteOriginUrl(primaryRoot);
29988
- if (!remote) {
29989
- for (const r of primaryRepoRoots.slice(1)) {
29990
- const u = await getRemoteOriginUrl(r);
29991
- if (u) {
29992
- primaryRoot = r;
29993
- remote = u;
29994
- break;
29995
- }
29996
- }
29997
- }
29998
- const repoId = remote ? canonicalUrlToRepoIdSync(remote) : fallbackRepoIdFromPath(primaryRoot);
29999
- const canonicalQueueKey = `repo:${repoId}::cwd:${cwdAbs}`;
30000
- return { canonicalQueueKey, repoId, cwdAbs };
29933
+ layout.launcherCwds.push({ absolutePath: norm, dirName: name });
29934
+ saveWorktreeLayout(layout);
29935
+ return name;
30001
29936
  }
30002
29937
 
30003
- // src/acp/from-bridge/handle-bridge-prompt.ts
30004
- var execFileAsync5 = promisify5(execFile5);
30005
- async function readGitBranch(cwd) {
30006
- try {
30007
- const { stdout } = await execFileAsync5("git", ["branch", "--show-current"], { cwd, maxBuffer: 64 * 1024 });
30008
- const b = stdout.trim();
30009
- return b || null;
30010
- } catch {
29938
+ // src/worktrees/prepare-new-session-worktrees.ts
29939
+ async function prepareNewSessionWorktrees(options) {
29940
+ const { rootAbs, launcherCwd, sessionId, layout, log: log2 } = options;
29941
+ const launcherResolved = path11.resolve(launcherCwd);
29942
+ const cwdKey = allocateDirNameForLauncherCwd(layout, launcherResolved);
29943
+ const agentMirrorRoot = path11.join(rootAbs, cwdKey);
29944
+ const repos = await discoverGitReposUnderRoot(launcherResolved);
29945
+ if (repos.length === 0) {
29946
+ log2("[worktrees] No Git repositories under launcher working directory; skipping worktree creation.");
30011
29947
  return null;
30012
29948
  }
30013
- }
30014
- function handleBridgePrompt(msg, deps) {
30015
- const { getWs, log: log2, acpManager, sessionWorktreeManager } = deps;
30016
- const rawPrompt = msg.prompt;
30017
- const promptText = typeof rawPrompt === "string" ? rawPrompt : rawPrompt != null ? String(rawPrompt) : "";
30018
- if (!promptText.trim()) {
30019
- log2(
30020
- `[Bridge service] Prompt ignored: empty or missing prompt text (session ${typeof msg.sessionId === "string" ? msg.sessionId.slice(0, 8) : "\u2014"}\u2026, run ${typeof msg.runId === "string" ? msg.runId.slice(0, 8) : "\u2014"}\u2026).`
30021
- );
30022
- return;
30023
- }
30024
- const sessionId = msg.sessionId;
30025
- const isNewSession = msg.isNewSession === true;
30026
- const sessionWorktreesEnabled = msg.sessionWorktreesEnabled === true;
30027
- const agentType = typeof msg.agentType === "string" && msg.agentType.trim() ? msg.agentType.trim() : void 0;
30028
- const runId = typeof msg.runId === "string" ? msg.runId : void 0;
30029
- const mode = typeof msg.mode === "string" && msg.mode.trim() ? msg.mode.trim() : void 0;
30030
- acpManager.logPromptReceivedFromBridge({ agentType, mode });
30031
- const sendResult = (result) => {
30032
- const s = getWs();
30033
- if (s) sendWsMessage(s, result);
30034
- };
30035
- const sendSessionUpdate = (payload) => {
30036
- const s = getWs();
30037
- if (!s) {
30038
- log2("[Bridge service] Session update not sent: not connected to the bridge.");
30039
- return;
30040
- }
30041
- const p = payload;
30042
- sendWsMessage(s, payload);
30043
- };
30044
- async function preambleAndPrompt(resolvedCwd) {
30045
- const s = getWs();
30046
- const effectiveCwd = path11.resolve(resolvedCwd ?? getBridgeWorkspaceDirectory());
30047
- const worktreePaths = sessionWorktreeManager.getWorktreePathsForSession(sessionId) ?? [];
30048
- const repoRoots = await resolveSnapshotRepoRoots({
30049
- worktreePaths,
30050
- fallbackCwd: effectiveCwd,
30051
- log: log2
30052
- });
30053
- if (s && sessionId) {
30054
- const bind = await resolveBridgeQueueBindFields({
30055
- effectiveCwd,
30056
- worktreePaths,
30057
- primaryRepoRoots: repoRoots,
30058
- log: log2
30059
- });
30060
- if (bind) {
30061
- sendWsMessage(s, {
30062
- type: "bridge_queue_bind",
30063
- sessionId,
30064
- canonicalQueueKey: bind.canonicalQueueKey,
30065
- repoId: bind.repoId,
30066
- cwdAbs: bind.cwdAbs
30067
- });
30068
- }
30069
- }
30070
- if (s && sessionId) {
30071
- const cliGitBranch = await readGitBranch(effectiveCwd);
30072
- sendWsMessage(s, {
30073
- type: "session_git_context_report",
30074
- sessionId,
30075
- cliGitBranch,
30076
- agentUsesWorktree: sessionWorktreeManager.usesWorktreeSession(sessionId)
30077
- });
30078
- }
30079
- if (s && sessionId && runId) {
30080
- const cap = repoRoots.length > 0 ? await capturePreTurnSnapshot({ runId, repoRoots, agentCwd: effectiveCwd, log: log2 }) : { ok: false, error: "No git repos" };
30081
- sendWsMessage(s, {
30082
- type: "pre_turn_snapshot_report",
30083
- sessionId,
30084
- turnId: runId,
30085
- captured: cap.ok
30086
- });
29949
+ const branch = `session-${sessionId}`;
29950
+ const worktreePaths = [];
29951
+ fs6.mkdirSync(agentMirrorRoot, { recursive: true });
29952
+ for (const repo of repos) {
29953
+ let rel = path11.relative(launcherResolved, repo.absolutePath);
29954
+ if (rel.startsWith("..") || path11.isAbsolute(rel)) continue;
29955
+ const relNorm = rel === "" ? "." : rel;
29956
+ const wtPath = path11.join(agentMirrorRoot, relNorm, sessionId);
29957
+ fs6.mkdirSync(path11.dirname(wtPath), { recursive: true });
29958
+ try {
29959
+ await gitWorktreeAddBranch(repo.absolutePath, wtPath, branch);
29960
+ log2(`[worktrees] Added worktree ${wtPath} (branch ${branch}).`);
29961
+ worktreePaths.push(wtPath);
29962
+ } catch (e) {
29963
+ log2(
29964
+ `[worktrees] Worktree add failed for ${repo.absolutePath}: ${e instanceof Error ? e.message : String(e)}`
29965
+ );
30087
29966
  }
30088
- acpManager.handlePrompt({
30089
- promptText,
30090
- promptId: msg.id,
30091
- sessionId,
30092
- runId,
30093
- mode,
30094
- agentType,
30095
- cwd: effectiveCwd,
30096
- sendResult,
30097
- sendSessionUpdate
30098
- });
30099
29967
  }
30100
- void sessionWorktreeManager.resolveCwdForPrompt(sessionId, { isNewSession, sessionWorktreesEnabled }).then((cwd) => preambleAndPrompt(cwd)).catch((err) => {
30101
- log2(`[Agent] Worktree resolve failed: ${err instanceof Error ? err.message : String(err)}`);
30102
- void preambleAndPrompt(void 0);
30103
- });
29968
+ if (worktreePaths.length === 0) return null;
29969
+ return { worktreePaths, agentCwd: agentMirrorRoot };
30104
29970
  }
30105
29971
 
30106
- // src/bridge/routing/handlers/prompt.ts
30107
- var handlePromptMessage = (msg, deps) => {
30108
- handleBridgePrompt(msg, deps);
30109
- };
29972
+ // src/git/rename-branch.ts
29973
+ async function gitRenameCurrentBranch(repoDir, newName) {
29974
+ const g = simpleGit(repoDir);
29975
+ await g.raw(["branch", "-m", newName]);
29976
+ }
30110
29977
 
30111
- // src/acp/from-bridge/handle-bridge-cancel-run.ts
30112
- function handleBridgeCancelRun(msg, { log: log2, acpManager }) {
30113
- const runId = msg.runId;
30114
- if (!runId) return;
30115
- void acpManager.cancelRun(runId).then((sent) => {
30116
- if (!sent) {
29978
+ // src/worktrees/rename-session-worktree-branches.ts
29979
+ async function renameSessionWorktreeBranches(paths, newBranch, log2) {
29980
+ const safe = newBranch.replace(/[^a-zA-Z0-9/_-]+/g, "-").slice(0, 80) || "session-branch";
29981
+ for (const wt of paths) {
29982
+ try {
29983
+ await gitRenameCurrentBranch(wt, safe);
29984
+ log2(`[worktrees] Renamed branch in ${wt} \u2192 ${safe}`);
29985
+ } catch (e) {
30117
29986
  log2(
30118
- `[Agent] Cancel ignored for run ${runId.slice(0, 8)}\u2026 (no active run or cancel not available).`
29987
+ `[worktrees] Branch rename failed in ${wt}: ${e instanceof Error ? e.message : String(e)}`
30119
29988
  );
30120
29989
  }
30121
- });
29990
+ }
30122
29991
  }
30123
29992
 
30124
- // src/bridge/routing/handlers/cancel-run.ts
30125
- var handleCancelRunMessage = (msg, deps) => {
30126
- handleBridgeCancelRun(msg, deps);
30127
- };
30128
-
30129
- // src/acp/from-bridge/handle-bridge-cursor-request-response.ts
30130
- function handleBridgeCursorRequestResponse(msg, { acpManager }) {
30131
- if (typeof msg.requestId !== "string") return;
30132
- acpManager.resolveRequest(msg.requestId, msg.result ?? {});
30133
- }
29993
+ // src/worktrees/remove-session-worktrees.ts
29994
+ import * as fs9 from "node:fs";
30134
29995
 
30135
- // src/bridge/routing/handlers/cursor-request-response.ts
30136
- var handleCursorRequestResponseMessage = (msg, deps) => {
30137
- handleBridgeCursorRequestResponse(msg, deps);
30138
- };
29996
+ // src/git/worktree-remove.ts
29997
+ import * as fs8 from "node:fs";
30139
29998
 
30140
- // src/skills/handle-skill-call.ts
30141
- function handleSkillCall(msg, socket, log2) {
30142
- callSkill(msg.skillId, msg.operationId, msg.params ?? {}).then((result) => {
30143
- sendWsMessage(socket, { type: "skill_result", id: msg.id, result });
30144
- }).catch((err) => {
30145
- sendWsMessage(socket, { type: "skill_result", id: msg.id, error: String(err) });
30146
- log2(`[Bridge service] Skill invocation failed (${msg.skillId}/${msg.operationId}): ${err}`);
30147
- });
29999
+ // src/git/resolve-main-repo-from-git-file.ts
30000
+ import * as fs7 from "node:fs";
30001
+ import * as path12 from "node:path";
30002
+ function resolveMainRepoFromWorktreeGitFile(wt) {
30003
+ const gitDirFile = path12.join(wt, ".git");
30004
+ if (!fs7.existsSync(gitDirFile) || !fs7.statSync(gitDirFile).isFile()) return "";
30005
+ const first2 = fs7.readFileSync(gitDirFile, "utf8").trim();
30006
+ const m = first2.match(/^gitdir:\s*(.+)$/im);
30007
+ if (!m) return "";
30008
+ const gitWorktreePath = path12.resolve(wt, m[1].trim());
30009
+ const gitDir = path12.dirname(path12.dirname(gitWorktreePath));
30010
+ return path12.dirname(gitDir);
30148
30011
  }
30149
30012
 
30150
- // src/bridge/routing/handlers/skill-call.ts
30151
- var handleSkillCallMessage = (msg, { getWs, log: log2 }) => {
30152
- if (!msg.skillId || msg.operationId === void 0) return;
30153
- const socket = getWs();
30154
- if (!socket) return;
30155
- handleSkillCall(
30156
- msg,
30157
- socket,
30158
- log2
30159
- );
30160
- };
30161
-
30162
- // src/files/list-dir.ts
30163
- import fs5 from "node:fs";
30164
- import path13 from "node:path";
30165
-
30166
- // src/files/ensure-under-cwd.ts
30167
- import path12 from "node:path";
30168
- function ensureUnderCwd(relativePath, cwd = getBridgeWorkspaceDirectory()) {
30169
- const normalized = path12.normalize(relativePath).replace(/^(\.\/)+/, "");
30170
- const resolved = path12.resolve(cwd, normalized);
30171
- if (!resolved.startsWith(cwd + path12.sep) && resolved !== cwd) {
30172
- return null;
30013
+ // src/git/worktree-remove.ts
30014
+ async function gitWorktreeRemoveForce(worktreePath) {
30015
+ const mainRepo = resolveMainRepoFromWorktreeGitFile(worktreePath);
30016
+ if (mainRepo) {
30017
+ await simpleGit(mainRepo).raw(["worktree", "remove", "--force", worktreePath]);
30018
+ } else {
30019
+ fs8.rmSync(worktreePath, { recursive: true, force: true });
30173
30020
  }
30174
- return resolved;
30175
30021
  }
30176
30022
 
30177
- // src/files/list-dir.ts
30178
- function listDir(relativePath) {
30179
- const resolved = ensureUnderCwd(relativePath || ".", getBridgeWorkspaceDirectory());
30180
- if (!resolved) {
30181
- return { error: "Path is outside working directory" };
30182
- }
30183
- try {
30184
- const names = fs5.readdirSync(resolved, { withFileTypes: true });
30185
- const entries = names.filter((d) => !d.name.startsWith(".")).map((d) => {
30186
- const entryPath = path13.join(relativePath || ".", d.name).replace(/\\/g, "/");
30187
- const fullPath = path13.join(resolved, d.name);
30188
- let isDir = d.isDirectory();
30189
- if (d.isSymbolicLink()) {
30190
- try {
30191
- const targetStat = fs5.statSync(fullPath);
30192
- isDir = targetStat.isDirectory();
30193
- } catch {
30194
- isDir = false;
30195
- }
30023
+ // src/worktrees/remove-session-worktrees.ts
30024
+ async function removeSessionWorktrees(paths, log2) {
30025
+ for (const wt of paths) {
30026
+ try {
30027
+ await gitWorktreeRemoveForce(wt);
30028
+ log2(`[worktrees] Removed worktree ${wt}`);
30029
+ } catch (e) {
30030
+ log2(`[worktrees] Remove failed for ${wt}: ${e instanceof Error ? e.message : String(e)}`);
30031
+ try {
30032
+ fs9.rmSync(wt, { recursive: true, force: true });
30033
+ } catch {
30196
30034
  }
30197
- return {
30198
- name: d.name,
30199
- path: entryPath,
30200
- isDir,
30201
- isSymlink: d.isSymbolicLink()
30202
- };
30203
- }).sort((a, b) => {
30204
- if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
30205
- return a.name.localeCompare(b.name, void 0, { sensitivity: "base" });
30206
- });
30207
- return { entries };
30208
- } catch (err) {
30209
- const message = err instanceof Error ? err.message : String(err);
30210
- return { error: message };
30035
+ }
30211
30036
  }
30212
30037
  }
30213
30038
 
30214
- // src/files/read-file.ts
30215
- import fs6 from "node:fs";
30216
- import { StringDecoder } from "node:string_decoder";
30217
- function resolveFilePath(relativePath) {
30218
- const resolved = ensureUnderCwd(relativePath, getBridgeWorkspaceDirectory());
30219
- if (!resolved) return { error: "Path is outside working directory" };
30220
- let real;
30039
+ // src/git/working-tree-status.ts
30040
+ async function commitsAheadOfUpstream(repoDir) {
30041
+ const g = simpleGit(repoDir);
30221
30042
  try {
30222
- real = fs6.realpathSync(resolved);
30043
+ await g.raw(["rev-parse", "--verify", "@{u}"]);
30223
30044
  } catch {
30224
- real = resolved;
30045
+ return 0;
30225
30046
  }
30226
- const stat2 = fs6.statSync(real);
30227
- if (!stat2.isFile()) return { error: "Not a file" };
30228
- return real;
30047
+ const out = await g.raw(["rev-list", "--count", "@{u}..HEAD"]);
30048
+ const n = parseInt(String(out).trim(), 10);
30049
+ return Number.isNaN(n) ? 0 : n;
30229
30050
  }
30230
- var LINE_CHUNK_SIZE = 64 * 1024;
30231
- function readFileRange(filePath, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
30232
- const fileSize = fs6.statSync(filePath).size;
30233
- const fd = fs6.openSync(filePath, "r");
30234
- const bufSize = 64 * 1024;
30235
- const buf = Buffer.alloc(bufSize);
30236
- const decoder = new StringDecoder("utf8");
30237
- let currentLine = 0;
30238
- const resultLines = [];
30239
- let partial2 = "";
30240
- let done = false;
30241
- let skipLine0Chars = typeof lineOffsetIn === "number" ? lineOffsetIn : 0;
30242
- let line0CharsReturned = 0;
30243
- let line0Accum = "";
30244
- try {
30245
- let bytesRead;
30246
- while (!done && (bytesRead = fs6.readSync(fd, buf, 0, bufSize, null)) > 0) {
30247
- const text = partial2 + decoder.write(buf.subarray(0, bytesRead));
30248
- partial2 = "";
30249
- let lineStart = 0;
30250
- for (let i = 0; i < text.length; i++) {
30251
- if (text[i] === "\n") {
30252
- const lineContent = (() => {
30253
- let lineEnd = i;
30254
- if (lineEnd > lineStart && text[lineEnd - 1] === "\r") lineEnd--;
30255
- return text.slice(lineStart, lineEnd);
30256
- })();
30257
- if (currentLine === 0 && (startLine === 0 || lineOffsetIn !== void 0)) {
30258
- line0Accum += lineContent;
30259
- const totalLine0 = line0Accum.length;
30260
- if (skipLine0Chars > 0) {
30261
- if (totalLine0 <= skipLine0Chars) {
30262
- skipLine0Chars -= totalLine0;
30263
- line0Accum = "";
30264
- currentLine++;
30265
- lineStart = i + 1;
30266
- if (currentLine > endLine) {
30267
- done = true;
30268
- break;
30269
- }
30270
- continue;
30271
- }
30272
- const from = skipLine0Chars;
30273
- const take = Math.min(lineChunkSize, totalLine0 - from);
30274
- resultLines.push(line0Accum.slice(from, from + take));
30275
- line0CharsReturned += take;
30276
- if (from + take < totalLine0) {
30277
- return {
30278
- content: resultLines.join("\n"),
30279
- size: fileSize,
30280
- lineOffset: lineOffsetIn + line0CharsReturned,
30281
- totalLines: 1
30282
- };
30283
- }
30284
- line0Accum = "";
30285
- skipLine0Chars = 0;
30286
- line0CharsReturned = 0;
30287
- } else if (totalLine0 > lineChunkSize) {
30288
- resultLines.push(line0Accum.slice(0, lineChunkSize));
30289
- return {
30290
- content: resultLines.join("\n"),
30291
- size: fileSize,
30292
- lineOffset: lineChunkSize,
30293
- totalLines: 1
30294
- };
30295
- } else {
30296
- resultLines.push(line0Accum);
30297
- line0Accum = "";
30298
- }
30299
- } else if (currentLine >= startLine && currentLine <= endLine) {
30300
- resultLines.push(lineContent);
30301
- }
30302
- currentLine++;
30303
- lineStart = i + 1;
30304
- if (currentLine > endLine) {
30305
- done = true;
30306
- break;
30307
- }
30308
- }
30309
- }
30310
- if (!done) {
30311
- const lineContent = text.slice(lineStart);
30312
- if (currentLine === 0 && (startLine === 0 || lineOffsetIn !== void 0)) {
30313
- line0Accum += lineContent;
30314
- const totalLine0 = line0Accum.length;
30315
- if (skipLine0Chars > 0) {
30316
- if (totalLine0 <= skipLine0Chars) {
30317
- skipLine0Chars -= totalLine0;
30318
- line0Accum = "";
30319
- } else {
30320
- const from = skipLine0Chars;
30321
- const take = Math.min(lineChunkSize, totalLine0 - from);
30322
- resultLines.push(line0Accum.slice(from, from + take));
30323
- return {
30324
- content: resultLines.join("\n"),
30325
- size: fileSize,
30326
- lineOffset: (lineOffsetIn ?? 0) + take,
30327
- totalLines: 1
30328
- };
30329
- }
30330
- } else if (totalLine0 > lineChunkSize) {
30331
- resultLines.push(line0Accum.slice(0, lineChunkSize));
30332
- return {
30333
- content: resultLines.join("\n"),
30334
- size: fileSize,
30335
- lineOffset: lineChunkSize,
30336
- totalLines: 1
30337
- };
30338
- }
30339
- }
30340
- partial2 = text.slice(lineStart);
30341
- }
30342
- }
30343
- if (!done) {
30344
- const tail = partial2 + decoder.end();
30345
- if (currentLine === 0 && (startLine === 0 || lineOffsetIn !== void 0) && tail.length > 0) {
30346
- line0Accum += tail.endsWith("\r") ? tail.slice(0, -1) : tail;
30347
- const totalLine0 = line0Accum.length;
30348
- if (skipLine0Chars > 0) {
30349
- if (totalLine0 <= skipLine0Chars) {
30350
- return { content: resultLines.join("\n"), size: fileSize };
30351
- }
30352
- const from = skipLine0Chars;
30353
- const take = Math.min(lineChunkSize, totalLine0 - from);
30354
- resultLines.push(line0Accum.slice(from, from + take));
30355
- line0CharsReturned += take;
30356
- if (from + take < totalLine0) {
30357
- return {
30358
- content: resultLines.join("\n"),
30359
- size: fileSize,
30360
- lineOffset: (lineOffsetIn ?? 0) + line0CharsReturned,
30361
- totalLines: 1
30362
- };
30363
- }
30364
- } else if (totalLine0 > lineChunkSize) {
30365
- resultLines.push(line0Accum.slice(0, lineChunkSize));
30366
- return {
30367
- content: resultLines.join("\n"),
30368
- size: fileSize,
30369
- lineOffset: lineChunkSize,
30370
- totalLines: 1
30371
- };
30372
- } else {
30373
- resultLines.push(line0Accum);
30374
- }
30375
- } else if (tail.length > 0 && currentLine >= startLine && currentLine <= endLine) {
30376
- resultLines.push(tail.endsWith("\r") ? tail.slice(0, -1) : tail);
30377
- }
30378
- }
30379
- return { content: resultLines.join("\n"), size: fileSize };
30380
- } finally {
30381
- fs6.closeSync(fd);
30051
+ async function getRepoWorkingTreeStatus(repoDir) {
30052
+ const g = simpleGit(repoDir);
30053
+ const st = await g.status();
30054
+ const hasUncommittedChanges = (st.files?.length ?? 0) > 0;
30055
+ const ahead = await commitsAheadOfUpstream(repoDir);
30056
+ return { hasUncommittedChanges, hasUnpushedCommits: ahead > 0 };
30057
+ }
30058
+ async function aggregateSessionPathsWorkingTreeStatus(paths) {
30059
+ let hasUncommittedChanges = false;
30060
+ let hasUnpushedCommits = false;
30061
+ for (const p of paths) {
30062
+ const s = await getRepoWorkingTreeStatus(p);
30063
+ if (s.hasUncommittedChanges) hasUncommittedChanges = true;
30064
+ if (s.hasUnpushedCommits) hasUnpushedCommits = true;
30382
30065
  }
30066
+ return { hasUncommittedChanges, hasUnpushedCommits };
30383
30067
  }
30384
- function readFile2(relativePath, startLine, endLine, lineOffset, lineChunkSize = LINE_CHUNK_SIZE) {
30385
- try {
30386
- const result = resolveFilePath(relativePath);
30387
- if (typeof result === "object") return result;
30388
- const hasRange = typeof startLine === "number" && typeof endLine === "number";
30389
- if (hasRange) {
30390
- return readFileRange(result, startLine, endLine, lineOffset, lineChunkSize);
30391
- }
30392
- const stat2 = fs6.statSync(result);
30393
- const raw = fs6.readFileSync(result, "utf8");
30394
- const lines = raw.split(/\r?\n/);
30395
- return { content: raw, totalLines: lines.length, size: stat2.size };
30396
- } catch (err) {
30397
- return { error: err instanceof Error ? err.message : String(err) };
30068
+ async function pushAheadOfUpstreamForPaths(paths) {
30069
+ for (const p of paths) {
30070
+ const g = simpleGit(p);
30071
+ const ahead = await commitsAheadOfUpstream(p);
30072
+ if (ahead <= 0) continue;
30073
+ await g.push();
30398
30074
  }
30399
30075
  }
30400
30076
 
30401
- // src/files/file-index.ts
30402
- import fs7 from "node:fs";
30403
- import path14 from "node:path";
30404
- import os2 from "node:os";
30405
- import crypto2 from "node:crypto";
30406
- var INDEX_DIR = path14.join(os2.homedir(), ".buildautomaton");
30407
- var HASH_LEN = 16;
30408
- var INDEX_VERSION = 2;
30409
- function getIndexPath(cwd) {
30410
- const hash = crypto2.createHash("sha256").update(cwd).digest("hex").slice(0, HASH_LEN);
30411
- return path14.join(INDEX_DIR, `.file-index-${hash}.json`);
30412
- }
30413
- function getTrigrams(s) {
30414
- const lower = s.toLowerCase();
30415
- const out = [];
30416
- for (let i = 0; i <= lower.length - 3; i++) {
30417
- out.push(lower.slice(i, i + 3));
30077
+ // src/git/commit-and-push.ts
30078
+ async function gitCommitAllIfDirty(repoDir, message, options) {
30079
+ const g = simpleGit(repoDir);
30080
+ const st = await g.status();
30081
+ if (!st.files?.length) return;
30082
+ const branch = options.branch.trim();
30083
+ if (!branch) {
30084
+ throw new Error("Branch name is required");
30418
30085
  }
30419
- return out;
30420
- }
30421
- function intersectSortedSets(arrays) {
30422
- if (arrays.length === 0) return [];
30423
- if (arrays.length === 1) return arrays[0];
30424
- const byLength = arrays.slice().sort((a, b) => a.length - b.length);
30425
- const smallest = byLength[0];
30426
- const rest = byLength.slice(1);
30427
- const result = [];
30428
- for (const idx of smallest) {
30429
- if (rest.every((arr) => binarySearch(arr, idx) >= 0)) {
30430
- result.push(idx);
30431
- }
30086
+ const branches = await g.branchLocal();
30087
+ const localNames = new Set(branches.all.map((b) => b.replace(/^\*\s*/, "").trim()));
30088
+ if (!localNames.has(branch)) {
30089
+ await g.checkoutLocalBranch(branch);
30090
+ } else {
30091
+ await g.checkout(branch);
30432
30092
  }
30433
- return result;
30434
- }
30435
- function binarySearch(arr, x) {
30436
- let lo = 0;
30437
- let hi = arr.length - 1;
30438
- while (lo <= hi) {
30439
- const mid = lo + hi >>> 1;
30440
- if (arr[mid] < x) lo = mid + 1;
30441
- else if (arr[mid] > x) hi = mid - 1;
30442
- else return mid;
30093
+ await g.add(".");
30094
+ await g.commit(message);
30095
+ if (options.push) {
30096
+ await g.push(["-u", "origin", branch]);
30443
30097
  }
30444
- return -1;
30445
30098
  }
30446
- function walkDir(dir, baseDir, out) {
30447
- let names;
30099
+
30100
+ // src/worktrees/commit-session-worktrees.ts
30101
+ async function commitSessionWorktrees(options) {
30102
+ const { paths, branch, message, push } = options;
30448
30103
  try {
30449
- names = fs7.readdirSync(dir);
30450
- } catch {
30451
- return;
30452
- }
30453
- for (const name of names) {
30454
- if (name.startsWith(".")) continue;
30455
- const full = path14.join(dir, name);
30456
- let stat2;
30457
- try {
30458
- stat2 = fs7.statSync(full);
30459
- } catch {
30460
- continue;
30461
- }
30462
- const relative4 = path14.relative(baseDir, full).replace(/\\/g, "/");
30463
- if (stat2.isDirectory()) {
30464
- walkDir(full, baseDir, out);
30465
- } else if (stat2.isFile()) {
30466
- out.push(relative4);
30467
- }
30468
- }
30469
- }
30470
- function buildFileIndex(cwd) {
30471
- const resolved = path14.resolve(cwd);
30472
- const paths = [];
30473
- walkDir(resolved, resolved, paths);
30474
- paths.sort((a, b) => a.localeCompare(b, void 0, { sensitivity: "base" }));
30475
- const trigramIndex = {};
30476
- for (let i = 0; i < paths.length; i++) {
30477
- const trigrams = getTrigrams(paths[i]);
30478
- const seen = /* @__PURE__ */ new Set();
30479
- for (const tri of trigrams) {
30480
- if (seen.has(tri)) continue;
30481
- seen.add(tri);
30482
- if (!trigramIndex[tri]) trigramIndex[tri] = [];
30483
- trigramIndex[tri].push(i);
30104
+ for (const wt of paths) {
30105
+ await gitCommitAllIfDirty(wt, message, { push, branch });
30484
30106
  }
30485
- }
30486
- const data = { version: INDEX_VERSION, paths, trigramIndex };
30487
- const indexPath = getIndexPath(resolved);
30488
- try {
30489
- if (!fs7.existsSync(INDEX_DIR)) fs7.mkdirSync(INDEX_DIR, { recursive: true });
30490
- fs7.writeFileSync(indexPath, JSON.stringify(data), "utf8");
30107
+ return { ok: true };
30491
30108
  } catch (e) {
30492
- console.error("[file-index] Failed to write index:", e);
30109
+ const err = e instanceof Error ? e.message : String(e);
30110
+ return { ok: false, error: err };
30493
30111
  }
30494
- return data;
30495
30112
  }
30496
- function loadFileIndex(cwd) {
30497
- const resolved = path14.resolve(cwd);
30498
- const indexPath = getIndexPath(resolved);
30499
- try {
30500
- const raw = fs7.readFileSync(indexPath, "utf8");
30501
- const parsed = JSON.parse(raw);
30502
- if (parsed !== null && typeof parsed === "object" && Array.isArray(parsed.paths)) {
30503
- const obj = parsed;
30504
- if (obj.version === INDEX_VERSION && obj.trigramIndex && typeof obj.trigramIndex === "object") {
30505
- return obj;
30506
- }
30507
- return buildFileIndex(resolved);
30113
+
30114
+ // src/worktrees/session-worktree-manager.ts
30115
+ var SessionWorktreeManager = class {
30116
+ rootAbs;
30117
+ log;
30118
+ bridgeWantsWorktrees = false;
30119
+ sessionPaths = /* @__PURE__ */ new Map();
30120
+ sessionAgentCwd = /* @__PURE__ */ new Map();
30121
+ layout;
30122
+ constructor(options) {
30123
+ this.rootAbs = options.worktreesRootAbs;
30124
+ this.log = options.log;
30125
+ this.layout = loadWorktreeLayout();
30126
+ }
30127
+ setBridgeSessionWorktrees(enabled) {
30128
+ this.bridgeWantsWorktrees = enabled;
30129
+ }
30130
+ effective() {
30131
+ return this.bridgeWantsWorktrees;
30132
+ }
30133
+ /**
30134
+ * Returns cwd for the agent (mirror of launcher tree), or undefined to use the bridge workspace directory.
30135
+ */
30136
+ async resolveCwdForPrompt(sessionId, opts) {
30137
+ if (!sessionId || !this.effective() || !opts.sessionWorktreesEnabled) {
30138
+ return void 0;
30508
30139
  }
30509
- if (Array.isArray(parsed) && parsed.every((p) => typeof p === "string")) {
30510
- return buildFileIndex(resolved);
30140
+ if (!opts.isNewSession) {
30141
+ const agentCwd = this.sessionAgentCwd.get(sessionId);
30142
+ if (agentCwd) return path13.resolve(agentCwd);
30143
+ return void 0;
30511
30144
  }
30512
- return null;
30513
- } catch {
30514
- return null;
30145
+ const prep = await prepareNewSessionWorktrees({
30146
+ rootAbs: this.rootAbs,
30147
+ launcherCwd: getBridgeWorkspaceDirectory(),
30148
+ sessionId,
30149
+ layout: this.layout,
30150
+ log: this.log
30151
+ });
30152
+ if (!prep) return void 0;
30153
+ this.sessionPaths.set(sessionId, prep.worktreePaths);
30154
+ this.sessionAgentCwd.set(sessionId, prep.agentCwd);
30155
+ return path13.resolve(prep.agentCwd);
30515
30156
  }
30516
- }
30517
- function ensureFileIndex(cwd) {
30518
- const resolved = path14.resolve(cwd);
30519
- const cached2 = loadFileIndex(resolved);
30520
- if (cached2 !== null) return { data: cached2, fromCache: true };
30521
- const data = buildFileIndex(resolved);
30522
- return { data, fromCache: false };
30523
- }
30524
- function searchFileIndex(index, query, limit = 100) {
30525
- const q = query.trim().toLowerCase();
30526
- if (!q) return [];
30527
- const { paths, trigramIndex } = index;
30528
- let candidateIndices;
30529
- if (q.length < 3) {
30530
- candidateIndices = paths.map((_, i) => i).filter((i) => paths[i].toLowerCase().includes(q));
30531
- } else {
30532
- const trigrams = getTrigrams(q);
30533
- if (trigrams.length === 0) {
30534
- candidateIndices = paths.map((_, i) => i).filter((i) => paths[i].toLowerCase().includes(q));
30535
- } else {
30536
- const arrays = trigrams.map((tri) => trigramIndex[tri]).filter((arr) => arr != null && arr.length > 0);
30537
- if (arrays.length === 0) return [];
30538
- candidateIndices = intersectSortedSets(arrays);
30539
- }
30157
+ async renameSessionBranch(sessionId, newBranch) {
30158
+ const paths = this.sessionPaths.get(sessionId);
30159
+ if (!paths?.length) return;
30160
+ await renameSessionWorktreeBranches(paths, newBranch, this.log);
30540
30161
  }
30541
- const out = [];
30542
- for (const i of candidateIndices) {
30543
- const p = paths[i];
30544
- if (p.toLowerCase().includes(q)) {
30545
- out.push(p);
30546
- if (out.length >= limit) break;
30547
- }
30162
+ /** True when this session runs in an isolated worktree mirror (not launcher cwd). */
30163
+ usesWorktreeSession(sessionId) {
30164
+ if (!sessionId) return false;
30165
+ return this.sessionAgentCwd.has(sessionId);
30548
30166
  }
30549
- return out;
30550
- }
30551
-
30552
- // src/files/handle-file-browser-search.ts
30553
- var SEARCH_LIMIT = 100;
30554
- function handleFileBrowserSearch(msg, socket) {
30555
- const q = typeof msg.q === "string" ? msg.q : "";
30556
- const cwd = getBridgeWorkspaceDirectory();
30557
- const index = loadFileIndex(cwd);
30558
- if (index === null) {
30559
- sendWsMessage(socket, {
30560
- type: "file_browser_search_response",
30561
- id: msg.id,
30562
- paths: [],
30563
- indexReady: false
30167
+ getWorktreePathsForSession(sessionId) {
30168
+ if (!sessionId) return void 0;
30169
+ const paths = this.sessionPaths.get(sessionId);
30170
+ return paths?.length ? [...paths] : void 0;
30171
+ }
30172
+ /** Session mirror root (parent of per-repo worktrees), when using worktrees for this session. */
30173
+ getAgentCwdForSession(sessionId) {
30174
+ if (!sessionId) return null;
30175
+ const c = this.sessionAgentCwd.get(sessionId);
30176
+ return c ? path13.resolve(c) : null;
30177
+ }
30178
+ async removeSessionWorktrees(sessionId) {
30179
+ const paths = this.sessionPaths.get(sessionId);
30180
+ this.sessionPaths.delete(sessionId);
30181
+ this.sessionAgentCwd.delete(sessionId);
30182
+ if (!paths?.length) return;
30183
+ await removeSessionWorktrees(paths, this.log);
30184
+ }
30185
+ async commitSession(params) {
30186
+ const paths = this.sessionPaths.get(params.sessionId);
30187
+ const targets = paths?.length ? paths : [getBridgeWorkspaceDirectory()];
30188
+ return commitSessionWorktrees({
30189
+ paths: targets,
30190
+ branch: params.branch,
30191
+ message: params.message,
30192
+ push: params.push
30564
30193
  });
30565
- return;
30566
30194
  }
30567
- const results = searchFileIndex(index, q, SEARCH_LIMIT);
30568
- sendWsMessage(socket, {
30569
- type: "file_browser_search_response",
30570
- id: msg.id,
30571
- paths: results,
30572
- indexReady: true
30573
- });
30574
- }
30575
- function triggerFileIndexBuild() {
30576
- setImmediate(() => {
30195
+ resolveCommitTargets(sessionId) {
30196
+ const paths = this.sessionPaths.get(sessionId);
30197
+ return paths?.length ? paths : [getBridgeWorkspaceDirectory()];
30198
+ }
30199
+ async getSessionWorkingTreeStatus(sessionId) {
30200
+ return aggregateSessionPathsWorkingTreeStatus(this.resolveCommitTargets(sessionId));
30201
+ }
30202
+ async pushSessionUpstream(sessionId) {
30577
30203
  try {
30578
- ensureFileIndex(getBridgeWorkspaceDirectory());
30204
+ await pushAheadOfUpstreamForPaths(this.resolveCommitTargets(sessionId));
30205
+ return { ok: true };
30579
30206
  } catch (e) {
30580
- console.error("[file-index] Background build failed:", e);
30581
- }
30582
- });
30583
- }
30584
-
30585
- // src/files/handle-file-browser-request.ts
30586
- function handleFileBrowserRequest(msg, socket) {
30587
- const reqPath = msg.path.replace(/^\/+/, "") || ".";
30588
- const op = msg.op === "read" ? "read" : "list";
30589
- if (op === "list") {
30590
- const result = listDir(reqPath);
30591
- if ("error" in result) {
30592
- sendWsMessage(socket, { type: "file_browser_response", id: msg.id, error: result.error });
30593
- } else {
30594
- sendWsMessage(socket, { type: "file_browser_response", id: msg.id, entries: result.entries });
30595
- if (reqPath === "." || reqPath === "") {
30596
- triggerFileIndexBuild();
30597
- }
30598
- }
30599
- } else {
30600
- const startLine = typeof msg.startLine === "number" ? msg.startLine : void 0;
30601
- const endLine = typeof msg.endLine === "number" ? msg.endLine : void 0;
30602
- const lineOffset = typeof msg.lineOffset === "number" ? msg.lineOffset : void 0;
30603
- const lineChunkSize = typeof msg.lineChunkSize === "number" ? msg.lineChunkSize : void 0;
30604
- const result = readFile2(reqPath, startLine, endLine, lineOffset, lineChunkSize);
30605
- if ("error" in result) {
30606
- sendWsMessage(socket, { type: "file_browser_response", id: msg.id, error: result.error });
30607
- } else {
30608
- const payload = {
30609
- type: "file_browser_response",
30610
- id: msg.id,
30611
- content: result.content,
30612
- totalLines: result.totalLines,
30613
- size: result.size
30614
- };
30615
- if (result.lineOffset != null) payload.lineOffset = result.lineOffset;
30616
- sendWsMessage(socket, payload);
30207
+ const err = e instanceof Error ? e.message : String(e);
30208
+ return { ok: false, error: err };
30617
30209
  }
30618
30210
  }
30211
+ };
30212
+ function defaultWorktreesRootAbs() {
30213
+ return path13.join(os3.homedir(), ".buildautomaton", "worktrees");
30619
30214
  }
30620
30215
 
30621
- // src/bridge/routing/handlers/file-browser-messages.ts
30622
- function handleFileBrowserRequestMessage(msg, { getWs }) {
30623
- if (typeof msg.id !== "string" || typeof msg.path !== "string") return;
30624
- const socket = getWs();
30625
- if (!socket) return;
30626
- handleFileBrowserRequest(
30627
- msg,
30628
- socket
30629
- );
30630
- }
30631
- function handleFileBrowserSearchMessage(msg, { getWs }) {
30632
- if (typeof msg.id !== "string") return;
30633
- const socket = getWs();
30634
- if (!socket) return;
30635
- handleFileBrowserSearch(msg, socket);
30216
+ // src/files/watch-file-index.ts
30217
+ import { watch } from "node:fs";
30218
+ import path20 from "node:path";
30219
+
30220
+ // src/files/index/build-file-index.ts
30221
+ import path17 from "node:path";
30222
+
30223
+ // src/runtime/yield-to-event-loop.ts
30224
+ function yieldToEventLoop() {
30225
+ return new Promise((resolve14) => setImmediate(resolve14));
30636
30226
  }
30637
30227
 
30638
- // src/skills/discover-local-agent-skills.ts
30639
- import fs8 from "node:fs";
30228
+ // src/files/index/walk-workspace-tree.ts
30229
+ import fs10 from "node:fs";
30640
30230
  import path15 from "node:path";
30641
- var SKILL_DISCOVERY_ROOTS = [".agents/skills", ".claude/skills", ".cursor/skills", "skills"];
30642
- function discoverLocalSkills(cwd) {
30643
- const out = [];
30644
- const seenKeys = /* @__PURE__ */ new Set();
30645
- for (const rel of SKILL_DISCOVERY_ROOTS) {
30646
- const base = path15.join(cwd, rel);
30647
- if (!fs8.existsSync(base) || !fs8.statSync(base).isDirectory()) continue;
30648
- let entries = [];
30231
+
30232
+ // src/files/index/constants.ts
30233
+ import path14 from "node:path";
30234
+ import os4 from "node:os";
30235
+ var INDEX_WORK_YIELD_EVERY = 256;
30236
+ var INDEX_DIR = path14.join(os4.homedir(), ".buildautomaton");
30237
+ var INDEX_HASH_LEN = 16;
30238
+ var INDEX_VERSION = 2;
30239
+ var INDEX_LOG_PREFIX = "[file-index]";
30240
+
30241
+ // src/files/index/walk-workspace-tree.ts
30242
+ function walkWorkspaceTreeSync(dir, baseDir, out) {
30243
+ let names;
30244
+ try {
30245
+ names = fs10.readdirSync(dir);
30246
+ } catch {
30247
+ return;
30248
+ }
30249
+ for (const name of names) {
30250
+ if (name.startsWith(".")) continue;
30251
+ const full = path15.join(dir, name);
30252
+ let stat2;
30649
30253
  try {
30650
- entries = fs8.readdirSync(base);
30254
+ stat2 = fs10.statSync(full);
30651
30255
  } catch {
30652
30256
  continue;
30653
30257
  }
30654
- for (const name of entries) {
30655
- const dir = path15.join(base, name);
30656
- try {
30657
- if (!fs8.statSync(dir).isDirectory()) continue;
30658
- } catch {
30659
- continue;
30660
- }
30661
- const skillMd = path15.join(dir, "SKILL.md");
30662
- if (!fs8.existsSync(skillMd)) continue;
30663
- const key = `${rel}/${name}`;
30664
- if (seenKeys.has(key)) continue;
30665
- seenKeys.add(key);
30666
- out.push({ skillKey: name, path: `${rel}/${name}`.replace(/\\/g, "/") });
30258
+ const relative4 = path15.relative(baseDir, full).replace(/\\/g, "/");
30259
+ if (stat2.isDirectory()) {
30260
+ walkWorkspaceTreeSync(full, baseDir, out);
30261
+ } else if (stat2.isFile()) {
30262
+ out.push(relative4);
30667
30263
  }
30668
30264
  }
30669
- return out;
30670
30265
  }
30671
- function discoverSkillLayoutRoots(cwd) {
30672
- const roots = [];
30673
- for (const rel of SKILL_DISCOVERY_ROOTS) {
30674
- const base = path15.join(cwd, rel);
30675
- if (!fs8.existsSync(base) || !fs8.statSync(base).isDirectory()) continue;
30676
- let entries = [];
30266
+ async function walkWorkspaceTreeAsync(dir, baseDir, out, state) {
30267
+ let names;
30268
+ try {
30269
+ names = await fs10.promises.readdir(dir);
30270
+ } catch {
30271
+ return;
30272
+ }
30273
+ for (const name of names) {
30274
+ if (name.startsWith(".")) continue;
30275
+ if (state.n > 0 && state.n % INDEX_WORK_YIELD_EVERY === 0) {
30276
+ await yieldToEventLoop();
30277
+ }
30278
+ state.n++;
30279
+ const full = path15.join(dir, name);
30280
+ let stat2;
30677
30281
  try {
30678
- entries = fs8.readdirSync(base);
30282
+ stat2 = await fs10.promises.stat(full);
30679
30283
  } catch {
30680
30284
  continue;
30681
30285
  }
30682
- const skills2 = [];
30683
- for (const name of entries) {
30684
- const dir = path15.join(base, name);
30685
- try {
30686
- if (!fs8.statSync(dir).isDirectory()) continue;
30687
- } catch {
30688
- continue;
30689
- }
30690
- if (!fs8.existsSync(path15.join(dir, "SKILL.md"))) continue;
30691
- const relPath = `${rel}/${name}`.replace(/\\/g, "/");
30692
- skills2.push({ name, relPath });
30693
- }
30694
- if (skills2.length > 0) {
30695
- roots.push({ path: rel.replace(/\\/g, "/"), skills: skills2 });
30286
+ const relative4 = path15.relative(baseDir, full).replace(/\\/g, "/");
30287
+ if (stat2.isDirectory()) {
30288
+ await walkWorkspaceTreeAsync(full, baseDir, out, state);
30289
+ } else if (stat2.isFile()) {
30290
+ out.push(relative4);
30696
30291
  }
30697
30292
  }
30698
- return roots;
30699
30293
  }
30700
-
30701
- // src/bridge/routing/handlers/skill-layout-request.ts
30702
- function handleSkillLayoutRequest(msg, deps) {
30703
- const socket = deps.getWs();
30704
- const id = typeof msg.id === "string" ? msg.id : "";
30705
- const roots = discoverSkillLayoutRoots(getBridgeWorkspaceDirectory());
30706
- socket?.send(JSON.stringify({ type: "skill_layout_response", id, roots }));
30294
+ function createWalkYieldState() {
30295
+ return { n: 0 };
30707
30296
  }
30708
30297
 
30709
- // src/skills/install-remote-skills.ts
30710
- import fs9 from "node:fs";
30711
- import path16 from "node:path";
30712
- function installRemoteSkills(cwd, targetDir, items) {
30713
- const installed = [];
30714
- if (!Array.isArray(items)) {
30715
- return { success: false, error: "Invalid items" };
30716
- }
30717
- try {
30718
- for (const item of items) {
30719
- if (typeof item.sourceId !== "string" || typeof item.skillName !== "string" || typeof item.versionHash !== "string" || !Array.isArray(item.files)) {
30720
- continue;
30721
- }
30722
- const skillDir = path16.join(cwd, targetDir, item.skillName);
30723
- for (const f of item.files) {
30724
- if (typeof f.path !== "string" || !f.text && !f.base64) continue;
30725
- const dest = path16.join(skillDir, f.path);
30726
- fs9.mkdirSync(path16.dirname(dest), { recursive: true });
30727
- if (f.text !== void 0) {
30728
- fs9.writeFileSync(dest, f.text, "utf8");
30729
- } else if (f.base64) {
30730
- fs9.writeFileSync(dest, Buffer.from(f.base64, "base64"));
30731
- }
30732
- }
30733
- installed.push({
30734
- sourceId: item.sourceId,
30735
- skillName: item.skillName,
30736
- versionHash: item.versionHash
30737
- });
30738
- }
30739
- return { success: true, installed };
30740
- } catch (e) {
30741
- return { success: false, error: e instanceof Error ? e.message : String(e) };
30298
+ // src/files/index/trigram-utils.ts
30299
+ function getTrigrams(s) {
30300
+ const lower = s.toLowerCase();
30301
+ const out = [];
30302
+ for (let i = 0; i <= lower.length - 3; i++) {
30303
+ out.push(lower.slice(i, i + 3));
30742
30304
  }
30305
+ return out;
30743
30306
  }
30744
-
30745
- // src/bridge/routing/handlers/install-skills.ts
30746
- var handleInstallSkillsMessage = (msg, deps) => {
30747
- const socket = deps.getWs();
30748
- const id = typeof msg.id === "string" ? msg.id : "";
30749
- const targetDir = typeof msg.targetDir === "string" && msg.targetDir.trim() ? msg.targetDir.trim() : ".agents/skills";
30750
- const rawItems = msg.items;
30751
- const cwd = getBridgeWorkspaceDirectory();
30752
- const result = installRemoteSkills(cwd, targetDir, rawItems);
30753
- if (!result.success) {
30754
- const err = result.error ?? "Invalid items";
30755
- deps.log(`[Bridge service] Install skills failed: ${err}`);
30756
- socket?.send(JSON.stringify({ type: "install_skills_result", id, success: false, error: err }));
30757
- return;
30307
+ function binarySearch(arr, x) {
30308
+ let lo = 0;
30309
+ let hi = arr.length - 1;
30310
+ while (lo <= hi) {
30311
+ const mid = lo + hi >>> 1;
30312
+ if (arr[mid] < x) lo = mid + 1;
30313
+ else if (arr[mid] > x) hi = mid - 1;
30314
+ else return mid;
30758
30315
  }
30759
- socket?.send(JSON.stringify({ type: "install_skills_result", id, success: true, installed: result.installed }));
30760
- };
30761
-
30762
- // src/bridge/routing/handlers/refresh-local-skills.ts
30763
- var handleRefreshLocalSkills = (_msg, deps) => {
30764
- deps.sendLocalSkillsReport?.();
30765
- };
30766
-
30767
- // src/bridge/routing/handlers/commit-session-request.ts
30768
- var handleCommitSessionRequestMessage = (msg, deps) => {
30769
- if (typeof msg.id !== "string") return;
30770
- const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
30771
- const branch = typeof msg.branch === "string" ? msg.branch : "";
30772
- const message = typeof msg.message === "string" ? msg.message : "";
30773
- const push = msg.push === true;
30774
- const endSession = msg.endSession === true;
30775
- if (!sessionId || !branch || !message) return;
30776
- void deps.sessionWorktreeManager.commitSession({ sessionId, branch, message, push }).then((r) => {
30777
- const s = deps.getWs();
30778
- if (s) {
30779
- sendWsMessage(s, {
30780
- type: "commit_session_result",
30781
- id: msg.id,
30782
- ok: r.ok,
30783
- ...r.error ? { error: r.error } : {},
30784
- endSession
30785
- });
30786
- }
30787
- }).catch((e) => {
30788
- const s = deps.getWs();
30789
- if (s) {
30790
- sendWsMessage(s, {
30791
- type: "commit_session_result",
30792
- id: msg.id,
30793
- ok: false,
30794
- error: e instanceof Error ? e.message : String(e),
30795
- endSession
30796
- });
30316
+ return -1;
30317
+ }
30318
+ function intersectSortedTrigramSets(arrays) {
30319
+ if (arrays.length === 0) return [];
30320
+ if (arrays.length === 1) return arrays[0];
30321
+ const byLength = arrays.slice().sort((a, b) => a.length - b.length);
30322
+ const smallest = byLength[0];
30323
+ const rest = byLength.slice(1);
30324
+ const result = [];
30325
+ for (const idx of smallest) {
30326
+ if (rest.every((arr) => binarySearch(arr, idx) >= 0)) {
30327
+ result.push(idx);
30797
30328
  }
30798
- });
30799
- };
30800
-
30801
- // src/bridge/routing/handlers/rename-session-branch.ts
30802
- var handleRenameSessionBranchMessage = (msg, deps) => {
30803
- const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
30804
- const newBranch = typeof msg.newBranch === "string" ? msg.newBranch : "";
30805
- if (!sessionId || !newBranch) return;
30806
- void deps.sessionWorktreeManager.renameSessionBranch(sessionId, newBranch);
30807
- };
30808
-
30809
- // src/bridge/routing/handlers/session-archived.ts
30810
- var handleSessionArchivedMessage = (msg, deps) => {
30811
- const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
30812
- if (!sessionId) return;
30813
- void deps.sessionWorktreeManager.removeSessionWorktrees(sessionId);
30814
- };
30815
-
30816
- // src/bridge/routing/handlers/session-discarded.ts
30817
- var handleSessionDiscardedMessage = (msg, deps) => {
30818
- const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
30819
- if (!sessionId) return;
30820
- void deps.sessionWorktreeManager.removeSessionWorktrees(sessionId);
30821
- };
30329
+ }
30330
+ return result;
30331
+ }
30822
30332
 
30823
- // src/bridge/routing/handlers/revert-turn-snapshot.ts
30824
- import * as fs10 from "node:fs";
30825
- var handleRevertTurnSnapshotMessage = (msg, deps) => {
30826
- const id = typeof msg.id === "string" ? msg.id : "";
30827
- const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
30828
- const turnId = typeof msg.turnId === "string" ? msg.turnId : "";
30829
- if (!id || !sessionId || !turnId) return;
30830
- const { getWs, log: log2, sessionWorktreeManager } = deps;
30831
- void (async () => {
30832
- const s = getWs();
30833
- if (!s) return;
30834
- const agentBase = sessionWorktreeManager.getAgentCwdForSession(sessionId) ?? getBridgeWorkspaceDirectory();
30835
- const file2 = snapshotFilePath(agentBase, turnId);
30836
- if (!fs10.existsSync(file2)) {
30837
- sendWsMessage(s, {
30838
- type: "revert_turn_snapshot_result",
30839
- id,
30840
- ok: false,
30841
- error: "No snapshot found for this turn (git state may be unavailable)."
30842
- });
30843
- return;
30333
+ // src/files/index/build-trigram-map.ts
30334
+ function buildTrigramMapForPaths(paths) {
30335
+ const trigramIndex = {};
30336
+ for (let i = 0; i < paths.length; i++) {
30337
+ const trigrams = getTrigrams(paths[i]);
30338
+ const seen = /* @__PURE__ */ new Set();
30339
+ for (const tri of trigrams) {
30340
+ if (seen.has(tri)) continue;
30341
+ seen.add(tri);
30342
+ if (!trigramIndex[tri]) trigramIndex[tri] = [];
30343
+ trigramIndex[tri].push(i);
30844
30344
  }
30845
- const res = await applyPreTurnSnapshot(file2, log2);
30846
- sendWsMessage(s, {
30847
- type: "revert_turn_snapshot_result",
30848
- id,
30849
- ok: res.ok,
30850
- ...res.error ? { error: res.error } : {}
30851
- });
30852
- })();
30853
- };
30854
-
30855
- // src/bridge/routing/handlers/dev-server-control.ts
30856
- var handleDevServerControl = (msg, deps) => {
30857
- const serverId = typeof msg.serverId === "string" ? msg.serverId : "";
30858
- const action = msg.action === "start" || msg.action === "stop" ? msg.action : null;
30859
- if (!serverId || !action) return;
30860
- deps.devServerManager?.handleControl(serverId, action);
30861
- };
30862
-
30863
- // src/bridge/routing/handlers/dev-servers-config.ts
30864
- var handleDevServersConfig = (msg, deps) => {
30865
- const devServers = msg.devServers;
30866
- deps.devServerManager?.applyConfig(devServers ?? []);
30867
- };
30868
-
30869
- // src/bridge/routing/dispatch-bridge-message.ts
30870
- function dispatchBridgeMessage(msg, deps) {
30871
- const type = msg.type;
30872
- if (typeof type !== "string") return;
30873
- switch (type) {
30874
- case "auth_token":
30875
- handleAuthToken(msg, deps);
30876
- break;
30877
- case "bridge_identified":
30878
- handleBridgeIdentified(msg, deps);
30879
- break;
30880
- case "dev_servers_config":
30881
- handleDevServersConfig(msg, deps);
30882
- break;
30883
- case "server_control":
30884
- handleDevServerControl(msg, deps);
30885
- break;
30886
- case "agent_config":
30887
- handleAgentConfigMessage(msg, deps);
30888
- break;
30889
- case "prompt":
30890
- handlePromptMessage(msg, deps);
30891
- break;
30892
- case "commit_session_request":
30893
- handleCommitSessionRequestMessage(msg, deps);
30894
- break;
30895
- case "rename_session_branch":
30896
- handleRenameSessionBranchMessage(msg, deps);
30897
- break;
30898
- case "session_archived":
30899
- handleSessionArchivedMessage(msg, deps);
30900
- break;
30901
- case "session_discarded":
30902
- handleSessionDiscardedMessage(msg, deps);
30903
- break;
30904
- case "revert_turn_snapshot":
30905
- handleRevertTurnSnapshotMessage(msg, deps);
30906
- break;
30907
- case "cancel_run":
30908
- handleCancelRunMessage(msg, deps);
30909
- break;
30910
- case "cursor_request_response":
30911
- handleCursorRequestResponseMessage(msg, deps);
30912
- break;
30913
- case "skill_call":
30914
- handleSkillCallMessage(msg, deps);
30915
- break;
30916
- case "file_browser_request":
30917
- handleFileBrowserRequestMessage(msg, deps);
30918
- break;
30919
- case "file_browser_search":
30920
- handleFileBrowserSearchMessage(msg, deps);
30921
- break;
30922
- case "skill_layout_request":
30923
- handleSkillLayoutRequest(msg, deps);
30924
- break;
30925
- case "install_skills":
30926
- handleInstallSkillsMessage(msg, deps);
30927
- break;
30928
- case "refresh_local_skills":
30929
- handleRefreshLocalSkills(msg, deps);
30930
- break;
30931
- default:
30932
- deps.log?.(`[Bridge service] unhandled message type: ${type}`);
30933
30345
  }
30346
+ return trigramIndex;
30934
30347
  }
30935
-
30936
- // src/bridge/routing/handle-bridge-message.ts
30937
- function handleBridgeMessage(data, deps) {
30938
- const msg = data;
30939
- if (!deps.getWs()) return;
30940
- setImmediate(() => {
30941
- dispatchBridgeMessage(msg, deps);
30942
- });
30348
+ async function buildTrigramMapForPathsAsync(paths) {
30349
+ const trigramIndex = {};
30350
+ for (let i = 0; i < paths.length; i++) {
30351
+ if (i > 0 && i % INDEX_WORK_YIELD_EVERY === 0) {
30352
+ await yieldToEventLoop();
30353
+ }
30354
+ const trigrams = getTrigrams(paths[i]);
30355
+ const seen = /* @__PURE__ */ new Set();
30356
+ for (const tri of trigrams) {
30357
+ if (seen.has(tri)) continue;
30358
+ seen.add(tri);
30359
+ if (!trigramIndex[tri]) trigramIndex[tri] = [];
30360
+ trigramIndex[tri].push(i);
30361
+ }
30362
+ }
30363
+ return trigramIndex;
30943
30364
  }
30944
30365
 
30945
- // src/worktrees/session-worktree-manager.ts
30946
- import * as path20 from "node:path";
30947
- import os4 from "node:os";
30948
-
30949
- // src/worktrees/prepare-new-session-worktrees.ts
30950
- import * as fs12 from "node:fs";
30951
- import * as path18 from "node:path";
30366
+ // src/files/index/write-index-file.ts
30367
+ import fs11 from "node:fs";
30952
30368
 
30953
- // src/git/worktree-add.ts
30954
- async function gitWorktreeAddBranch(mainRepoPath, worktreePath, branch) {
30955
- const mainGit = simpleGit(mainRepoPath);
30956
- await mainGit.raw(["worktree", "add", "-b", branch, worktreePath, "HEAD"]);
30369
+ // src/files/index/paths.ts
30370
+ import path16 from "node:path";
30371
+ import crypto2 from "node:crypto";
30372
+ function getIndexPathForCwd(resolvedCwd) {
30373
+ const hash = crypto2.createHash("sha256").update(resolvedCwd).digest("hex").slice(0, INDEX_HASH_LEN);
30374
+ return path16.join(INDEX_DIR, `.file-index-${hash}.json`);
30957
30375
  }
30958
30376
 
30959
- // src/worktrees/worktree-layout-file.ts
30960
- import * as fs11 from "node:fs";
30961
- import * as path17 from "node:path";
30962
- import os3 from "node:os";
30963
- var LAYOUT_FILENAME = "worktree-launcher-layout.json";
30964
- function defaultWorktreeLayoutPath() {
30965
- return path17.join(os3.homedir(), ".buildautomaton", LAYOUT_FILENAME);
30966
- }
30967
- function normalizeLoadedLayout(raw) {
30968
- if (raw && typeof raw === "object" && "launcherCwds" in raw) {
30969
- const j = raw;
30970
- if (Array.isArray(j.launcherCwds)) return { launcherCwds: j.launcherCwds };
30971
- }
30972
- return { launcherCwds: [] };
30973
- }
30974
- function loadWorktreeLayout() {
30377
+ // src/files/index/write-index-file.ts
30378
+ function writeIndexFileSync(resolvedCwd, data) {
30379
+ const indexPath = getIndexPathForCwd(resolvedCwd);
30975
30380
  try {
30976
- const p = defaultWorktreeLayoutPath();
30977
- if (!fs11.existsSync(p)) return { launcherCwds: [] };
30978
- const raw = JSON.parse(fs11.readFileSync(p, "utf8"));
30979
- return normalizeLoadedLayout(raw);
30980
- } catch {
30981
- return { launcherCwds: [] };
30381
+ if (!fs11.existsSync(INDEX_DIR)) fs11.mkdirSync(INDEX_DIR, { recursive: true });
30382
+ fs11.writeFileSync(indexPath, JSON.stringify(data), "utf8");
30383
+ } catch (e) {
30384
+ console.error(`${INDEX_LOG_PREFIX} Failed to write index:`, e);
30982
30385
  }
30983
30386
  }
30984
- function saveWorktreeLayout(layout) {
30387
+ async function writeIndexFileAsync(resolvedCwd, data) {
30388
+ const indexPath = getIndexPathForCwd(resolvedCwd);
30985
30389
  try {
30986
- const dir = path17.dirname(defaultWorktreeLayoutPath());
30987
- fs11.mkdirSync(dir, { recursive: true });
30988
- fs11.writeFileSync(defaultWorktreeLayoutPath(), JSON.stringify(layout, null, 2), "utf8");
30989
- } catch {
30390
+ await fs11.promises.mkdir(INDEX_DIR, { recursive: true });
30391
+ await fs11.promises.writeFile(indexPath, JSON.stringify(data), "utf8");
30392
+ } catch (e) {
30393
+ console.error(`${INDEX_LOG_PREFIX} Failed to write index:`, e);
30990
30394
  }
30991
30395
  }
30992
- function baseNameSafe(abs) {
30993
- return path17.basename(abs).replace(/[^a-zA-Z0-9._-]+/g, "-") || "cwd";
30994
- }
30995
- function allocateDirNameForLauncherCwd(layout, launcherCwdAbs) {
30996
- const norm = path17.resolve(launcherCwdAbs);
30997
- const existing = layout.launcherCwds.find((e) => path17.resolve(e.absolutePath) === norm);
30998
- if (existing) return existing.dirName;
30999
- const base = baseNameSafe(norm);
31000
- const used = new Set(layout.launcherCwds.map((e) => e.dirName));
31001
- let name = base;
31002
- let n = 2;
31003
- while (used.has(name)) {
31004
- name = `${base}-${n}`;
31005
- n += 1;
31006
- }
31007
- layout.launcherCwds.push({ absolutePath: norm, dirName: name });
31008
- saveWorktreeLayout(layout);
31009
- return name;
30396
+ function makeTrigramIndexData(paths, trigramIndex) {
30397
+ return { version: INDEX_VERSION, paths, trigramIndex };
31010
30398
  }
31011
30399
 
31012
- // src/worktrees/prepare-new-session-worktrees.ts
31013
- async function prepareNewSessionWorktrees(options) {
31014
- const { rootAbs, launcherCwd, sessionId, layout, log: log2 } = options;
31015
- const launcherResolved = path18.resolve(launcherCwd);
31016
- const cwdKey = allocateDirNameForLauncherCwd(layout, launcherResolved);
31017
- const agentMirrorRoot = path18.join(rootAbs, cwdKey);
31018
- const repos = await discoverGitReposUnderRoot(launcherResolved);
31019
- if (repos.length === 0) {
31020
- log2("[worktrees] No Git repositories under launcher working directory; skipping worktree creation.");
31021
- return null;
31022
- }
31023
- const branch = `session-${sessionId}`;
31024
- const worktreePaths = [];
31025
- fs12.mkdirSync(agentMirrorRoot, { recursive: true });
31026
- for (const repo of repos) {
31027
- let rel = path18.relative(launcherResolved, repo.absolutePath);
31028
- if (rel.startsWith("..") || path18.isAbsolute(rel)) continue;
31029
- const relNorm = rel === "" ? "." : rel;
31030
- const wtPath = path18.join(agentMirrorRoot, relNorm, sessionId);
31031
- fs12.mkdirSync(path18.dirname(wtPath), { recursive: true });
31032
- try {
31033
- await gitWorktreeAddBranch(repo.absolutePath, wtPath, branch);
31034
- log2(`[worktrees] Added worktree ${wtPath} (branch ${branch}).`);
31035
- worktreePaths.push(wtPath);
31036
- } catch (e) {
31037
- log2(
31038
- `[worktrees] Worktree add failed for ${repo.absolutePath}: ${e instanceof Error ? e.message : String(e)}`
31039
- );
31040
- }
31041
- }
31042
- if (worktreePaths.length === 0) return null;
31043
- return { worktreePaths, agentCwd: agentMirrorRoot };
31044
- }
31045
-
31046
- // src/git/rename-branch.ts
31047
- async function gitRenameCurrentBranch(repoDir, newName) {
31048
- const g = simpleGit(repoDir);
31049
- await g.raw(["branch", "-m", newName]);
31050
- }
31051
-
31052
- // src/worktrees/rename-session-worktree-branches.ts
31053
- async function renameSessionWorktreeBranches(paths, newBranch, log2) {
31054
- const safe = newBranch.replace(/[^a-zA-Z0-9/_-]+/g, "-").slice(0, 80) || "session-branch";
31055
- for (const wt of paths) {
31056
- try {
31057
- await gitRenameCurrentBranch(wt, safe);
31058
- log2(`[worktrees] Renamed branch in ${wt} \u2192 ${safe}`);
31059
- } catch (e) {
31060
- log2(
31061
- `[worktrees] Branch rename failed in ${wt}: ${e instanceof Error ? e.message : String(e)}`
31062
- );
31063
- }
31064
- }
30400
+ // src/files/index/build-file-index.ts
30401
+ function sortPaths(paths) {
30402
+ paths.sort((a, b) => a.localeCompare(b, void 0, { sensitivity: "base" }));
31065
30403
  }
31066
-
31067
- // src/worktrees/remove-session-worktrees.ts
31068
- import * as fs15 from "node:fs";
31069
-
31070
- // src/git/worktree-remove.ts
31071
- import * as fs14 from "node:fs";
31072
-
31073
- // src/git/resolve-main-repo-from-git-file.ts
31074
- import * as fs13 from "node:fs";
31075
- import * as path19 from "node:path";
31076
- function resolveMainRepoFromWorktreeGitFile(wt) {
31077
- const gitDirFile = path19.join(wt, ".git");
31078
- if (!fs13.existsSync(gitDirFile) || !fs13.statSync(gitDirFile).isFile()) return "";
31079
- const first2 = fs13.readFileSync(gitDirFile, "utf8").trim();
31080
- const m = first2.match(/^gitdir:\s*(.+)$/im);
31081
- if (!m) return "";
31082
- const gitWorktreePath = path19.resolve(wt, m[1].trim());
31083
- const gitDir = path19.dirname(path19.dirname(gitWorktreePath));
31084
- return path19.dirname(gitDir);
30404
+ function buildFileIndex(cwd) {
30405
+ const resolved = path17.resolve(cwd);
30406
+ const paths = [];
30407
+ walkWorkspaceTreeSync(resolved, resolved, paths);
30408
+ sortPaths(paths);
30409
+ const trigramIndex = buildTrigramMapForPaths(paths);
30410
+ const data = makeTrigramIndexData(paths, trigramIndex);
30411
+ writeIndexFileSync(resolved, data);
30412
+ return data;
31085
30413
  }
31086
-
31087
- // src/git/worktree-remove.ts
31088
- async function gitWorktreeRemoveForce(worktreePath) {
31089
- const mainRepo = resolveMainRepoFromWorktreeGitFile(worktreePath);
31090
- if (mainRepo) {
31091
- await simpleGit(mainRepo).raw(["worktree", "remove", "--force", worktreePath]);
31092
- } else {
31093
- fs14.rmSync(worktreePath, { recursive: true, force: true });
31094
- }
30414
+ async function buildFileIndexAsync(cwd) {
30415
+ const resolved = path17.resolve(cwd);
30416
+ const paths = [];
30417
+ await walkWorkspaceTreeAsync(resolved, resolved, paths, createWalkYieldState());
30418
+ await yieldToEventLoop();
30419
+ sortPaths(paths);
30420
+ const trigramIndex = await buildTrigramMapForPathsAsync(paths);
30421
+ const data = makeTrigramIndexData(paths, trigramIndex);
30422
+ await writeIndexFileAsync(resolved, data);
30423
+ return data;
31095
30424
  }
31096
30425
 
31097
- // src/worktrees/remove-session-worktrees.ts
31098
- async function removeSessionWorktrees(paths, log2) {
31099
- for (const wt of paths) {
31100
- try {
31101
- await gitWorktreeRemoveForce(wt);
31102
- log2(`[worktrees] Removed worktree ${wt}`);
31103
- } catch (e) {
31104
- log2(`[worktrees] Remove failed for ${wt}: ${e instanceof Error ? e.message : String(e)}`);
31105
- try {
31106
- fs15.rmSync(wt, { recursive: true, force: true });
31107
- } catch {
30426
+ // src/files/index/load-file-index.ts
30427
+ import fs12 from "node:fs";
30428
+ import path18 from "node:path";
30429
+ function loadFileIndex(cwd) {
30430
+ const resolved = path18.resolve(cwd);
30431
+ const indexPath = getIndexPathForCwd(resolved);
30432
+ try {
30433
+ const raw = fs12.readFileSync(indexPath, "utf8");
30434
+ const parsed = JSON.parse(raw);
30435
+ if (parsed !== null && typeof parsed === "object" && Array.isArray(parsed.paths)) {
30436
+ const obj = parsed;
30437
+ if (obj.version === INDEX_VERSION && obj.trigramIndex && typeof obj.trigramIndex === "object") {
30438
+ return obj;
31108
30439
  }
30440
+ return buildFileIndex(resolved);
31109
30441
  }
30442
+ if (Array.isArray(parsed) && parsed.every((p) => typeof p === "string")) {
30443
+ return buildFileIndex(resolved);
30444
+ }
30445
+ return null;
30446
+ } catch {
30447
+ return null;
31110
30448
  }
31111
30449
  }
31112
30450
 
31113
- // src/git/commit-and-push.ts
31114
- async function gitCommitAllIfDirty(repoDir, message, options) {
31115
- const g = simpleGit(repoDir);
31116
- const st = await g.status();
31117
- if (!st.files?.length) return;
31118
- const branch = options.branch.trim();
31119
- if (!branch) {
31120
- throw new Error("Branch name is required");
31121
- }
31122
- const branches = await g.branchLocal();
31123
- const localNames = new Set(branches.all.map((b) => b.replace(/^\*\s*/, "").trim()));
31124
- if (!localNames.has(branch)) {
31125
- await g.checkoutLocalBranch(branch);
31126
- } else {
31127
- await g.checkout(branch);
31128
- }
31129
- await g.add(".");
31130
- await g.commit(message);
31131
- if (options.push) {
31132
- await g.push(["-u", "origin", branch]);
31133
- }
31134
- }
31135
-
31136
- // src/worktrees/commit-session-worktrees.ts
31137
- async function commitSessionWorktrees(options) {
31138
- const { paths, branch, message, push } = options;
31139
- try {
31140
- for (const wt of paths) {
31141
- await gitCommitAllIfDirty(wt, message, { push, branch });
31142
- }
31143
- return { ok: true };
31144
- } catch (e) {
31145
- const err = e instanceof Error ? e.message : String(e);
31146
- return { ok: false, error: err };
31147
- }
30451
+ // src/files/index/ensure-file-index.ts
30452
+ import path19 from "node:path";
30453
+ async function ensureFileIndexAsync(cwd) {
30454
+ const resolved = path19.resolve(cwd);
30455
+ const cached2 = loadFileIndex(resolved);
30456
+ if (cached2 !== null) return { data: cached2, fromCache: true };
30457
+ const data = await buildFileIndexAsync(resolved);
30458
+ return { data, fromCache: false };
31148
30459
  }
31149
30460
 
31150
- // src/worktrees/session-worktree-manager.ts
31151
- var SessionWorktreeManager = class {
31152
- rootAbs;
31153
- log;
31154
- bridgeWantsWorktrees = false;
31155
- sessionPaths = /* @__PURE__ */ new Map();
31156
- sessionAgentCwd = /* @__PURE__ */ new Map();
31157
- layout;
31158
- constructor(options) {
31159
- this.rootAbs = options.worktreesRootAbs;
31160
- this.log = options.log;
31161
- this.layout = loadWorktreeLayout();
31162
- }
31163
- setBridgeSessionWorktrees(enabled) {
31164
- this.bridgeWantsWorktrees = enabled;
30461
+ // src/files/index/search-file-index.ts
30462
+ function candidatePathIndices(index, q) {
30463
+ const { paths, trigramIndex } = index;
30464
+ if (q.length < 3) {
30465
+ return paths.map((_, i) => i).filter((i) => paths[i].toLowerCase().includes(q));
31165
30466
  }
31166
- effective() {
31167
- return this.bridgeWantsWorktrees;
30467
+ const trigrams = getTrigrams(q);
30468
+ if (trigrams.length === 0) {
30469
+ return paths.map((_, i) => i).filter((i) => paths[i].toLowerCase().includes(q));
31168
30470
  }
31169
- /**
31170
- * Returns cwd for the agent (mirror of launcher tree), or undefined to use the bridge workspace directory.
31171
- */
31172
- async resolveCwdForPrompt(sessionId, opts) {
31173
- if (!sessionId || !this.effective() || !opts.sessionWorktreesEnabled) {
31174
- return void 0;
30471
+ const arrays = trigrams.map((tri) => trigramIndex[tri]).filter((arr) => arr != null && arr.length > 0);
30472
+ if (arrays.length === 0) return [];
30473
+ return intersectSortedTrigramSets(arrays);
30474
+ }
30475
+ async function searchFileIndexAsync(index, query, limit = 100) {
30476
+ await yieldToEventLoop();
30477
+ const q = query.trim().toLowerCase();
30478
+ if (!q) return [];
30479
+ const { paths } = index;
30480
+ const candidateIndices = candidatePathIndices(index, q);
30481
+ const out = [];
30482
+ let n = 0;
30483
+ for (const i of candidateIndices) {
30484
+ if (n > 0 && n % INDEX_WORK_YIELD_EVERY === 0) {
30485
+ await yieldToEventLoop();
31175
30486
  }
31176
- if (!opts.isNewSession) {
31177
- const agentCwd = this.sessionAgentCwd.get(sessionId);
31178
- if (agentCwd) return path20.resolve(agentCwd);
31179
- return void 0;
30487
+ const p = paths[i];
30488
+ if (p.toLowerCase().includes(q)) {
30489
+ out.push(p);
30490
+ if (out.length >= limit) break;
31180
30491
  }
31181
- const prep = await prepareNewSessionWorktrees({
31182
- rootAbs: this.rootAbs,
31183
- launcherCwd: getBridgeWorkspaceDirectory(),
31184
- sessionId,
31185
- layout: this.layout,
31186
- log: this.log
31187
- });
31188
- if (!prep) return void 0;
31189
- this.sessionPaths.set(sessionId, prep.worktreePaths);
31190
- this.sessionAgentCwd.set(sessionId, prep.agentCwd);
31191
- return path20.resolve(prep.agentCwd);
31192
- }
31193
- async renameSessionBranch(sessionId, newBranch) {
31194
- const paths = this.sessionPaths.get(sessionId);
31195
- if (!paths?.length) return;
31196
- await renameSessionWorktreeBranches(paths, newBranch, this.log);
31197
- }
31198
- /** True when this session runs in an isolated worktree mirror (not launcher cwd). */
31199
- usesWorktreeSession(sessionId) {
31200
- if (!sessionId) return false;
31201
- return this.sessionAgentCwd.has(sessionId);
31202
- }
31203
- getWorktreePathsForSession(sessionId) {
31204
- if (!sessionId) return void 0;
31205
- const paths = this.sessionPaths.get(sessionId);
31206
- return paths?.length ? [...paths] : void 0;
31207
- }
31208
- /** Session mirror root (parent of per-repo worktrees), when using worktrees for this session. */
31209
- getAgentCwdForSession(sessionId) {
31210
- if (!sessionId) return null;
31211
- const c = this.sessionAgentCwd.get(sessionId);
31212
- return c ? path20.resolve(c) : null;
31213
- }
31214
- async removeSessionWorktrees(sessionId) {
31215
- const paths = this.sessionPaths.get(sessionId);
31216
- this.sessionPaths.delete(sessionId);
31217
- this.sessionAgentCwd.delete(sessionId);
31218
- if (!paths?.length) return;
31219
- await removeSessionWorktrees(paths, this.log);
31220
- }
31221
- async commitSession(params) {
31222
- const paths = this.sessionPaths.get(params.sessionId);
31223
- const targets = paths?.length ? paths : [getBridgeWorkspaceDirectory()];
31224
- return commitSessionWorktrees({
31225
- paths: targets,
31226
- branch: params.branch,
31227
- message: params.message,
31228
- push: params.push
31229
- });
31230
- }
31231
- };
31232
- function defaultWorktreesRootAbs() {
31233
- return path20.join(os4.homedir(), ".buildautomaton", "worktrees");
31234
- }
31235
-
31236
- // src/auth/refresh-bridge-tokens.ts
31237
- async function refreshBridgeTokens(params) {
31238
- const base = params.apiUrl.replace(/\/$/, "");
31239
- const url2 = `${base}/api/bridges/tokens/refresh`;
31240
- try {
31241
- const res = await fetch(url2, {
31242
- method: "POST",
31243
- headers: { "Content-Type": "application/json" },
31244
- body: JSON.stringify({
31245
- workspaceId: params.workspaceId,
31246
- refreshToken: params.refreshToken
31247
- })
31248
- });
31249
- if (!res.ok) return null;
31250
- const data = await res.json();
31251
- if (typeof data.token !== "string" || typeof data.refreshToken !== "string") return null;
31252
- return {
31253
- token: data.token,
31254
- refreshToken: data.refreshToken,
31255
- workspaceId: typeof data.workspaceId === "string" ? data.workspaceId : params.workspaceId,
31256
- tokenId: typeof data.tokenId === "string" ? data.tokenId : ""
31257
- };
31258
- } catch {
31259
- return null;
30492
+ n++;
31260
30493
  }
30494
+ return out;
31261
30495
  }
31262
30496
 
31263
30497
  // src/files/watch-file-index.ts
31264
- import { watch } from "node:fs";
31265
- import path21 from "node:path";
31266
30498
  var DEBOUNCE_MS = 900;
31267
30499
  function shouldIgnoreRelative(rel) {
31268
30500
  const n = rel.replace(/\\/g, "/");
@@ -31302,19 +30534,15 @@ function createFsWatcher(resolved, schedule) {
31302
30534
  }
31303
30535
  }
31304
30536
  function startFileIndexWatcher(cwd = getBridgeWorkspaceDirectory()) {
31305
- const resolved = path21.resolve(cwd);
31306
- try {
31307
- buildFileIndex(resolved);
31308
- } catch (e) {
30537
+ const resolved = path20.resolve(cwd);
30538
+ void buildFileIndexAsync(resolved).catch((e) => {
31309
30539
  console.error("[file-index] Initial index build failed:", e);
31310
- }
30540
+ });
31311
30541
  let timer = null;
31312
30542
  const runRebuild = () => {
31313
- try {
31314
- buildFileIndex(resolved);
31315
- } catch (e) {
30543
+ void buildFileIndexAsync(resolved).catch((e) => {
31316
30544
  console.error("[file-index] Watch rebuild failed:", e);
31317
- }
30545
+ });
31318
30546
  };
31319
30547
  const schedule = () => {
31320
30548
  if (timer) clearTimeout(timer);
@@ -31375,7 +30603,7 @@ function forceKillChild(proc, log2, shortId, graceMs) {
31375
30603
  }
31376
30604
 
31377
30605
  // src/dev-servers/process/wire-dev-server-child-process.ts
31378
- import fs16 from "node:fs";
30606
+ import fs13 from "node:fs";
31379
30607
 
31380
30608
  // src/dev-servers/manager/forward-pipe.ts
31381
30609
  function forwardChildPipe(childReadable, terminal, onData) {
@@ -31411,7 +30639,7 @@ function wireDevServerChildProcess(d) {
31411
30639
  d.setPollInterval(void 0);
31412
30640
  return;
31413
30641
  }
31414
- fs16.readFile(d.mergedLogPath, (err, buf) => {
30642
+ fs13.readFile(d.mergedLogPath, (err, buf) => {
31415
30643
  if (err || (d.getSpawnGeneration() ?? 0) !== d.scheduledGen) return;
31416
30644
  if (buf.length <= d.mergedReadPos.value) return;
31417
30645
  const chunk = Buffer.from(buf.subarray(d.mergedReadPos.value));
@@ -31449,7 +30677,7 @@ ${errTail}` : ""}`);
31449
30677
  d.sendStatus(code === 0 || code == null ? "stopped" : "error", detail, tails);
31450
30678
  };
31451
30679
  if (mergedPath) {
31452
- fs16.readFile(mergedPath, (err, buf) => {
30680
+ fs13.readFile(mergedPath, (err, buf) => {
31453
30681
  if (!err && buf.length > d.mergedReadPos.value) {
31454
30682
  const chunk = Buffer.from(buf.subarray(d.mergedReadPos.value));
31455
30683
  if (chunk.length > 0) {
@@ -31551,13 +30779,13 @@ function parseDevServerDefs(servers) {
31551
30779
  }
31552
30780
 
31553
30781
  // src/dev-servers/manager/shell-spawn/utils.ts
31554
- import fs17 from "node:fs";
30782
+ import fs14 from "node:fs";
31555
30783
  function isSpawnEbadf(e) {
31556
30784
  return typeof e === "object" && e !== null && "code" in e && e.code === "EBADF";
31557
30785
  }
31558
30786
  function rmDirQuiet(dir) {
31559
30787
  try {
31560
- fs17.rmSync(dir, { recursive: true, force: true });
30788
+ fs14.rmSync(dir, { recursive: true, force: true });
31561
30789
  } catch {
31562
30790
  }
31563
30791
  }
@@ -31565,7 +30793,7 @@ var cachedDevNullReadFd;
31565
30793
  function devNullReadFd() {
31566
30794
  if (cachedDevNullReadFd === void 0) {
31567
30795
  const devPath = process.platform === "win32" ? "nul" : "/dev/null";
31568
- cachedDevNullReadFd = fs17.openSync(devPath, "r");
30796
+ cachedDevNullReadFd = fs14.openSync(devPath, "r");
31569
30797
  }
31570
30798
  return cachedDevNullReadFd;
31571
30799
  }
@@ -31639,15 +30867,15 @@ function trySpawnShellTruePiped(command, env, cwd, devNullFd, signal) {
31639
30867
 
31640
30868
  // src/dev-servers/manager/shell-spawn/try-spawn-merged-log-file.ts
31641
30869
  import { spawn as spawn7 } from "node:child_process";
31642
- import fs18 from "node:fs";
30870
+ import fs15 from "node:fs";
31643
30871
  import { tmpdir } from "node:os";
31644
- import path22 from "node:path";
30872
+ import path21 from "node:path";
31645
30873
  function trySpawnMergedLogFile(command, env, cwd, signal) {
31646
- const tmpRoot = fs18.mkdtempSync(path22.join(tmpdir(), "ba-devsrv-log-"));
31647
- const logPath = path22.join(tmpRoot, "combined.log");
30874
+ const tmpRoot = fs15.mkdtempSync(path21.join(tmpdir(), "ba-devsrv-log-"));
30875
+ const logPath = path21.join(tmpRoot, "combined.log");
31648
30876
  let logFd;
31649
30877
  try {
31650
- logFd = fs18.openSync(logPath, "a");
30878
+ logFd = fs15.openSync(logPath, "a");
31651
30879
  } catch {
31652
30880
  rmDirQuiet(tmpRoot);
31653
30881
  return null;
@@ -31666,7 +30894,7 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
31666
30894
  } else {
31667
30895
  proc = spawn7("/bin/sh", ["-c", command], { env, cwd, stdio, ...signal ? { signal } : {} });
31668
30896
  }
31669
- fs18.closeSync(logFd);
30897
+ fs15.closeSync(logFd);
31670
30898
  return {
31671
30899
  proc,
31672
30900
  pipedStdoutStderr: true,
@@ -31675,7 +30903,7 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
31675
30903
  };
31676
30904
  } catch (e) {
31677
30905
  try {
31678
- fs18.closeSync(logFd);
30906
+ fs15.closeSync(logFd);
31679
30907
  } catch {
31680
30908
  }
31681
30909
  rmDirQuiet(tmpRoot);
@@ -31686,22 +30914,22 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
31686
30914
 
31687
30915
  // src/dev-servers/manager/shell-spawn/try-spawn-shell-script-log-redirect.ts
31688
30916
  import { spawn as spawn8 } from "node:child_process";
31689
- import fs19 from "node:fs";
30917
+ import fs16 from "node:fs";
31690
30918
  import { tmpdir as tmpdir2 } from "node:os";
31691
- import path23 from "node:path";
30919
+ import path22 from "node:path";
31692
30920
  function shSingleQuote(s) {
31693
30921
  return `'${s.replace(/'/g, `'\\''`)}'`;
31694
30922
  }
31695
30923
  function trySpawnShellScriptLogRedirectUnix(command, env, cwd, signal) {
31696
- const tmpRoot = fs19.mkdtempSync(path23.join(tmpdir2(), "ba-devsrv-sh-"));
31697
- const logPath = path23.join(tmpRoot, "combined.log");
31698
- const innerPath = path23.join(tmpRoot, "_cmd.sh");
31699
- const runnerPath = path23.join(tmpRoot, "_run.sh");
30924
+ const tmpRoot = fs16.mkdtempSync(path22.join(tmpdir2(), "ba-devsrv-sh-"));
30925
+ const logPath = path22.join(tmpRoot, "combined.log");
30926
+ const innerPath = path22.join(tmpRoot, "_cmd.sh");
30927
+ const runnerPath = path22.join(tmpRoot, "_run.sh");
31700
30928
  try {
31701
- fs19.writeFileSync(innerPath, `#!/bin/sh
30929
+ fs16.writeFileSync(innerPath, `#!/bin/sh
31702
30930
  ${command}
31703
30931
  `);
31704
- fs19.writeFileSync(
30932
+ fs16.writeFileSync(
31705
30933
  runnerPath,
31706
30934
  `#!/bin/sh
31707
30935
  cd ${shSingleQuote(cwd)}
@@ -31727,13 +30955,13 @@ cd ${shSingleQuote(cwd)}
31727
30955
  }
31728
30956
  }
31729
30957
  function trySpawnShellScriptLogRedirectWin(command, env, cwd, signal) {
31730
- const tmpRoot = fs19.mkdtempSync(path23.join(tmpdir2(), "ba-devsrv-sh-"));
31731
- const logPath = path23.join(tmpRoot, "combined.log");
31732
- const runnerPath = path23.join(tmpRoot, "_run.bat");
30958
+ const tmpRoot = fs16.mkdtempSync(path22.join(tmpdir2(), "ba-devsrv-sh-"));
30959
+ const logPath = path22.join(tmpRoot, "combined.log");
30960
+ const runnerPath = path22.join(tmpRoot, "_run.bat");
31733
30961
  const q = (p) => `"${p.replace(/"/g, '""')}"`;
31734
30962
  const com = process.env.ComSpec || "cmd.exe";
31735
30963
  try {
31736
- fs19.writeFileSync(
30964
+ fs16.writeFileSync(
31737
30965
  runnerPath,
31738
30966
  `@ECHO OFF\r
31739
30967
  CD /D ${q(cwd)}\r
@@ -32328,213 +31556,1194 @@ function connectFirehose(options) {
32328
31556
  };
32329
31557
  }
32330
31558
 
32331
- // src/bridge/connection/create-bridge-identified-handler.ts
32332
- function createOnBridgeIdentified(opts) {
32333
- const { sessionWorktreeManager, devServerManager, firehoseServerUrl, workspaceId, state, logFn } = opts;
32334
- function clearFirehoseReconnectTimer() {
32335
- if (state.firehoseReconnectTimeout != null) {
32336
- clearTimeout(state.firehoseReconnectTimeout);
32337
- state.firehoseReconnectTimeout = null;
32338
- }
32339
- }
32340
- function firehoseCtx() {
32341
- return {
32342
- closedByUser: state.closedByUser,
32343
- currentWs: state.currentWs,
32344
- firehoseHandle: state.firehoseHandle,
32345
- firehoseQuiet: state.firehoseQuiet
32346
- };
32347
- }
32348
- function attachFirehose(params) {
32349
- state.lastFirehoseParams = params;
32350
- clearFirehoseReconnectTimer();
32351
- if (state.firehoseReconnectAttempt === 0) {
32352
- logFn("Connecting to preview tunnel (local HTTP proxy and dev logs)\u2026");
32353
- }
32354
- state.firehoseGeneration += 1;
32355
- const myGen = state.firehoseGeneration;
32356
- if (state.firehoseHandle) {
32357
- state.firehoseHandle.close();
32358
- state.firehoseHandle = null;
32359
- }
32360
- state.firehoseHandle = connectFirehose({
32361
- firehoseServerUrl: params.firehoseServerUrl,
32362
- workspaceId: params.workspaceId,
32363
- bridgeName: params.bridgeName,
32364
- proxyPorts: params.proxyPorts,
32365
- log: logFn,
32366
- devServerManager,
32367
- onOpen: () => {
32368
- if (myGen !== state.firehoseGeneration) return;
32369
- clearFirehoseReconnectQuietOnOpen({ firehoseQuiet: state.firehoseQuiet }, logFn);
32370
- const logOpenAsFirehoseReconnect = state.firehoseReconnectAttempt > 0;
32371
- state.firehoseReconnectAttempt = 0;
32372
- if (!logOpenAsFirehoseReconnect) {
32373
- logFn("Connected to preview tunnel (local HTTP proxy and dev logs).");
32374
- }
32375
- },
32376
- onClose: (code, reason) => {
32377
- if (myGen !== state.firehoseGeneration) return;
32378
- state.firehoseHandle = null;
32379
- if (state.closedByUser) return;
32380
- const main = state.currentWs;
32381
- if (!main || main.readyState !== wrapper_default.OPEN) {
32382
- logFn(
32383
- `${PROXY_AND_LOG_SERVICE_LABEL} Not reconnecting preview and log stream: main bridge connection is not open.`
32384
- );
32385
- return;
32386
- }
32387
- beginFirehoseDeferredDisconnect(firehoseCtx(), code, reason, logFn);
32388
- clearFirehoseReconnectTimer();
32389
- const delay2 = reconnectDelayMs(state.firehoseReconnectAttempt);
32390
- state.firehoseReconnectAttempt += 1;
32391
- logNextReconnectAttempt(
32392
- logFn,
32393
- PROXY_AND_LOG_SERVICE_LABEL,
32394
- state.firehoseQuiet,
32395
- delay2,
32396
- state.firehoseReconnectAttempt
32397
- );
32398
- state.firehoseReconnectTimeout = setTimeout(() => {
32399
- state.firehoseReconnectTimeout = null;
32400
- if (state.closedByUser) return;
32401
- const w = state.currentWs;
32402
- if (!w || w.readyState !== wrapper_default.OPEN) {
32403
- if (state.firehoseQuiet.verboseLogs) {
32404
- logFn(
32405
- `${PROXY_AND_LOG_SERVICE_LABEL} Reconnect skipped: main bridge connection closed before preview stream could reconnect.`
32406
- );
32407
- }
32408
- return;
32409
- }
32410
- const p = state.lastFirehoseParams;
32411
- if (!p) {
32412
- if (state.firehoseQuiet.verboseLogs) {
32413
- logFn(`${PROXY_AND_LOG_SERVICE_LABEL} Reconnect skipped: no stored connection parameters.`);
32414
- }
32415
- return;
32416
- }
32417
- attachFirehose(p);
32418
- }, delay2);
32419
- }
32420
- });
32421
- }
32422
- return (msg) => {
32423
- sessionWorktreeManager.setBridgeSessionWorktrees(msg.sessionWorktreesEnabled === true);
32424
- const bridgeName = msg.bridgeName;
32425
- const proxyPorts = Array.isArray(msg.proxyPorts) ? msg.proxyPorts : [];
32426
- const devServers = msg.devServers ?? [];
32427
- setImmediate(() => {
32428
- devServerManager.applyConfig(devServers);
32429
- if (!firehoseServerUrl || typeof bridgeName !== "string" || !bridgeName) return;
32430
- state.firehoseReconnectAttempt = 0;
32431
- attachFirehose({
32432
- firehoseServerUrl,
32433
- workspaceId,
32434
- bridgeName,
32435
- proxyPorts
32436
- });
32437
- });
32438
- };
31559
+ // src/bridge/connection/attach-firehose-after-identified.ts
31560
+ function attachFirehoseAfterIdentified(ctx, params) {
31561
+ const { state, devServerManager, logFn } = ctx;
31562
+ function clearFirehoseReconnectTimer() {
31563
+ if (state.firehoseReconnectTimeout != null) {
31564
+ clearTimeout(state.firehoseReconnectTimeout);
31565
+ state.firehoseReconnectTimeout = null;
31566
+ }
31567
+ }
31568
+ function firehoseCtx() {
31569
+ return {
31570
+ closedByUser: state.closedByUser,
31571
+ currentWs: state.currentWs,
31572
+ firehoseHandle: state.firehoseHandle,
31573
+ firehoseQuiet: state.firehoseQuiet
31574
+ };
31575
+ }
31576
+ state.lastFirehoseParams = params;
31577
+ clearFirehoseReconnectTimer();
31578
+ if (state.firehoseReconnectAttempt === 0) {
31579
+ logFn("Connecting to preview tunnel (local HTTP proxy and dev logs)\u2026");
31580
+ }
31581
+ state.firehoseGeneration += 1;
31582
+ const myGen = state.firehoseGeneration;
31583
+ if (state.firehoseHandle) {
31584
+ state.firehoseHandle.close();
31585
+ state.firehoseHandle = null;
31586
+ }
31587
+ state.firehoseHandle = connectFirehose({
31588
+ firehoseServerUrl: params.firehoseServerUrl,
31589
+ workspaceId: params.workspaceId,
31590
+ bridgeName: params.bridgeName,
31591
+ proxyPorts: params.proxyPorts,
31592
+ log: logFn,
31593
+ devServerManager,
31594
+ onOpen: () => {
31595
+ if (myGen !== state.firehoseGeneration) return;
31596
+ clearFirehoseReconnectQuietOnOpen({ firehoseQuiet: state.firehoseQuiet }, logFn);
31597
+ const logOpenAsFirehoseReconnect = state.firehoseReconnectAttempt > 0;
31598
+ state.firehoseReconnectAttempt = 0;
31599
+ if (!logOpenAsFirehoseReconnect) {
31600
+ logFn("Connected to preview tunnel (local HTTP proxy and dev logs).");
31601
+ }
31602
+ },
31603
+ onClose: (code, reason) => {
31604
+ if (myGen !== state.firehoseGeneration) return;
31605
+ state.firehoseHandle = null;
31606
+ if (state.closedByUser) return;
31607
+ const main = state.currentWs;
31608
+ if (!main || main.readyState !== wrapper_default.OPEN) {
31609
+ logFn(
31610
+ `${PROXY_AND_LOG_SERVICE_LABEL} Not reconnecting preview and log stream: main bridge connection is not open.`
31611
+ );
31612
+ return;
31613
+ }
31614
+ beginFirehoseDeferredDisconnect(firehoseCtx(), code, reason, logFn);
31615
+ clearFirehoseReconnectTimer();
31616
+ const delay2 = reconnectDelayMs(state.firehoseReconnectAttempt);
31617
+ state.firehoseReconnectAttempt += 1;
31618
+ logNextReconnectAttempt(
31619
+ logFn,
31620
+ PROXY_AND_LOG_SERVICE_LABEL,
31621
+ state.firehoseQuiet,
31622
+ delay2,
31623
+ state.firehoseReconnectAttempt
31624
+ );
31625
+ state.firehoseReconnectTimeout = setTimeout(() => {
31626
+ state.firehoseReconnectTimeout = null;
31627
+ if (state.closedByUser) return;
31628
+ const w = state.currentWs;
31629
+ if (!w || w.readyState !== wrapper_default.OPEN) {
31630
+ if (state.firehoseQuiet.verboseLogs) {
31631
+ logFn(
31632
+ `${PROXY_AND_LOG_SERVICE_LABEL} Reconnect skipped: main bridge connection closed before preview stream could reconnect.`
31633
+ );
31634
+ }
31635
+ return;
31636
+ }
31637
+ const p = state.lastFirehoseParams;
31638
+ if (!p) {
31639
+ if (state.firehoseQuiet.verboseLogs) {
31640
+ logFn(`${PROXY_AND_LOG_SERVICE_LABEL} Reconnect skipped: no stored connection parameters.`);
31641
+ }
31642
+ return;
31643
+ }
31644
+ attachFirehoseAfterIdentified(ctx, p);
31645
+ }, delay2);
31646
+ }
31647
+ });
31648
+ }
31649
+
31650
+ // src/bridge/connection/create-bridge-identified-handler.ts
31651
+ function createOnBridgeIdentified(opts) {
31652
+ const { sessionWorktreeManager, devServerManager, firehoseServerUrl, workspaceId, state, logFn } = opts;
31653
+ const firehoseCtx = { state, devServerManager, logFn };
31654
+ return (msg) => {
31655
+ sessionWorktreeManager.setBridgeSessionWorktrees(msg.sessionWorktreesEnabled === true);
31656
+ const bridgeName = msg.bridgeName;
31657
+ const proxyPorts = Array.isArray(msg.proxyPorts) ? msg.proxyPorts : [];
31658
+ const devServers = msg.devServers ?? [];
31659
+ setImmediate(() => {
31660
+ devServerManager.applyConfig(devServers);
31661
+ if (!firehoseServerUrl || typeof bridgeName !== "string" || !bridgeName) return;
31662
+ state.firehoseReconnectAttempt = 0;
31663
+ attachFirehoseAfterIdentified(firehoseCtx, {
31664
+ firehoseServerUrl,
31665
+ workspaceId,
31666
+ bridgeName,
31667
+ proxyPorts
31668
+ });
31669
+ });
31670
+ };
31671
+ }
31672
+
31673
+ // src/skills/discover-local-agent-skills.ts
31674
+ import fs17 from "node:fs";
31675
+ import path23 from "node:path";
31676
+ var SKILL_DISCOVERY_ROOTS = [".agents/skills", ".claude/skills", ".cursor/skills", "skills"];
31677
+ function discoverLocalSkills(cwd) {
31678
+ const out = [];
31679
+ const seenKeys = /* @__PURE__ */ new Set();
31680
+ for (const rel of SKILL_DISCOVERY_ROOTS) {
31681
+ const base = path23.join(cwd, rel);
31682
+ if (!fs17.existsSync(base) || !fs17.statSync(base).isDirectory()) continue;
31683
+ let entries = [];
31684
+ try {
31685
+ entries = fs17.readdirSync(base);
31686
+ } catch {
31687
+ continue;
31688
+ }
31689
+ for (const name of entries) {
31690
+ const dir = path23.join(base, name);
31691
+ try {
31692
+ if (!fs17.statSync(dir).isDirectory()) continue;
31693
+ } catch {
31694
+ continue;
31695
+ }
31696
+ const skillMd = path23.join(dir, "SKILL.md");
31697
+ if (!fs17.existsSync(skillMd)) continue;
31698
+ const key = `${rel}/${name}`;
31699
+ if (seenKeys.has(key)) continue;
31700
+ seenKeys.add(key);
31701
+ out.push({ skillKey: name, path: `${rel}/${name}`.replace(/\\/g, "/") });
31702
+ }
31703
+ }
31704
+ return out;
31705
+ }
31706
+ function discoverSkillLayoutRoots(cwd) {
31707
+ const roots = [];
31708
+ for (const rel of SKILL_DISCOVERY_ROOTS) {
31709
+ const base = path23.join(cwd, rel);
31710
+ if (!fs17.existsSync(base) || !fs17.statSync(base).isDirectory()) continue;
31711
+ let entries = [];
31712
+ try {
31713
+ entries = fs17.readdirSync(base);
31714
+ } catch {
31715
+ continue;
31716
+ }
31717
+ const skills2 = [];
31718
+ for (const name of entries) {
31719
+ const dir = path23.join(base, name);
31720
+ try {
31721
+ if (!fs17.statSync(dir).isDirectory()) continue;
31722
+ } catch {
31723
+ continue;
31724
+ }
31725
+ if (!fs17.existsSync(path23.join(dir, "SKILL.md"))) continue;
31726
+ const relPath = `${rel}/${name}`.replace(/\\/g, "/");
31727
+ skills2.push({ name, relPath });
31728
+ }
31729
+ if (skills2.length > 0) {
31730
+ roots.push({ path: rel.replace(/\\/g, "/"), skills: skills2 });
31731
+ }
31732
+ }
31733
+ return roots;
31734
+ }
31735
+
31736
+ // src/agents/detect-local-agent-types.ts
31737
+ var LOCAL_AGENT_ACP_MODULES = [
31738
+ cursor_acp_client_exports,
31739
+ codex_acp_client_exports,
31740
+ kiro_acp_client_exports,
31741
+ claude_code_acp_client_exports
31742
+ ];
31743
+ async function detectLocalAgentTypes() {
31744
+ try {
31745
+ const out = [];
31746
+ for (let i = 0; i < LOCAL_AGENT_ACP_MODULES.length; i++) {
31747
+ if (i > 0) {
31748
+ await yieldToEventLoop();
31749
+ }
31750
+ const mod = LOCAL_AGENT_ACP_MODULES[i];
31751
+ try {
31752
+ if (await mod.detectLocalAgentPresence()) out.push(mod.BACKEND_LOCAL_AGENT_TYPE);
31753
+ } catch {
31754
+ }
31755
+ }
31756
+ return out;
31757
+ } catch {
31758
+ return [];
31759
+ }
31760
+ }
31761
+
31762
+ // src/bridge/connection/create-bridge-local-reports.ts
31763
+ function createSendLocalSkillsReport(getWs, logFn) {
31764
+ return () => {
31765
+ setImmediate(() => {
31766
+ try {
31767
+ const socket = getWs();
31768
+ if (!socket || socket.readyState !== wrapper_default.OPEN) return;
31769
+ const skills2 = discoverLocalSkills(getBridgeWorkspaceDirectory());
31770
+ socket.send(JSON.stringify({ type: "local_skills", skills: skills2 }));
31771
+ } catch (e) {
31772
+ logFn(
31773
+ `[Bridge service] Local skills report failed: ${e instanceof Error ? e.message : String(e)}`
31774
+ );
31775
+ }
31776
+ });
31777
+ };
31778
+ }
31779
+ function createReportAutoDetectedAgents(getWs, logFn) {
31780
+ return async () => {
31781
+ try {
31782
+ const types = await detectLocalAgentTypes();
31783
+ const socket = getWs();
31784
+ if (socket && socket.readyState === wrapper_default.OPEN) {
31785
+ sendWsMessage(socket, { type: "auto_detected_agents_report", agentTypes: types });
31786
+ }
31787
+ } catch (e) {
31788
+ logFn(
31789
+ `[Bridge service] Auto-detected agents report failed: ${e instanceof Error ? e.message : String(e)}`
31790
+ );
31791
+ }
31792
+ };
31793
+ }
31794
+
31795
+ // src/bridge/connection/build-bridge-url.ts
31796
+ function buildBridgeUrl(apiUrl, workspaceId, authToken) {
31797
+ const base = apiUrl.startsWith("https") ? apiUrl.replace(/^https/, "wss") : apiUrl.replace(/^http/, "ws");
31798
+ const params = new URLSearchParams({ workspaceId, token: authToken });
31799
+ return `${base}/ws/bridge?${params.toString()}`;
31800
+ }
31801
+
31802
+ // src/bridge/connection/report-git-repos.ts
31803
+ function reportGitRepos(getWs, log2) {
31804
+ setImmediate(() => {
31805
+ discoverGitRepos().then((repos) => {
31806
+ if (repos.length > 0) {
31807
+ const socket = getWs();
31808
+ if (socket) {
31809
+ sendWsMessage(socket, {
31810
+ type: "git_repos",
31811
+ repos: repos.map((r) => ({ absolutePath: r.absolutePath, remoteUrl: r.remoteUrl }))
31812
+ });
31813
+ }
31814
+ }
31815
+ }).catch((err) => {
31816
+ log2(
31817
+ `[Bridge service] Git repository discovery failed: ${err instanceof Error ? err.message : String(err)}`
31818
+ );
31819
+ });
31820
+ });
31821
+ }
31822
+
31823
+ // src/bridge/routing/handlers/auth-token.ts
31824
+ var handleAuthToken = (msg, { log: log2 }) => {
31825
+ if (typeof msg.token !== "string") return;
31826
+ log2("Received auth token. Save it for future runs:");
31827
+ log2(` export BUILDAMATON_AUTH_TOKEN="${msg.token}"`);
31828
+ };
31829
+
31830
+ // src/bridge/routing/handlers/bridge-identified.ts
31831
+ var handleBridgeIdentified = (msg, deps) => {
31832
+ if (typeof msg.bridgeName !== "string") return;
31833
+ deps.onBridgeIdentified(
31834
+ msg
31835
+ );
31836
+ setImmediate(() => {
31837
+ void (async () => {
31838
+ try {
31839
+ await deps.reportAutoDetectedAgents?.();
31840
+ } catch (e) {
31841
+ deps.log(
31842
+ `[Bridge service] Auto-detect agents failed: ${e instanceof Error ? e.message : String(e)}`
31843
+ );
31844
+ }
31845
+ })();
31846
+ });
31847
+ setImmediate(() => {
31848
+ try {
31849
+ deps.sendLocalSkillsReport?.();
31850
+ } catch (e) {
31851
+ deps.log(
31852
+ `[Bridge service] Local skills report failed: ${e instanceof Error ? e.message : String(e)}`
31853
+ );
31854
+ }
31855
+ });
31856
+ };
31857
+
31858
+ // src/agents/acp/from-bridge/handle-bridge-agent-config.ts
31859
+ function handleBridgeAgentConfig(msg, { acpManager }) {
31860
+ if (!Array.isArray(msg.agents) || msg.agents.length === 0) return;
31861
+ acpManager.setPreferredAgentType(msg.agents[0].type);
31862
+ }
31863
+
31864
+ // src/bridge/routing/handlers/agent-config.ts
31865
+ var handleAgentConfigMessage = (msg, deps) => {
31866
+ handleBridgeAgentConfig(msg, deps);
31867
+ };
31868
+
31869
+ // src/agents/acp/from-bridge/handle-bridge-prompt.ts
31870
+ import * as path25 from "node:path";
31871
+ import { execFile as execFile5 } from "node:child_process";
31872
+ import { promisify as promisify5 } from "node:util";
31873
+
31874
+ // src/git/bridge-queue-key.ts
31875
+ import * as path24 from "node:path";
31876
+ import { createHash } from "node:crypto";
31877
+ function normalizeCanonicalGitUrl(url2) {
31878
+ let s = url2.trim();
31879
+ if (!s) return s;
31880
+ if (s.toLowerCase().endsWith(".git")) {
31881
+ s = s.slice(0, -4);
31882
+ }
31883
+ s = s.replace(/\/+$/, "");
31884
+ const httpsMatch = /^(https?):\/\/([^/]+)(\/.*)?$/i.exec(s);
31885
+ if (httpsMatch) {
31886
+ const host = httpsMatch[2].toLowerCase();
31887
+ const p = httpsMatch[3] ?? "";
31888
+ s = `${httpsMatch[1].toLowerCase()}://${host}${p}`;
31889
+ } else {
31890
+ const sshMatch = /^git@([^:]+):(.+)$/i.exec(s);
31891
+ if (sshMatch) {
31892
+ const host = sshMatch[1].toLowerCase();
31893
+ s = `git@${host}:${sshMatch[2]}`;
31894
+ }
31895
+ }
31896
+ return s;
31897
+ }
31898
+ function canonicalUrlToRepoIdSync(url2) {
31899
+ const normalized = normalizeCanonicalGitUrl(url2);
31900
+ return createHash("sha256").update(normalized).digest("hex").slice(0, 32);
31901
+ }
31902
+ function fallbackRepoIdFromPath(absPath) {
31903
+ return createHash("sha256").update(path24.resolve(absPath)).digest("hex").slice(0, 32);
31904
+ }
31905
+ async function resolveBridgeQueueBindFields(options) {
31906
+ const { effectiveCwd, worktreePaths, primaryRepoRoots, log: log2 } = options;
31907
+ const cwdAbs = worktreePaths.length > 0 ? path24.resolve(worktreePaths[0]) : path24.resolve(effectiveCwd);
31908
+ if (!primaryRepoRoots.length) {
31909
+ log2("[Bridge service] Prompt queue bind skipped: no Git repository roots under the working directory.");
31910
+ return null;
31911
+ }
31912
+ let primaryRoot = primaryRepoRoots[0];
31913
+ let remote = await getRemoteOriginUrl(primaryRoot);
31914
+ if (!remote) {
31915
+ for (const r of primaryRepoRoots.slice(1)) {
31916
+ const u = await getRemoteOriginUrl(r);
31917
+ if (u) {
31918
+ primaryRoot = r;
31919
+ remote = u;
31920
+ break;
31921
+ }
31922
+ }
31923
+ }
31924
+ const repoId = remote ? canonicalUrlToRepoIdSync(remote) : fallbackRepoIdFromPath(primaryRoot);
31925
+ const canonicalQueueKey = `repo:${repoId}::cwd:${cwdAbs}`;
31926
+ return { canonicalQueueKey, repoId, cwdAbs };
31927
+ }
31928
+
31929
+ // src/agents/acp/from-bridge/handle-bridge-prompt.ts
31930
+ var execFileAsync5 = promisify5(execFile5);
31931
+ async function readGitBranch(cwd) {
31932
+ try {
31933
+ const { stdout } = await execFileAsync5("git", ["branch", "--show-current"], { cwd, maxBuffer: 64 * 1024 });
31934
+ const b = stdout.trim();
31935
+ return b || null;
31936
+ } catch {
31937
+ return null;
31938
+ }
31939
+ }
31940
+ function handleBridgePrompt(msg, deps) {
31941
+ const { getWs, log: log2, acpManager, sessionWorktreeManager } = deps;
31942
+ const rawPrompt = msg.prompt;
31943
+ const promptText = typeof rawPrompt === "string" ? rawPrompt : rawPrompt != null ? String(rawPrompt) : "";
31944
+ if (!promptText.trim()) {
31945
+ log2(
31946
+ `[Bridge service] Prompt ignored: empty or missing prompt text (session ${typeof msg.sessionId === "string" ? msg.sessionId.slice(0, 8) : "\u2014"}\u2026, run ${typeof msg.runId === "string" ? msg.runId.slice(0, 8) : "\u2014"}\u2026).`
31947
+ );
31948
+ return;
31949
+ }
31950
+ const sessionId = msg.sessionId;
31951
+ const isNewSession = msg.isNewSession === true;
31952
+ const sessionWorktreesEnabled = msg.sessionWorktreesEnabled === true;
31953
+ const agentType = typeof msg.agentType === "string" && msg.agentType.trim() ? msg.agentType.trim() : void 0;
31954
+ const runId = typeof msg.runId === "string" ? msg.runId : void 0;
31955
+ const mode = typeof msg.mode === "string" && msg.mode.trim() ? msg.mode.trim() : void 0;
31956
+ acpManager.logPromptReceivedFromBridge({ agentType, mode });
31957
+ const sendResult2 = (result) => {
31958
+ const s = getWs();
31959
+ if (s) sendWsMessage(s, result);
31960
+ };
31961
+ const sendSessionUpdate = (payload) => {
31962
+ const s = getWs();
31963
+ if (!s) {
31964
+ log2("[Bridge service] Session update not sent: not connected to the bridge.");
31965
+ return;
31966
+ }
31967
+ const p = payload;
31968
+ sendWsMessage(s, payload);
31969
+ };
31970
+ async function preambleAndPrompt(resolvedCwd) {
31971
+ const s = getWs();
31972
+ const effectiveCwd = path25.resolve(resolvedCwd ?? getBridgeWorkspaceDirectory());
31973
+ const worktreePaths = sessionWorktreeManager.getWorktreePathsForSession(sessionId) ?? [];
31974
+ const repoRoots = await resolveSnapshotRepoRoots({
31975
+ worktreePaths,
31976
+ fallbackCwd: effectiveCwd,
31977
+ log: log2
31978
+ });
31979
+ if (s && sessionId) {
31980
+ const bind = await resolveBridgeQueueBindFields({
31981
+ effectiveCwd,
31982
+ worktreePaths,
31983
+ primaryRepoRoots: repoRoots,
31984
+ log: log2
31985
+ });
31986
+ if (bind) {
31987
+ sendWsMessage(s, {
31988
+ type: "bridge_queue_bind",
31989
+ sessionId,
31990
+ canonicalQueueKey: bind.canonicalQueueKey,
31991
+ repoId: bind.repoId,
31992
+ cwdAbs: bind.cwdAbs
31993
+ });
31994
+ }
31995
+ }
31996
+ if (s && sessionId) {
31997
+ const cliGitBranch = await readGitBranch(effectiveCwd);
31998
+ sendWsMessage(s, {
31999
+ type: "session_git_context_report",
32000
+ sessionId,
32001
+ cliGitBranch,
32002
+ agentUsesWorktree: sessionWorktreeManager.usesWorktreeSession(sessionId)
32003
+ });
32004
+ }
32005
+ if (s && sessionId && runId) {
32006
+ const cap = repoRoots.length > 0 ? await capturePreTurnSnapshot({ runId, repoRoots, agentCwd: effectiveCwd, log: log2 }) : { ok: false, error: "No git repos" };
32007
+ sendWsMessage(s, {
32008
+ type: "pre_turn_snapshot_report",
32009
+ sessionId,
32010
+ turnId: runId,
32011
+ captured: cap.ok
32012
+ });
32013
+ }
32014
+ acpManager.handlePrompt({
32015
+ promptText,
32016
+ promptId: msg.id,
32017
+ sessionId,
32018
+ runId,
32019
+ mode,
32020
+ agentType,
32021
+ cwd: effectiveCwd,
32022
+ sendResult: sendResult2,
32023
+ sendSessionUpdate
32024
+ });
32025
+ }
32026
+ void sessionWorktreeManager.resolveCwdForPrompt(sessionId, { isNewSession, sessionWorktreesEnabled }).then((cwd) => preambleAndPrompt(cwd)).catch((err) => {
32027
+ log2(`[Agent] Worktree resolve failed: ${err instanceof Error ? err.message : String(err)}`);
32028
+ void preambleAndPrompt(void 0);
32029
+ });
32030
+ }
32031
+
32032
+ // src/bridge/routing/handlers/prompt.ts
32033
+ var handlePromptMessage = (msg, deps) => {
32034
+ handleBridgePrompt(msg, deps);
32035
+ };
32036
+
32037
+ // src/agents/acp/from-bridge/handle-bridge-cancel-run.ts
32038
+ function handleBridgeCancelRun(msg, { log: log2, acpManager }) {
32039
+ const runId = msg.runId;
32040
+ if (!runId) return;
32041
+ void acpManager.cancelRun(runId).then((sent) => {
32042
+ if (!sent) {
32043
+ log2(
32044
+ `[Agent] Cancel ignored for run ${runId.slice(0, 8)}\u2026 (no active run or cancel not available).`
32045
+ );
32046
+ }
32047
+ });
32048
+ }
32049
+
32050
+ // src/bridge/routing/handlers/cancel-run.ts
32051
+ var handleCancelRunMessage = (msg, deps) => {
32052
+ handleBridgeCancelRun(msg, deps);
32053
+ };
32054
+
32055
+ // src/agents/acp/from-bridge/handle-bridge-cursor-request-response.ts
32056
+ function handleBridgeCursorRequestResponse(msg, { acpManager }) {
32057
+ if (typeof msg.requestId !== "string") return;
32058
+ acpManager.resolveRequest(msg.requestId, msg.result ?? {});
32059
+ }
32060
+
32061
+ // src/bridge/routing/handlers/cursor-request-response.ts
32062
+ var handleCursorRequestResponseMessage = (msg, deps) => {
32063
+ handleBridgeCursorRequestResponse(msg, deps);
32064
+ };
32065
+
32066
+ // src/skills/handle-skill-call.ts
32067
+ function handleSkillCall(msg, socket, log2) {
32068
+ callSkill(msg.skillId, msg.operationId, msg.params ?? {}).then((result) => {
32069
+ sendWsMessage(socket, { type: "skill_result", id: msg.id, result });
32070
+ }).catch((err) => {
32071
+ sendWsMessage(socket, { type: "skill_result", id: msg.id, error: String(err) });
32072
+ log2(`[Bridge service] Skill invocation failed (${msg.skillId}/${msg.operationId}): ${err}`);
32073
+ });
32074
+ }
32075
+
32076
+ // src/bridge/routing/handlers/skill-call.ts
32077
+ var handleSkillCallMessage = (msg, { getWs, log: log2 }) => {
32078
+ if (!msg.skillId || msg.operationId === void 0) return;
32079
+ const socket = getWs();
32080
+ if (!socket) return;
32081
+ handleSkillCall(
32082
+ msg,
32083
+ socket,
32084
+ log2
32085
+ );
32086
+ };
32087
+
32088
+ // src/files/list-dir.ts
32089
+ import fs18 from "node:fs";
32090
+ import path27 from "node:path";
32091
+
32092
+ // src/files/ensure-under-cwd.ts
32093
+ import path26 from "node:path";
32094
+ function ensureUnderCwd(relativePath, cwd = getBridgeWorkspaceDirectory()) {
32095
+ const normalized = path26.normalize(relativePath).replace(/^(\.\/)+/, "");
32096
+ const resolved = path26.resolve(cwd, normalized);
32097
+ if (!resolved.startsWith(cwd + path26.sep) && resolved !== cwd) {
32098
+ return null;
32099
+ }
32100
+ return resolved;
32101
+ }
32102
+
32103
+ // src/files/list-dir.ts
32104
+ var LIST_DIR_YIELD_EVERY = 256;
32105
+ async function listDirAsync(relativePath) {
32106
+ const resolved = ensureUnderCwd(relativePath || ".", getBridgeWorkspaceDirectory());
32107
+ if (!resolved) {
32108
+ return { error: "Path is outside working directory" };
32109
+ }
32110
+ try {
32111
+ const names = await fs18.promises.readdir(resolved, { withFileTypes: true });
32112
+ const visible = names.filter((d) => !d.name.startsWith("."));
32113
+ const entries = [];
32114
+ for (let i = 0; i < visible.length; i++) {
32115
+ if (i > 0 && i % LIST_DIR_YIELD_EVERY === 0) {
32116
+ await yieldToEventLoop();
32117
+ }
32118
+ const d = visible[i];
32119
+ const entryPath = path27.join(relativePath || ".", d.name).replace(/\\/g, "/");
32120
+ const fullPath = path27.join(resolved, d.name);
32121
+ let isDir = d.isDirectory();
32122
+ if (d.isSymbolicLink()) {
32123
+ try {
32124
+ const targetStat = await fs18.promises.stat(fullPath);
32125
+ isDir = targetStat.isDirectory();
32126
+ } catch {
32127
+ isDir = false;
32128
+ }
32129
+ }
32130
+ entries.push({
32131
+ name: d.name,
32132
+ path: entryPath,
32133
+ isDir,
32134
+ isSymlink: d.isSymbolicLink()
32135
+ });
32136
+ }
32137
+ entries.sort((a, b) => {
32138
+ if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
32139
+ return a.name.localeCompare(b.name, void 0, { sensitivity: "base" });
32140
+ });
32141
+ return { entries };
32142
+ } catch (err) {
32143
+ const message = err instanceof Error ? err.message : String(err);
32144
+ return { error: message };
32145
+ }
32146
+ }
32147
+
32148
+ // src/files/read-file.ts
32149
+ import fs19 from "node:fs";
32150
+ import { StringDecoder } from "node:string_decoder";
32151
+ function resolveFilePath(relativePath) {
32152
+ const resolved = ensureUnderCwd(relativePath, getBridgeWorkspaceDirectory());
32153
+ if (!resolved) return { error: "Path is outside working directory" };
32154
+ let real;
32155
+ try {
32156
+ real = fs19.realpathSync(resolved);
32157
+ } catch {
32158
+ real = resolved;
32159
+ }
32160
+ const stat2 = fs19.statSync(real);
32161
+ if (!stat2.isFile()) return { error: "Not a file" };
32162
+ return real;
32163
+ }
32164
+ var LINE_CHUNK_SIZE = 64 * 1024;
32165
+ function readFileRange(filePath, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
32166
+ const fileSize = fs19.statSync(filePath).size;
32167
+ const fd = fs19.openSync(filePath, "r");
32168
+ const bufSize = 64 * 1024;
32169
+ const buf = Buffer.alloc(bufSize);
32170
+ const decoder = new StringDecoder("utf8");
32171
+ let currentLine = 0;
32172
+ const resultLines = [];
32173
+ let partial2 = "";
32174
+ let done = false;
32175
+ let skipLine0Chars = typeof lineOffsetIn === "number" ? lineOffsetIn : 0;
32176
+ let line0CharsReturned = 0;
32177
+ let line0Accum = "";
32178
+ try {
32179
+ let bytesRead;
32180
+ while (!done && (bytesRead = fs19.readSync(fd, buf, 0, bufSize, null)) > 0) {
32181
+ const text = partial2 + decoder.write(buf.subarray(0, bytesRead));
32182
+ partial2 = "";
32183
+ let lineStart = 0;
32184
+ for (let i = 0; i < text.length; i++) {
32185
+ if (text[i] === "\n") {
32186
+ const lineContent = (() => {
32187
+ let lineEnd = i;
32188
+ if (lineEnd > lineStart && text[lineEnd - 1] === "\r") lineEnd--;
32189
+ return text.slice(lineStart, lineEnd);
32190
+ })();
32191
+ if (currentLine === 0 && (startLine === 0 || lineOffsetIn !== void 0)) {
32192
+ line0Accum += lineContent;
32193
+ const totalLine0 = line0Accum.length;
32194
+ if (skipLine0Chars > 0) {
32195
+ if (totalLine0 <= skipLine0Chars) {
32196
+ skipLine0Chars -= totalLine0;
32197
+ line0Accum = "";
32198
+ currentLine++;
32199
+ lineStart = i + 1;
32200
+ if (currentLine > endLine) {
32201
+ done = true;
32202
+ break;
32203
+ }
32204
+ continue;
32205
+ }
32206
+ const from = skipLine0Chars;
32207
+ const take = Math.min(lineChunkSize, totalLine0 - from);
32208
+ resultLines.push(line0Accum.slice(from, from + take));
32209
+ line0CharsReturned += take;
32210
+ if (from + take < totalLine0) {
32211
+ return {
32212
+ content: resultLines.join("\n"),
32213
+ size: fileSize,
32214
+ lineOffset: lineOffsetIn + line0CharsReturned,
32215
+ totalLines: 1
32216
+ };
32217
+ }
32218
+ line0Accum = "";
32219
+ skipLine0Chars = 0;
32220
+ line0CharsReturned = 0;
32221
+ } else if (totalLine0 > lineChunkSize) {
32222
+ resultLines.push(line0Accum.slice(0, lineChunkSize));
32223
+ return {
32224
+ content: resultLines.join("\n"),
32225
+ size: fileSize,
32226
+ lineOffset: lineChunkSize,
32227
+ totalLines: 1
32228
+ };
32229
+ } else {
32230
+ resultLines.push(line0Accum);
32231
+ line0Accum = "";
32232
+ }
32233
+ } else if (currentLine >= startLine && currentLine <= endLine) {
32234
+ resultLines.push(lineContent);
32235
+ }
32236
+ currentLine++;
32237
+ lineStart = i + 1;
32238
+ if (currentLine > endLine) {
32239
+ done = true;
32240
+ break;
32241
+ }
32242
+ }
32243
+ }
32244
+ if (!done) {
32245
+ const lineContent = text.slice(lineStart);
32246
+ if (currentLine === 0 && (startLine === 0 || lineOffsetIn !== void 0)) {
32247
+ line0Accum += lineContent;
32248
+ const totalLine0 = line0Accum.length;
32249
+ if (skipLine0Chars > 0) {
32250
+ if (totalLine0 <= skipLine0Chars) {
32251
+ skipLine0Chars -= totalLine0;
32252
+ line0Accum = "";
32253
+ } else {
32254
+ const from = skipLine0Chars;
32255
+ const take = Math.min(lineChunkSize, totalLine0 - from);
32256
+ resultLines.push(line0Accum.slice(from, from + take));
32257
+ return {
32258
+ content: resultLines.join("\n"),
32259
+ size: fileSize,
32260
+ lineOffset: (lineOffsetIn ?? 0) + take,
32261
+ totalLines: 1
32262
+ };
32263
+ }
32264
+ } else if (totalLine0 > lineChunkSize) {
32265
+ resultLines.push(line0Accum.slice(0, lineChunkSize));
32266
+ return {
32267
+ content: resultLines.join("\n"),
32268
+ size: fileSize,
32269
+ lineOffset: lineChunkSize,
32270
+ totalLines: 1
32271
+ };
32272
+ }
32273
+ }
32274
+ partial2 = text.slice(lineStart);
32275
+ }
32276
+ }
32277
+ if (!done) {
32278
+ const tail = partial2 + decoder.end();
32279
+ if (currentLine === 0 && (startLine === 0 || lineOffsetIn !== void 0) && tail.length > 0) {
32280
+ line0Accum += tail.endsWith("\r") ? tail.slice(0, -1) : tail;
32281
+ const totalLine0 = line0Accum.length;
32282
+ if (skipLine0Chars > 0) {
32283
+ if (totalLine0 <= skipLine0Chars) {
32284
+ return { content: resultLines.join("\n"), size: fileSize };
32285
+ }
32286
+ const from = skipLine0Chars;
32287
+ const take = Math.min(lineChunkSize, totalLine0 - from);
32288
+ resultLines.push(line0Accum.slice(from, from + take));
32289
+ line0CharsReturned += take;
32290
+ if (from + take < totalLine0) {
32291
+ return {
32292
+ content: resultLines.join("\n"),
32293
+ size: fileSize,
32294
+ lineOffset: (lineOffsetIn ?? 0) + line0CharsReturned,
32295
+ totalLines: 1
32296
+ };
32297
+ }
32298
+ } else if (totalLine0 > lineChunkSize) {
32299
+ resultLines.push(line0Accum.slice(0, lineChunkSize));
32300
+ return {
32301
+ content: resultLines.join("\n"),
32302
+ size: fileSize,
32303
+ lineOffset: lineChunkSize,
32304
+ totalLines: 1
32305
+ };
32306
+ } else {
32307
+ resultLines.push(line0Accum);
32308
+ }
32309
+ } else if (tail.length > 0 && currentLine >= startLine && currentLine <= endLine) {
32310
+ resultLines.push(tail.endsWith("\r") ? tail.slice(0, -1) : tail);
32311
+ }
32312
+ }
32313
+ return { content: resultLines.join("\n"), size: fileSize };
32314
+ } finally {
32315
+ fs19.closeSync(fd);
32316
+ }
32317
+ }
32318
+ function readFile2(relativePath, startLine, endLine, lineOffset, lineChunkSize = LINE_CHUNK_SIZE) {
32319
+ try {
32320
+ const result = resolveFilePath(relativePath);
32321
+ if (typeof result === "object") return result;
32322
+ const hasRange = typeof startLine === "number" && typeof endLine === "number";
32323
+ if (hasRange) {
32324
+ return readFileRange(result, startLine, endLine, lineOffset, lineChunkSize);
32325
+ }
32326
+ const stat2 = fs19.statSync(result);
32327
+ const raw = fs19.readFileSync(result, "utf8");
32328
+ const lines = raw.split(/\r?\n/);
32329
+ return { content: raw, totalLines: lines.length, size: stat2.size };
32330
+ } catch (err) {
32331
+ return { error: err instanceof Error ? err.message : String(err) };
32332
+ }
32333
+ }
32334
+ async function readFileAsync(relativePath, startLine, endLine, lineOffset, lineChunkSize = LINE_CHUNK_SIZE) {
32335
+ await yieldToEventLoop();
32336
+ return readFile2(relativePath, startLine, endLine, lineOffset, lineChunkSize);
32337
+ }
32338
+
32339
+ // src/files/handle-file-browser-search.ts
32340
+ var SEARCH_LIMIT = 100;
32341
+ function handleFileBrowserSearch(msg, socket) {
32342
+ void (async () => {
32343
+ await yieldToEventLoop();
32344
+ const q = typeof msg.q === "string" ? msg.q : "";
32345
+ const cwd = getBridgeWorkspaceDirectory();
32346
+ const index = loadFileIndex(cwd);
32347
+ if (index === null) {
32348
+ sendWsMessage(socket, {
32349
+ type: "file_browser_search_response",
32350
+ id: msg.id,
32351
+ paths: [],
32352
+ indexReady: false
32353
+ });
32354
+ return;
32355
+ }
32356
+ const results = await searchFileIndexAsync(index, q, SEARCH_LIMIT);
32357
+ sendWsMessage(socket, {
32358
+ type: "file_browser_search_response",
32359
+ id: msg.id,
32360
+ paths: results,
32361
+ indexReady: true
32362
+ });
32363
+ })();
32364
+ }
32365
+ function triggerFileIndexBuild() {
32366
+ setImmediate(() => {
32367
+ void ensureFileIndexAsync(getBridgeWorkspaceDirectory()).catch((e) => {
32368
+ console.error("[file-index] Background build failed:", e);
32369
+ });
32370
+ });
32371
+ }
32372
+
32373
+ // src/files/handle-file-browser-request.ts
32374
+ function handleFileBrowserRequest(msg, socket) {
32375
+ void (async () => {
32376
+ const reqPath = msg.path.replace(/^\/+/, "") || ".";
32377
+ const op = msg.op === "read" ? "read" : "list";
32378
+ if (op === "list") {
32379
+ const result = await listDirAsync(reqPath);
32380
+ if ("error" in result) {
32381
+ sendWsMessage(socket, { type: "file_browser_response", id: msg.id, error: result.error });
32382
+ } else {
32383
+ sendWsMessage(socket, { type: "file_browser_response", id: msg.id, entries: result.entries });
32384
+ if (reqPath === "." || reqPath === "") {
32385
+ triggerFileIndexBuild();
32386
+ }
32387
+ }
32388
+ } else {
32389
+ const startLine = typeof msg.startLine === "number" ? msg.startLine : void 0;
32390
+ const endLine = typeof msg.endLine === "number" ? msg.endLine : void 0;
32391
+ const lineOffset = typeof msg.lineOffset === "number" ? msg.lineOffset : void 0;
32392
+ const lineChunkSize = typeof msg.lineChunkSize === "number" ? msg.lineChunkSize : void 0;
32393
+ const result = await readFileAsync(reqPath, startLine, endLine, lineOffset, lineChunkSize);
32394
+ if ("error" in result) {
32395
+ sendWsMessage(socket, { type: "file_browser_response", id: msg.id, error: result.error });
32396
+ } else {
32397
+ const payload = {
32398
+ type: "file_browser_response",
32399
+ id: msg.id,
32400
+ content: result.content,
32401
+ totalLines: result.totalLines,
32402
+ size: result.size
32403
+ };
32404
+ if (result.lineOffset != null) payload.lineOffset = result.lineOffset;
32405
+ sendWsMessage(socket, payload);
32406
+ }
32407
+ }
32408
+ })();
32409
+ }
32410
+
32411
+ // src/bridge/routing/handlers/file-browser-messages.ts
32412
+ function handleFileBrowserRequestMessage(msg, { getWs }) {
32413
+ if (typeof msg.id !== "string" || typeof msg.path !== "string") return;
32414
+ const socket = getWs();
32415
+ if (!socket) return;
32416
+ handleFileBrowserRequest(
32417
+ msg,
32418
+ socket
32419
+ );
32420
+ }
32421
+ function handleFileBrowserSearchMessage(msg, { getWs }) {
32422
+ if (typeof msg.id !== "string") return;
32423
+ const socket = getWs();
32424
+ if (!socket) return;
32425
+ handleFileBrowserSearch(msg, socket);
32426
+ }
32427
+
32428
+ // src/bridge/routing/handlers/skill-layout-request.ts
32429
+ function handleSkillLayoutRequest(msg, deps) {
32430
+ const socket = deps.getWs();
32431
+ const id = typeof msg.id === "string" ? msg.id : "";
32432
+ const roots = discoverSkillLayoutRoots(getBridgeWorkspaceDirectory());
32433
+ socket?.send(JSON.stringify({ type: "skill_layout_response", id, roots }));
32434
+ }
32435
+
32436
+ // src/skills/install-remote-skills.ts
32437
+ import fs20 from "node:fs";
32438
+ import path28 from "node:path";
32439
+ function installRemoteSkills(cwd, targetDir, items) {
32440
+ const installed = [];
32441
+ if (!Array.isArray(items)) {
32442
+ return { success: false, error: "Invalid items" };
32443
+ }
32444
+ try {
32445
+ for (const item of items) {
32446
+ if (typeof item.sourceId !== "string" || typeof item.skillName !== "string" || typeof item.versionHash !== "string" || !Array.isArray(item.files)) {
32447
+ continue;
32448
+ }
32449
+ const skillDir = path28.join(cwd, targetDir, item.skillName);
32450
+ for (const f of item.files) {
32451
+ if (typeof f.path !== "string" || !f.text && !f.base64) continue;
32452
+ const dest = path28.join(skillDir, f.path);
32453
+ fs20.mkdirSync(path28.dirname(dest), { recursive: true });
32454
+ if (f.text !== void 0) {
32455
+ fs20.writeFileSync(dest, f.text, "utf8");
32456
+ } else if (f.base64) {
32457
+ fs20.writeFileSync(dest, Buffer.from(f.base64, "base64"));
32458
+ }
32459
+ }
32460
+ installed.push({
32461
+ sourceId: item.sourceId,
32462
+ skillName: item.skillName,
32463
+ versionHash: item.versionHash
32464
+ });
32465
+ }
32466
+ return { success: true, installed };
32467
+ } catch (e) {
32468
+ return { success: false, error: e instanceof Error ? e.message : String(e) };
32469
+ }
32470
+ }
32471
+
32472
+ // src/bridge/routing/handlers/install-skills.ts
32473
+ var handleInstallSkillsMessage = (msg, deps) => {
32474
+ const socket = deps.getWs();
32475
+ const id = typeof msg.id === "string" ? msg.id : "";
32476
+ const targetDir = typeof msg.targetDir === "string" && msg.targetDir.trim() ? msg.targetDir.trim() : ".agents/skills";
32477
+ const rawItems = msg.items;
32478
+ const cwd = getBridgeWorkspaceDirectory();
32479
+ const result = installRemoteSkills(cwd, targetDir, rawItems);
32480
+ if (!result.success) {
32481
+ const err = result.error ?? "Invalid items";
32482
+ deps.log(`[Bridge service] Install skills failed: ${err}`);
32483
+ socket?.send(JSON.stringify({ type: "install_skills_result", id, success: false, error: err }));
32484
+ return;
32485
+ }
32486
+ socket?.send(JSON.stringify({ type: "install_skills_result", id, success: true, installed: result.installed }));
32487
+ };
32488
+
32489
+ // src/bridge/routing/handlers/refresh-local-skills.ts
32490
+ var handleRefreshLocalSkills = (_msg, deps) => {
32491
+ deps.sendLocalSkillsReport?.();
32492
+ };
32493
+
32494
+ // src/bridge/routing/handlers/session-git-request.ts
32495
+ function sendResult(ws, id, payload) {
32496
+ if (!ws) return;
32497
+ sendWsMessage(ws, { type: "session_git_result", id, ...payload });
32498
+ }
32499
+ var handleSessionGitRequestMessage = (msg, deps) => {
32500
+ if (typeof msg.id !== "string") return;
32501
+ const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
32502
+ const action = msg.action;
32503
+ if (!sessionId || action !== "status" && action !== "push" && action !== "commit") return;
32504
+ void (async () => {
32505
+ const ws = deps.getWs();
32506
+ const reply = (payload) => sendResult(ws, msg.id, payload);
32507
+ try {
32508
+ if (action === "status") {
32509
+ const r = await deps.sessionWorktreeManager.getSessionWorkingTreeStatus(sessionId);
32510
+ reply({
32511
+ ok: true,
32512
+ hasUncommittedChanges: r.hasUncommittedChanges,
32513
+ hasUnpushedCommits: r.hasUnpushedCommits
32514
+ });
32515
+ return;
32516
+ }
32517
+ if (action === "push") {
32518
+ const pushRes = await deps.sessionWorktreeManager.pushSessionUpstream(sessionId);
32519
+ if (!pushRes.ok) {
32520
+ reply({ ok: false, error: pushRes.error ?? "Push failed" });
32521
+ return;
32522
+ }
32523
+ const st2 = await deps.sessionWorktreeManager.getSessionWorkingTreeStatus(sessionId);
32524
+ reply({
32525
+ ok: true,
32526
+ hasUncommittedChanges: st2.hasUncommittedChanges,
32527
+ hasUnpushedCommits: st2.hasUnpushedCommits
32528
+ });
32529
+ return;
32530
+ }
32531
+ const branch = typeof msg.branch === "string" ? msg.branch : "";
32532
+ const message = typeof msg.message === "string" ? msg.message : "";
32533
+ const pushAfterCommit = msg.pushAfterCommit === true;
32534
+ if (!branch.trim() || !message.trim()) {
32535
+ reply({ ok: false, error: "branch and message are required for commit" });
32536
+ return;
32537
+ }
32538
+ const commitRes = await deps.sessionWorktreeManager.commitSession({
32539
+ sessionId,
32540
+ branch: branch.trim(),
32541
+ message: message.trim(),
32542
+ push: pushAfterCommit
32543
+ });
32544
+ if (!commitRes.ok) {
32545
+ reply({ ok: false, error: commitRes.error ?? "Commit failed" });
32546
+ return;
32547
+ }
32548
+ const st = await deps.sessionWorktreeManager.getSessionWorkingTreeStatus(sessionId);
32549
+ reply({
32550
+ ok: true,
32551
+ hasUncommittedChanges: st.hasUncommittedChanges,
32552
+ hasUnpushedCommits: st.hasUnpushedCommits
32553
+ });
32554
+ } catch (e) {
32555
+ reply({ ok: false, error: e instanceof Error ? e.message : String(e) });
32556
+ }
32557
+ })();
32558
+ };
32559
+
32560
+ // src/bridge/routing/handlers/rename-session-branch.ts
32561
+ var handleRenameSessionBranchMessage = (msg, deps) => {
32562
+ const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
32563
+ const newBranch = typeof msg.newBranch === "string" ? msg.newBranch : "";
32564
+ if (!sessionId || !newBranch) return;
32565
+ void deps.sessionWorktreeManager.renameSessionBranch(sessionId, newBranch);
32566
+ };
32567
+
32568
+ // src/bridge/routing/handlers/session-archived.ts
32569
+ var handleSessionArchivedMessage = (msg, deps) => {
32570
+ const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
32571
+ if (!sessionId) return;
32572
+ void deps.sessionWorktreeManager.removeSessionWorktrees(sessionId);
32573
+ };
32574
+
32575
+ // src/bridge/routing/handlers/session-discarded.ts
32576
+ var handleSessionDiscardedMessage = (msg, deps) => {
32577
+ const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
32578
+ if (!sessionId) return;
32579
+ void deps.sessionWorktreeManager.removeSessionWorktrees(sessionId);
32580
+ };
32581
+
32582
+ // src/bridge/routing/handlers/revert-turn-snapshot.ts
32583
+ import * as fs21 from "node:fs";
32584
+ var handleRevertTurnSnapshotMessage = (msg, deps) => {
32585
+ const id = typeof msg.id === "string" ? msg.id : "";
32586
+ const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
32587
+ const turnId = typeof msg.turnId === "string" ? msg.turnId : "";
32588
+ if (!id || !sessionId || !turnId) return;
32589
+ const { getWs, log: log2, sessionWorktreeManager } = deps;
32590
+ void (async () => {
32591
+ const s = getWs();
32592
+ if (!s) return;
32593
+ const agentBase = sessionWorktreeManager.getAgentCwdForSession(sessionId) ?? getBridgeWorkspaceDirectory();
32594
+ const file2 = snapshotFilePath(agentBase, turnId);
32595
+ if (!fs21.existsSync(file2)) {
32596
+ sendWsMessage(s, {
32597
+ type: "revert_turn_snapshot_result",
32598
+ id,
32599
+ ok: false,
32600
+ error: "No snapshot found for this turn (git state may be unavailable)."
32601
+ });
32602
+ return;
32603
+ }
32604
+ const res = await applyPreTurnSnapshot(file2, log2);
32605
+ sendWsMessage(s, {
32606
+ type: "revert_turn_snapshot_result",
32607
+ id,
32608
+ ok: res.ok,
32609
+ ...res.error ? { error: res.error } : {}
32610
+ });
32611
+ })();
32612
+ };
32613
+
32614
+ // src/bridge/routing/handlers/dev-server-control.ts
32615
+ var handleDevServerControl = (msg, deps) => {
32616
+ const serverId = typeof msg.serverId === "string" ? msg.serverId : "";
32617
+ const action = msg.action === "start" || msg.action === "stop" ? msg.action : null;
32618
+ if (!serverId || !action) return;
32619
+ deps.devServerManager?.handleControl(serverId, action);
32620
+ };
32621
+
32622
+ // src/bridge/routing/handlers/dev-servers-config.ts
32623
+ var handleDevServersConfig = (msg, deps) => {
32624
+ const devServers = msg.devServers;
32625
+ deps.devServerManager?.applyConfig(devServers ?? []);
32626
+ };
32627
+
32628
+ // src/bridge/routing/dispatch-bridge-message.ts
32629
+ function dispatchBridgeMessage(msg, deps) {
32630
+ const type = msg.type;
32631
+ if (typeof type !== "string") return;
32632
+ switch (type) {
32633
+ case "auth_token":
32634
+ handleAuthToken(msg, deps);
32635
+ break;
32636
+ case "bridge_identified":
32637
+ handleBridgeIdentified(msg, deps);
32638
+ break;
32639
+ case "dev_servers_config":
32640
+ handleDevServersConfig(msg, deps);
32641
+ break;
32642
+ case "server_control":
32643
+ handleDevServerControl(msg, deps);
32644
+ break;
32645
+ case "agent_config":
32646
+ handleAgentConfigMessage(msg, deps);
32647
+ break;
32648
+ case "prompt":
32649
+ handlePromptMessage(msg, deps);
32650
+ break;
32651
+ case "session_git_request":
32652
+ handleSessionGitRequestMessage(msg, deps);
32653
+ break;
32654
+ case "rename_session_branch":
32655
+ handleRenameSessionBranchMessage(msg, deps);
32656
+ break;
32657
+ case "session_archived":
32658
+ handleSessionArchivedMessage(msg, deps);
32659
+ break;
32660
+ case "session_discarded":
32661
+ handleSessionDiscardedMessage(msg, deps);
32662
+ break;
32663
+ case "revert_turn_snapshot":
32664
+ handleRevertTurnSnapshotMessage(msg, deps);
32665
+ break;
32666
+ case "cancel_run":
32667
+ handleCancelRunMessage(msg, deps);
32668
+ break;
32669
+ case "cursor_request_response":
32670
+ handleCursorRequestResponseMessage(msg, deps);
32671
+ break;
32672
+ case "skill_call":
32673
+ handleSkillCallMessage(msg, deps);
32674
+ break;
32675
+ case "file_browser_request":
32676
+ handleFileBrowserRequestMessage(msg, deps);
32677
+ break;
32678
+ case "file_browser_search":
32679
+ handleFileBrowserSearchMessage(msg, deps);
32680
+ break;
32681
+ case "skill_layout_request":
32682
+ handleSkillLayoutRequest(msg, deps);
32683
+ break;
32684
+ case "install_skills":
32685
+ handleInstallSkillsMessage(msg, deps);
32686
+ break;
32687
+ case "refresh_local_skills":
32688
+ handleRefreshLocalSkills(msg, deps);
32689
+ break;
32690
+ default:
32691
+ deps.log?.(`[Bridge service] unhandled message type: ${type}`);
32692
+ }
32693
+ }
32694
+
32695
+ // src/bridge/routing/handle-bridge-message.ts
32696
+ function handleBridgeMessage(data, deps) {
32697
+ const msg = data;
32698
+ if (!deps.getWs()) return;
32699
+ setImmediate(() => {
32700
+ dispatchBridgeMessage(msg, deps);
32701
+ });
32439
32702
  }
32440
32703
 
32441
- // src/acp/detect-local-agent-types.ts
32442
- var LOCAL_AGENT_ACP_MODULES = [
32443
- cursor_acp_client_exports,
32444
- codex_acp_client_exports,
32445
- kiro_acp_client_exports,
32446
- claude_code_acp_client_exports
32447
- ];
32448
- async function detectLocalAgentTypes() {
32704
+ // src/auth/refresh-bridge-tokens.ts
32705
+ async function refreshBridgeTokens(params) {
32706
+ const base = params.apiUrl.replace(/\/$/, "");
32707
+ const url2 = `${base}/api/bridges/tokens/refresh`;
32449
32708
  try {
32450
- const out = [];
32451
- for (const mod of LOCAL_AGENT_ACP_MODULES) {
32452
- try {
32453
- if (await mod.detectLocalAgentPresence()) out.push(mod.BACKEND_LOCAL_AGENT_TYPE);
32454
- } catch {
32455
- }
32456
- }
32457
- return out;
32709
+ const res = await fetch(url2, {
32710
+ method: "POST",
32711
+ headers: { "Content-Type": "application/json" },
32712
+ body: JSON.stringify({
32713
+ workspaceId: params.workspaceId,
32714
+ refreshToken: params.refreshToken
32715
+ })
32716
+ });
32717
+ if (!res.ok) return null;
32718
+ const data = await res.json();
32719
+ if (typeof data.token !== "string" || typeof data.refreshToken !== "string") return null;
32720
+ return {
32721
+ token: data.token,
32722
+ refreshToken: data.refreshToken,
32723
+ workspaceId: typeof data.workspaceId === "string" ? data.workspaceId : params.workspaceId,
32724
+ tokenId: typeof data.tokenId === "string" ? data.tokenId : ""
32725
+ };
32458
32726
  } catch {
32459
- return [];
32727
+ return null;
32460
32728
  }
32461
32729
  }
32462
32730
 
32463
- // src/bridge/connection/create-bridge-local-reports.ts
32464
- function createSendLocalSkillsReport(getWs, logFn) {
32465
- return () => {
32466
- try {
32467
- const socket = getWs();
32468
- if (!socket || socket.readyState !== wrapper_default.OPEN) return;
32469
- const skills2 = discoverLocalSkills(getBridgeWorkspaceDirectory());
32470
- socket.send(JSON.stringify({ type: "local_skills", skills: skills2 }));
32471
- } catch (e) {
32472
- logFn(
32473
- `[Bridge service] Local skills report failed: ${e instanceof Error ? e.message : String(e)}`
32474
- );
32475
- }
32476
- };
32477
- }
32478
- function createReportAutoDetectedAgents(getWs, logFn) {
32479
- return async () => {
32480
- try {
32481
- const types = await detectLocalAgentTypes();
32482
- const socket = getWs();
32483
- if (socket && socket.readyState === wrapper_default.OPEN) {
32484
- sendWsMessage(socket, { type: "auto_detected_agents_report", agentTypes: types });
32485
- }
32486
- } catch (e) {
32487
- logFn(
32488
- `[Bridge service] Auto-detected agents report failed: ${e instanceof Error ? e.message : String(e)}`
32489
- );
32490
- }
32491
- };
32492
- }
32493
-
32494
- // src/bridge/connection/create-bridge-connection.ts
32731
+ // src/bridge/connection/main-bridge-ws-lifecycle.ts
32495
32732
  var BRIDGE_CLIENT_PING_MS = 25e3;
32496
- async function createBridgeConnection(options) {
32497
- const { apiUrl, workspaceId, justAuthenticated, onAuthInvalid, persistTokens } = options;
32498
- const firehoseServerUrl = options.firehoseServerUrl ?? options.proxyServerUrl;
32499
- const logFn = options.log ?? log;
32500
- let accessToken = options.authToken;
32501
- let refreshTok = options.refreshToken;
32502
- let authRefreshInFlight = false;
32503
- const state = {
32504
- closedByUser: false,
32505
- reconnectAttempt: 0,
32506
- logBridgeOpenAsReconnect: false,
32507
- reconnectTimeout: null,
32508
- currentWs: null,
32509
- mainQuiet: createEmptyReconnectQuietSlot(),
32510
- firehoseHandle: null,
32511
- lastFirehoseParams: null,
32512
- firehoseReconnectTimeout: null,
32513
- firehoseReconnectAttempt: 0,
32514
- firehoseGeneration: 0,
32515
- firehoseQuiet: createEmptyReconnectQuietSlot()
32516
- };
32517
- const worktreesRootAbs = options.worktreesRootAbs ?? defaultWorktreesRootAbs();
32518
- const sessionWorktreeManager = new SessionWorktreeManager({
32519
- worktreesRootAbs,
32520
- log: logFn
32521
- });
32522
- const acpManager = await createAcpManager({ log: logFn });
32523
- logFn("CLI running. Press Ctrl+C to exit.");
32524
- function getWs() {
32525
- return state.currentWs;
32526
- }
32527
- const devServerManager = new DevServerManager({ getWs, log: logFn, getBridgeCwd: getBridgeWorkspaceDirectory });
32528
- const onBridgeIdentified = createOnBridgeIdentified({
32529
- sessionWorktreeManager,
32530
- devServerManager,
32531
- firehoseServerUrl,
32532
- workspaceId,
32733
+ function createMainBridgeWebSocketLifecycle(params) {
32734
+ const {
32533
32735
  state,
32534
- logFn
32535
- });
32536
- const sendLocalSkillsReport = createSendLocalSkillsReport(getWs, logFn);
32537
- const reportAutoDetectedAgents = createReportAutoDetectedAgents(getWs, logFn);
32736
+ getWs,
32737
+ apiUrl,
32738
+ workspaceId,
32739
+ justAuthenticated,
32740
+ logFn,
32741
+ messageDeps,
32742
+ tokens,
32743
+ persistTokens,
32744
+ onAuthInvalid
32745
+ } = params;
32746
+ let authRefreshInFlight = false;
32538
32747
  function handleOpen() {
32539
32748
  const logOpenAsPostRefreshReconnect = state.logBridgeOpenAsReconnect;
32540
32749
  clearMainBridgeReconnectQuietOnOpen(state, logFn);
@@ -32549,8 +32758,10 @@ async function createBridgeConnection(options) {
32549
32758
  reportGitRepos(getWs, logFn);
32550
32759
  }
32551
32760
  if (justAuthenticated && socket) {
32552
- logFn("Save these for future runs (access token may rotate; refresh token is stored in ~/.buildautomaton/config.json when you use browser auth):");
32553
- logFn(` export BUILDAMATON_AUTH_TOKEN="${accessToken}"`);
32761
+ logFn(
32762
+ "Save these for future runs (access token may rotate; refresh token is stored in ~/.buildautomaton/config.json when you use browser auth):"
32763
+ );
32764
+ logFn(` export BUILDAMATON_AUTH_TOKEN="${tokens.accessToken}"`);
32554
32765
  logFn(` export BUILDAMATON_WORKSPACE_ID="${workspaceId}"`);
32555
32766
  }
32556
32767
  }
@@ -32564,16 +32775,6 @@ async function createBridgeConnection(options) {
32564
32775
  scheduleMainBridgeReconnect(state, connect, logFn);
32565
32776
  }
32566
32777
  }
32567
- const messageDeps = {
32568
- getWs,
32569
- log: logFn,
32570
- acpManager,
32571
- sessionWorktreeManager,
32572
- onBridgeIdentified,
32573
- sendLocalSkillsReport,
32574
- reportAutoDetectedAgents,
32575
- devServerManager
32576
- };
32577
32778
  function connect() {
32578
32779
  if (state.closedByUser) return;
32579
32780
  if (state.reconnectTimeout != null) {
@@ -32592,7 +32793,7 @@ async function createBridgeConnection(options) {
32592
32793
  }
32593
32794
  state.currentWs = null;
32594
32795
  }
32595
- const url2 = buildBridgeUrl(apiUrl, workspaceId, accessToken);
32796
+ const url2 = buildBridgeUrl(apiUrl, workspaceId, tokens.accessToken);
32596
32797
  state.currentWs = createWsBridge({
32597
32798
  url: url2,
32598
32799
  clientPingIntervalMs: BRIDGE_CLIENT_PING_MS,
@@ -32601,12 +32802,16 @@ async function createBridgeConnection(options) {
32601
32802
  void (async () => {
32602
32803
  authRefreshInFlight = true;
32603
32804
  try {
32604
- if (refreshTok) {
32605
- const next = await refreshBridgeTokens({ apiUrl, workspaceId, refreshToken: refreshTok });
32805
+ if (tokens.refreshToken) {
32806
+ const next = await refreshBridgeTokens({
32807
+ apiUrl,
32808
+ workspaceId,
32809
+ refreshToken: tokens.refreshToken
32810
+ });
32606
32811
  if (next?.token && next.refreshToken) {
32607
- accessToken = next.token;
32608
- refreshTok = next.refreshToken;
32609
- persistTokens?.({ token: accessToken, refreshToken: refreshTok });
32812
+ tokens.accessToken = next.token;
32813
+ tokens.refreshToken = next.refreshToken;
32814
+ persistTokens?.({ token: tokens.accessToken, refreshToken: tokens.refreshToken });
32610
32815
  logFn("[Bridge service] Access token refreshed; reconnecting\u2026");
32611
32816
  state.reconnectAttempt = 0;
32612
32817
  state.logBridgeOpenAsReconnect = true;
@@ -32631,6 +32836,75 @@ async function createBridgeConnection(options) {
32631
32836
  onMessage: (data) => handleBridgeMessage(data, messageDeps)
32632
32837
  });
32633
32838
  }
32839
+ return { connect };
32840
+ }
32841
+
32842
+ // src/bridge/connection/create-bridge-connection.ts
32843
+ async function createBridgeConnection(options) {
32844
+ const { apiUrl, workspaceId, justAuthenticated, onAuthInvalid, persistTokens } = options;
32845
+ const firehoseServerUrl = options.firehoseServerUrl ?? options.proxyServerUrl;
32846
+ const logFn = options.log ?? log;
32847
+ const tokens = {
32848
+ accessToken: options.authToken,
32849
+ refreshToken: options.refreshToken
32850
+ };
32851
+ const state = {
32852
+ closedByUser: false,
32853
+ reconnectAttempt: 0,
32854
+ logBridgeOpenAsReconnect: false,
32855
+ reconnectTimeout: null,
32856
+ currentWs: null,
32857
+ mainQuiet: createEmptyReconnectQuietSlot(),
32858
+ firehoseHandle: null,
32859
+ lastFirehoseParams: null,
32860
+ firehoseReconnectTimeout: null,
32861
+ firehoseReconnectAttempt: 0,
32862
+ firehoseGeneration: 0,
32863
+ firehoseQuiet: createEmptyReconnectQuietSlot()
32864
+ };
32865
+ const worktreesRootAbs = options.worktreesRootAbs ?? defaultWorktreesRootAbs();
32866
+ const sessionWorktreeManager = new SessionWorktreeManager({
32867
+ worktreesRootAbs,
32868
+ log: logFn
32869
+ });
32870
+ const acpManager = await createAcpManager({ log: logFn });
32871
+ logFn("CLI running. Press Ctrl+C to exit.");
32872
+ function getWs() {
32873
+ return state.currentWs;
32874
+ }
32875
+ const devServerManager = new DevServerManager({ getWs, log: logFn, getBridgeCwd: getBridgeWorkspaceDirectory });
32876
+ const onBridgeIdentified = createOnBridgeIdentified({
32877
+ sessionWorktreeManager,
32878
+ devServerManager,
32879
+ firehoseServerUrl,
32880
+ workspaceId,
32881
+ state,
32882
+ logFn
32883
+ });
32884
+ const sendLocalSkillsReport = createSendLocalSkillsReport(getWs, logFn);
32885
+ const reportAutoDetectedAgents = createReportAutoDetectedAgents(getWs, logFn);
32886
+ const messageDeps = {
32887
+ getWs,
32888
+ log: logFn,
32889
+ acpManager,
32890
+ sessionWorktreeManager,
32891
+ onBridgeIdentified,
32892
+ sendLocalSkillsReport,
32893
+ reportAutoDetectedAgents,
32894
+ devServerManager
32895
+ };
32896
+ const { connect } = createMainBridgeWebSocketLifecycle({
32897
+ state,
32898
+ getWs,
32899
+ apiUrl,
32900
+ workspaceId,
32901
+ justAuthenticated: justAuthenticated === true,
32902
+ logFn,
32903
+ messageDeps,
32904
+ tokens,
32905
+ persistTokens,
32906
+ onAuthInvalid
32907
+ });
32634
32908
  connect();
32635
32909
  const stopFileIndexWatcher = startFileIndexWatcher(getBridgeWorkspaceDirectory());
32636
32910
  return {