@buildautomaton/cli 0.1.35 → 0.1.37

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
@@ -22935,6 +22935,16 @@ function isClaudeCodePermissionMode(value) {
22935
22935
  return MODE_SET.has(value);
22936
22936
  }
22937
22937
 
22938
+ // ../types/src/codex-permission-mode.ts
22939
+ function isCodexPermissionMode(value) {
22940
+ return value.trim() !== "";
22941
+ }
22942
+ function normalizeCodexPermissionModeInput(raw) {
22943
+ if (typeof raw !== "string") return null;
22944
+ const t = raw.trim();
22945
+ return isCodexPermissionMode(t) ? t : null;
22946
+ }
22947
+
22938
22948
  // ../types/src/cli-permission-mode.ts
22939
22949
  var CLI_PERMISSION_MODE_DEFAULT = "default";
22940
22950
  var CLI_PERMISSION_MODE_DANGEROUS = "dangerous";
@@ -23006,6 +23016,7 @@ function buildCliAutoApprovedPermissionRpcResult(requestParams) {
23006
23016
  // ../types/src/agent-config.ts
23007
23017
  var AGENT_CONFIG_CLAUDE_PERMISSION_MODE_KEY = "claude_permission_mode";
23008
23018
  var AGENT_CONFIG_CLI_PERMISSION_MODE_KEY = "cli_permission_mode";
23019
+ var AGENT_CONFIG_CODEX_PERMISSION_MODE_KEY = "codex_permission_mode";
23009
23020
  var AGENT_CONFIG_AGENT_MODEL_KEY = "agent_model";
23010
23021
  function getClaudePermissionModeFromAgentConfig(config2) {
23011
23022
  if (!config2) return null;
@@ -23018,6 +23029,10 @@ function getCliPermissionModeFromAgentConfig(config2) {
23018
23029
  if (!config2) return CLI_PERMISSION_MODE_DEFAULT;
23019
23030
  return normalizeCliPermissionModeInput(config2[AGENT_CONFIG_CLI_PERMISSION_MODE_KEY]);
23020
23031
  }
23032
+ function getCodexPermissionModeFromAgentConfig(config2) {
23033
+ if (!config2) return null;
23034
+ return normalizeCodexPermissionModeInput(config2[AGENT_CONFIG_CODEX_PERMISSION_MODE_KEY]);
23035
+ }
23021
23036
  function getAgentModelFromAgentConfig(config2) {
23022
23037
  if (!config2) return null;
23023
23038
  const cur = config2[AGENT_CONFIG_AGENT_MODEL_KEY];
@@ -23080,8 +23095,60 @@ async function applyClaudePermissionFromAcpSession(params) {
23080
23095
  }
23081
23096
  }
23082
23097
 
23083
- // src/agents/acp/apply-acp-model-from-agent-session.ts
23098
+ // src/agents/acp/codex-acp-permission-from-session.ts
23084
23099
  function flattenSelectOptions2(options) {
23100
+ if (options == null || options.length === 0) return [];
23101
+ const first2 = options[0];
23102
+ if (first2 != null && typeof first2 === "object" && "group" in first2 && first2.group != null) {
23103
+ return options.flatMap(
23104
+ (g) => Array.isArray(g.options) ? g.options : []
23105
+ );
23106
+ }
23107
+ return options;
23108
+ }
23109
+ function pickModeConfigOption2(configOptions) {
23110
+ if (configOptions == null || configOptions.length === 0) return null;
23111
+ const byCategory = configOptions.find((o) => o.category === "mode");
23112
+ if (byCategory) return byCategory;
23113
+ return configOptions.find((o) => o.id === "mode") ?? null;
23114
+ }
23115
+ async function applyCodexPermissionFromAcpSession(params) {
23116
+ const { sessionId, agentConfig, configOptions, modes, setSessionConfigOption, setSessionMode, logDebug: logDebug2 } = params;
23117
+ const desiredMode = getCodexPermissionModeFromAgentConfig(agentConfig);
23118
+ if (desiredMode == null) return;
23119
+ const modeOpt = pickModeConfigOption2(configOptions ?? null);
23120
+ if (modeOpt != null) {
23121
+ const flat = flattenSelectOptions2(modeOpt.options);
23122
+ const allowed = flat.some((o) => o.value === desiredMode);
23123
+ if (allowed && modeOpt.currentValue !== desiredMode) {
23124
+ try {
23125
+ logDebug2(
23126
+ `[Agent] Codex: sending ACP session/set_config_option (mode) configId=${JSON.stringify(modeOpt.id)} value=${JSON.stringify(desiredMode)} was=${JSON.stringify(modeOpt.currentValue)} sessionId=${sessionId.slice(0, 8)}\u2026`
23127
+ );
23128
+ await setSessionConfigOption({ sessionId, configId: modeOpt.id, value: desiredMode });
23129
+ } catch (e) {
23130
+ logDebug2(`[Agent] Codex: session/set_config_option failed: ${e instanceof Error ? e.message : String(e)}`);
23131
+ }
23132
+ }
23133
+ return;
23134
+ }
23135
+ if (modes?.availableModes?.length) {
23136
+ const allowed = modes.availableModes.some((m) => m.id === desiredMode);
23137
+ if (allowed && desiredMode !== modes.currentModeId) {
23138
+ try {
23139
+ logDebug2(
23140
+ `[Agent] Codex: sending ACP session/set_mode modeId=${JSON.stringify(desiredMode)} was=${JSON.stringify(modes.currentModeId ?? null)} sessionId=${sessionId.slice(0, 8)}\u2026`
23141
+ );
23142
+ await setSessionMode({ sessionId, modeId: desiredMode });
23143
+ } catch (e) {
23144
+ logDebug2(`[Agent] Codex: session/set_mode failed: ${e instanceof Error ? e.message : String(e)}`);
23145
+ }
23146
+ }
23147
+ }
23148
+ }
23149
+
23150
+ // src/agents/acp/apply-acp-model-from-agent-session.ts
23151
+ function flattenSelectOptions3(options) {
23085
23152
  if (options == null || options.length === 0) return [];
23086
23153
  const first2 = options[0];
23087
23154
  if (first2 != null && typeof first2 === "object" && "group" in first2 && first2.group != null) {
@@ -23106,7 +23173,7 @@ async function applyAcpModelFromAcpSession(params) {
23106
23173
  if (desired == null) return;
23107
23174
  const modelOpt = pickModelConfigOption(configOptions ?? null);
23108
23175
  if (modelOpt == null) return;
23109
- const flat = flattenSelectOptions2(modelOpt.options);
23176
+ const flat = flattenSelectOptions3(modelOpt.options);
23110
23177
  const allowed = flat.some((o) => o.value === desired);
23111
23178
  if (!allowed) return;
23112
23179
  if (modelOpt.currentValue === desired) return;
@@ -23186,12 +23253,41 @@ function parseAcpInitAgentCapabilities(initResult) {
23186
23253
  }
23187
23254
 
23188
23255
  // src/agents/acp/clients/shared/bootstrap-acp-wire-session.ts
23256
+ function configOptionsWithModes(configOptions, modes) {
23257
+ const modeState = modes && typeof modes === "object" ? modes : null;
23258
+ if (!modeState?.availableModes?.length) return configOptions;
23259
+ const hasModeConfig = Array.isArray(configOptions) && configOptions.some((raw) => {
23260
+ if (raw == null || typeof raw !== "object" || Array.isArray(raw)) return false;
23261
+ const o = raw;
23262
+ return o.category === "mode" || o.id === "mode";
23263
+ });
23264
+ if (hasModeConfig) return configOptions;
23265
+ return [
23266
+ ...configOptions ?? [],
23267
+ {
23268
+ id: "mode",
23269
+ name: "Mode",
23270
+ type: "select",
23271
+ category: "mode",
23272
+ currentValue: modeState.currentModeId ?? null,
23273
+ options: modeState.availableModes.map((m) => {
23274
+ const r = m;
23275
+ return {
23276
+ value: m.id,
23277
+ name: m.name ?? m.id,
23278
+ ...typeof r.description === "string" && r.description.trim() !== "" ? { description: r.description.trim() } : {}
23279
+ };
23280
+ })
23281
+ }
23282
+ ];
23283
+ }
23189
23284
  async function bootstrapAcpWireSession(transport, ctx, initializeRequest) {
23190
23285
  const initResult = await transport.initialize(initializeRequest);
23191
23286
  const { canResume, canLoad, promptSupportsImage } = parseAcpInitAgentCapabilities(initResult);
23192
23287
  ctx.agentPromptImageSupported = promptSupportsImage;
23193
23288
  await transport.afterInitialize?.();
23194
23289
  const established = await establishAcpSessionWithTransport(transport, ctx, canResume, canLoad);
23290
+ established.configOptions = configOptionsWithModes(established.configOptions, established.modes);
23195
23291
  const sessionId = established.sessionId;
23196
23292
  ctx.onAcpSessionEstablished?.({
23197
23293
  acpSessionId: sessionId,
@@ -23214,6 +23310,22 @@ async function bootstrapAcpWireSession(transport, ctx, initializeRequest) {
23214
23310
  logDebug: ctx.logDebug
23215
23311
  });
23216
23312
  }
23313
+ if (ctx.backendAgentType === "codex-acp") {
23314
+ const cfg = ctx.agentConfig != null && typeof ctx.agentConfig === "object" && !Array.isArray(ctx.agentConfig) ? ctx.agentConfig : null;
23315
+ const configOptionsTyped = established.configOptions;
23316
+ const modesTyped = established.modes;
23317
+ await applyCodexPermissionFromAcpSession({
23318
+ sessionId,
23319
+ agentConfig: cfg,
23320
+ configOptions: configOptionsForPermission(ctx.getActiveConfigOptions, configOptionsTyped),
23321
+ modes: modesTyped,
23322
+ setSessionConfigOption: transport.setSessionConfigOption ? (p) => transport.setSessionConfigOption(p) : async () => {
23323
+ },
23324
+ setSessionMode: transport.setSessionMode ? (p) => transport.setSessionMode(p) : async () => {
23325
+ },
23326
+ logDebug: ctx.logDebug
23327
+ });
23328
+ }
23217
23329
  const cfgAll = ctx.agentConfig != null && typeof ctx.agentConfig === "object" && !Array.isArray(ctx.agentConfig) ? ctx.agentConfig : null;
23218
23330
  const configOptionsForModel = established.configOptions;
23219
23331
  if (transport.setSessionConfigOption) {
@@ -23964,7 +24076,7 @@ function installBridgeProcessResilience() {
23964
24076
  }
23965
24077
 
23966
24078
  // src/cli-version.ts
23967
- var CLI_VERSION = "0.1.35".length > 0 ? "0.1.35" : "0.0.0-dev";
24079
+ var CLI_VERSION = "0.1.37".length > 0 ? "0.1.37" : "0.0.0-dev";
23968
24080
 
23969
24081
  // src/connection/heartbeat/constants.ts
23970
24082
  var BRIDGE_APP_HEARTBEAT_INTERVAL_MS = 1e4;
@@ -25287,6 +25399,7 @@ function recordMigrationAndPruneCheckpointLegacy(db, migration, applied2) {
25287
25399
  var CHECKPOINT_V1 = "001_cli_sqlite_checkpoint_v1";
25288
25400
  var CHECKPOINT_V1_SQL = readCliSqliteMigrationSql("001_cli_sqlite_checkpoint_v1.sql");
25289
25401
  var AGENT_CAPABILITIES_SQL = readCliSqliteMigrationSql("002_agent_capabilities.sql");
25402
+ var FILE_INDEX_PARENT_PATHS_SQL = readCliSqliteMigrationSql("003_file_index_parent_paths.sql");
25290
25403
  function agentCapabilitiesTableState(db) {
25291
25404
  const rows = db.all(
25292
25405
  `SELECT name FROM sqlite_master WHERE type='table' AND name IN ('agent_capabilities', 'agent_capability_cache')`
@@ -25320,6 +25433,18 @@ var CLI_SQLITE_MIGRATIONS = [
25320
25433
  db.exec(AGENT_CAPABILITIES_SQL);
25321
25434
  },
25322
25435
  alreadyApplied: (db) => agentCapabilitiesTableState(db) === "current"
25436
+ },
25437
+ {
25438
+ name: "003_file_index_parent_paths",
25439
+ migrate: (db) => {
25440
+ db.exec(FILE_INDEX_PARENT_PATHS_SQL);
25441
+ },
25442
+ alreadyApplied: (db) => {
25443
+ const row = db.get(
25444
+ `SELECT 1 as ok FROM sqlite_master WHERE type='table' AND name='file_index_parent_path' LIMIT 1`
25445
+ );
25446
+ return row != null;
25447
+ }
25323
25448
  }
25324
25449
  ];
25325
25450
  function migrateCliSqlite(db) {
@@ -25358,6 +25483,10 @@ var CliSqliteInterrupted = class extends Error {
25358
25483
  }
25359
25484
  };
25360
25485
  function applyCliSqliteConcurrencyPragmas(db) {
25486
+ try {
25487
+ db.run("PRAGMA foreign_keys = ON");
25488
+ } catch {
25489
+ }
25361
25490
  try {
25362
25491
  db.exec("PRAGMA journal_mode = WAL");
25363
25492
  } catch {
@@ -30923,13 +31052,18 @@ function isCodexAcpCommand(command) {
30923
31052
  const i = command.indexOf("@zed-industries/codex-acp");
30924
31053
  return i >= 0 && (i === 0 || command[i - 1] === "npx" || command[i - 1] === "bunx");
30925
31054
  }
30926
- function buildCodexAcpSpawnCommand(base, _sessionMode) {
31055
+ function buildCodexAcpSpawnCommand(base, _sessionMode, _agentConfig) {
30927
31056
  return [...base];
30928
31057
  }
30929
31058
  async function createCodexAcpClient(options) {
30930
31059
  const base = options.command?.length && options.command.some((a) => a.includes("codex-acp")) ? options.command : [...DEFAULT_CODEX_ACP_COMMAND];
30931
- const command = buildCodexAcpSpawnCommand(base, options.sessionMode);
30932
- return createSdkStdioAcpClient({ ...options, command });
31060
+ const command = buildCodexAcpSpawnCommand(base, options.sessionMode, options.agentConfig);
31061
+ return createSdkStdioAcpClient({
31062
+ ...options,
31063
+ command,
31064
+ /** Codex ACP can ignore `session/cancel`; mirror Claude Code's subprocess fallback. */
31065
+ killSubprocessAfterCancelMs: options.killSubprocessAfterCancelMs ?? 2500
31066
+ });
30933
31067
  }
30934
31068
 
30935
31069
  // src/agents/acp/clients/cursor/cursor-acp-client.ts
@@ -31327,7 +31461,7 @@ function resolveAgentCommand(preferredAgentType) {
31327
31461
  command,
31328
31462
  label: preferredAgentType,
31329
31463
  createClient: createCodexAcpClient,
31330
- spawnCommandForSession: (sessionMode, _agentConfig) => buildCodexAcpSpawnCommand(command, sessionMode)
31464
+ spawnCommandForSession: (sessionMode, agentConfig) => buildCodexAcpSpawnCommand(command, sessionMode, agentConfig)
31331
31465
  };
31332
31466
  }
31333
31467
  if (useKiroAcp(preferredAgentType, command)) {
@@ -33783,8 +33917,18 @@ import path28 from "node:path";
33783
33917
  // src/files/index/walk-workspace-tree.ts
33784
33918
  import fs24 from "node:fs";
33785
33919
  import path27 from "node:path";
33920
+ var DEPENDENCY_INSTALL_DIR_NAMES = /* @__PURE__ */ new Set([
33921
+ "node_modules",
33922
+ "bower_components",
33923
+ "vendor",
33924
+ "Pods",
33925
+ "Carthage",
33926
+ "DerivedData",
33927
+ ".yarn",
33928
+ ".pnpm-store"
33929
+ ]);
33786
33930
  function shouldSkipWorkspaceWalkEntry(name) {
33787
- return name.startsWith(".");
33931
+ return DEPENDENCY_INSTALL_DIR_NAMES.has(name);
33788
33932
  }
33789
33933
  async function walkWorkspaceTreeAsync(dir, baseDir, onFile, state) {
33790
33934
  let names;
@@ -33855,14 +33999,26 @@ var FILE_INDEX_INTERRUPT_CHECK_EVERY = 256;
33855
33999
  function assertNotShutdown() {
33856
34000
  if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
33857
34001
  }
34002
+ function upsertFileIndexParentPath(db, resolved) {
34003
+ const now = (/* @__PURE__ */ new Date()).toISOString();
34004
+ db.run(
34005
+ `INSERT INTO file_index_parent_path (path, path_hash, updated_at)
34006
+ VALUES (?, ?, ?)
34007
+ ON CONFLICT(path) DO UPDATE SET path_hash = excluded.path_hash, updated_at = excluded.updated_at`,
34008
+ [resolved, getCwdHashForFileIndex(resolved), now]
34009
+ );
34010
+ const row = db.get("SELECT id FROM file_index_parent_path WHERE path = ?", [resolved]);
34011
+ if (row == null) throw new Error(`Failed to upsert file index parent path: ${resolved}`);
34012
+ return Number(row.id);
34013
+ }
33858
34014
  function persistFileIndexPaths(resolved, paths) {
33859
34015
  return withCliSqliteSync((db) => {
33860
- const h = getCwdHashForFileIndex(resolved);
33861
34016
  let pathCount = 0;
33862
34017
  db.run("BEGIN IMMEDIATE");
33863
34018
  try {
33864
- db.run("DELETE FROM file_index_path WHERE cwd_hash = ?", [h]);
33865
- const ins = db.prepare("INSERT INTO file_index_path (cwd_hash, path) VALUES (?, ?)");
34019
+ const parentId = upsertFileIndexParentPath(db, resolved);
34020
+ db.run("DELETE FROM file_index_child_path WHERE parent_id = ?", [parentId]);
34021
+ const ins = db.prepare("INSERT INTO file_index_child_path (parent_id, path) VALUES (?, ?)");
33866
34022
  try {
33867
34023
  let batch = 0;
33868
34024
  for (const rel of paths) {
@@ -33870,7 +34026,7 @@ function persistFileIndexPaths(resolved, paths) {
33870
34026
  batch = 0;
33871
34027
  assertNotShutdown();
33872
34028
  }
33873
- ins.run([h, rel]);
34029
+ ins.run([parentId, rel]);
33874
34030
  pathCount += 1;
33875
34031
  }
33876
34032
  } finally {
@@ -33912,36 +34068,38 @@ async function buildFileIndexAsync(cwd) {
33912
34068
  // src/files/index/ensure-file-index.ts
33913
34069
  import path29 from "node:path";
33914
34070
 
33915
- // src/files/index/file-index-dependency-path.ts
33916
- function sqliteExprBridgeFileIndexDependencyRank() {
33917
- return `CASE WHEN lower(path) = 'node_modules' OR lower(path) LIKE 'node_modules/%' OR lower(path) LIKE '%/node_modules/%' OR lower(path) = 'bower_components' OR lower(path) LIKE 'bower_components/%' OR lower(path) LIKE '%/bower_components/%' THEN 1 ELSE 0 END`;
33918
- }
33919
-
33920
34071
  // src/files/index/search-file-index.ts
33921
34072
  function escapeLikePattern(fragment) {
33922
34073
  return fragment.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
33923
34074
  }
33924
34075
  function bridgeFileIndexIsPopulatedWithDb(resolvedCwd, db) {
33925
- const h = getCwdHashForFileIndex(resolvedCwd);
33926
- const row = db.get("SELECT 1 as ok FROM file_index_path WHERE cwd_hash = ? LIMIT 1", [h]);
34076
+ const row = db.get("SELECT 1 as ok FROM file_index_parent_path WHERE path = ? LIMIT 1", [resolvedCwd]);
33927
34077
  return row != null;
33928
34078
  }
33929
34079
  function bridgeFileIndexPathCountWithDb(resolvedCwd, db) {
33930
- const h = getCwdHashForFileIndex(resolvedCwd);
33931
- const row = db.get("SELECT COUNT(*) as c FROM file_index_path WHERE cwd_hash = ?", [h]);
34080
+ const row = db.get(
34081
+ `SELECT COUNT(*) as c
34082
+ FROM file_index_child_path child
34083
+ JOIN file_index_parent_path parent ON parent.id = child.parent_id
34084
+ WHERE parent.path = ?`,
34085
+ [resolvedCwd]
34086
+ );
33932
34087
  const c = row?.c ?? 0;
33933
34088
  return Number(c);
33934
34089
  }
33935
34090
  function searchBridgeFilePathsWithDb(resolvedCwd, query, limit, db) {
33936
34091
  const q = query.trim().toLowerCase();
33937
34092
  if (!q) return [];
33938
- const h = getCwdHashForFileIndex(resolvedCwd);
33939
34093
  const pattern = `%${escapeLikePattern(q)}%`;
33940
34094
  const lim = Math.max(0, Math.min(1e4, Math.floor(limit)));
33941
- const depRank = sqliteExprBridgeFileIndexDependencyRank();
33942
34095
  const rows = db.all(
33943
- `SELECT path FROM file_index_path WHERE cwd_hash = ? AND lower(path) LIKE ? ESCAPE '\\' ORDER BY ${depRank}, path LIMIT ?`,
33944
- [h, pattern, lim]
34096
+ `SELECT child.path
34097
+ FROM file_index_child_path child
34098
+ JOIN file_index_parent_path parent ON parent.id = child.parent_id
34099
+ WHERE parent.path = ? AND lower(child.path) LIKE ? ESCAPE '\\'
34100
+ ORDER BY child.path
34101
+ LIMIT ?`,
34102
+ [resolvedCwd, pattern, lim]
33945
34103
  );
33946
34104
  return rows.map((r) => String(r.path));
33947
34105
  }
@@ -33971,9 +34129,7 @@ async function ensureFileIndexAsync(cwd) {
33971
34129
  var DEBOUNCE_MS = 900;
33972
34130
  function shouldIgnoreRelative(rel) {
33973
34131
  const n = rel.replace(/\\/g, "/");
33974
- if (n.includes("/.git/") || n === ".git" || n.startsWith(".git/")) return true;
33975
- if (n.includes("/.buildautomaton/") || n.startsWith(".buildautomaton/")) return true;
33976
- return false;
34132
+ return n.split("/").some((segment) => shouldSkipWorkspaceWalkEntry(segment));
33977
34133
  }
33978
34134
  function attachWatchErrorLog(w) {
33979
34135
  w.on("error", (err) => {
@@ -36016,9 +36172,8 @@ var handleSkillCallMessage = (msg, { getWs, log: log2 }) => {
36016
36172
  );
36017
36173
  };
36018
36174
 
36019
- // src/files/list-dir.ts
36020
- import fs32 from "node:fs";
36021
- import path35 from "node:path";
36175
+ // src/files/list-dir/index.ts
36176
+ import fs33 from "node:fs";
36022
36177
 
36023
36178
  // src/files/ensure-under-cwd.ts
36024
36179
  import path34 from "node:path";
@@ -36031,71 +36186,93 @@ function ensureUnderCwd(relativePath, cwd = getBridgeRoot()) {
36031
36186
  return resolved;
36032
36187
  }
36033
36188
 
36034
- // src/files/list-dir.ts
36189
+ // src/files/list-dir/types.ts
36035
36190
  var LIST_DIR_YIELD_EVERY = 256;
36036
- async function listDirAsync(relativePath) {
36037
- const resolved = ensureUnderCwd(relativePath || ".", getBridgeRoot());
36191
+
36192
+ // src/files/list-dir/map-dir-entry.ts
36193
+ import path35 from "node:path";
36194
+ import fs32 from "node:fs";
36195
+ async function mapDirEntry(d, relativePath, resolved) {
36196
+ const entryPath = path35.join(relativePath || ".", d.name).replace(/\\/g, "/");
36197
+ const fullPath = path35.join(resolved, d.name);
36198
+ let isDir = d.isDirectory();
36199
+ if (d.isSymbolicLink()) {
36200
+ try {
36201
+ const targetStat = await fs32.promises.stat(fullPath);
36202
+ isDir = targetStat.isDirectory();
36203
+ } catch {
36204
+ isDir = false;
36205
+ }
36206
+ }
36207
+ return {
36208
+ name: d.name,
36209
+ path: entryPath,
36210
+ isDir,
36211
+ isSymlink: d.isSymbolicLink()
36212
+ };
36213
+ }
36214
+
36215
+ // src/files/list-dir/sort-entries.ts
36216
+ function sortListEntries(entries) {
36217
+ return entries.sort((a, b) => {
36218
+ if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
36219
+ return a.name.localeCompare(b.name, void 0, { sensitivity: "base" });
36220
+ });
36221
+ }
36222
+
36223
+ // src/files/list-dir/index.ts
36224
+ async function listDirAsync(relativePath, sessionParentPath = getBridgeRoot()) {
36225
+ await yieldToEventLoop();
36226
+ const resolved = ensureUnderCwd(relativePath || ".", sessionParentPath);
36038
36227
  if (!resolved) {
36039
36228
  return { error: "Path is outside working directory" };
36040
36229
  }
36041
36230
  try {
36042
- const names = await fs32.promises.readdir(resolved, { withFileTypes: true });
36043
- const visible = names.filter((d) => !d.name.startsWith("."));
36231
+ const names = await fs33.promises.readdir(resolved, { withFileTypes: true });
36044
36232
  const entries = [];
36045
- for (let i = 0; i < visible.length; i++) {
36233
+ for (let i = 0; i < names.length; i++) {
36046
36234
  if (i > 0 && i % LIST_DIR_YIELD_EVERY === 0) {
36047
36235
  await yieldToEventLoop();
36048
36236
  }
36049
- const d = visible[i];
36050
- const entryPath = path35.join(relativePath || ".", d.name).replace(/\\/g, "/");
36051
- const fullPath = path35.join(resolved, d.name);
36052
- let isDir = d.isDirectory();
36053
- if (d.isSymbolicLink()) {
36054
- try {
36055
- const targetStat = await fs32.promises.stat(fullPath);
36056
- isDir = targetStat.isDirectory();
36057
- } catch {
36058
- isDir = false;
36059
- }
36060
- }
36061
- entries.push({
36062
- name: d.name,
36063
- path: entryPath,
36064
- isDir,
36065
- isSymlink: d.isSymbolicLink()
36066
- });
36237
+ entries.push(await mapDirEntry(names[i], relativePath, resolved));
36067
36238
  }
36068
- entries.sort((a, b) => {
36069
- if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
36070
- return a.name.localeCompare(b.name, void 0, { sensitivity: "base" });
36071
- });
36072
- return { entries };
36239
+ return { entries: sortListEntries(entries) };
36073
36240
  } catch (err) {
36074
36241
  const message = err instanceof Error ? err.message : String(err);
36075
36242
  return { error: message };
36076
36243
  }
36077
36244
  }
36078
36245
 
36079
- // src/files/read-file.ts
36080
- import fs33 from "node:fs";
36081
- import { StringDecoder } from "node:string_decoder";
36082
- function resolveFilePath(relativePath) {
36083
- const resolved = ensureUnderCwd(relativePath, getBridgeRoot());
36246
+ // src/files/read-file/types.ts
36247
+ var LINE_CHUNK_SIZE = 64 * 1024;
36248
+ var READ_RANGE_YIELD_EVERY_BYTES = 256 * 1024;
36249
+
36250
+ // src/files/read-file/resolve-file-path.ts
36251
+ import fs34 from "node:fs";
36252
+ async function resolveFilePathAsync(relativePath, sessionParentPath = getBridgeRoot()) {
36253
+ const resolved = ensureUnderCwd(relativePath, sessionParentPath);
36084
36254
  if (!resolved) return { error: "Path is outside working directory" };
36085
36255
  let real;
36086
36256
  try {
36087
- real = fs33.realpathSync(resolved);
36257
+ real = await fs34.promises.realpath(resolved);
36088
36258
  } catch {
36089
36259
  real = resolved;
36090
36260
  }
36091
- const stat2 = fs33.statSync(real);
36092
- if (!stat2.isFile()) return { error: "Not a file" };
36093
- return real;
36261
+ try {
36262
+ const stat2 = await fs34.promises.stat(real);
36263
+ if (!stat2.isFile()) return { error: "Not a file" };
36264
+ return real;
36265
+ } catch (err) {
36266
+ return { error: err instanceof Error ? err.message : String(err) };
36267
+ }
36094
36268
  }
36095
- var LINE_CHUNK_SIZE = 64 * 1024;
36096
- function readFileRange(filePath, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
36097
- const fileSize = fs33.statSync(filePath).size;
36098
- const fd = fs33.openSync(filePath, "r");
36269
+
36270
+ // src/files/read-file/read-file-range-async.ts
36271
+ import fs35 from "node:fs";
36272
+ import { StringDecoder } from "node:string_decoder";
36273
+ async function readFileRangeAsync(filePath, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
36274
+ const fileSize = (await fs35.promises.stat(filePath)).size;
36275
+ const fd = await fs35.promises.open(filePath, "r");
36099
36276
  const bufSize = 64 * 1024;
36100
36277
  const buf = Buffer.alloc(bufSize);
36101
36278
  const decoder = new StringDecoder("utf8");
@@ -36106,9 +36283,18 @@ function readFileRange(filePath, startLine, endLine, lineOffsetIn, lineChunkSize
36106
36283
  let skipLine0Chars = typeof lineOffsetIn === "number" ? lineOffsetIn : 0;
36107
36284
  let line0CharsReturned = 0;
36108
36285
  let line0Accum = "";
36286
+ let bytesSinceYield = 0;
36109
36287
  try {
36110
- let bytesRead;
36111
- while (!done && (bytesRead = fs33.readSync(fd, buf, 0, bufSize, null)) > 0) {
36288
+ let position = 0;
36289
+ while (!done) {
36290
+ const { bytesRead } = await fd.read(buf, 0, bufSize, position);
36291
+ if (bytesRead === 0) break;
36292
+ position += bytesRead;
36293
+ bytesSinceYield += bytesRead;
36294
+ if (bytesSinceYield >= READ_RANGE_YIELD_EVERY_BYTES) {
36295
+ await yieldToEventLoop();
36296
+ bytesSinceYield = 0;
36297
+ }
36112
36298
  const text = partial2 + decoder.write(buf.subarray(0, bytesRead));
36113
36299
  partial2 = "";
36114
36300
  let lineStart = 0;
@@ -36243,39 +36429,132 @@ function readFileRange(filePath, startLine, endLine, lineOffsetIn, lineChunkSize
36243
36429
  }
36244
36430
  return { content: resultLines.join("\n"), size: fileSize };
36245
36431
  } finally {
36246
- fs33.closeSync(fd);
36432
+ await fd.close();
36247
36433
  }
36248
36434
  }
36249
- function readFile2(relativePath, startLine, endLine, lineOffset, lineChunkSize = LINE_CHUNK_SIZE) {
36435
+
36436
+ // src/files/read-file/read-file-buffer-full-async.ts
36437
+ import fs36 from "node:fs";
36438
+ var READ_CHUNK_BYTES = 256 * 1024;
36439
+ async function readFileBufferFullAsync(filePath) {
36440
+ const stat2 = await fs36.promises.stat(filePath);
36441
+ const fd = await fs36.promises.open(filePath, "r");
36442
+ const chunks = [];
36443
+ let position = 0;
36444
+ let bytesSinceYield = 0;
36250
36445
  try {
36251
- const result = resolveFilePath(relativePath);
36446
+ while (position < stat2.size) {
36447
+ const buf = Buffer.alloc(Math.min(READ_CHUNK_BYTES, stat2.size - position));
36448
+ const { bytesRead } = await fd.read(buf, 0, buf.length, position);
36449
+ if (bytesRead === 0) break;
36450
+ chunks.push(buf.subarray(0, bytesRead));
36451
+ position += bytesRead;
36452
+ bytesSinceYield += bytesRead;
36453
+ if (bytesSinceYield >= READ_RANGE_YIELD_EVERY_BYTES) {
36454
+ await yieldToEventLoop();
36455
+ bytesSinceYield = 0;
36456
+ }
36457
+ }
36458
+ } finally {
36459
+ await fd.close();
36460
+ }
36461
+ return { buffer: Buffer.concat(chunks), size: stat2.size };
36462
+ }
36463
+
36464
+ // src/files/read-file/read-file-full-async.ts
36465
+ async function readFileFullAsync(filePath) {
36466
+ const { buffer, size } = await readFileBufferFullAsync(filePath);
36467
+ const raw = buffer.toString("utf8");
36468
+ const lines = raw.split(/\r?\n/);
36469
+ return { content: raw, totalLines: lines.length, size };
36470
+ }
36471
+
36472
+ // src/files/read-file/guess-mime-type.ts
36473
+ var MIME_BY_EXT = {
36474
+ png: "image/png",
36475
+ jpg: "image/jpeg",
36476
+ jpeg: "image/jpeg",
36477
+ gif: "image/gif",
36478
+ bmp: "image/bmp",
36479
+ ico: "image/x-icon",
36480
+ webp: "image/webp",
36481
+ avif: "image/avif",
36482
+ svg: "image/svg+xml",
36483
+ pdf: "application/pdf",
36484
+ json: "application/json",
36485
+ html: "text/html",
36486
+ htm: "text/html",
36487
+ css: "text/css",
36488
+ js: "text/javascript",
36489
+ mjs: "text/javascript",
36490
+ ts: "text/typescript",
36491
+ txt: "text/plain",
36492
+ md: "text/markdown",
36493
+ xml: "application/xml",
36494
+ zip: "application/zip",
36495
+ gz: "application/gzip",
36496
+ wasm: "application/wasm"
36497
+ };
36498
+ function guessMimeType(filePath) {
36499
+ const ext = filePath.split(".").pop()?.toLowerCase() ?? "";
36500
+ return MIME_BY_EXT[ext] ?? "application/octet-stream";
36501
+ }
36502
+
36503
+ // src/files/read-file/read-file-binary-full-async.ts
36504
+ async function readFileBinaryFullAsync(filePath) {
36505
+ const { buffer, size } = await readFileBufferFullAsync(filePath);
36506
+ return {
36507
+ content: buffer.toString("base64"),
36508
+ size,
36509
+ mimeType: guessMimeType(filePath)
36510
+ };
36511
+ }
36512
+
36513
+ // src/files/read-file/index.ts
36514
+ async function readFileAsync(relativePath, startLine, endLine, lineOffset, lineChunkSize = LINE_CHUNK_SIZE, sessionParentPath = getBridgeRoot(), encoding = "utf8") {
36515
+ await yieldToEventLoop();
36516
+ try {
36517
+ const result = await resolveFilePathAsync(relativePath, sessionParentPath);
36252
36518
  if (typeof result === "object") return result;
36519
+ const resolvedPath = result;
36253
36520
  const hasRange = typeof startLine === "number" && typeof endLine === "number";
36521
+ if (encoding === "base64") {
36522
+ if (hasRange) return { error: "base64 encoding requires a full file read (no line range)" };
36523
+ const read2 = await readFileBinaryFullAsync(resolvedPath);
36524
+ return { ...read2, resolvedPath };
36525
+ }
36254
36526
  if (hasRange) {
36255
- return readFileRange(result, startLine, endLine, lineOffset, lineChunkSize);
36527
+ return readFileRangeAsync(resolvedPath, startLine, endLine, lineOffset, lineChunkSize);
36256
36528
  }
36257
- const stat2 = fs33.statSync(result);
36258
- const raw = fs33.readFileSync(result, "utf8");
36259
- const lines = raw.split(/\r?\n/);
36260
- return { content: raw, totalLines: lines.length, size: stat2.size };
36529
+ const read = await readFileFullAsync(resolvedPath);
36530
+ return { ...read, resolvedPath };
36261
36531
  } catch (err) {
36262
36532
  return { error: err instanceof Error ? err.message : String(err) };
36263
36533
  }
36264
36534
  }
36265
- async function readFileAsync(relativePath, startLine, endLine, lineOffset, lineChunkSize = LINE_CHUNK_SIZE) {
36266
- await yieldToEventLoop();
36267
- return readFile2(relativePath, startLine, endLine, lineOffset, lineChunkSize);
36535
+
36536
+ // src/files/resolve-file-browser-session-parent.ts
36537
+ function resolveFileBrowserSessionParent(sessionWorktreeManager, sessionId) {
36538
+ const sid = sessionId?.trim();
36539
+ if (sid) {
36540
+ sessionWorktreeManager.ensureRepoCheckoutPathsForSession(sid);
36541
+ const worktreeRoot = sessionWorktreeManager.getSessionWorktreeRootForSession(sid);
36542
+ if (worktreeRoot) return worktreeRoot;
36543
+ }
36544
+ return getBridgeRoot();
36268
36545
  }
36269
36546
 
36270
36547
  // src/files/handle-file-browser-search.ts
36271
36548
  import path36 from "node:path";
36272
36549
  var SEARCH_LIMIT = 100;
36273
- function handleFileBrowserSearch(msg, socket, e2ee) {
36550
+ function handleFileBrowserSearch(msg, socket, e2ee, sessionWorktreeManager) {
36274
36551
  void (async () => {
36275
36552
  await yieldToEventLoop();
36276
36553
  const q = typeof msg.q === "string" ? msg.q : "";
36277
- const cwd = path36.resolve(getBridgeRoot());
36278
- if (!await bridgeFileIndexIsPopulated(cwd)) {
36554
+ const sessionParentPath = path36.resolve(
36555
+ sessionWorktreeManager != null ? resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : getBridgeRoot()
36556
+ );
36557
+ if (!await bridgeFileIndexIsPopulated(sessionParentPath)) {
36279
36558
  const payload2 = {
36280
36559
  type: "file_browser_search_response",
36281
36560
  id: msg.id,
@@ -36285,7 +36564,7 @@ function handleFileBrowserSearch(msg, socket, e2ee) {
36285
36564
  sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload2, ["paths"]) : payload2);
36286
36565
  return;
36287
36566
  }
36288
- const results = await searchBridgeFilePathsAsync(cwd, q, SEARCH_LIMIT);
36567
+ const results = await searchBridgeFilePathsAsync(sessionParentPath, q, SEARCH_LIMIT);
36289
36568
  const payload = {
36290
36569
  type: "file_browser_search_response",
36291
36570
  id: msg.id,
@@ -36295,9 +36574,9 @@ function handleFileBrowserSearch(msg, socket, e2ee) {
36295
36574
  sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload, ["paths"]) : payload);
36296
36575
  })();
36297
36576
  }
36298
- function triggerFileIndexBuild() {
36577
+ function triggerFileIndexBuild(sessionParentPath = getBridgeRoot()) {
36299
36578
  setImmediate(() => {
36300
- void ensureFileIndexAsync(getBridgeRoot()).catch((e) => {
36579
+ void ensureFileIndexAsync(sessionParentPath).catch((e) => {
36301
36580
  console.error("[file-index] Background build failed:", e);
36302
36581
  });
36303
36582
  });
@@ -36307,18 +36586,19 @@ function triggerFileIndexBuild() {
36307
36586
  function sendFileBrowserMessage(socket, e2ee, payload) {
36308
36587
  sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload, ["entries", "content", "totalLines", "size", "lineOffset"]) : payload);
36309
36588
  }
36310
- function handleFileBrowserRequest(msg, socket, e2ee) {
36589
+ function handleFileBrowserRequest(msg, socket, e2ee, sessionWorktreeManager) {
36311
36590
  void (async () => {
36312
36591
  const reqPath = msg.path.replace(/^\/+/, "") || ".";
36313
36592
  const op = msg.op === "read" ? "read" : "list";
36593
+ const sessionParentPath = sessionWorktreeManager != null ? resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : void 0;
36314
36594
  if (op === "list") {
36315
- const result = await listDirAsync(reqPath);
36595
+ const result = await listDirAsync(reqPath, sessionParentPath);
36316
36596
  if ("error" in result) {
36317
36597
  sendWsMessage(socket, { type: "file_browser_response", id: msg.id, error: result.error });
36318
36598
  } else {
36319
36599
  sendFileBrowserMessage(socket, e2ee, { type: "file_browser_response", id: msg.id, entries: result.entries });
36320
36600
  if (reqPath === "." || reqPath === "") {
36321
- triggerFileIndexBuild();
36601
+ triggerFileIndexBuild(sessionParentPath);
36322
36602
  }
36323
36603
  }
36324
36604
  } else {
@@ -36326,7 +36606,16 @@ function handleFileBrowserRequest(msg, socket, e2ee) {
36326
36606
  const endLine = typeof msg.endLine === "number" ? msg.endLine : void 0;
36327
36607
  const lineOffset = typeof msg.lineOffset === "number" ? msg.lineOffset : void 0;
36328
36608
  const lineChunkSize = typeof msg.lineChunkSize === "number" ? msg.lineChunkSize : void 0;
36329
- const result = await readFileAsync(reqPath, startLine, endLine, lineOffset, lineChunkSize);
36609
+ const encoding = msg.encoding === "base64" ? "base64" : "utf8";
36610
+ const result = await readFileAsync(
36611
+ reqPath,
36612
+ startLine,
36613
+ endLine,
36614
+ lineOffset,
36615
+ lineChunkSize,
36616
+ sessionParentPath,
36617
+ encoding
36618
+ );
36330
36619
  if ("error" in result) {
36331
36620
  sendWsMessage(socket, { type: "file_browser_response", id: msg.id, error: result.error });
36332
36621
  } else {
@@ -36338,6 +36627,8 @@ function handleFileBrowserRequest(msg, socket, e2ee) {
36338
36627
  size: result.size
36339
36628
  };
36340
36629
  if (result.lineOffset != null) payload.lineOffset = result.lineOffset;
36630
+ if (result.mimeType != null) payload.mimeType = result.mimeType;
36631
+ if (result.resolvedPath != null) payload.resolvedPath = result.resolvedPath;
36341
36632
  sendFileBrowserMessage(socket, e2ee, payload);
36342
36633
  }
36343
36634
  }
@@ -36345,21 +36636,27 @@ function handleFileBrowserRequest(msg, socket, e2ee) {
36345
36636
  }
36346
36637
 
36347
36638
  // src/routing/handlers/file-browser-messages.ts
36348
- function handleFileBrowserRequestMessage(msg, { getWs, e2ee }) {
36639
+ function handleFileBrowserRequestMessage(msg, { getWs, e2ee, sessionWorktreeManager }) {
36349
36640
  if (typeof msg.id !== "string" || typeof msg.path !== "string") return;
36350
36641
  const socket = getWs();
36351
36642
  if (!socket) return;
36352
36643
  handleFileBrowserRequest(
36353
36644
  msg,
36354
36645
  socket,
36355
- e2ee
36646
+ e2ee,
36647
+ sessionWorktreeManager
36356
36648
  );
36357
36649
  }
36358
- function handleFileBrowserSearchMessage(msg, { getWs, e2ee }) {
36650
+ function handleFileBrowserSearchMessage(msg, { getWs, e2ee, sessionWorktreeManager }) {
36359
36651
  if (typeof msg.id !== "string") return;
36360
36652
  const socket = getWs();
36361
36653
  if (!socket) return;
36362
- handleFileBrowserSearch(msg, socket, e2ee);
36654
+ handleFileBrowserSearch(
36655
+ msg,
36656
+ socket,
36657
+ e2ee,
36658
+ sessionWorktreeManager
36659
+ );
36363
36660
  }
36364
36661
 
36365
36662
  // src/routing/handlers/skill-layout-request.ts
@@ -36373,7 +36670,7 @@ function handleSkillLayoutRequest(msg, deps) {
36373
36670
  }
36374
36671
 
36375
36672
  // src/skills/install-remote-skills.ts
36376
- import fs34 from "node:fs";
36673
+ import fs37 from "node:fs";
36377
36674
  import path37 from "node:path";
36378
36675
  function installRemoteSkills(cwd, targetDir, items) {
36379
36676
  const installed2 = [];
@@ -36389,11 +36686,11 @@ function installRemoteSkills(cwd, targetDir, items) {
36389
36686
  for (const f of item.files) {
36390
36687
  if (typeof f.path !== "string" || !f.text && !f.base64) continue;
36391
36688
  const dest = path37.join(skillDir, f.path);
36392
- fs34.mkdirSync(path37.dirname(dest), { recursive: true });
36689
+ fs37.mkdirSync(path37.dirname(dest), { recursive: true });
36393
36690
  if (f.text !== void 0) {
36394
- fs34.writeFileSync(dest, f.text, "utf8");
36691
+ fs37.writeFileSync(dest, f.text, "utf8");
36395
36692
  } else if (f.base64) {
36396
- fs34.writeFileSync(dest, Buffer.from(f.base64, "base64"));
36693
+ fs37.writeFileSync(dest, Buffer.from(f.base64, "base64"));
36397
36694
  }
36398
36695
  }
36399
36696
  installed2.push({
@@ -36551,7 +36848,7 @@ var handleSessionDiscardedMessage = (msg, deps) => {
36551
36848
  };
36552
36849
 
36553
36850
  // src/routing/handlers/revert-turn-snapshot.ts
36554
- import * as fs35 from "node:fs";
36851
+ import * as fs38 from "node:fs";
36555
36852
  var handleRevertTurnSnapshotMessage = (msg, deps) => {
36556
36853
  const id = typeof msg.id === "string" ? msg.id : "";
36557
36854
  const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
@@ -36564,7 +36861,7 @@ var handleRevertTurnSnapshotMessage = (msg, deps) => {
36564
36861
  const agentBase = sessionWorktreeManager.getSessionWorktreeRootForSession(sessionId) ?? getBridgeRoot();
36565
36862
  const file2 = snapshotFilePath(agentBase, turnId);
36566
36863
  try {
36567
- await fs35.promises.access(file2, fs35.constants.F_OK);
36864
+ await fs38.promises.access(file2, fs38.constants.F_OK);
36568
36865
  } catch {
36569
36866
  sendWsMessage(s, {
36570
36867
  type: "revert_turn_snapshot_result",