@buildautomaton/cli 0.1.32 → 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.32".length > 0 ? "0.1.32" : "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,6 +26696,30 @@ 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
 
@@ -26999,6 +27023,12 @@ var { Database: SqliteDatabase } = sqliteWasm;
26999
27023
  var CLI_SQLITE_SYNC_RETRY_MAX = 40;
27000
27024
  var CLI_SQLITE_ASYNC_RETRY_MAX = 60;
27001
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
+ };
27002
27032
  function applyCliSqliteConcurrencyPragmas(db) {
27003
27033
  try {
27004
27034
  db.exec("PRAGMA journal_mode = WAL");
@@ -27009,7 +27039,7 @@ function applyCliSqliteConcurrencyPragmas(db) {
27009
27039
  } catch {
27010
27040
  }
27011
27041
  try {
27012
- db.run("PRAGMA busy_timeout = 8000");
27042
+ db.run("PRAGMA busy_timeout = 500");
27013
27043
  } catch {
27014
27044
  }
27015
27045
  }
@@ -27030,26 +27060,42 @@ function safeCloseCliSqliteDatabase(db) {
27030
27060
  function closeAllCliSqliteConnections() {
27031
27061
  }
27032
27062
  function isCliSqliteLockError(e) {
27063
+ if (e instanceof CliSqliteInterrupted) return false;
27033
27064
  const msg = e instanceof Error ? e.message : String(e);
27034
27065
  const lower = msg.toLowerCase();
27035
27066
  return lower.includes("database is locked") || lower.includes("sqlite_busy") || lower.includes("sqlite3_busy") || lower.includes("database") && lower.includes("locked");
27036
27067
  }
27037
27068
  function syncSleepMs(ms) {
27038
27069
  if (ms <= 0) return;
27039
- try {
27040
- const sab = new SharedArrayBuffer(4);
27041
- const ia = new Int32Array(sab);
27042
- Atomics.wait(ia, 0, 0, Math.min(ms, 1e4));
27043
- } catch {
27044
- const end = Date.now() + ms;
27045
- while (Date.now() < end) {
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
+ }
27046
27084
  }
27047
27085
  }
27048
27086
  }
27049
- function asyncDelayMs(ms) {
27050
- return new Promise((resolve20) => setTimeout(resolve20, ms));
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();
27095
+ }
27051
27096
  }
