@buildautomaton/cli 0.1.35 → 0.1.36

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
@@ -23964,7 +23964,7 @@ function installBridgeProcessResilience() {
23964
23964
  }
23965
23965
 
23966
23966
  // src/cli-version.ts
23967
- var CLI_VERSION = "0.1.35".length > 0 ? "0.1.35" : "0.0.0-dev";
23967
+ var CLI_VERSION = "0.1.36".length > 0 ? "0.1.36" : "0.0.0-dev";
23968
23968
 
23969
23969
  // src/connection/heartbeat/constants.ts
23970
23970
  var BRIDGE_APP_HEARTBEAT_INTERVAL_MS = 1e4;
@@ -25287,6 +25287,7 @@ function recordMigrationAndPruneCheckpointLegacy(db, migration, applied2) {
25287
25287
  var CHECKPOINT_V1 = "001_cli_sqlite_checkpoint_v1";
25288
25288
  var CHECKPOINT_V1_SQL = readCliSqliteMigrationSql("001_cli_sqlite_checkpoint_v1.sql");
25289
25289
  var AGENT_CAPABILITIES_SQL = readCliSqliteMigrationSql("002_agent_capabilities.sql");
25290
+ var FILE_INDEX_PARENT_PATHS_SQL = readCliSqliteMigrationSql("003_file_index_parent_paths.sql");
25290
25291
  function agentCapabilitiesTableState(db) {
25291
25292
  const rows = db.all(
25292
25293
  `SELECT name FROM sqlite_master WHERE type='table' AND name IN ('agent_capabilities', 'agent_capability_cache')`
@@ -25320,6 +25321,18 @@ var CLI_SQLITE_MIGRATIONS = [
25320
25321
  db.exec(AGENT_CAPABILITIES_SQL);
25321
25322
  },
25322
25323
  alreadyApplied: (db) => agentCapabilitiesTableState(db) === "current"
25324
+ },
25325
+ {
25326
+ name: "003_file_index_parent_paths",
25327
+ migrate: (db) => {
25328
+ db.exec(FILE_INDEX_PARENT_PATHS_SQL);
25329
+ },
25330
+ alreadyApplied: (db) => {
25331
+ const row = db.get(
25332
+ `SELECT 1 as ok FROM sqlite_master WHERE type='table' AND name='file_index_parent_path' LIMIT 1`
25333
+ );
25334
+ return row != null;
25335
+ }
25323
25336
  }
25324
25337
  ];
25325
25338
  function migrateCliSqlite(db) {
@@ -25358,6 +25371,10 @@ var CliSqliteInterrupted = class extends Error {
25358
25371
  }
25359
25372
  };
25360
25373
  function applyCliSqliteConcurrencyPragmas(db) {
25374
+ try {
25375
+ db.run("PRAGMA foreign_keys = ON");
25376
+ } catch {
25377
+ }
25361
25378
  try {
25362
25379
  db.exec("PRAGMA journal_mode = WAL");
25363
25380
  } catch {
@@ -33783,8 +33800,18 @@ import path28 from "node:path";
33783
33800
  // src/files/index/walk-workspace-tree.ts
33784
33801
  import fs24 from "node:fs";
33785
33802
  import path27 from "node:path";
33803
+ var DEPENDENCY_INSTALL_DIR_NAMES = /* @__PURE__ */ new Set([
33804
+ "node_modules",
33805
+ "bower_components",
33806
+ "vendor",
33807
+ "Pods",
33808
+ "Carthage",
33809
+ "DerivedData",
33810
+ ".yarn",
33811
+ ".pnpm-store"
33812
+ ]);
33786
33813
  function shouldSkipWorkspaceWalkEntry(name) {
33787
- return name.startsWith(".");
33814
+ return DEPENDENCY_INSTALL_DIR_NAMES.has(name);
33788
33815
  }
33789
33816
  async function walkWorkspaceTreeAsync(dir, baseDir, onFile, state) {
33790
33817
  let names;
@@ -33855,14 +33882,26 @@ var FILE_INDEX_INTERRUPT_CHECK_EVERY = 256;
33855
33882
  function assertNotShutdown() {
33856
33883
  if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
33857
33884
  }
33885
+ function upsertFileIndexParentPath(db, resolved) {
33886
+ const now = (/* @__PURE__ */ new Date()).toISOString();
33887
+ db.run(
33888
+ `INSERT INTO file_index_parent_path (path, path_hash, updated_at)
33889
+ VALUES (?, ?, ?)
33890
+ ON CONFLICT(path) DO UPDATE SET path_hash = excluded.path_hash, updated_at = excluded.updated_at`,
33891
+ [resolved, getCwdHashForFileIndex(resolved), now]
33892
+ );
33893
+ const row = db.get("SELECT id FROM file_index_parent_path WHERE path = ?", [resolved]);
33894
+ if (row == null) throw new Error(`Failed to upsert file index parent path: ${resolved}`);
33895
+ return Number(row.id);
33896
+ }
33858
33897
  function persistFileIndexPaths(resolved, paths) {
33859
33898
  return withCliSqliteSync((db) => {
33860
- const h = getCwdHashForFileIndex(resolved);
33861
33899
  let pathCount = 0;
33862
33900
  db.run("BEGIN IMMEDIATE");
33863
33901
  try {
33864
- db.run("DELETE FROM file_index_path WHERE cwd_hash = ?", [h]);
33865
- const ins = db.prepare("INSERT INTO file_index_path (cwd_hash, path) VALUES (?, ?)");
33902
+ const parentId = upsertFileIndexParentPath(db, resolved);
33903
+ db.run("DELETE FROM file_index_child_path WHERE parent_id = ?", [parentId]);
33904
+ const ins = db.prepare("INSERT INTO file_index_child_path (parent_id, path) VALUES (?, ?)");
33866
33905
  try {
33867
33906
  let batch = 0;
33868
33907
  for (const rel of paths) {
@@ -33870,7 +33909,7 @@ function persistFileIndexPaths(resolved, paths) {
33870
33909
  batch = 0;
33871
33910
  assertNotShutdown();
33872
33911
  }
33873
- ins.run([h, rel]);
33912
+ ins.run([parentId, rel]);
33874
33913
  pathCount += 1;
33875
33914
  }
33876
33915
  } finally {
@@ -33912,36 +33951,38 @@ async function buildFileIndexAsync(cwd) {
33912
33951
  // src/files/index/ensure-file-index.ts
33913
33952
  import path29 from "node:path";
33914
33953
 
33915
- // src/files/index/file-index-dependency-path.ts
33916
- function sqliteExprBridgeFileIndexDependencyRank() {
33917
- return `CASE WHEN lower(path) = 'node_modules' OR lower(path) LIKE 'node_modules/%' OR lower(path) LIKE '%/node_modules/%' OR lower(path) = 'bower_components' OR lower(path) LIKE 'bower_components/%' OR lower(path) LIKE '%/bower_components/%' THEN 1 ELSE 0 END`;
33918
- }
33919
-
33920
33954
  // src/files/index/search-file-index.ts
33921
33955
  function escapeLikePattern(fragment) {
33922
33956
  return fragment.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
33923
33957
  }
33924
33958
  function bridgeFileIndexIsPopulatedWithDb(resolvedCwd, db) {
33925
- const h = getCwdHashForFileIndex(resolvedCwd);
33926
- const row = db.get("SELECT 1 as ok FROM file_index_path WHERE cwd_hash = ? LIMIT 1", [h]);
33959
+ const row = db.get("SELECT 1 as ok FROM file_index_parent_path WHERE path = ? LIMIT 1", [resolvedCwd]);
33927
33960
  return row != null;
33928
33961
  }
33929
33962
  function bridgeFileIndexPathCountWithDb(resolvedCwd, db) {
33930
- const h = getCwdHashForFileIndex(resolvedCwd);
33931
- const row = db.get("SELECT COUNT(*) as c FROM file_index_path WHERE cwd_hash = ?", [h]);
33963
+ const row = db.get(
33964
+ `SELECT COUNT(*) as c
33965
+ FROM file_index_child_path child
33966
+ JOIN file_index_parent_path parent ON parent.id = child.parent_id
33967
+ WHERE parent.path = ?`,
33968
+ [resolvedCwd]
33969
+ );
33932
33970
  const c = row?.c ?? 0;
33933
33971
  return Number(c);
33934
33972
  }
33935
33973
  function searchBridgeFilePathsWithDb(resolvedCwd, query, limit, db) {
33936
33974
  const q = query.trim().toLowerCase();
33937
33975
  if (!q) return [];
33938
- const h = getCwdHashForFileIndex(resolvedCwd);
33939
33976
  const pattern = `%${escapeLikePattern(q)}%`;
33940
33977
  const lim = Math.max(0, Math.min(1e4, Math.floor(limit)));
33941
- const depRank = sqliteExprBridgeFileIndexDependencyRank();
33942
33978
  const rows = db.all(
33943
- `SELECT path FROM file_index_path WHERE cwd_hash = ? AND lower(path) LIKE ? ESCAPE '\\' ORDER BY ${depRank}, path LIMIT ?`,
33944
- [h, pattern, lim]
33979
+ `SELECT child.path
33980
+ FROM file_index_child_path child
33981
+ JOIN file_index_parent_path parent ON parent.id = child.parent_id
33982
+ WHERE parent.path = ? AND lower(child.path) LIKE ? ESCAPE '\\'
33983
+ ORDER BY child.path
33984
+ LIMIT ?`,
33985
+ [resolvedCwd, pattern, lim]
33945
33986
  );
33946
33987
  return rows.map((r) => String(r.path));
33947
33988
  }
@@ -33971,9 +34012,7 @@ async function ensureFileIndexAsync(cwd) {
33971
34012
  var DEBOUNCE_MS = 900;
33972
34013
  function shouldIgnoreRelative(rel) {
33973
34014
  const n = rel.replace(/\\/g, "/");
33974
- if (n.includes("/.git/") || n === ".git" || n.startsWith(".git/")) return true;
33975
- if (n.includes("/.buildautomaton/") || n.startsWith(".buildautomaton/")) return true;
33976
- return false;
34015
+ return n.split("/").some((segment) => shouldSkipWorkspaceWalkEntry(segment));
33977
34016
  }
33978
34017
  function attachWatchErrorLog(w) {
33979
34018
  w.on("error", (err) => {
@@ -36016,9 +36055,8 @@ var handleSkillCallMessage = (msg, { getWs, log: log2 }) => {
36016
36055
  );
36017
36056
  };
36018
36057
 
36019
- // src/files/list-dir.ts
36020
- import fs32 from "node:fs";
36021
- import path35 from "node:path";
36058
+ // src/files/list-dir/index.ts
36059
+ import fs33 from "node:fs";
36022
36060
 
36023
36061
  // src/files/ensure-under-cwd.ts
36024
36062
  import path34 from "node:path";
@@ -36031,71 +36069,93 @@ function ensureUnderCwd(relativePath, cwd = getBridgeRoot()) {
36031
36069
  return resolved;
36032
36070
  }
36033
36071
 
36034
- // src/files/list-dir.ts
36072
+ // src/files/list-dir/types.ts
36035
36073
  var LIST_DIR_YIELD_EVERY = 256;
36036
- async function listDirAsync(relativePath) {
36037
- const resolved = ensureUnderCwd(relativePath || ".", getBridgeRoot());
36074
+
36075
+ // src/files/list-dir/map-dir-entry.ts
36076
+ import path35 from "node:path";
36077
+ import fs32 from "node:fs";
36078
+ async function mapDirEntry(d, relativePath, resolved) {
36079
+ const entryPath = path35.join(relativePath || ".", d.name).replace(/\\/g, "/");
36080
+ const fullPath = path35.join(resolved, d.name);
36081
+ let isDir = d.isDirectory();
36082
+ if (d.isSymbolicLink()) {
36083
+ try {
36084
+ const targetStat = await fs32.promises.stat(fullPath);
36085
+ isDir = targetStat.isDirectory();
36086
+ } catch {
36087
+ isDir = false;
36088
+ }
36089
+ }
36090
+ return {
36091
+ name: d.name,
36092
+ path: entryPath,
36093
+ isDir,
36094
+ isSymlink: d.isSymbolicLink()
36095
+ };
36096
+ }
36097
+
36098
+ // src/files/list-dir/sort-entries.ts
36099
+ function sortListEntries(entries) {
36100
+ return entries.sort((a, b) => {
36101
+ if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
36102
+ return a.name.localeCompare(b.name, void 0, { sensitivity: "base" });
36103
+ });
36104
+ }
36105
+
36106
+ // src/files/list-dir/index.ts
36107
+ async function listDirAsync(relativePath, sessionParentPath = getBridgeRoot()) {
36108
+ await yieldToEventLoop();
36109
+ const resolved = ensureUnderCwd(relativePath || ".", sessionParentPath);
36038
36110
  if (!resolved) {
36039
36111
  return { error: "Path is outside working directory" };
36040
36112
  }
36041
36113
  try {
36042
- const names = await fs32.promises.readdir(resolved, { withFileTypes: true });
36043
- const visible = names.filter((d) => !d.name.startsWith("."));
36114
+ const names = await fs33.promises.readdir(resolved, { withFileTypes: true });
36044
36115
  const entries = [];
36045
- for (let i = 0; i < visible.length; i++) {
36116
+ for (let i = 0; i < names.length; i++) {
36046
36117
  if (i > 0 && i % LIST_DIR_YIELD_EVERY === 0) {
36047
36118
  await yieldToEventLoop();
36048
36119
  }
36049
- const d = visible[i];
36050
- const entryPath = path35.join(relativePath || ".", d.name).replace(/\\/g, "/");
36051
- const fullPath = path35.join(resolved, d.name);
36052
- let isDir = d.isDirectory();
36053
- if (d.isSymbolicLink()) {
36054
- try {
36055
- const targetStat = await fs32.promises.stat(fullPath);
36056
- isDir = targetStat.isDirectory();
36057
- } catch {
36058
- isDir = false;
36059
- }
36060
- }
36061
- entries.push({
36062
- name: d.name,
36063
- path: entryPath,
36064
- isDir,
36065
- isSymlink: d.isSymbolicLink()
36066
- });
36120
+ entries.push(await mapDirEntry(names[i], relativePath, resolved));
36067
36121
  }
36068
- entries.sort((a, b) => {
36069
- if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
36070
- return a.name.localeCompare(b.name, void 0, { sensitivity: "base" });
36071
- });
36072
- return { entries };
36122
+ return { entries: sortListEntries(entries) };
36073
36123
  } catch (err) {
36074
36124
  const message = err instanceof Error ? err.message : String(err);
36075
36125
  return { error: message };
36076
36126
  }
36077
36127
  }
36078
36128
 
36079
- // src/files/read-file.ts
36080
- import fs33 from "node:fs";
36081
- import { StringDecoder } from "node:string_decoder";
36082
- function resolveFilePath(relativePath) {
36083
- const resolved = ensureUnderCwd(relativePath, getBridgeRoot());
36129
+ // src/files/read-file/types.ts
36130
+ var LINE_CHUNK_SIZE = 64 * 1024;
36131
+ var READ_RANGE_YIELD_EVERY_BYTES = 256 * 1024;
36132
+
36133
+ // src/files/read-file/resolve-file-path.ts
36134
+ import fs34 from "node:fs";
36135
+ async function resolveFilePathAsync(relativePath, sessionParentPath = getBridgeRoot()) {
36136
+ const resolved = ensureUnderCwd(relativePath, sessionParentPath);
36084
36137
  if (!resolved) return { error: "Path is outside working directory" };
36085
36138
  let real;
36086
36139
  try {
36087
- real = fs33.realpathSync(resolved);
36140
+ real = await fs34.promises.realpath(resolved);
36088
36141
  } catch {
36089
36142
  real = resolved;
36090
36143
  }
36091
- const stat2 = fs33.statSync(real);
36092
- if (!stat2.isFile()) return { error: "Not a file" };
36093
- return real;
36144
+ try {
36145
+ const stat2 = await fs34.promises.stat(real);
36146
+ if (!stat2.isFile()) return { error: "Not a file" };
36147
+ return real;
36148
+ } catch (err) {
36149
+ return { error: err instanceof Error ? err.message : String(err) };
36150
+ }
36094
36151
  }
36095
- var LINE_CHUNK_SIZE = 64 * 1024;
36096
- function readFileRange(filePath, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
36097
- const fileSize = fs33.statSync(filePath).size;
36098
- const fd = fs33.openSync(filePath, "r");
36152
+
36153
+ // src/files/read-file/read-file-range-async.ts
36154
+ import fs35 from "node:fs";
36155
+ import { StringDecoder } from "node:string_decoder";
36156
+ async function readFileRangeAsync(filePath, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
36157
+ const fileSize = (await fs35.promises.stat(filePath)).size;
36158
+ const fd = await fs35.promises.open(filePath, "r");
36099
36159
  const bufSize = 64 * 1024;
36100
36160
  const buf = Buffer.alloc(bufSize);
36101
36161
  const decoder = new StringDecoder("utf8");
@@ -36106,9 +36166,18 @@ function readFileRange(filePath, startLine, endLine, lineOffsetIn, lineChunkSize
36106
36166
  let skipLine0Chars = typeof lineOffsetIn === "number" ? lineOffsetIn : 0;
36107
36167
  let line0CharsReturned = 0;
36108
36168
  let line0Accum = "";
36169
+ let bytesSinceYield = 0;
36109
36170
  try {
36110
- let bytesRead;
36111
- while (!done && (bytesRead = fs33.readSync(fd, buf, 0, bufSize, null)) > 0) {
36171
+ let position = 0;
36172
+ while (!done) {
36173
+ const { bytesRead } = await fd.read(buf, 0, bufSize, position);
36174
+ if (bytesRead === 0) break;
36175
+ position += bytesRead;
36176
+ bytesSinceYield += bytesRead;
36177
+ if (bytesSinceYield >= READ_RANGE_YIELD_EVERY_BYTES) {
36178
+ await yieldToEventLoop();
36179
+ bytesSinceYield = 0;
36180
+ }
36112
36181
  const text = partial2 + decoder.write(buf.subarray(0, bytesRead));
36113
36182
  partial2 = "";
36114
36183
  let lineStart = 0;
@@ -36243,39 +36312,132 @@ function readFileRange(filePath, startLine, endLine, lineOffsetIn, lineChunkSize
36243
36312
  }
36244
36313
  return { content: resultLines.join("\n"), size: fileSize };
36245
36314
  } finally {
36246
- fs33.closeSync(fd);
36315
+ await fd.close();
36247
36316
  }
36248
36317
  }
36249
- function readFile2(relativePath, startLine, endLine, lineOffset, lineChunkSize = LINE_CHUNK_SIZE) {
36318
+
36319
+ // src/files/read-file/read-file-buffer-full-async.ts
36320
+ import fs36 from "node:fs";
36321
+ var READ_CHUNK_BYTES = 256 * 1024;
36322
+ async function readFileBufferFullAsync(filePath) {
36323
+ const stat2 = await fs36.promises.stat(filePath);
36324
+ const fd = await fs36.promises.open(filePath, "r");
36325
+ const chunks = [];
36326
+ let position = 0;
36327
+ let bytesSinceYield = 0;
36250
36328
  try {
36251
- const result = resolveFilePath(relativePath);
36329
+ while (position < stat2.size) {
36330
+ const buf = Buffer.alloc(Math.min(READ_CHUNK_BYTES, stat2.size - position));
36331
+ const { bytesRead } = await fd.read(buf, 0, buf.length, position);
36332
+ if (bytesRead === 0) break;
36333
+ chunks.push(buf.subarray(0, bytesRead));
36334
+ position += bytesRead;
36335
+ bytesSinceYield += bytesRead;
36336
+ if (bytesSinceYield >= READ_RANGE_YIELD_EVERY_BYTES) {
36337
+ await yieldToEventLoop();
36338
+ bytesSinceYield = 0;
36339
+ }
36340
+ }
36341
+ } finally {
36342
+ await fd.close();
36343
+ }
36344
+ return { buffer: Buffer.concat(chunks), size: stat2.size };
36345
+ }
36346
+
36347
+ // src/files/read-file/read-file-full-async.ts
36348
+ async function readFileFullAsync(filePath) {
36349
+ const { buffer, size } = await readFileBufferFullAsync(filePath);
36350
+ const raw = buffer.toString("utf8");
36351
+ const lines = raw.split(/\r?\n/);
36352
+ return { content: raw, totalLines: lines.length, size };
36353
+ }
36354
+
36355
+ // src/files/read-file/guess-mime-type.ts
36356
+ var MIME_BY_EXT = {
36357
+ png: "image/png",
36358
+ jpg: "image/jpeg",
36359
+ jpeg: "image/jpeg",
36360
+ gif: "image/gif",
36361
+ bmp: "image/bmp",
36362
+ ico: "image/x-icon",
36363
+ webp: "image/webp",
36364
+ avif: "image/avif",
36365
+ svg: "image/svg+xml",
36366
+ pdf: "application/pdf",
36367
+ json: "application/json",
36368
+ html: "text/html",
36369
+ htm: "text/html",
36370
+ css: "text/css",
36371
+ js: "text/javascript",
36372
+ mjs: "text/javascript",
36373
+ ts: "text/typescript",
36374
+ txt: "text/plain",
36375
+ md: "text/markdown",
36376
+ xml: "application/xml",
36377
+ zip: "application/zip",
36378
+ gz: "application/gzip",
36379
+ wasm: "application/wasm"
36380
+ };
36381
+ function guessMimeType(filePath) {
36382
+ const ext = filePath.split(".").pop()?.toLowerCase() ?? "";
36383
+ return MIME_BY_EXT[ext] ?? "application/octet-stream";
36384
+ }
36385
+
36386
+ // src/files/read-file/read-file-binary-full-async.ts
36387
+ async function readFileBinaryFullAsync(filePath) {
36388
+ const { buffer, size } = await readFileBufferFullAsync(filePath);
36389
+ return {
36390
+ content: buffer.toString("base64"),
36391
+ size,
36392
+ mimeType: guessMimeType(filePath)
36393
+ };
36394
+ }
36395
+
36396
+ // src/files/read-file/index.ts
36397
+ async function readFileAsync(relativePath, startLine, endLine, lineOffset, lineChunkSize = LINE_CHUNK_SIZE, sessionParentPath = getBridgeRoot(), encoding = "utf8") {
36398
+ await yieldToEventLoop();
36399
+ try {
36400
+ const result = await resolveFilePathAsync(relativePath, sessionParentPath);
36252
36401
  if (typeof result === "object") return result;
36402
+ const resolvedPath = result;
36253
36403
  const hasRange = typeof startLine === "number" && typeof endLine === "number";
36404
+ if (encoding === "base64") {
36405
+ if (hasRange) return { error: "base64 encoding requires a full file read (no line range)" };
36406
+ const read2 = await readFileBinaryFullAsync(resolvedPath);
36407
+ return { ...read2, resolvedPath };
36408
+ }
36254
36409
  if (hasRange) {
36255
- return readFileRange(result, startLine, endLine, lineOffset, lineChunkSize);
36410
+ return readFileRangeAsync(resolvedPath, startLine, endLine, lineOffset, lineChunkSize);
36256
36411
  }
36257
- const stat2 = fs33.statSync(result);
36258
- const raw = fs33.readFileSync(result, "utf8");
36259
- const lines = raw.split(/\r?\n/);
36260
- return { content: raw, totalLines: lines.length, size: stat2.size };
36412
+ const read = await readFileFullAsync(resolvedPath);
36413
+ return { ...read, resolvedPath };
36261
36414
  } catch (err) {
36262
36415
  return { error: err instanceof Error ? err.message : String(err) };
36263
36416
  }
36264
36417
  }
36265
- async function readFileAsync(relativePath, startLine, endLine, lineOffset, lineChunkSize = LINE_CHUNK_SIZE) {
36266
- await yieldToEventLoop();
36267
- return readFile2(relativePath, startLine, endLine, lineOffset, lineChunkSize);
36418
+
36419
+ // src/files/resolve-file-browser-session-parent.ts
36420
+ function resolveFileBrowserSessionParent(sessionWorktreeManager, sessionId) {
36421
+ const sid = sessionId?.trim();
36422
+ if (sid) {
36423
+ sessionWorktreeManager.ensureRepoCheckoutPathsForSession(sid);
36424
+ const worktreeRoot = sessionWorktreeManager.getSessionWorktreeRootForSession(sid);
36425
+ if (worktreeRoot) return worktreeRoot;
36426
+ }
36427
+ return getBridgeRoot();
36268
36428
  }
36269
36429
 
36270
36430
  // src/files/handle-file-browser-search.ts
36271
36431
  import path36 from "node:path";
36272
36432
  var SEARCH_LIMIT = 100;
36273
- function handleFileBrowserSearch(msg, socket, e2ee) {
36433
+ function handleFileBrowserSearch(msg, socket, e2ee, sessionWorktreeManager) {
36274
36434
  void (async () => {
36275
36435
  await yieldToEventLoop();
36276
36436
  const q = typeof msg.q === "string" ? msg.q : "";
36277
- const cwd = path36.resolve(getBridgeRoot());
36278
- if (!await bridgeFileIndexIsPopulated(cwd)) {
36437
+ const sessionParentPath = path36.resolve(
36438
+ sessionWorktreeManager != null ? resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : getBridgeRoot()
36439
+ );
36440
+ if (!await bridgeFileIndexIsPopulated(sessionParentPath)) {
36279
36441
  const payload2 = {
36280
36442
  type: "file_browser_search_response",
36281
36443
  id: msg.id,
@@ -36285,7 +36447,7 @@ function handleFileBrowserSearch(msg, socket, e2ee) {
36285
36447
  sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload2, ["paths"]) : payload2);
36286
36448
  return;
36287
36449
  }
36288
- const results = await searchBridgeFilePathsAsync(cwd, q, SEARCH_LIMIT);
36450
+ const results = await searchBridgeFilePathsAsync(sessionParentPath, q, SEARCH_LIMIT);
36289
36451
  const payload = {
36290
36452
  type: "file_browser_search_response",
36291
36453
  id: msg.id,
@@ -36295,9 +36457,9 @@ function handleFileBrowserSearch(msg, socket, e2ee) {
36295
36457
  sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload, ["paths"]) : payload);
36296
36458
  })();
36297
36459
  }
36298
- function triggerFileIndexBuild() {
36460
+ function triggerFileIndexBuild(sessionParentPath = getBridgeRoot()) {
36299
36461
  setImmediate(() => {
36300
- void ensureFileIndexAsync(getBridgeRoot()).catch((e) => {
36462
+ void ensureFileIndexAsync(sessionParentPath).catch((e) => {
36301
36463
  console.error("[file-index] Background build failed:", e);
36302
36464
  });
36303
36465
  });
@@ -36307,18 +36469,19 @@ function triggerFileIndexBuild() {
36307
36469
  function sendFileBrowserMessage(socket, e2ee, payload) {
36308
36470
  sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload, ["entries", "content", "totalLines", "size", "lineOffset"]) : payload);
36309
36471
  }
36310
- function handleFileBrowserRequest(msg, socket, e2ee) {
36472
+ function handleFileBrowserRequest(msg, socket, e2ee, sessionWorktreeManager) {
36311
36473
  void (async () => {
36312
36474
  const reqPath = msg.path.replace(/^\/+/, "") || ".";
36313
36475
  const op = msg.op === "read" ? "read" : "list";
36476
+ const sessionParentPath = sessionWorktreeManager != null ? resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : void 0;
36314
36477
  if (op === "list") {
36315
- const result = await listDirAsync(reqPath);
36478
+ const result = await listDirAsync(reqPath, sessionParentPath);
36316
36479
  if ("error" in result) {
36317
36480
  sendWsMessage(socket, { type: "file_browser_response", id: msg.id, error: result.error });
36318
36481
  } else {
36319
36482
  sendFileBrowserMessage(socket, e2ee, { type: "file_browser_response", id: msg.id, entries: result.entries });
36320
36483
  if (reqPath === "." || reqPath === "") {
36321
- triggerFileIndexBuild();
36484
+ triggerFileIndexBuild(sessionParentPath);
36322
36485
  }
36323
36486
  }
36324
36487
  } else {
@@ -36326,7 +36489,16 @@ function handleFileBrowserRequest(msg, socket, e2ee) {
36326
36489
  const endLine = typeof msg.endLine === "number" ? msg.endLine : void 0;
36327
36490
  const lineOffset = typeof msg.lineOffset === "number" ? msg.lineOffset : void 0;
36328
36491
  const lineChunkSize = typeof msg.lineChunkSize === "number" ? msg.lineChunkSize : void 0;
36329
- const result = await readFileAsync(reqPath, startLine, endLine, lineOffset, lineChunkSize);
36492
+ const encoding = msg.encoding === "base64" ? "base64" : "utf8";
36493
+ const result = await readFileAsync(
36494
+ reqPath,
36495
+ startLine,
36496
+ endLine,
36497
+ lineOffset,
36498
+ lineChunkSize,
36499
+ sessionParentPath,
36500
+ encoding
36501
+ );
36330
36502
  if ("error" in result) {
36331
36503
  sendWsMessage(socket, { type: "file_browser_response", id: msg.id, error: result.error });
36332
36504
  } else {
@@ -36338,6 +36510,8 @@ function handleFileBrowserRequest(msg, socket, e2ee) {
36338
36510
  size: result.size
36339
36511
  };
36340
36512
  if (result.lineOffset != null) payload.lineOffset = result.lineOffset;
36513
+ if (result.mimeType != null) payload.mimeType = result.mimeType;
36514
+ if (result.resolvedPath != null) payload.resolvedPath = result.resolvedPath;
36341
36515
  sendFileBrowserMessage(socket, e2ee, payload);
36342
36516
  }
36343
36517
  }
@@ -36345,21 +36519,27 @@ function handleFileBrowserRequest(msg, socket, e2ee) {
36345
36519
  }
36346
36520
 
36347
36521
  // src/routing/handlers/file-browser-messages.ts
36348
- function handleFileBrowserRequestMessage(msg, { getWs, e2ee }) {
36522
+ function handleFileBrowserRequestMessage(msg, { getWs, e2ee, sessionWorktreeManager }) {
36349
36523
  if (typeof msg.id !== "string" || typeof msg.path !== "string") return;
36350
36524
  const socket = getWs();
36351
36525
  if (!socket) return;
36352
36526
  handleFileBrowserRequest(
36353
36527
  msg,
36354
36528
  socket,
36355
- e2ee
36529
+ e2ee,
36530
+ sessionWorktreeManager
36356
36531
  );
36357
36532
  }
36358
- function handleFileBrowserSearchMessage(msg, { getWs, e2ee }) {
36533
+ function handleFileBrowserSearchMessage(msg, { getWs, e2ee, sessionWorktreeManager }) {
36359
36534
  if (typeof msg.id !== "string") return;
36360
36535
  const socket = getWs();
36361
36536
  if (!socket) return;
36362
- handleFileBrowserSearch(msg, socket, e2ee);
36537
+ handleFileBrowserSearch(
36538
+ msg,
36539
+ socket,
36540
+ e2ee,
36541
+ sessionWorktreeManager
36542
+ );
36363
36543
  }
36364
36544
 
36365
36545
  // src/routing/handlers/skill-layout-request.ts
@@ -36373,7 +36553,7 @@ function handleSkillLayoutRequest(msg, deps) {
36373
36553
  }
36374
36554
 
36375
36555
  // src/skills/install-remote-skills.ts
36376
- import fs34 from "node:fs";
36556
+ import fs37 from "node:fs";
36377
36557
  import path37 from "node:path";
36378
36558
  function installRemoteSkills(cwd, targetDir, items) {
36379
36559
  const installed2 = [];
@@ -36389,11 +36569,11 @@ function installRemoteSkills(cwd, targetDir, items) {
36389
36569
  for (const f of item.files) {
36390
36570
  if (typeof f.path !== "string" || !f.text && !f.base64) continue;
36391
36571
  const dest = path37.join(skillDir, f.path);
36392
- fs34.mkdirSync(path37.dirname(dest), { recursive: true });
36572
+ fs37.mkdirSync(path37.dirname(dest), { recursive: true });
36393
36573
  if (f.text !== void 0) {
36394
- fs34.writeFileSync(dest, f.text, "utf8");
36574
+ fs37.writeFileSync(dest, f.text, "utf8");
36395
36575
  } else if (f.base64) {
36396
- fs34.writeFileSync(dest, Buffer.from(f.base64, "base64"));
36576
+ fs37.writeFileSync(dest, Buffer.from(f.base64, "base64"));
36397
36577
  }
36398
36578
  }
36399
36579
  installed2.push({
@@ -36551,7 +36731,7 @@ var handleSessionDiscardedMessage = (msg, deps) => {
36551
36731
  };
36552
36732
 
36553
36733
  // src/routing/handlers/revert-turn-snapshot.ts
36554
- import * as fs35 from "node:fs";
36734
+ import * as fs38 from "node:fs";
36555
36735
  var handleRevertTurnSnapshotMessage = (msg, deps) => {
36556
36736
  const id = typeof msg.id === "string" ? msg.id : "";
36557
36737
  const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
@@ -36564,7 +36744,7 @@ var handleRevertTurnSnapshotMessage = (msg, deps) => {
36564
36744
  const agentBase = sessionWorktreeManager.getSessionWorktreeRootForSession(sessionId) ?? getBridgeRoot();
36565
36745
  const file2 = snapshotFilePath(agentBase, turnId);
36566
36746
  try {
36567
- await fs35.promises.access(file2, fs35.constants.F_OK);
36747
+ await fs38.promises.access(file2, fs38.constants.F_OK);
36568
36748
  } catch {
36569
36749
  sendWsMessage(s, {
36570
36750
  type: "revert_turn_snapshot_result",