@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/index.js CHANGED
@@ -8000,8 +8000,8 @@ function getElementAtPath(obj, path41) {
8000
8000
  }
8001
8001
  function promiseAllObject(promisesObj) {
8002
8002
  const keys = Object.keys(promisesObj);
8003
- const promises3 = keys.map((key) => promisesObj[key]);
8004
- return Promise.all(promises3).then((results) => {
8003
+ const promises5 = keys.map((key) => promisesObj[key]);
8004
+ return Promise.all(promises5).then((results) => {
8005
8005
  const resolvedObj = {};
8006
8006
  for (let i = 0; i < keys.length; i++) {
8007
8007
  resolvedObj[keys[i]] = results[i];
@@ -22158,20 +22158,22 @@ function createWsBridge(options) {
22158
22158
  onOpen?.();
22159
22159
  });
22160
22160
  ws.on("message", (raw) => {
22161
- try {
22162
- let data;
22163
- if (typeof raw === "string") {
22164
- data = JSON.parse(raw);
22165
- } else if (Buffer.isBuffer(raw) || raw instanceof ArrayBuffer) {
22166
- const str = Buffer.isBuffer(raw) ? raw.toString("utf8") : Buffer.from(raw).toString("utf8");
22167
- data = JSON.parse(str);
22168
- } else {
22169
- data = raw;
22161
+ setImmediate(() => {
22162
+ try {
22163
+ let data;
22164
+ if (typeof raw === "string") {
22165
+ data = JSON.parse(raw);
22166
+ } else if (Buffer.isBuffer(raw) || raw instanceof ArrayBuffer) {
22167
+ const str = Buffer.isBuffer(raw) ? raw.toString("utf8") : Buffer.from(raw).toString("utf8");
22168
+ data = JSON.parse(str);
22169
+ } else {
22170
+ data = raw;
22171
+ }
22172
+ onMessage?.(data);
22173
+ } catch {
22174
+ onMessage?.(raw);
22170
22175
  }
22171
- onMessage?.(data);
22172
- } catch {
22173
- onMessage?.(raw);
22174
- }
22176
+ });
22175
22177
  });
22176
22178
  ws.on("close", (code, reason) => {
22177
22179
  disposeClientPing();
@@ -23962,7 +23964,7 @@ function installBridgeProcessResilience() {
23962
23964
  }
23963
23965
 
23964
23966
  // src/cli-version.ts
23965
- var CLI_VERSION = "0.1.34".length > 0 ? "0.1.34" : "0.0.0-dev";
23967
+ var CLI_VERSION = "0.1.36".length > 0 ? "0.1.36" : "0.0.0-dev";
23966
23968
 
23967
23969
  // src/connection/heartbeat/constants.ts
23968
23970
  var BRIDGE_APP_HEARTBEAT_INTERVAL_MS = 1e4;
@@ -25285,6 +25287,7 @@ function recordMigrationAndPruneCheckpointLegacy(db, migration, applied2) {
25285
25287
  var CHECKPOINT_V1 = "001_cli_sqlite_checkpoint_v1";
25286
25288
  var CHECKPOINT_V1_SQL = readCliSqliteMigrationSql("001_cli_sqlite_checkpoint_v1.sql");
25287
25289
  var AGENT_CAPABILITIES_SQL = readCliSqliteMigrationSql("002_agent_capabilities.sql");
25290
+ var FILE_INDEX_PARENT_PATHS_SQL = readCliSqliteMigrationSql("003_file_index_parent_paths.sql");
25288
25291
  function agentCapabilitiesTableState(db) {
25289
25292
  const rows = db.all(
25290
25293
  `SELECT name FROM sqlite_master WHERE type='table' AND name IN ('agent_capabilities', 'agent_capability_cache')`
@@ -25318,6 +25321,18 @@ var CLI_SQLITE_MIGRATIONS = [
25318
25321
  db.exec(AGENT_CAPABILITIES_SQL);
25319
25322
  },
25320
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
+ }
25321
25336
  }
25322
25337
  ];
25323
25338
  function migrateCliSqlite(db) {
@@ -25356,6 +25371,10 @@ var CliSqliteInterrupted = class extends Error {
25356
25371
  }
25357
25372
  };
25358
25373
  function applyCliSqliteConcurrencyPragmas(db) {
25374
+ try {
25375
+ db.run("PRAGMA foreign_keys = ON");
25376
+ } catch {
25377
+ }
25359
25378
  try {
25360
25379
  db.exec("PRAGMA journal_mode = WAL");
25361
25380
  } catch {
@@ -32186,7 +32205,7 @@ async function ensureAcpClient(options) {
32186
32205
  if (!state.acpStartPromise) {
32187
32206
  let statOk = false;
32188
32207
  try {
32189
- const st = fs15.statSync(targetSessionParentPath);
32208
+ const st = await fs15.promises.stat(targetSessionParentPath);
32190
32209
  statOk = st.isDirectory();
32191
32210
  if (!statOk) {
32192
32211
  state.lastAcpStartError = `Agent cwd is not a directory: ${targetSessionParentPath}`;
@@ -33781,8 +33800,18 @@ import path28 from "node:path";
33781
33800
  // src/files/index/walk-workspace-tree.ts
33782
33801
  import fs24 from "node:fs";
33783
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
+ ]);
33784
33813
  function shouldSkipWorkspaceWalkEntry(name) {
33785
- return name.startsWith(".");
33814
+ return DEPENDENCY_INSTALL_DIR_NAMES.has(name);
33786
33815
  }
33787
33816
  async function walkWorkspaceTreeAsync(dir, baseDir, onFile, state) {
33788
33817
  let names;
@@ -33853,14 +33882,26 @@ var FILE_INDEX_INTERRUPT_CHECK_EVERY = 256;
33853
33882
  function assertNotShutdown() {
33854
33883
  if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
33855
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
+ }
33856
33897
  function persistFileIndexPaths(resolved, paths) {
33857
33898
  return withCliSqliteSync((db) => {
33858
- const h = getCwdHashForFileIndex(resolved);
33859
33899
  let pathCount = 0;
33860
33900
  db.run("BEGIN IMMEDIATE");
33861
33901
  try {
33862
- db.run("DELETE FROM file_index_path WHERE cwd_hash = ?", [h]);
33863
- 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 (?, ?)");
33864
33905
  try {
33865
33906
  let batch = 0;
33866
33907
  for (const rel of paths) {
@@ -33868,7 +33909,7 @@ function persistFileIndexPaths(resolved, paths) {
33868
33909
  batch = 0;
33869
33910
  assertNotShutdown();
33870
33911
  }
33871
- ins.run([h, rel]);
33912
+ ins.run([parentId, rel]);
33872
33913
  pathCount += 1;
33873
33914
  }
33874
33915
  } finally {
@@ -33910,36 +33951,38 @@ async function buildFileIndexAsync(cwd) {
33910
33951
  // src/files/index/ensure-file-index.ts
33911
33952
  import path29 from "node:path";
33912
33953
 
33913
- // src/files/index/file-index-dependency-path.ts
33914
- function sqliteExprBridgeFileIndexDependencyRank() {
33915
- 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`;
33916
- }
33917
-
33918
33954
  // src/files/index/search-file-index.ts
33919
33955
  function escapeLikePattern(fragment) {
33920
33956
  return fragment.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
33921
33957
  }
33922
33958
  function bridgeFileIndexIsPopulatedWithDb(resolvedCwd, db) {
33923
- const h = getCwdHashForFileIndex(resolvedCwd);
33924
- 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]);
33925
33960
  return row != null;
33926
33961
  }
33927
33962
  function bridgeFileIndexPathCountWithDb(resolvedCwd, db) {
33928
- const h = getCwdHashForFileIndex(resolvedCwd);
33929
- 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
+ );
33930
33970
  const c = row?.c ?? 0;
33931
33971
  return Number(c);
33932
33972
  }
33933
33973
  function searchBridgeFilePathsWithDb(resolvedCwd, query, limit, db) {
33934
33974
  const q = query.trim().toLowerCase();
33935
33975
  if (!q) return [];
33936
- const h = getCwdHashForFileIndex(resolvedCwd);
33937
33976
  const pattern = `%${escapeLikePattern(q)}%`;
33938
33977
  const lim = Math.max(0, Math.min(1e4, Math.floor(limit)));
33939
- const depRank = sqliteExprBridgeFileIndexDependencyRank();
33940
33978
  const rows = db.all(
33941
- `SELECT path FROM file_index_path WHERE cwd_hash = ? AND lower(path) LIKE ? ESCAPE '\\' ORDER BY ${depRank}, path LIMIT ?`,
33942
- [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]
33943
33986
  );
33944
33987
  return rows.map((r) => String(r.path));
33945
33988
  }
@@ -33969,9 +34012,7 @@ async function ensureFileIndexAsync(cwd) {
33969
34012
  var DEBOUNCE_MS = 900;
33970
34013
  function shouldIgnoreRelative(rel) {
33971
34014
  const n = rel.replace(/\\/g, "/");
33972
- if (n.includes("/.git/") || n === ".git" || n.startsWith(".git/")) return true;
33973
- if (n.includes("/.buildautomaton/") || n.startsWith(".buildautomaton/")) return true;
33974
- return false;
34015
+ return n.split("/").some((segment) => shouldSkipWorkspaceWalkEntry(segment));
33975
34016
  }
33976
34017
  function attachWatchErrorLog(w) {
33977
34018
  w.on("error", (err) => {
@@ -35040,11 +35081,13 @@ function connectFirehose(options) {
35040
35081
  if (Buffer.isBuffer(raw) && tryConsumeBinaryProxyBody(raw, deps)) {
35041
35082
  return;
35042
35083
  }
35043
- try {
35044
- const text = Buffer.isBuffer(raw) ? raw.toString("utf8") : String(raw);
35045
- dispatchFirehoseJsonMessage(JSON.parse(text), deps);
35046
- } catch {
35047
- }
35084
+ setImmediate(() => {
35085
+ try {
35086
+ const text = Buffer.isBuffer(raw) ? raw.toString("utf8") : String(raw);
35087
+ dispatchFirehoseJsonMessage(JSON.parse(text), deps);
35088
+ } catch {
35089
+ }
35090
+ });
35048
35091
  });
35049
35092
  ws.on("close", (code, reason) => {
35050
35093
  disposeClientPing();
@@ -35560,7 +35603,9 @@ async function runLocalRevertBeforeQueuedPrompt(next, deps) {
35560
35603
  const tid = typeof pl.snapshotRevertTurnId === "string" && pl.snapshotRevertTurnId.trim() !== "" ? pl.snapshotRevertTurnId.trim() : next.turnId;
35561
35604
  const agentBase = deps.sessionWorktreeManager.getSessionWorktreeRootForSession(sid) ?? getBridgeRoot();
35562
35605
  const file2 = snapshotFilePath(agentBase, tid);
35563
- if (!fs31.existsSync(file2)) {
35606
+ try {
35607
+ await fs31.promises.access(file2, fs31.constants.F_OK);
35608
+ } catch {
35564
35609
  deps.log(
35565
35610
  `[Queue] requeued_with_revert: no pre-turn snapshot for ${tid.slice(0, 8)}\u2026; continuing without revert.`
35566
35611
  );
@@ -36010,9 +36055,8 @@ var handleSkillCallMessage = (msg, { getWs, log: log2 }) => {
36010
36055
  );
36011
36056
  };
36012
36057
 
36013
- // src/files/list-dir.ts
36014
- import fs32 from "node:fs";
36015
- import path35 from "node:path";
36058
+ // src/files/list-dir/index.ts
36059
+ import fs33 from "node:fs";
36016
36060
 
36017
36061
  // src/files/ensure-under-cwd.ts
36018
36062
  import path34 from "node:path";
@@ -36025,71 +36069,93 @@ function ensureUnderCwd(relativePath, cwd = getBridgeRoot()) {
36025
36069
  return resolved;
36026
36070
  }
36027
36071
 
36028
- // src/files/list-dir.ts
36072
+ // src/files/list-dir/types.ts
36029
36073
  var LIST_DIR_YIELD_EVERY = 256;
36030
- async function listDirAsync(relativePath) {
36031
- 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);
36032
36110
  if (!resolved) {
36033
36111
  return { error: "Path is outside working directory" };
36034
36112
  }
36035
36113
  try {
36036
- const names = await fs32.promises.readdir(resolved, { withFileTypes: true });
36037
- const visible = names.filter((d) => !d.name.startsWith("."));
36114
+ const names = await fs33.promises.readdir(resolved, { withFileTypes: true });
36038
36115
  const entries = [];
36039
- for (let i = 0; i < visible.length; i++) {
36116
+ for (let i = 0; i < names.length; i++) {
36040
36117
  if (i > 0 && i % LIST_DIR_YIELD_EVERY === 0) {
36041
36118
  await yieldToEventLoop();
36042
36119
  }
36043
- const d = visible[i];
36044
- const entryPath = path35.join(relativePath || ".", d.name).replace(/\\/g, "/");
36045
- const fullPath = path35.join(resolved, d.name);
36046
- let isDir = d.isDirectory();
36047
- if (d.isSymbolicLink()) {
36048
- try {
36049
- const targetStat = await fs32.promises.stat(fullPath);
36050
- isDir = targetStat.isDirectory();
36051
- } catch {
36052
- isDir = false;
36053
- }
36054
- }
36055
- entries.push({
36056
- name: d.name,
36057
- path: entryPath,
36058
- isDir,
36059
- isSymlink: d.isSymbolicLink()
36060
- });
36120
+ entries.push(await mapDirEntry(names[i], relativePath, resolved));
36061
36121
  }
36062
- entries.sort((a, b) => {
36063
- if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
36064
- return a.name.localeCompare(b.name, void 0, { sensitivity: "base" });
36065
- });
36066
- return { entries };
36122
+ return { entries: sortListEntries(entries) };
36067
36123
  } catch (err) {
36068
36124
  const message = err instanceof Error ? err.message : String(err);
36069
36125
  return { error: message };
36070
36126
  }
36071
36127
  }
36072
36128
 
36073
- // src/files/read-file.ts
36074
- import fs33 from "node:fs";
36075
- import { StringDecoder } from "node:string_decoder";
36076
- function resolveFilePath(relativePath) {
36077
- 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);
36078
36137
  if (!resolved) return { error: "Path is outside working directory" };
36079
36138
  let real;
36080
36139
  try {
36081
- real = fs33.realpathSync(resolved);
36140
+ real = await fs34.promises.realpath(resolved);
36082
36141
  } catch {
36083
36142
  real = resolved;
36084
36143
  }
36085
- const stat2 = fs33.statSync(real);
36086
- if (!stat2.isFile()) return { error: "Not a file" };
36087
- 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
+ }
36088
36151
  }
36089
- var LINE_CHUNK_SIZE = 64 * 1024;
36090
- function readFileRange(filePath, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
36091
- const fileSize = fs33.statSync(filePath).size;
36092
- 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");
36093
36159
  const bufSize = 64 * 1024;
36094
36160
  const buf = Buffer.alloc(bufSize);
36095
36161
  const decoder = new StringDecoder("utf8");
@@ -36100,9 +36166,18 @@ function readFileRange(filePath, startLine, endLine, lineOffsetIn, lineChunkSize
36100
36166
  let skipLine0Chars = typeof lineOffsetIn === "number" ? lineOffsetIn : 0;
36101
36167
  let line0CharsReturned = 0;
36102
36168
  let line0Accum = "";
36169
+ let bytesSinceYield = 0;
36103
36170
  try {
36104
- let bytesRead;
36105
- 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
+ }
36106
36181
  const text = partial2 + decoder.write(buf.subarray(0, bytesRead));
36107
36182
  partial2 = "";
36108
36183
  let lineStart = 0;
@@ -36237,39 +36312,132 @@ function readFileRange(filePath, startLine, endLine, lineOffsetIn, lineChunkSize
36237
36312
  }
36238
36313
  return { content: resultLines.join("\n"), size: fileSize };
36239
36314
  } finally {
36240
- fs33.closeSync(fd);
36315
+ await fd.close();
36241
36316
  }
36242
36317
  }
36243
- 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;
36244
36328
  try {
36245
- 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);
36246
36401
  if (typeof result === "object") return result;
36402
+ const resolvedPath = result;
36247
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
+ }
36248
36409
  if (hasRange) {
36249
- return readFileRange(result, startLine, endLine, lineOffset, lineChunkSize);
36410
+ return readFileRangeAsync(resolvedPath, startLine, endLine, lineOffset, lineChunkSize);
36250
36411
  }
36251
- const stat2 = fs33.statSync(result);
36252
- const raw = fs33.readFileSync(result, "utf8");
36253
- const lines = raw.split(/\r?\n/);
36254
- return { content: raw, totalLines: lines.length, size: stat2.size };
36412
+ const read = await readFileFullAsync(resolvedPath);
36413
+ return { ...read, resolvedPath };
36255
36414
  } catch (err) {
36256
36415
  return { error: err instanceof Error ? err.message : String(err) };
36257
36416
  }
36258
36417
  }
36259
- async function readFileAsync(relativePath, startLine, endLine, lineOffset, lineChunkSize = LINE_CHUNK_SIZE) {
36260
- await yieldToEventLoop();
36261
- 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();
36262
36428
  }
36263
36429
 
36264
36430
  // src/files/handle-file-browser-search.ts
36265
36431
  import path36 from "node:path";
36266
36432
  var SEARCH_LIMIT = 100;
36267
- function handleFileBrowserSearch(msg, socket, e2ee) {
36433
+ function handleFileBrowserSearch(msg, socket, e2ee, sessionWorktreeManager) {
36268
36434
  void (async () => {
36269
36435
  await yieldToEventLoop();
36270
36436
  const q = typeof msg.q === "string" ? msg.q : "";
36271
- const cwd = path36.resolve(getBridgeRoot());
36272
- if (!await bridgeFileIndexIsPopulated(cwd)) {
36437
+ const sessionParentPath = path36.resolve(
36438
+ sessionWorktreeManager != null ? resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : getBridgeRoot()
36439
+ );
36440
+ if (!await bridgeFileIndexIsPopulated(sessionParentPath)) {
36273
36441
  const payload2 = {
36274
36442
  type: "file_browser_search_response",
36275
36443
  id: msg.id,
@@ -36279,7 +36447,7 @@ function handleFileBrowserSearch(msg, socket, e2ee) {
36279
36447
  sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload2, ["paths"]) : payload2);
36280
36448
  return;
36281
36449
  }
36282
- const results = await searchBridgeFilePathsAsync(cwd, q, SEARCH_LIMIT);
36450
+ const results = await searchBridgeFilePathsAsync(sessionParentPath, q, SEARCH_LIMIT);
36283
36451
  const payload = {
36284
36452
  type: "file_browser_search_response",
36285
36453
  id: msg.id,
@@ -36289,9 +36457,9 @@ function handleFileBrowserSearch(msg, socket, e2ee) {
36289
36457
  sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload, ["paths"]) : payload);
36290
36458
  })();
36291
36459
  }
36292
- function triggerFileIndexBuild() {
36460
+ function triggerFileIndexBuild(sessionParentPath = getBridgeRoot()) {
36293
36461
  setImmediate(() => {
36294
- void ensureFileIndexAsync(getBridgeRoot()).catch((e) => {
36462
+ void ensureFileIndexAsync(sessionParentPath).catch((e) => {
36295
36463
  console.error("[file-index] Background build failed:", e);
36296
36464
  });
36297
36465
  });
@@ -36301,18 +36469,19 @@ function triggerFileIndexBuild() {
36301
36469
  function sendFileBrowserMessage(socket, e2ee, payload) {
36302
36470
  sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload, ["entries", "content", "totalLines", "size", "lineOffset"]) : payload);
36303
36471
  }
36304
- function handleFileBrowserRequest(msg, socket, e2ee) {
36472
+ function handleFileBrowserRequest(msg, socket, e2ee, sessionWorktreeManager) {
36305
36473
  void (async () => {
36306
36474
  const reqPath = msg.path.replace(/^\/+/, "") || ".";
36307
36475
  const op = msg.op === "read" ? "read" : "list";
36476
+ const sessionParentPath = sessionWorktreeManager != null ? resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : void 0;
36308
36477
  if (op === "list") {
36309
- const result = await listDirAsync(reqPath);
36478
+ const result = await listDirAsync(reqPath, sessionParentPath);
36310
36479
  if ("error" in result) {
36311
36480
  sendWsMessage(socket, { type: "file_browser_response", id: msg.id, error: result.error });
36312
36481
  } else {
36313
36482
  sendFileBrowserMessage(socket, e2ee, { type: "file_browser_response", id: msg.id, entries: result.entries });
36314
36483
  if (reqPath === "." || reqPath === "") {
36315
- triggerFileIndexBuild();
36484
+ triggerFileIndexBuild(sessionParentPath);
36316
36485
  }
36317
36486
  }
36318
36487
  } else {
@@ -36320,7 +36489,16 @@ function handleFileBrowserRequest(msg, socket, e2ee) {
36320
36489
  const endLine = typeof msg.endLine === "number" ? msg.endLine : void 0;
36321
36490
  const lineOffset = typeof msg.lineOffset === "number" ? msg.lineOffset : void 0;
36322
36491
  const lineChunkSize = typeof msg.lineChunkSize === "number" ? msg.lineChunkSize : void 0;
36323
- 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
+ );
36324
36502
  if ("error" in result) {
36325
36503
  sendWsMessage(socket, { type: "file_browser_response", id: msg.id, error: result.error });
36326
36504
  } else {
@@ -36332,6 +36510,8 @@ function handleFileBrowserRequest(msg, socket, e2ee) {
36332
36510
  size: result.size
36333
36511
  };
36334
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;
36335
36515
  sendFileBrowserMessage(socket, e2ee, payload);
36336
36516
  }
36337
36517
  }
@@ -36339,21 +36519,27 @@ function handleFileBrowserRequest(msg, socket, e2ee) {
36339
36519
  }
36340
36520
 
36341
36521
  // src/routing/handlers/file-browser-messages.ts
36342
- function handleFileBrowserRequestMessage(msg, { getWs, e2ee }) {
36522
+ function handleFileBrowserRequestMessage(msg, { getWs, e2ee, sessionWorktreeManager }) {
36343
36523
  if (typeof msg.id !== "string" || typeof msg.path !== "string") return;
36344
36524
  const socket = getWs();
36345
36525
  if (!socket) return;
36346
36526
  handleFileBrowserRequest(
36347
36527
  msg,
36348
36528
  socket,
36349
- e2ee
36529
+ e2ee,
36530
+ sessionWorktreeManager
36350
36531
  );
36351
36532
  }
36352
- function handleFileBrowserSearchMessage(msg, { getWs, e2ee }) {
36533
+ function handleFileBrowserSearchMessage(msg, { getWs, e2ee, sessionWorktreeManager }) {
36353
36534
  if (typeof msg.id !== "string") return;
36354
36535
  const socket = getWs();
36355
36536
  if (!socket) return;
36356
- handleFileBrowserSearch(msg, socket, e2ee);
36537
+ handleFileBrowserSearch(
36538
+ msg,
36539
+ socket,
36540
+ e2ee,
36541
+ sessionWorktreeManager
36542
+ );
36357
36543
  }
36358
36544
 
36359
36545
  // src/routing/handlers/skill-layout-request.ts
@@ -36367,7 +36553,7 @@ function handleSkillLayoutRequest(msg, deps) {
36367
36553
  }
36368
36554
 
36369
36555
  // src/skills/install-remote-skills.ts
36370
- import fs34 from "node:fs";
36556
+ import fs37 from "node:fs";
36371
36557
  import path37 from "node:path";
36372
36558
  function installRemoteSkills(cwd, targetDir, items) {
36373
36559
  const installed2 = [];
@@ -36383,11 +36569,11 @@ function installRemoteSkills(cwd, targetDir, items) {
36383
36569
  for (const f of item.files) {
36384
36570
  if (typeof f.path !== "string" || !f.text && !f.base64) continue;
36385
36571
  const dest = path37.join(skillDir, f.path);
36386
- fs34.mkdirSync(path37.dirname(dest), { recursive: true });
36572
+ fs37.mkdirSync(path37.dirname(dest), { recursive: true });
36387
36573
  if (f.text !== void 0) {
36388
- fs34.writeFileSync(dest, f.text, "utf8");
36574
+ fs37.writeFileSync(dest, f.text, "utf8");
36389
36575
  } else if (f.base64) {
36390
- fs34.writeFileSync(dest, Buffer.from(f.base64, "base64"));
36576
+ fs37.writeFileSync(dest, Buffer.from(f.base64, "base64"));
36391
36577
  }
36392
36578
  }
36393
36579
  installed2.push({
@@ -36545,7 +36731,7 @@ var handleSessionDiscardedMessage = (msg, deps) => {
36545
36731
  };
36546
36732
 
36547
36733
  // src/routing/handlers/revert-turn-snapshot.ts
36548
- import * as fs35 from "node:fs";
36734
+ import * as fs38 from "node:fs";
36549
36735
  var handleRevertTurnSnapshotMessage = (msg, deps) => {
36550
36736
  const id = typeof msg.id === "string" ? msg.id : "";
36551
36737
  const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
@@ -36557,7 +36743,9 @@ var handleRevertTurnSnapshotMessage = (msg, deps) => {
36557
36743
  if (!s) return;
36558
36744
  const agentBase = sessionWorktreeManager.getSessionWorktreeRootForSession(sessionId) ?? getBridgeRoot();
36559
36745
  const file2 = snapshotFilePath(agentBase, turnId);
36560
- if (!fs35.existsSync(file2)) {
36746
+ try {
36747
+ await fs38.promises.access(file2, fs38.constants.F_OK);
36748
+ } catch {
36561
36749
  sendWsMessage(s, {
36562
36750
  type: "revert_turn_snapshot_result",
36563
36751
  id,
@@ -36676,9 +36864,7 @@ function handleBridgeMessage(data, deps) {
36676
36864
  if (!deps.getWs()) return;
36677
36865
  const msg = parseApiToBridgeMessage(normalizeInboundBridgeWebSocketJson(data), deps.log);
36678
36866
  if (!msg) return;
36679
- setImmediate(() => {
36680
- dispatchBridgeMessage(msg, deps);
36681
- });
36867
+ dispatchBridgeMessage(msg, deps);
36682
36868
  }
36683
36869
 
36684
36870
  // src/auth/refresh-bridge-tokens.ts
@@ -37323,25 +37509,27 @@ async function createBridgeConnection(options) {
37323
37509
  }
37324
37510
  function sendAgentCapabilitiesToBridge(info) {
37325
37511
  if (!Array.isArray(info.configOptions) || info.configOptions.length === 0) return;
37326
- let changed = false;
37327
- try {
37328
- changed = withCliSqliteSync(
37329
- (db) => upsertCliAgentCapabilityCache(db, {
37330
- workspaceId,
37331
- agentType: info.agentType,
37332
- configOptions: info.configOptions
37333
- })
37334
- );
37335
- } catch (e) {
37336
- if (e instanceof CliSqliteInterrupted) return;
37337
- }
37338
- if (!changed) return;
37339
- const socket = getWs();
37340
- if (!socket || socket.readyState !== wrapper_default.OPEN) return;
37341
- sendWsMessage(socket, {
37342
- type: "agent_capabilities",
37343
- agentType: info.agentType,
37344
- configOptions: info.configOptions
37512
+ setImmediate(() => {
37513
+ let changed = false;
37514
+ try {
37515
+ changed = withCliSqliteSync(
37516
+ (db) => upsertCliAgentCapabilityCache(db, {
37517
+ workspaceId,
37518
+ agentType: info.agentType,
37519
+ configOptions: info.configOptions
37520
+ })
37521
+ );
37522
+ } catch (e) {
37523
+ if (e instanceof CliSqliteInterrupted) return;
37524
+ }
37525
+ if (!changed) return;
37526
+ const socket = getWs();
37527
+ if (!socket || socket.readyState !== wrapper_default.OPEN) return;
37528
+ sendWsMessage(socket, {
37529
+ type: "agent_capabilities",
37530
+ agentType: info.agentType,
37531
+ configOptions: info.configOptions
37532
+ });
37345
37533
  });
37346
37534
  }
37347
37535
  const worktreesRootPath = options.worktreesRootPath ?? defaultWorktreesRootPath();