27052
27097
  function openCliSqliteConnection(options) {
27098
+ if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
27053
27099
  const sqlitePath = getCliSqlitePath();
27054
27100
  ensureCliSqliteParentDir(sqlitePath);
27055
27101
  const db = new SqliteDatabase(sqlitePath);
@@ -27066,6 +27112,7 @@ function openCliSqliteConnection(options) {
27066
27112
  }
27067
27113
  function withCliSqliteSync(fn, options) {
27068
27114
  for (let attempt = 1; attempt <= CLI_SQLITE_SYNC_RETRY_MAX; attempt++) {
27115
+ if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
27069
27116
  let db;
27070
27117
  try {
27071
27118
  db = openCliSqliteConnection(options);
@@ -27076,6 +27123,7 @@ function withCliSqliteSync(fn, options) {
27076
27123
  }
27077
27124
  } catch (e) {
27078
27125
  safeCloseCliSqliteDatabase(db);
27126
+ if (e instanceof CliSqliteInterrupted) throw e;
27079
27127
  if (!isCliSqliteLockError(e) || attempt === CLI_SQLITE_SYNC_RETRY_MAX) throw e;
27080
27128
  syncSleepMs(Math.min(500, 12 * attempt));
27081
27129
  }
@@ -27085,6 +27133,7 @@ function withCliSqliteSync(fn, options) {
27085
27133
  async function withCliSqlite(fn, options) {
27086
27134
  let lastError;
27087
27135
  for (let attempt = 1; attempt <= CLI_SQLITE_ASYNC_RETRY_MAX; attempt++) {
27136
+ if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
27088
27137
  let db;
27089
27138
  try {
27090
27139
  db = openCliSqliteConnection(options);
@@ -27096,9 +27145,10 @@ async function withCliSqlite(fn, options) {
27096
27145
  } catch (e) {
27097
27146
  lastError = e;
27098
27147
  safeCloseCliSqliteDatabase(db);
27148
+ if (e instanceof CliSqliteInterrupted) throw e;
27099
27149
  if (!isCliSqliteLockError(e) || attempt === CLI_SQLITE_ASYNC_RETRY_MAX) throw e;
27100
27150
  const delayMs = Math.min(600, CLI_SQLITE_ASYNC_BASE_DELAY_MS * attempt);
27101
- await asyncDelayMs(delayMs);
27151
+ await asyncDelayMsInterruptible(delayMs);
27102
27152
  await yieldToEventLoop();
27103
27153
  }
27104
27154
  }
@@ -27111,9 +27161,9 @@ async function ensureCliSqliteInitialized(options) {
27111
27161
 
27112
27162
  // src/connection/close-bridge-connection.ts
27113
27163
  async function closeBridgeConnection(state, acpManager, devServerManager, log2) {
27164
+ requestCliImmediateShutdown();
27114
27165
  const say = log2 ?? logImmediate;
27115
27166
  say("Cleaning up connections\u2026");
27116
- await new Promise((resolve20) => setImmediate(resolve20));
27117
27167
  state.closedByUser = true;
27118
27168
  clearReconnectQuietTimer(state.mainQuiet);
27119
27169
  clearReconnectQuietTimer(state.firehoseQuiet);
@@ -27150,7 +27200,7 @@ async function closeBridgeConnection(state, acpManager, devServerManager, log2)
27150
27200
  }
27151
27201
  if (devServerManager) {
27152
27202
  say("Stopping local dev server processes\u2026");
27153
- await devServerManager.shutdownAllGraceful();
27203
+ await devServerManager.shutdownAllGraceful({ graceMs: BRIDGE_CLOSE_DEV_SERVER_GRACE_MS });
27154
27204
  }
27155
27205
  try {
27156
27206
  closeAllCliSqliteConnections();
@@ -32929,16 +32979,38 @@ __export(claude_code_acp_client_exports, {
32929
32979
  createClaudeCodeAcpClient: () => createClaudeCodeAcpClient,
32930
32980
  detectLocalAgentPresence: () => detectLocalAgentPresence
32931
32981
  });
32932
- import { execFile as execFile9 } from "node:child_process";
32933
- import { promisify as promisify9 } from "node:util";
32934
32982
 
32935
32983
  // src/agents/acp/clients/detect-command-on-path.ts
32936
32984
  import { execFile as execFile8 } from "node:child_process";
32937
32985
  import { promisify as promisify8 } from "node:util";
32938
32986
  var execFileAsync7 = promisify8(execFile8);
32939
- 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?.();
32940
32995
  try {
32941
- await execFileAsync7("which", [command], { timeout: timeoutMs });
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;
33012
+ try {
33013
+ await execFileShutdownAware(file2, args, timeoutMs);
32942
33014
  return true;
32943
33015
  } catch {
32944
33016
  return false;
@@ -33699,16 +33771,10 @@ async function createSdkStdioAcpClient(options) {
33699
33771
  }
33700
33772
 
33701
33773
  // src/agents/acp/clients/claude-code-acp-client.ts
33702
- var execFileAsync8 = promisify9(execFile9);
33703
33774
  var BACKEND_LOCAL_AGENT_TYPE = "claude-code";
33704
33775
  async function detectLocalAgentPresence() {
33705
33776
  if (await isCommandOnPath("claude")) return true;
33706
- try {
33707
- await execFileAsync8("npx", ["--yes", "@anthropic-ai/claude-code", "--version"], { timeout: 25e3 });
33708
- return true;
33709
- } catch {
33710
- return false;
33711
- }
33777
+ return execProbeShutdownAware("npx", ["--yes", "@anthropic-ai/claude-code", "--version"], 3e3);
33712
33778
  }
33713
33779
  function buildClaudeCodeAcpSpawnCommand(base, _sessionMode) {
33714
33780
  return [...base];
@@ -36537,30 +36603,39 @@ import path28 from "node:path";
36537
36603
  function shouldSkipWorkspaceWalkEntry(name) {
36538
36604
  return name.startsWith(".");
36539
36605
  }
36540
- function walkWorkspaceTreeSync(dir, baseDir, onFile) {
36606
+ async function walkWorkspaceTreeAsync(dir, baseDir, onFile, state) {
36541
36607
  let names;
36542
36608
  try {
36543
- names = fs25.readdirSync(dir);
36609
+ names = await fs25.promises.readdir(dir);
36544
36610
  } catch {
36545
36611
  return;
36546
36612
  }
36547
36613
  for (const name of names) {
36548
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++;
36549
36621
  const full = path28.join(dir, name);
36550
36622
  let stat3;
36551
36623
  try {
36552
- stat3 = fs25.statSync(full);
36624
+ stat3 = await fs25.promises.stat(full);
36553
36625
  } catch {
36554
36626
  continue;
36555
36627
  }
36556
36628
  const relative5 = path28.relative(baseDir, full).replace(/\\/g, "/");
36557
36629
  if (stat3.isDirectory()) {
36558
- walkWorkspaceTreeSync(full, baseDir, onFile);
36630
+ await walkWorkspaceTreeAsync(full, baseDir, onFile, state);
36559
36631
  } else if (stat3.isFile()) {
36560
36632
  onFile(relative5);
36561
36633
  }
36562
36634
  }
36563
36635
  }
36636
+ function createWalkYieldState() {
36637
+ return { n: 0 };
36638
+ }
36564
36639
 
36565
36640
  // src/files/index/file-index-sqlite-lock.ts
36566
36641
  import fs26 from "node:fs";
@@ -36593,29 +36668,28 @@ function withFileIndexSqliteLock(fn) {
36593
36668
  }
36594
36669
 
36595
36670
  // src/files/index/build-file-index.ts
36596
- var FILE_INDEX_INSERT_BUFFER = 2048;
36597
- function persistFileIndexForResolvedCwd(resolved) {
36671
+ var FILE_INDEX_INTERRUPT_CHECK_EVERY = 256;
36672
+ function assertNotShutdown() {
36673
+ if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
36674
+ }
36675
+ function persistFileIndexPaths(resolved, paths) {
36598
36676
  return withCliSqliteSync((db) => {
36599
36677
  const h = getCwdHashForFileIndex(resolved);
36600
- const buf = [];
36601
36678
  let pathCount = 0;
36602
36679
  db.run("BEGIN IMMEDIATE");
36603
36680
  try {
36604
36681
  db.run("DELETE FROM file_index_path WHERE cwd_hash = ?", [h]);
36605
36682
  const ins = db.prepare("INSERT INTO file_index_path (cwd_hash, path) VALUES (?, ?)");
36606
36683
  try {
36607
- const flushBuf = () => {
36608
- for (const rel of buf) {
36609
- ins.run([h, rel]);
36684
+ let batch = 0;
36685
+ for (const rel of paths) {
36686
+ if (++batch >= FILE_INDEX_INTERRUPT_CHECK_EVERY) {
36687
+ batch = 0;
36688
+ assertNotShutdown();
36610
36689
  }
36611
- pathCount += buf.length;
36612
- buf.length = 0;
36613
- };
36614
- walkWorkspaceTreeSync(resolved, resolved, (rel) => {
36615
- buf.push(rel);
36616
- if (buf.length >= FILE_INDEX_INSERT_BUFFER) flushBuf();
36617
- });
36618
- flushBuf();
36690
+ ins.run([h, rel]);
36691
+ pathCount += 1;
36692
+ }
36619
36693
  } finally {
36620
36694
  ins.finalize();
36621
36695
  }
@@ -36630,12 +36704,24 @@ function persistFileIndexForResolvedCwd(resolved) {
36630
36704
  return pathCount;
36631
36705
  });
36632
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;
36715
+ }
36633
36716
  async function buildFileIndexAsync(cwd) {
36634
36717
  return withFileIndexSqliteLock(async () => {
36635
36718
  const resolved = path29.resolve(cwd);
36636
36719
  await yieldToEventLoop();
36637
- const pathCount = persistFileIndexForResolvedCwd(resolved);
36720
+ assertNotShutdown();
36721
+ const paths = await collectWorkspacePathsAsync(resolved);
36638
36722
  await yieldToEventLoop();
36723
+ assertNotShutdown();
36724
+ const pathCount = persistFileIndexPaths(resolved, paths);
36639
36725
  return { pathCount };
36640
36726
  });
36641
36727
  }
@@ -36739,11 +36825,13 @@ function createFsWatcher(resolved, schedule) {
36739
36825
  function startFileIndexWatcher(cwd = getBridgeRoot()) {
36740
36826
  const resolved = path31.resolve(cwd);
36741
36827
  void buildFileIndexAsync(resolved).catch((e) => {
36828
+ if (e instanceof CliSqliteInterrupted) return;
36742
36829
  console.error("[file-index] Initial index build failed:", e);
36743
36830
  });
36744
36831
  let timer = null;
36745
36832
  const runRebuild = () => {
36746
36833
  void buildFileIndexAsync(resolved).catch((e) => {
36834
+ if (e instanceof CliSqliteInterrupted) return;
36747
36835
  console.error("[file-index] Watch rebuild failed:", e);
36748
36836
  });
36749
36837
  };
@@ -37277,9 +37365,6 @@ var StreamTail = class {
37277
37365
  }
37278
37366
  };
37279
37367
 
37280
- // src/dev-servers/manager/dev-server-constants.ts
37281
- var BRIDGE_SHUTDOWN_GRACE_MS = 8e3;
37282
-
37283
37368
  // src/dev-servers/manager/dev-server-firehose-messages.ts
37284
37369
  function buildFirehoseSnapshotMessage(params) {
37285
37370
  const payload = {
@@ -37565,22 +37650,23 @@ var DevServerManager = class {
37565
37650
  }
37566
37651
  this.start(serverId);
37567
37652
  }
37568
- async shutdownAllGraceful() {
37653
+ async shutdownAllGraceful(opts) {
37654
+ const graceMs = opts?.graceMs ?? BRIDGE_SHUTDOWN_GRACE_MS;
37569
37655
  const pairs = [...this.processes.entries()];
37570
37656
  if (pairs.length === 0) return;
37571
37657
  this.log(
37572
37658
  `[dev-server] Stopping ${pairs.length} local dev server process${pairs.length === 1 ? "" : "es"}\u2026`
37573
37659
  );
37574
- await Promise.all(pairs.map(([serverId, proc]) => this.gracefulTerminateOrUnknown(serverId, proc)));
37660
+ await Promise.all(pairs.map(([serverId, proc]) => this.gracefulTerminateOrUnknown(serverId, proc, graceMs)));
37575
37661
  }
37576
- async gracefulTerminateOrUnknown(serverId, proc) {
37662
+ async gracefulTerminateOrUnknown(serverId, proc, graceMs) {
37577
37663
  const shortId = `${serverId.slice(0, 8)}\u2026`;
37578
- await sigtermAndWaitForExit(proc, BRIDGE_SHUTDOWN_GRACE_MS, this.log, shortId);
37664
+ await sigtermAndWaitForExit(proc, graceMs, this.log, shortId);
37579
37665
  if (!this.processes.has(serverId) || this.processes.get(serverId) !== proc) {
37580
37666
  return;
37581
37667
  }
37582
37668
  this.bumpGeneration(serverId);
37583
- forceKillChild(proc, this.log, shortId, BRIDGE_SHUTDOWN_GRACE_MS);
37669
+ forceKillChild(proc, this.log, shortId, graceMs);
37584
37670
  this.processes.delete(serverId);
37585
37671
  this.clearPoll(serverId);
37586
37672
  this.pipedCaptureByServerId.delete(serverId);
@@ -38175,10 +38261,13 @@ var LOCAL_AGENT_ACP_MODULES = [
38175
38261
  ];
38176
38262
  async function detectLocalAgentTypes() {
38177
38263
  try {
38264
+ if (isCliImmediateShutdownRequested()) return [];
38178
38265
  const out = [];
38179
38266
  for (let i = 0; i < LOCAL_AGENT_ACP_MODULES.length; i++) {
38267
+ if (isCliImmediateShutdownRequested()) return out;
38180
38268
  if (i > 0) {
38181
38269
  await yieldToEventLoop();
38270
+ if (isCliImmediateShutdownRequested()) return out;
38182
38271
  }
38183
38272
  const mod = LOCAL_AGENT_ACP_MODULES[i];
38184
38273
  try {
@@ -38211,6 +38300,7 @@ function createSendLocalSkillsReport(getWs, logFn) {
38211
38300
  }
38212
38301
  function createReportAutoDetectedAgents(getWs, logFn) {
38213
38302
  return async () => {
38303
+ if (isCliImmediateShutdownRequested()) return;
38214
38304
  try {
38215
38305
  const types = await detectLocalAgentTypes();
38216
38306
  const socket = getWs();
@@ -38310,6 +38400,7 @@ var handleBridgeIdentified = (msg, deps) => {
38310
38400
  });
38311
38401
  setImmediate(() => {
38312
38402
  void (async () => {
38403
+ if (isCliImmediateShutdownRequested()) return;
38313
38404
  try {
38314
38405
  await deps.reportAutoDetectedAgents?.();
38315
38406
  } catch (e) {
@@ -38317,6 +38408,7 @@ var handleBridgeIdentified = (msg, deps) => {
38317
38408
  `[Bridge service] Auto-detect agents failed: ${e instanceof Error ? e.message : String(e)}`
38318
38409
  );
38319
38410
  }
38411
+ if (isCliImmediateShutdownRequested()) return;
38320
38412
  try {
38321
38413
  await deps.warmupAgentCapabilitiesOnConnect?.();
38322
38414
  } catch (e) {
@@ -38327,6 +38419,7 @@ var handleBridgeIdentified = (msg, deps) => {
38327
38419
  })();
38328
38420
  });
38329
38421
  setImmediate(() => {
38422
+ if (isCliImmediateShutdownRequested()) return;
38330
38423
  try {
38331
38424
  deps.sendLocalSkillsReport?.();
38332
38425
  } catch (e) {
@@ -38628,12 +38721,12 @@ function createBridgePromptSenders(deps, getWs) {
38628
38721
  }
38629
38722
 
38630
38723
  // src/agents/acp/from-bridge/bridge-prompt-preamble.ts
38631
- import { execFile as execFile10 } from "node:child_process";
38632
- import { promisify as promisify10 } from "node:util";
38633
- 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);
38634
38727
  async function readGitBranch(cwd) {
38635
38728
  try {
38636
- 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 });
38637
38730
  const b = stdout.trim();
38638
38731
  return b || null;
38639
38732
  } catch {
@@ -40099,6 +40192,7 @@ import * as path40 from "node:path";
40099
40192
  import * as path39 from "node:path";
40100
40193
  async function probeOneAgentTypeForCapabilities(params) {
40101
40194
  const { agentType, cwd, workspaceId, log: log2, reportAgentCapabilities, bridgeReport = true } = params;
40195
+ if (isCliImmediateShutdownRequested()) return false;
40102
40196
  const resolved = resolveAgentCommand(agentType);
40103
40197
  if (!resolved) return false;
40104
40198
  let sqliteChanged = false;
@@ -40132,6 +40226,7 @@ async function probeOneAgentTypeForCapabilities(params) {
40132
40226
  }, 28e3);
40133
40227
  killTimer.unref?.();
40134
40228
  try {
40229
+ if (isCliImmediateShutdownRequested()) return false;
40135
40230
  handle = await resolved.createClient({
40136
40231
  command: resolved.command,
40137
40232
  cwd: path39.resolve(cwd),
@@ -40151,7 +40246,7 @@ async function probeOneAgentTypeForCapabilities(params) {
40151
40246
  onSessionUpdate: () => {
40152
40247
  }
40153
40248
  });
40154
- await new Promise((r) => setTimeout(r, 1200));
40249
+ if (!await delayMsUnlessShutdownRequested(1200)) return false;
40155
40250
  } catch (e) {
40156
40251
  log2(
40157
40252
  `[Bridge service] Agent capability probe (${agentType}): ${e instanceof Error ? e.message : String(e)}`
@@ -40179,6 +40274,7 @@ async function probeAgentCapabilitiesForDetectedTypes(params) {
40179
40274
  } = params;
40180
40275
  let changedCount = 0;
40181
40276
  for (let i = 0; i < agentTypes.length; i++) {
40277
+ if (isCliImmediateShutdownRequested()) return changedCount;
40182
40278
  if (i > 0) await yieldToEventLoop();
40183
40279
  const agentType = agentTypes[i];
40184
40280
  if (!agentType.trim()) continue;
@@ -40206,6 +40302,7 @@ async function probeAgentCapabilitiesForDetectedTypes(params) {
40206
40302
  // src/agents/capabilities/warmup-agent-capabilities-on-connect.ts
40207
40303
  async function warmupAgentCapabilitiesOnConnect(params) {
40208
40304
  const { workspaceId, log: log2, getWs } = params;
40305
+ if (isCliImmediateShutdownRequested()) return;
40209
40306
  const cwd = path40.resolve(getBridgeRoot());
40210
40307
  async function sendBatchFromCache() {
40211
40308
  const socket = getWs();
@@ -40218,18 +40315,21 @@ async function warmupAgentCapabilitiesOnConnect(params) {
40218
40315
  items: rows.map((r) => ({ agentType: r.agentType, configOptions: r.configOptions }))
40219
40316
  });
40220
40317
  } catch (e) {
40318
+ if (e instanceof CliSqliteInterrupted) return;
40221
40319
  log2(
40222
40320
  `[Bridge service] Agent capability batch to bridge failed: ${e instanceof Error ? e.message : String(e)}`
40223
40321
  );
40224
40322
  }
40225
40323
  }
40226
40324
  await sendBatchFromCache();
40325
+ if (isCliImmediateShutdownRequested()) return;
40227
40326
  let types = [];
40228
40327
  try {
40229
40328
  types = [...await detectLocalAgentTypes()];
40230
40329
  } catch (e) {
40231
40330
  log2(`[Bridge service] detectLocalAgentTypes failed: ${e instanceof Error ? e.message : String(e)}`);
40232
40331
  }
40332
+ if (isCliImmediateShutdownRequested()) return;
40233
40333
  try {
40234
40334
  const n = await probeAgentCapabilitiesForDetectedTypes({
40235
40335
  agentTypes: types,
@@ -40241,11 +40341,13 @@ async function warmupAgentCapabilitiesOnConnect(params) {
40241
40341
  });
40242
40342
  if (n > 0) await sendBatchFromCache();
40243
40343
  } catch (e) {
40344
+ if (e instanceof CliSqliteInterrupted) return;
40244
40345
  log2(`[Bridge service] Agent capability probe (missing cache) failed: ${e instanceof Error ? e.message : String(e)}`);
40245
40346
  }
40246
40347
  void (async () => {
40247
40348
  try {
40248
40349
  await yieldToEventLoop();
40350
+ if (isCliImmediateShutdownRequested()) return;
40249
40351
  const n = await probeAgentCapabilitiesForDetectedTypes({
40250
40352
  agentTypes: types,
40251
40353
  cwd,
@@ -40256,6 +40358,7 @@ async function warmupAgentCapabilitiesOnConnect(params) {
40256
40358
  });
40257
40359
  if (n > 0) await sendBatchFromCache();
40258
40360
  } catch (e) {
40361
+ if (e instanceof CliSqliteInterrupted) return;
40259
40362
  log2(`[Bridge service] Agent capability lazy refresh failed: ${e instanceof Error ? e.message : String(e)}`);
40260
40363
  }
40261
40364
  })();
@@ -40303,7 +40406,8 @@ async function createBridgeConnection(options) {
40303
40406
  configOptions: info.configOptions
40304
40407
  })
40305
40408
  );
40306
- } catch {
40409
+ } catch (e) {
40410
+ if (e instanceof CliSqliteInterrupted) return;
40307
40411
  }
40308
40412
  if (!changed) return;
40309
40413
  const socket = getWs();
@@ -40386,6 +40490,7 @@ async function createBridgeConnection(options) {
40386
40490
  const stopFileIndexWatcher = startFileIndexWatcher(getBridgeRoot());
40387
40491
  return {
40388
40492
  close: async () => {
40493
+ requestCliImmediateShutdown();
40389
40494
  stopFileIndexWatcher();
40390
40495
  bridgeHeartbeat.stop();
40391
40496
  await closeBridgeConnection(state, acpManager, devServerManager, logFn);
@@ -40459,47 +40564,60 @@ async function runConnectedBridge(options, restartWithoutAuth) {
40459
40564
  } = options;
40460
40565
  const firehoseServerUrl = options.firehoseServerUrl ?? options.proxyServerUrl;
40461
40566
  let cleanupKeyCommand;
40462
- const handle = await createBridgeConnection({
40463
- apiUrl,
40464
- workspaceId,
40465
- authToken,
40466
- refreshToken,
40467
- firehoseServerUrl,
40468
- justAuthenticated,
40469
- worktreesRootPath,
40470
- e2eCertificate,
40471
- log,
40472
- persistTokens: (t) => {
40473
- writeConfigForApi(apiUrl, {
40474
- workspaceId,
40475
- token: t.token,
40476
- refreshToken: t.refreshToken
40477
- });
40478
- },
40479
- onAuthInvalid: () => {
40480
- cleanupKeyCommand?.();
40481
- log("[Bridge service] Access token invalid or revoked; re-authenticating\u2026");
40482
- clearConfigForApi(apiUrl);
40483
- void handle.close().then(() => {
40484
- void restartWithoutAuth({ apiUrl, firehoseServerUrl, worktreesRootPath, e2eCertificate });
40485
- });
40486
- }
40487
- });
40567
+ let bridgeClose = null;
40488
40568
  const onSignal = (kind) => {
40569
+ requestCliImmediateShutdown();
40489
40570
  cleanupKeyCommand?.();
40490
40571
  logImmediate(
40491
40572
  kind === "interrupt" ? "Keyboard interrupt (Ctrl+C) \u2014 stopping\u2026" : "Stop requested \u2014 shutting down\u2026"
40492
40573
  );
40493
- setImmediate(() => {
40494
- void handle.close().then(() => {
40495
- process.exit(0);
40496
- });
40497
- });
40574
+ if (bridgeClose) {
40575
+ void bridgeClose().then(() => process.exit(0));
40576
+ return;
40577
+ }
40578
+ process.exit(0);
40498
40579
  };
40499
40580
  const onSigInt = () => onSignal("interrupt");
40500
40581
  const onSigTerm = () => onSignal("stop");
40501
40582
  process.on("SIGINT", onSigInt);
40502
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();
40503
40621
  if (e2eCertificate) {
40504
40622
  let openingCertificate = false;
40505
40623
  cleanupKeyCommand = installE2eCertificateKeyCommand({
@@ -40544,6 +40662,7 @@ async function runBridge(options) {
40544
40662
  }
40545
40663
  });
40546
40664
  const onSignal = (kind) => {
40665
+ requestCliImmediateShutdown();
40547
40666
  logImmediate(
40548
40667
  kind === "interrupt" ? "Keyboard interrupt (Ctrl+C) \u2014 stopping\u2026" : "Stop requested \u2014 shutting down\u2026"
40549
40668
  );