@buildautomaton/cli 0.1.31 → 0.1.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -25064,7 +25064,7 @@ var {
25064
25064
  } = import_index.default;
25065
25065
 
25066
25066
  // src/cli-version.ts
25067
- var CLI_VERSION = "0.1.31".length > 0 ? "0.1.31" : "0.0.0-dev";
25067
+ var CLI_VERSION = "0.1.33".length > 0 ? "0.1.33" : "0.0.0-dev";
25068
25068
 
25069
25069
  // src/cli/defaults.ts
25070
25070
  var DEFAULT_API_URL = process.env.BUILDAUTOMATON_API_URL ?? "https://api.buildautomaton.com";
@@ -26696,9 +26696,38 @@ function runPendingAuth(options) {
26696
26696
  };
26697
26697
  }
26698
26698
 
26699
+ // src/dev-servers/manager/dev-server-constants.ts
26700
+ var BRIDGE_CLOSE_DEV_SERVER_GRACE_MS = 0;
26701
+ var BRIDGE_SHUTDOWN_GRACE_MS = 8e3;
26702
+
26703
+ // src/runtime/cli-process-interrupt.ts
26704
+ var cliImmediateShutdownRequested = false;
26705
+ function requestCliImmediateShutdown() {
26706
+ cliImmediateShutdownRequested = true;
26707
+ }
26708
+ function isCliImmediateShutdownRequested() {
26709
+ return cliImmediateShutdownRequested;
26710
+ }
26711
+ async function delayMsUnlessShutdownRequested(ms) {
26712
+ if (ms <= 0) return true;
26713
+ const end = Date.now() + ms;
26714
+ while (Date.now() < end) {
26715
+ if (isCliImmediateShutdownRequested()) return false;
26716
+ const chunk = Math.min(50, end - Date.now());
26717
+ if (chunk <= 0) break;
26718
+ await new Promise((r) => setTimeout(r, chunk));
26719
+ }
26720
+ return !isCliImmediateShutdownRequested();
26721
+ }
26722
+
26699
26723
  // src/sqlite/cli-database.ts
26700
26724
  import sqliteWasm from "node-sqlite3-wasm";
26701
26725
 
26726
+ // src/runtime/yield-to-event-loop.ts
26727
+ function yieldToEventLoop() {
26728
+ return new Promise((resolve20) => setImmediate(resolve20));
26729
+ }
26730
+
26702
26731
  // src/sqlite/cli-sqlite-paths.ts
26703
26732
  import fs8 from "node:fs";
26704
26733
  import path5 from "node:path";
