@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/index.js CHANGED
@@ -23962,7 +23962,7 @@ function installBridgeProcessResilience() {
23962
23962
  }
23963
23963
 
23964
23964
  // src/cli-version.ts
23965
- var CLI_VERSION = "0.1.31".length > 0 ? "0.1.31" : "0.0.0-dev";
23965
+ var CLI_VERSION = "0.1.33".length > 0 ? "0.1.33" : "0.0.0-dev";
23966
23966
 
23967
23967
  // src/connection/heartbeat/constants.ts
23968
23968
  var BRIDGE_APP_HEARTBEAT_INTERVAL_MS = 1e4;
@@ -25022,9 +25022,38 @@ function runPendingAuth(options) {
25022
25022
  };
25023
25023
  }
25024
25024
 
25025
+ // src/dev-servers/manager/dev-server-constants.ts
25026
+ var BRIDGE_CLOSE_DEV_SERVER_GRACE_MS = 0;
25027
+ var BRIDGE_SHUTDOWN_GRACE_MS = 8e3;
25028
+
25029
+ // src/runtime/cli-process-interrupt.ts
25030
+ var cliImmediateShutdownRequested = false;
25031
+ function requestCliImmediateShutdown() {
25032
+ cliImmediateShutdownRequested = true;
25033
+ }
25034
+ function isCliImmediateShutdownRequested() {
25035
+ return cliImmediateShutdownRequested;
25036
+ }
25037
+ async function delayMsUnlessShutdownRequested(ms) {
25038
+ if (ms <= 0) return true;
25039
+ const end = Date.now() + ms;
25040
+ while (Date.now() < end) {
25041
+ if (isCliImmediateShutdownRequested()) return false;
25042
+ const chunk = Math.min(50, end - Date.now());
25043
+ if (chunk <= 0) break;
25044
+ await new Promise((r) => setTimeout(r, chunk));
25045
+ }
25046
+ return !isCliImmediateShutdownRequested();
25047
+ }
25048
+
25025
25049
  // src/sqlite/cli-database.ts
25026
25050
  import sqliteWasm from "node-sqlite3-wasm";
25027
25051
 
25052
+ // src/runtime/yield-to-event-loop.ts
25053
+ function yieldToEventLoop() {
25054
+ return new Promise((resolve18) => setImmediate(resolve18));
25055
+ }
25056
+
25028
25057
  // src/sqlite/cli-sqlite-paths.ts
25029
25058
  import fs7 from "node:fs";
25030
25059
  import path5 from "node:path";
