@buildautomaton/cli 0.1.34 → 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
  }
@@ -11857,8 +11857,8 @@ function getElementAtPath(obj, path43) {
11857
11857
  }
11858
11858
  function promiseAllObject(promisesObj) {
11859
11859
  const keys = Object.keys(promisesObj);
11860
- const promises3 = keys.map((key) => promisesObj[key]);
11861
- return Promise.all(promises3).then((results) => {
11860
+ const promises5 = keys.map((key) => promisesObj[key]);
11861
+ return Promise.all(promises5).then((results) => {
11862
11862
  const resolvedObj = {};
11863
11863
  for (let i = 0; i < keys.length; i++) {
11864
11864
  resolvedObj[keys[i]] = results[i];
@@ -25064,14 +25064,14 @@ var {
25064
25064
  } = import_index.default;
25065
25065
 
25066
25066
  // src/cli-version.ts
25067
- var CLI_VERSION = "0.1.34".length > 0 ? "0.1.34" : "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
@@ -25607,20 +25607,22 @@ function createWsBridge(options) {
25607
25607
  onOpen?.();
25608
25608
  });
25609
25609
  ws.on("message", (raw) => {
25610
- try {
25611
- let data;
25612
- if (typeof raw === "string") {
25613
- data = JSON.parse(raw);
25614
- } else if (Buffer.isBuffer(raw) || raw instanceof ArrayBuffer) {
25615
- const str = Buffer.isBuffer(raw) ? raw.toString("utf8") : Buffer.from(raw).toString("utf8");
25616
- data = JSON.parse(str);
25617
- } else {
25618
- data = raw;
25610
+ setImmediate(() => {
25611
+ try {
25612
+ let data;
25613
+ if (typeof raw === "string") {
25614
+ data = JSON.parse(raw);
25615
+ } else if (Buffer.isBuffer(raw) || raw instanceof ArrayBuffer) {
25616
+ const str = Buffer.isBuffer(raw) ? raw.toString("utf8") : Buffer.from(raw).toString("utf8");
25617
+ data = JSON.parse(str);
25618
+ } else {
25619
+ data = raw;
25620
+ }
25621
+ onMessage?.(data);
25622
+ } catch {
25623
+ onMessage?.(raw);
25619
25624
  }
25620
- onMessage?.(data);
25621
- } catch {
25622
- onMessage?.(raw);
25623
- }
25625
+ });
25624
25626
  });
25625
25627
  ws.on("close", (code, reason) => {
25626
25628
  disposeClientPing();
@@ -26959,6 +26961,7 @@ function recordMigrationAndPruneCheckpointLegacy(db, migration, applied2) {
26959
26961
  var CHECKPOINT_V1 = "001_cli_sqlite_checkpoint_v1";
26960
26962
  var CHECKPOINT_V1_SQL = readCliSqliteMigrationSql("001_cli_sqlite_checkpoint_v1.sql");
26961
26963
  var AGENT_CAPABILITIES_SQL = readCliSqliteMigrationSql("002_agent_capabilities.sql");
26964
+ var FILE_INDEX_PARENT_PATHS_SQL = readCliSqliteMigrationSql("003_file_index_parent_paths.sql");
26962
26965
  function agentCapabilitiesTableState(db) {
26963
26966
  const rows = db.all(
26964
26967
  `SELECT name FROM sqlite_master WHERE type='table' AND name IN ('agent_capabilities', 'agent_capability_cache')`
@@ -26992,6 +26995,18 @@ var CLI_SQLITE_MIGRATIONS = [
26992
26995
  db.exec(AGENT_CAPABILITIES_SQL);
26993
26996
  },
26994
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
+ }
26995
27010
  }
26996
27011
  ];
26997
27012
  function migrateCliSqlite(db) {
@@ -27030,6 +27045,10 @@ var CliSqliteInterrupted = class extends Error {
27030
27045
  }
27031
27046
  };
27032
27047
  function applyCliSqliteConcurrencyPragmas(db) {
27048
+ try {
27049
+ db.run("PRAGMA foreign_keys = ON");
27050
+ } catch {
27051
+ }
27033
27052
  try {
27034
27053
  db.exec("PRAGMA journal_mode = WAL");
27035
27054
  } catch {
@@ -35146,7 +35165,7 @@ async function ensureAcpClient(options) {
35146
35165
  if (!state.acpStartPromise) {
35147
35166
  let statOk = false;
35148
35167
  try {
35149
- const st = fs16.statSync(targetSessionParentPath);
35168
+ const st = await fs16.promises.stat(targetSessionParentPath);
35150
35169
  statOk = st.isDirectory();
35151
35170
  if (!statOk) {
35152
35171
  state.lastAcpStartError = `Agent cwd is not a directory: ${targetSessionParentPath}`;
@@ -36741,8 +36760,18 @@ import path29 from "node:path";
36741
36760
  // src/files/index/walk-workspace-tree.ts
36742
36761
  import fs25 from "node:fs";
36743
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
+ ]);
36744
36773
  function shouldSkipWorkspaceWalkEntry(name) {
36745
- return name.startsWith(".");
36774
+ return DEPENDENCY_INSTALL_DIR_NAMES.has(name);
36746
36775
  }
36747
36776
  async function walkWorkspaceTreeAsync(dir, baseDir, onFile, state) {
36748
36777
  let names;
@@ -36813,14 +36842,26 @@ var FILE_INDEX_INTERRUPT_CHECK_EVERY = 256;
36813
36842
  function assertNotShutdown() {
36814
36843
  if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
36815
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
+ }
36816
36857
  function persistFileIndexPaths(resolved, paths) {
36817
36858
  return withCliSqliteSync((db) => {
36818
- const h = getCwdHashForFileIndex(resolved);
36819
36859
  let pathCount = 0;
36820
36860
  db.run("BEGIN IMMEDIATE");
36821
36861
  try {
36822
- db.run("DELETE FROM file_index_path WHERE cwd_hash = ?", [h]);
36823
- 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 (?, ?)");
36824
36865
  try {
36825
36866
  let batch = 0;
36826
36867
  for (const rel of paths) {
@@ -36828,7 +36869,7 @@ function persistFileIndexPaths(resolved, paths) {
36828
36869
  batch = 0;
36829
36870
  assertNotShutdown();
36830
36871
  }
36831
- ins.run([h, rel]);
36872
+ ins.run([parentId, rel]);
36832
36873
  pathCount += 1;
36833
36874
  }
36834
36875
  } finally {
@@ -36870,36 +36911,38 @@ async function buildFileIndexAsync(cwd) {
36870
36911
  // src/files/index/ensure-file-index.ts
36871
36912
  import path30 from "node:path";
36872
36913
 
36873
- // src/files/index/file-index-dependency-path.ts
36874
- function sqliteExprBridgeFileIndexDependencyRank() {
36875
- 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`;
36876
- }
36877
-
36878
36914
  // src/files/index/search-file-index.ts
36879
36915
  function escapeLikePattern(fragment) {
36880
36916
  return fragment.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
36881
36917
  }
36882
36918
  function bridgeFileIndexIsPopulatedWithDb(resolvedCwd, db) {
36883
- const h = getCwdHashForFileIndex(resolvedCwd);
36884
- 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]);
36885
36920
  return row != null;
36886
36921
  }
36887
36922
  function bridgeFileIndexPathCountWithDb(resolvedCwd, db) {
36888
- const h = getCwdHashForFileIndex(resolvedCwd);
36889
- 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
+ );
36890
36930
  const c = row?.c ?? 0;
36891
36931
  return Number(c);
36892
36932
  }
36893
36933
  function searchBridgeFilePathsWithDb(resolvedCwd, query, limit, db) {
36894
36934
  const q = query.trim().toLowerCase();
36895
36935
  if (!q) return [];
36896
- const h = getCwdHashForFileIndex(resolvedCwd);
36897
36936
  const pattern = `%${escapeLikePattern(q)}%`;
36898
36937
  const lim = Math.max(0, Math.min(1e4, Math.floor(limit)));
36899
- const depRank = sqliteExprBridgeFileIndexDependencyRank();
36900
36938
  const rows = db.all(
36901
- `SELECT path FROM file_index_path WHERE cwd_hash = ? AND lower(path) LIKE ? ESCAPE '\\' ORDER BY ${depRank}, path LIMIT ?`,
36902
- [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]
36903
36946
  );
36904
36947
  return rows.map((r) => String(r.path));
36905
36948
  }
@@ -36929,9 +36972,7 @@ async function ensureFileIndexAsync(cwd) {
36929
36972
  var DEBOUNCE_MS = 900;
36930
36973
  function shouldIgnoreRelative(rel) {
36931
36974
  const n = rel.replace(/\\/g, "/");
36932
- if (n.includes("/.git/") || n === ".git" || n.startsWith(".git/")) return true;
36933
- if (n.includes("/.buildautomaton/") || n.startsWith(".buildautomaton/")) return true;
36934
- return false;
36975
+ return n.split("/").some((segment) => shouldSkipWorkspaceWalkEntry(segment));
36935
36976
  }
36936
36977
  function attachWatchErrorLog(w) {
36937
36978
  w.on("error", (err) => {
@@ -38168,11 +38209,13 @@ function connectFirehose(options) {
38168
38209
  if (Buffer.isBuffer(raw) && tryConsumeBinaryProxyBody(raw, deps)) {
38169
38210
  return;
38170
38211
  }
38171
- try {
38172
- const text = Buffer.isBuffer(raw) ? raw.toString("utf8") : String(raw);
38173
- dispatchFirehoseJsonMessage(JSON.parse(text), deps);
38174
- } catch {
38175
- }
38212
+ setImmediate(() => {
38213
+ try {
38214
+ const text = Buffer.isBuffer(raw) ? raw.toString("utf8") : String(raw);
38215
+ dispatchFirehoseJsonMessage(JSON.parse(text), deps);
38216
+ } catch {
38217
+ }
38218
+ });
38176
38219
  });
38177
38220
  ws.on("close", (code, reason) => {
38178
38221
  disposeClientPing();
@@ -38688,7 +38731,9 @@ async function runLocalRevertBeforeQueuedPrompt(next, deps) {
38688
38731
  const tid = typeof pl.snapshotRevertTurnId === "string" && pl.snapshotRevertTurnId.trim() !== "" ? pl.snapshotRevertTurnId.trim() : next.turnId;
38689
38732
  const agentBase = deps.sessionWorktreeManager.getSessionWorktreeRootForSession(sid) ?? getBridgeRoot();
38690
38733
  const file2 = snapshotFilePath(agentBase, tid);
38691
- if (!fs32.existsSync(file2)) {
38734
+ try {
38735
+ await fs32.promises.access(file2, fs32.constants.F_OK);
38736
+ } catch {
38692
38737
  deps.log(
38693
38738
  `[Queue] requeued_with_revert: no pre-turn snapshot for ${tid.slice(0, 8)}\u2026; continuing without revert.`
38694
38739
  );
@@ -39312,9 +39357,8 @@ var handleSkillCallMessage = (msg, { getWs, log: log2 }) => {
39312
39357
  );
39313
39358
  };
39314
39359
 
39315
- // src/files/list-dir.ts
39316
- import fs33 from "node:fs";
39317
- import path36 from "node:path";
39360
+ // src/files/list-dir/index.ts
39361
+ import fs34 from "node:fs";
39318
39362
 
39319
39363
  // src/files/ensure-under-cwd.ts
39320
39364
  import path35 from "node:path";
@@ -39327,71 +39371,93 @@ function ensureUnderCwd(relativePath, cwd = getBridgeRoot()) {
39327
39371
  return resolved;
39328
39372
  }
39329
39373
 
39330
- // src/files/list-dir.ts
39374
+ // src/files/list-dir/types.ts
39331
39375
  var LIST_DIR_YIELD_EVERY = 256;
39332
- async function listDirAsync(relativePath) {
39333
- 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);
39334
39412
  if (!resolved) {
39335
39413
  return { error: "Path is outside working directory" };
39336
39414
  }
39337
39415
  try {
39338
- const names = await fs33.promises.readdir(resolved, { withFileTypes: true });
39339
- const visible = names.filter((d) => !d.name.startsWith("."));
39416
+ const names = await fs34.promises.readdir(resolved, { withFileTypes: true });
39340
39417
  const entries = [];
39341
- for (let i = 0; i < visible.length; i++) {
39418
+ for (let i = 0; i < names.length; i++) {
39342
39419
  if (i > 0 && i % LIST_DIR_YIELD_EVERY === 0) {
39343
39420
  await yieldToEventLoop();
39344
39421
  }
39345
- const d = visible[i];
39346
- const entryPath = path36.join(relativePath || ".", d.name).replace(/\\/g, "/");
39347
- const fullPath = path36.join(resolved, d.name);
39348
- let isDir = d.isDirectory();
39349
- if (d.isSymbolicLink()) {
39350
- try {
39351
- const targetStat = await fs33.promises.stat(fullPath);
39352
- isDir = targetStat.isDirectory();
39353
- } catch {
39354
- isDir = false;
39355
- }
39356
- }
39357
- entries.push({
39358
- name: d.name,
39359
- path: entryPath,
39360
- isDir,
39361
- isSymlink: d.isSymbolicLink()
39362
- });
39422
+ entries.push(await mapDirEntry(names[i], relativePath, resolved));
39363
39423
  }
39364
- entries.sort((a, b) => {
39365
- if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
39366
- return a.name.localeCompare(b.name, void 0, { sensitivity: "base" });
39367
- });
39368
- return { entries };
39424
+ return { entries: sortListEntries(entries) };
39369
39425
  } catch (err) {
39370
39426
  const message = err instanceof Error ? err.message : String(err);
39371
39427
  return { error: message };
39372
39428
  }
39373
39429
  }
39374
39430
 
39375
- // src/files/read-file.ts
39376
- import fs34 from "node:fs";
39377
- import { StringDecoder } from "node:string_decoder";
39378
- function resolveFilePath(relativePath) {
39379
- 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);
39380
39439
  if (!resolved) return { error: "Path is outside working directory" };
39381
39440
  let real;
39382
39441
  try {
39383
- real = fs34.realpathSync(resolved);
39442
+ real = await fs35.promises.realpath(resolved);
39384
39443
  } catch {
39385
39444
  real = resolved;
39386
39445
  }
39387
- const stat3 = fs34.statSync(real);
39388
- if (!stat3.isFile()) return { error: "Not a file" };
39389
- 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
+ }
39390
39453
  }
39391
- var LINE_CHUNK_SIZE = 64 * 1024;
39392
- function readFileRange(filePath, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
39393
- const fileSize = fs34.statSync(filePath).size;
39394
- 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");
39395
39461
  const bufSize = 64 * 1024;
39396
39462
  const buf = Buffer.alloc(bufSize);
39397
39463
  const decoder = new StringDecoder("utf8");
@@ -39402,9 +39468,18 @@ function readFileRange(filePath, startLine, endLine, lineOffsetIn, lineChunkSize
39402
39468
  let skipLine0Chars = typeof lineOffsetIn === "number" ? lineOffsetIn : 0;
39403
39469
  let line0CharsReturned = 0;
39404
39470
  let line0Accum = "";
39471
+ let bytesSinceYield = 0;
39405
39472
  try {
39406
- let bytesRead;
39407
- 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
+ }
39408
39483
  const text = partial2 + decoder.write(buf.subarray(0, bytesRead));
39409
39484
  partial2 = "";
39410
39485
  let lineStart = 0;
@@ -39539,39 +39614,132 @@ function readFileRange(filePath, startLine, endLine, lineOffsetIn, lineChunkSize
39539
39614
  }
39540
39615
  return { content: resultLines.join("\n"), size: fileSize };
39541
39616
  } finally {
39542
- fs34.closeSync(fd);
39617
+ await fd.close();
39543
39618
  }
39544
39619
  }
39545
- 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;
39546
39630
  try {
39547
- 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);
39548
39703
  if (typeof result === "object") return result;
39704
+ const resolvedPath = result;
39549
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
+ }
39550
39711
  if (hasRange) {
39551
- return readFileRange(result, startLine, endLine, lineOffset, lineChunkSize);
39712
+ return readFileRangeAsync(resolvedPath, startLine, endLine, lineOffset, lineChunkSize);
39552
39713
  }
39553
- const stat3 = fs34.statSync(result);
39554
- const raw = fs34.readFileSync(result, "utf8");
39555
- const lines = raw.split(/\r?\n/);
39556
- return { content: raw, totalLines: lines.length, size: stat3.size };
39714
+ const read = await readFileFullAsync(resolvedPath);
39715
+ return { ...read, resolvedPath };
39557
39716
  } catch (err) {
39558
39717
  return { error: err instanceof Error ? err.message : String(err) };
39559
39718
  }
39560
39719
  }
39561
- async function readFileAsync(relativePath, startLine, endLine, lineOffset, lineChunkSize = LINE_CHUNK_SIZE) {
39562
- await yieldToEventLoop();
39563
- 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();
39564
39730
  }
39565
39731
 
39566
39732
  // src/files/handle-file-browser-search.ts
39567
39733
  import path37 from "node:path";
39568
39734
  var SEARCH_LIMIT = 100;
39569
- function handleFileBrowserSearch(msg, socket, e2ee) {
39735
+ function handleFileBrowserSearch(msg, socket, e2ee, sessionWorktreeManager) {
39570
39736
  void (async () => {
39571
39737
  await yieldToEventLoop();
39572
39738
  const q = typeof msg.q === "string" ? msg.q : "";
39573
- const cwd = path37.resolve(getBridgeRoot());
39574
- if (!await bridgeFileIndexIsPopulated(cwd)) {
39739
+ const sessionParentPath = path37.resolve(
39740
+ sessionWorktreeManager != null ? resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : getBridgeRoot()
39741
+ );
39742
+ if (!await bridgeFileIndexIsPopulated(sessionParentPath)) {
39575
39743
  const payload2 = {
39576
39744
  type: "file_browser_search_response",
39577
39745
  id: msg.id,
@@ -39581,7 +39749,7 @@ function handleFileBrowserSearch(msg, socket, e2ee) {
39581
39749
  sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload2, ["paths"]) : payload2);
39582
39750
  return;
39583
39751
  }
39584
- const results = await searchBridgeFilePathsAsync(cwd, q, SEARCH_LIMIT);
39752
+ const results = await searchBridgeFilePathsAsync(sessionParentPath, q, SEARCH_LIMIT);
39585
39753
  const payload = {
39586
39754
  type: "file_browser_search_response",
39587
39755
  id: msg.id,
@@ -39591,9 +39759,9 @@ function handleFileBrowserSearch(msg, socket, e2ee) {
39591
39759
  sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload, ["paths"]) : payload);
39592
39760
  })();
39593
39761
  }
39594
- function triggerFileIndexBuild() {
39762
+ function triggerFileIndexBuild(sessionParentPath = getBridgeRoot()) {
39595
39763
  setImmediate(() => {
39596
- void ensureFileIndexAsync(getBridgeRoot()).catch((e) => {
39764
+ void ensureFileIndexAsync(sessionParentPath).catch((e) => {
39597
39765
  console.error("[file-index] Background build failed:", e);
39598
39766
  });
39599
39767
  });
@@ -39603,18 +39771,19 @@ function triggerFileIndexBuild() {
39603
39771
  function sendFileBrowserMessage(socket, e2ee, payload) {
39604
39772
  sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload, ["entries", "content", "totalLines", "size", "lineOffset"]) : payload);
39605
39773
  }
39606
- function handleFileBrowserRequest(msg, socket, e2ee) {
39774
+ function handleFileBrowserRequest(msg, socket, e2ee, sessionWorktreeManager) {
39607
39775
  void (async () => {
39608
39776
  const reqPath = msg.path.replace(/^\/+/, "") || ".";
39609
39777
  const op = msg.op === "read" ? "read" : "list";
39778
+ const sessionParentPath = sessionWorktreeManager != null ? resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : void 0;
39610
39779
  if (op === "list") {
39611
- const result = await listDirAsync(reqPath);
39780
+ const result = await listDirAsync(reqPath, sessionParentPath);
39612
39781
  if ("error" in result) {
39613
39782
  sendWsMessage(socket, { type: "file_browser_response", id: msg.id, error: result.error });
39614
39783
  } else {
39615
39784
  sendFileBrowserMessage(socket, e2ee, { type: "file_browser_response", id: msg.id, entries: result.entries });
39616
39785
  if (reqPath === "." || reqPath === "") {
39617
- triggerFileIndexBuild();
39786
+ triggerFileIndexBuild(sessionParentPath);
39618
39787
  }
39619
39788
  }
39620
39789
  } else {
@@ -39622,7 +39791,16 @@ function handleFileBrowserRequest(msg, socket, e2ee) {
39622
39791
  const endLine = typeof msg.endLine === "number" ? msg.endLine : void 0;
39623
39792
  const lineOffset = typeof msg.lineOffset === "number" ? msg.lineOffset : void 0;
39624
39793
  const lineChunkSize = typeof msg.lineChunkSize === "number" ? msg.lineChunkSize : void 0;
39625
- 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
+ );
39626
39804
  if ("error" in result) {
39627
39805
  sendWsMessage(socket, { type: "file_browser_response", id: msg.id, error: result.error });
39628
39806
  } else {
@@ -39634,6 +39812,8 @@ function handleFileBrowserRequest(msg, socket, e2ee) {
39634
39812
  size: result.size
39635
39813
  };
39636
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;
39637
39817
  sendFileBrowserMessage(socket, e2ee, payload);
39638
39818
  }
39639
39819
  }
@@ -39641,21 +39821,27 @@ function handleFileBrowserRequest(msg, socket, e2ee) {
39641
39821
  }
39642
39822
 
39643
39823
  // src/routing/handlers/file-browser-messages.ts
39644
- function handleFileBrowserRequestMessage(msg, { getWs, e2ee }) {
39824
+ function handleFileBrowserRequestMessage(msg, { getWs, e2ee, sessionWorktreeManager }) {
39645
39825
  if (typeof msg.id !== "string" || typeof msg.path !== "string") return;
39646
39826
  const socket = getWs();
39647
39827
  if (!socket) return;
39648
39828
  handleFileBrowserRequest(
39649
39829
  msg,
39650
39830
  socket,
39651
- e2ee
39831
+ e2ee,
39832
+ sessionWorktreeManager
39652
39833
  );
39653
39834
  }
39654
- function handleFileBrowserSearchMessage(msg, { getWs, e2ee }) {
39835
+ function handleFileBrowserSearchMessage(msg, { getWs, e2ee, sessionWorktreeManager }) {
39655
39836
  if (typeof msg.id !== "string") return;
39656
39837
  const socket = getWs();
39657
39838
  if (!socket) return;
39658
- handleFileBrowserSearch(msg, socket, e2ee);
39839
+ handleFileBrowserSearch(
39840
+ msg,
39841
+ socket,
39842
+ e2ee,
39843
+ sessionWorktreeManager
39844
+ );
39659
39845
  }
39660
39846
 
39661
39847
  // src/routing/handlers/skill-layout-request.ts
@@ -39669,7 +39855,7 @@ function handleSkillLayoutRequest(msg, deps) {
39669
39855
  }
39670
39856
 
39671
39857
  // src/skills/install-remote-skills.ts
39672
- import fs35 from "node:fs";
39858
+ import fs38 from "node:fs";
39673
39859
  import path38 from "node:path";
39674
39860
  function installRemoteSkills(cwd, targetDir, items) {
39675
39861
  const installed2 = [];
@@ -39685,11 +39871,11 @@ function installRemoteSkills(cwd, targetDir, items) {
39685
39871
  for (const f of item.files) {
39686
39872
  if (typeof f.path !== "string" || !f.text && !f.base64) continue;
39687
39873
  const dest = path38.join(skillDir, f.path);
39688
- fs35.mkdirSync(path38.dirname(dest), { recursive: true });
39874
+ fs38.mkdirSync(path38.dirname(dest), { recursive: true });
39689
39875
  if (f.text !== void 0) {
39690
- fs35.writeFileSync(dest, f.text, "utf8");
39876
+ fs38.writeFileSync(dest, f.text, "utf8");
39691
39877
  } else if (f.base64) {
39692
- fs35.writeFileSync(dest, Buffer.from(f.base64, "base64"));
39878
+ fs38.writeFileSync(dest, Buffer.from(f.base64, "base64"));
39693
39879
  }
39694
39880
  }
39695
39881
  installed2.push({
@@ -39847,7 +40033,7 @@ var handleSessionDiscardedMessage = (msg, deps) => {
39847
40033
  };
39848
40034
 
39849
40035
  // src/routing/handlers/revert-turn-snapshot.ts
39850
- import * as fs36 from "node:fs";
40036
+ import * as fs39 from "node:fs";
39851
40037
  var handleRevertTurnSnapshotMessage = (msg, deps) => {
39852
40038
  const id = typeof msg.id === "string" ? msg.id : "";
39853
40039
  const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
@@ -39859,7 +40045,9 @@ var handleRevertTurnSnapshotMessage = (msg, deps) => {
39859
40045
  if (!s) return;
39860
40046
  const agentBase = sessionWorktreeManager.getSessionWorktreeRootForSession(sessionId) ?? getBridgeRoot();
39861
40047
  const file2 = snapshotFilePath(agentBase, turnId);
39862
- if (!fs36.existsSync(file2)) {
40048
+ try {
40049
+ await fs39.promises.access(file2, fs39.constants.F_OK);
40050
+ } catch {
39863
40051
  sendWsMessage(s, {
39864
40052
  type: "revert_turn_snapshot_result",
39865
40053
  id,
@@ -39978,9 +40166,7 @@ function handleBridgeMessage(data, deps) {
39978
40166
  if (!deps.getWs()) return;
39979
40167
  const msg = parseApiToBridgeMessage(normalizeInboundBridgeWebSocketJson(data), deps.log);
39980
40168
  if (!msg) return;
39981
- setImmediate(() => {
39982
- dispatchBridgeMessage(msg, deps);
39983
- });
40169
+ dispatchBridgeMessage(msg, deps);
39984
40170
  }
39985
40171
 
39986
40172
  // src/auth/refresh-bridge-tokens.ts
@@ -40546,25 +40732,27 @@ async function createBridgeConnection(options) {
40546
40732
  }
40547
40733
  function sendAgentCapabilitiesToBridge(info) {
40548
40734
  if (!Array.isArray(info.configOptions) || info.configOptions.length === 0) return;
40549
- let changed = false;
40550
- try {
40551
- changed = withCliSqliteSync(
40552
- (db) => upsertCliAgentCapabilityCache(db, {
40553
- workspaceId,
40554
- agentType: info.agentType,
40555
- configOptions: info.configOptions
40556
- })
40557
- );
40558
- } catch (e) {
40559
- if (e instanceof CliSqliteInterrupted) return;
40560
- }
40561
- if (!changed) return;
40562
- const socket = getWs();
40563
- if (!socket || socket.readyState !== wrapper_default.OPEN) return;
40564
- sendWsMessage(socket, {
40565
- type: "agent_capabilities",
40566
- agentType: info.agentType,
40567
- configOptions: info.configOptions
40735
+ setImmediate(() => {
40736
+ let changed = false;
40737
+ try {
40738
+ changed = withCliSqliteSync(
40739
+ (db) => upsertCliAgentCapabilityCache(db, {
40740
+ workspaceId,
40741
+ agentType: info.agentType,
40742
+ configOptions: info.configOptions
40743
+ })
40744
+ );
40745
+ } catch (e) {
40746
+ if (e instanceof CliSqliteInterrupted) return;
40747
+ }
40748
+ if (!changed) return;
40749
+ const socket = getWs();
40750
+ if (!socket || socket.readyState !== wrapper_default.OPEN) return;
40751
+ sendWsMessage(socket, {
40752
+ type: "agent_capabilities",
40753
+ agentType: info.agentType,
40754
+ configOptions: info.configOptions
40755
+ });
40568
40756
  });
40569
40757
  }
40570
40758
  const worktreesRootPath = options.worktreesRootPath ?? defaultWorktreesRootPath();
@@ -40874,7 +41062,7 @@ async function runCliAction(program2, opts) {
40874
41062
  if (bridgeRootOpt) {
40875
41063
  const resolvedBridgeRoot = path42.resolve(process.cwd(), bridgeRootOpt);
40876
41064
  try {
40877
- const st = fs37.statSync(resolvedBridgeRoot);
41065
+ const st = fs40.statSync(resolvedBridgeRoot);
40878
41066
  if (!st.isDirectory()) {
40879
41067
  console.error(`Bridge root is not a directory: ${resolvedBridgeRoot}`);
40880
41068
  process.exit(1);