@buildautomaton/cli 0.1.27 → 0.1.29

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
@@ -4065,8 +4065,8 @@ var init_parseUtil = __esm({
4065
4065
  init_errors();
4066
4066
  init_en();
4067
4067
  makeIssue = (params) => {
4068
- const { data, path: path35, errorMaps, issueData } = params;
4069
- const fullPath = [...path35, ...issueData.path || []];
4068
+ const { data, path: path39, errorMaps, issueData } = params;
4069
+ const fullPath = [...path39, ...issueData.path || []];
4070
4070
  const fullIssue = {
4071
4071
  ...issueData,
4072
4072
  path: fullPath
@@ -4374,11 +4374,11 @@ var init_types = __esm({
4374
4374
  init_parseUtil();
4375
4375
  init_util();
4376
4376
  ParseInputLazyPath = class {
4377
- constructor(parent, value, path35, key) {
4377
+ constructor(parent, value, path39, key) {
4378
4378
  this._cachedPath = [];
4379
4379
  this.parent = parent;
4380
4380
  this.data = value;
4381
- this._path = path35;
4381
+ this._path = path39;
4382
4382
  this._key = key;
4383
4383
  }
4384
4384
  get path() {
@@ -7993,10 +7993,10 @@ function assignProp(target, prop, value) {
7993
7993
  configurable: true
7994
7994
  });
7995
7995
  }
7996
- function getElementAtPath(obj, path35) {
7997
- if (!path35)
7996
+ function getElementAtPath(obj, path39) {
7997
+ if (!path39)
7998
7998
  return obj;
7999
- return path35.reduce((acc, key) => acc?.[key], obj);
7999
+ return path39.reduce((acc, key) => acc?.[key], obj);
8000
8000
  }
8001
8001
  function promiseAllObject(promisesObj) {
8002
8002
  const keys = Object.keys(promisesObj);
@@ -8245,11 +8245,11 @@ function aborted(x, startIndex = 0) {
8245
8245
  }
8246
8246
  return false;
8247
8247
  }
8248
- function prefixIssues(path35, issues) {
8248
+ function prefixIssues(path39, issues) {
8249
8249
  return issues.map((iss) => {
8250
8250
  var _a2;
8251
8251
  (_a2 = iss).path ?? (_a2.path = []);
8252
- iss.path.unshift(path35);
8252
+ iss.path.unshift(path39);
8253
8253
  return iss;
8254
8254
  });
8255
8255
  }
@@ -8438,7 +8438,7 @@ function treeifyError(error40, _mapper) {
8438
8438
  return issue2.message;
8439
8439
  };
8440
8440
  const result = { errors: [] };
8441
- const processError = (error41, path35 = []) => {
8441
+ const processError = (error41, path39 = []) => {
8442
8442
  var _a2, _b;
8443
8443
  for (const issue2 of error41.issues) {
8444
8444
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -8448,7 +8448,7 @@ function treeifyError(error40, _mapper) {
8448
8448
  } else if (issue2.code === "invalid_element") {
8449
8449
  processError({ issues: issue2.issues }, issue2.path);
8450
8450
  } else {
8451
- const fullpath = [...path35, ...issue2.path];
8451
+ const fullpath = [...path39, ...issue2.path];
8452
8452
  if (fullpath.length === 0) {
8453
8453
  result.errors.push(mapper(issue2));
8454
8454
  continue;
@@ -8478,9 +8478,9 @@ function treeifyError(error40, _mapper) {
8478
8478
  processError(error40);
8479
8479
  return result;
8480
8480
  }
8481
- function toDotPath(path35) {
8481
+ function toDotPath(path39) {
8482
8482
  const segs = [];
8483
- for (const seg of path35) {
8483
+ for (const seg of path39) {
8484
8484
  if (typeof seg === "number")
8485
8485
  segs.push(`[${seg}]`);
8486
8486
  else if (typeof seg === "symbol")
@@ -21669,7 +21669,7 @@ var require_has_flag = __commonJS({
21669
21669
  var require_supports_color = __commonJS({
21670
21670
  "../../node_modules/.pnpm/supports-color@7.2.0/node_modules/supports-color/index.js"(exports, module) {
21671
21671
  "use strict";
21672
- var os8 = __require("os");
21672
+ var os9 = __require("os");
21673
21673
  var tty = __require("tty");
21674
21674
  var hasFlag = require_has_flag();
21675
21675
  var { env } = process;
@@ -21717,7 +21717,7 @@ var require_supports_color = __commonJS({
21717
21717
  return min;
21718
21718
  }
21719
21719
  if (process.platform === "win32") {
21720
- const osRelease = os8.release().split(".");
21720
+ const osRelease = os9.release().split(".");
21721
21721
  if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
21722
21722
  return Number(osRelease[2]) >= 14931 ? 3 : 2;
21723
21723
  }
@@ -21963,10 +21963,10 @@ var require_src2 = __commonJS({
21963
21963
  var fs_1 = __require("fs");
21964
21964
  var debug_1 = __importDefault(require_src());
21965
21965
  var log2 = debug_1.default("@kwsites/file-exists");
21966
- function check2(path35, isFile, isDirectory) {
21967
- log2(`checking %s`, path35);
21966
+ function check2(path39, isFile, isDirectory) {
21967
+ log2(`checking %s`, path39);
21968
21968
  try {
21969
- const stat2 = fs_1.statSync(path35);
21969
+ const stat2 = fs_1.statSync(path39);
21970
21970
  if (stat2.isFile() && isFile) {
21971
21971
  log2(`[OK] path represents a file`);
21972
21972
  return true;
@@ -21986,8 +21986,8 @@ var require_src2 = __commonJS({
21986
21986
  throw e;
21987
21987
  }
21988
21988
  }
21989
- function exists2(path35, type = exports.READABLE) {
21990
- return check2(path35, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
21989
+ function exists2(path39, type = exports.READABLE) {
21990
+ return check2(path39, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
21991
21991
  }
21992
21992
  exports.exists = exists2;
21993
21993
  exports.FILE = 1;
@@ -22792,9 +22792,9 @@ function parseChangeSummaryJson(raw, allowedPaths, options) {
22792
22792
  const rawPath = typeof o.path === "string" ? o.path.trim() : "";
22793
22793
  const summary = typeof o.summary === "string" ? o.summary.trim() : "";
22794
22794
  if (!rawPath || !summary) continue;
22795
- const path35 = skip ? normalizeRepoRelativePath(rawPath) || rawPath : resolveChangeSummaryPathAgainstAllowed(rawPath, allowedPaths);
22796
- if (!path35) continue;
22797
- rows.push({ path: path35, summary: clampSummaryToAtMostTwoLines(summary) });
22795
+ const path39 = skip ? normalizeRepoRelativePath(rawPath) || rawPath : resolveChangeSummaryPathAgainstAllowed(rawPath, allowedPaths);
22796
+ if (!path39) continue;
22797
+ rows.push({ path: path39, summary: clampSummaryToAtMostTwoLines(summary) });
22798
22798
  }
22799
22799
  return rows;
22800
22800
  }
@@ -23668,8 +23668,8 @@ function randomSecret() {
23668
23668
  }
23669
23669
  return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
23670
23670
  }
23671
- async function requestPreviewApi(port, secret, method, path35, body) {
23672
- const url2 = `http://127.0.0.1:${port}${path35}`;
23671
+ async function requestPreviewApi(port, secret, method, path39, body) {
23672
+ const url2 = `http://127.0.0.1:${port}${path39}`;
23673
23673
  const headers = {
23674
23674
  [PREVIEW_SECRET_HEADER]: secret,
23675
23675
  "Content-Type": "application/json"
@@ -23681,7 +23681,7 @@ async function requestPreviewApi(port, secret, method, path35, body) {
23681
23681
  });
23682
23682
  const data = await res.json().catch(() => ({}));
23683
23683
  if (!res.ok) {
23684
- throw new Error(data?.error ?? `Preview API ${method} ${path35}: ${res.status}`);
23684
+ throw new Error(data?.error ?? `Preview API ${method} ${path39}: ${res.status}`);
23685
23685
  }
23686
23686
  return data;
23687
23687
  }
@@ -23896,7 +23896,13 @@ function installBridgeProcessResilience() {
23896
23896
  }
23897
23897
 
23898
23898
  // src/cli-version.ts
23899
- var CLI_VERSION = "0.1.27".length > 0 ? "0.1.27" : "0.0.0-dev";
23899
+ var CLI_VERSION = "0.1.29".length > 0 ? "0.1.29" : "0.0.0-dev";
23900
+
23901
+ // src/connection/heartbeat/constants.ts
23902
+ var BRIDGE_APP_HEARTBEAT_INTERVAL_MS = 1e4;
23903
+ var BRIDGE_HEARTBEAT_SEQ_MAX = 2147483646;
23904
+ var BRIDGE_HEARTBEAT_MISSED_ACKS_BEFORE_RECONNECT = 4;
23905
+ var BRIDGE_HEARTBEAT_RTT_SAMPLE_MAX = 5;
23900
23906
 
23901
23907
  // ../../node_modules/.pnpm/open@10.2.0/node_modules/open/index.js
23902
23908
  import process7 from "node:process";
@@ -24876,14 +24882,18 @@ function runPendingAuth(options) {
24876
24882
  }
24877
24883
  function connect() {
24878
24884
  const url2 = buildPendingBridgeUrl(apiUrl, connectionId);
24885
+ let pendingHbSeq = -1;
24879
24886
  ws = createWsBridge({
24880
24887
  url: url2,
24881
24888
  onOpen: () => {
24882
24889
  clearQuietOnOpen();
24890
+ pendingHbSeq = -1;
24883
24891
  sendWsMessage(ws, { type: "identify", role: "cli", cliVersion: CLI_VERSION });
24884
24892
  keepaliveInterval = setInterval(() => {
24885
24893
  if (resolved || !ws || ws.readyState !== 1) return;
24886
- sendWsMessage(ws, { type: "ping", timestamp: Date.now() });
24894
+ pendingHbSeq = pendingHbSeq >= BRIDGE_HEARTBEAT_SEQ_MAX ? 0 : pendingHbSeq + 1;
24895
+ const hb = { t: "h", s: pendingHbSeq };
24896
+ sendWsMessage(ws, hb);
24887
24897
  }, PENDING_KEEPALIVE_MS);
24888
24898
  if (browserFallback) {
24889
24899
  clearTimeout(browserFallback);
@@ -24946,6 +24956,321 @@ function runPendingAuth(options) {
24946
24956
  };
24947
24957
  }
24948
24958
 
24959
+ // src/sqlite/cli-database.ts
24960
+ import sqliteWasm from "node-sqlite3-wasm";
24961
+
24962
+ // src/sqlite/cli-sqlite-paths.ts
24963
+ import fs7 from "node:fs";
24964
+ import path5 from "node:path";
24965
+ import os3 from "node:os";
24966
+ function getCliSqlitePath() {
24967
+ const override = process.env.BUILDAMATON_CLI_SQLITE_PATH?.trim();
24968
+ if (override) return path5.resolve(override);
24969
+ return path5.join(os3.homedir(), ".buildautomaton", "cli.sqlite");
24970
+ }
24971
+ function ensureCliSqliteParentDir(sqlitePath) {
24972
+ const dir = path5.dirname(sqlitePath);
24973
+ if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
24974
+ }
24975
+
24976
+ // src/sqlite/legacy_migration/import-cli-legacy-disk-data.ts
24977
+ import fs12 from "node:fs";
24978
+
24979
+ // src/files/index/constants.ts
24980
+ import path6 from "node:path";
24981
+ import os4 from "node:os";
24982
+ var INDEX_WORK_YIELD_EVERY = 256;
24983
+ var INDEX_DIR = path6.join(os4.homedir(), ".buildautomaton");
24984
+ var INDEX_HASH_LEN = 16;
24985
+
24986
+ // src/prompt-turn-queue/paths.ts
24987
+ import path7 from "node:path";
24988
+ import os5 from "node:os";
24989
+ function getPromptQueuesDirectory() {
24990
+ const override = process.env.BUILDAMATON_PROMPT_QUEUES_DIR?.trim();
24991
+ if (override) return path7.resolve(override);
24992
+ return path7.join(os5.homedir(), ".buildautomaton", "queues");
24993
+ }
24994
+
24995
+ // src/sqlite/legacy_migration/archive-file-index-json.ts
24996
+ import fs9 from "node:fs";
24997
+ import path9 from "node:path";
24998
+
24999
+ // src/sqlite/legacy_migration/archive-to-old-version.ts
25000
+ import fs8 from "node:fs";
25001
+ import path8 from "node:path";
25002
+ var OLD_VERSION_DIR = path8.join(INDEX_DIR, "old-version");
25003
+ function moveLegacyFileToOldVersionBackup(category, sourcePath) {
25004
+ const destDir = path8.join(OLD_VERSION_DIR, category);
25005
+ fs8.mkdirSync(destDir, { recursive: true });
25006
+ const base = path8.basename(sourcePath);
25007
+ let dest = path8.join(destDir, base);
25008
+ if (fs8.existsSync(dest)) {
25009
+ const ext = path8.extname(base);
25010
+ const stem = ext ? base.slice(0, -ext.length) : base;
25011
+ dest = path8.join(destDir, `${stem}-${Date.now()}${ext}`);
25012
+ }
25013
+ fs8.renameSync(sourcePath, dest);
25014
+ }
25015
+
25016
+ // src/sqlite/legacy_migration/archive-file-index-json.ts
25017
+ function archiveLegacyFileIndexJsonFiles() {
25018
+ try {
25019
+ if (!fs9.existsSync(INDEX_DIR)) return;
25020
+ for (const name of fs9.readdirSync(INDEX_DIR)) {
25021
+ if (name.startsWith(".file-index-") && name.endsWith(".json")) {
25022
+ const full = path9.join(INDEX_DIR, name);
25023
+ try {
25024
+ moveLegacyFileToOldVersionBackup("file-index", full);
25025
+ } catch (err) {
25026
+ console.error(`[cli-sqlite] legacy import: could not archive ${name}`, err);
25027
+ }
25028
+ }
25029
+ }
25030
+ } catch (e) {
25031
+ console.error("[cli-sqlite] legacy import: archive file-index json failed", e);
25032
+ }
25033
+ }
25034
+
25035
+ // src/sqlite/legacy_migration/import-agent-sessions-from-disk.ts
25036
+ import fs10 from "node:fs";
25037
+ import path10 from "node:path";
25038
+ import os6 from "node:os";
25039
+ var LEGACY_AGENT_SESSION_DIR = path10.join(os6.homedir(), ".buildautomaton", "agent-sessions");
25040
+ function importLegacyAgentSessionsFromDisk(db) {
25041
+ try {
25042
+ if (!fs10.existsSync(LEGACY_AGENT_SESSION_DIR)) return;
25043
+ const names = fs10.readdirSync(LEGACY_AGENT_SESSION_DIR).filter((n) => n.endsWith(".json"));
25044
+ for (const name of names) {
25045
+ const sessionKey = name.slice(0, -".json".length);
25046
+ const full = path10.join(LEGACY_AGENT_SESSION_DIR, name);
25047
+ let raw;
25048
+ try {
25049
+ raw = fs10.readFileSync(full, "utf8");
25050
+ } catch {
25051
+ continue;
25052
+ }
25053
+ let parsed;
25054
+ try {
25055
+ parsed = JSON.parse(raw);
25056
+ } catch {
25057
+ continue;
25058
+ }
25059
+ if (parsed.v !== 1) continue;
25060
+ const acpSessionId = typeof parsed.acpSessionId === "string" ? parsed.acpSessionId : null;
25061
+ const backendAgentType = typeof parsed.backendAgentType === "string" ? parsed.backendAgentType : null;
25062
+ const configOptionsJson = Array.isArray(parsed.configOptions) ? JSON.stringify(parsed.configOptions) : null;
25063
+ const updatedAt = typeof parsed.updatedAt === "string" ? parsed.updatedAt : (/* @__PURE__ */ new Date()).toISOString();
25064
+ db.run(
25065
+ `INSERT OR REPLACE INTO agent_session (session_key, acp_session_id, backend_agent_type, config_options_json, updated_at)
25066
+ VALUES (?, ?, ?, ?, ?)`,
25067
+ [sessionKey, acpSessionId, backendAgentType, configOptionsJson, updatedAt]
25068
+ );
25069
+ try {
25070
+ moveLegacyFileToOldVersionBackup("agent-sessions", full);
25071
+ } catch {
25072
+ }
25073
+ }
25074
+ } catch (e) {
25075
+ console.error("[cli-sqlite] legacy import: agent_sessions from disk failed", e);
25076
+ }
25077
+ }
25078
+
25079
+ // src/sqlite/legacy_migration/import-prompt-queues-from-disk.ts
25080
+ import fs11 from "node:fs";
25081
+ import path11 from "node:path";
25082
+
25083
+ // src/sqlite/legacy_migration/parse-persisted-queue-json.ts
25084
+ function parsePersistedQueueFromJson(raw) {
25085
+ try {
25086
+ const o = JSON.parse(raw);
25087
+ const queueKey = typeof o.queueKey === "string" ? o.queueKey : typeof o.queueKeyHash === "string" ? o.queueKeyHash : null;
25088
+ if (!queueKey || typeof o.updatedAt !== "string" || !Array.isArray(o.turns)) return null;
25089
+ return { queueKey, updatedAt: o.updatedAt, turns: o.turns };
25090
+ } catch {
25091
+ return null;
25092
+ }
25093
+ }
25094
+
25095
+ // src/sqlite/legacy_migration/import-prompt-queues-from-disk.ts
25096
+ function importLegacyPromptQueuesFromDisk(db) {
25097
+ try {
25098
+ const dir = getPromptQueuesDirectory();
25099
+ if (!fs11.existsSync(dir)) return;
25100
+ for (const name of fs11.readdirSync(dir)) {
25101
+ if (!name.endsWith(".json")) continue;
25102
+ const full = path11.join(dir, name);
25103
+ let raw;
25104
+ try {
25105
+ raw = fs11.readFileSync(full, "utf8");
25106
+ } catch {
25107
+ continue;
25108
+ }
25109
+ const file2 = parsePersistedQueueFromJson(raw);
25110
+ if (!file2) continue;
25111
+ db.run(
25112
+ `INSERT OR REPLACE INTO prompt_queue (queue_key, updated_at, turns_json) VALUES (?, ?, ?)`,
25113
+ [file2.queueKey, file2.updatedAt, JSON.stringify(file2.turns)]
25114
+ );
25115
+ try {
25116
+ moveLegacyFileToOldVersionBackup("queues", full);
25117
+ } catch {
25118
+ }
25119
+ }
25120
+ } catch (e) {
25121
+ console.error("[cli-sqlite] legacy import: prompt queues from disk failed", e);
25122
+ }
25123
+ }
25124
+
25125
+ // src/sqlite/legacy_migration/import-cli-legacy-disk-data.ts
25126
+ function legacyCliDiskMigrationIsPending() {
25127
+ try {
25128
+ if (fs12.existsSync(INDEX_DIR)) {
25129
+ for (const name of fs12.readdirSync(INDEX_DIR)) {
25130
+ if (name.startsWith(".file-index-") && name.endsWith(".json")) return true;
25131
+ }
25132
+ }
25133
+ } catch {
25134
+ }
25135
+ try {
25136
+ if (fs12.existsSync(LEGACY_AGENT_SESSION_DIR)) {
25137
+ if (fs12.readdirSync(LEGACY_AGENT_SESSION_DIR).some((n) => n.endsWith(".json"))) return true;
25138
+ }
25139
+ } catch {
25140
+ }
25141
+ try {
25142
+ const dir = getPromptQueuesDirectory();
25143
+ if (fs12.existsSync(dir) && fs12.readdirSync(dir).some((n) => n.endsWith(".json"))) return true;
25144
+ } catch {
25145
+ }
25146
+ return false;
25147
+ }
25148
+ function importCliSqliteLegacyDiskData(db, log2) {
25149
+ const pending = legacyCliDiskMigrationIsPending();
25150
+ if (pending && log2) {
25151
+ log2("Migrating legacy on-disk CLI data to SQLite\u2026");
25152
+ }
25153
+ archiveLegacyFileIndexJsonFiles();
25154
+ importLegacyAgentSessionsFromDisk(db);
25155
+ importLegacyPromptQueuesFromDisk(db);
25156
+ if (pending && log2) {
25157
+ log2("Legacy on-disk CLI data migration finished.");
25158
+ }
25159
+ }
25160
+
25161
+ // src/sqlite/load-cli-migration-sql.ts
25162
+ import { existsSync, readFileSync as readFileSync2 } from "node:fs";
25163
+ import { dirname as dirname2, join } from "node:path";
25164
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
25165
+ function readCliSqliteMigrationSql(filename) {
25166
+ const dir = dirname2(fileURLToPath2(import.meta.url));
25167
+ const resolved = join(dir, "migrations", filename);
25168
+ if (!existsSync(resolved)) {
25169
+ throw new Error(`Missing CLI SQLite migration SQL: ${resolved}`);
25170
+ }
25171
+ return readFileSync2(resolved, "utf8");
25172
+ }
25173
+
25174
+ // src/sqlite/migrate-cli-sqlite.ts
25175
+ function checkpointSatisfiedByLegacyChain(applied2, replacesLegacyMigrations) {
25176
+ return Array.isArray(replacesLegacyMigrations) && replacesLegacyMigrations.length > 0 && replacesLegacyMigrations.every((n) => applied2.has(n));
25177
+ }
25178
+ function recordMigrationAndPruneCheckpointLegacy(db, migration, applied2) {
25179
+ db.run("INSERT INTO __migrations (name) VALUES (?)", [migration.name]);
25180
+ applied2.add(migration.name);
25181
+ if (migration.checkpoint !== true) return;
25182
+ const legacy = migration.replacesLegacyMigrations;
25183
+ if (!legacy?.length) return;
25184
+ for (const legacyName of legacy) {
25185
+ if (legacyName === migration.name) continue;
25186
+ db.run("DELETE FROM __migrations WHERE name = ?", [legacyName]);
25187
+ applied2.delete(legacyName);
25188
+ }
25189
+ }
25190
+ var CHECKPOINT_V1 = "001_cli_sqlite_checkpoint_v1";
25191
+ var CHECKPOINT_V1_SQL = readCliSqliteMigrationSql("001_cli_sqlite_checkpoint_v1.sql");
25192
+ var CLI_SQLITE_MIGRATIONS = [
25193
+ {
25194
+ name: CHECKPOINT_V1,
25195
+ checkpoint: true,
25196
+ migrate: (db) => {
25197
+ db.exec(CHECKPOINT_V1_SQL);
25198
+ }
25199
+ }
25200
+ ];
25201
+ function migrateCliSqlite(db) {
25202
+ db.exec(readCliSqliteMigrationSql("000_bootstrap_migrations_table.sql"));
25203
+ const appliedRows = db.all("SELECT name FROM __migrations");
25204
+ const applied2 = new Set(appliedRows.map((r) => r.name));
25205
+ for (const migration of CLI_SQLITE_MIGRATIONS) {
25206
+ if (applied2.has(migration.name)) continue;
25207
+ if (migration.checkpoint === true && checkpointSatisfiedByLegacyChain(applied2, migration.replacesLegacyMigrations)) {
25208
+ recordMigrationAndPruneCheckpointLegacy(db, migration, applied2);
25209
+ continue;
25210
+ }
25211
+ if (migration.alreadyApplied?.(db)) {
25212
+ recordMigrationAndPruneCheckpointLegacy(db, migration, applied2);
25213
+ continue;
25214
+ }
25215
+ try {
25216
+ migration.migrate(db);
25217
+ recordMigrationAndPruneCheckpointLegacy(db, migration, applied2);
25218
+ } catch (e) {
25219
+ console.error(`[cli-sqlite] Migration failed: ${migration.name}`, e);
25220
+ throw e;
25221
+ }
25222
+ }
25223
+ }
25224
+
25225
+ // src/sqlite/cli-database.ts
25226
+ var { Database: SqliteDatabase } = sqliteWasm;
25227
+ var openDatabases = /* @__PURE__ */ new Map();
25228
+ var processExitCloseRegistered = false;
25229
+ function registerProcessExitSqliteClose() {
25230
+ if (processExitCloseRegistered) return;
25231
+ processExitCloseRegistered = true;
25232
+ process.once("exit", () => {
25233
+ for (const db of openDatabases.values()) {
25234
+ safeCloseCliSqliteDatabase(db);
25235
+ }
25236
+ openDatabases.clear();
25237
+ });
25238
+ }
25239
+ function safeCloseCliSqliteDatabase(db) {
25240
+ if (db == null) return;
25241
+ try {
25242
+ if (db.isOpen) db.close();
25243
+ } catch {
25244
+ }
25245
+ }
25246
+ function closeAllCliSqliteConnections() {
25247
+ for (const db of openDatabases.values()) {
25248
+ safeCloseCliSqliteDatabase(db);
25249
+ }
25250
+ openDatabases.clear();
25251
+ }
25252
+ function getCliDatabase(options) {
25253
+ const sqlitePath = getCliSqlitePath();
25254
+ const existing = openDatabases.get(sqlitePath);
25255
+ if (existing?.isOpen) return existing;
25256
+ if (existing && !existing.isOpen) {
25257
+ safeCloseCliSqliteDatabase(existing);
25258
+ openDatabases.delete(sqlitePath);
25259
+ }
25260
+ ensureCliSqliteParentDir(sqlitePath);
25261
+ const db = new SqliteDatabase(sqlitePath);
25262
+ try {
25263
+ migrateCliSqlite(db);
25264
+ importCliSqliteLegacyDiskData(db, options?.logLegacyMigration);
25265
+ } catch (e) {
25266
+ safeCloseCliSqliteDatabase(db);
25267
+ throw e;
25268
+ }
25269
+ openDatabases.set(sqlitePath, db);
25270
+ registerProcessExitSqliteClose();
25271
+ return db;
25272
+ }
25273
+
24949
25274
  // src/connection/close-bridge-connection.ts
24950
25275
  async function closeBridgeConnection(state, acpManager, devServerManager, log2) {
24951
25276
  const say = log2 ?? logImmediate;
@@ -24989,20 +25314,24 @@ async function closeBridgeConnection(state, acpManager, devServerManager, log2)
24989
25314
  say("Stopping local dev server processes\u2026");
24990
25315
  await devServerManager.shutdownAllGraceful();
24991
25316
  }
25317
+ try {
25318
+ closeAllCliSqliteConnections();
25319
+ } catch {
25320
+ }
24992
25321
  say("Shutdown complete.");
24993
25322
  }
24994
25323
 
24995
25324
  // src/paths/session-layout-paths.ts
24996
- import * as path5 from "node:path";
25325
+ import * as path12 from "node:path";
24997
25326
  function resolveIsolatedSessionParentPathFromCheckouts(worktreePaths) {
24998
- const resolved = worktreePaths.map((p) => path5.resolve(p)).filter(Boolean);
25327
+ const resolved = worktreePaths.map((p) => path12.resolve(p)).filter(Boolean);
24999
25328
  if (resolved.length === 0) return null;
25000
25329
  resolved.sort();
25001
25330
  return resolved[0];
25002
25331
  }
25003
25332
  function resolveSessionParentPathForAgentProcess(resolvedSessionParentPath) {
25004
25333
  if (resolvedSessionParentPath != null && String(resolvedSessionParentPath).trim() !== "") {
25005
- return path5.resolve(String(resolvedSessionParentPath).trim());
25334
+ return path12.resolve(String(resolvedSessionParentPath).trim());
25006
25335
  }
25007
25336
  return getBridgeRoot();
25008
25337
  }
@@ -25011,17 +25340,17 @@ function resolveSessionParentPathForAgentProcess(resolvedSessionParentPath) {
25011
25340
  import { execFile as execFile7 } from "node:child_process";
25012
25341
  import { readFile, stat } from "node:fs/promises";
25013
25342
  import { promisify as promisify7 } from "node:util";
25014
- import * as path8 from "node:path";
25343
+ import * as path15 from "node:path";
25015
25344
 
25016
25345
  // src/git/pre-turn-snapshot.ts
25017
- import * as fs8 from "node:fs";
25018
- import * as path7 from "node:path";
25346
+ import * as fs14 from "node:fs";
25347
+ import * as path14 from "node:path";
25019
25348
  import { execFile as execFile6 } from "node:child_process";
25020
25349
  import { promisify as promisify6 } from "node:util";
25021
25350
 
25022
25351
  // src/git/discover-repos.ts
25023
- import * as fs7 from "node:fs";
25024
- import * as path6 from "node:path";
25352
+ import * as fs13 from "node:fs";
25353
+ import * as path13 from "node:path";
25025
25354
 
25026
25355
  // ../../node_modules/.pnpm/simple-git@3.32.3/node_modules/simple-git/dist/esm/index.js
25027
25356
  var import_file_exists = __toESM(require_dist(), 1);
@@ -25060,8 +25389,8 @@ function pathspec(...paths) {
25060
25389
  cache.set(key, paths);
25061
25390
  return key;
25062
25391
  }
25063
- function isPathSpec(path35) {
25064
- return path35 instanceof String && cache.has(path35);
25392
+ function isPathSpec(path39) {
25393
+ return path39 instanceof String && cache.has(path39);
25065
25394
  }
25066
25395
  function toPaths(pathSpec) {
25067
25396
  return cache.get(pathSpec) || [];
@@ -25150,8 +25479,8 @@ function toLinesWithContent(input = "", trimmed2 = true, separator = "\n") {
25150
25479
  function forEachLineWithContent(input, callback) {
25151
25480
  return toLinesWithContent(input, true).map((line) => callback(line));
25152
25481
  }
25153
- function folderExists(path35) {
25154
- return (0, import_file_exists.exists)(path35, import_file_exists.FOLDER);
25482
+ function folderExists(path39) {
25483
+ return (0, import_file_exists.exists)(path39, import_file_exists.FOLDER);
25155
25484
  }
25156
25485
  function append(target, item) {
25157
25486
  if (Array.isArray(target)) {
@@ -25555,8 +25884,8 @@ function checkIsRepoRootTask() {
25555
25884
  commands,
25556
25885
  format: "utf-8",
25557
25886
  onError,
25558
- parser(path35) {
25559
- return /^\.(git)?$/.test(path35.trim());
25887
+ parser(path39) {
25888
+ return /^\.(git)?$/.test(path39.trim());
25560
25889
  }
25561
25890
  };
25562
25891
  }
@@ -25990,11 +26319,11 @@ function parseGrep(grep) {
25990
26319
  const paths = /* @__PURE__ */ new Set();
25991
26320
  const results = {};
25992
26321
  forEachLineWithContent(grep, (input) => {
25993
- const [path35, line, preview] = input.split(NULL);
25994
- paths.add(path35);
25995
- (results[path35] = results[path35] || []).push({
26322
+ const [path39, line, preview] = input.split(NULL);
26323
+ paths.add(path39);
26324
+ (results[path39] = results[path39] || []).push({
25996
26325
  line: asNumber(line),
25997
- path: path35,
26326
+ path: path39,
25998
26327
  preview
25999
26328
  });
26000
26329
  });
@@ -26759,14 +27088,14 @@ var init_hash_object = __esm2({
26759
27088
  init_task();
26760
27089
  }
26761
27090
  });
26762
- function parseInit(bare, path35, text) {
27091
+ function parseInit(bare, path39, text) {
26763
27092
  const response = String(text).trim();
26764
27093
  let result;
26765
27094
  if (result = initResponseRegex.exec(response)) {
26766
- return new InitSummary(bare, path35, false, result[1]);
27095
+ return new InitSummary(bare, path39, false, result[1]);
26767
27096
  }
26768
27097
  if (result = reInitResponseRegex.exec(response)) {
26769
- return new InitSummary(bare, path35, true, result[1]);
27098
+ return new InitSummary(bare, path39, true, result[1]);
26770
27099
  }
26771
27100
  let gitDir = "";
26772
27101
  const tokens = response.split(" ");
@@ -26777,7 +27106,7 @@ function parseInit(bare, path35, text) {
26777
27106
  break;
26778
27107
  }
26779
27108
  }
26780
- return new InitSummary(bare, path35, /^re/i.test(response), gitDir);
27109
+ return new InitSummary(bare, path39, /^re/i.test(response), gitDir);
26781
27110
  }
26782
27111
  var InitSummary;
26783
27112
  var initResponseRegex;
@@ -26786,9 +27115,9 @@ var init_InitSummary = __esm2({
26786
27115
  "src/lib/responses/InitSummary.ts"() {
26787
27116
  "use strict";
26788
27117
  InitSummary = class {
26789
- constructor(bare, path35, existing, gitDir) {
27118
+ constructor(bare, path39, existing, gitDir) {
26790
27119
  this.bare = bare;
26791
- this.path = path35;
27120
+ this.path = path39;
26792
27121
  this.existing = existing;
26793
27122
  this.gitDir = gitDir;
26794
27123
  }
@@ -26800,7 +27129,7 @@ var init_InitSummary = __esm2({
26800
27129
  function hasBareCommand(command) {
26801
27130
  return command.includes(bareCommand);
26802
27131
  }
26803
- function initTask(bare = false, path35, customArgs) {
27132
+ function initTask(bare = false, path39, customArgs) {
26804
27133
  const commands = ["init", ...customArgs];
26805
27134
  if (bare && !hasBareCommand(commands)) {
26806
27135
  commands.splice(1, 0, bareCommand);
@@ -26809,7 +27138,7 @@ function initTask(bare = false, path35, customArgs) {
26809
27138
  commands,
26810
27139
  format: "utf-8",
26811
27140
  parser(text) {
26812
- return parseInit(commands.includes("--bare"), path35, text);
27141
+ return parseInit(commands.includes("--bare"), path39, text);
26813
27142
  }
26814
27143
  };
26815
27144
  }
@@ -27625,12 +27954,12 @@ var init_FileStatusSummary = __esm2({
27625
27954
  "use strict";
27626
27955
  fromPathRegex = /^(.+)\0(.+)$/;
27627
27956
  FileStatusSummary = class {
27628
- constructor(path35, index, working_dir) {
27629
- this.path = path35;
27957
+ constructor(path39, index, working_dir) {
27958
+ this.path = path39;
27630
27959
  this.index = index;
27631
27960
  this.working_dir = working_dir;
27632
27961
  if (index === "R" || working_dir === "R") {
27633
- const detail = fromPathRegex.exec(path35) || [null, path35, path35];
27962
+ const detail = fromPathRegex.exec(path39) || [null, path39, path39];
27634
27963
  this.from = detail[2] || "";
27635
27964
  this.path = detail[1] || "";
27636
27965
  }
@@ -27661,14 +27990,14 @@ function splitLine(result, lineStr) {
27661
27990
  default:
27662
27991
  return;
27663
27992
  }
27664
- function data(index, workingDir, path35) {
27993
+ function data(index, workingDir, path39) {
27665
27994
  const raw = `${index}${workingDir}`;
27666
27995
  const handler = parsers6.get(raw);
27667
27996
  if (handler) {
27668
- handler(result, path35);
27997
+ handler(result, path39);
27669
27998
  }
27670
27999
  if (raw !== "##" && raw !== "!!") {
27671
- result.files.push(new FileStatusSummary(path35, index, workingDir));
28000
+ result.files.push(new FileStatusSummary(path39, index, workingDir));
27672
28001
  }
27673
28002
  }
27674
28003
  }
@@ -27941,15 +28270,15 @@ var init_simple_git_api = __esm2({
27941
28270
  this._executor = _executor;
27942
28271
  }
27943
28272
  _runTask(task, then) {
27944
- const chain = this._executor.chain();
27945
- const promise2 = chain.push(task);
28273
+ const chain2 = this._executor.chain();
28274
+ const promise2 = chain2.push(task);
27946
28275
  if (then) {
27947
28276
  taskCallback(task, promise2, then);
27948
28277
  }
27949
28278
  return Object.create(this, {
27950
28279
  then: { value: promise2.then.bind(promise2) },
27951
28280
  catch: { value: promise2.catch.bind(promise2) },
27952
- _executor: { value: chain }
28281
+ _executor: { value: chain2 }
27953
28282
  });
27954
28283
  }
27955
28284
  add(files) {
@@ -27977,9 +28306,9 @@ var init_simple_git_api = __esm2({
27977
28306
  next
27978
28307
  );
27979
28308
  }
27980
- hashObject(path35, write) {
28309
+ hashObject(path39, write) {
27981
28310
  return this._runTask(
27982
- hashObjectTask(path35, write === true),
28311
+ hashObjectTask(path39, write === true),
27983
28312
  trailingFunctionArgument(arguments)
27984
28313
  );
27985
28314
  }
@@ -28332,8 +28661,8 @@ var init_branch = __esm2({
28332
28661
  }
28333
28662
  });
28334
28663
  function toPath(input) {
28335
- const path35 = input.trim().replace(/^["']|["']$/g, "");
28336
- return path35 && normalize2(path35);
28664
+ const path39 = input.trim().replace(/^["']|["']$/g, "");
28665
+ return path39 && normalize2(path39);
28337
28666
  }
28338
28667
  var parseCheckIgnore;
28339
28668
  var init_CheckIgnore = __esm2({
@@ -28647,8 +28976,8 @@ __export2(sub_module_exports, {
28647
28976
  subModuleTask: () => subModuleTask,
28648
28977
  updateSubModuleTask: () => updateSubModuleTask
28649
28978
  });
28650
- function addSubModuleTask(repo, path35) {
28651
- return subModuleTask(["add", repo, path35]);
28979
+ function addSubModuleTask(repo, path39) {
28980
+ return subModuleTask(["add", repo, path39]);
28652
28981
  }
28653
28982
  function initSubModuleTask(customArgs) {
28654
28983
  return subModuleTask(["init", ...customArgs]);
@@ -28981,8 +29310,8 @@ var require_git = __commonJS2({
28981
29310
  }
28982
29311
  return this._runTask(straightThroughStringTask2(command, this._trimmed), next);
28983
29312
  };
28984
- Git2.prototype.submoduleAdd = function(repo, path35, then) {
28985
- return this._runTask(addSubModuleTask2(repo, path35), trailingFunctionArgument2(arguments));
29313
+ Git2.prototype.submoduleAdd = function(repo, path39, then) {
29314
+ return this._runTask(addSubModuleTask2(repo, path39), trailingFunctionArgument2(arguments));
28986
29315
  };
28987
29316
  Git2.prototype.submoduleUpdate = function(args, then) {
28988
29317
  return this._runTask(
@@ -29626,20 +29955,20 @@ async function isGitRepoDirectory(dirPath) {
29626
29955
  // src/git/discover-repos.ts
29627
29956
  async function discoverGitRepos(cwd = getBridgeRoot()) {
29628
29957
  const result = [];
29629
- const cwdResolved = path6.resolve(cwd);
29958
+ const cwdResolved = path13.resolve(cwd);
29630
29959
  if (await isGitRepoDirectory(cwdResolved)) {
29631
29960
  const remoteUrl = await getRemoteOriginUrl(cwdResolved);
29632
29961
  result.push({ absolutePath: cwdResolved, remoteUrl });
29633
29962
  }
29634
29963
  let entries;
29635
29964
  try {
29636
- entries = fs7.readdirSync(cwdResolved, { withFileTypes: true });
29965
+ entries = fs13.readdirSync(cwdResolved, { withFileTypes: true });
29637
29966
  } catch {
29638
29967
  return result;
29639
29968
  }
29640
29969
  for (const ent of entries) {
29641
29970
  if (!ent.isDirectory()) continue;
29642
- const childPath = path6.join(cwdResolved, ent.name);
29971
+ const childPath = path13.join(cwdResolved, ent.name);
29643
29972
  if (await isGitRepoDirectory(childPath)) {
29644
29973
  const remoteUrl = await getRemoteOriginUrl(childPath);
29645
29974
  result.push({ absolutePath: childPath, remoteUrl });
@@ -29648,22 +29977,22 @@ async function discoverGitRepos(cwd = getBridgeRoot()) {
29648
29977
  return result;
29649
29978
  }
29650
29979
  async function discoverGitReposUnderRoot(rootPath) {
29651
- const root = path6.resolve(rootPath);
29980
+ const root = path13.resolve(rootPath);
29652
29981
  const roots = [];
29653
29982
  async function walk(dir) {
29654
29983
  if (await isGitRepoDirectory(dir)) {
29655
- roots.push(path6.resolve(dir));
29984
+ roots.push(path13.resolve(dir));
29656
29985
  return;
29657
29986
  }
29658
29987
  let entries;
29659
29988
  try {
29660
- entries = fs7.readdirSync(dir, { withFileTypes: true });
29989
+ entries = fs13.readdirSync(dir, { withFileTypes: true });
29661
29990
  } catch {
29662
29991
  return;
29663
29992
  }
29664
29993
  for (const ent of entries) {
29665
29994
  if (!ent.isDirectory() || ent.name === ".git") continue;
29666
- await walk(path6.join(dir, ent.name));
29995
+ await walk(path13.join(dir, ent.name));
29667
29996
  }
29668
29997
  }
29669
29998
  await walk(root);
@@ -29679,7 +30008,7 @@ async function discoverGitReposUnderRoot(rootPath) {
29679
30008
  // src/git/pre-turn-snapshot.ts
29680
30009
  var execFileAsync5 = promisify6(execFile6);
29681
30010
  function snapshotsDirForCwd(agentCwd) {
29682
- return path7.join(agentCwd, ".buildautomaton", "snapshots");
30011
+ return path14.join(agentCwd, ".buildautomaton", "snapshots");
29683
30012
  }
29684
30013
  async function gitStashCreate(repoRoot, log2) {
29685
30014
  try {
@@ -29708,7 +30037,7 @@ async function gitRun(repoRoot, args, log2, label) {
29708
30037
  async function resolveSnapshotRepoRoots(options) {
29709
30038
  const { worktreePaths, fallbackCwd, sessionId, log: log2 } = options;
29710
30039
  if (worktreePaths?.length) {
29711
- const uniq = [...new Set(worktreePaths.map((p) => path7.resolve(p)))];
30040
+ const uniq = [...new Set(worktreePaths.map((p) => path14.resolve(p)))];
29712
30041
  return uniq;
29713
30042
  }
29714
30043
  try {
@@ -29716,7 +30045,7 @@ async function resolveSnapshotRepoRoots(options) {
29716
30045
  const mapped = repos.map((r) => r.absolutePath);
29717
30046
  const sid = sessionId?.trim();
29718
30047
  if (sid) {
29719
- const filtered = mapped.filter((root) => path7.basename(root) === sid);
30048
+ const filtered = mapped.filter((root) => path14.basename(root) === sid);
29720
30049
  if (filtered.length > 0) return filtered;
29721
30050
  }
29722
30051
  return mapped;
@@ -29737,7 +30066,7 @@ async function capturePreTurnSnapshot(options) {
29737
30066
  }
29738
30067
  const dir = snapshotsDirForCwd(agentCwd);
29739
30068
  try {
29740
- fs8.mkdirSync(dir, { recursive: true });
30069
+ fs14.mkdirSync(dir, { recursive: true });
29741
30070
  } catch (e) {
29742
30071
  return { ok: false, error: e instanceof Error ? e.message : String(e) };
29743
30072
  }
@@ -29746,9 +30075,9 @@ async function capturePreTurnSnapshot(options) {
29746
30075
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
29747
30076
  repos
29748
30077
  };
29749
- const filePath = path7.join(dir, `${runId}.json`);
30078
+ const filePath = path14.join(dir, `${runId}.json`);
29750
30079
  try {
29751
- fs8.writeFileSync(filePath, JSON.stringify(payload, null, 2), "utf8");
30080
+ fs14.writeFileSync(filePath, JSON.stringify(payload, null, 2), "utf8");
29752
30081
  } catch (e) {
29753
30082
  return { ok: false, error: e instanceof Error ? e.message : String(e) };
29754
30083
  }
@@ -29761,7 +30090,7 @@ async function capturePreTurnSnapshot(options) {
29761
30090
  async function applyPreTurnSnapshot(filePath, log2) {
29762
30091
  let data;
29763
30092
  try {
29764
- const raw = fs8.readFileSync(filePath, "utf8");
30093
+ const raw = fs14.readFileSync(filePath, "utf8");
29765
30094
  data = JSON.parse(raw);
29766
30095
  } catch (e) {
29767
30096
  return { ok: false, error: e instanceof Error ? e.message : String(e) };
@@ -29784,7 +30113,7 @@ async function applyPreTurnSnapshot(filePath, log2) {
29784
30113
  return { ok: true };
29785
30114
  }
29786
30115
  function snapshotFilePath(agentCwd, runId) {
29787
- return path7.join(snapshotsDirForCwd(agentCwd), `${runId}.json`);
30116
+ return path14.join(snapshotsDirForCwd(agentCwd), `${runId}.json`);
29788
30117
  }
29789
30118
 
29790
30119
  // src/git/session-git-queue.ts
@@ -29833,7 +30162,7 @@ async function collectTurnGitDiffFromPreTurnSnapshot(options) {
29833
30162
  continue;
29834
30163
  }
29835
30164
  const lines = namesRaw.split("\n").map((l) => l.trim()).filter(Boolean);
29836
- const slug = path8.basename(repo.path).replace(/[^\w.-]+/g, "_") || "repo";
30165
+ const slug = path15.basename(repo.path).replace(/[^\w.-]+/g, "_") || "repo";
29837
30166
  for (const rel of lines) {
29838
30167
  if (rel.includes("..")) continue;
29839
30168
  try {
@@ -29847,7 +30176,7 @@ async function collectTurnGitDiffFromPreTurnSnapshot(options) {
29847
30176
  );
29848
30177
  if (!patchContent.trim()) continue;
29849
30178
  const displayPath = multiRepo ? `${slug}/${rel}` : rel;
29850
- const workspaceFilePath = path8.join(repo.path, rel);
30179
+ const workspaceFilePath = path15.join(repo.path, rel);
29851
30180
  const newText = await readWorkspaceFileAsUtf8(workspaceFilePath);
29852
30181
  sendSessionUpdate({
29853
30182
  type: "session_file_change",
@@ -29869,9 +30198,9 @@ async function collectTurnGitDiffFromPreTurnSnapshot(options) {
29869
30198
  // src/agents/acp/put-summarize-change-summaries.ts
29870
30199
  async function putEncryptedChangeSummaryRows(params) {
29871
30200
  const base = params.apiBaseUrl.replace(/\/+$/, "");
29872
- const entries = params.rows.map(({ path: path35, summary }) => {
30201
+ const entries = params.rows.map(({ path: path39, summary }) => {
29873
30202
  const enc = params.e2ee.encryptFields({ summary }, ["summary"]);
29874
- return { path: path35, summary: JSON.stringify(enc) };
30203
+ return { path: path39, summary: JSON.stringify(enc) };
29875
30204
  });
29876
30205
  const res = await fetch(
29877
30206
  `${base}/api/sessions/${encodeURIComponent(params.sessionId)}/follow-ups/summarize-changes`,
@@ -30208,8 +30537,8 @@ async function sendPromptToAgent(options) {
30208
30537
  }
30209
30538
 
30210
30539
  // src/agents/acp/ensure-acp-client.ts
30211
- import * as fs10 from "node:fs";
30212
- import * as path12 from "node:path";
30540
+ import * as fs15 from "node:fs";
30541
+ import * as path18 from "node:path";
30213
30542
 
30214
30543
  // src/error-message.ts
30215
30544
  function errorMessage(err) {
@@ -30711,20 +31040,20 @@ function resolveAgentCommand(preferredAgentType) {
30711
31040
 
30712
31041
  // src/agents/acp/session-file-change-path-kind.ts
30713
31042
  import { execFileSync as execFileSync4 } from "node:child_process";
30714
- import { existsSync, statSync } from "node:fs";
31043
+ import { existsSync as existsSync2, statSync } from "node:fs";
30715
31044
 
30716
31045
  // src/git/get-git-repo-root-sync.ts
30717
31046
  import { execFileSync as execFileSync2 } from "node:child_process";
30718
- import * as path9 from "node:path";
31047
+ import * as path16 from "node:path";
30719
31048
  function getGitRepoRootSync(startDir) {
30720
31049
  try {
30721
31050
  const out = execFileSync2("git", ["rev-parse", "--show-toplevel"], {
30722
- cwd: path9.resolve(startDir),
31051
+ cwd: path16.resolve(startDir),
30723
31052
  encoding: "utf8",
30724
31053
  stdio: ["ignore", "pipe", "ignore"],
30725
31054
  maxBuffer: 1024 * 1024
30726
31055
  }).trim();
30727
- return out ? path9.resolve(out) : null;
31056
+ return out ? path16.resolve(out) : null;
30728
31057
  } catch {
30729
31058
  return null;
30730
31059
  }
@@ -30732,26 +31061,26 @@ function getGitRepoRootSync(startDir) {
30732
31061
 
30733
31062
  // src/agents/acp/workspace-files.ts
30734
31063
  import { execFileSync as execFileSync3 } from "node:child_process";
30735
- import { readFileSync as readFileSync3 } from "node:fs";
30736
- import * as path10 from "node:path";
31064
+ import { readFileSync as readFileSync4 } from "node:fs";
31065
+ import * as path17 from "node:path";
30737
31066
  function resolveWorkspaceFilePath(sessionParentPath, rawPath) {
30738
31067
  const trimmed2 = rawPath.trim();
30739
31068
  if (!trimmed2) return null;
30740
- const normalizedSessionParent = path10.resolve(sessionParentPath);
31069
+ const normalizedSessionParent = path17.resolve(sessionParentPath);
30741
31070
  let resolvedPath = resolveSafePathUnderCwd(sessionParentPath, trimmed2);
30742
31071
  if (!resolvedPath) {
30743
- const candidate = path10.isAbsolute(trimmed2) ? path10.normalize(trimmed2) : path10.normalize(path10.resolve(normalizedSessionParent, trimmed2));
31072
+ const candidate = path17.isAbsolute(trimmed2) ? path17.normalize(trimmed2) : path17.normalize(path17.resolve(normalizedSessionParent, trimmed2));
30744
31073
  const gitRoot2 = getGitRepoRootSync(sessionParentPath);
30745
31074
  if (!gitRoot2) return null;
30746
- const rel = path10.relative(gitRoot2, candidate);
30747
- if (rel.startsWith("..") || path10.isAbsolute(rel)) return null;
31075
+ const rel = path17.relative(gitRoot2, candidate);
31076
+ if (rel.startsWith("..") || path17.isAbsolute(rel)) return null;
30748
31077
  resolvedPath = candidate;
30749
31078
  }
30750
31079
  const gitRoot = getGitRepoRootSync(sessionParentPath);
30751
31080
  if (gitRoot) {
30752
- const relFromRoot = path10.relative(gitRoot, resolvedPath);
30753
- if (!relFromRoot.startsWith("..") && !path10.isAbsolute(relFromRoot)) {
30754
- return { resolvedPath, display: relFromRoot.split(path10.sep).join("/") };
31081
+ const relFromRoot = path17.relative(gitRoot, resolvedPath);
31082
+ if (!relFromRoot.startsWith("..") && !path17.isAbsolute(relFromRoot)) {
31083
+ return { resolvedPath, display: relFromRoot.split(path17.sep).join("/") };
30755
31084
  }
30756
31085
  }
30757
31086
  return { resolvedPath, display: toDisplayPathRelativeToCwd(sessionParentPath, resolvedPath) };
@@ -30760,11 +31089,11 @@ function readUtf8WorkspaceFile(sessionParentPath, displayPath) {
30760
31089
  if (!displayPath || displayPath.includes("..")) return "";
30761
31090
  const gitRoot = getGitRepoRootSync(sessionParentPath);
30762
31091
  if (gitRoot) {
30763
- const resolvedPath2 = path10.resolve(gitRoot, displayPath);
30764
- const rel = path10.relative(gitRoot, resolvedPath2);
30765
- if (!rel.startsWith("..") && !path10.isAbsolute(rel)) {
31092
+ const resolvedPath2 = path17.resolve(gitRoot, displayPath);
31093
+ const rel = path17.relative(gitRoot, resolvedPath2);
31094
+ if (!rel.startsWith("..") && !path17.isAbsolute(rel)) {
30766
31095
  try {
30767
- return readFileSync3(resolvedPath2, "utf8");
31096
+ return readFileSync4(resolvedPath2, "utf8");
30768
31097
  } catch {
30769
31098
  }
30770
31099
  }
@@ -30772,7 +31101,7 @@ function readUtf8WorkspaceFile(sessionParentPath, displayPath) {
30772
31101
  const resolvedPath = resolveSafePathUnderCwd(sessionParentPath, displayPath);
30773
31102
  if (!resolvedPath) return "";
30774
31103
  try {
30775
- return readFileSync3(resolvedPath, "utf8");
31104
+ return readFileSync4(resolvedPath, "utf8");
30776
31105
  } catch {
30777
31106
  return "";
30778
31107
  }
@@ -30781,9 +31110,9 @@ function tryWorkspaceDisplayToPath(sessionParentPath, displayPath) {
30781
31110
  if (!displayPath || displayPath.includes("..")) return null;
30782
31111
  const gitRoot = getGitRepoRootSync(sessionParentPath);
30783
31112
  if (gitRoot) {
30784
- const resolvedPath = path10.resolve(gitRoot, displayPath);
30785
- const rel = path10.relative(gitRoot, resolvedPath);
30786
- if (!rel.startsWith("..") && !path10.isAbsolute(rel)) return resolvedPath;
31113
+ const resolvedPath = path17.resolve(gitRoot, displayPath);
31114
+ const rel = path17.relative(gitRoot, resolvedPath);
31115
+ if (!rel.startsWith("..") && !path17.isAbsolute(rel)) return resolvedPath;
30787
31116
  }
30788
31117
  return resolveSafePathUnderCwd(sessionParentPath, displayPath);
30789
31118
  }
@@ -30818,7 +31147,7 @@ function gitHeadPathObjectType(sessionParentPath, displayPath) {
30818
31147
  }
30819
31148
  function getSessionFileChangeDirectoryFlags(sessionParentPath, displayPath) {
30820
31149
  const resolvedPath = tryWorkspaceDisplayToPath(sessionParentPath, displayPath);
30821
- if (resolvedPath && existsSync(resolvedPath)) {
31150
+ if (resolvedPath && existsSync2(resolvedPath)) {
30822
31151
  try {
30823
31152
  if (statSync(resolvedPath).isDirectory()) {
30824
31153
  return { isDirectory: true, directoryRemoved: false };
@@ -30918,7 +31247,7 @@ function createBridgeOnRequest(opts) {
30918
31247
  }
30919
31248
 
30920
31249
  // src/agents/acp/hooks/extract-acp-file-diffs-from-update/paths-and-text.ts
30921
- import { fileURLToPath as fileURLToPath2 } from "node:url";
31250
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
30922
31251
  function readOptionalTextField(v) {
30923
31252
  if (v === null || v === void 0) return "";
30924
31253
  if (typeof v === "string") return v;
@@ -30928,7 +31257,7 @@ function normalizePathField(raw) {
30928
31257
  const t = raw.trim();
30929
31258
  if (t.startsWith("file://")) {
30930
31259
  try {
30931
- return fileURLToPath2(t);
31260
+ return fileURLToPath3(t);
30932
31261
  } catch {
30933
31262
  return t;
30934
31263
  }
@@ -31386,29 +31715,34 @@ function buildAcpSessionBridgeHooks(opts) {
31386
31715
  }
31387
31716
 
31388
31717
  // src/agents/acp/local-agent-session-file.ts
31389
- import fs9 from "node:fs";
31390
- import os3 from "node:os";
31391
- import path11 from "node:path";
31392
- var LOCAL_AGENT_SESSION_DIR = path11.join(os3.homedir(), ".buildautomaton", "agent-sessions");
31393
- function safeFileSlug(cloudSessionId) {
31718
+ function sessionKeyForCloudSessionId(cloudSessionId) {
31394
31719
  const t = cloudSessionId.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 220);
31395
31720
  return t.length > 0 ? t : "session";
31396
31721
  }
31397
- function localAgentSessionFilePath(cloudSessionId) {
31398
- return path11.join(LOCAL_AGENT_SESSION_DIR, `${safeFileSlug(cloudSessionId)}.json`);
31399
- }
31400
31722
  function readLocalAgentSessionFile(cloudSessionId) {
31401
31723
  try {
31402
- const p = localAgentSessionFilePath(cloudSessionId);
31403
- const raw = fs9.readFileSync(p, "utf8");
31404
- const parsed = JSON.parse(raw);
31405
- if (parsed.v !== 1) return null;
31724
+ const db = getCliDatabase();
31725
+ const key = sessionKeyForCloudSessionId(cloudSessionId);
31726
+ const row = db.get(
31727
+ "SELECT acp_session_id, backend_agent_type, config_options_json, updated_at FROM agent_session WHERE session_key = ?",
31728
+ [key]
31729
+ );
31730
+ if (!row) return null;
31731
+ let configOptions = null;
31732
+ if (row.config_options_json != null && row.config_options_json !== "") {
31733
+ try {
31734
+ const parsed = JSON.parse(row.config_options_json);
31735
+ configOptions = Array.isArray(parsed) ? parsed : null;
31736
+ } catch {
31737
+ configOptions = null;
31738
+ }
31739
+ }
31406
31740
  return {
31407
31741
  v: 1,
31408
- acpSessionId: typeof parsed.acpSessionId === "string" ? parsed.acpSessionId : null,
31409
- backendAgentType: typeof parsed.backendAgentType === "string" ? parsed.backendAgentType : null,
31410
- configOptions: Array.isArray(parsed.configOptions) ? parsed.configOptions : null,
31411
- updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : (/* @__PURE__ */ new Date()).toISOString()
31742
+ acpSessionId: row.acp_session_id,
31743
+ backendAgentType: row.backend_agent_type,
31744
+ configOptions,
31745
+ updatedAt: row.updated_at
31412
31746
  };
31413
31747
  } catch {
31414
31748
  return null;
@@ -31416,9 +31750,8 @@ function readLocalAgentSessionFile(cloudSessionId) {
31416
31750
  }
31417
31751
  function writeLocalAgentSessionFile(cloudSessionId, patch) {
31418
31752
  try {
31419
- const dir = LOCAL_AGENT_SESSION_DIR;
31420
- if (!fs9.existsSync(dir)) fs9.mkdirSync(dir, { recursive: true });
31421
- const p = localAgentSessionFilePath(cloudSessionId);
31753
+ const db = getCliDatabase();
31754
+ const key = sessionKeyForCloudSessionId(cloudSessionId);
31422
31755
  const prev = readLocalAgentSessionFile(cloudSessionId);
31423
31756
  const next = {
31424
31757
  v: 1,
@@ -31427,7 +31760,17 @@ function writeLocalAgentSessionFile(cloudSessionId, patch) {
31427
31760
  configOptions: patch.configOptions !== void 0 ? patch.configOptions : prev?.configOptions ?? null,
31428
31761
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
31429
31762
  };
31430
- fs9.writeFileSync(p, JSON.stringify(next, null, 2), "utf8");
31763
+ const configJson = next.configOptions != null ? JSON.stringify(next.configOptions) : null;
31764
+ db.run(
31765
+ `INSERT INTO agent_session (session_key, acp_session_id, backend_agent_type, config_options_json, updated_at)
31766
+ VALUES (?, ?, ?, ?, ?)
31767
+ ON CONFLICT(session_key) DO UPDATE SET
31768
+ acp_session_id = excluded.acp_session_id,
31769
+ backend_agent_type = excluded.backend_agent_type,
31770
+ config_options_json = excluded.config_options_json,
31771
+ updated_at = excluded.updated_at`,
31772
+ [key, next.acpSessionId, next.backendAgentType, configJson, next.updatedAt]
31773
+ );
31431
31774
  } catch {
31432
31775
  }
31433
31776
  }
@@ -31451,7 +31794,7 @@ async function ensureAcpClient(options) {
31451
31794
  if (state.acpStartPromise && !state.acpHandle) {
31452
31795
  await state.acpStartPromise;
31453
31796
  }
31454
- if (state.acpHandle && state.lastAcpCwd != null && path12.resolve(state.lastAcpCwd) !== path12.resolve(targetSessionParentPath)) {
31797
+ if (state.acpHandle && state.lastAcpCwd != null && path18.resolve(state.lastAcpCwd) !== path18.resolve(targetSessionParentPath)) {
31455
31798
  try {
31456
31799
  state.acpHandle.disconnect();
31457
31800
  } catch {
@@ -31486,7 +31829,7 @@ async function ensureAcpClient(options) {
31486
31829
  if (!state.acpStartPromise) {
31487
31830
  let statOk = false;
31488
31831
  try {
31489
- const st = fs10.statSync(targetSessionParentPath);
31832
+ const st = fs15.statSync(targetSessionParentPath);
31490
31833
  statOk = st.isDirectory();
31491
31834
  if (!statOk) {
31492
31835
  state.lastAcpStartError = `Agent cwd is not a directory: ${targetSessionParentPath}`;
@@ -31733,12 +32076,12 @@ async function createAcpManager(options) {
31733
32076
  }
31734
32077
 
31735
32078
  // src/worktrees/session-worktree-manager.ts
31736
- import * as path19 from "node:path";
31737
- import os5 from "node:os";
32079
+ import * as path25 from "node:path";
32080
+ import os8 from "node:os";
31738
32081
 
31739
32082
  // src/worktrees/prepare-new-session-worktrees.ts
31740
- import * as fs12 from "node:fs";
31741
- import * as path14 from "node:path";
32083
+ import * as fs17 from "node:fs";
32084
+ import * as path20 from "node:path";
31742
32085
 
31743
32086
  // src/git/worktree-add.ts
31744
32087
  async function gitWorktreeAddBranch(mainRepoPath, worktreePath, branch) {
@@ -31747,12 +32090,12 @@ async function gitWorktreeAddBranch(mainRepoPath, worktreePath, branch) {
31747
32090
  }
31748
32091
 
31749
32092
  // src/worktrees/worktree-layout-file.ts
31750
- import * as fs11 from "node:fs";
31751
- import * as path13 from "node:path";
31752
- import os4 from "node:os";
32093
+ import * as fs16 from "node:fs";
32094
+ import * as path19 from "node:path";
32095
+ import os7 from "node:os";
31753
32096
  var LAYOUT_FILENAME = "worktree-launcher-layout.json";
31754
32097
  function defaultWorktreeLayoutPath() {
31755
- return path13.join(os4.homedir(), ".buildautomaton", LAYOUT_FILENAME);
32098
+ return path19.join(os7.homedir(), ".buildautomaton", LAYOUT_FILENAME);
31756
32099
  }
31757
32100
  function normalizeLoadedLayout(raw) {
31758
32101
  if (raw && typeof raw === "object" && "launcherCwds" in raw) {
@@ -31764,8 +32107,8 @@ function normalizeLoadedLayout(raw) {
31764
32107
  function loadWorktreeLayout() {
31765
32108
  try {
31766
32109
  const p = defaultWorktreeLayoutPath();
31767
- if (!fs11.existsSync(p)) return { launcherCwds: [] };
31768
- const raw = JSON.parse(fs11.readFileSync(p, "utf8"));
32110
+ if (!fs16.existsSync(p)) return { launcherCwds: [] };
32111
+ const raw = JSON.parse(fs16.readFileSync(p, "utf8"));
31769
32112
  return normalizeLoadedLayout(raw);
31770
32113
  } catch {
31771
32114
  return { launcherCwds: [] };
@@ -31773,24 +32116,24 @@ function loadWorktreeLayout() {
31773
32116
  }
31774
32117
  function saveWorktreeLayout(layout) {
31775
32118
  try {
31776
- const dir = path13.dirname(defaultWorktreeLayoutPath());
31777
- fs11.mkdirSync(dir, { recursive: true });
31778
- fs11.writeFileSync(defaultWorktreeLayoutPath(), JSON.stringify(layout, null, 2), "utf8");
32119
+ const dir = path19.dirname(defaultWorktreeLayoutPath());
32120
+ fs16.mkdirSync(dir, { recursive: true });
32121
+ fs16.writeFileSync(defaultWorktreeLayoutPath(), JSON.stringify(layout, null, 2), "utf8");
31779
32122
  } catch {
31780
32123
  }
31781
32124
  }
31782
32125
  function baseNameSafe(pathString) {
31783
- return path13.basename(pathString).replace(/[^a-zA-Z0-9._-]+/g, "-") || "cwd";
32126
+ return path19.basename(pathString).replace(/[^a-zA-Z0-9._-]+/g, "-") || "cwd";
31784
32127
  }
31785
32128
  function getLauncherDirNameIfPresent(layout, bridgeRootPath2) {
31786
- const norm = path13.resolve(bridgeRootPath2);
31787
- const existing = layout.launcherCwds.find((e) => path13.resolve(e.absolutePath) === norm);
32129
+ const norm = path19.resolve(bridgeRootPath2);
32130
+ const existing = layout.launcherCwds.find((e) => path19.resolve(e.absolutePath) === norm);
31788
32131
  return existing?.dirName;
31789
32132
  }
31790
32133
  function allocateDirNameForLauncherCwd(layout, bridgeRootPath2) {
31791
32134
  const existing = getLauncherDirNameIfPresent(layout, bridgeRootPath2);
31792
32135
  if (existing) return existing;
31793
- const norm = path13.resolve(bridgeRootPath2);
32136
+ const norm = path19.resolve(bridgeRootPath2);
31794
32137
  const base = baseNameSafe(norm);
31795
32138
  const used = new Set(layout.launcherCwds.map((e) => e.dirName));
31796
32139
  let name = base;
@@ -31807,10 +32150,10 @@ function allocateDirNameForLauncherCwd(layout, bridgeRootPath2) {
31807
32150
  // src/worktrees/prepare-new-session-worktrees.ts
31808
32151
  async function prepareNewSessionWorktrees(options) {
31809
32152
  const { worktreesRootPath, bridgeRoot, sessionId, layout, log: log2 } = options;
31810
- const bridgeResolved = path14.resolve(bridgeRoot);
32153
+ const bridgeResolved = path20.resolve(bridgeRoot);
31811
32154
  const cwdKey = allocateDirNameForLauncherCwd(layout, bridgeResolved);
31812
- const bridgeKeyDir = path14.join(worktreesRootPath, cwdKey);
31813
- const sessionDir = path14.join(bridgeKeyDir, sessionId);
32155
+ const bridgeKeyDir = path20.join(worktreesRootPath, cwdKey);
32156
+ const sessionDir = path20.join(bridgeKeyDir, sessionId);
31814
32157
  const repos = await discoverGitReposUnderRoot(bridgeResolved);
31815
32158
  if (repos.length === 0) {
31816
32159
  log2("[worktrees] No Git repositories under bridge root; skipping worktree creation.");
@@ -31818,14 +32161,14 @@ async function prepareNewSessionWorktrees(options) {
31818
32161
  }
31819
32162
  const branch = `session-${sessionId}`;
31820
32163
  const worktreePaths = [];
31821
- fs12.mkdirSync(sessionDir, { recursive: true });
32164
+ fs17.mkdirSync(sessionDir, { recursive: true });
31822
32165
  for (const repo of repos) {
31823
- let rel = path14.relative(bridgeResolved, repo.absolutePath);
31824
- if (rel.startsWith("..") || path14.isAbsolute(rel)) continue;
32166
+ let rel = path20.relative(bridgeResolved, repo.absolutePath);
32167
+ if (rel.startsWith("..") || path20.isAbsolute(rel)) continue;
31825
32168
  const relNorm = rel === "" ? "." : rel;
31826
- const wtPath = relNorm === "." ? sessionDir : path14.join(sessionDir, relNorm);
32169
+ const wtPath = relNorm === "." ? sessionDir : path20.join(sessionDir, relNorm);
31827
32170
  if (relNorm !== ".") {
31828
- fs12.mkdirSync(path14.dirname(wtPath), { recursive: true });
32171
+ fs17.mkdirSync(path20.dirname(wtPath), { recursive: true });
31829
32172
  }
31830
32173
  try {
31831
32174
  await gitWorktreeAddBranch(repo.absolutePath, wtPath, branch);
@@ -31867,23 +32210,23 @@ async function renameSessionWorktreeBranches(paths, newBranch, log2) {
31867
32210
  }
31868
32211
 
31869
32212
  // src/worktrees/remove-session-worktrees.ts
31870
- import * as fs15 from "node:fs";
32213
+ import * as fs20 from "node:fs";
31871
32214
 
31872
32215
  // src/git/worktree-remove.ts
31873
- import * as fs14 from "node:fs";
32216
+ import * as fs19 from "node:fs";
31874
32217
 
31875
32218
  // src/git/resolve-main-repo-from-git-file.ts
31876
- import * as fs13 from "node:fs";
31877
- import * as path15 from "node:path";
32219
+ import * as fs18 from "node:fs";
32220
+ import * as path21 from "node:path";
31878
32221
  function resolveMainRepoFromWorktreeGitFile(wt) {
31879
- const gitDirFile = path15.join(wt, ".git");
31880
- if (!fs13.existsSync(gitDirFile) || !fs13.statSync(gitDirFile).isFile()) return "";
31881
- const first2 = fs13.readFileSync(gitDirFile, "utf8").trim();
32222
+ const gitDirFile = path21.join(wt, ".git");
32223
+ if (!fs18.existsSync(gitDirFile) || !fs18.statSync(gitDirFile).isFile()) return "";
32224
+ const first2 = fs18.readFileSync(gitDirFile, "utf8").trim();
31882
32225
  const m = first2.match(/^gitdir:\s*(.+)$/im);
31883
32226
  if (!m) return "";
31884
- const gitWorktreePath = path15.resolve(wt, m[1].trim());
31885
- const gitDir = path15.dirname(path15.dirname(gitWorktreePath));
31886
- return path15.dirname(gitDir);
32227
+ const gitWorktreePath = path21.resolve(wt, m[1].trim());
32228
+ const gitDir = path21.dirname(path21.dirname(gitWorktreePath));
32229
+ return path21.dirname(gitDir);
31887
32230
  }
31888
32231
 
31889
32232
  // src/git/worktree-remove.ts
@@ -31892,7 +32235,7 @@ async function gitWorktreeRemoveForce(worktreePath) {
31892
32235
  if (mainRepo) {
31893
32236
  await cliSimpleGit(mainRepo).raw(["worktree", "remove", "--force", worktreePath]);
31894
32237
  } else {
31895
- fs14.rmSync(worktreePath, { recursive: true, force: true });
32238
+ fs19.rmSync(worktreePath, { recursive: true, force: true });
31896
32239
  }
31897
32240
  }
31898
32241
 
@@ -31905,7 +32248,7 @@ async function removeSessionWorktrees(paths, log2) {
31905
32248
  } catch (e) {
31906
32249
  log2(`[worktrees] Remove failed for ${wt}: ${e instanceof Error ? e.message : String(e)}`);
31907
32250
  try {
31908
- fs15.rmSync(wt, { recursive: true, force: true });
32251
+ fs20.rmSync(wt, { recursive: true, force: true });
31909
32252
  } catch {
31910
32253
  }
31911
32254
  }
@@ -32125,7 +32468,7 @@ function formatRemoteDisplayLabel(remoteUrl) {
32125
32468
  }
32126
32469
 
32127
32470
  // src/git/working-directory/changes/get-working-tree-change-repo-details.ts
32128
- import * as path17 from "node:path";
32471
+ import * as path23 from "node:path";
32129
32472
 
32130
32473
  // src/git/working-directory/changes/parse-git-status.ts
32131
32474
  function parseNameStatusLines(lines) {
@@ -32245,8 +32588,8 @@ async function listChangedFilesForCommit(repoGitCwd, repoRelPath, commitSha) {
32245
32588
  }
32246
32589
 
32247
32590
  // src/git/working-directory/changes/list-changed-files-for-repo.ts
32248
- import * as fs17 from "node:fs";
32249
- import * as path16 from "node:path";
32591
+ import * as fs22 from "node:fs";
32592
+ import * as path22 from "node:path";
32250
32593
 
32251
32594
  // src/git/working-directory/changes/count-lines.ts
32252
32595
  import { createReadStream } from "node:fs";
@@ -32270,7 +32613,7 @@ async function countTextFileLines(filePath) {
32270
32613
  }
32271
32614
 
32272
32615
  // src/git/working-directory/changes/hydrate-patch.ts
32273
- import * as fs16 from "node:fs";
32616
+ import * as fs21 from "node:fs";
32274
32617
  var UNIFIED_HUNK_HEADER_RE = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
32275
32618
  var MAX_HYDRATE_LINES_PER_GAP = 8e3;
32276
32619
  var MAX_HYDRATE_LINES_PER_FILE = 8e4;
@@ -32285,7 +32628,7 @@ async function readGitBlobLines(repoCwd, pathInRepo) {
32285
32628
  }
32286
32629
  async function readWorktreeFileLines(filePath) {
32287
32630
  try {
32288
- const raw = await fs16.promises.readFile(filePath, "utf8");
32631
+ const raw = await fs21.promises.readFile(filePath, "utf8");
32289
32632
  return raw.split(/\r?\n/);
32290
32633
  } catch {
32291
32634
  return null;
@@ -32420,7 +32763,7 @@ async function listChangedFilesForRepo(repoGitCwd, repoRelPath) {
32420
32763
  const rows = [];
32421
32764
  for (const pathInRepo of paths) {
32422
32765
  const relLauncher = posixJoinDirFile(repoRelPath, pathInRepo.replace(/\\/g, "/"));
32423
- const repoFilePath = path16.join(repoGitCwd, pathInRepo);
32766
+ const repoFilePath = path22.join(repoGitCwd, pathInRepo);
32424
32767
  const nums = numByPath.get(pathInRepo);
32425
32768
  let additions = nums?.additions ?? 0;
32426
32769
  let deletions = nums?.deletions ?? 0;
@@ -32433,7 +32776,7 @@ async function listChangedFilesForRepo(repoGitCwd, repoRelPath) {
32433
32776
  deletions = fromGit.deletions;
32434
32777
  } else {
32435
32778
  try {
32436
- const st = await fs17.promises.stat(repoFilePath);
32779
+ const st = await fs22.promises.stat(repoFilePath);
32437
32780
  if (st.isFile()) additions = await countTextFileLines(repoFilePath);
32438
32781
  else additions = 0;
32439
32782
  } catch {
@@ -32459,7 +32802,7 @@ async function listChangedFilesForRepo(repoGitCwd, repoRelPath) {
32459
32802
  } else {
32460
32803
  pathInRepo = row.pathRelLauncher;
32461
32804
  }
32462
- const filePath = path16.join(repoGitCwd, pathInRepo);
32805
+ const filePath = path22.join(repoGitCwd, pathInRepo);
32463
32806
  let patch = await unifiedDiffForFile(repoGitCwd, pathInRepo, row.change);
32464
32807
  if (patch) {
32465
32808
  patch = await hydrateUnifiedPatchWithFileContext(patch, filePath, repoGitCwd, pathInRepo, row.change);
@@ -32475,8 +32818,8 @@ function normRepoRel(p) {
32475
32818
  return x === "" ? "." : x;
32476
32819
  }
32477
32820
  async function getWorkingTreeChangeRepoDetails(options) {
32478
- const bridgeRoot = path17.resolve(getBridgeRoot());
32479
- const sessionWtRoot = options.sessionWorktreeRootPath ? path17.resolve(options.sessionWorktreeRootPath) : null;
32821
+ const bridgeRoot = path23.resolve(getBridgeRoot());
32822
+ const sessionWtRoot = options.sessionWorktreeRootPath ? path23.resolve(options.sessionWorktreeRootPath) : null;
32480
32823
  const legacyNested = options.legacyRepoNestedSessionLayout === true;
32481
32824
  const out = [];
32482
32825
  const filter = options.repoFilterRelPath != null ? normRepoRel(options.repoFilterRelPath) : null;
@@ -32489,7 +32832,7 @@ async function getWorkingTreeChangeRepoDetails(options) {
32489
32832
  }
32490
32833
  const basis = filter == null && basisInput.kind === "commit" ? { kind: "working" } : basisInput;
32491
32834
  for (const target of options.commitTargetPaths) {
32492
- const t = path17.resolve(target);
32835
+ const t = path23.resolve(target);
32493
32836
  if (!await isGitRepoDirectory(t)) continue;
32494
32837
  const g = cliSimpleGit(t);
32495
32838
  let branch = "HEAD";
@@ -32502,8 +32845,8 @@ async function getWorkingTreeChangeRepoDetails(options) {
32502
32845
  const remoteDisplay = formatRemoteDisplayLabel(remoteUrl);
32503
32846
  let repoRelPath;
32504
32847
  if (sessionWtRoot) {
32505
- const anchor = legacyNested ? path17.dirname(t) : t;
32506
- const relNorm = path17.relative(sessionWtRoot, anchor);
32848
+ const anchor = legacyNested ? path23.dirname(t) : t;
32849
+ const relNorm = path23.relative(sessionWtRoot, anchor);
32507
32850
  repoRelPath = relNorm === "" ? "." : relNorm.replace(/\\/g, "/");
32508
32851
  } else {
32509
32852
  let top = t;
@@ -32512,8 +32855,8 @@ async function getWorkingTreeChangeRepoDetails(options) {
32512
32855
  } catch {
32513
32856
  top = t;
32514
32857
  }
32515
- const rel = path17.relative(bridgeRoot, path17.resolve(top)).replace(/\\/g, "/") || ".";
32516
- repoRelPath = rel.startsWith("..") ? path17.basename(path17.resolve(top)) : rel;
32858
+ const rel = path23.relative(bridgeRoot, path23.resolve(top)).replace(/\\/g, "/") || ".";
32859
+ repoRelPath = rel.startsWith("..") ? path23.basename(path23.resolve(top)) : rel;
32517
32860
  }
32518
32861
  const norm = normRepoRel(repoRelPath === "" ? "." : repoRelPath);
32519
32862
  if (filter && norm !== filter) continue;
@@ -32578,11 +32921,11 @@ async function commitSessionWorktrees(options) {
32578
32921
  }
32579
32922
 
32580
32923
  // src/worktrees/discover-session-worktree-on-disk.ts
32581
- import * as fs18 from "node:fs";
32582
- import * as path18 from "node:path";
32924
+ import * as fs23 from "node:fs";
32925
+ import * as path24 from "node:path";
32583
32926
  function isGitDir(dirPath) {
32584
32927
  try {
32585
- return fs18.existsSync(path18.join(dirPath, ".git"));
32928
+ return fs23.existsSync(path24.join(dirPath, ".git"));
32586
32929
  } catch {
32587
32930
  return false;
32588
32931
  }
@@ -32591,23 +32934,23 @@ function collectGitRepoRootsUnderDirectory(rootPath) {
32591
32934
  const out = [];
32592
32935
  const walk = (dir) => {
32593
32936
  if (isGitDir(dir)) {
32594
- out.push(path18.resolve(dir));
32937
+ out.push(path24.resolve(dir));
32595
32938
  return;
32596
32939
  }
32597
32940
  let entries;
32598
32941
  try {
32599
- entries = fs18.readdirSync(dir, { withFileTypes: true });
32942
+ entries = fs23.readdirSync(dir, { withFileTypes: true });
32600
32943
  } catch {
32601
32944
  return;
32602
32945
  }
32603
32946
  for (const e of entries) {
32604
32947
  if (e.name.startsWith(".")) continue;
32605
- const full = path18.join(dir, e.name);
32948
+ const full = path24.join(dir, e.name);
32606
32949
  if (!e.isDirectory()) continue;
32607
32950
  walk(full);
32608
32951
  }
32609
32952
  };
32610
- walk(path18.resolve(rootPath));
32953
+ walk(path24.resolve(rootPath));
32611
32954
  return [...new Set(out)];
32612
32955
  }
32613
32956
  function collectWorktreeRootsNamed(root, sessionId, maxDepth) {
@@ -32616,16 +32959,16 @@ function collectWorktreeRootsNamed(root, sessionId, maxDepth) {
32616
32959
  if (depth > maxDepth) return;
32617
32960
  let entries;
32618
32961
  try {
32619
- entries = fs18.readdirSync(dir, { withFileTypes: true });
32962
+ entries = fs23.readdirSync(dir, { withFileTypes: true });
32620
32963
  } catch {
32621
32964
  return;
32622
32965
  }
32623
32966
  for (const e of entries) {
32624
32967
  if (e.name.startsWith(".")) continue;
32625
- const full = path18.join(dir, e.name);
32968
+ const full = path24.join(dir, e.name);
32626
32969
  if (!e.isDirectory()) continue;
32627
32970
  if (e.name === sessionId) {
32628
- if (isGitDir(full)) out.push(path18.resolve(full));
32971
+ if (isGitDir(full)) out.push(path24.resolve(full));
32629
32972
  } else {
32630
32973
  walk(full, depth + 1);
32631
32974
  }
@@ -32637,14 +32980,14 @@ function collectWorktreeRootsNamed(root, sessionId, maxDepth) {
32637
32980
  function tryBindingFromSessionDirectory(sessionDir) {
32638
32981
  let st;
32639
32982
  try {
32640
- st = fs18.statSync(sessionDir);
32983
+ st = fs23.statSync(sessionDir);
32641
32984
  } catch {
32642
32985
  return null;
32643
32986
  }
32644
32987
  if (!st.isDirectory()) return null;
32645
32988
  const worktreePaths = collectGitRepoRootsUnderDirectory(sessionDir);
32646
32989
  if (worktreePaths.length === 0) return null;
32647
- const abs = path18.resolve(sessionDir);
32990
+ const abs = path24.resolve(sessionDir);
32648
32991
  return {
32649
32992
  sessionParentPath: abs,
32650
32993
  workingTreeRelRoot: abs,
@@ -32654,20 +32997,20 @@ function tryBindingFromSessionDirectory(sessionDir) {
32654
32997
  function discoverLegacyBindingAscendingFromCheckout(sessionId, checkoutPath) {
32655
32998
  const sid = sessionId.trim();
32656
32999
  if (!sid) return null;
32657
- const hintR = path18.resolve(checkoutPath);
33000
+ const hintR = path24.resolve(checkoutPath);
32658
33001
  let best = null;
32659
- let cur = path18.dirname(hintR);
33002
+ let cur = path24.dirname(hintR);
32660
33003
  for (let i = 0; i < 40; i++) {
32661
33004
  const paths = collectWorktreeRootsNamed(cur, sid, 24);
32662
- if (paths.some((p) => path18.resolve(p) === hintR)) {
32663
- const isolated = resolveIsolatedSessionParentPathFromCheckouts(paths) ?? path18.resolve(paths[0]);
33005
+ if (paths.some((p) => path24.resolve(p) === hintR)) {
33006
+ const isolated = resolveIsolatedSessionParentPathFromCheckouts(paths) ?? path24.resolve(paths[0]);
32664
33007
  best = {
32665
- sessionParentPath: path18.resolve(isolated),
32666
- workingTreeRelRoot: path18.resolve(cur),
32667
- repoCheckoutPaths: paths.map((p) => path18.resolve(p))
33008
+ sessionParentPath: path24.resolve(isolated),
33009
+ workingTreeRelRoot: path24.resolve(cur),
33010
+ repoCheckoutPaths: paths.map((p) => path24.resolve(p))
32668
33011
  };
32669
33012
  }
32670
- const next = path18.dirname(cur);
33013
+ const next = path24.dirname(cur);
32671
33014
  if (next === cur) break;
32672
33015
  cur = next;
32673
33016
  }
@@ -32675,33 +33018,33 @@ function discoverLegacyBindingAscendingFromCheckout(sessionId, checkoutPath) {
32675
33018
  }
32676
33019
  function discoverSessionWorktreeOnDisk(options) {
32677
33020
  const { sessionId, worktreesRootPath, layout, bridgeRoot } = options;
32678
- if (!sessionId.trim() || !fs18.existsSync(worktreesRootPath)) return null;
33021
+ if (!sessionId.trim() || !fs23.existsSync(worktreesRootPath)) return null;
32679
33022
  const preferredKey = getLauncherDirNameIfPresent(layout, bridgeRoot);
32680
33023
  const keys = [];
32681
33024
  if (preferredKey) keys.push(preferredKey);
32682
33025
  try {
32683
- for (const name of fs18.readdirSync(worktreesRootPath)) {
33026
+ for (const name of fs23.readdirSync(worktreesRootPath)) {
32684
33027
  if (name.startsWith(".")) continue;
32685
- const p = path18.join(worktreesRootPath, name);
32686
- if (!fs18.statSync(p).isDirectory()) continue;
33028
+ const p = path24.join(worktreesRootPath, name);
33029
+ if (!fs23.statSync(p).isDirectory()) continue;
32687
33030
  if (name !== preferredKey) keys.push(name);
32688
33031
  }
32689
33032
  } catch {
32690
33033
  return null;
32691
33034
  }
32692
33035
  for (const key of keys) {
32693
- const layoutRoot = path18.join(worktreesRootPath, key);
32694
- if (!fs18.existsSync(layoutRoot) || !fs18.statSync(layoutRoot).isDirectory()) continue;
32695
- const sessionDir = path18.join(layoutRoot, sessionId);
33036
+ const layoutRoot = path24.join(worktreesRootPath, key);
33037
+ if (!fs23.existsSync(layoutRoot) || !fs23.statSync(layoutRoot).isDirectory()) continue;
33038
+ const sessionDir = path24.join(layoutRoot, sessionId);
32696
33039
  const nested = tryBindingFromSessionDirectory(sessionDir);
32697
33040
  if (nested) return nested;
32698
33041
  const legacyPaths = collectWorktreeRootsNamed(layoutRoot, sessionId, 24);
32699
33042
  if (legacyPaths.length > 0) {
32700
- const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths) ?? path18.resolve(legacyPaths[0]);
33043
+ const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths) ?? path24.resolve(legacyPaths[0]);
32701
33044
  return {
32702
- sessionParentPath: path18.resolve(isolated),
32703
- workingTreeRelRoot: path18.resolve(layoutRoot),
32704
- repoCheckoutPaths: legacyPaths.map((p) => path18.resolve(p))
33045
+ sessionParentPath: path24.resolve(isolated),
33046
+ workingTreeRelRoot: path24.resolve(layoutRoot),
33047
+ repoCheckoutPaths: legacyPaths.map((p) => path24.resolve(p))
32705
33048
  };
32706
33049
  }
32707
33050
  }
@@ -32710,12 +33053,12 @@ function discoverSessionWorktreeOnDisk(options) {
32710
33053
  function discoverSessionWorktreesUnderSessionWorktreeRoot(sessionWorktreeRootPathOrHint, sessionId) {
32711
33054
  const sid = sessionId.trim();
32712
33055
  if (!sid) return null;
32713
- const hint = path18.resolve(sessionWorktreeRootPathOrHint);
32714
- const underHint = tryBindingFromSessionDirectory(path18.join(hint, sid));
33056
+ const hint = path24.resolve(sessionWorktreeRootPathOrHint);
33057
+ const underHint = tryBindingFromSessionDirectory(path24.join(hint, sid));
32715
33058
  if (underHint) return underHint;
32716
33059
  const direct = tryBindingFromSessionDirectory(hint);
32717
33060
  if (direct) {
32718
- if (path18.basename(hint) === sid && isGitDir(hint)) {
33061
+ if (path24.basename(hint) === sid && isGitDir(hint)) {
32719
33062
  const legacyFromCheckout = discoverLegacyBindingAscendingFromCheckout(sid, hint);
32720
33063
  if (legacyFromCheckout && legacyFromCheckout.repoCheckoutPaths.length > direct.repoCheckoutPaths.length) {
32721
33064
  return legacyFromCheckout;
@@ -32723,24 +33066,24 @@ function discoverSessionWorktreesUnderSessionWorktreeRoot(sessionWorktreeRootPat
32723
33066
  }
32724
33067
  return direct;
32725
33068
  }
32726
- if (path18.basename(hint) === sid && isGitDir(hint)) {
33069
+ if (path24.basename(hint) === sid && isGitDir(hint)) {
32727
33070
  const legacyFromCheckout = discoverLegacyBindingAscendingFromCheckout(sid, hint);
32728
33071
  if (legacyFromCheckout) return legacyFromCheckout;
32729
33072
  }
32730
33073
  let st;
32731
33074
  try {
32732
- st = fs18.statSync(hint);
33075
+ st = fs23.statSync(hint);
32733
33076
  } catch {
32734
33077
  return null;
32735
33078
  }
32736
33079
  if (!st.isDirectory()) return null;
32737
33080
  const legacyPaths = collectWorktreeRootsNamed(hint, sid, 24);
32738
33081
  if (legacyPaths.length === 0) return null;
32739
- const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths) ?? path18.resolve(legacyPaths[0]);
33082
+ const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths) ?? path24.resolve(legacyPaths[0]);
32740
33083
  return {
32741
- sessionParentPath: path18.resolve(isolated),
33084
+ sessionParentPath: path24.resolve(isolated),
32742
33085
  workingTreeRelRoot: hint,
32743
- repoCheckoutPaths: legacyPaths.map((p) => path18.resolve(p))
33086
+ repoCheckoutPaths: legacyPaths.map((p) => path24.resolve(p))
32744
33087
  };
32745
33088
  }
32746
33089
 
@@ -32763,10 +33106,10 @@ var SessionWorktreeManager = class {
32763
33106
  this.layout = loadWorktreeLayout();
32764
33107
  }
32765
33108
  rememberSessionWorktrees(sessionId, binding) {
32766
- const paths = binding.repoCheckoutPaths.map((p) => path19.resolve(p));
33109
+ const paths = binding.repoCheckoutPaths.map((p) => path25.resolve(p));
32767
33110
  this.sessionRepoCheckoutPaths.set(sessionId, paths);
32768
- this.sessionParentPathBySession.set(sessionId, path19.resolve(binding.sessionParentPath));
32769
- this.sessionWorkingTreeRelRootBySession.set(sessionId, path19.resolve(binding.workingTreeRelRoot));
33111
+ this.sessionParentPathBySession.set(sessionId, path25.resolve(binding.sessionParentPath));
33112
+ this.sessionWorkingTreeRelRootBySession.set(sessionId, path25.resolve(binding.workingTreeRelRoot));
32770
33113
  }
32771
33114
  sessionParentPathAfterRemember(sessionId) {
32772
33115
  return this.sessionParentPathBySession.get(sessionId);
@@ -32783,7 +33126,7 @@ var SessionWorktreeManager = class {
32783
33126
  const parent = this.sessionParentPathBySession.get(sessionId);
32784
33127
  const relRoot = this.sessionWorkingTreeRelRootBySession.get(sessionId);
32785
33128
  if (!parent || !relRoot) return false;
32786
- return path19.resolve(parent) !== path19.resolve(relRoot);
33129
+ return path25.resolve(parent) !== path25.resolve(relRoot);
32787
33130
  }
32788
33131
  /**
32789
33132
  * Session parent path for `worktrees_root`: the per-session directory (new layout) or primary checkout (legacy).
@@ -32792,7 +33135,7 @@ var SessionWorktreeManager = class {
32792
33135
  if (!sessionId) return null;
32793
33136
  const sid = sessionId.trim();
32794
33137
  const cached2 = this.sessionParentPathBySession.get(sid);
32795
- if (cached2) return path19.resolve(cached2);
33138
+ if (cached2) return path25.resolve(cached2);
32796
33139
  const paths = this.ensureRepoCheckoutPathsForSession(sid) ?? this.getRepoCheckoutPathsForSession(sid);
32797
33140
  if (!paths?.length) return null;
32798
33141
  return resolveIsolatedSessionParentPathFromCheckouts(paths);
@@ -32806,7 +33149,7 @@ var SessionWorktreeManager = class {
32806
33149
  const sid = sessionId.trim();
32807
33150
  const parentPathRaw = opts.sessionParentPath?.trim();
32808
33151
  if (parentPathRaw) {
32809
- const resolved = path19.resolve(parentPathRaw);
33152
+ const resolved = path25.resolve(parentPathRaw);
32810
33153
  if (sid && parseSessionParent(opts.sessionParent) === "worktrees_root") {
32811
33154
  const diskFirst = this.tryDiscoverFromDisk(sid);
32812
33155
  if (diskFirst) {
@@ -32825,7 +33168,7 @@ var SessionWorktreeManager = class {
32825
33168
  this.rememberSessionWorktrees(sid, tryRoot);
32826
33169
  return this.sessionParentPathAfterRemember(sid);
32827
33170
  }
32828
- const next = path19.dirname(cur);
33171
+ const next = path25.dirname(cur);
32829
33172
  if (next === cur) break;
32830
33173
  cur = next;
32831
33174
  }
@@ -32978,15 +33321,22 @@ var SessionWorktreeManager = class {
32978
33321
  }
32979
33322
  };
32980
33323
  function defaultWorktreesRootPath() {
32981
- return path19.join(os5.homedir(), ".buildautomaton", "worktrees");
33324
+ return path25.join(os8.homedir(), ".buildautomaton", "worktrees");
32982
33325
  }
32983
33326
 
32984
33327
  // src/files/watch-file-index.ts
32985
33328
  import { watch } from "node:fs";
33329
+ import path30 from "node:path";
33330
+
33331
+ // src/files/index/paths.ts
32986
33332
  import path26 from "node:path";
33333
+ import crypto2 from "node:crypto";
33334
+ function getCwdHashForFileIndex(resolvedCwd) {
33335
+ return crypto2.createHash("sha256").update(path26.resolve(resolvedCwd)).digest("hex").slice(0, INDEX_HASH_LEN);
33336
+ }
32987
33337
 
32988
33338
  // src/files/index/build-file-index.ts
32989
- import path23 from "node:path";
33339
+ import path28 from "node:path";
32990
33340
 
32991
33341
  // src/runtime/yield-to-event-loop.ts
32992
33342
  function yieldToEventLoop() {
@@ -32994,47 +33344,12 @@ function yieldToEventLoop() {
32994
33344
  }
32995
33345
 
32996
33346
  // src/files/index/walk-workspace-tree.ts
32997
- import fs19 from "node:fs";
32998
- import path21 from "node:path";
32999
-
33000
- // src/files/index/constants.ts
33001
- import path20 from "node:path";
33002
- import os6 from "node:os";
33003
- var INDEX_WORK_YIELD_EVERY = 256;
33004
- var INDEX_DIR = path20.join(os6.homedir(), ".buildautomaton");
33005
- var INDEX_HASH_LEN = 16;
33006
- var INDEX_VERSION = 2;
33007
- var INDEX_LOG_PREFIX = "[file-index]";
33008
-
33009
- // src/files/index/walk-workspace-tree.ts
33010
- function walkWorkspaceTreeSync(dir, baseDir, out) {
33011
- let names;
33012
- try {
33013
- names = fs19.readdirSync(dir);
33014
- } catch {
33015
- return;
33016
- }
33017
- for (const name of names) {
33018
- if (name.startsWith(".")) continue;
33019
- const full = path21.join(dir, name);
33020
- let stat2;
33021
- try {
33022
- stat2 = fs19.statSync(full);
33023
- } catch {
33024
- continue;
33025
- }
33026
- const relative5 = path21.relative(baseDir, full).replace(/\\/g, "/");
33027
- if (stat2.isDirectory()) {
33028
- walkWorkspaceTreeSync(full, baseDir, out);
33029
- } else if (stat2.isFile()) {
33030
- out.push(relative5);
33031
- }
33032
- }
33033
- }
33347
+ import fs24 from "node:fs";
33348
+ import path27 from "node:path";
33034
33349
  async function walkWorkspaceTreeAsync(dir, baseDir, out, state) {
33035
33350
  let names;
33036
33351
  try {
33037
- names = await fs19.promises.readdir(dir);
33352
+ names = await fs24.promises.readdir(dir);
33038
33353
  } catch {
33039
33354
  return;
33040
33355
  }
@@ -33044,14 +33359,14 @@ async function walkWorkspaceTreeAsync(dir, baseDir, out, state) {
33044
33359
  await yieldToEventLoop();
33045
33360
  }
33046
33361
  state.n++;
33047
- const full = path21.join(dir, name);
33362
+ const full = path27.join(dir, name);
33048
33363
  let stat2;
33049
33364
  try {
33050
- stat2 = await fs19.promises.stat(full);
33365
+ stat2 = await fs24.promises.stat(full);
33051
33366
  } catch {
33052
33367
  continue;
33053
33368
  }
33054
- const relative5 = path21.relative(baseDir, full).replace(/\\/g, "/");
33369
+ const relative5 = path27.relative(baseDir, full).replace(/\\/g, "/");
33055
33370
  if (stat2.isDirectory()) {
33056
33371
  await walkWorkspaceTreeAsync(full, baseDir, out, state);
33057
33372
  } else if (stat2.isFile()) {
@@ -33063,205 +33378,124 @@ function createWalkYieldState() {
33063
33378
  return { n: 0 };
33064
33379
  }
33065
33380
 
33066
- // src/files/index/trigram-utils.ts
33067
- function getTrigrams(s) {
33068
- const lower = s.toLowerCase();
33069
- const out = [];
33070
- for (let i = 0; i <= lower.length - 3; i++) {
33071
- out.push(lower.slice(i, i + 3));
33072
- }
33073
- return out;
33074
- }
33075
- function binarySearch(arr, x) {
33076
- let lo = 0;
33077
- let hi = arr.length - 1;
33078
- while (lo <= hi) {
33079
- const mid = lo + hi >>> 1;
33080
- if (arr[mid] < x) lo = mid + 1;
33081
- else if (arr[mid] > x) hi = mid - 1;
33082
- else return mid;
33083
- }
33084
- return -1;
33085
- }
33086
- function intersectSortedTrigramSets(arrays) {
33087
- if (arrays.length === 0) return [];
33088
- if (arrays.length === 1) return arrays[0];
33089
- const byLength = arrays.slice().sort((a, b) => a.length - b.length);
33090
- const smallest = byLength[0];
33091
- const rest = byLength.slice(1);
33092
- const result = [];
33093
- for (const idx of smallest) {
33094
- if (rest.every((arr) => binarySearch(arr, idx) >= 0)) {
33095
- result.push(idx);
33096
- }
33097
- }
33098
- return result;
33099
- }
33100
-
33101
- // src/files/index/build-trigram-map.ts
33102
- function buildTrigramMapForPaths(paths) {
33103
- const trigramIndex = {};
33104
- for (let i = 0; i < paths.length; i++) {
33105
- const trigrams = getTrigrams(paths[i]);
33106
- const seen = /* @__PURE__ */ new Set();
33107
- for (const tri of trigrams) {
33108
- if (seen.has(tri)) continue;
33109
- seen.add(tri);
33110
- if (!trigramIndex[tri]) trigramIndex[tri] = [];
33111
- trigramIndex[tri].push(i);
33112
- }
33113
- }
33114
- return trigramIndex;
33381
+ // src/files/index/file-index-sqlite-lock.ts
33382
+ import fs25 from "node:fs";
33383
+ function isSqliteCorruptError(e) {
33384
+ const msg = e instanceof Error ? e.message : String(e);
33385
+ return msg.includes("malformed") || msg.includes("database disk image is malformed") || msg.includes("corrupt");
33115
33386
  }
33116
- async function buildTrigramMapForPathsAsync(paths) {
33117
- const trigramIndex = {};
33118
- for (let i = 0; i < paths.length; i++) {
33119
- if (i > 0 && i % INDEX_WORK_YIELD_EVERY === 0) {
33120
- await yieldToEventLoop();
33121
- }
33122
- const trigrams = getTrigrams(paths[i]);
33123
- const seen = /* @__PURE__ */ new Set();
33124
- for (const tri of trigrams) {
33125
- if (seen.has(tri)) continue;
33126
- seen.add(tri);
33127
- if (!trigramIndex[tri]) trigramIndex[tri] = [];
33128
- trigramIndex[tri].push(i);
33387
+ var chain = Promise.resolve();
33388
+ function withFileIndexSqliteLock(fn) {
33389
+ const run = async () => {
33390
+ try {
33391
+ return await Promise.resolve(fn());
33392
+ } catch (e) {
33393
+ if (!isSqliteCorruptError(e)) throw e;
33394
+ closeAllCliSqliteConnections();
33395
+ try {
33396
+ fs25.unlinkSync(getCliSqlitePath());
33397
+ } catch {
33398
+ }
33399
+ chain = Promise.resolve();
33400
+ return await Promise.resolve(fn());
33129
33401
  }
33130
- }
33131
- return trigramIndex;
33132
- }
33133
-
33134
- // src/files/index/write-index-file.ts
33135
- import fs20 from "node:fs";
33136
-
33137
- // src/files/index/paths.ts
33138
- import path22 from "node:path";
33139
- import crypto2 from "node:crypto";
33140
- function getIndexPathForCwd(resolvedCwd) {
33141
- const hash = crypto2.createHash("sha256").update(resolvedCwd).digest("hex").slice(0, INDEX_HASH_LEN);
33142
- return path22.join(INDEX_DIR, `.file-index-${hash}.json`);
33143
- }
33144
-
33145
- // src/files/index/write-index-file.ts
33146
- function writeIndexFileSync(resolvedCwd, data) {
33147
- const indexPath = getIndexPathForCwd(resolvedCwd);
33148
- try {
33149
- if (!fs20.existsSync(INDEX_DIR)) fs20.mkdirSync(INDEX_DIR, { recursive: true });
33150
- fs20.writeFileSync(indexPath, JSON.stringify(data), "utf8");
33151
- } catch (e) {
33152
- console.error(`${INDEX_LOG_PREFIX} Failed to write index:`, e);
33153
- }
33154
- }
33155
- async function writeIndexFileAsync(resolvedCwd, data) {
33156
- const indexPath = getIndexPathForCwd(resolvedCwd);
33157
- try {
33158
- await fs20.promises.mkdir(INDEX_DIR, { recursive: true });
33159
- await fs20.promises.writeFile(indexPath, JSON.stringify(data), "utf8");
33160
- } catch (e) {
33161
- console.error(`${INDEX_LOG_PREFIX} Failed to write index:`, e);
33162
- }
33163
- }
33164
- function makeTrigramIndexData(paths, trigramIndex) {
33165
- return { version: INDEX_VERSION, paths, trigramIndex };
33402
+ };
33403
+ const next = chain.then(run);
33404
+ chain = next.then(
33405
+ () => void 0,
33406
+ () => void 0
33407
+ );
33408
+ return next;
33166
33409
  }
33167
33410
 
33168
33411
  // src/files/index/build-file-index.ts
33169
33412
  function sortPaths(paths) {
33170
33413
  paths.sort((a, b) => a.localeCompare(b, void 0, { sensitivity: "base" }));
33171
33414
  }
33172
- function buildFileIndex(cwd) {
33173
- const resolved = path23.resolve(cwd);
33174
- const paths = [];
33175
- walkWorkspaceTreeSync(resolved, resolved, paths);
33176
- sortPaths(paths);
33177
- const trigramIndex = buildTrigramMapForPaths(paths);
33178
- const data = makeTrigramIndexData(paths, trigramIndex);
33179
- writeIndexFileSync(resolved, data);
33180
- return data;
33181
- }
33182
- async function buildFileIndexAsync(cwd) {
33183
- const resolved = path23.resolve(cwd);
33184
- const paths = [];
33185
- await walkWorkspaceTreeAsync(resolved, resolved, paths, createWalkYieldState());
33186
- await yieldToEventLoop();
33187
- sortPaths(paths);
33188
- const trigramIndex = await buildTrigramMapForPathsAsync(paths);
33189
- const data = makeTrigramIndexData(paths, trigramIndex);
33190
- await writeIndexFileAsync(resolved, data);
33191
- return data;
33192
- }
33193
-
33194
- // src/files/index/load-file-index.ts
33195
- import fs21 from "node:fs";
33196
- import path24 from "node:path";
33197
- function loadFileIndex(cwd) {
33198
- const resolved = path24.resolve(cwd);
33199
- const indexPath = getIndexPathForCwd(resolved);
33415
+ function persistPathsToSqlite(resolved, paths) {
33416
+ const db = getCliDatabase();
33417
+ const h = getCwdHashForFileIndex(resolved);
33418
+ db.run("BEGIN IMMEDIATE");
33200
33419
  try {
33201
- const raw = fs21.readFileSync(indexPath, "utf8");
33202
- const parsed = JSON.parse(raw);
33203
- if (parsed !== null && typeof parsed === "object" && Array.isArray(parsed.paths)) {
33204
- const obj = parsed;
33205
- if (obj.version === INDEX_VERSION && obj.trigramIndex && typeof obj.trigramIndex === "object") {
33206
- return obj;
33420
+ db.run("DELETE FROM file_index_path WHERE cwd_hash = ?", [h]);
33421
+ const ins = db.prepare("INSERT INTO file_index_path (cwd_hash, path) VALUES (?, ?)");
33422
+ try {
33423
+ for (const rel of paths) {
33424
+ ins.run([h, rel]);
33207
33425
  }
33208
- return buildFileIndex(resolved);
33426
+ } finally {
33427
+ ins.finalize();
33209
33428
  }
33210
- if (Array.isArray(parsed) && parsed.every((p) => typeof p === "string")) {
33211
- return buildFileIndex(resolved);
33429
+ db.run("COMMIT");
33430
+ } catch (e) {
33431
+ try {
33432
+ db.run("ROLLBACK");
33433
+ } catch {
33212
33434
  }
33213
- return null;
33214
- } catch {
33215
- return null;
33435
+ throw e;
33216
33436
  }
33217
33437
  }
33438
+ async function buildFileIndexAsync(cwd) {
33439
+ return withFileIndexSqliteLock(async () => {
33440
+ const resolved = path28.resolve(cwd);
33441
+ const paths = [];
33442
+ await walkWorkspaceTreeAsync(resolved, resolved, paths, createWalkYieldState());
33443
+ await yieldToEventLoop();
33444
+ sortPaths(paths);
33445
+ persistPathsToSqlite(resolved, paths);
33446
+ return { pathCount: paths.length };
33447
+ });
33448
+ }
33218
33449
 
33219
33450
  // src/files/index/ensure-file-index.ts
33220
- import path25 from "node:path";
33221
- async function ensureFileIndexAsync(cwd) {
33222
- const resolved = path25.resolve(cwd);
33223
- const cached2 = loadFileIndex(resolved);
33224
- if (cached2 !== null) return { data: cached2, fromCache: true };
33225
- const data = await buildFileIndexAsync(resolved);
33226
- return { data, fromCache: false };
33227
- }
33451
+ import path29 from "node:path";
33228
33452
 
33229
33453
  // src/files/index/search-file-index.ts
33230
- function candidatePathIndices(index, q) {
33231
- const { paths, trigramIndex } = index;
33232
- if (q.length < 3) {
33233
- return paths.map((_, i) => i).filter((i) => paths[i].toLowerCase().includes(q));
33234
- }
33235
- const trigrams = getTrigrams(q);
33236
- if (trigrams.length === 0) {
33237
- return paths.map((_, i) => i).filter((i) => paths[i].toLowerCase().includes(q));
33238
- }
33239
- const arrays = trigrams.map((tri) => trigramIndex[tri]).filter((arr) => arr != null && arr.length > 0);
33240
- if (arrays.length === 0) return [];
33241
- return intersectSortedTrigramSets(arrays);
33242
- }
33243
- async function searchFileIndexAsync(index, query, limit = 100) {
33244
- await yieldToEventLoop();
33454
+ function escapeLikePattern(fragment) {
33455
+ return fragment.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
33456
+ }
33457
+ function bridgeFileIndexIsPopulated(resolvedCwd) {
33458
+ const db = getCliDatabase();
33459
+ const h = getCwdHashForFileIndex(resolvedCwd);
33460
+ const row = db.get("SELECT 1 as ok FROM file_index_path WHERE cwd_hash = ? LIMIT 1", [h]);
33461
+ return row != null;
33462
+ }
33463
+ function bridgeFileIndexPathCount(resolvedCwd) {
33464
+ const db = getCliDatabase();
33465
+ const h = getCwdHashForFileIndex(resolvedCwd);
33466
+ const row = db.get("SELECT COUNT(*) as c FROM file_index_path WHERE cwd_hash = ?", [h]);
33467
+ const c = row?.c ?? 0;
33468
+ return Number(c);
33469
+ }
33470
+ function searchBridgeFilePaths(resolvedCwd, query, limit = 100) {
33245
33471
  const q = query.trim().toLowerCase();
33246
33472
  if (!q) return [];
33247
- const { paths } = index;
33248
- const candidateIndices = candidatePathIndices(index, q);
33249
- const out = [];
33250
- let n = 0;
33251
- for (const i of candidateIndices) {
33252
- if (n > 0 && n % INDEX_WORK_YIELD_EVERY === 0) {
33253
- await yieldToEventLoop();
33254
- }
33255
- const p = paths[i];
33256
- if (p.toLowerCase().includes(q)) {
33257
- out.push(p);
33258
- if (out.length >= limit) break;
33259
- }
33260
- n++;
33261
- }
33473
+ const db = getCliDatabase();
33474
+ const h = getCwdHashForFileIndex(resolvedCwd);
33475
+ const pattern = `%${escapeLikePattern(q)}%`;
33476
+ const lim = Math.max(0, Math.min(1e4, Math.floor(limit)));
33477
+ const rows = db.all(
33478
+ `SELECT path FROM file_index_path WHERE cwd_hash = ? AND lower(path) LIKE ? ESCAPE '\\' LIMIT ?`,
33479
+ [h, pattern, lim]
33480
+ );
33481
+ return rows.map((r) => String(r.path));
33482
+ }
33483
+ async function searchBridgeFilePathsAsync(resolvedCwd, query, limit = 100) {
33484
+ await yieldToEventLoop();
33485
+ const out = searchBridgeFilePaths(resolvedCwd, query, limit);
33486
+ if (out.length >= INDEX_WORK_YIELD_EVERY) await yieldToEventLoop();
33262
33487
  return out;
33263
33488
  }
33264
33489
 
33490
+ // src/files/index/ensure-file-index.ts
33491
+ async function ensureFileIndexAsync(cwd) {
33492
+ const resolved = path29.resolve(cwd);
33493
+ if (bridgeFileIndexIsPopulated(resolved)) {
33494
+ return { fromCache: true, pathCount: bridgeFileIndexPathCount(resolved) };
33495
+ }
33496
+ return { ...await buildFileIndexAsync(resolved), fromCache: false };
33497
+ }
33498
+
33265
33499
  // src/files/watch-file-index.ts
33266
33500
  var DEBOUNCE_MS = 900;
33267
33501
  function shouldIgnoreRelative(rel) {
@@ -33302,7 +33536,7 @@ function createFsWatcher(resolved, schedule) {
33302
33536
  }
33303
33537
  }
33304
33538
  function startFileIndexWatcher(cwd = getBridgeRoot()) {
33305
- const resolved = path26.resolve(cwd);
33539
+ const resolved = path30.resolve(cwd);
33306
33540
  void buildFileIndexAsync(resolved).catch((e) => {
33307
33541
  console.error("[file-index] Initial index build failed:", e);
33308
33542
  });
@@ -33330,7 +33564,7 @@ function startFileIndexWatcher(cwd = getBridgeRoot()) {
33330
33564
  }
33331
33565
 
33332
33566
  // src/connection/create-bridge-connection.ts
33333
- import * as path34 from "node:path";
33567
+ import * as path38 from "node:path";
33334
33568
 
33335
33569
  // src/dev-servers/manager/dev-server-manager.ts
33336
33570
  import { rm as rm2 } from "node:fs/promises";
@@ -33374,7 +33608,7 @@ function forceKillChild(proc, log2, shortId, graceMs) {
33374
33608
  }
33375
33609
 
33376
33610
  // src/dev-servers/process/wire-dev-server-child-process.ts
33377
- import fs22 from "node:fs";
33611
+ import fs26 from "node:fs";
33378
33612
 
33379
33613
  // src/dev-servers/manager/forward-pipe.ts
33380
33614
  function forwardChildPipe(childReadable, terminal, onData) {
@@ -33410,7 +33644,7 @@ function wireDevServerChildProcess(d) {
33410
33644
  d.setPollInterval(void 0);
33411
33645
  return;
33412
33646
  }
33413
- fs22.readFile(d.mergedLogPath, (err, buf) => {
33647
+ fs26.readFile(d.mergedLogPath, (err, buf) => {
33414
33648
  if (err || (d.getSpawnGeneration() ?? 0) !== d.scheduledGen) return;
33415
33649
  if (buf.length <= d.mergedReadPos.value) return;
33416
33650
  const chunk = Buffer.from(buf.subarray(d.mergedReadPos.value));
@@ -33448,7 +33682,7 @@ ${errTail}` : ""}`);
33448
33682
  d.sendStatus(code === 0 || code == null ? "stopped" : "error", detail, tails);
33449
33683
  };
33450
33684
  if (mergedPath) {
33451
- fs22.readFile(mergedPath, (err, buf) => {
33685
+ fs26.readFile(mergedPath, (err, buf) => {
33452
33686
  if (!err && buf.length > d.mergedReadPos.value) {
33453
33687
  const chunk = Buffer.from(buf.subarray(d.mergedReadPos.value));
33454
33688
  if (chunk.length > 0) {
@@ -33550,13 +33784,13 @@ function parseDevServerDefs(servers) {
33550
33784
  }
33551
33785
 
33552
33786
  // src/dev-servers/manager/shell-spawn/utils.ts
33553
- import fs23 from "node:fs";
33787
+ import fs27 from "node:fs";
33554
33788
  function isSpawnEbadf(e) {
33555
33789
  return typeof e === "object" && e !== null && "code" in e && e.code === "EBADF";
33556
33790
  }
33557
33791
  function rmDirQuiet(dir) {
33558
33792
  try {
33559
- fs23.rmSync(dir, { recursive: true, force: true });
33793
+ fs27.rmSync(dir, { recursive: true, force: true });
33560
33794
  } catch {
33561
33795
  }
33562
33796
  }
@@ -33564,7 +33798,7 @@ var cachedDevNullReadFd;
33564
33798
  function devNullReadFd() {
33565
33799
  if (cachedDevNullReadFd === void 0) {
33566
33800
  const devPath = process.platform === "win32" ? "nul" : "/dev/null";
33567
- cachedDevNullReadFd = fs23.openSync(devPath, "r");
33801
+ cachedDevNullReadFd = fs27.openSync(devPath, "r");
33568
33802
  }
33569
33803
  return cachedDevNullReadFd;
33570
33804
  }
@@ -33638,15 +33872,15 @@ function trySpawnShellTruePiped(command, env, cwd, devNullFd, signal) {
33638
33872
 
33639
33873
  // src/dev-servers/manager/shell-spawn/try-spawn-merged-log-file.ts
33640
33874
  import { spawn as spawn7 } from "node:child_process";
33641
- import fs24 from "node:fs";
33875
+ import fs28 from "node:fs";
33642
33876
  import { tmpdir } from "node:os";
33643
- import path27 from "node:path";
33877
+ import path31 from "node:path";
33644
33878
  function trySpawnMergedLogFile(command, env, cwd, signal) {
33645
- const tmpRoot = fs24.mkdtempSync(path27.join(tmpdir(), "ba-devsrv-log-"));
33646
- const logPath = path27.join(tmpRoot, "combined.log");
33879
+ const tmpRoot = fs28.mkdtempSync(path31.join(tmpdir(), "ba-devsrv-log-"));
33880
+ const logPath = path31.join(tmpRoot, "combined.log");
33647
33881
  let logFd;
33648
33882
  try {
33649
- logFd = fs24.openSync(logPath, "a");
33883
+ logFd = fs28.openSync(logPath, "a");
33650
33884
  } catch {
33651
33885
  rmDirQuiet(tmpRoot);
33652
33886
  return null;
@@ -33665,7 +33899,7 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
33665
33899
  } else {
33666
33900
  proc = spawn7("/bin/sh", ["-c", command], { env, cwd, stdio, ...signal ? { signal } : {} });
33667
33901
  }
33668
- fs24.closeSync(logFd);
33902
+ fs28.closeSync(logFd);
33669
33903
  return {
33670
33904
  proc,
33671
33905
  pipedStdoutStderr: true,
@@ -33674,7 +33908,7 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
33674
33908
  };
33675
33909
  } catch (e) {
33676
33910
  try {
33677
- fs24.closeSync(logFd);
33911
+ fs28.closeSync(logFd);
33678
33912
  } catch {
33679
33913
  }
33680
33914
  rmDirQuiet(tmpRoot);
@@ -33685,22 +33919,22 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
33685
33919
 
33686
33920
  // src/dev-servers/manager/shell-spawn/try-spawn-shell-script-log-redirect.ts
33687
33921
  import { spawn as spawn8 } from "node:child_process";
33688
- import fs25 from "node:fs";
33922
+ import fs29 from "node:fs";
33689
33923
  import { tmpdir as tmpdir2 } from "node:os";
33690
- import path28 from "node:path";
33924
+ import path32 from "node:path";
33691
33925
  function shSingleQuote(s) {
33692
33926
  return `'${s.replace(/'/g, `'\\''`)}'`;
33693
33927
  }
33694
33928
  function trySpawnShellScriptLogRedirectUnix(command, env, cwd, signal) {
33695
- const tmpRoot = fs25.mkdtempSync(path28.join(tmpdir2(), "ba-devsrv-sh-"));
33696
- const logPath = path28.join(tmpRoot, "combined.log");
33697
- const innerPath = path28.join(tmpRoot, "_cmd.sh");
33698
- const runnerPath = path28.join(tmpRoot, "_run.sh");
33929
+ const tmpRoot = fs29.mkdtempSync(path32.join(tmpdir2(), "ba-devsrv-sh-"));
33930
+ const logPath = path32.join(tmpRoot, "combined.log");
33931
+ const innerPath = path32.join(tmpRoot, "_cmd.sh");
33932
+ const runnerPath = path32.join(tmpRoot, "_run.sh");
33699
33933
  try {
33700
- fs25.writeFileSync(innerPath, `#!/bin/sh
33934
+ fs29.writeFileSync(innerPath, `#!/bin/sh
33701
33935
  ${command}
33702
33936
  `);
33703
- fs25.writeFileSync(
33937
+ fs29.writeFileSync(
33704
33938
  runnerPath,
33705
33939
  `#!/bin/sh
33706
33940
  cd ${shSingleQuote(cwd)}
@@ -33726,13 +33960,13 @@ cd ${shSingleQuote(cwd)}
33726
33960
  }
33727
33961
  }
33728
33962
  function trySpawnShellScriptLogRedirectWin(command, env, cwd, signal) {
33729
- const tmpRoot = fs25.mkdtempSync(path28.join(tmpdir2(), "ba-devsrv-sh-"));
33730
- const logPath = path28.join(tmpRoot, "combined.log");
33731
- const runnerPath = path28.join(tmpRoot, "_run.bat");
33963
+ const tmpRoot = fs29.mkdtempSync(path32.join(tmpdir2(), "ba-devsrv-sh-"));
33964
+ const logPath = path32.join(tmpRoot, "combined.log");
33965
+ const runnerPath = path32.join(tmpRoot, "_run.bat");
33732
33966
  const q = (p) => `"${p.replace(/"/g, '""')}"`;
33733
33967
  const com = process.env.ComSpec || "cmd.exe";
33734
33968
  try {
33735
- fs25.writeFileSync(
33969
+ fs29.writeFileSync(
33736
33970
  runnerPath,
33737
33971
  `@ECHO OFF\r
33738
33972
  CD /D ${q(cwd)}\r
@@ -34501,30 +34735,30 @@ function createOnBridgeIdentified(opts) {
34501
34735
  }
34502
34736
 
34503
34737
  // src/skills/discover-local-agent-skills.ts
34504
- import fs26 from "node:fs";
34505
- import path29 from "node:path";
34738
+ import fs30 from "node:fs";
34739
+ import path33 from "node:path";
34506
34740
  var SKILL_DISCOVERY_ROOTS = [".agents/skills", ".claude/skills", ".cursor/skills", "skills"];
34507
34741
  function discoverLocalSkills(cwd) {
34508
34742
  const out = [];
34509
34743
  const seenKeys = /* @__PURE__ */ new Set();
34510
34744
  for (const rel of SKILL_DISCOVERY_ROOTS) {
34511
- const base = path29.join(cwd, rel);
34512
- if (!fs26.existsSync(base) || !fs26.statSync(base).isDirectory()) continue;
34745
+ const base = path33.join(cwd, rel);
34746
+ if (!fs30.existsSync(base) || !fs30.statSync(base).isDirectory()) continue;
34513
34747
  let entries = [];
34514
34748
  try {
34515
- entries = fs26.readdirSync(base);
34749
+ entries = fs30.readdirSync(base);
34516
34750
  } catch {
34517
34751
  continue;
34518
34752
  }
34519
34753
  for (const name of entries) {
34520
- const dir = path29.join(base, name);
34754
+ const dir = path33.join(base, name);
34521
34755
  try {
34522
- if (!fs26.statSync(dir).isDirectory()) continue;
34756
+ if (!fs30.statSync(dir).isDirectory()) continue;
34523
34757
  } catch {
34524
34758
  continue;
34525
34759
  }
34526
- const skillMd = path29.join(dir, "SKILL.md");
34527
- if (!fs26.existsSync(skillMd)) continue;
34760
+ const skillMd = path33.join(dir, "SKILL.md");
34761
+ if (!fs30.existsSync(skillMd)) continue;
34528
34762
  const key = `${rel}/${name}`;
34529
34763
  if (seenKeys.has(key)) continue;
34530
34764
  seenKeys.add(key);
@@ -34536,23 +34770,23 @@ function discoverLocalSkills(cwd) {
34536
34770
  function discoverSkillLayoutRoots(cwd) {
34537
34771
  const roots = [];
34538
34772
  for (const rel of SKILL_DISCOVERY_ROOTS) {
34539
- const base = path29.join(cwd, rel);
34540
- if (!fs26.existsSync(base) || !fs26.statSync(base).isDirectory()) continue;
34773
+ const base = path33.join(cwd, rel);
34774
+ if (!fs30.existsSync(base) || !fs30.statSync(base).isDirectory()) continue;
34541
34775
  let entries = [];
34542
34776
  try {
34543
- entries = fs26.readdirSync(base);
34777
+ entries = fs30.readdirSync(base);
34544
34778
  } catch {
34545
34779
  continue;
34546
34780
  }
34547
34781
  const skills2 = [];
34548
34782
  for (const name of entries) {
34549
- const dir = path29.join(base, name);
34783
+ const dir = path33.join(base, name);
34550
34784
  try {
34551
- if (!fs26.statSync(dir).isDirectory()) continue;
34785
+ if (!fs30.statSync(dir).isDirectory()) continue;
34552
34786
  } catch {
34553
34787
  continue;
34554
34788
  }
34555
- if (!fs26.existsSync(path29.join(dir, "SKILL.md"))) continue;
34789
+ if (!fs30.existsSync(path33.join(dir, "SKILL.md"))) continue;
34556
34790
  const relPath = `${rel}/${name}`.replace(/\\/g, "/");
34557
34791
  skills2.push({ name, relPath });
34558
34792
  }
@@ -34654,6 +34888,7 @@ function reportGitRepos(getWs, log2) {
34654
34888
  var API_TO_BRIDGE_MESSAGE_TYPES = [
34655
34889
  "auth_token",
34656
34890
  "bridge_identified",
34891
+ "ha",
34657
34892
  "dev_servers_config",
34658
34893
  "server_control",
34659
34894
  "agent_config",
@@ -34682,6 +34917,10 @@ function parseApiToBridgeMessage(data, log2) {
34682
34917
  }
34683
34918
  return null;
34684
34919
  }
34920
+ if (t === "ha") {
34921
+ const s = data.s;
34922
+ if (typeof s !== "number" || !Number.isFinite(s)) return null;
34923
+ }
34685
34924
  return data;
34686
34925
  }
34687
34926
 
@@ -34722,6 +34961,13 @@ var handleBridgeIdentified = (msg, deps) => {
34722
34961
  });
34723
34962
  };
34724
34963
 
34964
+ // src/connection/heartbeat/ack.ts
34965
+ var handleBridgeHeartbeatAck = (msg, deps) => {
34966
+ const raw = msg.s;
34967
+ if (typeof raw !== "number" || !Number.isFinite(raw)) return;
34968
+ deps.onBridgeHeartbeatAck?.(Math.trunc(raw));
34969
+ };
34970
+
34725
34971
  // src/agents/acp/from-bridge/handle-bridge-agent-config.ts
34726
34972
  function handleBridgeAgentConfig(msg, { acpManager }) {
34727
34973
  if (!Array.isArray(msg.agents) || msg.agents.length === 0) return;
@@ -34734,7 +34980,7 @@ var handleAgentConfigMessage = (msg, deps) => {
34734
34980
  };
34735
34981
 
34736
34982
  // src/prompt-turn-queue/runner.ts
34737
- import fs29 from "node:fs";
34983
+ import fs31 from "node:fs";
34738
34984
 
34739
34985
  // src/prompt-turn-queue/client-report.ts
34740
34986
  function sendPromptQueueClientReport(ws, queues) {
@@ -34743,32 +34989,6 @@ function sendPromptQueueClientReport(ws, queues) {
34743
34989
  return true;
34744
34990
  }
34745
34991
 
34746
- // src/prompt-turn-queue/disk-store.ts
34747
- import fs28 from "node:fs";
34748
-
34749
- // src/prompt-turn-queue/paths.ts
34750
- import crypto3 from "node:crypto";
34751
- import fs27 from "node:fs";
34752
- import path30 from "node:path";
34753
- import os7 from "node:os";
34754
- var QUEUE_KEY_HEX_64 = /^[a-f0-9]{64}$/i;
34755
- function queueStateFileSlug(queueKey) {
34756
- if (QUEUE_KEY_HEX_64.test(queueKey)) return queueKey.toLowerCase();
34757
- return crypto3.createHash("sha256").update(queueKey, "utf8").digest("hex");
34758
- }
34759
- function getPromptQueuesDirectory() {
34760
- const override = process.env.BUILDAMATON_PROMPT_QUEUES_DIR?.trim();
34761
- if (override) return path30.resolve(override);
34762
- return path30.join(os7.homedir(), ".buildautomaton", "queues");
34763
- }
34764
- function ensurePromptQueuesDirectory() {
34765
- const dir = getPromptQueuesDirectory();
34766
- if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
34767
- }
34768
- function queueStateFilePath(queueKey) {
34769
- return path30.join(getPromptQueuesDirectory(), `${queueStateFileSlug(queueKey)}.json`);
34770
- }
34771
-
34772
34992
  // src/prompt-turn-queue/disk-store.ts
34773
34993
  var MERGEABLE_SERVER_STATES = /* @__PURE__ */ new Set([
34774
34994
  "queued",
@@ -34778,28 +34998,27 @@ var MERGEABLE_SERVER_STATES = /* @__PURE__ */ new Set([
34778
34998
  "stopping",
34779
34999
  "discarded"
34780
35000
  ]);
34781
- function parsePersistedQueueFile(raw) {
34782
- try {
34783
- const o = JSON.parse(raw);
34784
- const queueKey = typeof o.queueKey === "string" ? o.queueKey : typeof o.queueKeyHash === "string" ? o.queueKeyHash : null;
34785
- if (!queueKey || typeof o.updatedAt !== "string" || !Array.isArray(o.turns)) return null;
34786
- return { queueKey, updatedAt: o.updatedAt, turns: o.turns };
34787
- } catch {
34788
- return null;
34789
- }
34790
- }
34791
35001
  function readPersistedQueue(queueKey) {
34792
- const p = queueStateFilePath(queueKey);
35002
+ const db = getCliDatabase();
35003
+ const row = db.get("SELECT queue_key, updated_at, turns_json FROM prompt_queue WHERE queue_key = ?", [
35004
+ queueKey
35005
+ ]);
35006
+ if (!row) return null;
34793
35007
  try {
34794
- return parsePersistedQueueFile(fs28.readFileSync(p, "utf8"));
35008
+ const turns = JSON.parse(row.turns_json);
35009
+ if (!Array.isArray(turns)) return null;
35010
+ return { queueKey: row.queue_key, updatedAt: row.updated_at, turns };
34795
35011
  } catch {
34796
35012
  return null;
34797
35013
  }
34798
35014
  }
34799
35015
  function writePersistedQueue(file2) {
34800
- ensurePromptQueuesDirectory();
34801
- const p = queueStateFilePath(file2.queueKey);
34802
- fs28.writeFileSync(p, JSON.stringify(file2, null, 2), "utf8");
35016
+ const db = getCliDatabase();
35017
+ db.run(
35018
+ `INSERT INTO prompt_queue (queue_key, updated_at, turns_json) VALUES (?, ?, ?)
35019
+ ON CONFLICT(queue_key) DO UPDATE SET updated_at = excluded.updated_at, turns_json = excluded.turns_json`,
35020
+ [file2.queueKey, file2.updatedAt, JSON.stringify(file2.turns)]
35021
+ );
34803
35022
  }
34804
35023
  function mergeServerQueueSnapshot(queueKey, serverTurns) {
34805
35024
  const prev = readPersistedQueue(queueKey);
@@ -34857,7 +35076,7 @@ async function runLocalRevertBeforeQueuedPrompt(next, deps) {
34857
35076
  const tid = typeof pl.snapshotRevertTurnId === "string" && pl.snapshotRevertTurnId.trim() !== "" ? pl.snapshotRevertTurnId.trim() : next.turnId;
34858
35077
  const agentBase = deps.sessionWorktreeManager.getSessionWorktreeRootForSession(sid) ?? getBridgeRoot();
34859
35078
  const file2 = snapshotFilePath(agentBase, tid);
34860
- if (!fs29.existsSync(file2)) {
35079
+ if (!fs31.existsSync(file2)) {
34861
35080
  deps.log(
34862
35081
  `[Queue] requeued_with_revert: no pre-turn snapshot for ${tid.slice(0, 8)}\u2026; continuing without revert.`
34863
35082
  );
@@ -35081,9 +35300,9 @@ function parseChangeSummarySnapshots(raw) {
35081
35300
  for (const item of raw) {
35082
35301
  if (!item || typeof item !== "object") continue;
35083
35302
  const o = item;
35084
- const path35 = typeof o.path === "string" && o.path.trim() !== "" ? o.path.trim() : "";
35085
- if (!path35) continue;
35086
- const row = { path: path35 };
35303
+ const path39 = typeof o.path === "string" && o.path.trim() !== "" ? o.path.trim() : "";
35304
+ if (!path39) continue;
35305
+ const row = { path: path39 };
35087
35306
  if (typeof o.patchContent === "string") row.patchContent = o.patchContent;
35088
35307
  if (typeof o.oldText === "string") row.oldText = o.oldText;
35089
35308
  if (typeof o.newText === "string") row.newText = o.newText;
@@ -35304,15 +35523,15 @@ var handleSkillCallMessage = (msg, { getWs, log: log2 }) => {
35304
35523
  };
35305
35524
 
35306
35525
  // src/files/list-dir.ts
35307
- import fs30 from "node:fs";
35308
- import path32 from "node:path";
35526
+ import fs32 from "node:fs";
35527
+ import path35 from "node:path";
35309
35528
 
35310
35529
  // src/files/ensure-under-cwd.ts
35311
- import path31 from "node:path";
35530
+ import path34 from "node:path";
35312
35531
  function ensureUnderCwd(relativePath, cwd = getBridgeRoot()) {
35313
- const normalized = path31.normalize(relativePath).replace(/^(\.\/)+/, "");
35314
- const resolved = path31.resolve(cwd, normalized);
35315
- if (!resolved.startsWith(cwd + path31.sep) && resolved !== cwd) {
35532
+ const normalized = path34.normalize(relativePath).replace(/^(\.\/)+/, "");
35533
+ const resolved = path34.resolve(cwd, normalized);
35534
+ if (!resolved.startsWith(cwd + path34.sep) && resolved !== cwd) {
35316
35535
  return null;
35317
35536
  }
35318
35537
  return resolved;
@@ -35326,7 +35545,7 @@ async function listDirAsync(relativePath) {
35326
35545
  return { error: "Path is outside working directory" };
35327
35546
  }
35328
35547
  try {
35329
- const names = await fs30.promises.readdir(resolved, { withFileTypes: true });
35548
+ const names = await fs32.promises.readdir(resolved, { withFileTypes: true });
35330
35549
  const visible = names.filter((d) => !d.name.startsWith("."));
35331
35550
  const entries = [];
35332
35551
  for (let i = 0; i < visible.length; i++) {
@@ -35334,12 +35553,12 @@ async function listDirAsync(relativePath) {
35334
35553
  await yieldToEventLoop();
35335
35554
  }
35336
35555
  const d = visible[i];
35337
- const entryPath = path32.join(relativePath || ".", d.name).replace(/\\/g, "/");
35338
- const fullPath = path32.join(resolved, d.name);
35556
+ const entryPath = path35.join(relativePath || ".", d.name).replace(/\\/g, "/");
35557
+ const fullPath = path35.join(resolved, d.name);
35339
35558
  let isDir = d.isDirectory();
35340
35559
  if (d.isSymbolicLink()) {
35341
35560
  try {
35342
- const targetStat = await fs30.promises.stat(fullPath);
35561
+ const targetStat = await fs32.promises.stat(fullPath);
35343
35562
  isDir = targetStat.isDirectory();
35344
35563
  } catch {
35345
35564
  isDir = false;
@@ -35364,25 +35583,25 @@ async function listDirAsync(relativePath) {
35364
35583
  }
35365
35584
 
35366
35585
  // src/files/read-file.ts
35367
- import fs31 from "node:fs";
35586
+ import fs33 from "node:fs";
35368
35587
  import { StringDecoder } from "node:string_decoder";
35369
35588
  function resolveFilePath(relativePath) {
35370
35589
  const resolved = ensureUnderCwd(relativePath, getBridgeRoot());
35371
35590
  if (!resolved) return { error: "Path is outside working directory" };
35372
35591
  let real;
35373
35592
  try {
35374
- real = fs31.realpathSync(resolved);
35593
+ real = fs33.realpathSync(resolved);
35375
35594
  } catch {
35376
35595
  real = resolved;
35377
35596
  }
35378
- const stat2 = fs31.statSync(real);
35597
+ const stat2 = fs33.statSync(real);
35379
35598
  if (!stat2.isFile()) return { error: "Not a file" };
35380
35599
  return real;
35381
35600
  }
35382
35601
  var LINE_CHUNK_SIZE = 64 * 1024;
35383
35602
  function readFileRange(filePath, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
35384
- const fileSize = fs31.statSync(filePath).size;
35385
- const fd = fs31.openSync(filePath, "r");
35603
+ const fileSize = fs33.statSync(filePath).size;
35604
+ const fd = fs33.openSync(filePath, "r");
35386
35605
  const bufSize = 64 * 1024;
35387
35606
  const buf = Buffer.alloc(bufSize);
35388
35607
  const decoder = new StringDecoder("utf8");
@@ -35395,7 +35614,7 @@ function readFileRange(filePath, startLine, endLine, lineOffsetIn, lineChunkSize
35395
35614
  let line0Accum = "";
35396
35615
  try {
35397
35616
  let bytesRead;
35398
- while (!done && (bytesRead = fs31.readSync(fd, buf, 0, bufSize, null)) > 0) {
35617
+ while (!done && (bytesRead = fs33.readSync(fd, buf, 0, bufSize, null)) > 0) {
35399
35618
  const text = partial2 + decoder.write(buf.subarray(0, bytesRead));
35400
35619
  partial2 = "";
35401
35620
  let lineStart = 0;
@@ -35530,7 +35749,7 @@ function readFileRange(filePath, startLine, endLine, lineOffsetIn, lineChunkSize
35530
35749
  }
35531
35750
  return { content: resultLines.join("\n"), size: fileSize };
35532
35751
  } finally {
35533
- fs31.closeSync(fd);
35752
+ fs33.closeSync(fd);
35534
35753
  }
35535
35754
  }
35536
35755
  function readFile2(relativePath, startLine, endLine, lineOffset, lineChunkSize = LINE_CHUNK_SIZE) {
@@ -35541,8 +35760,8 @@ function readFile2(relativePath, startLine, endLine, lineOffset, lineChunkSize =
35541
35760
  if (hasRange) {
35542
35761
  return readFileRange(result, startLine, endLine, lineOffset, lineChunkSize);
35543
35762
  }
35544
- const stat2 = fs31.statSync(result);
35545
- const raw = fs31.readFileSync(result, "utf8");
35763
+ const stat2 = fs33.statSync(result);
35764
+ const raw = fs33.readFileSync(result, "utf8");
35546
35765
  const lines = raw.split(/\r?\n/);
35547
35766
  return { content: raw, totalLines: lines.length, size: stat2.size };
35548
35767
  } catch (err) {
@@ -35555,14 +35774,14 @@ async function readFileAsync(relativePath, startLine, endLine, lineOffset, lineC
35555
35774
  }
35556
35775
 
35557
35776
  // src/files/handle-file-browser-search.ts
35777
+ import path36 from "node:path";
35558
35778
  var SEARCH_LIMIT = 100;
35559
35779
  function handleFileBrowserSearch(msg, socket, e2ee) {
35560
35780
  void (async () => {
35561
35781
  await yieldToEventLoop();
35562
35782
  const q = typeof msg.q === "string" ? msg.q : "";
35563
- const cwd = getBridgeRoot();
35564
- const index = loadFileIndex(cwd);
35565
- if (index === null) {
35783
+ const cwd = path36.resolve(getBridgeRoot());
35784
+ if (!bridgeFileIndexIsPopulated(cwd)) {
35566
35785
  const payload2 = {
35567
35786
  type: "file_browser_search_response",
35568
35787
  id: msg.id,
@@ -35572,7 +35791,7 @@ function handleFileBrowserSearch(msg, socket, e2ee) {
35572
35791
  sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload2, ["paths"]) : payload2);
35573
35792
  return;
35574
35793
  }
35575
- const results = await searchFileIndexAsync(index, q, SEARCH_LIMIT);
35794
+ const results = await searchBridgeFilePathsAsync(cwd, q, SEARCH_LIMIT);
35576
35795
  const payload = {
35577
35796
  type: "file_browser_search_response",
35578
35797
  id: msg.id,
@@ -35660,8 +35879,8 @@ function handleSkillLayoutRequest(msg, deps) {
35660
35879
  }
35661
35880
 
35662
35881
  // src/skills/install-remote-skills.ts
35663
- import fs32 from "node:fs";
35664
- import path33 from "node:path";
35882
+ import fs34 from "node:fs";
35883
+ import path37 from "node:path";
35665
35884
  function installRemoteSkills(cwd, targetDir, items) {
35666
35885
  const installed2 = [];
35667
35886
  if (!Array.isArray(items)) {
@@ -35672,15 +35891,15 @@ function installRemoteSkills(cwd, targetDir, items) {
35672
35891
  if (typeof item.sourceId !== "string" || typeof item.skillName !== "string" || typeof item.versionHash !== "string" || !Array.isArray(item.files)) {
35673
35892
  continue;
35674
35893
  }
35675
- const skillDir = path33.join(cwd, targetDir, item.skillName);
35894
+ const skillDir = path37.join(cwd, targetDir, item.skillName);
35676
35895
  for (const f of item.files) {
35677
35896
  if (typeof f.path !== "string" || !f.text && !f.base64) continue;
35678
- const dest = path33.join(skillDir, f.path);
35679
- fs32.mkdirSync(path33.dirname(dest), { recursive: true });
35897
+ const dest = path37.join(skillDir, f.path);
35898
+ fs34.mkdirSync(path37.dirname(dest), { recursive: true });
35680
35899
  if (f.text !== void 0) {
35681
- fs32.writeFileSync(dest, f.text, "utf8");
35900
+ fs34.writeFileSync(dest, f.text, "utf8");
35682
35901
  } else if (f.base64) {
35683
- fs32.writeFileSync(dest, Buffer.from(f.base64, "base64"));
35902
+ fs34.writeFileSync(dest, Buffer.from(f.base64, "base64"));
35684
35903
  }
35685
35904
  }
35686
35905
  installed2.push({
@@ -35830,7 +36049,7 @@ var handleSessionDiscardedMessage = (msg, deps) => {
35830
36049
  };
35831
36050
 
35832
36051
  // src/routing/handlers/revert-turn-snapshot.ts
35833
- import * as fs33 from "node:fs";
36052
+ import * as fs35 from "node:fs";
35834
36053
  var handleRevertTurnSnapshotMessage = (msg, deps) => {
35835
36054
  const id = typeof msg.id === "string" ? msg.id : "";
35836
36055
  const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
@@ -35842,7 +36061,7 @@ var handleRevertTurnSnapshotMessage = (msg, deps) => {
35842
36061
  if (!s) return;
35843
36062
  const agentBase = sessionWorktreeManager.getSessionWorktreeRootForSession(sessionId) ?? getBridgeRoot();
35844
36063
  const file2 = snapshotFilePath(agentBase, turnId);
35845
- if (!fs33.existsSync(file2)) {
36064
+ if (!fs35.existsSync(file2)) {
35846
36065
  sendWsMessage(s, {
35847
36066
  type: "revert_turn_snapshot_result",
35848
36067
  id,
@@ -35891,6 +36110,9 @@ function dispatchBridgeMessage(msg, deps) {
35891
36110
  case "bridge_identified":
35892
36111
  handleBridgeIdentified(msg, deps);
35893
36112
  break;
36113
+ case "ha":
36114
+ handleBridgeHeartbeatAck(msg, deps);
36115
+ break;
35894
36116
  case "dev_servers_config":
35895
36117
  handleDevServersConfig(msg, deps);
35896
36118
  break;
@@ -35946,9 +36168,17 @@ function dispatchBridgeMessage(msg, deps) {
35946
36168
  }
35947
36169
 
35948
36170
  // src/routing/handle-bridge-message.ts
36171
+ function normalizeInboundBridgeWebSocketJson(data) {
36172
+ if (data === null || typeof data !== "object" || Array.isArray(data)) return data;
36173
+ const o = data;
36174
+ if (o.t === "ha" && typeof o.s === "number" && Number.isFinite(o.s)) {
36175
+ return { type: "ha", s: Math.trunc(o.s) };
36176
+ }
36177
+ return data;
36178
+ }
35949
36179
  function handleBridgeMessage(data, deps) {
35950
36180
  if (!deps.getWs()) return;
35951
- const msg = parseApiToBridgeMessage(data, deps.log);
36181
+ const msg = parseApiToBridgeMessage(normalizeInboundBridgeWebSocketJson(data), deps.log);
35952
36182
  if (!msg) return;
35953
36183
  setImmediate(() => {
35954
36184
  dispatchBridgeMessage(msg, deps);
@@ -35996,7 +36226,8 @@ function createMainBridgeWebSocketLifecycle(params) {
35996
36226
  persistTokens,
35997
36227
  onAuthInvalid,
35998
36228
  e2ee,
35999
- identifyReportedPaths
36229
+ identifyReportedPaths,
36230
+ bridgeHeartbeat
36000
36231
  } = params;
36001
36232
  let authRefreshInFlight = false;
36002
36233
  function handleOpen() {
@@ -36028,6 +36259,7 @@ function createMainBridgeWebSocketLifecycle(params) {
36028
36259
  }
36029
36260
  }
36030
36261
  function handleClose(code, reason) {
36262
+ bridgeHeartbeat?.stop();
36031
36263
  try {
36032
36264
  const was = state.currentWs;
36033
36265
  state.currentWs = null;
@@ -36062,6 +36294,7 @@ function createMainBridgeWebSocketLifecycle(params) {
36062
36294
  } catch {
36063
36295
  }
36064
36296
  }
36297
+ bridgeHeartbeat?.stop();
36065
36298
  const prev = state.currentWs;
36066
36299
  if (prev) {
36067
36300
  prev.removeAllListeners();
@@ -36232,11 +36465,98 @@ function createCliE2eeRuntime(key) {
36232
36465
  };
36233
36466
  }
36234
36467
 
36468
+ // src/connection/heartbeat/controller.ts
36469
+ function meanRttMs(samples) {
36470
+ if (samples.length === 0) return void 0;
36471
+ let sum = 0;
36472
+ for (const x of samples) sum += x;
36473
+ return sum / samples.length;
36474
+ }
36475
+ function createBridgeHeartbeatController(params) {
36476
+ const { getWs, log: log2 } = params;
36477
+ let interval = null;
36478
+ let seqCursor = -1;
36479
+ let awaitingSeq = null;
36480
+ let sentAtMs = 0;
36481
+ let missed = 0;
36482
+ const rttSamples = [];
36483
+ function clearTimer() {
36484
+ if (interval != null) {
36485
+ clearInterval(interval);
36486
+ interval = null;
36487
+ }
36488
+ }
36489
+ function nextSeq() {
36490
+ seqCursor = seqCursor >= BRIDGE_HEARTBEAT_SEQ_MAX ? 0 : seqCursor + 1;
36491
+ return seqCursor;
36492
+ }
36493
+ function tick() {
36494
+ const ws = getWs();
36495
+ if (!ws || ws.readyState !== wrapper_default.OPEN) return;
36496
+ if (awaitingSeq !== null) {
36497
+ missed++;
36498
+ if (missed >= BRIDGE_HEARTBEAT_MISSED_ACKS_BEFORE_RECONNECT) {
36499
+ try {
36500
+ log2("[Bridge service] Heartbeat missed repeatedly; reconnecting\u2026");
36501
+ } catch {
36502
+ }
36503
+ clearTimer();
36504
+ awaitingSeq = null;
36505
+ missed = 0;
36506
+ rttSamples.length = 0;
36507
+ safeCloseWebSocket(ws);
36508
+ return;
36509
+ }
36510
+ }
36511
+ const seq = nextSeq();
36512
+ const mean = meanRttMs(rttSamples);
36513
+ const payload = mean !== void 0 && Number.isFinite(mean) ? { t: "h", s: seq, m: Math.round(mean) } : { t: "h", s: seq };
36514
+ sendWsMessage(ws, payload);
36515
+ awaitingSeq = seq;
36516
+ sentAtMs = Date.now();
36517
+ }
36518
+ return {
36519
+ start() {
36520
+ clearTimer();
36521
+ awaitingSeq = null;
36522
+ missed = 0;
36523
+ seqCursor = -1;
36524
+ rttSamples.length = 0;
36525
+ interval = setInterval(tick, BRIDGE_APP_HEARTBEAT_INTERVAL_MS);
36526
+ },
36527
+ stop() {
36528
+ clearTimer();
36529
+ awaitingSeq = null;
36530
+ missed = 0;
36531
+ rttSamples.length = 0;
36532
+ },
36533
+ onAck(seq) {
36534
+ if (awaitingSeq === null) return;
36535
+ if (!Number.isFinite(seq)) return;
36536
+ const ack = Math.trunc(seq);
36537
+ const pending = awaitingSeq;
36538
+ if (ack < pending) return;
36539
+ if (ack === pending) {
36540
+ const rtt = Date.now() - sentAtMs;
36541
+ if (Number.isFinite(rtt) && rtt >= 0 && rtt < 6e5) {
36542
+ rttSamples.push(rtt);
36543
+ if (rttSamples.length > BRIDGE_HEARTBEAT_RTT_SAMPLE_MAX) {
36544
+ rttSamples.splice(0, rttSamples.length - BRIDGE_HEARTBEAT_RTT_SAMPLE_MAX);
36545
+ }
36546
+ }
36547
+ }
36548
+ awaitingSeq = null;
36549
+ missed = 0;
36550
+ }
36551
+ };
36552
+ }
36553
+
36235
36554
  // src/connection/create-bridge-connection.ts
36236
36555
  async function createBridgeConnection(options) {
36237
36556
  const { apiUrl, workspaceId, justAuthenticated, onAuthInvalid, persistTokens } = options;
36238
36557
  const firehoseServerUrl = options.firehoseServerUrl ?? options.proxyServerUrl;
36239
36558
  const logFn = options.log ?? log;
36559
+ getCliDatabase({ logLegacyMigration: logFn });
36240
36560
  const tokens = {
36241
36561
  accessToken: options.authToken,
36242
36562
  refreshToken: options.refreshToken
@@ -36271,13 +36591,18 @@ async function createBridgeConnection(options) {
36271
36591
  }
36272
36592
  const e2ee = options.e2eCertificate ? createCliE2eeRuntime(options.e2eCertificate) : void 0;
36273
36593
  const devServerManager = new DevServerManager({ getWs, log: logFn, getBridgeRoot, e2ee });
36274
- const onBridgeIdentified = createOnBridgeIdentified({
36594
+ const bridgeHeartbeat = createBridgeHeartbeatController({ getWs, log: logFn });
36595
+ const baseOnBridgeIdentified = createOnBridgeIdentified({
36275
36596
  devServerManager,
36276
36597
  firehoseServerUrl,
36277
36598
  workspaceId,
36278
36599
  state,
36279
36600
  logFn
36280
36601
  });
36602
+ const onBridgeIdentified = (msg) => {
36603
+ baseOnBridgeIdentified(msg);
36604
+ bridgeHeartbeat.start();
36605
+ };
36281
36606
  const sendLocalSkillsReport = createSendLocalSkillsReport(getWs, logFn);
36282
36607
  const reportAutoDetectedAgents = createReportAutoDetectedAgents(getWs, logFn);
36283
36608
  const messageDeps = {
@@ -36286,6 +36611,9 @@ async function createBridgeConnection(options) {
36286
36611
  acpManager,
36287
36612
  sessionWorktreeManager,
36288
36613
  onBridgeIdentified,
36614
+ onBridgeHeartbeatAck: (seq) => {
36615
+ bridgeHeartbeat.onAck(seq);
36616
+ },
36289
36617
  sendLocalSkillsReport,
36290
36618
  reportAutoDetectedAgents,
36291
36619
  devServerManager,
@@ -36294,8 +36622,8 @@ async function createBridgeConnection(options) {
36294
36622
  getCloudAccessToken: () => tokens.accessToken
36295
36623
  };
36296
36624
  const identifyReportedPaths = {
36297
- bridgeRootPath: path34.resolve(getBridgeRoot()),
36298
- worktreesRootPath: path34.resolve(worktreesRootPath)
36625
+ bridgeRootPath: path38.resolve(getBridgeRoot()),
36626
+ worktreesRootPath: path38.resolve(worktreesRootPath)
36299
36627
  };
36300
36628
  const { connect } = createMainBridgeWebSocketLifecycle({
36301
36629
  state,
@@ -36309,13 +36637,15 @@ async function createBridgeConnection(options) {
36309
36637
  persistTokens,
36310
36638
  onAuthInvalid,
36311
36639
  e2ee,
36312
- identifyReportedPaths
36640
+ identifyReportedPaths,
36641
+ bridgeHeartbeat
36313
36642
  });
36314
36643
  connect();
36315
36644
  const stopFileIndexWatcher = startFileIndexWatcher(getBridgeRoot());
36316
36645
  return {
36317
36646
  close: async () => {
36318
36647
  stopFileIndexWatcher();
36648
+ bridgeHeartbeat.stop();
36319
36649
  await closeBridgeConnection(state, acpManager, devServerManager, logFn);
36320
36650
  }
36321
36651
  };