@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/cli.js CHANGED
@@ -974,7 +974,7 @@ var require_command = __commonJS({
974
974
  var EventEmitter2 = __require("node:events").EventEmitter;
975
975
  var childProcess2 = __require("node:child_process");
976
976
  var path43 = __require("node:path");
977
- var fs38 = __require("node:fs");
977
+ var fs41 = __require("node:fs");
978
978
  var process8 = __require("node:process");
979
979
  var { Argument: Argument2, humanReadableArgName } = require_argument();
980
980
  var { CommanderError: CommanderError2 } = require_error();
@@ -1907,10 +1907,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
1907
1907
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
1908
1908
  function findFile(baseDir, baseName) {
1909
1909
  const localBin = path43.resolve(baseDir, baseName);
1910
- if (fs38.existsSync(localBin)) return localBin;
1910
+ if (fs41.existsSync(localBin)) return localBin;
1911
1911
  if (sourceExt.includes(path43.extname(baseName))) return void 0;
1912
1912
  const foundExt = sourceExt.find(
1913
- (ext) => fs38.existsSync(`${localBin}${ext}`)
1913
+ (ext) => fs41.existsSync(`${localBin}${ext}`)
1914
1914
  );
1915
1915
  if (foundExt) return `${localBin}${foundExt}`;
1916
1916
  return void 0;
@@ -1922,7 +1922,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1922
1922
  if (this._scriptPath) {
1923
1923
  let resolvedScriptPath;
1924
1924
  try {
1925
- resolvedScriptPath = fs38.realpathSync(this._scriptPath);
1925
+ resolvedScriptPath = fs41.realpathSync(this._scriptPath);
1926
1926
  } catch (err) {
1927
1927
  resolvedScriptPath = this._scriptPath;
1928
1928
  }
@@ -25064,14 +25064,14 @@ var {
25064
25064
  } = import_index.default;
25065
25065
 
25066
25066
  // src/cli-version.ts
25067
- var CLI_VERSION = "0.1.35".length > 0 ? "0.1.35" : "0.0.0-dev";
25067
+ var CLI_VERSION = "0.1.36".length > 0 ? "0.1.36" : "0.0.0-dev";
25068
25068
 
25069
25069
  // src/cli/defaults.ts
25070
25070
  var DEFAULT_API_URL = process.env.BUILDAUTOMATON_API_URL ?? "https://api.buildautomaton.com";
25071
25071
  var DEFAULT_FIREHOSE_URL = "https://buildautomaton-firehose.fly.dev";
25072
25072
 
25073
25073
  // src/cli/run-cli-action.ts
25074
- import * as fs37 from "node:fs";
25074
+ import * as fs40 from "node:fs";
25075
25075
  import * as path42 from "node:path";
25076
25076
 
25077
25077
  // src/cli-log-level.ts
@@ -26961,6 +26961,7 @@ function recordMigrationAndPruneCheckpointLegacy(db, migration, applied2) {
26961
26961
  var CHECKPOINT_V1 = "001_cli_sqlite_checkpoint_v1";
26962
26962
  var CHECKPOINT_V1_SQL = readCliSqliteMigrationSql("001_cli_sqlite_checkpoint_v1.sql");
26963
26963
  var AGENT_CAPABILITIES_SQL = readCliSqliteMigrationSql("002_agent_capabilities.sql");
26964
+ var FILE_INDEX_PARENT_PATHS_SQL = readCliSqliteMigrationSql("003_file_index_parent_paths.sql");
26964
26965
  function agentCapabilitiesTableState(db) {
26965
26966
  const rows = db.all(
26966
26967
  `SELECT name FROM sqlite_master WHERE type='table' AND name IN ('agent_capabilities', 'agent_capability_cache')`
@@ -26994,6 +26995,18 @@ var CLI_SQLITE_MIGRATIONS = [
26994
26995
  db.exec(AGENT_CAPABILITIES_SQL);
26995
26996
  },
26996
26997
  alreadyApplied: (db) => agentCapabilitiesTableState(db) === "current"
26998
+ },
26999
+ {
27000
+ name: "003_file_index_parent_paths",
27001
+ migrate: (db) => {
27002
+ db.exec(FILE_INDEX_PARENT_PATHS_SQL);
27003
+ },
27004
+ alreadyApplied: (db) => {
27005
+ const row = db.get(
27006
+ `SELECT 1 as ok FROM sqlite_master WHERE type='table' AND name='file_index_parent_path' LIMIT 1`
27007
+ );
27008
+ return row != null;
27009
+ }
26997
27010
  }
26998
27011
  ];
26999
27012
  function migrateCliSqlite(db) {
@@ -27032,6 +27045,10 @@ var CliSqliteInterrupted = class extends Error {
27032
27045
  }
27033
27046
  };
27034
27047
  function applyCliSqliteConcurrencyPragmas(db) {
27048
+ try {
27049
+ db.run("PRAGMA foreign_keys = ON");
27050
+ } catch {
27051
+ }
27035
27052
  try {
27036
27053
  db.exec("PRAGMA journal_mode = WAL");
27037
27054
  } catch {
@@ -36743,8 +36760,18 @@ import path29 from "node:path";
36743
36760
  // src/files/index/walk-workspace-tree.ts
36744
36761
  import fs25 from "node:fs";
36745
36762
  import path28 from "node:path";
36763
+ var DEPENDENCY_INSTALL_DIR_NAMES = /* @__PURE__ */ new Set([
36764
+ "node_modules",
36765
+ "bower_components",
36766
+ "vendor",
36767
+ "Pods",
36768
+ "Carthage",
36769
+ "DerivedData",
36770
+ ".yarn",
36771
+ ".pnpm-store"
36772
+ ]);
36746
36773
  function shouldSkipWorkspaceWalkEntry(name) {
36747
- return name.startsWith(".");
36774
+ return DEPENDENCY_INSTALL_DIR_NAMES.has(name);
36748
36775
  }
36749
36776
  async function walkWorkspaceTreeAsync(dir, baseDir, onFile, state) {
36750
36777
  let names;
@@ -36815,14 +36842,26 @@ var FILE_INDEX_INTERRUPT_CHECK_EVERY = 256;
36815
36842
  function assertNotShutdown() {
36816
36843
  if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
36817
36844
  }
36845
+ function upsertFileIndexParentPath(db, resolved) {
36846
+ const now = (/* @__PURE__ */ new Date()).toISOString();
36847
+ db.run(
36848
+ `INSERT INTO file_index_parent_path (path, path_hash, updated_at)
36849
+ VALUES (?, ?, ?)
36850
+ ON CONFLICT(path) DO UPDATE SET path_hash = excluded.path_hash, updated_at = excluded.updated_at`,
36851
+ [resolved, getCwdHashForFileIndex(resolved), now]
36852
+ );
36853
+ const row = db.get("SELECT id FROM file_index_parent_path WHERE path = ?", [resolved]);
36854
+ if (row == null) throw new Error(`Failed to upsert file index parent path: ${resolved}`);
36855
+ return Number(row.id);
36856
+ }
36818
36857
  function persistFileIndexPaths(resolved, paths) {
36819
36858
  return withCliSqliteSync((db) => {
36820
- const h = getCwdHashForFileIndex(resolved);
36821
36859
  let pathCount = 0;
36822
36860
  db.run("BEGIN IMMEDIATE");
36823
36861
  try {
36824
- db.run("DELETE FROM file_index_path WHERE cwd_hash = ?", [h]);
36825
- const ins = db.prepare("INSERT INTO file_index_path (cwd_hash, path) VALUES (?, ?)");
36862
+ const parentId = upsertFileIndexParentPath(db, resolved);
36863
+ db.run("DELETE FROM file_index_child_path WHERE parent_id = ?", [parentId]);
36864
+ const ins = db.prepare("INSERT INTO file_index_child_path (parent_id, path) VALUES (?, ?)");
36826
36865
  try {
36827
36866
  let batch = 0;
36828
36867
  for (const rel of paths) {
@@ -36830,7 +36869,7 @@ function persistFileIndexPaths(resolved, paths) {
36830
36869
  batch = 0;
36831
36870
  assertNotShutdown();
36832
36871
  }
36833
- ins.run([h, rel]);
36872
+ ins.run([parentId, rel]);
36834
36873
  pathCount += 1;
36835
36874
  }
36836
36875
  } finally {
@@ -36872,36 +36911,38 @@ async function buildFileIndexAsync(cwd) {
36872
36911
  // src/files/index/ensure-file-index.ts
36873
36912
  import path30 from "node:path";
36874
36913
 
36875
- // src/files/index/file-index-dependency-path.ts
36876
- function sqliteExprBridgeFileIndexDependencyRank() {
36877
- 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`;
36878
- }
36879
-
36880
36914
  // src/files/index/search-file-index.ts
36881
36915
  function escapeLikePattern(fragment) {
36882
36916
  return fragment.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
36883
36917
  }
36884
36918
  function bridgeFileIndexIsPopulatedWithDb(resolvedCwd, db) {
36885
- const h = getCwdHashForFileIndex(resolvedCwd);
36886
- const row = db.get("SELECT 1 as ok FROM file_index_path WHERE cwd_hash = ? LIMIT 1", [h]);
36919
+ const row = db.get("SELECT 1 as ok FROM file_index_parent_path WHERE path = ? LIMIT 1", [resolvedCwd]);
36887
36920
  return row != null;
36888
36921
  }
36889
36922
  function bridgeFileIndexPathCountWithDb(resolvedCwd, db) {
36890
- const h = getCwdHashForFileIndex(resolvedCwd);
36891
- const row = db.get("SELECT COUNT(*) as c FROM file_index_path WHERE cwd_hash = ?", [h]);
36923
+ const row = db.get(
36924
+ `SELECT COUNT(*) as c
36925
+ FROM file_index_child_path child
36926
+ JOIN file_index_parent_path parent ON parent.id = child.parent_id
36927
+ WHERE parent.path = ?`,
36928
+ [resolvedCwd]
36929
+ );
36892
36930
  const c = row?.c ?? 0;
36893
36931
  return Number(c);
36894
36932
  }
36895
36933
  function searchBridgeFilePathsWithDb(resolvedCwd, query, limit, db) {
36896
36934
  const q = query.trim().toLowerCase();
36897
36935
  if (!q) return [];
36898
- const h = getCwdHashForFileIndex(resolvedCwd);
36899
36936
  const pattern = `%${escapeLikePattern(q)}%`;
36900
36937
  const lim = Math.max(0, Math.min(1e4, Math.floor(limit)));
36901
- const depRank = sqliteExprBridgeFileIndexDependencyRank();
36902
36938
  const rows = db.all(
36903
- `SELECT path FROM file_index_path WHERE cwd_hash = ? AND lower(path) LIKE ? ESCAPE '\\' ORDER BY ${depRank}, path LIMIT ?`,
36904
- [h, pattern, lim]
36939
+ `SELECT child.path
36940
+ FROM file_index_child_path child
36941
+ JOIN file_index_parent_path parent ON parent.id = child.parent_id
36942
+ WHERE parent.path = ? AND lower(child.path) LIKE ? ESCAPE '\\'
36943
+ ORDER BY child.path
36944
+ LIMIT ?`,
36945
+ [resolvedCwd, pattern, lim]
36905
36946
  );
36906
36947
  return rows.map((r) => String(r.path));
36907
36948
  }
@@ -36931,9 +36972,7 @@ async function ensureFileIndexAsync(cwd) {
36931
36972
  var DEBOUNCE_MS = 900;
36932
36973
  function shouldIgnoreRelative(rel) {
36933
36974
  const n = rel.replace(/\\/g, "/");
36934
- if (n.includes("/.git/") || n === ".git" || n.startsWith(".git/")) return true;
36935
- if (n.includes("/.buildautomaton/") || n.startsWith(".buildautomaton/")) return true;
36936
- return false;
36975
+ return n.split("/").some((segment) => shouldSkipWorkspaceWalkEntry(segment));
36937
36976
  }
36938
36977
  function attachWatchErrorLog(w) {
36939
36978
  w.on("error", (err) => {
@@ -39318,9 +39357,8 @@ var handleSkillCallMessage = (msg, { getWs, log: log2 }) => {
39318
39357
  );
39319
39358
  };
39320
39359
 
39321
- // src/files/list-dir.ts
39322
- import fs33 from "node:fs";
39323
- import path36 from "node:path";
39360
+ // src/files/list-dir/index.ts
39361
+ import fs34 from "node:fs";
39324
39362
 
39325
39363
  // src/files/ensure-under-cwd.ts
39326
39364
  import path35 from "node:path";
@@ -39333,71 +39371,93 @@ function ensureUnderCwd(relativePath, cwd = getBridgeRoot()) {
39333
39371
  return resolved;
39334
39372
  }
39335
39373
 
39336
- // src/files/list-dir.ts
39374
+ // src/files/list-dir/types.ts
39337
39375
  var LIST_DIR_YIELD_EVERY = 256;
39338
- async function listDirAsync(relativePath) {
39339
- const resolved = ensureUnderCwd(relativePath || ".", getBridgeRoot());
39376
+
39377
+ // src/files/list-dir/map-dir-entry.ts
39378
+ import path36 from "node:path";
39379
+ import fs33 from "node:fs";
39380
+ async function mapDirEntry(d, relativePath, resolved) {
39381
+ const entryPath = path36.join(relativePath || ".", d.name).replace(/\\/g, "/");
39382
+ const fullPath = path36.join(resolved, d.name);
39383
+ let isDir = d.isDirectory();
39384
+ if (d.isSymbolicLink()) {
39385
+ try {
39386
+ const targetStat = await fs33.promises.stat(fullPath);
39387
+ isDir = targetStat.isDirectory();
39388
+ } catch {
39389
+ isDir = false;
39390
+ }
39391
+ }
39392
+ return {
39393
+ name: d.name,
39394
+ path: entryPath,
39395
+ isDir,
39396
+ isSymlink: d.isSymbolicLink()
39397
+ };
39398
+ }
39399
+
39400
+ // src/files/list-dir/sort-entries.ts
39401
+ function sortListEntries(entries) {
39402
+ return entries.sort((a, b) => {
39403
+ if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
39404
+ return a.name.localeCompare(b.name, void 0, { sensitivity: "base" });
39405
+ });
39406
+ }
39407
+
39408
+ // src/files/list-dir/index.ts
39409
+ async function listDirAsync(relativePath, sessionParentPath = getBridgeRoot()) {
39410
+ await yieldToEventLoop();
39411
+ const resolved = ensureUnderCwd(relativePath || ".", sessionParentPath);
39340
39412
  if (!resolved) {
39341
39413
  return { error: "Path is outside working directory" };
39342
39414
  }
39343
39415
  try {
39344
- const names = await fs33.promises.readdir(resolved, { withFileTypes: true });
39345
- const visible = names.filter((d) => !d.name.startsWith("."));
39416
+ const names = await fs34.promises.readdir(resolved, { withFileTypes: true });
39346
39417
  const entries = [];
39347
- for (let i = 0; i < visible.length; i++) {
39418
+ for (let i = 0; i < names.length; i++) {
39348
39419
  if (i > 0 && i % LIST_DIR_YIELD_EVERY === 0) {
39349
39420
  await yieldToEventLoop();
39350
39421
  }
39351
- const d = visible[i];
39352
- const entryPath = path36.join(relativePath || ".", d.name).replace(/\\/g, "/");
39353
- const fullPath = path36.join(resolved, d.name);
39354
- let isDir = d.isDirectory();
39355
- if (d.isSymbolicLink()) {
39356
- try {
39357
- const targetStat = await fs33.promises.stat(fullPath);
39358
- isDir = targetStat.isDirectory();
39359
- } catch {
39360
- isDir = false;
39361
- }
39362
- }
39363
- entries.push({
39364
- name: d.name,
39365
- path: entryPath,
39366
- isDir,
39367
- isSymlink: d.isSymbolicLink()
39368
- });
39422
+ entries.push(await mapDirEntry(names[i], relativePath, resolved));
39369
39423
  }
39370
- entries.sort((a, b) => {
39371
- if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
39372
- return a.name.localeCompare(b.name, void 0, { sensitivity: "base" });
39373
- });
39374
- return { entries };
39424
+ return { entries: sortListEntries(entries) };
39375
39425
  } catch (err) {
39376
39426
  const message = err instanceof Error ? err.message : String(err);
39377
39427
  return { error: message };
39378
39428
  }
39379
39429
  }
39380
39430
 
39381
- // src/files/read-file.ts
39382
- import fs34 from "node:fs";
39383
- import { StringDecoder } from "node:string_decoder";
39384
- function resolveFilePath(relativePath) {
39385
- const resolved = ensureUnderCwd(relativePath, getBridgeRoot());
39431
+ // src/files/read-file/types.ts
39432
+ var LINE_CHUNK_SIZE = 64 * 1024;
39433
+ var READ_RANGE_YIELD_EVERY_BYTES = 256 * 1024;
39434
+
39435
+ // src/files/read-file/resolve-file-path.ts
39436
+ import fs35 from "node:fs";
39437
+ async function resolveFilePathAsync(relativePath, sessionParentPath = getBridgeRoot()) {
39438
+ const resolved = ensureUnderCwd(relativePath, sessionParentPath);
39386
39439
  if (!resolved) return { error: "Path is outside working directory" };
39387
39440
  let real;
39388
39441
  try {
39389
- real = fs34.realpathSync(resolved);
39442
+ real = await fs35.promises.realpath(resolved);
39390
39443
  } catch {
39391
39444
  real = resolved;
39392
39445
  }
39393
- const stat3 = fs34.statSync(real);
39394
- if (!stat3.isFile()) return { error: "Not a file" };
39395
- return real;
39446
+ try {
39447
+ const stat3 = await fs35.promises.stat(real);
39448
+ if (!stat3.isFile()) return { error: "Not a file" };
39449
+ return real;
39450
+ } catch (err) {
39451
+ return { error: err instanceof Error ? err.message : String(err) };
39452
+ }
39396
39453
  }
39397
- var LINE_CHUNK_SIZE = 64 * 1024;
39398
- function readFileRange(filePath, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
39399
- const fileSize = fs34.statSync(filePath).size;
39400
- const fd = fs34.openSync(filePath, "r");
39454
+
39455
+ // src/files/read-file/read-file-range-async.ts
39456
+ import fs36 from "node:fs";
39457
+ import { StringDecoder } from "node:string_decoder";
39458
+ async function readFileRangeAsync(filePath, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
39459
+ const fileSize = (await fs36.promises.stat(filePath)).size;
39460
+ const fd = await fs36.promises.open(filePath, "r");
39401
39461
  const bufSize = 64 * 1024;
39402
39462
  const buf = Buffer.alloc(bufSize);
39403
39463
  const decoder = new StringDecoder("utf8");
@@ -39408,9 +39468,18 @@ function readFileRange(filePath, startLine, endLine, lineOffsetIn, lineChunkSize
39408
39468
  let skipLine0Chars = typeof lineOffsetIn === "number" ? lineOffsetIn : 0;
39409
39469
  let line0CharsReturned = 0;
39410
39470
  let line0Accum = "";
39471
+ let bytesSinceYield = 0;
39411
39472
  try {
39412
- let bytesRead;
39413
- while (!done && (bytesRead = fs34.readSync(fd, buf, 0, bufSize, null)) > 0) {
39473
+ let position = 0;
39474
+ while (!done) {
39475
+ const { bytesRead } = await fd.read(buf, 0, bufSize, position);
39476
+ if (bytesRead === 0) break;
39477
+ position += bytesRead;
39478
+ bytesSinceYield += bytesRead;
39479
+ if (bytesSinceYield >= READ_RANGE_YIELD_EVERY_BYTES) {
39480
+ await yieldToEventLoop();
39481
+ bytesSinceYield = 0;
39482
+ }
39414
39483
  const text = partial2 + decoder.write(buf.subarray(0, bytesRead));
39415
39484
  partial2 = "";
39416
39485
  let lineStart = 0;
@@ -39545,39 +39614,132 @@ function readFileRange(filePath, startLine, endLine, lineOffsetIn, lineChunkSize
39545
39614
  }
39546
39615
  return { content: resultLines.join("\n"), size: fileSize };
39547
39616
  } finally {
39548
- fs34.closeSync(fd);
39617
+ await fd.close();
39549
39618
  }
39550
39619
  }
39551
- function readFile3(relativePath, startLine, endLine, lineOffset, lineChunkSize = LINE_CHUNK_SIZE) {
39620
+
39621
+ // src/files/read-file/read-file-buffer-full-async.ts
39622
+ import fs37 from "node:fs";
39623
+ var READ_CHUNK_BYTES = 256 * 1024;
39624
+ async function readFileBufferFullAsync(filePath) {
39625
+ const stat3 = await fs37.promises.stat(filePath);
39626
+ const fd = await fs37.promises.open(filePath, "r");
39627
+ const chunks = [];
39628
+ let position = 0;
39629
+ let bytesSinceYield = 0;
39552
39630
  try {
39553
- const result = resolveFilePath(relativePath);
39631
+ while (position < stat3.size) {
39632
+ const buf = Buffer.alloc(Math.min(READ_CHUNK_BYTES, stat3.size - position));
39633
+ const { bytesRead } = await fd.read(buf, 0, buf.length, position);
39634
+ if (bytesRead === 0) break;
39635
+ chunks.push(buf.subarray(0, bytesRead));
39636
+ position += bytesRead;
39637
+ bytesSinceYield += bytesRead;
39638
+ if (bytesSinceYield >= READ_RANGE_YIELD_EVERY_BYTES) {
39639
+ await yieldToEventLoop();
39640
+ bytesSinceYield = 0;
39641
+ }
39642
+ }
39643
+ } finally {
39644
+ await fd.close();
39645
+ }
39646
+ return { buffer: Buffer.concat(chunks), size: stat3.size };
39647
+ }
39648
+
39649
+ // src/files/read-file/read-file-full-async.ts
39650
+ async function readFileFullAsync(filePath) {
39651
+ const { buffer, size } = await readFileBufferFullAsync(filePath);
39652
+ const raw = buffer.toString("utf8");
39653
+ const lines = raw.split(/\r?\n/);
39654
+ return { content: raw, totalLines: lines.length, size };
39655
+ }
39656
+
39657
+ // src/files/read-file/guess-mime-type.ts
39658
+ var MIME_BY_EXT = {
39659
+ png: "image/png",
39660
+ jpg: "image/jpeg",
39661
+ jpeg: "image/jpeg",
39662
+ gif: "image/gif",
39663
+ bmp: "image/bmp",
39664
+ ico: "image/x-icon",
39665
+ webp: "image/webp",
39666
+ avif: "image/avif",
39667
+ svg: "image/svg+xml",
39668
+ pdf: "application/pdf",
39669
+ json: "application/json",
39670
+ html: "text/html",
39671
+ htm: "text/html",
39672
+ css: "text/css",
39673
+ js: "text/javascript",
39674
+ mjs: "text/javascript",
39675
+ ts: "text/typescript",
39676
+ txt: "text/plain",
39677
+ md: "text/markdown",
39678
+ xml: "application/xml",
39679
+ zip: "application/zip",
39680
+ gz: "application/gzip",
39681
+ wasm: "application/wasm"
39682
+ };
39683
+ function guessMimeType(filePath) {
39684
+ const ext = filePath.split(".").pop()?.toLowerCase() ?? "";
39685
+ return MIME_BY_EXT[ext] ?? "application/octet-stream";
39686
+ }
39687
+
39688
+ // src/files/read-file/read-file-binary-full-async.ts
39689
+ async function readFileBinaryFullAsync(filePath) {
39690
+ const { buffer, size } = await readFileBufferFullAsync(filePath);
39691
+ return {
39692
+ content: buffer.toString("base64"),
39693
+ size,
39694
+ mimeType: guessMimeType(filePath)
39695
+ };
39696
+ }
39697
+
39698
+ // src/files/read-file/index.ts
39699
+ async function readFileAsync(relativePath, startLine, endLine, lineOffset, lineChunkSize = LINE_CHUNK_SIZE, sessionParentPath = getBridgeRoot(), encoding = "utf8") {
39700
+ await yieldToEventLoop();
39701
+ try {
39702
+ const result = await resolveFilePathAsync(relativePath, sessionParentPath);
39554
39703
  if (typeof result === "object") return result;
39704
+ const resolvedPath = result;
39555
39705
  const hasRange = typeof startLine === "number" && typeof endLine === "number";
39706
+ if (encoding === "base64") {
39707
+ if (hasRange) return { error: "base64 encoding requires a full file read (no line range)" };
39708
+ const read2 = await readFileBinaryFullAsync(resolvedPath);
39709
+ return { ...read2, resolvedPath };
39710
+ }
39556
39711
  if (hasRange) {
39557
- return readFileRange(result, startLine, endLine, lineOffset, lineChunkSize);
39712
+ return readFileRangeAsync(resolvedPath, startLine, endLine, lineOffset, lineChunkSize);
39558
39713
  }
39559
- const stat3 = fs34.statSync(result);
39560
- const raw = fs34.readFileSync(result, "utf8");
39561
- const lines = raw.split(/\r?\n/);
39562
- return { content: raw, totalLines: lines.length, size: stat3.size };
39714
+ const read = await readFileFullAsync(resolvedPath);
39715
+ return { ...read, resolvedPath };
39563
39716
  } catch (err) {
39564
39717
  return { error: err instanceof Error ? err.message : String(err) };
39565
39718
  }
39566
39719
  }
39567
- async function readFileAsync(relativePath, startLine, endLine, lineOffset, lineChunkSize = LINE_CHUNK_SIZE) {
39568
- await yieldToEventLoop();
39569
- return readFile3(relativePath, startLine, endLine, lineOffset, lineChunkSize);
39720
+
39721
+ // src/files/resolve-file-browser-session-parent.ts
39722
+ function resolveFileBrowserSessionParent(sessionWorktreeManager, sessionId) {
39723
+ const sid = sessionId?.trim();
39724
+ if (sid) {
39725
+ sessionWorktreeManager.ensureRepoCheckoutPathsForSession(sid);
39726
+ const worktreeRoot = sessionWorktreeManager.getSessionWorktreeRootForSession(sid);
39727
+ if (worktreeRoot) return worktreeRoot;
39728
+ }
39729
+ return getBridgeRoot();
39570
39730
  }
39571
39731
 
39572
39732
  // src/files/handle-file-browser-search.ts
39573
39733
  import path37 from "node:path";
39574
39734
  var SEARCH_LIMIT = 100;
39575
- function handleFileBrowserSearch(msg, socket, e2ee) {
39735
+ function handleFileBrowserSearch(msg, socket, e2ee, sessionWorktreeManager) {
39576
39736
  void (async () => {
39577
39737
  await yieldToEventLoop();
39578
39738
  const q = typeof msg.q === "string" ? msg.q : "";
39579
- const cwd = path37.resolve(getBridgeRoot());
39580
- if (!await bridgeFileIndexIsPopulated(cwd)) {
39739
+ const sessionParentPath = path37.resolve(
39740
+ sessionWorktreeManager != null ? resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : getBridgeRoot()
39741
+ );
39742
+ if (!await bridgeFileIndexIsPopulated(sessionParentPath)) {
39581
39743
  const payload2 = {
39582
39744
  type: "file_browser_search_response",
39583
39745
  id: msg.id,
@@ -39587,7 +39749,7 @@ function handleFileBrowserSearch(msg, socket, e2ee) {
39587
39749
  sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload2, ["paths"]) : payload2);
39588
39750
  return;
39589
39751
  }
39590
- const results = await searchBridgeFilePathsAsync(cwd, q, SEARCH_LIMIT);
39752
+ const results = await searchBridgeFilePathsAsync(sessionParentPath, q, SEARCH_LIMIT);
39591
39753
  const payload = {
39592
39754
  type: "file_browser_search_response",
39593
39755
  id: msg.id,
@@ -39597,9 +39759,9 @@ function handleFileBrowserSearch(msg, socket, e2ee) {
39597
39759
  sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload, ["paths"]) : payload);
39598
39760
  })();
39599
39761
  }
39600
- function triggerFileIndexBuild() {
39762
+ function triggerFileIndexBuild(sessionParentPath = getBridgeRoot()) {
39601
39763
  setImmediate(() => {
39602
- void ensureFileIndexAsync(getBridgeRoot()).catch((e) => {
39764
+ void ensureFileIndexAsync(sessionParentPath).catch((e) => {
39603
39765
  console.error("[file-index] Background build failed:", e);
39604
39766
  });
39605
39767
  });
@@ -39609,18 +39771,19 @@ function triggerFileIndexBuild() {
39609
39771
  function sendFileBrowserMessage(socket, e2ee, payload) {
39610
39772
  sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload, ["entries", "content", "totalLines", "size", "lineOffset"]) : payload);
39611
39773
  }
39612
- function handleFileBrowserRequest(msg, socket, e2ee) {
39774
+ function handleFileBrowserRequest(msg, socket, e2ee, sessionWorktreeManager) {
39613
39775
  void (async () => {
39614
39776
  const reqPath = msg.path.replace(/^\/+/, "") || ".";
39615
39777
  const op = msg.op === "read" ? "read" : "list";
39778
+ const sessionParentPath = sessionWorktreeManager != null ? resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : void 0;
39616
39779
  if (op === "list") {
39617
- const result = await listDirAsync(reqPath);
39780
+ const result = await listDirAsync(reqPath, sessionParentPath);
39618
39781
  if ("error" in result) {
39619
39782
  sendWsMessage(socket, { type: "file_browser_response", id: msg.id, error: result.error });
39620
39783
  } else {
39621
39784
  sendFileBrowserMessage(socket, e2ee, { type: "file_browser_response", id: msg.id, entries: result.entries });
39622
39785
  if (reqPath === "." || reqPath === "") {
39623
- triggerFileIndexBuild();
39786
+ triggerFileIndexBuild(sessionParentPath);
39624
39787
  }
39625
39788
  }
39626
39789
  } else {
@@ -39628,7 +39791,16 @@ function handleFileBrowserRequest(msg, socket, e2ee) {
39628
39791
  const endLine = typeof msg.endLine === "number" ? msg.endLine : void 0;
39629
39792
  const lineOffset = typeof msg.lineOffset === "number" ? msg.lineOffset : void 0;
39630
39793
  const lineChunkSize = typeof msg.lineChunkSize === "number" ? msg.lineChunkSize : void 0;
39631
- const result = await readFileAsync(reqPath, startLine, endLine, lineOffset, lineChunkSize);
39794
+ const encoding = msg.encoding === "base64" ? "base64" : "utf8";
39795
+ const result = await readFileAsync(
39796
+ reqPath,
39797
+ startLine,
39798
+ endLine,
39799
+ lineOffset,
39800
+ lineChunkSize,
39801
+ sessionParentPath,
39802
+ encoding
39803
+ );
39632
39804
  if ("error" in result) {
39633
39805
  sendWsMessage(socket, { type: "file_browser_response", id: msg.id, error: result.error });
39634
39806
  } else {
@@ -39640,6 +39812,8 @@ function handleFileBrowserRequest(msg, socket, e2ee) {
39640
39812
  size: result.size
39641
39813
  };
39642
39814
  if (result.lineOffset != null) payload.lineOffset = result.lineOffset;
39815
+ if (result.mimeType != null) payload.mimeType = result.mimeType;
39816
+ if (result.resolvedPath != null) payload.resolvedPath = result.resolvedPath;
39643
39817
  sendFileBrowserMessage(socket, e2ee, payload);
39644
39818
  }
39645
39819
  }
@@ -39647,21 +39821,27 @@ function handleFileBrowserRequest(msg, socket, e2ee) {
39647
39821
  }
39648
39822
 
39649
39823
  // src/routing/handlers/file-browser-messages.ts
39650
- function handleFileBrowserRequestMessage(msg, { getWs, e2ee }) {
39824
+ function handleFileBrowserRequestMessage(msg, { getWs, e2ee, sessionWorktreeManager }) {
39651
39825
  if (typeof msg.id !== "string" || typeof msg.path !== "string") return;
39652
39826
  const socket = getWs();
39653
39827
  if (!socket) return;
39654
39828
  handleFileBrowserRequest(
39655
39829
  msg,
39656
39830
  socket,
39657
- e2ee
39831
+ e2ee,
39832
+ sessionWorktreeManager
39658
39833
  );
39659
39834
  }
39660
- function handleFileBrowserSearchMessage(msg, { getWs, e2ee }) {
39835
+ function handleFileBrowserSearchMessage(msg, { getWs, e2ee, sessionWorktreeManager }) {
39661
39836
  if (typeof msg.id !== "string") return;
39662
39837
  const socket = getWs();
39663
39838
  if (!socket) return;
39664
- handleFileBrowserSearch(msg, socket, e2ee);
39839
+ handleFileBrowserSearch(
39840
+ msg,
39841
+ socket,
39842
+ e2ee,
39843
+ sessionWorktreeManager
39844
+ );
39665
39845
  }
39666
39846
 
39667
39847
  // src/routing/handlers/skill-layout-request.ts
@@ -39675,7 +39855,7 @@ function handleSkillLayoutRequest(msg, deps) {
39675
39855
  }
39676
39856
 
39677
39857
  // src/skills/install-remote-skills.ts
39678
- import fs35 from "node:fs";
39858
+ import fs38 from "node:fs";
39679
39859
  import path38 from "node:path";
39680
39860
  function installRemoteSkills(cwd, targetDir, items) {
39681
39861
  const installed2 = [];
@@ -39691,11 +39871,11 @@ function installRemoteSkills(cwd, targetDir, items) {
39691
39871
  for (const f of item.files) {
39692
39872
  if (typeof f.path !== "string" || !f.text && !f.base64) continue;
39693
39873
  const dest = path38.join(skillDir, f.path);
39694
- fs35.mkdirSync(path38.dirname(dest), { recursive: true });
39874
+ fs38.mkdirSync(path38.dirname(dest), { recursive: true });
39695
39875
  if (f.text !== void 0) {
39696
- fs35.writeFileSync(dest, f.text, "utf8");
39876
+ fs38.writeFileSync(dest, f.text, "utf8");
39697
39877
  } else if (f.base64) {
39698
- fs35.writeFileSync(dest, Buffer.from(f.base64, "base64"));
39878
+ fs38.writeFileSync(dest, Buffer.from(f.base64, "base64"));
39699
39879
  }
39700
39880
  }
39701
39881
  installed2.push({
@@ -39853,7 +40033,7 @@ var handleSessionDiscardedMessage = (msg, deps) => {
39853
40033
  };
39854
40034
 
39855
40035
  // src/routing/handlers/revert-turn-snapshot.ts
39856
- import * as fs36 from "node:fs";
40036
+ import * as fs39 from "node:fs";
39857
40037
  var handleRevertTurnSnapshotMessage = (msg, deps) => {
39858
40038
  const id = typeof msg.id === "string" ? msg.id : "";
39859
40039
  const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
@@ -39866,7 +40046,7 @@ var handleRevertTurnSnapshotMessage = (msg, deps) => {
39866
40046
  const agentBase = sessionWorktreeManager.getSessionWorktreeRootForSession(sessionId) ?? getBridgeRoot();
39867
40047
  const file2 = snapshotFilePath(agentBase, turnId);
39868
40048
  try {
39869
- await fs36.promises.access(file2, fs36.constants.F_OK);
40049
+ await fs39.promises.access(file2, fs39.constants.F_OK);
39870
40050
  } catch {
39871
40051
  sendWsMessage(s, {
39872
40052
  type: "revert_turn_snapshot_result",
@@ -40882,7 +41062,7 @@ async function runCliAction(program2, opts) {
40882
41062
  if (bridgeRootOpt) {
40883
41063
  const resolvedBridgeRoot = path42.resolve(process.cwd(), bridgeRootOpt);
40884
41064
  try {
40885
- const st = fs37.statSync(resolvedBridgeRoot);
41065
+ const st = fs40.statSync(resolvedBridgeRoot);
40886
41066
  if (!st.isDirectory()) {
40887
41067
  console.error(`Bridge root is not a directory: ${resolvedBridgeRoot}`);
40888
41068
  process.exit(1);