@@ -26991,6 +27020,29 @@ function migrateCliSqlite(db) {
26991
27020
 
26992
27021
  // src/sqlite/cli-database.ts
26993
27022
  var { Database: SqliteDatabase } = sqliteWasm;
27023
+ var CLI_SQLITE_SYNC_RETRY_MAX = 40;
27024
+ var CLI_SQLITE_ASYNC_RETRY_MAX = 60;
27025
+ var CLI_SQLITE_ASYNC_BASE_DELAY_MS = 20;
27026
+ var CliSqliteInterrupted = class extends Error {
27027
+ name = "CliSqliteInterrupted";
27028
+ constructor() {
27029
+ super("CLI SQLite interrupted (shutdown)");
27030
+ }
27031
+ };
27032
+ function applyCliSqliteConcurrencyPragmas(db) {
27033
+ try {
27034
+ db.exec("PRAGMA journal_mode = WAL");
27035
+ } catch {
27036
+ }
27037
+ try {
27038
+ db.run("PRAGMA synchronous = NORMAL");
27039
+ } catch {
27040
+ }
27041
+ try {
27042
+ db.run("PRAGMA busy_timeout = 500");
27043
+ } catch {
27044
+ }
27045
+ }
26994
27046
  function applyCliSqliteMemoryPragmas(db) {
26995
27047
  try {
26996
27048
  db.run("PRAGMA cache_size = -8192");
@@ -26998,18 +27050,6 @@ function applyCliSqliteMemoryPragmas(db) {
26998
27050
  } catch {
26999
27051
  }
27000
27052
  }
27001
- var openDatabases = /* @__PURE__ */ new Map();
27002
- var processExitCloseRegistered = false;
27003
- function registerProcessExitSqliteClose() {
27004
- if (processExitCloseRegistered) return;
27005
- processExitCloseRegistered = true;
27006
- process.once("exit", () => {
27007
- for (const db of openDatabases.values()) {
27008
- safeCloseCliSqliteDatabase(db);
27009
- }
27010
- openDatabases.clear();
27011
- });
27012
- }
27013
27053
  function safeCloseCliSqliteDatabase(db) {
27014
27054
  if (db == null) return;
27015
27055
  try {
@@ -27018,22 +27058,49 @@ function safeCloseCliSqliteDatabase(db) {
27018
27058
  }
27019
27059
  }
27020
27060
  function closeAllCliSqliteConnections() {
27021
- for (const db of openDatabases.values()) {
27022
- safeCloseCliSqliteDatabase(db);
27061
+ }
27062
+ function isCliSqliteLockError(e) {
27063
+ if (e instanceof CliSqliteInterrupted) return false;
27064
+ const msg = e instanceof Error ? e.message : String(e);
27065
+ const lower = msg.toLowerCase();
27066
+ return lower.includes("database is locked") || lower.includes("sqlite_busy") || lower.includes("sqlite3_busy") || lower.includes("database") && lower.includes("locked");
27067
+ }
27068
+ function syncSleepMs(ms) {
27069
+ if (ms <= 0) return;
27070
+ const deadline = Date.now() + ms;
27071
+ while (Date.now() < deadline) {
27072
+ if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
27073
+ const chunk = Math.min(80, deadline - Date.now());
27074
+ if (chunk <= 0) break;
27075
+ try {
27076
+ const sab = new SharedArrayBuffer(4);
27077
+ const ia = new Int32Array(sab);
27078
+ Atomics.wait(ia, 0, 0, chunk);
27079
+ } catch {
27080
+ const end = Date.now() + chunk;
27081
+ while (Date.now() < end) {
27082
+ if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
27083
+ }
27084
+ }
27023
27085
  }
27024
- openDatabases.clear();
27025
27086
  }
27026
- function getCliDatabase(options) {
27027
- const sqlitePath = getCliSqlitePath();
27028
- const existing = openDatabases.get(sqlitePath);
27029
- if (existing?.isOpen) return existing;
27030
- if (existing && !existing.isOpen) {
27031
- safeCloseCliSqliteDatabase(existing);
27032
- openDatabases.delete(sqlitePath);
27087
+ async function asyncDelayMsInterruptible(ms) {
27088
+ const end = Date.now() + ms;
27089
+ while (Date.now() < end) {
27090
+ if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
27091
+ const chunk = Math.min(50, end - Date.now());
27092
+ if (chunk <= 0) break;
27093
+ await new Promise((r) => setTimeout(r, chunk));
27094
+ await yieldToEventLoop();
27033
27095
  }
27096
+ }
27097
+ function openCliSqliteConnection(options) {
27098
+ if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
27099
+ const sqlitePath = getCliSqlitePath();
27034
27100
  ensureCliSqliteParentDir(sqlitePath);
27035
27101
  const db = new SqliteDatabase(sqlitePath);
27036
27102
  try {
27103
+ applyCliSqliteConcurrencyPragmas(db);
27037
27104
  applyCliSqliteMemoryPragmas(db);
27038
27105
  migrateCliSqlite(db);
27039
27106
  importCliSqliteLegacyDiskData(db, options?.logLegacyMigration);
@@ -27041,16 +27108,62 @@ function getCliDatabase(options) {
27041
27108
  safeCloseCliSqliteDatabase(db);
27042
27109
  throw e;
27043
27110
  }
27044
- openDatabases.set(sqlitePath, db);
27045
- registerProcessExitSqliteClose();
27046
27111
  return db;
27047
27112
  }
27113
+ function withCliSqliteSync(fn, options) {
27114
+ for (let attempt = 1; attempt <= CLI_SQLITE_SYNC_RETRY_MAX; attempt++) {
27115
+ if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
27116
+ let db;
27117
+ try {
27118
+ db = openCliSqliteConnection(options);
27119
+ try {
27120
+ return fn(db);
27121
+ } finally {
27122
+ safeCloseCliSqliteDatabase(db);
27123
+ }
27124
+ } catch (e) {
27125
+ safeCloseCliSqliteDatabase(db);
27126
+ if (e instanceof CliSqliteInterrupted) throw e;
27127
+ if (!isCliSqliteLockError(e) || attempt === CLI_SQLITE_SYNC_RETRY_MAX) throw e;
27128
+ syncSleepMs(Math.min(500, 12 * attempt));
27129
+ }
27130
+ }
27131
+ throw new Error("withCliSqliteSync: exhausted retries");
27132
+ }
27133
+ async function withCliSqlite(fn, options) {
27134
+ let lastError;
27135
+ for (let attempt = 1; attempt <= CLI_SQLITE_ASYNC_RETRY_MAX; attempt++) {
27136
+ if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
27137
+ let db;
27138
+ try {
27139
+ db = openCliSqliteConnection(options);
27140
+ try {
27141
+ return await Promise.resolve(fn(db));
27142
+ } finally {
27143
+ safeCloseCliSqliteDatabase(db);
27144
+ }
27145
+ } catch (e) {
27146
+ lastError = e;
27147
+ safeCloseCliSqliteDatabase(db);
27148
+ if (e instanceof CliSqliteInterrupted) throw e;
27149
+ if (!isCliSqliteLockError(e) || attempt === CLI_SQLITE_ASYNC_RETRY_MAX) throw e;
27150
+ const delayMs = Math.min(600, CLI_SQLITE_ASYNC_BASE_DELAY_MS * attempt);
27151
+ await asyncDelayMsInterruptible(delayMs);
27152
+ await yieldToEventLoop();
27153
+ }
27154
+ }
27155
+ if (lastError instanceof Error) throw lastError;
27156
+ throw new Error(String(lastError));
27157
+ }
27158
+ async function ensureCliSqliteInitialized(options) {
27159
+ await withCliSqlite(() => void 0, options);
27160
+ }
27048
27161
 
27049
27162
  // src/connection/close-bridge-connection.ts
27050
27163
  async function closeBridgeConnection(state, acpManager, devServerManager, log2) {
27164
+ requestCliImmediateShutdown();
27051
27165
  const say = log2 ?? logImmediate;
27052
27166
  say("Cleaning up connections\u2026");
27053
- await new Promise((resolve20) => setImmediate(resolve20));
27054
27167
  state.closedByUser = true;
27055
27168
  clearReconnectQuietTimer(state.mainQuiet);
27056
27169
  clearReconnectQuietTimer(state.firehoseQuiet);
@@ -27087,7 +27200,7 @@ async function closeBridgeConnection(state, acpManager, devServerManager, log2)
27087
27200
  }
27088
27201
  if (devServerManager) {
27089
27202
  say("Stopping local dev server processes\u2026");
27090
- await devServerManager.shutdownAllGraceful();
27203
+ await devServerManager.shutdownAllGraceful({ graceMs: BRIDGE_CLOSE_DEV_SERVER_GRACE_MS });
27091
27204
  }
27092
27205
  try {
27093
27206
  closeAllCliSqliteConnections();
@@ -32866,16 +32979,38 @@ __export(claude_code_acp_client_exports, {
32866
32979
  createClaudeCodeAcpClient: () => createClaudeCodeAcpClient,
32867
32980
  detectLocalAgentPresence: () => detectLocalAgentPresence
32868
32981
  });
32869
- import { execFile as execFile9 } from "node:child_process";
32870
- import { promisify as promisify9 } from "node:util";
32871
32982
 
32872
32983
  // src/agents/acp/clients/detect-command-on-path.ts
32873
32984
  import { execFile as execFile8 } from "node:child_process";
32874
32985
  import { promisify as promisify8 } from "node:util";
32875
32986
  var execFileAsync7 = promisify8(execFile8);
32876
- async function isCommandOnPath(command, timeoutMs = 4e3) {
32987
+ var COMMAND_ON_PATH_PROBE_TIMEOUT_MS = 750;
32988
+ async function execFileShutdownAware(file2, args, timeoutMs) {
32989
+ if (isCliImmediateShutdownRequested()) throw new Error("shutdown");
32990
+ const ac = new AbortController();
32991
+ const shutdownPoll = setInterval(() => {
32992
+ if (isCliImmediateShutdownRequested()) ac.abort();
32993
+ }, 50);
32994
+ shutdownPoll.unref?.();
32995
+ try {
32996
+ await execFileAsync7(file2, args, { timeout: timeoutMs, signal: ac.signal });
32997
+ } finally {
32998
+ clearInterval(shutdownPoll);
32999
+ }
33000
+ }
33001
+ async function isCommandOnPath(command, timeoutMs = COMMAND_ON_PATH_PROBE_TIMEOUT_MS) {
33002
+ if (isCliImmediateShutdownRequested()) return false;
33003
+ try {
33004
+ await execFileShutdownAware("which", [command], timeoutMs);
33005
+ return true;
33006
+ } catch {
33007
+ return false;
33008
+ }
33009
+ }
33010
+ async function execProbeShutdownAware(file2, args, timeoutMs) {
33011
+ if (isCliImmediateShutdownRequested()) return false;
32877
33012
  try {
32878
- await execFileAsync7("which", [command], { timeout: timeoutMs });
33013
+ await execFileShutdownAware(file2, args, timeoutMs);
32879
33014
  return true;
32880
33015
  } catch {
32881
33016
  return false;
@@ -33636,16 +33771,10 @@ async function createSdkStdioAcpClient(options) {
33636
33771
  }
33637
33772
 
33638
33773
  // src/agents/acp/clients/claude-code-acp-client.ts
33639
- var execFileAsync8 = promisify9(execFile9);
33640
33774
  var BACKEND_LOCAL_AGENT_TYPE = "claude-code";
33641
33775
  async function detectLocalAgentPresence() {
33642
33776
  if (await isCommandOnPath("claude")) return true;
33643
- try {
33644
- await execFileAsync8("npx", ["--yes", "@anthropic-ai/claude-code", "--version"], { timeout: 25e3 });
33645
- return true;
33646
- } catch {
33647
- return false;
33648
- }
33777
+ return execProbeShutdownAware("npx", ["--yes", "@anthropic-ai/claude-code", "--version"], 3e3);
33649
33778
  }
33650
33779
  function buildClaudeCodeAcpSpawnCommand(base, _sessionMode) {
33651
33780
  return [...base];
@@ -34812,56 +34941,80 @@ function sessionKeyForCloudSessionId(cloudSessionId) {
34812
34941
  }
34813
34942
  function readLocalAgentSessionFile(cloudSessionId) {
34814
34943
  try {
34815
- const db = getCliDatabase();
34816
- const key = sessionKeyForCloudSessionId(cloudSessionId);
34817
- const row = db.get(
34818
- "SELECT acp_session_id, backend_agent_type, config_options_json, updated_at FROM agent_session WHERE session_key = ?",
34819
- [key]
34820
- );
34821
- if (!row) return null;
34822
- let configOptions = null;
34823
- if (row.config_options_json != null && row.config_options_json !== "") {
34824
- try {
34825
- const parsed = JSON.parse(row.config_options_json);
34826
- configOptions = Array.isArray(parsed) ? parsed : null;
34827
- } catch {
34828
- configOptions = null;
34944
+ return withCliSqliteSync((db) => {
34945
+ const key = sessionKeyForCloudSessionId(cloudSessionId);
34946
+ const row = db.get(
34947
+ "SELECT acp_session_id, backend_agent_type, config_options_json, updated_at FROM agent_session WHERE session_key = ?",
34948
+ [key]
34949
+ );
34950
+ if (!row) return null;
34951
+ let configOptions = null;
34952
+ if (row.config_options_json != null && row.config_options_json !== "") {
34953
+ try {
34954
+ const parsed = JSON.parse(row.config_options_json);
34955
+ configOptions = Array.isArray(parsed) ? parsed : null;
34956
+ } catch {
34957
+ configOptions = null;
34958
+ }
34829
34959
  }
34830
- }
34831
- return {
34832
- v: 1,
34833
- acpSessionId: row.acp_session_id,
34834
- backendAgentType: row.backend_agent_type,
34835
- configOptions,
34836
- updatedAt: row.updated_at
34837
- };
34960
+ return {
34961
+ v: 1,
34962
+ acpSessionId: row.acp_session_id,
34963
+ backendAgentType: row.backend_agent_type,
34964
+ configOptions,
34965
+ updatedAt: row.updated_at
34966
+ };
34967
+ });
34838
34968
  } catch {
34839
34969
  return null;
34840
34970
  }
34841
34971
  }
34842
34972
  function writeLocalAgentSessionFile(cloudSessionId, patch) {
34843
34973
  try {
34844
- const db = getCliDatabase();
34845
- const key = sessionKeyForCloudSessionId(cloudSessionId);
34846
- const prev = readLocalAgentSessionFile(cloudSessionId);
34847
- const next = {
34848
- v: 1,
34849
- acpSessionId: patch.acpSessionId !== void 0 ? patch.acpSessionId : prev?.acpSessionId ?? null,
34850
- backendAgentType: patch.backendAgentType !== void 0 ? patch.backendAgentType : prev?.backendAgentType ?? null,
34851
- configOptions: patch.configOptions !== void 0 ? patch.configOptions : prev?.configOptions ?? null,
34852
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
34853
- };
34854
- const configJson = next.configOptions != null ? JSON.stringify(next.configOptions) : null;
34855
- db.run(
34856
- `INSERT INTO agent_session (session_key, acp_session_id, backend_agent_type, config_options_json, updated_at)
34857
- VALUES (?, ?, ?, ?, ?)
34858
- ON CONFLICT(session_key) DO UPDATE SET
34859
- acp_session_id = excluded.acp_session_id,
34860
- backend_agent_type = excluded.backend_agent_type,
34861
- config_options_json = excluded.config_options_json,
34862
- updated_at = excluded.updated_at`,
34863
- [key, next.acpSessionId, next.backendAgentType, configJson, next.updatedAt]
34864
- );
34974
+ withCliSqliteSync((db) => {
34975
+ const key = sessionKeyForCloudSessionId(cloudSessionId);
34976
+ const prevRow = db.get(
34977
+ "SELECT acp_session_id, backend_agent_type, config_options_json, updated_at FROM agent_session WHERE session_key = ?",
34978
+ [key]
34979
+ );
34980
+ let prev = null;
34981
+ if (prevRow) {
34982
+ let configOptions = null;
34983
+ if (prevRow.config_options_json != null && prevRow.config_options_json !== "") {
34984
+ try {
34985
+ const parsed = JSON.parse(prevRow.config_options_json);
34986
+ configOptions = Array.isArray(parsed) ? parsed : null;
34987
+ } catch {
34988
+ configOptions = null;
34989
+ }
34990
+ }
34991
+ prev = {
34992
+ v: 1,
34993
+ acpSessionId: prevRow.acp_session_id,
34994
+ backendAgentType: prevRow.backend_agent_type,
34995
+ configOptions,
34996
+ updatedAt: prevRow.updated_at
34997
+ };
34998
+ }
34999
+ const next = {
35000
+ v: 1,
35001
+ acpSessionId: patch.acpSessionId !== void 0 ? patch.acpSessionId : prev?.acpSessionId ?? null,
35002
+ backendAgentType: patch.backendAgentType !== void 0 ? patch.backendAgentType : prev?.backendAgentType ?? null,
35003
+ configOptions: patch.configOptions !== void 0 ? patch.configOptions : prev?.configOptions ?? null,
35004
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
35005
+ };
35006
+ const configJson = next.configOptions != null ? JSON.stringify(next.configOptions) : null;
35007
+ db.run(
35008
+ `INSERT INTO agent_session (session_key, acp_session_id, backend_agent_type, config_options_json, updated_at)
35009
+ VALUES (?, ?, ?, ?, ?)
35010
+ ON CONFLICT(session_key) DO UPDATE SET
35011
+ acp_session_id = excluded.acp_session_id,
35012
+ backend_agent_type = excluded.backend_agent_type,
35013
+ config_options_json = excluded.config_options_json,
35014
+ updated_at = excluded.updated_at`,
35015
+ [key, next.acpSessionId, next.backendAgentType, configJson, next.updatedAt]
35016
+ );
35017
+ });
34865
35018
  } catch {
34866
35019
  }
34867
35020
  }
@@ -36444,41 +36597,45 @@ function getCwdHashForFileIndex(resolvedCwd) {
36444
36597
  // src/files/index/build-file-index.ts
36445
36598
  import path29 from "node:path";
36446
36599
 
36447
- // src/runtime/yield-to-event-loop.ts
36448
- function yieldToEventLoop() {
36449
- return new Promise((resolve20) => setImmediate(resolve20));
36450
- }
36451
-
36452
36600
  // src/files/index/walk-workspace-tree.ts
36453
36601
  import fs25 from "node:fs";
36454
36602
  import path28 from "node:path";
36455
36603
  function shouldSkipWorkspaceWalkEntry(name) {
36456
36604
  return name.startsWith(".");
36457
36605
  }
36458
- function walkWorkspaceTreeSync(dir, baseDir, onFile) {
36606
+ async function walkWorkspaceTreeAsync(dir, baseDir, onFile, state) {
36459
36607
  let names;
36460
36608
  try {
36461
- names = fs25.readdirSync(dir);
36609
+ names = await fs25.promises.readdir(dir);
36462
36610
  } catch {
36463
36611
  return;
36464
36612
  }
36465
36613
  for (const name of names) {
36466
36614
  if (shouldSkipWorkspaceWalkEntry(name)) continue;
36615
+ if (state.n > 0 && state.n % INDEX_WORK_YIELD_EVERY === 0) {
36616
+ if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
36617
+ await yieldToEventLoop();
36618
+ if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
36619
+ }
36620
+ state.n++;
36467
36621
  const full = path28.join(dir, name);
36468
36622
  let stat3;
36469
36623
  try {
36470
- stat3 = fs25.statSync(full);
36624
+ stat3 = await fs25.promises.stat(full);
36471
36625
  } catch {
36472
36626
  continue;
36473
36627
  }
36474
36628
  const relative5 = path28.relative(baseDir, full).replace(/\\/g, "/");
36475
36629
  if (stat3.isDirectory()) {
36476
- walkWorkspaceTreeSync(full, baseDir, onFile);
36630
+ await walkWorkspaceTreeAsync(full, baseDir, onFile, state);
36477
36631
  } else if (stat3.isFile()) {
36478
36632
  onFile(relative5);
36479
36633
  }
36480
36634
  }
36481
36635
  }
36636
+ function createWalkYieldState() {
36637
+ return { n: 0 };
36638
+ }
36482
36639
 
36483
36640
  // src/files/index/file-index-sqlite-lock.ts
36484
36641
  import fs26 from "node:fs";
@@ -36511,48 +36668,60 @@ function withFileIndexSqliteLock(fn) {
36511
36668
  }
36512
36669
 
36513
36670
  // src/files/index/build-file-index.ts
36514
- var FILE_INDEX_INSERT_BUFFER = 2048;
36515
- function persistFileIndexForResolvedCwd(resolved) {
36516
- const db = getCliDatabase();
36517
- const h = getCwdHashForFileIndex(resolved);
36518
- const buf = [];
36519
- let pathCount = 0;
36520
- db.run("BEGIN IMMEDIATE");
36521
- try {
36522
- db.run("DELETE FROM file_index_path WHERE cwd_hash = ?", [h]);
36523
- const ins = db.prepare("INSERT INTO file_index_path (cwd_hash, path) VALUES (?, ?)");
36671
+ var FILE_INDEX_INTERRUPT_CHECK_EVERY = 256;
36672
+ function assertNotShutdown() {
36673
+ if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
36674
+ }
36675
+ function persistFileIndexPaths(resolved, paths) {
36676
+ return withCliSqliteSync((db) => {
36677
+ const h = getCwdHashForFileIndex(resolved);
36678
+ let pathCount = 0;
36679
+ db.run("BEGIN IMMEDIATE");
36524
36680
  try {
36525
- const flushBuf = () => {
36526
- for (const rel of buf) {
36681
+ db.run("DELETE FROM file_index_path WHERE cwd_hash = ?", [h]);
36682
+ const ins = db.prepare("INSERT INTO file_index_path (cwd_hash, path) VALUES (?, ?)");
36683
+ try {
36684
+ let batch = 0;
36685
+ for (const rel of paths) {
36686
+ if (++batch >= FILE_INDEX_INTERRUPT_CHECK_EVERY) {
36687
+ batch = 0;
36688
+ assertNotShutdown();
36689
+ }
36527
36690
  ins.run([h, rel]);
36691
+ pathCount += 1;
36528
36692
  }
36529
- pathCount += buf.length;
36530
- buf.length = 0;
36531
- };
36532
- walkWorkspaceTreeSync(resolved, resolved, (rel) => {
36533
- buf.push(rel);
36534
- if (buf.length >= FILE_INDEX_INSERT_BUFFER) flushBuf();
36535
- });
36536
- flushBuf();
36537
- } finally {
36538
- ins.finalize();
36539
- }
36540
- db.run("COMMIT");
36541
- } catch (e) {
36542
- try {
36543
- db.run("ROLLBACK");
36544
- } catch {
36693
+ } finally {
36694
+ ins.finalize();
36695
+ }
36696
+ db.run("COMMIT");
36697
+ } catch (e) {
36698
+ try {
36699
+ db.run("ROLLBACK");
36700
+ } catch {
36701
+ }
36702
+ throw e;
36545
36703
  }
36546
- throw e;
36547
- }
36548
- return pathCount;
36704
+ return pathCount;
36705
+ });
36706
+ }
36707
+ async function collectWorkspacePathsAsync(resolved) {
36708
+ const paths = [];
36709
+ const state = createWalkYieldState();
36710
+ await walkWorkspaceTreeAsync(resolved, resolved, (rel) => {
36711
+ assertNotShutdown();
36712
+ paths.push(rel);
36713
+ }, state);
36714
+ return paths;
36549
36715
  }
36550
36716
  async function buildFileIndexAsync(cwd) {
36551
36717
  return withFileIndexSqliteLock(async () => {
36552
36718
  const resolved = path29.resolve(cwd);
36553
36719
  await yieldToEventLoop();
36554
- const pathCount = persistFileIndexForResolvedCwd(resolved);
36720
+ assertNotShutdown();
36721
+ const paths = await collectWorkspacePathsAsync(resolved);
36555
36722
  await yieldToEventLoop();
36723
+ assertNotShutdown();
36724
+ const pathCount = persistFileIndexPaths(resolved, paths);
36556
36725
  return { pathCount };
36557
36726
  });
36558
36727
  }
@@ -36569,23 +36738,20 @@ function sqliteExprBridgeFileIndexDependencyRank() {
36569
36738
  function escapeLikePattern(fragment) {
36570
36739
  return fragment.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
36571
36740
  }
36572
- function bridgeFileIndexIsPopulated(resolvedCwd) {
36573
- const db = getCliDatabase();
36741
+ function bridgeFileIndexIsPopulatedWithDb(resolvedCwd, db) {
36574
36742
  const h = getCwdHashForFileIndex(resolvedCwd);
36575
36743
  const row = db.get("SELECT 1 as ok FROM file_index_path WHERE cwd_hash = ? LIMIT 1", [h]);
36576
36744
  return row != null;
36577
36745
  }
36578
- function bridgeFileIndexPathCount(resolvedCwd) {
36579
- const db = getCliDatabase();
36746
+ function bridgeFileIndexPathCountWithDb(resolvedCwd, db) {
36580
36747
  const h = getCwdHashForFileIndex(resolvedCwd);
36581
36748
  const row = db.get("SELECT COUNT(*) as c FROM file_index_path WHERE cwd_hash = ?", [h]);
36582
36749
  const c = row?.c ?? 0;
36583
36750
  return Number(c);
36584
36751
  }
36585
- function searchBridgeFilePaths(resolvedCwd, query, limit = 100) {
36752
+ function searchBridgeFilePathsWithDb(resolvedCwd, query, limit, db) {
36586
36753
  const q = query.trim().toLowerCase();
36587
36754
  if (!q) return [];
36588
- const db = getCliDatabase();
36589
36755
  const h = getCwdHashForFileIndex(resolvedCwd);
36590
36756
  const pattern = `%${escapeLikePattern(q)}%`;
36591
36757
  const lim = Math.max(0, Math.min(1e4, Math.floor(limit)));
@@ -36596,9 +36762,15 @@ function searchBridgeFilePaths(resolvedCwd, query, limit = 100) {
36596
36762
  );
36597
36763
  return rows.map((r) => String(r.path));
36598
36764
  }
36765
+ async function bridgeFileIndexIsPopulated(resolvedCwd) {
36766
+ return withCliSqlite((db) => bridgeFileIndexIsPopulatedWithDb(resolvedCwd, db));
36767
+ }
36768
+ async function bridgeFileIndexPathCount(resolvedCwd) {
36769
+ return withCliSqlite((db) => bridgeFileIndexPathCountWithDb(resolvedCwd, db));
36770
+ }
36599
36771
  async function searchBridgeFilePathsAsync(resolvedCwd, query, limit = 100) {
36600
36772
  await yieldToEventLoop();
36601
- const out = searchBridgeFilePaths(resolvedCwd, query, limit);
36773
+ const out = await withCliSqlite((db) => searchBridgeFilePathsWithDb(resolvedCwd, query, limit, db));
36602
36774
  if (out.length >= INDEX_WORK_YIELD_EVERY) await yieldToEventLoop();
36603
36775
  return out;
36604
36776
  }
@@ -36606,8 +36778,8 @@ async function searchBridgeFilePathsAsync(resolvedCwd, query, limit = 100) {
36606
36778
  // src/files/index/ensure-file-index.ts
36607
36779
  async function ensureFileIndexAsync(cwd) {
36608
36780
  const resolved = path30.resolve(cwd);
36609
- if (bridgeFileIndexIsPopulated(resolved)) {
36610
- return { fromCache: true, pathCount: bridgeFileIndexPathCount(resolved) };
36781
+ if (await bridgeFileIndexIsPopulated(resolved)) {
36782
+ return { fromCache: true, pathCount: await bridgeFileIndexPathCount(resolved) };
36611
36783
  }
36612
36784
  return { ...await buildFileIndexAsync(resolved), fromCache: false };
36613
36785
  }
@@ -36653,11 +36825,13 @@ function createFsWatcher(resolved, schedule) {
36653
36825
  function startFileIndexWatcher(cwd = getBridgeRoot()) {
36654
36826
  const resolved = path31.resolve(cwd);
36655
36827
  void buildFileIndexAsync(resolved).catch((e) => {
36828
+ if (e instanceof CliSqliteInterrupted) return;
36656
36829
  console.error("[file-index] Initial index build failed:", e);
36657
36830
  });
36658
36831
  let timer = null;
36659
36832
  const runRebuild = () => {
36660
36833
  void buildFileIndexAsync(resolved).catch((e) => {
36834
+ if (e instanceof CliSqliteInterrupted) return;
36661
36835
  console.error("[file-index] Watch rebuild failed:", e);
36662
36836
  });
36663
36837
  };
@@ -37191,9 +37365,6 @@ var StreamTail = class {
37191
37365
  }
37192
37366
  };
37193
37367
 
37194
- // src/dev-servers/manager/dev-server-constants.ts
37195
- var BRIDGE_SHUTDOWN_GRACE_MS = 8e3;
37196
-
37197
37368
  // src/dev-servers/manager/dev-server-firehose-messages.ts
37198
37369
  function buildFirehoseSnapshotMessage(params) {
37199
37370
  const payload = {
@@ -37479,22 +37650,23 @@ var DevServerManager = class {
37479
37650
  }
37480
37651
  this.start(serverId);
37481
37652
  }
37482
- async shutdownAllGraceful() {
37653
+ async shutdownAllGraceful(opts) {
37654
+ const graceMs = opts?.graceMs ?? BRIDGE_SHUTDOWN_GRACE_MS;
37483
37655
  const pairs = [...this.processes.entries()];
37484
37656
  if (pairs.length === 0) return;
37485
37657
  this.log(
37486
37658
  `[dev-server] Stopping ${pairs.length} local dev server process${pairs.length === 1 ? "" : "es"}\u2026`
37487
37659
  );
37488
- await Promise.all(pairs.map(([serverId, proc]) => this.gracefulTerminateOrUnknown(serverId, proc)));
37660
+ await Promise.all(pairs.map(([serverId, proc]) => this.gracefulTerminateOrUnknown(serverId, proc, graceMs)));
37489
37661
  }
37490
- async gracefulTerminateOrUnknown(serverId, proc) {
37662
+ async gracefulTerminateOrUnknown(serverId, proc, graceMs) {
37491
37663
  const shortId = `${serverId.slice(0, 8)}\u2026`;
37492
- await sigtermAndWaitForExit(proc, BRIDGE_SHUTDOWN_GRACE_MS, this.log, shortId);
37664
+ await sigtermAndWaitForExit(proc, graceMs, this.log, shortId);
37493
37665
  if (!this.processes.has(serverId) || this.processes.get(serverId) !== proc) {
37494
37666
  return;
37495
37667
  }
37496
37668
  this.bumpGeneration(serverId);
37497
- forceKillChild(proc, this.log, shortId, BRIDGE_SHUTDOWN_GRACE_MS);
37669
+ forceKillChild(proc, this.log, shortId, graceMs);
37498
37670
  this.processes.delete(serverId);
37499
37671
  this.clearPoll(serverId);
37500
37672
  this.pipedCaptureByServerId.delete(serverId);
@@ -38089,10 +38261,13 @@ var LOCAL_AGENT_ACP_MODULES = [
38089
38261
  ];
38090
38262
  async function detectLocalAgentTypes() {
38091
38263
  try {
38264
+ if (isCliImmediateShutdownRequested()) return [];
38092
38265
  const out = [];
38093
38266
  for (let i = 0; i < LOCAL_AGENT_ACP_MODULES.length; i++) {
38267
+ if (isCliImmediateShutdownRequested()) return out;
38094
38268
  if (i > 0) {
38095
38269
  await yieldToEventLoop();
38270
+ if (isCliImmediateShutdownRequested()) return out;
38096
38271
  }
38097
38272
  const mod = LOCAL_AGENT_ACP_MODULES[i];
38098
38273
  try {
@@ -38125,6 +38300,7 @@ function createSendLocalSkillsReport(getWs, logFn) {
38125
38300
  }
38126
38301
  function createReportAutoDetectedAgents(getWs, logFn) {
38127
38302
  return async () => {
38303
+ if (isCliImmediateShutdownRequested()) return;
38128
38304
  try {
38129
38305
  const types = await detectLocalAgentTypes();
38130
38306
  const socket = getWs();
@@ -38224,6 +38400,7 @@ var handleBridgeIdentified = (msg, deps) => {
38224
38400
  });
38225
38401
  setImmediate(() => {
38226
38402
  void (async () => {
38403
+ if (isCliImmediateShutdownRequested()) return;
38227
38404
  try {
38228
38405
  await deps.reportAutoDetectedAgents?.();
38229
38406
  } catch (e) {
@@ -38231,6 +38408,7 @@ var handleBridgeIdentified = (msg, deps) => {
38231
38408
  `[Bridge service] Auto-detect agents failed: ${e instanceof Error ? e.message : String(e)}`
38232
38409
  );
38233
38410
  }
38411
+ if (isCliImmediateShutdownRequested()) return;
38234
38412
  try {
38235
38413
  await deps.warmupAgentCapabilitiesOnConnect?.();
38236
38414
  } catch (e) {
@@ -38241,6 +38419,7 @@ var handleBridgeIdentified = (msg, deps) => {
38241
38419
  })();
38242
38420
  });
38243
38421
  setImmediate(() => {
38422
+ if (isCliImmediateShutdownRequested()) return;
38244
38423
  try {
38245
38424
  deps.sendLocalSkillsReport?.();
38246
38425
  } catch (e) {
@@ -38288,30 +38467,32 @@ var MERGEABLE_SERVER_STATES = /* @__PURE__ */ new Set([
38288
38467
  "stopping",
38289
38468
  "discarded"
38290
38469
  ]);
38291
- function readPersistedQueue(queueKey) {
38292
- const db = getCliDatabase();
38293
- const row = db.get("SELECT queue_key, updated_at, turns_json FROM prompt_queue WHERE queue_key = ?", [
38294
- queueKey
38295
- ]);
38296
- if (!row) return null;
38297
- try {
38298
- const turns = JSON.parse(row.turns_json);
38299
- if (!Array.isArray(turns)) return null;
38300
- return { queueKey: row.queue_key, updatedAt: row.updated_at, turns };
38301
- } catch {
38302
- return null;
38303
- }
38470
+ async function readPersistedQueue(queueKey) {
38471
+ return withCliSqlite((db) => {
38472
+ const row = db.get("SELECT queue_key, updated_at, turns_json FROM prompt_queue WHERE queue_key = ?", [
38473
+ queueKey
38474
+ ]);
38475
+ if (!row) return null;
38476
+ try {
38477
+ const turns = JSON.parse(row.turns_json);
38478
+ if (!Array.isArray(turns)) return null;
38479
+ return { queueKey: row.queue_key, updatedAt: row.updated_at, turns };
38480
+ } catch {
38481
+ return null;
38482
+ }
38483
+ });
38304
38484
  }
38305
- function writePersistedQueue(file2) {
38306
- const db = getCliDatabase();
38307
- db.run(
38308
- `INSERT INTO prompt_queue (queue_key, updated_at, turns_json) VALUES (?, ?, ?)
38309
- ON CONFLICT(queue_key) DO UPDATE SET updated_at = excluded.updated_at, turns_json = excluded.turns_json`,
38310
- [file2.queueKey, file2.updatedAt, JSON.stringify(file2.turns)]
38311
- );
38485
+ async function writePersistedQueue(file2) {
38486
+ await withCliSqlite((db) => {
38487
+ db.run(
38488
+ `INSERT INTO prompt_queue (queue_key, updated_at, turns_json) VALUES (?, ?, ?)
38489
+ ON CONFLICT(queue_key) DO UPDATE SET updated_at = excluded.updated_at, turns_json = excluded.turns_json`,
38490
+ [file2.queueKey, file2.updatedAt, JSON.stringify(file2.turns)]
38491
+ );
38492
+ });
38312
38493
  }
38313
- function mergeServerQueueSnapshot(queueKey, serverTurns) {
38314
- const prev = readPersistedQueue(queueKey);
38494
+ async function mergeServerQueueSnapshot(queueKey, serverTurns) {
38495
+ const prev = await readPersistedQueue(queueKey);
38315
38496
  const turns = [];
38316
38497
  for (const raw of serverTurns) {
38317
38498
  if (!raw || typeof raw !== "object") continue;
@@ -38408,12 +38589,12 @@ async function applyPromptQueueStateFromServer(msg, deps) {
38408
38589
  const getWs = deps.getWs;
38409
38590
  for (const [queueKey, serverTurns] of Object.entries(raw)) {
38410
38591
  if (!Array.isArray(serverTurns)) continue;
38411
- const file2 = mergeServerQueueSnapshot(queueKey, serverTurns);
38412
- writePersistedQueue(file2);
38592
+ const file2 = await mergeServerQueueSnapshot(queueKey, serverTurns);
38593
+ await writePersistedQueue(file2);
38413
38594
  }
38414
38595
  for (const [queueKey, serverTurns] of Object.entries(raw)) {
38415
38596
  if (!Array.isArray(serverTurns)) continue;
38416
- const file2 = readPersistedQueue(queueKey);
38597
+ const file2 = await readPersistedQueue(queueKey);
38417
38598
  if (!file2) continue;
38418
38599
  for (const running of file2.turns.filter((t) => t.lastClientState === "running")) {
38419
38600
  runIdToQueueKey.set(running.turnId, queueKey);
@@ -38421,7 +38602,7 @@ async function applyPromptQueueStateFromServer(msg, deps) {
38421
38602
  }
38422
38603
  for (const [queueKey, serverTurns] of Object.entries(raw)) {
38423
38604
  if (!Array.isArray(serverTurns)) continue;
38424
- const file2 = readPersistedQueue(queueKey);
38605
+ const file2 = await readPersistedQueue(queueKey);
38425
38606
  if (!file2) continue;
38426
38607
  const cancelRow = file2.turns.find((t) => t.serverState === "cancel_requested" && t.lastClientState === "running");
38427
38608
  if (cancelRow) {
@@ -38430,7 +38611,7 @@ async function applyPromptQueueStateFromServer(msg, deps) {
38430
38611
  deps.log(
38431
38612
  `[Queue] server cancel_requested for ${cancelRow.turnId.slice(0, 8)}\u2026 but no local agent run is active (e.g. after CLI restart); marking cancelled and notifying bridge.`
38432
38613
  );
38433
- finalizePromptTurnOnBridge(deps.getWs, cancelRow.turnId, false, { terminalClientState: "cancelled" });
38614
+ await finalizePromptTurnOnBridge(deps.getWs, cancelRow.turnId, false, { terminalClientState: "cancelled" });
38434
38615
  const ws = deps.getWs();
38435
38616
  if (ws && cancelRow.sessionId) {
38436
38617
  sendWsMessage(ws, {
@@ -38449,19 +38630,19 @@ async function applyPromptQueueStateFromServer(msg, deps) {
38449
38630
  const startedThisTick = /* @__PURE__ */ new Set();
38450
38631
  for (const [queueKey, serverTurns] of Object.entries(raw)) {
38451
38632
  if (!Array.isArray(serverTurns)) continue;
38452
- const file2 = readPersistedQueue(queueKey);
38633
+ const file2 = await readPersistedQueue(queueKey);
38453
38634
  if (!file2) continue;
38454
38635
  if (hasRunningTurn(file2.turns)) continue;
38455
38636
  const next = pickNextRunnableTurn(file2.turns);
38456
38637
  if (!next) continue;
38457
38638
  if (!await runLocalRevertBeforeQueuedPrompt(next, deps)) {
38458
38639
  next.lastClientState = "failed";
38459
- writePersistedQueue(file2);
38640
+ await writePersistedQueue(file2);
38460
38641
  sendPromptQueueClientReport(getWs(), { [queueKey]: [{ turnId: next.turnId, clientState: "failed" }] });
38461
38642
  continue;
38462
38643
  }
38463
38644
  next.lastClientState = "running";
38464
- writePersistedQueue(file2);
38645
+ await writePersistedQueue(file2);
38465
38646
  runIdToQueueKey.set(next.turnId, queueKey);
38466
38647
  startedThisTick.add(next.turnId);
38467
38648
  report[queueKey] = [{ turnId: next.turnId, clientState: "running" }];
@@ -38471,7 +38652,7 @@ async function applyPromptQueueStateFromServer(msg, deps) {
38471
38652
  }
38472
38653
  for (const [queueKey, serverTurns] of Object.entries(raw)) {
38473
38654
  if (!Array.isArray(serverTurns)) continue;
38474
- const file2 = readPersistedQueue(queueKey);
38655
+ const file2 = await readPersistedQueue(queueKey);
38475
38656
  if (!file2) continue;
38476
38657
  const running = file2.turns.find((t) => t.lastClientState === "running");
38477
38658
  if (!running || !startedThisTick.has(running.turnId)) continue;
@@ -38479,17 +38660,17 @@ async function applyPromptQueueStateFromServer(msg, deps) {
38479
38660
  dispatchLocalPrompt(running, deps);
38480
38661
  }
38481
38662
  }
38482
- function finalizePromptTurnOnBridge(getWs, runId, success2, opts) {
38663
+ async function finalizePromptTurnOnBridge(getWs, runId, success2, opts) {
38483
38664
  if (!runId) return false;
38484
38665
  const queueKey = runIdToQueueKey.get(runId);
38485
38666
  runIdToQueueKey.delete(runId);
38486
38667
  if (!queueKey) return false;
38487
- const f = readPersistedQueue(queueKey);
38668
+ const f = await readPersistedQueue(queueKey);
38488
38669
  if (!f) return false;
38489
38670
  const t = f.turns.find((x) => x.turnId === runId);
38490
38671
  if (!t) return false;
38491
38672
  t.lastClientState = opts?.terminalClientState ?? (success2 ? "stopped" : "failed");
38492
- writePersistedQueue(f);
38673
+ await writePersistedQueue(f);
38493
38674
  sendPromptQueueClientReport(getWs(), { [queueKey]: [{ turnId: runId, clientState: t.lastClientState }] });
38494
38675
  return true;
38495
38676
  }
@@ -38510,12 +38691,13 @@ function createBridgePromptSenders(deps, getWs) {
38510
38691
  if (result.type === "prompt_result") {
38511
38692
  const pr = result;
38512
38693
  const cancelled = pr.stopReason === "cancelled";
38513
- finalizePromptTurnOnBridge(
38694
+ void finalizePromptTurnOnBridge(
38514
38695
  getWs,
38515
38696
  typeof pr.runId === "string" ? pr.runId : void 0,
38516
38697
  pr.success === true,
38517
38698
  cancelled ? { terminalClientState: "cancelled" } : void 0
38518
- );
38699
+ ).catch(() => {
38700
+ });
38519
38701
  }
38520
38702
  };
38521
38703
  const sendSessionUpdate = (payload) => {
@@ -38539,12 +38721,12 @@ function createBridgePromptSenders(deps, getWs) {
38539
38721
  }
38540
38722
 
38541
38723
  // src/agents/acp/from-bridge/bridge-prompt-preamble.ts
38542
- import { execFile as execFile10 } from "node:child_process";
38543
- import { promisify as promisify10 } from "node:util";
38544
- var execFileAsync9 = promisify10(execFile10);
38724
+ import { execFile as execFile9 } from "node:child_process";
38725
+ import { promisify as promisify9 } from "node:util";
38726
+ var execFileAsync8 = promisify9(execFile9);
38545
38727
  async function readGitBranch(cwd) {
38546
38728
  try {
38547
- const { stdout } = await execFileAsync9("git", ["branch", "--show-current"], { cwd, maxBuffer: 64 * 1024 });
38729
+ const { stdout } = await execFileAsync8("git", ["branch", "--show-current"], { cwd, maxBuffer: 64 * 1024 });
38548
38730
  const b = stdout.trim();
38549
38731
  return b || null;
38550
38732
  } catch {
@@ -39248,7 +39430,7 @@ function handleFileBrowserSearch(msg, socket, e2ee) {
39248
39430
  await yieldToEventLoop();
39249
39431
  const q = typeof msg.q === "string" ? msg.q : "";
39250
39432
  const cwd = path37.resolve(getBridgeRoot());
39251
- if (!bridgeFileIndexIsPopulated(cwd)) {
39433
+ if (!await bridgeFileIndexIsPopulated(cwd)) {
39252
39434
  const payload2 = {
39253
39435
  type: "file_browser_search_response",
39254
39436
  id: msg.id,
@@ -40009,7 +40191,8 @@ import * as path40 from "node:path";
40009
40191
  // src/agents/capabilities/probe-one-agent-type-for-capabilities.ts
40010
40192
  import * as path39 from "node:path";
40011
40193
  async function probeOneAgentTypeForCapabilities(params) {
40012
- const { agentType, cwd, workspaceId, log: log2, getDb, reportAgentCapabilities, bridgeReport = true } = params;
40194
+ const { agentType, cwd, workspaceId, log: log2, reportAgentCapabilities, bridgeReport = true } = params;
40195
+ if (isCliImmediateShutdownRequested()) return false;
40013
40196
  const resolved = resolveAgentCommand(agentType);
40014
40197
  if (!resolved) return false;
40015
40198
  let sqliteChanged = false;
@@ -40020,11 +40203,13 @@ async function probeOneAgentTypeForCapabilities(params) {
40020
40203
  reportedRef.done = true;
40021
40204
  let changed = false;
40022
40205
  try {
40023
- changed = upsertCliAgentCapabilityCache(getDb(), {
40024
- workspaceId,
40025
- agentType,
40026
- configOptions: co
40027
- });
40206
+ changed = withCliSqliteSync(
40207
+ (db) => upsertCliAgentCapabilityCache(db, {
40208
+ workspaceId,
40209
+ agentType,
40210
+ configOptions: co
40211
+ })
40212
+ );
40028
40213
  } catch {
40029
40214
  }
40030
40215
  sqliteChanged ||= changed;
@@ -40041,6 +40226,7 @@ async function probeOneAgentTypeForCapabilities(params) {
40041
40226
  }, 28e3);
40042
40227
  killTimer.unref?.();
40043
40228
  try {
40229
+ if (isCliImmediateShutdownRequested()) return false;
40044
40230
  handle = await resolved.createClient({
40045
40231
  command: resolved.command,
40046
40232
  cwd: path39.resolve(cwd),
@@ -40060,7 +40246,7 @@ async function probeOneAgentTypeForCapabilities(params) {
40060
40246
  onSessionUpdate: () => {
40061
40247
  }
40062
40248
  });
40063
- await new Promise((r) => setTimeout(r, 1200));
40249
+ if (!await delayMsUnlessShutdownRequested(1200)) return false;
40064
40250
  } catch (e) {
40065
40251
  log2(
40066
40252
  `[Bridge service] Agent capability probe (${agentType}): ${e instanceof Error ? e.message : String(e)}`
@@ -40082,19 +40268,19 @@ async function probeAgentCapabilitiesForDetectedTypes(params) {
40082
40268
  cwd,
40083
40269
  workspaceId,
40084
40270
  log: log2,
40085
- getDb,
40086
40271
  reportAgentCapabilities,
40087
40272
  bridgeReport = true,
40088
40273
  forceAllTypes = false
40089
40274
  } = params;
40090
40275
  let changedCount = 0;
40091
40276
  for (let i = 0; i < agentTypes.length; i++) {
40277
+ if (isCliImmediateShutdownRequested()) return changedCount;
40092
40278
  if (i > 0) await yieldToEventLoop();
40093
40279
  const agentType = agentTypes[i];
40094
40280
  if (!agentType.trim()) continue;
40095
40281
  if (!forceAllTypes) {
40096
40282
  try {
40097
- if (process.env.BUILDAUTOMATON_FORCE_PROBE_ACP_CAPABILITIES !== "1" && hasNonEmptyAgentCapabilityCache(getDb(), workspaceId, agentType)) {
40283
+ if (process.env.BUILDAUTOMATON_FORCE_PROBE_ACP_CAPABILITIES !== "1" && await withCliSqlite((db) => hasNonEmptyAgentCapabilityCache(db, workspaceId, agentType))) {
40098
40284
  continue;
40099
40285
  }
40100
40286
  } catch {
@@ -40105,7 +40291,6 @@ async function probeAgentCapabilitiesForDetectedTypes(params) {
40105
40291
  cwd,
40106
40292
  workspaceId,
40107
40293
  log: log2,
40108
- getDb,
40109
40294
  reportAgentCapabilities,
40110
40295
  bridgeReport
40111
40296
  });
@@ -40116,60 +40301,64 @@ async function probeAgentCapabilitiesForDetectedTypes(params) {
40116
40301
 
40117
40302
  // src/agents/capabilities/warmup-agent-capabilities-on-connect.ts
40118
40303
  async function warmupAgentCapabilitiesOnConnect(params) {
40119
- const { workspaceId, log: log2, getDb, getWs } = params;
40304
+ const { workspaceId, log: log2, getWs } = params;
40305
+ if (isCliImmediateShutdownRequested()) return;
40120
40306
  const cwd = path40.resolve(getBridgeRoot());
40121
- const db = getDb();
40122
- function sendBatchFromCache() {
40307
+ async function sendBatchFromCache() {
40123
40308
  const socket = getWs();
40124
40309
  if (!socket || socket.readyState !== wrapper_default.OPEN) return;
40125
40310
  try {
40126
- const rows = listCliAgentCapabilityCacheForWorkspace(db, workspaceId);
40311
+ const rows = await withCliSqlite((db) => listCliAgentCapabilityCacheForWorkspace(db, workspaceId));
40127
40312
  if (rows.length === 0) return;
40128
40313
  sendWsMessage(socket, {
40129
40314
  type: "agent_capabilities_batch",
40130
40315
  items: rows.map((r) => ({ agentType: r.agentType, configOptions: r.configOptions }))
40131
40316
  });
40132
40317
  } catch (e) {
40318
+ if (e instanceof CliSqliteInterrupted) return;
40133
40319
  log2(
40134
40320
  `[Bridge service] Agent capability batch to bridge failed: ${e instanceof Error ? e.message : String(e)}`
40135
40321
  );
40136
40322
  }
40137
40323
  }
40138
- sendBatchFromCache();
40324
+ await sendBatchFromCache();
40325
+ if (isCliImmediateShutdownRequested()) return;
40139
40326
  let types = [];
40140
40327
  try {
40141
40328
  types = [...await detectLocalAgentTypes()];
40142
40329
  } catch (e) {
40143
40330
  log2(`[Bridge service] detectLocalAgentTypes failed: ${e instanceof Error ? e.message : String(e)}`);
40144
40331
  }
40332
+ if (isCliImmediateShutdownRequested()) return;
40145
40333
  try {
40146
40334
  const n = await probeAgentCapabilitiesForDetectedTypes({
40147
40335
  agentTypes: types,
40148
40336
  cwd,
40149
40337
  workspaceId,
40150
40338
  log: log2,
40151
- getDb,
40152
40339
  bridgeReport: false,
40153
40340
  forceAllTypes: false
40154
40341
  });
40155
- if (n > 0) sendBatchFromCache();
40342
+ if (n > 0) await sendBatchFromCache();
40156
40343
  } catch (e) {
40344
+ if (e instanceof CliSqliteInterrupted) return;
40157
40345
  log2(`[Bridge service] Agent capability probe (missing cache) failed: ${e instanceof Error ? e.message : String(e)}`);
40158
40346
  }
40159
40347
  void (async () => {
40160
40348
  try {
40161
40349
  await yieldToEventLoop();
40350
+ if (isCliImmediateShutdownRequested()) return;
40162
40351
  const n = await probeAgentCapabilitiesForDetectedTypes({
40163
40352
  agentTypes: types,
40164
40353
  cwd,
40165
40354
  workspaceId,
40166
40355
  log: log2,
40167
- getDb,
40168
40356
  bridgeReport: false,
40169
40357
  forceAllTypes: true
40170
40358
  });
40171
- if (n > 0) sendBatchFromCache();
40359
+ if (n > 0) await sendBatchFromCache();
40172
40360
  } catch (e) {
40361
+ if (e instanceof CliSqliteInterrupted) return;
40173
40362
  log2(`[Bridge service] Agent capability lazy refresh failed: ${e instanceof Error ? e.message : String(e)}`);
40174
40363
  }
40175
40364
  })();
@@ -40180,7 +40369,7 @@ async function createBridgeConnection(options) {
40180
40369
  const { apiUrl, workspaceId, justAuthenticated, onAuthInvalid, persistTokens } = options;
40181
40370
  const firehoseServerUrl = options.firehoseServerUrl ?? options.proxyServerUrl;
40182
40371
  const logFn = options.log ?? log;
40183
- getCliDatabase({ logLegacyMigration: logFn });
40372
+ await ensureCliSqliteInitialized({ logLegacyMigration: logFn });
40184
40373
  const tokens = {
40185
40374
  accessToken: options.authToken,
40186
40375
  refreshToken: options.refreshToken
@@ -40210,12 +40399,15 @@ async function createBridgeConnection(options) {
40210
40399
  if (!Array.isArray(info.configOptions) || info.configOptions.length === 0) return;
40211
40400
  let changed = false;
40212
40401
  try {
40213
- changed = upsertCliAgentCapabilityCache(getCliDatabase(), {
40214
- workspaceId,
40215
- agentType: info.agentType,
40216
- configOptions: info.configOptions
40217
- });
40218
- } catch {
40402
+ changed = withCliSqliteSync(
40403
+ (db) => upsertCliAgentCapabilityCache(db, {
40404
+ workspaceId,
40405
+ agentType: info.agentType,
40406
+ configOptions: info.configOptions
40407
+ })
40408
+ );
40409
+ } catch (e) {
40410
+ if (e instanceof CliSqliteInterrupted) return;
40219
40411
  }
40220
40412
  if (!changed) return;
40221
40413
  const socket = getWs();
@@ -40267,7 +40459,6 @@ async function createBridgeConnection(options) {
40267
40459
  await warmupAgentCapabilitiesOnConnect({
40268
40460
  workspaceId,
40269
40461
  log: logFn,
40270
- getDb: getCliDatabase,
40271
40462
  getWs
40272
40463
  });
40273
40464
  },
@@ -40299,6 +40490,7 @@ async function createBridgeConnection(options) {
40299
40490
  const stopFileIndexWatcher = startFileIndexWatcher(getBridgeRoot());
40300
40491
  return {
40301
40492
  close: async () => {
40493
+ requestCliImmediateShutdown();
40302
40494
  stopFileIndexWatcher();
40303
40495
  bridgeHeartbeat.stop();
40304
40496
  await closeBridgeConnection(state, acpManager, devServerManager, logFn);
@@ -40372,47 +40564,60 @@ async function runConnectedBridge(options, restartWithoutAuth) {
40372
40564
  } = options;
40373
40565
  const firehoseServerUrl = options.firehoseServerUrl ?? options.proxyServerUrl;
40374
40566
  let cleanupKeyCommand;
40375
- const handle = await createBridgeConnection({
40376
- apiUrl,
40377
- workspaceId,
40378
- authToken,
40379
- refreshToken,
40380
- firehoseServerUrl,
40381
- justAuthenticated,
40382
- worktreesRootPath,
40383
- e2eCertificate,
40384
- log,
40385
- persistTokens: (t) => {
40386
- writeConfigForApi(apiUrl, {
40387
- workspaceId,
40388
- token: t.token,
40389
- refreshToken: t.refreshToken
40390
- });
40391
- },
40392
- onAuthInvalid: () => {
40393
- cleanupKeyCommand?.();
40394
- log("[Bridge service] Access token invalid or revoked; re-authenticating\u2026");
40395
- clearConfigForApi(apiUrl);
40396
- void handle.close().then(() => {
40397
- void restartWithoutAuth({ apiUrl, firehoseServerUrl, worktreesRootPath, e2eCertificate });
40398
- });
40399
- }
40400
- });
40567
+ let bridgeClose = null;
40401
40568
  const onSignal = (kind) => {
40569
+ requestCliImmediateShutdown();
40402
40570
  cleanupKeyCommand?.();
40403
40571
  logImmediate(
40404
40572
  kind === "interrupt" ? "Keyboard interrupt (Ctrl+C) \u2014 stopping\u2026" : "Stop requested \u2014 shutting down\u2026"
40405
40573
  );
40406
- setImmediate(() => {
40407
- void handle.close().then(() => {
40408
- process.exit(0);
40409
- });
40410
- });
40574
+ if (bridgeClose) {
40575
+ void bridgeClose().then(() => process.exit(0));
40576
+ return;
40577
+ }
40578
+ process.exit(0);
40411
40579
  };
40412
40580
  const onSigInt = () => onSignal("interrupt");
40413
40581
  const onSigTerm = () => onSignal("stop");
40414
40582
  process.on("SIGINT", onSigInt);
40415
40583
  process.on("SIGTERM", onSigTerm);
40584
+ let handle;
40585
+ try {
40586
+ handle = await createBridgeConnection({
40587
+ apiUrl,
40588
+ workspaceId,
40589
+ authToken,
40590
+ refreshToken,
40591
+ firehoseServerUrl,
40592
+ justAuthenticated,
40593
+ worktreesRootPath,
40594
+ e2eCertificate,
40595
+ log,
40596
+ persistTokens: (t) => {
40597
+ writeConfigForApi(apiUrl, {
40598
+ workspaceId,
40599
+ token: t.token,
40600
+ refreshToken: t.refreshToken
40601
+ });
40602
+ },
40603
+ onAuthInvalid: () => {
40604
+ cleanupKeyCommand?.();
40605
+ log("[Bridge service] Access token invalid or revoked; re-authenticating\u2026");
40606
+ clearConfigForApi(apiUrl);
40607
+ void handle.close().then(() => {
40608
+ void restartWithoutAuth({ apiUrl, firehoseServerUrl, worktreesRootPath, e2eCertificate });
40609
+ });
40610
+ }
40611
+ });
40612
+ } catch (e) {
40613
+ process.off("SIGINT", onSigInt);
40614
+ process.off("SIGTERM", onSigTerm);
40615
+ if (e instanceof CliSqliteInterrupted) {
40616
+ process.exit(0);
40617
+ }
40618
+ throw e;
40619
+ }
40620
+ bridgeClose = () => handle.close();
40416
40621
  if (e2eCertificate) {
40417
40622
  let openingCertificate = false;
40418
40623
  cleanupKeyCommand = installE2eCertificateKeyCommand({
@@ -40457,6 +40662,7 @@ async function runBridge(options) {
40457
40662
  }
40458
40663
  });
40459
40664
  const onSignal = (kind) => {
40665
+ requestCliImmediateShutdown();
40460
40666
  logImmediate(
40461
40667
  kind === "interrupt" ? "Keyboard interrupt (Ctrl+C) \u2014 stopping\u2026" : "Stop requested \u2014 shutting down\u2026"
40462
40668
  );