@@ -25317,6 +25346,29 @@ function migrateCliSqlite(db) {
25317
25346
 
25318
25347
  // src/sqlite/cli-database.ts
25319
25348
  var { Database: SqliteDatabase } = sqliteWasm;
25349
+ var CLI_SQLITE_SYNC_RETRY_MAX = 40;
25350
+ var CLI_SQLITE_ASYNC_RETRY_MAX = 60;
25351
+ var CLI_SQLITE_ASYNC_BASE_DELAY_MS = 20;
25352
+ var CliSqliteInterrupted = class extends Error {
25353
+ name = "CliSqliteInterrupted";
25354
+ constructor() {
25355
+ super("CLI SQLite interrupted (shutdown)");
25356
+ }
25357
+ };
25358
+ function applyCliSqliteConcurrencyPragmas(db) {
25359
+ try {
25360
+ db.exec("PRAGMA journal_mode = WAL");
25361
+ } catch {
25362
+ }
25363
+ try {
25364
+ db.run("PRAGMA synchronous = NORMAL");
25365
+ } catch {
25366
+ }
25367
+ try {
25368
+ db.run("PRAGMA busy_timeout = 500");
25369
+ } catch {
25370
+ }
25371
+ }
25320
25372
  function applyCliSqliteMemoryPragmas(db) {
25321
25373
  try {
25322
25374
  db.run("PRAGMA cache_size = -8192");
@@ -25324,18 +25376,6 @@ function applyCliSqliteMemoryPragmas(db) {
25324
25376
  } catch {
25325
25377
  }
25326
25378
  }
25327
- var openDatabases = /* @__PURE__ */ new Map();
25328
- var processExitCloseRegistered = false;
25329
- function registerProcessExitSqliteClose() {
25330
- if (processExitCloseRegistered) return;
25331
- processExitCloseRegistered = true;
25332
- process.once("exit", () => {
25333
- for (const db of openDatabases.values()) {
25334
- safeCloseCliSqliteDatabase(db);
25335
- }
25336
- openDatabases.clear();
25337
- });
25338
- }
25339
25379
  function safeCloseCliSqliteDatabase(db) {
25340
25380
  if (db == null) return;
25341
25381
  try {
@@ -25344,22 +25384,49 @@ function safeCloseCliSqliteDatabase(db) {
25344
25384
  }
25345
25385
  }
25346
25386
  function closeAllCliSqliteConnections() {
25347
- for (const db of openDatabases.values()) {
25348
- safeCloseCliSqliteDatabase(db);
25387
+ }
25388
+ function isCliSqliteLockError(e) {
25389
+ if (e instanceof CliSqliteInterrupted) return false;
25390
+ const msg = e instanceof Error ? e.message : String(e);
25391
+ const lower = msg.toLowerCase();
25392
+ return lower.includes("database is locked") || lower.includes("sqlite_busy") || lower.includes("sqlite3_busy") || lower.includes("database") && lower.includes("locked");
25393
+ }
25394
+ function syncSleepMs(ms) {
25395
+ if (ms <= 0) return;
25396
+ const deadline = Date.now() + ms;
25397
+ while (Date.now() < deadline) {
25398
+ if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
25399
+ const chunk = Math.min(80, deadline - Date.now());
25400
+ if (chunk <= 0) break;
25401
+ try {
25402
+ const sab = new SharedArrayBuffer(4);
25403
+ const ia = new Int32Array(sab);
25404
+ Atomics.wait(ia, 0, 0, chunk);
25405
+ } catch {
25406
+ const end = Date.now() + chunk;
25407
+ while (Date.now() < end) {
25408
+ if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
25409
+ }
25410
+ }
25349
25411
  }
25350
- openDatabases.clear();
25351
25412
  }
25352
- function getCliDatabase(options) {
25353
- const sqlitePath = getCliSqlitePath();
25354
- const existing = openDatabases.get(sqlitePath);
25355
- if (existing?.isOpen) return existing;
25356
- if (existing && !existing.isOpen) {
25357
- safeCloseCliSqliteDatabase(existing);
25358
- openDatabases.delete(sqlitePath);
25413
+ async function asyncDelayMsInterruptible(ms) {
25414
+ const end = Date.now() + ms;
25415
+ while (Date.now() < end) {
25416
+ if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
25417
+ const chunk = Math.min(50, end - Date.now());
25418
+ if (chunk <= 0) break;
25419
+ await new Promise((r) => setTimeout(r, chunk));
25420
+ await yieldToEventLoop();
25359
25421
  }
25422
+ }
25423
+ function openCliSqliteConnection(options) {
25424
+ if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
25425
+ const sqlitePath = getCliSqlitePath();
25360
25426
  ensureCliSqliteParentDir(sqlitePath);
25361
25427
  const db = new SqliteDatabase(sqlitePath);
25362
25428
  try {
25429
+ applyCliSqliteConcurrencyPragmas(db);
25363
25430
  applyCliSqliteMemoryPragmas(db);
25364
25431
  migrateCliSqlite(db);
25365
25432
  importCliSqliteLegacyDiskData(db, options?.logLegacyMigration);
@@ -25367,16 +25434,62 @@ function getCliDatabase(options) {
25367
25434
  safeCloseCliSqliteDatabase(db);
25368
25435
  throw e;
25369
25436
  }
25370
- openDatabases.set(sqlitePath, db);
25371
- registerProcessExitSqliteClose();
25372
25437
  return db;
25373
25438
  }
25439
+ function withCliSqliteSync(fn, options) {
25440
+ for (let attempt = 1; attempt <= CLI_SQLITE_SYNC_RETRY_MAX; attempt++) {
25441
+ if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
25442
+ let db;
25443
+ try {
25444
+ db = openCliSqliteConnection(options);
25445
+ try {
25446
+ return fn(db);
25447
+ } finally {
25448
+ safeCloseCliSqliteDatabase(db);
25449
+ }
25450
+ } catch (e) {
25451
+ safeCloseCliSqliteDatabase(db);
25452
+ if (e instanceof CliSqliteInterrupted) throw e;
25453
+ if (!isCliSqliteLockError(e) || attempt === CLI_SQLITE_SYNC_RETRY_MAX) throw e;
25454
+ syncSleepMs(Math.min(500, 12 * attempt));
25455
+ }
25456
+ }
25457
+ throw new Error("withCliSqliteSync: exhausted retries");
25458
+ }
25459
+ async function withCliSqlite(fn, options) {
25460
+ let lastError;
25461
+ for (let attempt = 1; attempt <= CLI_SQLITE_ASYNC_RETRY_MAX; attempt++) {
25462
+ if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
25463
+ let db;
25464
+ try {
25465
+ db = openCliSqliteConnection(options);
25466
+ try {
25467
+ return await Promise.resolve(fn(db));
25468
+ } finally {
25469
+ safeCloseCliSqliteDatabase(db);
25470
+ }
25471
+ } catch (e) {
25472
+ lastError = e;
25473
+ safeCloseCliSqliteDatabase(db);
25474
+ if (e instanceof CliSqliteInterrupted) throw e;
25475
+ if (!isCliSqliteLockError(e) || attempt === CLI_SQLITE_ASYNC_RETRY_MAX) throw e;
25476
+ const delayMs = Math.min(600, CLI_SQLITE_ASYNC_BASE_DELAY_MS * attempt);
25477
+ await asyncDelayMsInterruptible(delayMs);
25478
+ await yieldToEventLoop();
25479
+ }
25480
+ }
25481
+ if (lastError instanceof Error) throw lastError;
25482
+ throw new Error(String(lastError));
25483
+ }
25484
+ async function ensureCliSqliteInitialized(options) {
25485
+ await withCliSqlite(() => void 0, options);
25486
+ }
25374
25487
 
25375
25488
  // src/connection/close-bridge-connection.ts
25376
25489
  async function closeBridgeConnection(state, acpManager, devServerManager, log2) {
25490
+ requestCliImmediateShutdown();
25377
25491
  const say = log2 ?? logImmediate;
25378
25492
  say("Cleaning up connections\u2026");
25379
- await new Promise((resolve18) => setImmediate(resolve18));
25380
25493
  state.closedByUser = true;
25381
25494
  clearReconnectQuietTimer(state.mainQuiet);
25382
25495
  clearReconnectQuietTimer(state.firehoseQuiet);
@@ -25413,7 +25526,7 @@ async function closeBridgeConnection(state, acpManager, devServerManager, log2)
25413
25526
  }
25414
25527
  if (devServerManager) {
25415
25528
  say("Stopping local dev server processes\u2026");
25416
- await devServerManager.shutdownAllGraceful();
25529
+ await devServerManager.shutdownAllGraceful({ graceMs: BRIDGE_CLOSE_DEV_SERVER_GRACE_MS });
25417
25530
  }
25418
25531
  try {
25419
25532
  closeAllCliSqliteConnections();
@@ -30659,34 +30772,50 @@ __export(claude_code_acp_client_exports, {
30659
30772
  createClaudeCodeAcpClient: () => createClaudeCodeAcpClient,
30660
30773
  detectLocalAgentPresence: () => detectLocalAgentPresence
30661
30774
  });
30662
- import { execFile as execFile9 } from "node:child_process";
30663
- import { promisify as promisify9 } from "node:util";
30664
30775
 
30665
30776
  // src/agents/acp/clients/detect-command-on-path.ts
30666
30777
  import { execFile as execFile8 } from "node:child_process";
30667
30778
  import { promisify as promisify8 } from "node:util";
30668
30779
  var execFileAsync7 = promisify8(execFile8);
30669
- async function isCommandOnPath(command, timeoutMs = 4e3) {
30780
+ var COMMAND_ON_PATH_PROBE_TIMEOUT_MS = 750;
30781
+ async function execFileShutdownAware(file2, args, timeoutMs) {
30782
+ if (isCliImmediateShutdownRequested()) throw new Error("shutdown");
30783
+ const ac = new AbortController();
30784
+ const shutdownPoll = setInterval(() => {
30785
+ if (isCliImmediateShutdownRequested()) ac.abort();
30786
+ }, 50);
30787
+ shutdownPoll.unref?.();
30788
+ try {
30789
+ await execFileAsync7(file2, args, { timeout: timeoutMs, signal: ac.signal });
30790
+ } finally {
30791
+ clearInterval(shutdownPoll);
30792
+ }
30793
+ }
30794
+ async function isCommandOnPath(command, timeoutMs = COMMAND_ON_PATH_PROBE_TIMEOUT_MS) {
30795
+ if (isCliImmediateShutdownRequested()) return false;
30670
30796
  try {
30671
- await execFileAsync7("which", [command], { timeout: timeoutMs });
30797
+ await execFileShutdownAware("which", [command], timeoutMs);
30672
30798
  return true;
30673
30799
  } catch {
30674
30800
  return false;
30675
30801
  }
30676
30802
  }
30677
-
30678
- // src/agents/acp/clients/claude-code-acp-client.ts
30679
- var execFileAsync8 = promisify9(execFile9);
30680
- var BACKEND_LOCAL_AGENT_TYPE = "claude-code";
30681
- async function detectLocalAgentPresence() {
30682
- if (await isCommandOnPath("claude")) return true;
30803
+ async function execProbeShutdownAware(file2, args, timeoutMs) {
30804
+ if (isCliImmediateShutdownRequested()) return false;
30683
30805
  try {
30684
- await execFileAsync8("npx", ["--yes", "@anthropic-ai/claude-code", "--version"], { timeout: 25e3 });
30806
+ await execFileShutdownAware(file2, args, timeoutMs);
30685
30807
  return true;
30686
30808
  } catch {
30687
30809
  return false;
30688
30810
  }
30689
30811
  }
30812
+
30813
+ // src/agents/acp/clients/claude-code-acp-client.ts
30814
+ var BACKEND_LOCAL_AGENT_TYPE = "claude-code";
30815
+ async function detectLocalAgentPresence() {
30816
+ if (await isCommandOnPath("claude")) return true;
30817
+ return execProbeShutdownAware("npx", ["--yes", "@anthropic-ai/claude-code", "--version"], 3e3);
30818
+ }
30690
30819
  function buildClaudeCodeAcpSpawnCommand(base, _sessionMode) {
30691
30820
  return [...base];
30692
30821
  }
@@ -31852,56 +31981,80 @@ function sessionKeyForCloudSessionId(cloudSessionId) {
31852
31981
  }
31853
31982
  function readLocalAgentSessionFile(cloudSessionId) {
31854
31983
  try {
31855
- const db = getCliDatabase();
31856
- const key = sessionKeyForCloudSessionId(cloudSessionId);
31857
- const row = db.get(
31858
- "SELECT acp_session_id, backend_agent_type, config_options_json, updated_at FROM agent_session WHERE session_key = ?",
31859
- [key]
31860
- );
31861
- if (!row) return null;
31862
- let configOptions = null;
31863
- if (row.config_options_json != null && row.config_options_json !== "") {
31864
- try {
31865
- const parsed = JSON.parse(row.config_options_json);
31866
- configOptions = Array.isArray(parsed) ? parsed : null;
31867
- } catch {
31868
- configOptions = null;
31984
+ return withCliSqliteSync((db) => {
31985
+ const key = sessionKeyForCloudSessionId(cloudSessionId);
31986
+ const row = db.get(
31987
+ "SELECT acp_session_id, backend_agent_type, config_options_json, updated_at FROM agent_session WHERE session_key = ?",
31988
+ [key]
31989
+ );
31990
+ if (!row) return null;
31991
+ let configOptions = null;
31992
+ if (row.config_options_json != null && row.config_options_json !== "") {
31993
+ try {
31994
+ const parsed = JSON.parse(row.config_options_json);
31995
+ configOptions = Array.isArray(parsed) ? parsed : null;
31996
+ } catch {
31997
+ configOptions = null;
31998
+ }
31869
31999
  }
31870
- }
31871
- return {
31872
- v: 1,
31873
- acpSessionId: row.acp_session_id,
31874
- backendAgentType: row.backend_agent_type,
31875
- configOptions,
31876
- updatedAt: row.updated_at
31877
- };
32000
+ return {
32001
+ v: 1,
32002
+ acpSessionId: row.acp_session_id,
32003
+ backendAgentType: row.backend_agent_type,
32004
+ configOptions,
32005
+ updatedAt: row.updated_at
32006
+ };
32007
+ });
31878
32008
  } catch {
31879
32009
  return null;
31880
32010
  }
31881
32011
  }
31882
32012
  function writeLocalAgentSessionFile(cloudSessionId, patch) {
31883
32013
  try {
31884
- const db = getCliDatabase();
31885
- const key = sessionKeyForCloudSessionId(cloudSessionId);
31886
- const prev = readLocalAgentSessionFile(cloudSessionId);
31887
- const next = {
31888
- v: 1,
31889
- acpSessionId: patch.acpSessionId !== void 0 ? patch.acpSessionId : prev?.acpSessionId ?? null,
31890
- backendAgentType: patch.backendAgentType !== void 0 ? patch.backendAgentType : prev?.backendAgentType ?? null,
31891
- configOptions: patch.configOptions !== void 0 ? patch.configOptions : prev?.configOptions ?? null,
31892
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
31893
- };
31894
- const configJson = next.configOptions != null ? JSON.stringify(next.configOptions) : null;
31895
- db.run(
31896
- `INSERT INTO agent_session (session_key, acp_session_id, backend_agent_type, config_options_json, updated_at)
31897
- VALUES (?, ?, ?, ?, ?)
31898
- ON CONFLICT(session_key) DO UPDATE SET
31899
- acp_session_id = excluded.acp_session_id,
31900
- backend_agent_type = excluded.backend_agent_type,
31901
- config_options_json = excluded.config_options_json,
31902
- updated_at = excluded.updated_at`,
31903
- [key, next.acpSessionId, next.backendAgentType, configJson, next.updatedAt]
31904
- );
32014
+ withCliSqliteSync((db) => {
32015
+ const key = sessionKeyForCloudSessionId(cloudSessionId);
32016
+ const prevRow = db.get(
32017
+ "SELECT acp_session_id, backend_agent_type, config_options_json, updated_at FROM agent_session WHERE session_key = ?",
32018
+ [key]
32019
+ );
32020
+ let prev = null;
32021
+ if (prevRow) {
32022
+ let configOptions = null;
32023
+ if (prevRow.config_options_json != null && prevRow.config_options_json !== "") {
32024
+ try {
32025
+ const parsed = JSON.parse(prevRow.config_options_json);
32026
+ configOptions = Array.isArray(parsed) ? parsed : null;
32027
+ } catch {
32028
+ configOptions = null;
32029
+ }
32030
+ }
32031
+ prev = {
32032
+ v: 1,
32033
+ acpSessionId: prevRow.acp_session_id,
32034
+ backendAgentType: prevRow.backend_agent_type,
32035
+ configOptions,
32036
+ updatedAt: prevRow.updated_at
32037
+ };
32038
+ }
32039
+ const next = {
32040
+ v: 1,
32041
+ acpSessionId: patch.acpSessionId !== void 0 ? patch.acpSessionId : prev?.acpSessionId ?? null,
32042
+ backendAgentType: patch.backendAgentType !== void 0 ? patch.backendAgentType : prev?.backendAgentType ?? null,
32043
+ configOptions: patch.configOptions !== void 0 ? patch.configOptions : prev?.configOptions ?? null,
32044
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
32045
+ };
32046
+ const configJson = next.configOptions != null ? JSON.stringify(next.configOptions) : null;
32047
+ db.run(
32048
+ `INSERT INTO agent_session (session_key, acp_session_id, backend_agent_type, config_options_json, updated_at)
32049
+ VALUES (?, ?, ?, ?, ?)
32050
+ ON CONFLICT(session_key) DO UPDATE SET
32051
+ acp_session_id = excluded.acp_session_id,
32052
+ backend_agent_type = excluded.backend_agent_type,
32053
+ config_options_json = excluded.config_options_json,
32054
+ updated_at = excluded.updated_at`,
32055
+ [key, next.acpSessionId, next.backendAgentType, configJson, next.updatedAt]
32056
+ );
32057
+ });
31905
32058
  } catch {
31906
32059
  }
31907
32060
  }
@@ -33484,41 +33637,45 @@ function getCwdHashForFileIndex(resolvedCwd) {
33484
33637
  // src/files/index/build-file-index.ts
33485
33638
  import path28 from "node:path";
33486
33639
 
33487
- // src/runtime/yield-to-event-loop.ts
33488
- function yieldToEventLoop() {
33489
- return new Promise((resolve18) => setImmediate(resolve18));
33490
- }
33491
-
33492
33640
  // src/files/index/walk-workspace-tree.ts
33493
33641
  import fs24 from "node:fs";
33494
33642
  import path27 from "node:path";
33495
33643
  function shouldSkipWorkspaceWalkEntry(name) {
33496
33644
  return name.startsWith(".");
33497
33645
  }
33498
- function walkWorkspaceTreeSync(dir, baseDir, onFile) {
33646
+ async function walkWorkspaceTreeAsync(dir, baseDir, onFile, state) {
33499
33647
  let names;
33500
33648
  try {
33501
- names = fs24.readdirSync(dir);
33649
+ names = await fs24.promises.readdir(dir);
33502
33650
  } catch {
33503
33651
  return;
33504
33652
  }
33505
33653
  for (const name of names) {
33506
33654
  if (shouldSkipWorkspaceWalkEntry(name)) continue;
33655
+ if (state.n > 0 && state.n % INDEX_WORK_YIELD_EVERY === 0) {
33656
+ if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
33657
+ await yieldToEventLoop();
33658
+ if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
33659
+ }
33660
+ state.n++;
33507
33661
  const full = path27.join(dir, name);
33508
33662
  let stat2;
33509
33663
  try {
33510
- stat2 = fs24.statSync(full);
33664
+ stat2 = await fs24.promises.stat(full);
33511
33665
  } catch {
33512
33666
  continue;
33513
33667
  }
33514
33668
  const relative5 = path27.relative(baseDir, full).replace(/\\/g, "/");
33515
33669
  if (stat2.isDirectory()) {
33516
- walkWorkspaceTreeSync(full, baseDir, onFile);
33670
+ await walkWorkspaceTreeAsync(full, baseDir, onFile, state);
33517
33671
  } else if (stat2.isFile()) {
33518
33672
  onFile(relative5);
33519
33673
  }
33520
33674
  }
33521
33675
  }
33676
+ function createWalkYieldState() {
33677
+ return { n: 0 };
33678
+ }
33522
33679
 
33523
33680
  // src/files/index/file-index-sqlite-lock.ts
33524
33681
  import fs25 from "node:fs";
@@ -33551,48 +33708,60 @@ function withFileIndexSqliteLock(fn) {
33551
33708
  }
33552
33709
 
33553
33710
  // src/files/index/build-file-index.ts
33554
- var FILE_INDEX_INSERT_BUFFER = 2048;
33555
- function persistFileIndexForResolvedCwd(resolved) {
33556
- const db = getCliDatabase();
33557
- const h = getCwdHashForFileIndex(resolved);
33558
- const buf = [];
33559
- let pathCount = 0;
33560
- db.run("BEGIN IMMEDIATE");
33561
- try {
33562
- db.run("DELETE FROM file_index_path WHERE cwd_hash = ?", [h]);
33563
- const ins = db.prepare("INSERT INTO file_index_path (cwd_hash, path) VALUES (?, ?)");
33711
+ var FILE_INDEX_INTERRUPT_CHECK_EVERY = 256;
33712
+ function assertNotShutdown() {
33713
+ if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
33714
+ }
33715
+ function persistFileIndexPaths(resolved, paths) {
33716
+ return withCliSqliteSync((db) => {
33717
+ const h = getCwdHashForFileIndex(resolved);
33718
+ let pathCount = 0;
33719
+ db.run("BEGIN IMMEDIATE");
33564
33720
  try {
33565
- const flushBuf = () => {
33566
- for (const rel of buf) {
33721
+ db.run("DELETE FROM file_index_path WHERE cwd_hash = ?", [h]);
33722
+ const ins = db.prepare("INSERT INTO file_index_path (cwd_hash, path) VALUES (?, ?)");
33723
+ try {
33724
+ let batch = 0;
33725
+ for (const rel of paths) {
33726
+ if (++batch >= FILE_INDEX_INTERRUPT_CHECK_EVERY) {
33727
+ batch = 0;
33728
+ assertNotShutdown();
33729
+ }
33567
33730
  ins.run([h, rel]);
33731
+ pathCount += 1;
33568
33732
  }
33569
- pathCount += buf.length;
33570
- buf.length = 0;
33571
- };
33572
- walkWorkspaceTreeSync(resolved, resolved, (rel) => {
33573
- buf.push(rel);
33574
- if (buf.length >= FILE_INDEX_INSERT_BUFFER) flushBuf();
33575
- });
33576
- flushBuf();
33577
- } finally {
33578
- ins.finalize();
33579
- }
33580
- db.run("COMMIT");
33581
- } catch (e) {
33582
- try {
33583
- db.run("ROLLBACK");
33584
- } catch {
33733
+ } finally {
33734
+ ins.finalize();
33735
+ }
33736
+ db.run("COMMIT");
33737
+ } catch (e) {
33738
+ try {
33739
+ db.run("ROLLBACK");
33740
+ } catch {
33741
+ }
33742
+ throw e;
33585
33743
  }
33586
- throw e;
33587
- }
33588
- return pathCount;
33744
+ return pathCount;
33745
+ });
33746
+ }
33747
+ async function collectWorkspacePathsAsync(resolved) {
33748
+ const paths = [];
33749
+ const state = createWalkYieldState();
33750
+ await walkWorkspaceTreeAsync(resolved, resolved, (rel) => {
33751
+ assertNotShutdown();
33752
+ paths.push(rel);
33753
+ }, state);
33754
+ return paths;
33589
33755
  }
33590
33756
  async function buildFileIndexAsync(cwd) {
33591
33757
  return withFileIndexSqliteLock(async () => {
33592
33758
  const resolved = path28.resolve(cwd);
33593
33759
  await yieldToEventLoop();
33594
- const pathCount = persistFileIndexForResolvedCwd(resolved);
33760
+ assertNotShutdown();
33761
+ const paths = await collectWorkspacePathsAsync(resolved);
33595
33762
  await yieldToEventLoop();
33763
+ assertNotShutdown();
33764
+ const pathCount = persistFileIndexPaths(resolved, paths);
33596
33765
  return { pathCount };
33597
33766
  });
33598
33767
  }
@@ -33609,23 +33778,20 @@ function sqliteExprBridgeFileIndexDependencyRank() {
33609
33778
  function escapeLikePattern(fragment) {
33610
33779
  return fragment.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
33611
33780
  }
33612
- function bridgeFileIndexIsPopulated(resolvedCwd) {
33613
- const db = getCliDatabase();
33781
+ function bridgeFileIndexIsPopulatedWithDb(resolvedCwd, db) {
33614
33782
  const h = getCwdHashForFileIndex(resolvedCwd);
33615
33783
  const row = db.get("SELECT 1 as ok FROM file_index_path WHERE cwd_hash = ? LIMIT 1", [h]);
33616
33784
  return row != null;
33617
33785
  }
33618
- function bridgeFileIndexPathCount(resolvedCwd) {
33619
- const db = getCliDatabase();
33786
+ function bridgeFileIndexPathCountWithDb(resolvedCwd, db) {
33620
33787
  const h = getCwdHashForFileIndex(resolvedCwd);
33621
33788
  const row = db.get("SELECT COUNT(*) as c FROM file_index_path WHERE cwd_hash = ?", [h]);
33622
33789
  const c = row?.c ?? 0;
33623
33790
  return Number(c);
33624
33791
  }
33625
- function searchBridgeFilePaths(resolvedCwd, query, limit = 100) {
33792
+ function searchBridgeFilePathsWithDb(resolvedCwd, query, limit, db) {
33626
33793
  const q = query.trim().toLowerCase();
33627
33794
  if (!q) return [];
33628
- const db = getCliDatabase();
33629
33795
  const h = getCwdHashForFileIndex(resolvedCwd);
33630
33796
  const pattern = `%${escapeLikePattern(q)}%`;
33631
33797
  const lim = Math.max(0, Math.min(1e4, Math.floor(limit)));
@@ -33636,9 +33802,15 @@ function searchBridgeFilePaths(resolvedCwd, query, limit = 100) {
33636
33802
  );
33637
33803
  return rows.map((r) => String(r.path));
33638
33804
  }
33805
+ async function bridgeFileIndexIsPopulated(resolvedCwd) {
33806
+ return withCliSqlite((db) => bridgeFileIndexIsPopulatedWithDb(resolvedCwd, db));
33807
+ }
33808
+ async function bridgeFileIndexPathCount(resolvedCwd) {
33809
+ return withCliSqlite((db) => bridgeFileIndexPathCountWithDb(resolvedCwd, db));
33810
+ }
33639
33811
  async function searchBridgeFilePathsAsync(resolvedCwd, query, limit = 100) {
33640
33812
  await yieldToEventLoop();
33641
- const out = searchBridgeFilePaths(resolvedCwd, query, limit);
33813
+ const out = await withCliSqlite((db) => searchBridgeFilePathsWithDb(resolvedCwd, query, limit, db));
33642
33814
  if (out.length >= INDEX_WORK_YIELD_EVERY) await yieldToEventLoop();
33643
33815
  return out;
33644
33816
  }
@@ -33646,8 +33818,8 @@ async function searchBridgeFilePathsAsync(resolvedCwd, query, limit = 100) {
33646
33818
  // src/files/index/ensure-file-index.ts
33647
33819
  async function ensureFileIndexAsync(cwd) {
33648
33820
  const resolved = path29.resolve(cwd);
33649
- if (bridgeFileIndexIsPopulated(resolved)) {
33650
- return { fromCache: true, pathCount: bridgeFileIndexPathCount(resolved) };
33821
+ if (await bridgeFileIndexIsPopulated(resolved)) {
33822
+ return { fromCache: true, pathCount: await bridgeFileIndexPathCount(resolved) };
33651
33823
  }
33652
33824
  return { ...await buildFileIndexAsync(resolved), fromCache: false };
33653
33825
  }
@@ -33693,11 +33865,13 @@ function createFsWatcher(resolved, schedule) {
33693
33865
  function startFileIndexWatcher(cwd = getBridgeRoot()) {
33694
33866
  const resolved = path30.resolve(cwd);
33695
33867
  void buildFileIndexAsync(resolved).catch((e) => {
33868
+ if (e instanceof CliSqliteInterrupted) return;
33696
33869
  console.error("[file-index] Initial index build failed:", e);
33697
33870
  });
33698
33871
  let timer = null;
33699
33872
  const runRebuild = () => {
33700
33873
  void buildFileIndexAsync(resolved).catch((e) => {
33874
+ if (e instanceof CliSqliteInterrupted) return;
33701
33875
  console.error("[file-index] Watch rebuild failed:", e);
33702
33876
  });
33703
33877
  };
@@ -34231,9 +34405,6 @@ var StreamTail = class {
34231
34405
  }
34232
34406
  };
34233
34407
 
34234
- // src/dev-servers/manager/dev-server-constants.ts
34235
- var BRIDGE_SHUTDOWN_GRACE_MS = 8e3;
34236
-
34237
34408
  // src/dev-servers/manager/dev-server-firehose-messages.ts
34238
34409
  function buildFirehoseSnapshotMessage(params) {
34239
34410
  const payload = {
@@ -34519,22 +34690,23 @@ var DevServerManager = class {
34519
34690
  }
34520
34691
  this.start(serverId);
34521
34692
  }
34522
- async shutdownAllGraceful() {
34693
+ async shutdownAllGraceful(opts) {
34694
+ const graceMs = opts?.graceMs ?? BRIDGE_SHUTDOWN_GRACE_MS;
34523
34695
  const pairs = [...this.processes.entries()];
34524
34696
  if (pairs.length === 0) return;
34525
34697
  this.log(
34526
34698
  `[dev-server] Stopping ${pairs.length} local dev server process${pairs.length === 1 ? "" : "es"}\u2026`
34527
34699
  );
34528
- await Promise.all(pairs.map(([serverId, proc]) => this.gracefulTerminateOrUnknown(serverId, proc)));
34700
+ await Promise.all(pairs.map(([serverId, proc]) => this.gracefulTerminateOrUnknown(serverId, proc, graceMs)));
34529
34701
  }
34530
- async gracefulTerminateOrUnknown(serverId, proc) {
34702
+ async gracefulTerminateOrUnknown(serverId, proc, graceMs) {
34531
34703
  const shortId = `${serverId.slice(0, 8)}\u2026`;
34532
- await sigtermAndWaitForExit(proc, BRIDGE_SHUTDOWN_GRACE_MS, this.log, shortId);
34704
+ await sigtermAndWaitForExit(proc, graceMs, this.log, shortId);
34533
34705
  if (!this.processes.has(serverId) || this.processes.get(serverId) !== proc) {
34534
34706
  return;
34535
34707
  }
34536
34708
  this.bumpGeneration(serverId);
34537
- forceKillChild(proc, this.log, shortId, BRIDGE_SHUTDOWN_GRACE_MS);
34709
+ forceKillChild(proc, this.log, shortId, graceMs);
34538
34710
  this.processes.delete(serverId);
34539
34711
  this.clearPoll(serverId);
34540
34712
  this.pipedCaptureByServerId.delete(serverId);
@@ -34961,10 +35133,13 @@ var LOCAL_AGENT_ACP_MODULES = [
34961
35133
  ];
34962
35134
  async function detectLocalAgentTypes() {
34963
35135
  try {
35136
+ if (isCliImmediateShutdownRequested()) return [];
34964
35137
  const out = [];
34965
35138
  for (let i = 0; i < LOCAL_AGENT_ACP_MODULES.length; i++) {
35139
+ if (isCliImmediateShutdownRequested()) return out;
34966
35140
  if (i > 0) {
34967
35141
  await yieldToEventLoop();
35142
+ if (isCliImmediateShutdownRequested()) return out;
34968
35143
  }
34969
35144
  const mod = LOCAL_AGENT_ACP_MODULES[i];
34970
35145
  try {
@@ -34997,6 +35172,7 @@ function createSendLocalSkillsReport(getWs, logFn) {
34997
35172
  }
34998
35173
  function createReportAutoDetectedAgents(getWs, logFn) {
34999
35174
  return async () => {
35175
+ if (isCliImmediateShutdownRequested()) return;
35000
35176
  try {
35001
35177
  const types = await detectLocalAgentTypes();
35002
35178
  const socket = getWs();
@@ -35096,6 +35272,7 @@ var handleBridgeIdentified = (msg, deps) => {
35096
35272
  });
35097
35273
  setImmediate(() => {
35098
35274
  void (async () => {
35275
+ if (isCliImmediateShutdownRequested()) return;
35099
35276
  try {
35100
35277
  await deps.reportAutoDetectedAgents?.();
35101
35278
  } catch (e) {
@@ -35103,6 +35280,7 @@ var handleBridgeIdentified = (msg, deps) => {
35103
35280
  `[Bridge service] Auto-detect agents failed: ${e instanceof Error ? e.message : String(e)}`
35104
35281
  );
35105
35282
  }
35283
+ if (isCliImmediateShutdownRequested()) return;
35106
35284
  try {
35107
35285
  await deps.warmupAgentCapabilitiesOnConnect?.();
35108
35286
  } catch (e) {
@@ -35113,6 +35291,7 @@ var handleBridgeIdentified = (msg, deps) => {
35113
35291
  })();
35114
35292
  });
35115
35293
  setImmediate(() => {
35294
+ if (isCliImmediateShutdownRequested()) return;
35116
35295
  try {
35117
35296
  deps.sendLocalSkillsReport?.();
35118
35297
  } catch (e) {
@@ -35160,30 +35339,32 @@ var MERGEABLE_SERVER_STATES = /* @__PURE__ */ new Set([
35160
35339
  "stopping",
35161
35340
  "discarded"
35162
35341
  ]);
35163
- function readPersistedQueue(queueKey) {
35164
- const db = getCliDatabase();
35165
- const row = db.get("SELECT queue_key, updated_at, turns_json FROM prompt_queue WHERE queue_key = ?", [
35166
- queueKey
35167
- ]);
35168
- if (!row) return null;
35169
- try {
35170
- const turns = JSON.parse(row.turns_json);
35171
- if (!Array.isArray(turns)) return null;
35172
- return { queueKey: row.queue_key, updatedAt: row.updated_at, turns };
35173
- } catch {
35174
- return null;
35175
- }
35342
+ async function readPersistedQueue(queueKey) {
35343
+ return withCliSqlite((db) => {
35344
+ const row = db.get("SELECT queue_key, updated_at, turns_json FROM prompt_queue WHERE queue_key = ?", [
35345
+ queueKey
35346
+ ]);
35347
+ if (!row) return null;
35348
+ try {
35349
+ const turns = JSON.parse(row.turns_json);
35350
+ if (!Array.isArray(turns)) return null;
35351
+ return { queueKey: row.queue_key, updatedAt: row.updated_at, turns };
35352
+ } catch {
35353
+ return null;
35354
+ }
35355
+ });
35176
35356
  }
35177
- function writePersistedQueue(file2) {
35178
- const db = getCliDatabase();
35179
- db.run(
35180
- `INSERT INTO prompt_queue (queue_key, updated_at, turns_json) VALUES (?, ?, ?)
35181
- ON CONFLICT(queue_key) DO UPDATE SET updated_at = excluded.updated_at, turns_json = excluded.turns_json`,
35182
- [file2.queueKey, file2.updatedAt, JSON.stringify(file2.turns)]
35183
- );
35357
+ async function writePersistedQueue(file2) {
35358
+ await withCliSqlite((db) => {
35359
+ db.run(
35360
+ `INSERT INTO prompt_queue (queue_key, updated_at, turns_json) VALUES (?, ?, ?)
35361
+ ON CONFLICT(queue_key) DO UPDATE SET updated_at = excluded.updated_at, turns_json = excluded.turns_json`,
35362
+ [file2.queueKey, file2.updatedAt, JSON.stringify(file2.turns)]
35363
+ );
35364
+ });
35184
35365
  }
35185
- function mergeServerQueueSnapshot(queueKey, serverTurns) {
35186
- const prev = readPersistedQueue(queueKey);
35366
+ async function mergeServerQueueSnapshot(queueKey, serverTurns) {
35367
+ const prev = await readPersistedQueue(queueKey);
35187
35368
  const turns = [];
35188
35369
  for (const raw of serverTurns) {
35189
35370
  if (!raw || typeof raw !== "object") continue;
@@ -35280,12 +35461,12 @@ async function applyPromptQueueStateFromServer(msg, deps) {
35280
35461
  const getWs = deps.getWs;
35281
35462
  for (const [queueKey, serverTurns] of Object.entries(raw)) {
35282
35463
  if (!Array.isArray(serverTurns)) continue;
35283
- const file2 = mergeServerQueueSnapshot(queueKey, serverTurns);
35284
- writePersistedQueue(file2);
35464
+ const file2 = await mergeServerQueueSnapshot(queueKey, serverTurns);
35465
+ await writePersistedQueue(file2);
35285
35466
  }
35286
35467
  for (const [queueKey, serverTurns] of Object.entries(raw)) {
35287
35468
  if (!Array.isArray(serverTurns)) continue;
35288
- const file2 = readPersistedQueue(queueKey);
35469
+ const file2 = await readPersistedQueue(queueKey);
35289
35470
  if (!file2) continue;
35290
35471
  for (const running of file2.turns.filter((t) => t.lastClientState === "running")) {
35291
35472
  runIdToQueueKey.set(running.turnId, queueKey);
@@ -35293,7 +35474,7 @@ async function applyPromptQueueStateFromServer(msg, deps) {
35293
35474
  }
35294
35475
  for (const [queueKey, serverTurns] of Object.entries(raw)) {
35295
35476
  if (!Array.isArray(serverTurns)) continue;
35296
- const file2 = readPersistedQueue(queueKey);
35477
+ const file2 = await readPersistedQueue(queueKey);
35297
35478
  if (!file2) continue;
35298
35479
  const cancelRow = file2.turns.find((t) => t.serverState === "cancel_requested" && t.lastClientState === "running");
35299
35480
  if (cancelRow) {
@@ -35302,7 +35483,7 @@ async function applyPromptQueueStateFromServer(msg, deps) {
35302
35483
  deps.log(
35303
35484
  `[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.`
35304
35485
  );
35305
- finalizePromptTurnOnBridge(deps.getWs, cancelRow.turnId, false, { terminalClientState: "cancelled" });
35486
+ await finalizePromptTurnOnBridge(deps.getWs, cancelRow.turnId, false, { terminalClientState: "cancelled" });
35306
35487
  const ws = deps.getWs();
35307
35488
  if (ws && cancelRow.sessionId) {
35308
35489
  sendWsMessage(ws, {
@@ -35321,19 +35502,19 @@ async function applyPromptQueueStateFromServer(msg, deps) {
35321
35502
  const startedThisTick = /* @__PURE__ */ new Set();
35322
35503
  for (const [queueKey, serverTurns] of Object.entries(raw)) {
35323
35504
  if (!Array.isArray(serverTurns)) continue;
35324
- const file2 = readPersistedQueue(queueKey);
35505
+ const file2 = await readPersistedQueue(queueKey);
35325
35506
  if (!file2) continue;
35326
35507
  if (hasRunningTurn(file2.turns)) continue;
35327
35508
  const next = pickNextRunnableTurn(file2.turns);
35328
35509
  if (!next) continue;
35329
35510
  if (!await runLocalRevertBeforeQueuedPrompt(next, deps)) {
35330
35511
  next.lastClientState = "failed";
35331
- writePersistedQueue(file2);
35512
+ await writePersistedQueue(file2);
35332
35513
  sendPromptQueueClientReport(getWs(), { [queueKey]: [{ turnId: next.turnId, clientState: "failed" }] });
35333
35514
  continue;
35334
35515
  }
35335
35516
  next.lastClientState = "running";
35336
- writePersistedQueue(file2);
35517
+ await writePersistedQueue(file2);
35337
35518
  runIdToQueueKey.set(next.turnId, queueKey);
35338
35519
  startedThisTick.add(next.turnId);
35339
35520
  report[queueKey] = [{ turnId: next.turnId, clientState: "running" }];
@@ -35343,7 +35524,7 @@ async function applyPromptQueueStateFromServer(msg, deps) {
35343
35524
  }
35344
35525
  for (const [queueKey, serverTurns] of Object.entries(raw)) {
35345
35526
  if (!Array.isArray(serverTurns)) continue;
35346
- const file2 = readPersistedQueue(queueKey);
35527
+ const file2 = await readPersistedQueue(queueKey);
35347
35528
  if (!file2) continue;
35348
35529
  const running = file2.turns.find((t) => t.lastClientState === "running");
35349
35530
  if (!running || !startedThisTick.has(running.turnId)) continue;
@@ -35351,17 +35532,17 @@ async function applyPromptQueueStateFromServer(msg, deps) {
35351
35532
  dispatchLocalPrompt(running, deps);
35352
35533
  }
35353
35534
  }
35354
- function finalizePromptTurnOnBridge(getWs, runId, success2, opts) {
35535
+ async function finalizePromptTurnOnBridge(getWs, runId, success2, opts) {
35355
35536
  if (!runId) return false;
35356
35537
  const queueKey = runIdToQueueKey.get(runId);
35357
35538
  runIdToQueueKey.delete(runId);
35358
35539
  if (!queueKey) return false;
35359
- const f = readPersistedQueue(queueKey);
35540
+ const f = await readPersistedQueue(queueKey);
35360
35541
  if (!f) return false;
35361
35542
  const t = f.turns.find((x) => x.turnId === runId);
35362
35543
  if (!t) return false;
35363
35544
  t.lastClientState = opts?.terminalClientState ?? (success2 ? "stopped" : "failed");
35364
- writePersistedQueue(f);
35545
+ await writePersistedQueue(f);
35365
35546
  sendPromptQueueClientReport(getWs(), { [queueKey]: [{ turnId: runId, clientState: t.lastClientState }] });
35366
35547
  return true;
35367
35548
  }
@@ -35382,12 +35563,13 @@ function createBridgePromptSenders(deps, getWs) {
35382
35563
  if (result.type === "prompt_result") {
35383
35564
  const pr = result;
35384
35565
  const cancelled = pr.stopReason === "cancelled";
35385
- finalizePromptTurnOnBridge(
35566
+ void finalizePromptTurnOnBridge(
35386
35567
  getWs,
35387
35568
  typeof pr.runId === "string" ? pr.runId : void 0,
35388
35569
  pr.success === true,
35389
35570
  cancelled ? { terminalClientState: "cancelled" } : void 0
35390
- );
35571
+ ).catch(() => {
35572
+ });
35391
35573
  }
35392
35574
  };
35393
35575
  const sendSessionUpdate = (payload) => {
@@ -35411,12 +35593,12 @@ function createBridgePromptSenders(deps, getWs) {
35411
35593
  }
35412
35594
 
35413
35595
  // src/agents/acp/from-bridge/bridge-prompt-preamble.ts
35414
- import { execFile as execFile10 } from "node:child_process";
35415
- import { promisify as promisify10 } from "node:util";
35416
- var execFileAsync9 = promisify10(execFile10);
35596
+ import { execFile as execFile9 } from "node:child_process";
35597
+ import { promisify as promisify9 } from "node:util";
35598
+ var execFileAsync8 = promisify9(execFile9);
35417
35599
  async function readGitBranch(cwd) {
35418
35600
  try {
35419
- const { stdout } = await execFileAsync9("git", ["branch", "--show-current"], { cwd, maxBuffer: 64 * 1024 });
35601
+ const { stdout } = await execFileAsync8("git", ["branch", "--show-current"], { cwd, maxBuffer: 64 * 1024 });
35420
35602
  const b = stdout.trim();
35421
35603
  return b || null;
35422
35604
  } catch {
@@ -35946,7 +36128,7 @@ function handleFileBrowserSearch(msg, socket, e2ee) {
35946
36128
  await yieldToEventLoop();
35947
36129
  const q = typeof msg.q === "string" ? msg.q : "";
35948
36130
  const cwd = path36.resolve(getBridgeRoot());
35949
- if (!bridgeFileIndexIsPopulated(cwd)) {
36131
+ if (!await bridgeFileIndexIsPopulated(cwd)) {
35950
36132
  const payload2 = {
35951
36133
  type: "file_browser_search_response",
35952
36134
  id: msg.id,
@@ -36786,7 +36968,8 @@ import * as path39 from "node:path";
36786
36968
  // src/agents/capabilities/probe-one-agent-type-for-capabilities.ts
36787
36969
  import * as path38 from "node:path";
36788
36970
  async function probeOneAgentTypeForCapabilities(params) {
36789
- const { agentType, cwd, workspaceId, log: log2, getDb, reportAgentCapabilities, bridgeReport = true } = params;
36971
+ const { agentType, cwd, workspaceId, log: log2, reportAgentCapabilities, bridgeReport = true } = params;
36972
+ if (isCliImmediateShutdownRequested()) return false;
36790
36973
  const resolved = resolveAgentCommand(agentType);
36791
36974
  if (!resolved) return false;
36792
36975
  let sqliteChanged = false;
@@ -36797,11 +36980,13 @@ async function probeOneAgentTypeForCapabilities(params) {
36797
36980
  reportedRef.done = true;
36798
36981
  let changed = false;
36799
36982
  try {
36800
- changed = upsertCliAgentCapabilityCache(getDb(), {
36801
- workspaceId,
36802
- agentType,
36803
- configOptions: co
36804
- });
36983
+ changed = withCliSqliteSync(
36984
+ (db) => upsertCliAgentCapabilityCache(db, {
36985
+ workspaceId,
36986
+ agentType,
36987
+ configOptions: co
36988
+ })
36989
+ );
36805
36990
  } catch {
36806
36991
  }
36807
36992
  sqliteChanged ||= changed;
@@ -36818,6 +37003,7 @@ async function probeOneAgentTypeForCapabilities(params) {
36818
37003
  }, 28e3);
36819
37004
  killTimer.unref?.();
36820
37005
  try {
37006
+ if (isCliImmediateShutdownRequested()) return false;
36821
37007
  handle = await resolved.createClient({
36822
37008
  command: resolved.command,
36823
37009
  cwd: path38.resolve(cwd),
@@ -36837,7 +37023,7 @@ async function probeOneAgentTypeForCapabilities(params) {
36837
37023
  onSessionUpdate: () => {
36838
37024
  }
36839
37025
  });
36840
- await new Promise((r) => setTimeout(r, 1200));
37026
+ if (!await delayMsUnlessShutdownRequested(1200)) return false;
36841
37027
  } catch (e) {
36842
37028
  log2(
36843
37029
  `[Bridge service] Agent capability probe (${agentType}): ${e instanceof Error ? e.message : String(e)}`
@@ -36859,19 +37045,19 @@ async function probeAgentCapabilitiesForDetectedTypes(params) {
36859
37045
  cwd,
36860
37046
  workspaceId,
36861
37047
  log: log2,
36862
- getDb,
36863
37048
  reportAgentCapabilities,
36864
37049
  bridgeReport = true,
36865
37050
  forceAllTypes = false
36866
37051
  } = params;
36867
37052
  let changedCount = 0;
36868
37053
  for (let i = 0; i < agentTypes.length; i++) {
37054
+ if (isCliImmediateShutdownRequested()) return changedCount;
36869
37055
  if (i > 0) await yieldToEventLoop();
36870
37056
  const agentType = agentTypes[i];
36871
37057
  if (!agentType.trim()) continue;
36872
37058
  if (!forceAllTypes) {
36873
37059
  try {
36874
- if (process.env.BUILDAUTOMATON_FORCE_PROBE_ACP_CAPABILITIES !== "1" && hasNonEmptyAgentCapabilityCache(getDb(), workspaceId, agentType)) {
37060
+ if (process.env.BUILDAUTOMATON_FORCE_PROBE_ACP_CAPABILITIES !== "1" && await withCliSqlite((db) => hasNonEmptyAgentCapabilityCache(db, workspaceId, agentType))) {
36875
37061
  continue;
36876
37062
  }
36877
37063
  } catch {
@@ -36882,7 +37068,6 @@ async function probeAgentCapabilitiesForDetectedTypes(params) {
36882
37068
  cwd,
36883
37069
  workspaceId,
36884
37070
  log: log2,
36885
- getDb,
36886
37071
  reportAgentCapabilities,
36887
37072
  bridgeReport
36888
37073
  });
@@ -36893,60 +37078,64 @@ async function probeAgentCapabilitiesForDetectedTypes(params) {
36893
37078
 
36894
37079
  // src/agents/capabilities/warmup-agent-capabilities-on-connect.ts
36895
37080
  async function warmupAgentCapabilitiesOnConnect(params) {
36896
- const { workspaceId, log: log2, getDb, getWs } = params;
37081
+ const { workspaceId, log: log2, getWs } = params;
37082
+ if (isCliImmediateShutdownRequested()) return;
36897
37083
  const cwd = path39.resolve(getBridgeRoot());
36898
- const db = getDb();
36899
- function sendBatchFromCache() {
37084
+ async function sendBatchFromCache() {
36900
37085
  const socket = getWs();
36901
37086
  if (!socket || socket.readyState !== wrapper_default.OPEN) return;
36902
37087
  try {
36903
- const rows = listCliAgentCapabilityCacheForWorkspace(db, workspaceId);
37088
+ const rows = await withCliSqlite((db) => listCliAgentCapabilityCacheForWorkspace(db, workspaceId));
36904
37089
  if (rows.length === 0) return;
36905
37090
  sendWsMessage(socket, {
36906
37091
  type: "agent_capabilities_batch",
36907
37092
  items: rows.map((r) => ({ agentType: r.agentType, configOptions: r.configOptions }))
36908
37093
  });
36909
37094
  } catch (e) {
37095
+ if (e instanceof CliSqliteInterrupted) return;
36910
37096
  log2(
36911
37097
  `[Bridge service] Agent capability batch to bridge failed: ${e instanceof Error ? e.message : String(e)}`
36912
37098
  );
36913
37099
  }
36914
37100
  }
36915
- sendBatchFromCache();
37101
+ await sendBatchFromCache();
37102
+ if (isCliImmediateShutdownRequested()) return;
36916
37103
  let types = [];
36917
37104
  try {
36918
37105
  types = [...await detectLocalAgentTypes()];
36919
37106
  } catch (e) {
36920
37107
  log2(`[Bridge service] detectLocalAgentTypes failed: ${e instanceof Error ? e.message : String(e)}`);
36921
37108
  }
37109
+ if (isCliImmediateShutdownRequested()) return;
36922
37110
  try {
36923
37111
  const n = await probeAgentCapabilitiesForDetectedTypes({
36924
37112
  agentTypes: types,
36925
37113
  cwd,
36926
37114
  workspaceId,
36927
37115
  log: log2,
36928
- getDb,
36929
37116
  bridgeReport: false,
36930
37117
  forceAllTypes: false
36931
37118
  });
36932
- if (n > 0) sendBatchFromCache();
37119
+ if (n > 0) await sendBatchFromCache();
36933
37120
  } catch (e) {
37121
+ if (e instanceof CliSqliteInterrupted) return;
36934
37122
  log2(`[Bridge service] Agent capability probe (missing cache) failed: ${e instanceof Error ? e.message : String(e)}`);
36935
37123
  }
36936
37124
  void (async () => {
36937
37125
  try {
36938
37126
  await yieldToEventLoop();
37127
+ if (isCliImmediateShutdownRequested()) return;
36939
37128
  const n = await probeAgentCapabilitiesForDetectedTypes({
36940
37129
  agentTypes: types,
36941
37130
  cwd,
36942
37131
  workspaceId,
36943
37132
  log: log2,
36944
- getDb,
36945
37133
  bridgeReport: false,
36946
37134
  forceAllTypes: true
36947
37135
  });
36948
- if (n > 0) sendBatchFromCache();
37136
+ if (n > 0) await sendBatchFromCache();
36949
37137
  } catch (e) {
37138
+ if (e instanceof CliSqliteInterrupted) return;
36950
37139
  log2(`[Bridge service] Agent capability lazy refresh failed: ${e instanceof Error ? e.message : String(e)}`);
36951
37140
  }
36952
37141
  })();
@@ -36957,7 +37146,7 @@ async function createBridgeConnection(options) {
36957
37146
  const { apiUrl, workspaceId, justAuthenticated, onAuthInvalid, persistTokens } = options;
36958
37147
  const firehoseServerUrl = options.firehoseServerUrl ?? options.proxyServerUrl;
36959
37148
  const logFn = options.log ?? log;
36960
- getCliDatabase({ logLegacyMigration: logFn });
37149
+ await ensureCliSqliteInitialized({ logLegacyMigration: logFn });
36961
37150
  const tokens = {
36962
37151
  accessToken: options.authToken,
36963
37152
  refreshToken: options.refreshToken
@@ -36987,12 +37176,15 @@ async function createBridgeConnection(options) {
36987
37176
  if (!Array.isArray(info.configOptions) || info.configOptions.length === 0) return;
36988
37177
  let changed = false;
36989
37178
  try {
36990
- changed = upsertCliAgentCapabilityCache(getCliDatabase(), {
36991
- workspaceId,
36992
- agentType: info.agentType,
36993
- configOptions: info.configOptions
36994
- });
36995
- } catch {
37179
+ changed = withCliSqliteSync(
37180
+ (db) => upsertCliAgentCapabilityCache(db, {
37181
+ workspaceId,
37182
+ agentType: info.agentType,
37183
+ configOptions: info.configOptions
37184
+ })
37185
+ );
37186
+ } catch (e) {
37187
+ if (e instanceof CliSqliteInterrupted) return;
36996
37188
  }
36997
37189
  if (!changed) return;
36998
37190
  const socket = getWs();
@@ -37044,7 +37236,6 @@ async function createBridgeConnection(options) {
37044
37236
  await warmupAgentCapabilitiesOnConnect({
37045
37237
  workspaceId,
37046
37238
  log: logFn,
37047
- getDb: getCliDatabase,
37048
37239
  getWs
37049
37240
  });
37050
37241
  },
@@ -37076,6 +37267,7 @@ async function createBridgeConnection(options) {
37076
37267
  const stopFileIndexWatcher = startFileIndexWatcher(getBridgeRoot());
37077
37268
  return {
37078
37269
  close: async () => {
37270
+ requestCliImmediateShutdown();
37079
37271
  stopFileIndexWatcher();
37080
37272
  bridgeHeartbeat.stop();
37081
37273
  await closeBridgeConnection(state, acpManager, devServerManager, logFn);
@@ -37149,47 +37341,60 @@ async function runConnectedBridge(options, restartWithoutAuth) {
37149
37341
  } = options;
37150
37342
  const firehoseServerUrl = options.firehoseServerUrl ?? options.proxyServerUrl;
37151
37343
  let cleanupKeyCommand;
37152
- const handle = await createBridgeConnection({
37153
- apiUrl,
37154
- workspaceId,
37155
- authToken,
37156
- refreshToken,
37157
- firehoseServerUrl,
37158
- justAuthenticated,
37159
- worktreesRootPath,
37160
- e2eCertificate,
37161
- log,
37162
- persistTokens: (t) => {
37163
- writeConfigForApi(apiUrl, {
37164
- workspaceId,
37165
- token: t.token,
37166
- refreshToken: t.refreshToken
37167
- });
37168
- },
37169
- onAuthInvalid: () => {
37170
- cleanupKeyCommand?.();
37171
- log("[Bridge service] Access token invalid or revoked; re-authenticating\u2026");
37172
- clearConfigForApi(apiUrl);
37173
- void handle.close().then(() => {
37174
- void restartWithoutAuth({ apiUrl, firehoseServerUrl, worktreesRootPath, e2eCertificate });
37175
- });
37176
- }
37177
- });
37344
+ let bridgeClose = null;
37178
37345
  const onSignal = (kind) => {
37346
+ requestCliImmediateShutdown();
37179
37347
  cleanupKeyCommand?.();
37180
37348
  logImmediate(
37181
37349
  kind === "interrupt" ? "Keyboard interrupt (Ctrl+C) \u2014 stopping\u2026" : "Stop requested \u2014 shutting down\u2026"
37182
37350
  );
37183
- setImmediate(() => {
37184
- void handle.close().then(() => {
37185
- process.exit(0);
37186
- });
37187
- });
37351
+ if (bridgeClose) {
37352
+ void bridgeClose().then(() => process.exit(0));
37353
+ return;
37354
+ }
37355
+ process.exit(0);
37188
37356
  };
37189
37357
  const onSigInt = () => onSignal("interrupt");
37190
37358
  const onSigTerm = () => onSignal("stop");
37191
37359
  process.on("SIGINT", onSigInt);
37192
37360
  process.on("SIGTERM", onSigTerm);
37361
+ let handle;
37362
+ try {
37363
+ handle = await createBridgeConnection({
37364
+ apiUrl,
37365
+ workspaceId,
37366
+ authToken,
37367
+ refreshToken,
37368
+ firehoseServerUrl,
37369
+ justAuthenticated,
37370
+ worktreesRootPath,
37371
+ e2eCertificate,
37372
+ log,
37373
+ persistTokens: (t) => {
37374
+ writeConfigForApi(apiUrl, {
37375
+ workspaceId,
37376
+ token: t.token,
37377
+ refreshToken: t.refreshToken
37378
+ });
37379
+ },
37380
+ onAuthInvalid: () => {
37381
+ cleanupKeyCommand?.();
37382
+ log("[Bridge service] Access token invalid or revoked; re-authenticating\u2026");
37383
+ clearConfigForApi(apiUrl);
37384
+ void handle.close().then(() => {
37385
+ void restartWithoutAuth({ apiUrl, firehoseServerUrl, worktreesRootPath, e2eCertificate });
37386
+ });
37387
+ }
37388
+ });
37389
+ } catch (e) {
37390
+ process.off("SIGINT", onSigInt);
37391
+ process.off("SIGTERM", onSigTerm);
37392
+ if (e instanceof CliSqliteInterrupted) {
37393
+ process.exit(0);
37394
+ }
37395
+ throw e;
37396
+ }
37397
+ bridgeClose = () => handle.close();
37193
37398
  if (e2eCertificate) {
37194
37399
  let openingCertificate = false;
37195
37400
  cleanupKeyCommand = installE2eCertificateKeyCommand({
@@ -37234,6 +37439,7 @@ async function runBridge(options) {
37234
37439
  }
37235
37440
  });
37236
37441
  const onSignal = (kind) => {
37442
+ requestCliImmediateShutdown();
37237
37443
  logImmediate(
37238
37444
  kind === "interrupt" ? "Keyboard interrupt (Ctrl+C) \u2014 stopping\u2026" : "Stop requested \u2014 shutting down\u2026"
37239
37445
  );