@cortexkit/aft 0.62.0 → 0.63.4

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
@@ -110,12 +110,125 @@ var init_active_logger = __esm(() => {
110
110
  ACTIVE_LOGGER_SYMBOL = Symbol.for("aft-bridge-active-logger");
111
111
  });
112
112
 
113
+ // ../aft-bridge/dist/bash-env.js
114
+ var BLOCKED_BASH_ENV_NAMES, PROJECT_ONLY_BLOCKED_BASH_ENV_NAMES, PROJECT_ONLY_NETWORK_BLOCKED_BASH_ENV_NAMES, BLOCKED, PROJECT_ONLY;
115
+ var init_bash_env = __esm(() => {
116
+ BLOCKED_BASH_ENV_NAMES = [
117
+ "LD_PRELOAD",
118
+ "LD_LIBRARY_PATH",
119
+ "LD_AUDIT",
120
+ "DYLD_INSERT_LIBRARIES",
121
+ "DYLD_LIBRARY_PATH",
122
+ "DYLD_FALLBACK_LIBRARY_PATH",
123
+ "BASH_ENV",
124
+ "ENV",
125
+ "IFS",
126
+ "PATH",
127
+ "SHELLOPTS",
128
+ "BASHOPTS",
129
+ "PROMPT_COMMAND",
130
+ "PS4",
131
+ "PYTHONSTARTUP",
132
+ "PYTHONPATH",
133
+ "PYTHONHOME",
134
+ "PERL5OPT",
135
+ "PERL5LIB",
136
+ "PERLLIB",
137
+ "PERL5DB",
138
+ "RUBYOPT",
139
+ "RUBYLIB",
140
+ "NODE_OPTIONS",
141
+ "GCONV_PATH",
142
+ "LOCPATH",
143
+ "NLSPATH",
144
+ "HOSTALIASES",
145
+ "RESOLV_HOST_CONF",
146
+ "HOME",
147
+ "SHELL",
148
+ "TMPDIR",
149
+ "TMP",
150
+ "TEMP",
151
+ "XDG_CONFIG_HOME",
152
+ "XDG_DATA_HOME",
153
+ "XDG_CACHE_HOME",
154
+ "XDG_STATE_HOME",
155
+ "XDG_RUNTIME_DIR",
156
+ "CARGO_HOME",
157
+ "RUSTUP_HOME",
158
+ "USERPROFILE",
159
+ "APPDATA",
160
+ "LOCALAPPDATA",
161
+ "COMSPEC",
162
+ "PATHEXT",
163
+ "SYSTEMROOT",
164
+ "PSMODULEPATH",
165
+ "NODE_PATH",
166
+ "JAVA_TOOL_OPTIONS",
167
+ "_JAVA_OPTIONS",
168
+ "JDK_JAVA_OPTIONS",
169
+ "PYTHONUSERBASE",
170
+ "GIT_DIR",
171
+ "GIT_WORK_TREE",
172
+ "GIT_COMMON_DIR",
173
+ "GIT_INDEX_FILE",
174
+ "GIT_OBJECT_DIRECTORY",
175
+ "GIT_ALTERNATE_OBJECT_DIRECTORIES",
176
+ "GIT_CEILING_DIRECTORIES",
177
+ "GIT_EXEC_PATH",
178
+ "GIT_TEMPLATE_DIR",
179
+ "GIT_SSH",
180
+ "GIT_SSH_COMMAND",
181
+ "GIT_ASKPASS",
182
+ "SSH_ASKPASS",
183
+ "GIT_EXTERNAL_DIFF",
184
+ "GIT_PROXY_COMMAND",
185
+ "RUSTC_WRAPPER",
186
+ "CARGO_BUILD_RUSTC_WRAPPER",
187
+ "RUSTC_WORKSPACE_WRAPPER",
188
+ "RUST_TEST_THREADS",
189
+ "NEXTEST_TEST_THREADS",
190
+ "DBUS_SESSION_BUS_ADDRESS"
191
+ ];
192
+ PROJECT_ONLY_BLOCKED_BASH_ENV_NAMES = [
193
+ "SSH_AUTH_SOCK",
194
+ "GOOGLE_APPLICATION_CREDENTIALS",
195
+ "KUBECONFIG",
196
+ "DOCKER_HOST",
197
+ "DOCKER_CONFIG"
198
+ ];
199
+ PROJECT_ONLY_NETWORK_BLOCKED_BASH_ENV_NAMES = [
200
+ "HTTP_PROXY",
201
+ "HTTPS_PROXY",
202
+ "ALL_PROXY",
203
+ "NO_PROXY",
204
+ "SSL_CERT_FILE",
205
+ "SSL_CERT_DIR",
206
+ "NODE_EXTRA_CA_CERTS",
207
+ "REQUESTS_CA_BUNDLE",
208
+ "CURL_CA_BUNDLE",
209
+ "GIT_SSL_NO_VERIFY",
210
+ "NODE_TLS_REJECT_UNAUTHORIZED"
211
+ ];
212
+ BLOCKED = new Set(BLOCKED_BASH_ENV_NAMES);
213
+ PROJECT_ONLY = new Set([
214
+ ...PROJECT_ONLY_BLOCKED_BASH_ENV_NAMES,
215
+ ...PROJECT_ONLY_NETWORK_BLOCKED_BASH_ENV_NAMES
216
+ ]);
217
+ });
218
+
113
219
  // ../aft-bridge/dist/protocol.js
114
220
  function isTerminalBashStatus(status, compatibilityAliases = false) {
115
221
  return typeof status === "string" && (TERMINAL_BASH_STATUSES.has(status) || compatibilityAliases && (status === "cancelled" || status === "timeout"));
116
222
  }
117
- var TERMINAL_BG_TASK_STATUSES, TERMINAL_BASH_STATUSES;
223
+ var LIVE_DESCENDANTS_UNAVAILABLE_REASONS, TERMINAL_BG_TASK_STATUSES, TERMINAL_BASH_STATUSES;
118
224
  var init_protocol = __esm(() => {
225
+ LIVE_DESCENDANTS_UNAVAILABLE_REASONS = Object.freeze([
226
+ "unsupported_platform",
227
+ "no_process_group",
228
+ "listing_failed",
229
+ "listing_incomplete",
230
+ "not_checked"
231
+ ]);
119
232
  TERMINAL_BG_TASK_STATUSES = Object.freeze([
120
233
  "completed",
121
234
  "failed",
@@ -392,7 +505,16 @@ var init_bash_host_fallback = __esm(() => {
392
505
  BASH_HOST_FALLBACK_MAX_OUTPUT_BYTES = 100 * 1024;
393
506
  BASH_HOST_FALLBACK_MAX_TIMEOUT_MS = 10 * 60 * 1000;
394
507
  });
508
+ // ../aft-bridge/dist/bash-permissions.js
509
+ var init_bash_permissions = __esm(() => {
510
+ init_active_logger();
511
+ });
395
512
 
513
+ // ../aft-bridge/dist/bash-watch-lines.js
514
+ var DEFAULT_MASKING_LINE_CAP_BYTES;
515
+ var init_bash_watch_lines = __esm(() => {
516
+ DEFAULT_MASKING_LINE_CAP_BYTES = 8 * 1024;
517
+ });
396
518
  // ../aft-bridge/dist/binary-identity.js
397
519
  import { createHash } from "node:crypto";
398
520
  import { closeSync, createReadStream, openSync, readFileSync, readSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
@@ -627,9 +749,22 @@ var init_binary_identity = __esm(() => {
627
749
  function isPassiveCommand(command) {
628
750
  return PASSIVE_COMMANDS.has(command);
629
751
  }
630
- var PASSIVE_COMMANDS, PASSIVE_COMMAND_TIMEOUT_MS = 5000;
752
+ function isReadOnlyRetryCommand(tool) {
753
+ return READ_ONLY_RETRY_COMMANDS.has(tool);
754
+ }
755
+ var PASSIVE_COMMANDS, PASSIVE_COMMAND_TIMEOUT_MS = 5000, READ_ONLY_RETRY_COMMANDS;
631
756
  var init_command_timeouts = __esm(() => {
632
757
  PASSIVE_COMMANDS = new Set(["status"]);
758
+ READ_ONLY_RETRY_COMMANDS = new Set([
759
+ "read",
760
+ "outline",
761
+ "zoom",
762
+ "grep",
763
+ "glob",
764
+ "search",
765
+ "callgraph",
766
+ "inspect"
767
+ ]);
633
768
  });
634
769
 
635
770
  // ../aft-bridge/dist/transport.js
@@ -639,6 +774,12 @@ var WORKER_SESSION_FIELD = "worker_session", HOST_TOOL_NAMES_FIELD = "host_tool_
639
774
  import { homedir as homedir4 } from "node:os";
640
775
  import { join as join3 } from "node:path";
641
776
  import { StringDecoder } from "node:string_decoder";
777
+ function hangWindowMs(budgetsMs, minWindowMs = DEFAULT_MIN_HANG_WINDOW_MS) {
778
+ let longest = 0;
779
+ for (const budget of budgetsMs)
780
+ longest = Math.max(longest, budget);
781
+ return Math.max(minWindowMs, 2 * longest);
782
+ }
642
783
  function bashTaskIdFrom(response) {
643
784
  const snakeCase = response.task_id;
644
785
  if (typeof snakeCase === "string" && snakeCase.length > 0)
@@ -728,7 +869,7 @@ function shellOutcomeFromLateReply(response) {
728
869
  }
729
870
  return { kind: "unknown" };
730
871
  }
731
- var DEFAULT_BRIDGE_TIMEOUT_MS = 30000, BRIDGE_HANG_TIMEOUT_THRESHOLD = 2, MAX_STDOUT_BUFFER, MALFORMED_STDOUT_LOG_CHARS = 512, HASHLINE_REGISTRATION_LOG_INTERVAL_MS = 60000, HASHLINE_REGISTRATION_LOG_STATE_LIMIT = 256, binaryFingerprintReader, binaryFingerprintAsyncReader, BENIGN_CPUINFO_PROC_CPUINFO_PARSE_FAILURE = "failed to parse processor information from /proc/cpuinfo", BridgeReplacedDuringVersionCheck, BridgeTransportTimeoutError, SHELL_TASK_COMMANDS, DEFAULT_LATE_SHELL_REPLY_GRACE_MS = 1e4, BridgeTransportUnavailableError, BridgeTransportUnknownOutcomeError, BinaryBridge;
872
+ var DEFAULT_BRIDGE_TIMEOUT_MS = 30000, DEFAULT_MIN_HANG_WINDOW_MS = 60000, DEFAULT_HANG_PROBE_TIMEOUT_MS = 5000, MAX_STDOUT_BUFFER, MALFORMED_STDOUT_LOG_CHARS = 512, HASHLINE_REGISTRATION_LOG_INTERVAL_MS = 60000, HASHLINE_REGISTRATION_LOG_STATE_LIMIT = 256, binaryFingerprintReader, binaryFingerprintAsyncReader, BENIGN_CPUINFO_PROC_CPUINFO_PARSE_FAILURE = "failed to parse processor information from /proc/cpuinfo", SYSTEM_CLOCK, BridgeReplacedDuringVersionCheck, BridgeTransportTimeoutError, SHELL_TASK_COMMANDS, DEFAULT_LATE_SHELL_REPLY_GRACE_MS = 1e4, BridgeTransportUnavailableError, BridgeTransportUnknownOutcomeError, BridgeRestartedError, BinaryBridge;
732
873
  var init_bridge = __esm(() => {
733
874
  init_active_logger();
734
875
  init_binary_identity();
@@ -738,6 +879,11 @@ var init_bridge = __esm(() => {
738
879
  MAX_STDOUT_BUFFER = 64 * 1024 * 1024;
739
880
  binaryFingerprintReader = peekBinaryFingerprint;
740
881
  binaryFingerprintAsyncReader = binaryContentHash;
882
+ SYSTEM_CLOCK = {
883
+ now: () => Date.now(),
884
+ setTimeout: (callback, ms) => setTimeout(callback, ms),
885
+ clearTimeout: (timer) => clearTimeout(timer)
886
+ };
741
887
  BridgeReplacedDuringVersionCheck = class BridgeReplacedDuringVersionCheck extends Error {
742
888
  newBinaryPath;
743
889
  constructor(newBinaryPath) {
@@ -773,6 +919,18 @@ var init_bridge = __esm(() => {
773
919
  this.name = "BridgeTransportUnknownOutcomeError";
774
920
  }
775
921
  };
922
+ BridgeRestartedError = class BridgeRestartedError extends BridgeTransportUnknownOutcomeError {
923
+ tool;
924
+ outcomeUnknown;
925
+ retryable = true;
926
+ restartReason = "hang_watchdog";
927
+ constructor(tool, outcomeUnknown, message) {
928
+ super(message);
929
+ this.tool = tool;
930
+ this.outcomeUnknown = outcomeUnknown;
931
+ this.name = "BridgeRestartedError";
932
+ }
933
+ };
776
934
  BinaryBridge = class BinaryBridge {
777
935
  static RESTART_RESET_MS = 5 * 60 * 1000;
778
936
  static STDERR_TAIL_MAX = 20;
@@ -799,7 +957,9 @@ var init_bridge = __esm(() => {
799
957
  _restartCount = 0;
800
958
  _shuttingDown = false;
801
959
  timeoutMs;
802
- hangThreshold;
960
+ hangWindowMinMs;
961
+ hangProbeTimeoutMs;
962
+ clock;
803
963
  lateShellReplyGraceMs;
804
964
  maxRestarts;
805
965
  configured = false;
@@ -819,8 +979,11 @@ var init_bridge = __esm(() => {
819
979
  configureWarningClients = new Map;
820
980
  restartTimer = null;
821
981
  restartResetTimer = null;
822
- lastChildActivityAt = 0;
823
- consecutiveRequestTimeouts = 0;
982
+ awaitingReply = new Map;
983
+ lastChildActivity = Number.NEGATIVE_INFINITY;
984
+ hangProbe = null;
985
+ hangWatchdogTimer = null;
986
+ hangSuppressionLogged = false;
824
987
  timeoutRestartAwaitingRecovery = false;
825
988
  errorPrefix;
826
989
  logger;
@@ -830,7 +993,9 @@ var init_bridge = __esm(() => {
830
993
  this.binaryPath = binaryPath;
831
994
  this.cwd = cwd;
832
995
  this.timeoutMs = options?.timeoutMs ?? DEFAULT_BRIDGE_TIMEOUT_MS;
833
- this.hangThreshold = options?.hangThreshold ?? BRIDGE_HANG_TIMEOUT_THRESHOLD;
996
+ this.hangWindowMinMs = options?.hangWindowMinMs ?? DEFAULT_MIN_HANG_WINDOW_MS;
997
+ this.hangProbeTimeoutMs = options?.hangProbeTimeoutMs ?? DEFAULT_HANG_PROBE_TIMEOUT_MS;
998
+ this.clock = options?.clock ?? SYSTEM_CLOCK;
834
999
  this.lateShellReplyGraceMs = options?.lateShellReplyGraceMs ?? DEFAULT_LATE_SHELL_REPLY_GRACE_MS;
835
1000
  this.maxRestarts = options?.maxRestarts ?? 3;
836
1001
  this.errorPrefix = options?.errorPrefix ?? "[aft-bridge]";
@@ -871,6 +1036,19 @@ var init_bridge = __esm(() => {
871
1036
  log(message, meta);
872
1037
  }
873
1038
  }
1039
+ debugVia(message, meta) {
1040
+ const logger = this.logger ?? getActiveLogger();
1041
+ if (!logger?.debug) {
1042
+ this.logVia(message, meta);
1043
+ return;
1044
+ }
1045
+ try {
1046
+ logger.debug(message, meta);
1047
+ } catch (err) {
1048
+ console.error(`[aft-bridge] ERROR: logger debug threw: ${err instanceof Error ? err.message : String(err)}`);
1049
+ console.error(`[aft-bridge] ${message}`);
1050
+ }
1051
+ }
874
1052
  warnVia(message, meta) {
875
1053
  const logger = this.logger ?? getActiveLogger();
876
1054
  if (logger) {
@@ -1091,7 +1269,7 @@ var init_bridge = __esm(() => {
1091
1269
  params[HOST_TOOL_NAMES_FIELD] = hostToolNames;
1092
1270
  return await this.send("tool_call", params, Object.keys(sendOptions).length > 0 ? sendOptions : undefined);
1093
1271
  }
1094
- async sendWithVersionMismatchRetry(command, params, options, canRetryAfterVersionSwap) {
1272
+ async sendWithVersionMismatchRetry(command, params, options, canRetryAfterVersionSwap, retriedAfterHangRestart = false) {
1095
1273
  try {
1096
1274
  if (this._retiringDueToBinaryChange) {
1097
1275
  throw new BridgeTransportUnavailableError(`${this.errorPrefix} Bridge is retiring after the on-disk binary changed; retry to respawn on the updated binary`);
@@ -1176,13 +1354,15 @@ var init_bridge = __esm(() => {
1176
1354
  } else {
1177
1355
  request = { id, command, ...params };
1178
1356
  }
1357
+ delete request.handles_progress;
1358
+ if (options?.onProgress)
1359
+ request.handles_progress = true;
1179
1360
  const line = `${JSON.stringify(request)}
1180
1361
  `;
1181
1362
  const keepBridgeOnTimeout = passive || command === "bash_drain_completions" || options?.keepBridgeOnTimeout === true;
1182
- let requestSentAt = Date.now();
1183
1363
  const child = this.process;
1184
1364
  const response = await new Promise((resolve, reject) => {
1185
- const timer = setTimeout(() => {
1365
+ const timer = this.clock.setTimeout(() => {
1186
1366
  const entry = this.pending.get(id);
1187
1367
  if (!entry)
1188
1368
  return;
@@ -1190,45 +1370,25 @@ var init_bridge = __esm(() => {
1190
1370
  return;
1191
1371
  }
1192
1372
  this.pending.delete(id);
1193
- clearTimeout(entry.timer);
1373
+ this.clock.clearTimeout(entry.timer);
1194
1374
  entry.onSettled?.();
1195
- if (keepBridgeOnTimeout) {
1196
- const timeoutMsg = `Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`;
1197
- if (requestSessionId) {
1198
- this.sessionWarnVia(requestSessionId, timeoutMsg);
1199
- } else {
1200
- this.warnVia(timeoutMsg);
1201
- }
1202
- entry.reject(new BridgeTransportTimeoutError(command, effectiveTimeoutMs, `${this.errorPrefix} Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
1203
- return;
1204
- }
1205
- const childActiveSinceRequest = this.lastChildActivityAt > requestSentAt;
1206
- const consecutiveTimeouts = this.consecutiveRequestTimeouts + 1;
1207
- this.consecutiveRequestTimeouts = consecutiveTimeouts;
1208
- const restartSuppressed = this.timeoutRestartAwaitingRecovery;
1209
- const keepWarm = restartSuppressed || childActiveSinceRequest || consecutiveTimeouts < this.hangThreshold;
1210
- const restartSuffix = keepWarm ? " — bridge kept warm" : " — restarting bridge";
1211
- const timeoutMsg = `Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms${restartSuffix}`;
1375
+ const timeoutMsg = `Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms — bridge kept warm`;
1212
1376
  if (requestSessionId) {
1213
1377
  this.sessionWarnVia(requestSessionId, timeoutMsg);
1214
1378
  } else {
1215
1379
  this.warnVia(timeoutMsg);
1216
1380
  }
1217
- if (keepWarm) {
1218
- entry.reject(new Error(restartSuppressed ? `${this.errorPrefix} request "${command}" timed out after ${effectiveTimeoutMs}ms; automatic timeout restart already attempted; bridge kept warm to finish startup. Retry later or restart the host explicitly if it remains unresponsive. A timed-out mutation may still complete; inspect before repeating it.` : `${this.errorPrefix} request "${command}" timed out after ${effectiveTimeoutMs}ms (bridge busy/under load); bridge kept warm — retry`));
1381
+ if (keepBridgeOnTimeout) {
1382
+ entry.reject(new BridgeTransportTimeoutError(command, effectiveTimeoutMs, `${this.errorPrefix} Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
1219
1383
  return;
1220
1384
  }
1221
- entry.reject(new Error(`${this.errorPrefix} Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
1222
- this.handleTimeout({
1223
- requestId: id,
1224
- tool: entry.tool,
1225
- generation: entry.generation
1226
- }, requestSessionId);
1385
+ entry.reject(new Error(`${this.errorPrefix} request "${command}" timed out after ${effectiveTimeoutMs}ms (bridge busy/under load); bridge kept warm — retry. A timed-out mutation may still complete; inspect before repeating it.`));
1227
1386
  }, effectiveTimeoutMs);
1228
1387
  const entry = {
1229
1388
  resolve,
1230
1389
  reject,
1231
1390
  timer,
1391
+ budgetMs: effectiveTimeoutMs,
1232
1392
  onProgress: options?.onProgress,
1233
1393
  command,
1234
1394
  tool,
@@ -1237,21 +1397,21 @@ var init_bridge = __esm(() => {
1237
1397
  this.pending.set(id, entry);
1238
1398
  if (!child?.stdin?.writable) {
1239
1399
  this.pending.delete(id);
1240
- clearTimeout(timer);
1400
+ this.clock.clearTimeout(timer);
1241
1401
  const error = new BridgeTransportUnavailableError(`${this.errorPrefix} stdin not writable for command "${command}"`);
1242
1402
  reject(error);
1243
1403
  if (this.process === child)
1244
1404
  this.invalidateTransportProcess(error);
1245
1405
  return;
1246
1406
  }
1247
- requestSentAt = Date.now();
1407
+ this.recordAwaitingReply(id, effectiveTimeoutMs, tool, requestSessionId);
1248
1408
  const handleWriteFailure = (cause) => {
1249
1409
  const writeError = cause instanceof Error ? cause : new Error(String(cause));
1250
1410
  const error = new BridgeTransportUnknownOutcomeError(`${this.errorPrefix} Failed to write to stdin: ${writeError.message}`, { cause: writeError });
1251
1411
  const entry = this.pending.get(id);
1252
1412
  if (entry) {
1253
1413
  this.pending.delete(id);
1254
- clearTimeout(entry.timer);
1414
+ this.clock.clearTimeout(entry.timer);
1255
1415
  entry.onSettled?.();
1256
1416
  entry.reject(error);
1257
1417
  }
@@ -1277,7 +1437,12 @@ var init_bridge = __esm(() => {
1277
1437
  } catch (err) {
1278
1438
  if (err instanceof BridgeReplacedDuringVersionCheck && canRetryAfterVersionSwap && command !== "configure" && command !== "version") {
1279
1439
  this.logVia(`Retrying request "${command}" once after coordinated binary replacement: ${err.newBinaryPath}`);
1280
- return this.sendWithVersionMismatchRetry(command, params, options, false);
1440
+ return this.sendWithVersionMismatchRetry(command, params, options, false, retriedAfterHangRestart);
1441
+ }
1442
+ const tool = command === "tool_call" && typeof params.name === "string" ? params.name : command;
1443
+ if (err instanceof BridgeRestartedError && !retriedAfterHangRestart && isReadOnlyRetryCommand(tool) && !this._shuttingDown && options?.abortSignal?.aborted !== true) {
1444
+ this.logVia(`Retrying read-only request "${tool}" once after the hang watchdog restart`);
1445
+ return this.sendWithVersionMismatchRetry(command, params, options, canRetryAfterVersionSwap, true);
1281
1446
  }
1282
1447
  throw err;
1283
1448
  }
@@ -1346,6 +1511,7 @@ var init_bridge = __esm(() => {
1346
1511
  async shutdown() {
1347
1512
  this._shuttingDown = true;
1348
1513
  this.spawnedBinaryFingerprint = null;
1514
+ this.resetHangWatchdog();
1349
1515
  this.clearRestartResetTimer();
1350
1516
  this.clearRestartTimer();
1351
1517
  this.configureWarningClients.clear();
@@ -1544,8 +1710,7 @@ var init_bridge = __esm(() => {
1544
1710
  this.stdoutPending = [];
1545
1711
  this.stdoutPendingLength = 0;
1546
1712
  this.stderrBuffer = "";
1547
- this.lastChildActivityAt = 0;
1548
- this.consecutiveRequestTimeouts = 0;
1713
+ this.resetHangWatchdog();
1549
1714
  this.stderrTail = [];
1550
1715
  this.processLoopReferenced = true;
1551
1716
  this.syncEventLoopHold();
@@ -1589,7 +1754,16 @@ var init_bridge = __esm(() => {
1589
1754
  if (!line || !shouldSurfaceStderrLine(line))
1590
1755
  return;
1591
1756
  const tagged = tagStderrLine(line);
1592
- this.logVia(tagged);
1757
+ const level = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z \[aft(?:-lsp)?\] (INFO|WARN|ERROR|DEBUG) /.exec(line)?.[1];
1758
+ const meta = level ? { engineRecord: true } : undefined;
1759
+ if (level === "WARN")
1760
+ this.warnVia(tagged, meta);
1761
+ else if (level === "ERROR")
1762
+ this.errorVia(tagged, meta);
1763
+ else if (level === "DEBUG")
1764
+ this.debugVia(tagged, meta);
1765
+ else
1766
+ this.logVia(tagged, meta);
1593
1767
  this.pushStderrLine(tagged);
1594
1768
  }
1595
1769
  formatStderrTail() {
@@ -1652,7 +1826,7 @@ var init_bridge = __esm(() => {
1652
1826
  processStdoutLine(line) {
1653
1827
  try {
1654
1828
  const response = JSON.parse(line);
1655
- this.lastChildActivityAt = Date.now();
1829
+ this.lastChildActivity = this.clock.now();
1656
1830
  if (response.type === "progress") {
1657
1831
  const requestId = response.request_id;
1658
1832
  const entry = requestId ? this.pending.get(requestId) : undefined;
@@ -1666,7 +1840,7 @@ var init_bridge = __esm(() => {
1666
1840
  const entry = requestId ? this.pending.get(requestId) : undefined;
1667
1841
  if (requestId && entry) {
1668
1842
  this.pending.delete(requestId);
1669
- clearTimeout(entry.timer);
1843
+ this.clock.clearTimeout(entry.timer);
1670
1844
  entry.onSettled?.();
1671
1845
  entry.resolve({
1672
1846
  success: false,
@@ -1675,6 +1849,8 @@ var init_bridge = __esm(() => {
1675
1849
  asks: response.asks
1676
1850
  });
1677
1851
  }
1852
+ if (requestId)
1853
+ this.settleHangEvidence(requestId);
1678
1854
  return;
1679
1855
  }
1680
1856
  if (response.type === "bash_completed") {
@@ -1704,14 +1880,18 @@ var init_bridge = __esm(() => {
1704
1880
  return;
1705
1881
  }
1706
1882
  const id = response.id;
1883
+ if (id && this.hangProbe?.id === id) {
1884
+ this.onHangProbeAnswered();
1885
+ return;
1886
+ }
1707
1887
  if (id && this.pending.has(id)) {
1708
1888
  const entry = this.pending.get(id);
1709
1889
  if (!entry)
1710
1890
  return;
1711
1891
  this.pending.delete(id);
1712
- clearTimeout(entry.timer);
1892
+ this.settleHangEvidence(id);
1893
+ this.clock.clearTimeout(entry.timer);
1713
1894
  entry.onSettled?.();
1714
- this.consecutiveRequestTimeouts = 0;
1715
1895
  if (response.success !== false && ![
1716
1896
  "configure",
1717
1897
  "version",
@@ -1727,6 +1907,7 @@ var init_bridge = __esm(() => {
1727
1907
  this.accountForBashTaskResponse(entry.command, response);
1728
1908
  entry.resolve(response);
1729
1909
  } else if (id && typeof response.type !== "string") {
1910
+ this.settleHangEvidence(id);
1730
1911
  const taskId = bashTaskIdFrom(response);
1731
1912
  if (taskId) {
1732
1913
  this.warnVia(`Dropped late reply for settled request id=${id} task_id=${taskId}`);
@@ -1757,8 +1938,8 @@ var init_bridge = __esm(() => {
1757
1938
  rejectWith(new BridgeTransportTimeoutError(entry.command, effectiveTimeoutMs, message, outcome));
1758
1939
  };
1759
1940
  entry.resolve = (response) => settle(shellOutcomeFromLateReply(response));
1760
- clearTimeout(entry.timer);
1761
- entry.timer = setTimeout(() => {
1941
+ this.clock.clearTimeout(entry.timer);
1942
+ entry.timer = this.clock.setTimeout(() => {
1762
1943
  if (this.pending.get(id) !== entry)
1763
1944
  return;
1764
1945
  this.pending.delete(id);
@@ -1780,17 +1961,150 @@ var init_bridge = __esm(() => {
1780
1961
  this.outstandingBackgroundTaskIds.add(taskId);
1781
1962
  }
1782
1963
  }
1783
- handleTimeout(trigger, triggeringSessionId) {
1784
- this.consecutiveRequestTimeouts = 0;
1964
+ recordAwaitingReply(id, budgetMs, tool, sessionId) {
1965
+ this.awaitingReply.set(id, { sentAt: this.clock.now(), budgetMs, tool, sessionId });
1966
+ this.armHangWatchdog();
1967
+ }
1968
+ settleHangEvidence(id) {
1969
+ if (!this.awaitingReply.delete(id))
1970
+ return;
1971
+ this.clearHangWatchdogTimer();
1972
+ this.armHangWatchdog();
1973
+ }
1974
+ hangDeadline() {
1975
+ if (this.awaitingReply.size === 0)
1976
+ return null;
1977
+ let oldestId = "";
1978
+ let oldestSentAt = Number.POSITIVE_INFINITY;
1979
+ const budgets = [];
1980
+ for (const [id, entry] of this.awaitingReply) {
1981
+ budgets.push(entry.budgetMs);
1982
+ if (entry.sentAt < oldestSentAt) {
1983
+ oldestSentAt = entry.sentAt;
1984
+ oldestId = id;
1985
+ }
1986
+ }
1987
+ for (const entry of this.pending.values())
1988
+ budgets.push(entry.budgetMs);
1989
+ const windowMs = hangWindowMs(budgets, this.hangWindowMinMs);
1990
+ const silentSince = Math.max(oldestSentAt, this.lastChildActivity);
1991
+ return { deadline: silentSince + windowMs, windowMs, silentSince, oldestId };
1992
+ }
1993
+ armHangWatchdog() {
1994
+ if (this.hangWatchdogTimer || this.hangProbe)
1995
+ return;
1996
+ const hang = this.hangDeadline();
1997
+ if (!hang)
1998
+ return;
1999
+ const timer = this.clock.setTimeout(() => {
2000
+ this.hangWatchdogTimer = null;
2001
+ this.checkForHang();
2002
+ }, Math.max(0, hang.deadline - this.clock.now()));
2003
+ timer.unref?.();
2004
+ this.hangWatchdogTimer = timer;
2005
+ }
2006
+ clearHangWatchdogTimer() {
2007
+ if (this.hangWatchdogTimer)
2008
+ this.clock.clearTimeout(this.hangWatchdogTimer);
2009
+ this.hangWatchdogTimer = null;
2010
+ }
2011
+ clearHangProbe() {
2012
+ if (this.hangProbe)
2013
+ this.clock.clearTimeout(this.hangProbe.timer);
2014
+ this.hangProbe = null;
2015
+ }
2016
+ resetHangWatchdog() {
2017
+ this.clearHangWatchdogTimer();
2018
+ this.clearHangProbe();
2019
+ this.awaitingReply.clear();
2020
+ this.lastChildActivity = Number.NEGATIVE_INFINITY;
2021
+ this.hangSuppressionLogged = false;
2022
+ }
2023
+ checkForHang() {
2024
+ if (!this.isAlive() || this.hangProbe)
2025
+ return;
2026
+ const hang = this.hangDeadline();
2027
+ if (!hang)
2028
+ return;
2029
+ const now = this.clock.now();
2030
+ if (now < hang.deadline) {
2031
+ this.armHangWatchdog();
2032
+ return;
2033
+ }
2034
+ if (this.timeoutRestartAwaitingRecovery) {
2035
+ if (!this.hangSuppressionLogged) {
2036
+ this.hangSuppressionLogged = true;
2037
+ this.warnVia(`Hang watchdog: child silent past its window, but it was already restarted once and no tool has succeeded since; not restarting again; ${this.hangAttribution(hang)}`);
2038
+ }
2039
+ return;
2040
+ }
2041
+ this.sendHangProbe(hang);
2042
+ }
2043
+ hangAttribution(hang) {
2044
+ return `silent_ms=${this.clock.now() - hang.silentSince} ` + `hang_window_ms=${hang.windowMs} ` + `oldest_unanswered_request_id=${hang.oldestId} ` + `oldest_unanswered_tool=${JSON.stringify(this.awaitingReply.get(hang.oldestId)?.tool)} ` + `bridge_generation=${this.processGeneration}`;
2045
+ }
2046
+ sendHangProbe(hang) {
2047
+ const id = `hang-probe-${this.nextId++}`;
2048
+ const timer = this.clock.setTimeout(() => this.onHangProbeTimeout(id), this.hangProbeTimeoutMs);
2049
+ timer.unref?.();
2050
+ this.hangProbe = { id, sentAt: this.clock.now(), timer };
2051
+ this.logVia(`Hang watchdog: child silent past its window; sending liveness probe id=${id} probe_timeout_ms=${this.hangProbeTimeoutMs}; ${this.hangAttribution(hang)}`);
2052
+ try {
2053
+ this.process?.stdin?.write(`${JSON.stringify({ id, command: "ping" })}
2054
+ `, () => {});
2055
+ } catch {}
2056
+ }
2057
+ onHangProbeAnswered() {
2058
+ const probeId = this.hangProbe?.id;
2059
+ this.clearHangProbe();
2060
+ let dropped = 0;
2061
+ for (const id of Array.from(this.awaitingReply.keys())) {
2062
+ if (this.pending.has(id))
2063
+ continue;
2064
+ this.awaitingReply.delete(id);
2065
+ dropped += 1;
2066
+ }
2067
+ this.logVia(`Hang watchdog: liveness probe id=${probeId} answered; child kept; dropped ${dropped} unanswered request(s) whose callers already settled, ${this.awaitingReply.size} still waiting`);
2068
+ this.clearHangWatchdogTimer();
2069
+ this.armHangWatchdog();
2070
+ }
2071
+ onHangProbeTimeout(probeId) {
2072
+ const probe = this.hangProbe;
2073
+ if (!probe || probe.id !== probeId)
2074
+ return;
2075
+ this.hangProbe = null;
2076
+ if (!this.isAlive())
2077
+ return;
2078
+ const hang = this.hangDeadline();
2079
+ if (!hang)
2080
+ return;
2081
+ if (this.lastChildActivity >= probe.sentAt) {
2082
+ this.armHangWatchdog();
2083
+ return;
2084
+ }
2085
+ const attribution = `${this.hangAttribution(hang)} probe_id=${probeId} probe_timeout_ms=${this.hangProbeTimeoutMs}`;
2086
+ this.killHungChild(attribution, this.awaitingReply.get(hang.oldestId)?.sessionId);
2087
+ }
2088
+ killHungChild(attribution, sessionId) {
1785
2089
  this.timeoutRestartAwaitingRecovery = true;
1786
2090
  this.spawnedBinaryFingerprint = null;
1787
- const abortedSiblings = Array.from(this.pending, ([requestId, entry]) => ({
2091
+ const aborted = Array.from(this.pending, ([requestId, entry]) => ({
1788
2092
  request_id: requestId,
1789
- tool: entry.tool
2093
+ tool: entry.tool,
2094
+ retry: isReadOnlyRetryCommand(entry.tool)
1790
2095
  }));
1791
- const attribution = `triggering_request_id=${trigger.requestId} ` + `triggering_tool=${JSON.stringify(trigger.tool)} ` + `bridge_generation=${trigger.generation} ` + `aborted_siblings=${JSON.stringify(abortedSiblings)}`;
1792
- this.rejectAllPending(new BridgeTransportUnknownOutcomeError(`${this.errorPrefix} bridge killed during sibling timeout — request aborted; ${attribution}`));
2096
+ const fullAttribution = `${attribution} aborted_requests=${JSON.stringify(aborted)}`;
2097
+ this.configureWarningClients.clear();
2098
+ const pending = Array.from(this.pending.values());
2099
+ this.pending.clear();
2100
+ for (const entry of pending) {
2101
+ this.clock.clearTimeout(entry.timer);
2102
+ entry.onSettled?.();
2103
+ const readOnly = isReadOnlyRetryCommand(entry.tool);
2104
+ entry.reject(new BridgeRestartedError(entry.tool, !readOnly, readOnly ? `${this.errorPrefix} bridge restarted: the child wrote nothing for longer than its hang window and did not answer a liveness probe; "${entry.tool}" is read-only and safe to retry; ${fullAttribution}` : `${this.errorPrefix} bridge restarted: the child wrote nothing for longer than its hang window and did not answer a liveness probe; the outcome of "${entry.tool}" is unknown — a mutation may already have been applied, inspect before repeating it; ${fullAttribution}`));
2105
+ }
1793
2106
  this.outstandingBackgroundTaskIds.clear();
2107
+ this.resetHangWatchdog();
1794
2108
  if (this.process) {
1795
2109
  this.process.kill("SIGKILL");
1796
2110
  this.process = null;
@@ -1799,9 +2113,9 @@ var init_bridge = __esm(() => {
1799
2113
  this.configured = false;
1800
2114
  const tail = this.formatStderrTail();
1801
2115
  this.stderrTail = [];
1802
- const killedMsg = tail ? `Bridge killed after timeout; ${attribution}.${tail}` : `Bridge killed after timeout; ${attribution} (see ${this.getLogFilePathVia()})`;
1803
- if (triggeringSessionId) {
1804
- this.sessionErrorVia(triggeringSessionId, killedMsg);
2116
+ const killedMsg = tail ? `Bridge killed after timeout of the hang watchdog; ${fullAttribution}.${tail}` : `Bridge killed after timeout of the hang watchdog; ${fullAttribution} (see ${this.getLogFilePathVia()})`;
2117
+ if (sessionId) {
2118
+ this.sessionErrorVia(sessionId, killedMsg);
1805
2119
  } else {
1806
2120
  this.errorVia(killedMsg);
1807
2121
  }
@@ -1850,7 +2164,7 @@ var init_bridge = __esm(() => {
1850
2164
  rejectAllPending(error) {
1851
2165
  this.configureWarningClients.clear();
1852
2166
  for (const [_id, entry] of this.pending) {
1853
- clearTimeout(entry.timer);
2167
+ this.clock.clearTimeout(entry.timer);
1854
2168
  entry.onSettled?.();
1855
2169
  entry.reject(error);
1856
2170
  }
@@ -3217,6 +3531,14 @@ function translateInspectLspPaths(map, prefix, out) {
3217
3531
  recordRetired(out, prefix, `inspect.${leaf}`);
3218
3532
  }
3219
3533
  }
3534
+ function dropNoEffectPaths(map, prefix, out) {
3535
+ for (const path of REMOVED_NO_EFFECT_PATHS) {
3536
+ const [container, leaf] = path.split(".");
3537
+ if (container && leaf && takeNested(map, container, leaf).found) {
3538
+ recordRetired(out, prefix, path);
3539
+ }
3540
+ }
3541
+ }
3220
3542
  function translateExperimentalPaths(map, prefix, out) {
3221
3543
  for (const [oldKey, leaf] of [
3222
3544
  ["experimental_lsp_ty", "lsp_ty"],
@@ -3278,6 +3600,7 @@ function translateBlock(map, userBase, blockLabel, out) {
3278
3600
  const prefix = blockLabel === "base" ? "" : `${blockLabel}.`;
3279
3601
  translateGithubAliases(map, prefix, out);
3280
3602
  translateInspectLspPaths(map, prefix, out);
3603
+ dropNoEffectPaths(map, prefix, out);
3281
3604
  translateExperimentalPaths(map, prefix, out);
3282
3605
  if (isRecord(map.sandbox) && Object.hasOwn(map.sandbox, "enabled")) {
3283
3606
  const sandbox = map.sandbox;
@@ -3485,7 +3808,7 @@ function mergeIndexes(base, override, restricted) {
3485
3808
  }
3486
3809
  return merged;
3487
3810
  }
3488
- var CANONICAL_TOOLS, HOST_TOOL_NAMES, DEFAULT_DISABLED_TOOLS, LEGACY_TOOL_ALIASES, RETIRED_PATHS, INDEX_INPUTS, LSP_IDLE_MINUTES_DEFAULT = 60, LSP_IDLE_MINUTES_MIN = 5, LSP_IDLE_MINUTES_MAX = 1440, RETIRED_ENABLED_FALSE_INDEXES_NOTE = "enabled: false no longer turns AFT off: it is translated to disabling every tool, but the trigram, semantic and callgraph indexes still build. To keep AFT from indexing this repository, also set indexes.trigram, indexes.semantic and indexes.callgraph to false.", SEMANTIC_COST_NOTICE_DIGEST;
3811
+ var CANONICAL_TOOLS, HOST_TOOL_NAMES, DEFAULT_DISABLED_TOOLS, LEGACY_TOOL_ALIASES, RETIRED_PATHS, INDEX_INPUTS, LSP_IDLE_MINUTES_DEFAULT = 60, LSP_IDLE_MINUTES_MIN = 5, LSP_IDLE_MINUTES_MAX = 1440, REMOVED_NO_EFFECT_PATHS, RETIRED_ENABLED_FALSE_INDEXES_NOTE = "enabled: false no longer turns AFT off: it is translated to disabling every tool, but the trigram, semantic and callgraph indexes still build. To keep AFT from indexing this repository, also set indexes.trigram, indexes.semantic and indexes.callgraph to false.", SEMANTIC_COST_NOTICE_DIGEST, SECURITY_CONFIG_SECTIONS;
3489
3812
  var init_feature_config = __esm(() => {
3490
3813
  CANONICAL_TOOLS = [
3491
3814
  "aft_callgraph",
@@ -3553,7 +3876,26 @@ var init_feature_config = __esm(() => {
3553
3876
  ["semantic", "semantic_search", "experimental_semantic_search"],
3554
3877
  ["callgraph", "callgraph_store", undefined]
3555
3878
  ];
3879
+ REMOVED_NO_EFFECT_PATHS = ["bridge.hang_threshold"];
3556
3880
  SEMANTIC_COST_NOTICE_DIGEST = noticeDigest({ notice: "semantic_default_cost_v1" });
3881
+ SECURITY_CONFIG_SECTIONS = new Set([
3882
+ "restrict_to_project_root",
3883
+ "url_fetch_allow_private",
3884
+ "sandbox",
3885
+ "indexes",
3886
+ "index",
3887
+ "remote_exec",
3888
+ "disabled_tools",
3889
+ "bash",
3890
+ "lsp",
3891
+ "semantic",
3892
+ "search",
3893
+ "subc",
3894
+ "opencode",
3895
+ "github",
3896
+ "gh_shim",
3897
+ "auto_update"
3898
+ ]);
3557
3899
  });
3558
3900
  // ../aft-bridge/dist/pool.js
3559
3901
  var DEFAULT_IDLE_TIMEOUT_MS, CLEANUP_INTERVAL_MS;
@@ -3593,8 +3935,17 @@ var init_location_lifecycle = __esm(() => {
3593
3935
  });
3594
3936
 
3595
3937
  // ../aft-bridge/dist/lsp-github-probe.js
3938
+ var ALLOWED_GITHUB_HOSTS;
3596
3939
  var init_lsp_github_probe = __esm(() => {
3597
3940
  init_active_logger();
3941
+ ALLOWED_GITHUB_HOSTS = new Set([
3942
+ "github.com",
3943
+ "api.github.com",
3944
+ "objects.githubusercontent.com",
3945
+ "release-assets.githubusercontent.com",
3946
+ "raw.githubusercontent.com",
3947
+ "codeload.github.com"
3948
+ ]);
3598
3949
  });
3599
3950
 
3600
3951
  // ../aft-bridge/dist/lsp-github-table.js
@@ -3659,166 +4010,15 @@ var init_lsp_github_install = __esm(() => {
3659
4010
  init_tar_executable();
3660
4011
  });
3661
4012
 
3662
- // ../aft-bridge/dist/message-detach.js
3663
- var BASH_WAIT_DETACH_MAGIC_KEYWORD = "&detach", STANDALONE_DETACH_KEYWORD_SOURCE, STANDALONE_DETACH_KEYWORD_PATTERN, STANDALONE_DETACH_KEYWORDS_PATTERN;
3664
- var init_message_detach = __esm(() => {
3665
- STANDALONE_DETACH_KEYWORD_SOURCE = `(^|[^\\p{L}\\p{N}_])${BASH_WAIT_DETACH_MAGIC_KEYWORD}(?![\\p{L}\\p{N}_])`;
3666
- STANDALONE_DETACH_KEYWORD_PATTERN = new RegExp(STANDALONE_DETACH_KEYWORD_SOURCE, "u");
3667
- STANDALONE_DETACH_KEYWORDS_PATTERN = new RegExp(STANDALONE_DETACH_KEYWORD_SOURCE, "gu");
3668
- });
3669
-
3670
- // ../aft-bridge/dist/paths.js
3671
- import { homedir as homedir7 } from "node:os";
3672
- import { dirname as dirname2, isAbsolute as isAbsolute4, join as join9, resolve as resolve5 } from "node:path";
3673
- function homeDir3() {
3674
- if (process.platform === "win32")
3675
- return process.env.USERPROFILE || process.env.HOME || homedir7();
3676
- return process.env.HOME || homedir7();
3677
- }
3678
- function configHome() {
3679
- const xdg = process.env.XDG_CONFIG_HOME;
3680
- if (xdg && isAbsolute4(xdg))
3681
- return xdg;
3682
- return join9(homeDir3(), ".config");
3683
- }
3684
- function resolveCortexKitUserConfigPath() {
3685
- return join9(configHome(), "cortexkit", "aft.jsonc");
3686
- }
3687
- function resolveCortexKitProjectConfigPath(projectDirectory) {
3688
- return join9(projectDirectory, ".cortexkit", "aft.jsonc");
3689
- }
3690
- var init_paths = __esm(() => {
3691
- init_private_storage();
3692
- });
3693
-
3694
- // ../aft-bridge/dist/resolver.js
3695
- import { closeSync as closeSync4, existsSync as existsSync4, promises as fsPromises, openSync as openSync4, readFileSync as readFileSync4, readSync as readSync3 } from "node:fs";
3696
- function isNativeExecutable(binaryPath) {
3697
- let fd = null;
3698
- try {
3699
- fd = openSync4(binaryPath, "r");
3700
- const buf = Buffer.alloc(4);
3701
- const read = readSync3(fd, buf, 0, 4, 0);
3702
- if (read < 2)
3703
- return false;
3704
- const b0 = buf[0];
3705
- const b1 = buf[1];
3706
- if (b0 === 35 && b1 === 33)
3707
- return false;
3708
- const m32 = buf.readUInt32BE(0);
3709
- const machO = new Set([4277009102, 4277009103, 3472551422, 3489328638, 3405691582]);
3710
- if (read >= 4 && machO.has(m32))
3711
- return true;
3712
- if (read >= 4 && m32 === 2135247942)
3713
- return true;
3714
- if (b0 === 77 && b1 === 90)
3715
- return true;
3716
- return false;
3717
- } catch {
3718
- return false;
3719
- } finally {
3720
- if (fd !== null) {
3721
- try {
3722
- closeSync4(fd);
3723
- } catch {}
3724
- }
3725
- }
3726
- }
3727
- var cacheCopiesInFlight;
3728
- var init_resolver = __esm(() => {
3729
- init_active_logger();
3730
- init_binary_identity();
3731
- init_cache_paths();
3732
- init_downloader();
3733
- init_platform();
3734
- init_version_probe();
3735
- cacheCopiesInFlight = new Map;
3736
- });
3737
-
3738
- // ../aft-bridge/dist/migration.js
3739
- var DEFAULT_TIMEOUT_MS;
3740
- var init_migration = __esm(() => {
3741
- init_child_process();
3742
- init_paths();
3743
- init_resolver();
3744
- init_storage_paths();
3745
- init_storage_paths();
3746
- DEFAULT_TIMEOUT_MS = 30 * 60 * 1000;
3747
- });
3748
-
3749
- // ../aft-bridge/dist/migration-notices.js
3750
- import { readFileSync as readFileSync5, renameSync as renameSync3, rmdirSync, statSync as statSync3, writeFileSync as writeFileSync2 } from "node:fs";
3751
- import { homedir as homedir8 } from "node:os";
3752
- import { dirname as dirname3, join as join10, resolve as resolve6 } from "node:path";
3753
- function migrationNoticeStorePath(env = process.env) {
3754
- const stateHome = env.XDG_STATE_HOME && env.XDG_STATE_HOME.length > 0 ? env.XDG_STATE_HOME : join10(homedir8(), ".local", "state");
3755
- return join10(stateHome, "cortexkit", "aft", "migration-notices.json");
3756
- }
3757
- function readRecords(storePath) {
3758
- try {
3759
- const parsed = JSON.parse(readFileSync5(storePath, "utf8"));
3760
- return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
3761
- } catch {
3762
- return {};
3763
- }
3764
- }
3765
- function withStoreLock(storePath, action) {
3766
- const lockPath = `${storePath}.lock`;
3767
- openPrivateStorageDir(dirname3(storePath));
3768
- const deadline = Date.now() + 2000;
3769
- for (;; ) {
3770
- try {
3771
- privateMkdirSync(lockPath, false);
3772
- break;
3773
- } catch (error) {
3774
- if (error.code !== "EEXIST")
3775
- throw error;
3776
- try {
3777
- if (Date.now() - statSync3(lockPath).mtimeMs > LOCK_STALE_MS)
3778
- rmdirSync(lockPath);
3779
- } catch {}
3780
- if (Date.now() > deadline)
3781
- throw error;
3782
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
3783
- }
3784
- }
3785
- try {
3786
- return action();
3787
- } finally {
3788
- try {
3789
- rmdirSync(lockPath);
3790
- } catch {}
3791
- }
3792
- }
3793
- function clearMigrationNotices(configPath, storePath = migrationNoticeStorePath()) {
3794
- const prefix = `${resolve6(configPath)}|`;
3795
- withStoreLock(storePath, () => {
3796
- const records = readRecords(storePath);
3797
- for (const key of Object.keys(records))
3798
- if (key.startsWith(prefix))
3799
- delete records[key];
3800
- writeFileSync2(storePath, `${JSON.stringify(records, null, 2)}
3801
- `, {
3802
- encoding: "utf8",
3803
- mode: PRIVATE_FILE_MODE
3804
- });
3805
- });
3806
- }
3807
- var LOCK_STALE_MS = 1e4;
3808
- var init_migration_notices = __esm(() => {
3809
- init_feature_config();
3810
- init_private_storage();
3811
- });
3812
-
3813
4013
  // ../aft-bridge/dist/npm-resolver.js
3814
- import { readdirSync as readdirSync2, statSync as statSync4 } from "node:fs";
3815
- import { homedir as homedir9 } from "node:os";
3816
- import { dirname as dirname4, isAbsolute as isAbsolute5, join as join11 } from "node:path";
4014
+ import { readdirSync as readdirSync2, statSync as statSync3 } from "node:fs";
4015
+ import { homedir as homedir7 } from "node:os";
4016
+ import { dirname as dirname2, isAbsolute as isAbsolute4, join as join9 } from "node:path";
3817
4017
  function defaultDeps() {
3818
4018
  return {
3819
4019
  platform: process.platform,
3820
4020
  env: process.env,
3821
- home: homedir9(),
4021
+ home: homedir7(),
3822
4022
  execPath: process.execPath
3823
4023
  };
3824
4024
  }
@@ -3827,7 +4027,7 @@ function npmBinaryName(platform) {
3827
4027
  }
3828
4028
  function isFile(p) {
3829
4029
  try {
3830
- return statSync4(p).isFile();
4030
+ return statSync3(p).isFile();
3831
4031
  } catch {
3832
4032
  return false;
3833
4033
  }
@@ -3840,16 +4040,16 @@ function npmFromPath(deps) {
3840
4040
  const separator = deps.platform === "win32" ? ";" : ":";
3841
4041
  for (const entry of raw.split(separator)) {
3842
4042
  const dir = entry.trim().replace(/^"|"$/g, "");
3843
- if (!dir || !isAbsolute5(dir))
4043
+ if (!dir || !isAbsolute4(dir))
3844
4044
  continue;
3845
- if (isFile(join11(dir, name)))
4045
+ if (isFile(join9(dir, name)))
3846
4046
  return dir;
3847
4047
  }
3848
4048
  return null;
3849
4049
  }
3850
4050
  function npmAdjacentToNode(deps) {
3851
- const dir = dirname4(deps.execPath);
3852
- return isFile(join11(dir, npmBinaryName(deps.platform))) ? dir : null;
4051
+ const dir = dirname2(deps.execPath);
4052
+ return isFile(join9(dir, npmBinaryName(deps.platform))) ? dir : null;
3853
4053
  }
3854
4054
  function highestVersionedNodeBin(installsDir, name) {
3855
4055
  let entries;
@@ -3858,8 +4058,8 @@ function highestVersionedNodeBin(installsDir, name) {
3858
4058
  } catch {
3859
4059
  return null;
3860
4060
  }
3861
- const candidates = entries.filter((v) => isFile(join11(installsDir, v, "bin", name))).sort((a, b) => compareVersionsDesc(a, b));
3862
- return candidates.length > 0 ? join11(installsDir, candidates[0], "bin") : null;
4061
+ const candidates = entries.filter((v) => isFile(join9(installsDir, v, "bin", name))).sort((a, b) => compareVersionsDesc(a, b));
4062
+ return candidates.length > 0 ? join9(installsDir, candidates[0], "bin") : null;
3863
4063
  }
3864
4064
  function compareVersionsDesc(a, b) {
3865
4065
  const pa = a.replace(/^v/, "").split(".").map((n) => Number.parseInt(n, 10));
@@ -3884,22 +4084,22 @@ function wellKnownNpmDirs(deps) {
3884
4084
  const programFiles = env.ProgramFiles || "C:\\Program Files";
3885
4085
  const appData = env.APPDATA;
3886
4086
  const localAppData = env.LOCALAPPDATA;
3887
- push(join11(programFiles, "nodejs"));
4087
+ push(join9(programFiles, "nodejs"));
3888
4088
  if (appData)
3889
- push(join11(appData, "npm"));
4089
+ push(join9(appData, "npm"));
3890
4090
  if (localAppData)
3891
- push(join11(localAppData, "Volta", "bin"));
4091
+ push(join9(localAppData, "Volta", "bin"));
3892
4092
  if (env.NVM_SYMLINK)
3893
4093
  push(env.NVM_SYMLINK);
3894
4094
  } else {
3895
4095
  if (env.NVM_BIN)
3896
4096
  push(env.NVM_BIN);
3897
- push(highestVersionedNodeBin(join11(home, ".nvm", "versions", "node"), name));
3898
- push(highestVersionedNodeBin(join11(home, ".local", "share", "mise", "installs", "node"), name));
3899
- push(highestVersionedNodeBin(join11(home, ".asdf", "installs", "nodejs"), name));
3900
- push(join11(home, ".volta", "bin"));
3901
- push(join11(home, ".asdf", "shims"));
3902
- const systemDirs = deps.systemNpmDirs ?? (platform === "darwin" ? ["/opt/homebrew/bin", "/usr/local/bin"] : ["/usr/local/bin", "/usr/bin", join11(home, ".local", "bin")]);
4097
+ push(highestVersionedNodeBin(join9(home, ".nvm", "versions", "node"), name));
4098
+ push(highestVersionedNodeBin(join9(home, ".local", "share", "mise", "installs", "node"), name));
4099
+ push(highestVersionedNodeBin(join9(home, ".asdf", "installs", "nodejs"), name));
4100
+ push(join9(home, ".volta", "bin"));
4101
+ push(join9(home, ".asdf", "shims"));
4102
+ const systemDirs = deps.systemNpmDirs ?? (platform === "darwin" ? ["/opt/homebrew/bin", "/usr/local/bin"] : ["/usr/local/bin", "/usr/bin", join9(home, ".local", "bin")]);
3903
4103
  for (const dir of systemDirs)
3904
4104
  push(dir);
3905
4105
  }
@@ -3909,12 +4109,12 @@ function resolveNpm(deps = defaultDeps()) {
3909
4109
  const name = npmBinaryName(deps.platform);
3910
4110
  const onPath = npmFromPath(deps);
3911
4111
  if (onPath)
3912
- return { command: join11(onPath, name), binDir: onPath };
4112
+ return { command: join9(onPath, name), binDir: onPath };
3913
4113
  const adjacent = npmAdjacentToNode(deps);
3914
4114
  if (adjacent)
3915
- return { command: join11(adjacent, name), binDir: adjacent };
4115
+ return { command: join9(adjacent, name), binDir: adjacent };
3916
4116
  for (const dir of wellKnownNpmDirs(deps)) {
3917
- const candidate = join11(dir, name);
4117
+ const candidate = join9(dir, name);
3918
4118
  if (isFile(candidate))
3919
4119
  return { command: candidate, binDir: dir };
3920
4120
  }
@@ -4075,7 +4275,7 @@ function terminateNpmProcessTree(child, invocation, env = process.env, gracePeri
4075
4275
  };
4076
4276
  child.once("exit", onChildExit);
4077
4277
  const systemRoot = env.SystemRoot ?? env.SYSTEMROOT;
4078
- const taskkill = systemRoot ? join11(systemRoot, "System32", "taskkill.exe") : "taskkill.exe";
4278
+ const taskkill = systemRoot ? join9(systemRoot, "System32", "taskkill.exe") : "taskkill.exe";
4079
4279
  try {
4080
4280
  killer = spawn(taskkill, ["/pid", String(child.pid), "/t", "/f"], {
4081
4281
  stdio: "ignore",
@@ -4110,6 +4310,166 @@ var init_npm_resolver = __esm(() => {
4110
4310
  };
4111
4311
  });
4112
4312
 
4313
+ // ../aft-bridge/dist/lsp-npm-policy.js
4314
+ var npmCutoffSupport, NPM_PROBE_RETRY_MS;
4315
+ var init_lsp_npm_policy = __esm(() => {
4316
+ init_child_process();
4317
+ init_npm_resolver();
4318
+ npmCutoffSupport = new Map;
4319
+ NPM_PROBE_RETRY_MS = 5 * 60000;
4320
+ });
4321
+
4322
+ // ../aft-bridge/dist/message-detach.js
4323
+ var BASH_WAIT_DETACH_MAGIC_KEYWORD = "&detach", STANDALONE_DETACH_KEYWORD_SOURCE, STANDALONE_DETACH_KEYWORD_PATTERN, STANDALONE_DETACH_KEYWORDS_PATTERN;
4324
+ var init_message_detach = __esm(() => {
4325
+ STANDALONE_DETACH_KEYWORD_SOURCE = `(^|[^\\p{L}\\p{N}_])${BASH_WAIT_DETACH_MAGIC_KEYWORD}(?![\\p{L}\\p{N}_])`;
4326
+ STANDALONE_DETACH_KEYWORD_PATTERN = new RegExp(STANDALONE_DETACH_KEYWORD_SOURCE, "u");
4327
+ STANDALONE_DETACH_KEYWORDS_PATTERN = new RegExp(STANDALONE_DETACH_KEYWORD_SOURCE, "gu");
4328
+ });
4329
+
4330
+ // ../aft-bridge/dist/paths.js
4331
+ import { homedir as homedir8 } from "node:os";
4332
+ import { dirname as dirname3, isAbsolute as isAbsolute5, join as join10, resolve as resolve5 } from "node:path";
4333
+ function homeDir3() {
4334
+ if (process.platform === "win32")
4335
+ return process.env.USERPROFILE || process.env.HOME || homedir8();
4336
+ return process.env.HOME || homedir8();
4337
+ }
4338
+ function configHome() {
4339
+ const xdg = process.env.XDG_CONFIG_HOME;
4340
+ if (xdg && isAbsolute5(xdg))
4341
+ return xdg;
4342
+ return join10(homeDir3(), ".config");
4343
+ }
4344
+ function resolveCortexKitUserConfigPath() {
4345
+ return join10(configHome(), "cortexkit", "aft.jsonc");
4346
+ }
4347
+ function resolveCortexKitProjectConfigPath(projectDirectory) {
4348
+ return join10(projectDirectory, ".cortexkit", "aft.jsonc");
4349
+ }
4350
+ var init_paths = __esm(() => {
4351
+ init_private_storage();
4352
+ });
4353
+
4354
+ // ../aft-bridge/dist/resolver.js
4355
+ import { closeSync as closeSync4, existsSync as existsSync4, promises as fsPromises, openSync as openSync4, readFileSync as readFileSync4, readSync as readSync3 } from "node:fs";
4356
+ function isNativeExecutable(binaryPath) {
4357
+ let fd = null;
4358
+ try {
4359
+ fd = openSync4(binaryPath, "r");
4360
+ const buf = Buffer.alloc(4);
4361
+ const read = readSync3(fd, buf, 0, 4, 0);
4362
+ if (read < 2)
4363
+ return false;
4364
+ const b0 = buf[0];
4365
+ const b1 = buf[1];
4366
+ if (b0 === 35 && b1 === 33)
4367
+ return false;
4368
+ const m32 = buf.readUInt32BE(0);
4369
+ const machO = new Set([4277009102, 4277009103, 3472551422, 3489328638, 3405691582]);
4370
+ if (read >= 4 && machO.has(m32))
4371
+ return true;
4372
+ if (read >= 4 && m32 === 2135247942)
4373
+ return true;
4374
+ if (b0 === 77 && b1 === 90)
4375
+ return true;
4376
+ return false;
4377
+ } catch {
4378
+ return false;
4379
+ } finally {
4380
+ if (fd !== null) {
4381
+ try {
4382
+ closeSync4(fd);
4383
+ } catch {}
4384
+ }
4385
+ }
4386
+ }
4387
+ var cacheCopiesInFlight;
4388
+ var init_resolver = __esm(() => {
4389
+ init_active_logger();
4390
+ init_binary_identity();
4391
+ init_cache_paths();
4392
+ init_downloader();
4393
+ init_platform();
4394
+ init_version_probe();
4395
+ cacheCopiesInFlight = new Map;
4396
+ });
4397
+
4398
+ // ../aft-bridge/dist/migration.js
4399
+ var DEFAULT_TIMEOUT_MS;
4400
+ var init_migration = __esm(() => {
4401
+ init_child_process();
4402
+ init_paths();
4403
+ init_resolver();
4404
+ init_storage_paths();
4405
+ init_storage_paths();
4406
+ DEFAULT_TIMEOUT_MS = 30 * 60 * 1000;
4407
+ });
4408
+
4409
+ // ../aft-bridge/dist/migration-notices.js
4410
+ import { readFileSync as readFileSync5, renameSync as renameSync3, rmdirSync, statSync as statSync4, writeFileSync as writeFileSync2 } from "node:fs";
4411
+ import { homedir as homedir9 } from "node:os";
4412
+ import { dirname as dirname4, join as join11, resolve as resolve6 } from "node:path";
4413
+ function migrationNoticeStorePath(env = process.env) {
4414
+ const stateHome = env.XDG_STATE_HOME && env.XDG_STATE_HOME.length > 0 ? env.XDG_STATE_HOME : join11(homedir9(), ".local", "state");
4415
+ return join11(stateHome, "cortexkit", "aft", "migration-notices.json");
4416
+ }
4417
+ function readRecords(storePath) {
4418
+ try {
4419
+ const parsed = JSON.parse(readFileSync5(storePath, "utf8"));
4420
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
4421
+ } catch {
4422
+ return {};
4423
+ }
4424
+ }
4425
+ function withStoreLock(storePath, action) {
4426
+ const lockPath = `${storePath}.lock`;
4427
+ openPrivateStorageDir(dirname4(storePath));
4428
+ const deadline = Date.now() + 2000;
4429
+ for (;; ) {
4430
+ try {
4431
+ privateMkdirSync(lockPath, false);
4432
+ break;
4433
+ } catch (error) {
4434
+ if (error.code !== "EEXIST")
4435
+ throw error;
4436
+ try {
4437
+ if (Date.now() - statSync4(lockPath).mtimeMs > LOCK_STALE_MS)
4438
+ rmdirSync(lockPath);
4439
+ } catch {}
4440
+ if (Date.now() > deadline)
4441
+ throw error;
4442
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
4443
+ }
4444
+ }
4445
+ try {
4446
+ return action();
4447
+ } finally {
4448
+ try {
4449
+ rmdirSync(lockPath);
4450
+ } catch {}
4451
+ }
4452
+ }
4453
+ function clearMigrationNotices(configPath, storePath = migrationNoticeStorePath()) {
4454
+ const prefix = `${resolve6(configPath)}|`;
4455
+ withStoreLock(storePath, () => {
4456
+ const records = readRecords(storePath);
4457
+ for (const key of Object.keys(records))
4458
+ if (key.startsWith(prefix))
4459
+ delete records[key];
4460
+ writeFileSync2(storePath, `${JSON.stringify(records, null, 2)}
4461
+ `, {
4462
+ encoding: "utf8",
4463
+ mode: PRIVATE_FILE_MODE
4464
+ });
4465
+ });
4466
+ }
4467
+ var LOCK_STALE_MS = 1e4;
4468
+ var init_migration_notices = __esm(() => {
4469
+ init_feature_config();
4470
+ init_private_storage();
4471
+ });
4472
+
4113
4473
  // ../aft-bridge/dist/onnx-probe.js
4114
4474
  import { closeSync as closeSync5, existsSync as existsSync5, openSync as openSync5, readSync as readSync4, realpathSync as realpathSync2 } from "node:fs";
4115
4475
  function readAt(fd, offset, length) {
@@ -5104,9 +5464,12 @@ var init_semantic_status = __esm(() => {
5104
5464
  // ../aft-bridge/dist/index.js
5105
5465
  var init_dist2 = __esm(() => {
5106
5466
  init_active_logger();
5467
+ init_bash_env();
5107
5468
  init_bash_format();
5108
5469
  init_bash_hints();
5109
5470
  init_bash_host_fallback();
5471
+ init_bash_permissions();
5472
+ init_bash_watch_lines();
5110
5473
  init_binary_identity();
5111
5474
  init_bridge();
5112
5475
  init_cache_paths();
@@ -5125,6 +5488,7 @@ var init_dist2 = __esm(() => {
5125
5488
  init_lsp_github_install();
5126
5489
  init_lsp_github_probe();
5127
5490
  init_lsp_github_table();
5491
+ init_lsp_npm_policy();
5128
5492
  init_message_detach();
5129
5493
  init_migration();
5130
5494
  init_migration_notices();
@@ -5138,6 +5502,7 @@ var init_dist2 = __esm(() => {
5138
5502
  init_platform();
5139
5503
  init_pool();
5140
5504
  init_project_identity();
5505
+ init_protocol();
5141
5506
  init_resolver();
5142
5507
  init_revivable_transport();
5143
5508
  init_semantic_status();
@@ -5275,12 +5640,7 @@ function firstExisting(candidates) {
5275
5640
  return null;
5276
5641
  }
5277
5642
  function platformKey2(platform = process.platform, arch = process.arch) {
5278
- const table = {
5279
- darwin: { arm64: "darwin-arm64", x64: "darwin-x64" },
5280
- linux: { arm64: "linux-arm64", x64: "linux-x64" },
5281
- win32: { x64: "win32-x64" }
5282
- };
5283
- return table[platform]?.[arch] ?? null;
5643
+ return PLATFORM_ARCH_MAP[platform]?.[arch] ?? null;
5284
5644
  }
5285
5645
  function newestCachedBinary(cacheDir, binaryName) {
5286
5646
  let entries;
@@ -5411,13 +5771,13 @@ var init_binary_probe = __esm(async () => {
5411
5771
  });
5412
5772
 
5413
5773
  // src/lib/fs-util.ts
5414
- import { existsSync as existsSync8, readdirSync as readdirSync5, statSync as statSync7 } from "node:fs";
5774
+ import { existsSync as existsSync8, lstatSync as lstatSync3, readdirSync as readdirSync5 } from "node:fs";
5415
5775
  import { join as join14 } from "node:path";
5416
5776
  function dirSize(path) {
5417
5777
  if (!existsSync8(path)) {
5418
5778
  return 0;
5419
5779
  }
5420
- const stat = statSync7(path);
5780
+ const stat = lstatSync3(path);
5421
5781
  if (stat.isFile()) {
5422
5782
  return stat.size;
5423
5783
  }
@@ -5431,10 +5791,10 @@ function dirSize(path) {
5431
5791
  return total;
5432
5792
  }
5433
5793
  function formatBytes(bytes) {
5434
- if (bytes === 0)
5794
+ if (!Number.isFinite(bytes) || bytes < 1)
5435
5795
  return "0 B";
5436
5796
  const units = ["B", "KB", "MB", "GB", "TB"];
5437
- const i = Math.floor(Math.log(bytes) / Math.log(1024));
5797
+ const i = Math.min(units.length - 1, Math.floor(Math.log(bytes) / Math.log(1024)));
5438
5798
  return `${(bytes / 1024 ** i).toFixed(1)} ${units[i]}`;
5439
5799
  }
5440
5800
  function compareVersionLabels(a, b) {
@@ -5505,12 +5865,12 @@ function getOmpPackageDir() {
5505
5865
  var init_omp_paths = () => {};
5506
5866
 
5507
5867
  // src/lib/omp-helpers.ts
5508
- import { accessSync as accessSync2, constants as constants3, existsSync as existsSync10, readFileSync as readFileSync7, statSync as statSync8 } from "node:fs";
5868
+ import { accessSync as accessSync2, constants as constants3, existsSync as existsSync10, readFileSync as readFileSync7, statSync as statSync7 } from "node:fs";
5509
5869
  import { homedir as homedir12 } from "node:os";
5510
5870
  import { delimiter as delimiter2, extname, join as join16, resolve as resolve10 } from "node:path";
5511
5871
  function isExecutableFile(path, platform = process.platform) {
5512
5872
  try {
5513
- if (!existsSync10(path) || !statSync8(path).isFile())
5873
+ if (!existsSync10(path) || !statSync7(path).isFile())
5514
5874
  return false;
5515
5875
  accessSync2(path, platform === "win32" ? constants3.F_OK : constants3.X_OK);
5516
5876
  return true;
@@ -5836,7 +6196,7 @@ var init_omp = __esm(() => {
5836
6196
  });
5837
6197
 
5838
6198
  // src/lib/fs-errors.ts
5839
- import { existsSync as existsSync12, statSync as statSync9 } from "node:fs";
6199
+ import { existsSync as existsSync12, statSync as statSync8 } from "node:fs";
5840
6200
  import { homedir as homedir13 } from "node:os";
5841
6201
  import { dirname as dirname6, resolve as resolve11, sep as sep2 } from "node:path";
5842
6202
  function errorCode(error) {
@@ -5921,7 +6281,7 @@ var init_fs_errors = __esm(() => {
5921
6281
  defaultFacts = {
5922
6282
  ownerUid(path) {
5923
6283
  try {
5924
- return statSync9(path).uid;
6284
+ return statSync8(path).uid;
5925
6285
  } catch {
5926
6286
  return null;
5927
6287
  }
@@ -13709,7 +14069,19 @@ var require_src2 = __commonJS(function(exports, module) {
13709
14069
  });
13710
14070
 
13711
14071
  // src/lib/jsonc.ts
13712
- import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "node:fs";
14072
+ import { randomUUID as randomUUID2 } from "node:crypto";
14073
+ import {
14074
+ chmodSync as chmodSync3,
14075
+ existsSync as existsSync13,
14076
+ lstatSync as lstatSync4,
14077
+ mkdirSync as mkdirSync4,
14078
+ readFileSync as readFileSync9,
14079
+ realpathSync as realpathSync4,
14080
+ renameSync as renameSync5,
14081
+ rmSync as rmSync3,
14082
+ statSync as statSync9,
14083
+ writeFileSync as writeFileSync4
14084
+ } from "node:fs";
13713
14085
  import { dirname as dirname7 } from "node:path";
13714
14086
  function detectJsoncFile(configDir, baseName) {
13715
14087
  const jsoncPath = `${configDir}/${baseName}.jsonc`;
@@ -13737,11 +14109,28 @@ function readJsoncFile(path) {
13737
14109
  };
13738
14110
  }
13739
14111
  }
13740
- function writeJsoncFile(path, value, format = "json") {
14112
+ function writeJsoncFile(path, value, _format = "json") {
13741
14113
  mkdirSync4(dirname7(path), { recursive: true });
13742
- const serialized = format === "jsonc" ? import_comment_json.stringify(value, null, 2) : JSON.stringify(value, null, 2);
13743
- writeFileSync4(path, `${serialized}
13744
- `);
14114
+ const link = lstatSync4(path, { throwIfNoEntry: false });
14115
+ if (link?.isSymbolicLink() && !existsSync13(path)) {
14116
+ throw new Error(`Cannot write ${path}: config symlink target is missing or unreadable`);
14117
+ }
14118
+ const target = existsSync13(path) ? realpathSync4(path) : path;
14119
+ const mode = existsSync13(target) ? statSync9(target).mode & 511 : undefined;
14120
+ const temporary = `${target}.tmp-${randomUUID2()}`;
14121
+ try {
14122
+ writeFileSync4(temporary, `${import_comment_json.stringify(value, null, 2)}
14123
+ `, { flag: "wx", mode });
14124
+ if (mode !== undefined)
14125
+ chmodSync3(temporary, mode);
14126
+ renameSync5(temporary, target);
14127
+ } catch (error) {
14128
+ if (error && typeof error === "object" && "path" in error)
14129
+ error.path = path;
14130
+ throw error;
14131
+ } finally {
14132
+ rmSync3(temporary, { force: true });
14133
+ }
13745
14134
  }
13746
14135
  function ensureAftSchemaUrl(path, format) {
13747
14136
  const existed = existsSync13(path);
@@ -14020,8 +14409,8 @@ import {
14020
14409
  readdirSync as readdirSync6,
14021
14410
  readFileSync as readFileSync10,
14022
14411
  readSync as readSync5,
14023
- realpathSync as realpathSync4,
14024
- rmSync as rmSync3,
14412
+ realpathSync as realpathSync5,
14413
+ rmSync as rmSync4,
14025
14414
  statSync as statSync10
14026
14415
  } from "node:fs";
14027
14416
  import { homedir as homedir14, tmpdir as tmpdir3 } from "node:os";
@@ -14040,7 +14429,7 @@ function packageMetadataAt(path, expectedName) {
14040
14429
  function packageMetadataNearExecutable(executable, expectedName) {
14041
14430
  let current;
14042
14431
  try {
14043
- current = dirname8(realpathSync4(executable));
14432
+ current = dirname8(realpathSync5(executable));
14044
14433
  } catch {
14045
14434
  current = dirname8(executable);
14046
14435
  }
@@ -14205,7 +14594,7 @@ function probeOpenCodeV1Version(executable, options = {}) {
14205
14594
  }
14206
14595
  const after = snapshotOperatorState(operatorHome);
14207
14596
  const createdIsolatedState = probeCreatedIsolatedState(data, state);
14208
- rmSync3(root, { recursive: true, force: true });
14597
+ rmSync4(root, { recursive: true, force: true });
14209
14598
  if (after !== before && env.AFT_LOAD_MATRIX_ALLOW_LIVE_OPERATOR === "0") {
14210
14599
  throw new Error("OpenCode host probe changed the operator database or log directory");
14211
14600
  }
@@ -14243,7 +14632,7 @@ function isV2Version(version) {
14243
14632
  }
14244
14633
  function resolvedPath(executable) {
14245
14634
  try {
14246
- return realpathSync4(executable);
14635
+ return realpathSync5(executable);
14247
14636
  } catch {
14248
14637
  return executable;
14249
14638
  }
@@ -14324,7 +14713,7 @@ var init_host_generation = __esm(() => {
14324
14713
  });
14325
14714
 
14326
14715
  // src/adapters/opencode.ts
14327
- import { existsSync as existsSync15, readFileSync as readFileSync11, rmSync as rmSync4, statSync as statSync11 } from "node:fs";
14716
+ import { existsSync as existsSync15, readFileSync as readFileSync11, rmSync as rmSync5, statSync as statSync11 } from "node:fs";
14328
14717
  import { homedir as homedir15 } from "node:os";
14329
14718
  import { dirname as dirname9, join as join19, parse as parse3, resolve as resolve12 } from "node:path";
14330
14719
  import { fileURLToPath } from "node:url";
@@ -14346,7 +14735,7 @@ function clearLegacyOpenCodePluginCache(primaryPath) {
14346
14735
  if (!legacyPath || !existsSync15(legacyPath))
14347
14736
  return { clearedPath: null };
14348
14737
  try {
14349
- rmSync4(legacyPath, { recursive: true, force: true });
14738
+ rmSync5(legacyPath, { recursive: true, force: true });
14350
14739
  return { clearedPath: legacyPath };
14351
14740
  } catch (error) {
14352
14741
  return { clearedPath: null, error: error instanceof Error ? error.message : String(error) };
@@ -14666,18 +15055,20 @@ class OpenCodeAdapter {
14666
15055
  const configuredEntry = openCodePluginReadKeys(this.configGeneration()).flatMap((key) => Array.isArray(value?.[key]) ? value[key] : []).map(pluginEntryPackage).find(isAftNpmEntry);
14667
15056
  const path = join19(getOpenCodeCacheDir(), "packages", configuredEntry ?? PLUGIN_ENTRY);
14668
15057
  let cached;
15058
+ let readError;
14669
15059
  try {
14670
15060
  const installedPkgPath = join19(path, "node_modules", "@cortexkit", "aft-opencode", "package.json");
14671
- if (existsSync15(installedPkgPath)) {
14672
- const pkg = JSON.parse(readFileSync11(installedPkgPath, "utf-8"));
14673
- cached = typeof pkg.version === "string" ? pkg.version : undefined;
15061
+ const pkg = JSON.parse(readFileSync11(installedPkgPath, "utf-8"));
15062
+ cached = typeof pkg.version === "string" ? pkg.version : undefined;
15063
+ } catch (error) {
15064
+ if (error.code !== "ENOENT") {
15065
+ readError = error instanceof Error ? error.message : String(error);
14674
15066
  }
14675
- } catch {
14676
- cached = undefined;
14677
15067
  }
14678
15068
  return {
14679
15069
  path,
14680
15070
  cached,
15071
+ readError,
14681
15072
  latest: getSelfVersion(),
14682
15073
  exists: existsSync15(path),
14683
15074
  configuredEntry
@@ -14719,7 +15110,7 @@ class OpenCodeAdapter {
14719
15110
  };
14720
15111
  }
14721
15112
  try {
14722
- rmSync4(info.path, { recursive: true, force: true });
15113
+ rmSync5(info.path, { recursive: true, force: true });
14723
15114
  return {
14724
15115
  action: "cleared",
14725
15116
  path: info.path,
@@ -14770,7 +15161,8 @@ var init_opencode = __esm(() => {
14770
15161
  // src/adapters/pi.ts
14771
15162
  import { existsSync as existsSync16, readFileSync as readFileSync12 } from "node:fs";
14772
15163
  import { homedir as homedir16 } from "node:os";
14773
- import { join as join20, resolve as resolve13 } from "node:path";
15164
+ import { isAbsolute as isAbsolute7, join as join20, resolve as resolve13 } from "node:path";
15165
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
14774
15166
  function getPiAgentDir() {
14775
15167
  const configuredDir = process.env.PI_CODING_AGENT_DIR?.trim();
14776
15168
  if (configuredDir)
@@ -14832,12 +15224,11 @@ function piEntryMatchesAft(entry) {
14832
15224
  let resolved = null;
14833
15225
  if (entry.startsWith("file:")) {
14834
15226
  try {
14835
- const stripped = entry.slice("file:".length);
14836
- resolved = stripped.startsWith("//") ? stripped.slice(2) : stripped;
15227
+ resolved = fileURLToPath2(entry);
14837
15228
  } catch {
14838
15229
  return false;
14839
15230
  }
14840
- } else if (entry.startsWith("/")) {
15231
+ } else if (isAbsolute7(entry)) {
14841
15232
  resolved = entry;
14842
15233
  } else if (entry.length > 0) {
14843
15234
  resolved = join20(getPiAgentDir(), entry);
@@ -14887,7 +15278,7 @@ class PiAdapter {
14887
15278
  const stderr = (result.stderr ?? "").trim();
14888
15279
  const text = stdout || stderr;
14889
15280
  const semverLine = text.split(/\r?\n/).find((l) => /^\d+\.\d+\.\d+/.test(l.trim()));
14890
- return semverLine?.trim() ?? text ?? null;
15281
+ return semverLine?.trim() ?? (text || null);
14891
15282
  } catch {
14892
15283
  return null;
14893
15284
  }
@@ -14942,29 +15333,37 @@ class PiAdapter {
14942
15333
  };
14943
15334
  }
14944
15335
  }
15336
+ getConfiguredPluginEntry() {
15337
+ return readPiExtensionIndex().installed.find(piEntryMatchesAft);
15338
+ }
14945
15339
  getPluginCacheInfo() {
14946
15340
  const candidates = [
14947
15341
  join20(getPiAgentDir(), "npm", "node_modules", "@cortexkit", "aft-pi", "package.json"),
14948
15342
  join20(getPiAgentDir(), "node_modules", "@cortexkit", "aft-pi", "package.json"),
14949
15343
  join20(getPiAgentDir(), "extensions", "node_modules", "@cortexkit", "aft-pi", "package.json")
14950
15344
  ];
15345
+ let readError;
14951
15346
  for (const candidate of candidates) {
14952
- if (!existsSync16(candidate))
14953
- continue;
14954
15347
  try {
14955
15348
  const pkg = JSON.parse(readFileSync12(candidate, "utf-8"));
14956
15349
  const cached = typeof pkg.version === "string" ? pkg.version : undefined;
14957
15350
  return {
14958
15351
  path: candidate,
14959
15352
  cached,
15353
+ readError,
14960
15354
  latest: undefined,
14961
15355
  exists: true
14962
15356
  };
14963
- } catch {}
15357
+ } catch (error) {
15358
+ if (error.code !== "ENOENT") {
15359
+ readError ??= error instanceof Error ? error.message : String(error);
15360
+ }
15361
+ }
14964
15362
  }
14965
15363
  return {
14966
15364
  path: join20(getPiAgentDir(), "npm", "node_modules", "@cortexkit", "aft-pi", "package.json"),
14967
- exists: false
15365
+ exists: readError !== undefined,
15366
+ readError
14968
15367
  };
14969
15368
  }
14970
15369
  getStorageDir() {
@@ -16256,7 +16655,7 @@ var init_dist6 = __esm(() => {
16256
16655
  function handleCancel(value, message = "Cancelled.") {
16257
16656
  if (isCancel(value)) {
16258
16657
  log2.warn(message);
16259
- process.exit(0);
16658
+ process.exit(130);
16260
16659
  }
16261
16660
  }
16262
16661
  async function confirm2(message, defaultYes = true) {
@@ -16639,7 +17038,7 @@ function runConfigFix(run = runNative) {
16639
17038
  try {
16640
17039
  const report = JSON.parse(result.stdout);
16641
17040
  if (Array.isArray(report.files))
16642
- return { ok: true, files: report.files };
17041
+ return { ok: true, files: report.files, cacheCleanup: report.cache_cleanup };
16643
17042
  } catch {}
16644
17043
  return {
16645
17044
  ok: false,
@@ -17043,7 +17442,7 @@ var init_feature_wizard = __esm(async () => {
17043
17442
  clackIO = {
17044
17443
  selectRows: (message, options, initial, rules) => promptFeatureList(message, options, initial, () => {
17045
17444
  log2.warn("Cancelled.");
17046
- process.exit(0);
17445
+ process.exit(130);
17047
17446
  }, {}, rules),
17048
17447
  info: (message) => log2.info(message),
17049
17448
  warn: (message) => log2.warn(message),
@@ -17237,68 +17636,76 @@ var init_setup = __esm(async () => {
17237
17636
  ]);
17238
17637
  });
17239
17638
 
17240
- // src/commands/index.ts
17241
- function runIndex(argv) {
17242
- const binary = findAftBinary();
17639
+ // src/lib/native-forward.ts
17640
+ import { constants as constants4 } from "node:os";
17641
+ function forwardToNative(command, argv) {
17642
+ const version = getSelfVersion();
17643
+ const preferred = /^v?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version) ? version : undefined;
17644
+ const binary = findAftBinary(preferred);
17243
17645
  if (!binary) {
17244
- console.error(missingAftBinaryMessage(`${CLI} index`));
17646
+ console.error(missingAftBinaryMessage(`${CLI} ${command}`, preferred));
17245
17647
  return 1;
17246
17648
  }
17247
- const result = spawnSync(binary, ["index", ...argv], {
17248
- stdio: "inherit",
17249
- env: process.env
17250
- });
17649
+ const result = spawnSync(binary, [command, ...argv], { stdio: "inherit", env: process.env });
17251
17650
  if (result.error) {
17252
- console.error(`aft index failed to start ${binary}: ${result.error.message}`);
17651
+ console.error(`aft ${command} failed to start ${binary}: ${result.error.message}`);
17253
17652
  return 1;
17254
17653
  }
17255
- return result.status ?? 1;
17654
+ if (result.status !== null)
17655
+ return result.status;
17656
+ const signal = result.signal ? constants4.signals[result.signal] : undefined;
17657
+ return signal === undefined ? 1 : 128 + signal;
17256
17658
  }
17257
- var init_commands = __esm(async () => {
17659
+ var init_native_forward = __esm(async () => {
17258
17660
  init_dist2();
17661
+ init_self_version();
17259
17662
  await init_binary_probe();
17260
17663
  });
17261
17664
 
17665
+ // src/commands/index.ts
17666
+ function runIndex(argv) {
17667
+ return forwardToNative("index", argv);
17668
+ }
17669
+ var init_commands = __esm(async () => {
17670
+ await init_native_forward();
17671
+ });
17672
+
17262
17673
  // src/commands/backups.ts
17263
17674
  function runBackups(argv) {
17264
- const binary = findAftBinary();
17265
- if (!binary) {
17266
- console.error(missingAftBinaryMessage(`${CLI} backups`));
17267
- return 1;
17268
- }
17269
- const result = spawnSync(binary, ["backups", ...argv], {
17270
- stdio: "inherit",
17271
- env: process.env
17272
- });
17273
- if (result.error) {
17274
- console.error(`aft backups failed to start ${binary}: ${result.error.message}`);
17275
- return 1;
17276
- }
17277
- return result.status ?? 1;
17675
+ return forwardToNative("backups", argv);
17278
17676
  }
17279
17677
  var init_backups = __esm(async () => {
17280
- init_dist2();
17281
- await init_binary_probe();
17678
+ await init_native_forward();
17282
17679
  });
17283
17680
 
17284
17681
  // src/commands/cache.ts
17285
17682
  function runCache(argv) {
17683
+ return forwardToNative("cache", argv);
17684
+ }
17685
+ var init_cache = __esm(async () => {
17686
+ await init_native_forward();
17687
+ });
17688
+
17689
+ // src/commands/clean-old-caches.ts
17690
+ function runOldCacheCleanup(argv) {
17286
17691
  const binary = findAftBinary();
17287
17692
  if (!binary) {
17288
- console.error(missingAftBinaryMessage(`${CLI} cache`));
17693
+ console.error(missingAftBinaryMessage(`${CLI} doctor --clean-old-caches`));
17289
17694
  return 1;
17290
17695
  }
17291
- const result = spawnSync(binary, ["cache", ...argv], {
17696
+ if (argv.some((arg) => !["--clean-old-caches", "--yes", "--help", "-h"].includes(arg))) {
17697
+ console.error("usage: aft doctor --clean-old-caches [--yes]");
17698
+ return 2;
17699
+ }
17700
+ const result = spawnSync(binary, ["clean-old-caches", ...argv.filter((arg) => arg !== "--clean-old-caches")], {
17292
17701
  stdio: "inherit",
17293
17702
  env: process.env
17294
17703
  });
17295
- if (result.error) {
17296
- console.error(`aft cache failed to start ${binary}: ${result.error.message}`);
17297
- return 1;
17298
- }
17704
+ if (result.error)
17705
+ console.error(`Old cache cleanup failed to start: ${result.error.message}`);
17299
17706
  return result.status ?? 1;
17300
17707
  }
17301
- var init_cache = __esm(async () => {
17708
+ var init_clean_old_caches = __esm(async () => {
17302
17709
  init_dist2();
17303
17710
  await init_binary_probe();
17304
17711
  });
@@ -17413,7 +17820,7 @@ var init_features = __esm(() => {
17413
17820
  // src/doctor/opencode.ts
17414
17821
  import { existsSync as existsSync18, readFileSync as readFileSync13 } from "node:fs";
17415
17822
  import { dirname as dirname11, join as join23 } from "node:path";
17416
- import { fileURLToPath as fileURLToPath2 } from "node:url";
17823
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
17417
17824
  function isLoadPath(value) {
17418
17825
  return OPENCODE_LOAD_PATHS.includes(value);
17419
17826
  }
@@ -17460,7 +17867,7 @@ function localPluginRoot(entry) {
17460
17867
  return null;
17461
17868
  if (entry.startsWith("file://")) {
17462
17869
  try {
17463
- return fileURLToPath2(entry);
17870
+ return fileURLToPath3(entry);
17464
17871
  } catch {
17465
17872
  return null;
17466
17873
  }
@@ -17636,7 +18043,7 @@ async function sendAftRequest(binaryPath, request) {
17636
18043
  throw new Error("aft exited before responding");
17637
18044
  return response;
17638
18045
  }
17639
- async function sendAftRequests(binaryPath, requests) {
18046
+ async function sendAftRequests(binaryPath, requests, timeoutMs = 120000) {
17640
18047
  return new Promise((resolve, reject) => {
17641
18048
  const child = spawn(binaryPath, [], {
17642
18049
  stdio: ["pipe", "pipe", "pipe"]
@@ -17646,10 +18053,16 @@ async function sendAftRequests(binaryPath, requests) {
17646
18053
  let noiseLineCount = 0;
17647
18054
  let stderr = "";
17648
18055
  let settled = false;
18056
+ let timedOut = false;
18057
+ const deadline = setTimeout(() => {
18058
+ timedOut = true;
18059
+ child.kill("SIGKILL");
18060
+ }, timeoutMs);
17649
18061
  const finish = (fn) => {
17650
18062
  if (settled)
17651
18063
  return;
17652
18064
  settled = true;
18065
+ clearTimeout(deadline);
17653
18066
  child.kill();
17654
18067
  fn();
17655
18068
  };
@@ -17688,7 +18101,7 @@ async function sendAftRequests(binaryPath, requests) {
17688
18101
  });
17689
18102
  child.stdout.setEncoding("utf-8");
17690
18103
  child.stdout.on("data", (chunk) => {
17691
- if (settled)
18104
+ if (settled || timedOut)
17692
18105
  return;
17693
18106
  stdoutLines.push(chunk);
17694
18107
  });
@@ -17702,6 +18115,13 @@ async function sendAftRequests(binaryPath, requests) {
17702
18115
  child.on("close", (code) => {
17703
18116
  if (settled)
17704
18117
  return;
18118
+ if (timedOut) {
18119
+ const received = new Set(responses.map((response) => response.id));
18120
+ const pending = requests.filter((request) => !received.has(request.id)).map((request) => request.command);
18121
+ finish(() => reject(new Error(`aft request deadline exceeded; pending: ${pending.join(", ")}.
18122
+ ${stderr.trim()}`, { cause: { exitCode: code, signal: child.signalCode } })));
18123
+ return;
18124
+ }
17705
18125
  finish(() => reject(buildBridgeError({ binaryPath, code, stderr, noiseLines, noiseLineCount, responses })));
17706
18126
  });
17707
18127
  child.stdin.on("error", () => {});
@@ -17781,14 +18201,14 @@ var init_binary_cache = __esm(() => {
17781
18201
  });
17782
18202
 
17783
18203
  // src/lib/sanitize.ts
17784
- import { realpathSync as realpathSync5 } from "node:fs";
18204
+ import { realpathSync as realpathSync6 } from "node:fs";
17785
18205
  import { homedir as homedir17, userInfo } from "node:os";
17786
18206
  function escapeRegex(value) {
17787
18207
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
17788
18208
  }
17789
18209
  function safeRealpath(p) {
17790
18210
  try {
17791
- return realpathSync5(p);
18211
+ return realpathSync6(p);
17792
18212
  } catch {
17793
18213
  return null;
17794
18214
  }
@@ -18317,7 +18737,7 @@ var init_legacy_storage = __esm(() => {
18317
18737
  });
18318
18738
 
18319
18739
  // src/lib/lsp-cache.ts
18320
- import { existsSync as existsSync24, readdirSync as readdirSync10, rmSync as rmSync5, statSync as statSync15 } from "node:fs";
18740
+ import { existsSync as existsSync24, readdirSync as readdirSync10, rmSync as rmSync6, statSync as statSync15 } from "node:fs";
18321
18741
  import { join as join27 } from "node:path";
18322
18742
  function inspectDir(path) {
18323
18743
  if (!existsSync24(path)) {
@@ -18337,8 +18757,12 @@ function inspectDir(path) {
18337
18757
  if (!statSync15(full).isDirectory())
18338
18758
  continue;
18339
18759
  const size = dirSize(full);
18760
+ let decodedName = name;
18761
+ try {
18762
+ decodedName = decodeURIComponent(name);
18763
+ } catch {}
18340
18764
  entries.push({
18341
- name: decodeURIComponent(name),
18765
+ name: decodedName,
18342
18766
  path: full,
18343
18767
  size
18344
18768
  });
@@ -18372,7 +18796,7 @@ function clearLspCaches() {
18372
18796
  const report = getLspCacheReport();
18373
18797
  for (const entry of [...report.npm.entries, ...report.github.entries]) {
18374
18798
  try {
18375
- rmSync5(entry.path, { recursive: true, force: true });
18799
+ rmSync6(entry.path, { recursive: true, force: true });
18376
18800
  result.cleared.push({ name: entry.name, path: entry.path, size: entry.size });
18377
18801
  result.totalBytes += entry.size;
18378
18802
  } catch (err) {
@@ -18390,8 +18814,8 @@ var init_lsp_cache = __esm(() => {
18390
18814
  });
18391
18815
 
18392
18816
  // src/lib/onnx.ts
18393
- import { existsSync as existsSync25, readdirSync as readdirSync11, readlinkSync as readlinkSync2, realpathSync as realpathSync6 } from "node:fs";
18394
- import { basename as basename2, isAbsolute as isAbsolute7, join as join28, resolve as resolve14, win32 as win323 } from "node:path";
18817
+ import { existsSync as existsSync25, readdirSync as readdirSync11, readlinkSync as readlinkSync2, realpathSync as realpathSync7 } from "node:fs";
18818
+ import { basename as basename2, isAbsolute as isAbsolute8, join as join28, resolve as resolve14, win32 as win323 } from "node:path";
18395
18819
  function getOnnxLibraryName() {
18396
18820
  if (process.platform === "darwin")
18397
18821
  return "libonnxruntime.dylib";
@@ -18427,7 +18851,7 @@ function pathEntriesForPlatform2() {
18427
18851
  return pathEnvValue2().split(delimiter).map((entry) => entry.trim().replace(/^"|"$/g, "")).filter((entry) => {
18428
18852
  if (!entry || entry === "." || entry.includes("\x00"))
18429
18853
  return false;
18430
- return isAbsolute7(entry) || win323.isAbsolute(entry);
18854
+ return isAbsolute8(entry) || win323.isAbsolute(entry);
18431
18855
  });
18432
18856
  }
18433
18857
  function isWindowsSystem32Directory2(dir) {
@@ -18566,7 +18990,7 @@ function detectOrtVersion(libDir) {
18566
18990
  return null;
18567
18991
  const libName = getOnnxLibraryName();
18568
18992
  try {
18569
- const resolved = parseOrtVersionFromPath(realpathSync6(join28(libDir, libName)));
18993
+ const resolved = parseOrtVersionFromPath(realpathSync7(join28(libDir, libName)));
18570
18994
  if (resolved && resolved !== INVALID_ORT_VERSION2)
18571
18995
  return resolved;
18572
18996
  } catch {}
@@ -18585,7 +19009,7 @@ function detectOrtVersion(libDir) {
18585
19009
  const base = join28(libDir, libName);
18586
19010
  if (existsSync25(base)) {
18587
19011
  try {
18588
- const real = realpathSync6(base);
19012
+ const real = realpathSync7(base);
18589
19013
  const version = parseOrtVersionFromPath(real) ?? parseOrtVersionFromDirectoryPath(real);
18590
19014
  if (version)
18591
19015
  return version;
@@ -18617,6 +19041,8 @@ var init_onnx = __esm(() => {
18617
19041
 
18618
19042
  // src/lib/plugin-load.ts
18619
19043
  import { existsSync as existsSync26, readFileSync as readFileSync15 } from "node:fs";
19044
+ import { homedir as homedir18 } from "node:os";
19045
+ import { isAbsolute as isAbsolute9, join as join29, normalize } from "node:path";
18620
19046
  function isRecord3(value) {
18621
19047
  return typeof value === "object" && value !== null && !Array.isArray(value);
18622
19048
  }
@@ -18639,7 +19065,7 @@ function failsEveryToolCall(toolCallError, scope = "") {
18639
19065
  }
18640
19066
  function withHarness(block, harness) {
18641
19067
  const override = isRecord3(block.harnesses) ? block.harnesses[harness] : undefined;
18642
- return isRecord3(override) ? { ...block, ...override } : block;
19068
+ return isRecord3(override) ? { ...block, ...override, storage: block.storage } : block;
18643
19069
  }
18644
19070
  function indexesOf(block) {
18645
19071
  return block && isRecord3(block.indexes) ? block.indexes : undefined;
@@ -18696,7 +19122,12 @@ function evaluatePluginLoad(input) {
18696
19122
  const semanticIndex = indexes?.semantic ?? true;
18697
19123
  const userSemantic = loaded.user && isRecord3(loaded.user.semantic) ? loaded.user.semantic : null;
18698
19124
  const semanticBackend = typeof userSemantic?.backend === "string" && userSemantic.backend.length > 0 ? userSemantic.backend : "fastembed";
19125
+ const storage = loaded.user && isRecord3(loaded.user.storage) ? loaded.user.storage : null;
19126
+ const requested = typeof storage?.cache_dir === "string" ? storage.cache_dir : "";
19127
+ const home = input.home ?? homedir18();
19128
+ const expanded = requested === "~" ? home : requested.startsWith("~/") || requested.startsWith("~\\") ? join29(home, requested.slice(2)) : requested;
18699
19129
  return {
19130
+ ...isAbsolute9(expanded) ? { cacheDir: normalize(expanded) } : {},
18700
19131
  blockers,
18701
19132
  semanticIndex,
18702
19133
  semanticBackend,
@@ -18713,12 +19144,13 @@ var init_plugin_load = __esm(() => {
18713
19144
  import {
18714
19145
  accessSync as accessSync3,
18715
19146
  closeSync as closeSync9,
18716
- constants as constants4,
19147
+ constants as constants5,
18717
19148
  existsSync as existsSync27,
18718
19149
  openSync as openSync9,
18719
19150
  readSync as readSync7,
18720
19151
  statSync as statSync16
18721
19152
  } from "node:fs";
19153
+ import { join as join30 } from "node:path";
18722
19154
  async function collectDiagnostics(adapters) {
18723
19155
  const cliVersion = getSelfVersion();
18724
19156
  const binaryVersion = probeBinaryVersion(cliVersion);
@@ -18756,7 +19188,7 @@ async function diagnoseHarness(adapter) {
18756
19188
  if (!existsSync27(storage))
18757
19189
  return false;
18758
19190
  try {
18759
- accessSync3(storage, constants4.R_OK | constants4.W_OK);
19191
+ accessSync3(storage, constants5.R_OK | constants5.W_OK);
18760
19192
  return true;
18761
19193
  } catch {
18762
19194
  return false;
@@ -18800,6 +19232,7 @@ async function diagnoseHarness(adapter) {
18800
19232
  exists: existsSync27(storage),
18801
19233
  accessible: storageAccessible,
18802
19234
  sizesByKey: describeStorage,
19235
+ ...pluginLoad.cacheDir ? { cacheRoot: join30(pluginLoad.cacheDir, "aft") } : {},
18803
19236
  ...legacyDuplication.totalPartitions > 0 ? { legacyDuplication } : {}
18804
19237
  },
18805
19238
  onnxRuntime: {
@@ -19251,14 +19684,14 @@ var init_issue_body = __esm(() => {
19251
19684
 
19252
19685
  // src/lib/onnx-fix.ts
19253
19686
  import { randomBytes as randomBytes2 } from "node:crypto";
19254
- import { existsSync as existsSync28, mkdirSync as mkdirSync7, renameSync as renameSync5, rmSync as rmSync6 } from "node:fs";
19255
- import { isAbsolute as isAbsolute8, join as join29, relative as relative4 } from "node:path";
19687
+ import { existsSync as existsSync28, mkdirSync as mkdirSync7, renameSync as renameSync6, rmSync as rmSync7 } from "node:fs";
19688
+ import { isAbsolute as isAbsolute10, join as join31, relative as relative4 } from "node:path";
19256
19689
  function findOnnxFixCandidates(report) {
19257
19690
  const candidates = [];
19258
19691
  for (const harness of report.harnesses) {
19259
19692
  if (!harness.onnxRuntime.required)
19260
19693
  continue;
19261
- const storageOnnxDir = join29(harness.storageDir.path, "onnxruntime");
19694
+ const storageOnnxDir = join31(harness.storageDir.path, "onnxruntime");
19262
19695
  const systemTooOld = harness.onnxRuntime.systemPath !== null && harness.onnxRuntime.systemCompatible === false;
19263
19696
  const cachedTooOld = harness.onnxRuntime.cachedPath !== null && harness.onnxRuntime.cachedCompatible === false;
19264
19697
  const hasCompatibleCached = harness.onnxRuntime.cachedCompatible === true;
@@ -19297,7 +19730,7 @@ function findOnnxFixCandidates(report) {
19297
19730
  async function installManagedReplacement(candidate, ensureFn, rmFn) {
19298
19731
  const suffix = `${process.pid}.${Date.now().toString(36)}.${randomBytes2(6).toString("hex")}`;
19299
19732
  const stagingStorageDir = `${candidate.harness.storageDir.path}.onnx-fix.tmp.${suffix}`;
19300
- const stagedOnnxDir = join29(stagingStorageDir, "onnxruntime");
19733
+ const stagedOnnxDir = join31(stagingStorageDir, "onnxruntime");
19301
19734
  const backupDir = `${candidate.storageOnnxDir}.backup.${suffix}`;
19302
19735
  try {
19303
19736
  const stagedInstalledPath = await ensureFn(stagingStorageDir);
@@ -19305,19 +19738,19 @@ async function installManagedReplacement(candidate, ensureFn, rmFn) {
19305
19738
  throw new Error("managed ONNX Runtime download was unavailable");
19306
19739
  }
19307
19740
  const installedRelativePath = relative4(stagedOnnxDir, stagedInstalledPath);
19308
- if (isAbsolute8(installedRelativePath) || installedRelativePath === ".." || installedRelativePath.startsWith("../") || installedRelativePath.startsWith("..\\")) {
19741
+ if (isAbsolute10(installedRelativePath) || installedRelativePath === ".." || installedRelativePath.startsWith("../") || installedRelativePath.startsWith("..\\")) {
19309
19742
  throw new Error(`managed ONNX Runtime was installed outside staging: ${stagedInstalledPath}`);
19310
19743
  }
19311
19744
  mkdirSync7(candidate.harness.storageDir.path, { recursive: true });
19312
19745
  const hadPrevious = existsSync28(candidate.storageOnnxDir);
19313
19746
  if (hadPrevious)
19314
- renameSync5(candidate.storageOnnxDir, backupDir);
19747
+ renameSync6(candidate.storageOnnxDir, backupDir);
19315
19748
  try {
19316
- renameSync5(stagedOnnxDir, candidate.storageOnnxDir);
19749
+ renameSync6(stagedOnnxDir, candidate.storageOnnxDir);
19317
19750
  } catch (installError) {
19318
19751
  if (hadPrevious) {
19319
19752
  try {
19320
- renameSync5(backupDir, candidate.storageOnnxDir);
19753
+ renameSync6(backupDir, candidate.storageOnnxDir);
19321
19754
  } catch (restoreError) {
19322
19755
  throw new Error(`managed ONNX Runtime replacement failed and the previous runtime could not be restored from ${backupDir}: ${restoreError}`, { cause: installError });
19323
19756
  }
@@ -19333,13 +19766,13 @@ async function installManagedReplacement(candidate, ensureFn, rmFn) {
19333
19766
  }
19334
19767
  }
19335
19768
  return {
19336
- installedPath: join29(candidate.storageOnnxDir, installedRelativePath),
19769
+ installedPath: join31(candidate.storageOnnxDir, installedRelativePath),
19337
19770
  clearedPrevious: hadPrevious && cleanupError === undefined,
19338
19771
  cleanupError
19339
19772
  };
19340
19773
  } finally {
19341
19774
  try {
19342
- rmSync6(stagingStorageDir, { recursive: true, force: true });
19775
+ rmSync7(stagingStorageDir, { recursive: true, force: true });
19343
19776
  } catch {}
19344
19777
  }
19345
19778
  }
@@ -19364,7 +19797,7 @@ async function runOnnxFix(adapters, report, options = {}) {
19364
19797
  return null;
19365
19798
  }
19366
19799
  const result = { cleared: 0, installed: 0, bytesReclaimed: 0, errors: [] };
19367
- const rmFn = options.rmFn ?? rmSync6;
19800
+ const rmFn = options.rmFn ?? rmSync7;
19368
19801
  const ensureFn = options.ensureFn ?? ensureOnnxRuntime;
19369
19802
  for (const candidate of candidates) {
19370
19803
  try {
@@ -19402,21 +19835,24 @@ var init_onnx_fix = __esm(() => {
19402
19835
  // src/lib/session-log.ts
19403
19836
  import { closeSync as closeSync10, openSync as openSync10, readSync as readSync8, statSync as statSync17 } from "node:fs";
19404
19837
  function readSessionLog(path, session, maxBytes = MAX_SCAN_BYTES) {
19838
+ return readLogHistory([path, `${path}.1`], session, maxBytes);
19839
+ }
19840
+ function readLogHistory(files, session, maxBytes = MAX_SCAN_BYTES) {
19405
19841
  const lines = [];
19406
19842
  const errors = [];
19407
19843
  let remaining = maxBytes;
19408
19844
  let boundHit = false;
19409
- const bareId = session.id.replace(/^ses_/, "");
19845
+ const bareId = session?.id.replace(/^ses_/, "");
19410
19846
  const selectedTags = [`[ses_${bareId}]`, `[${bareId}]`];
19411
19847
  const tagPattern = /\[ses_[^\]\s]+\]|\[[0-9a-fA-F]{8}(?:-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}\]/;
19412
19848
  const accept = (line) => {
19413
19849
  if (!line)
19414
19850
  return;
19415
19851
  const tag = tagPattern.exec(line)?.[0];
19416
- if (tag && !selectedTags.includes(tag))
19852
+ if (session && tag && !selectedTags.includes(tag))
19417
19853
  return;
19418
- if (!tag) {
19419
- const stamp = /^\[([^\]]+)\]/.exec(line)?.[1];
19854
+ if (session && !tag) {
19855
+ const stamp = /^\[([^\]]+)\]/.exec(line)?.[1] ?? /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z) \[aft(?:-lsp)?\] /.exec(line)?.[1];
19420
19856
  const time = stamp ? Date.parse(stamp) : NaN;
19421
19857
  const inWindow = Number.isFinite(time) && (session.startedAt === undefined || time >= session.startedAt) && time <= session.lastActivity;
19422
19858
  if (session.projectRoot) {
@@ -19433,7 +19869,7 @@ function readSessionLog(path, session, maxBytes = MAX_SCAN_BYTES) {
19433
19869
  if (errors.length < 20 && isErrorLogLine(line))
19434
19870
  errors.push(line);
19435
19871
  };
19436
- for (const file of [path, `${path}.1`]) {
19872
+ for (const file of files) {
19437
19873
  if (remaining <= 0 || lines.length >= 200 && errors.length >= 20)
19438
19874
  break;
19439
19875
  let fd;
@@ -19481,8 +19917,8 @@ var init_session_log = __esm(() => {
19481
19917
  // src/lib/sessions.ts
19482
19918
  import { existsSync as existsSync29, readdirSync as readdirSync12, readFileSync as readFileSync16, statSync as statSync18 } from "node:fs";
19483
19919
  import { createRequire as createRequire4 } from "node:module";
19484
- import { homedir as homedir18 } from "node:os";
19485
- import { basename as basename3, join as join30 } from "node:path";
19920
+ import { homedir as homedir19 } from "node:os";
19921
+ import { basename as basename3, join as join32 } from "node:path";
19486
19922
  function listRecentSessions(adapter) {
19487
19923
  try {
19488
19924
  if (adapter.kind === "opencode")
@@ -19516,7 +19952,7 @@ function mapOpenCodeSessionRows(rows) {
19516
19952
  }).filter((session) => session !== null).sort((a, b) => b.lastActivity - a.lastActivity).slice(0, MAX_RECENT_SESSIONS);
19517
19953
  }
19518
19954
  function listRecentOpenCodeSessions() {
19519
- const dbPath = join30(getXdgDataHome(), "opencode", "opencode.db");
19955
+ const dbPath = join32(getXdgDataHome(), "opencode", "opencode.db");
19520
19956
  if (!existsSync29(dbPath))
19521
19957
  return [];
19522
19958
  let db = null;
@@ -19536,14 +19972,14 @@ function listRecentOpenCodeSessions() {
19536
19972
  }
19537
19973
  function getXdgDataHome() {
19538
19974
  const xdgDataHome = process.env.XDG_DATA_HOME;
19539
- return xdgDataHome && xdgDataHome.length > 0 ? xdgDataHome : join30(homedir18(), ".local", "share");
19975
+ return xdgDataHome && xdgDataHome.length > 0 ? xdgDataHome : join32(homedir19(), ".local", "share");
19540
19976
  }
19541
19977
  function listRecentPiSessions() {
19542
- return listPiSessionsFromDir(join30(getHomeDir(), ".pi", "agent", "sessions"));
19978
+ return listPiSessionsFromDir(join32(getHomeDir(), ".pi", "agent", "sessions"));
19543
19979
  }
19544
19980
  function getHomeDir() {
19545
19981
  const envHome = process.platform === "win32" ? process.env.USERPROFILE : process.env.HOME;
19546
- return envHome && envHome.length > 0 ? envHome : homedir18();
19982
+ return envHome && envHome.length > 0 ? envHome : homedir19();
19547
19983
  }
19548
19984
  function listPiSessionsFromDir(sessionsDir) {
19549
19985
  try {
@@ -19585,7 +20021,7 @@ function collectJsonlFiles(root) {
19585
20021
  continue;
19586
20022
  }
19587
20023
  for (const entry of entries) {
19588
- const path = join30(dir, entry.name);
20024
+ const path = join32(dir, entry.name);
19589
20025
  if (entry.isDirectory()) {
19590
20026
  stack.push(path);
19591
20027
  } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
@@ -19685,18 +20121,20 @@ var init_sessions = __esm(() => {
19685
20121
 
19686
20122
  // src/commands/doctor.ts
19687
20123
  import {
19688
- chmodSync as chmodSync3,
20124
+ chmodSync as chmodSync4,
19689
20125
  existsSync as existsSync30,
20126
+ lstatSync as lstatSync5,
19690
20127
  mkdirSync as mkdirSync8,
19691
20128
  mkdtempSync as mkdtempSync2,
20129
+ readdirSync as readdirSync13,
19692
20130
  readFileSync as readFileSync17,
19693
- realpathSync as realpathSync7,
19694
- rmSync as rmSync7,
20131
+ realpathSync as realpathSync8,
20132
+ rmSync as rmSync8,
19695
20133
  statSync as statSync19,
19696
20134
  writeFileSync as writeFileSync5
19697
20135
  } from "node:fs";
19698
20136
  import { tmpdir as tmpdir5 } from "node:os";
19699
- import { join as join31 } from "node:path";
20137
+ import { dirname as dirname12, join as join33 } from "node:path";
19700
20138
  function openCodeAdapter(adapters) {
19701
20139
  return adapters.find((adapter) => adapter.kind === "opencode");
19702
20140
  }
@@ -20097,8 +20535,8 @@ async function clearDoctorCaches(adapters, targets, options = {}) {
20097
20535
  }
20098
20536
  return summary;
20099
20537
  }
20100
- function clearOldBinaries() {
20101
- const cliVersion = getSelfVersion();
20538
+ function clearOldBinaries(cliVersion = getSelfVersion(), plugins = getAllAdapters()) {
20539
+ const knownVersion = /^(?:v)?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
20102
20540
  const keepTag = `v${cliVersion.replace(/^v/, "")}`;
20103
20541
  const info = getBinaryCacheInfo(cliVersion);
20104
20542
  const result = {
@@ -20111,13 +20549,34 @@ function clearOldBinaries() {
20111
20549
  log2.info(`Binary cache: nothing to clear at ${info.path}`);
20112
20550
  return result;
20113
20551
  }
20114
- const stale = info.versions.filter((v) => v !== keepTag);
20552
+ if (!knownVersion.test(cliVersion)) {
20553
+ result.keptVersion = null;
20554
+ return result;
20555
+ }
20556
+ const retained = new Set([keepTag]);
20557
+ for (const plugin of plugins) {
20558
+ try {
20559
+ const info = plugin.getPluginCacheInfo();
20560
+ if (info.readError !== undefined)
20561
+ return result;
20562
+ const entry = (info.configuredEntry ?? plugin.getConfiguredPluginEntry?.())?.replace(/^npm:/, "");
20563
+ const prefix = `${plugin.pluginPackageName}@`;
20564
+ const pinned = entry?.startsWith(prefix) ? entry.slice(prefix.length) : undefined;
20565
+ for (const version of [info.cached, pinned]) {
20566
+ if (version && knownVersion.test(version))
20567
+ retained.add(`v${version.replace(/^v/, "")}`);
20568
+ }
20569
+ } catch {
20570
+ return result;
20571
+ }
20572
+ }
20573
+ const stale = info.versions.filter((v) => !retained.has(v));
20115
20574
  if (stale.length === 0) {
20116
20575
  log2.info(`Binary cache: only the active version (${keepTag}) is present at ${info.path}; nothing to clear`);
20117
20576
  return result;
20118
20577
  }
20119
20578
  for (const version of stale) {
20120
- const dir = join31(info.path, version);
20579
+ const dir = join33(info.path, version);
20121
20580
  let bytes = 0;
20122
20581
  try {
20123
20582
  bytes = statSync19(dir).isDirectory() ? dirSize(dir) : 0;
@@ -20125,7 +20584,7 @@ function clearOldBinaries() {
20125
20584
  bytes = 0;
20126
20585
  }
20127
20586
  try {
20128
- rmSync7(dir, { recursive: true, force: true });
20587
+ rmSync8(dir, { recursive: true, force: true });
20129
20588
  result.cleared += 1;
20130
20589
  result.bytesReclaimed += bytes;
20131
20590
  log2.success(`Binary cache: cleared ${dir} (reclaimed ${formatBytes(bytes)})`);
@@ -20277,7 +20736,7 @@ async function runDoctorNpmInstall(npm, installDir) {
20277
20736
  });
20278
20737
  }
20279
20738
  function installedPluginVersion(installDir) {
20280
- const manifest = join31(installDir, "node_modules", ...AFT_OPENCODE_PACKAGE.split("/"), "package.json");
20739
+ const manifest = join33(installDir, "node_modules", ...AFT_OPENCODE_PACKAGE.split("/"), "package.json");
20281
20740
  try {
20282
20741
  const pkg = JSON.parse(readFileSync17(manifest, "utf-8"));
20283
20742
  return typeof pkg.version === "string" ? pkg.version : null;
@@ -20695,6 +21154,22 @@ function applyConfigMigration(run, binaryDownloadFailed = false, targets = []) {
20695
21154
  for (const line of file.notes)
20696
21155
  log2.warn(` ${line}`);
20697
21156
  }
21157
+ for (const path of result.cacheCleanup?.removed ?? []) {
21158
+ changed += 1;
21159
+ log2.success(`Removed inactive rebuildable cache ${path}`);
21160
+ }
21161
+ for (const skipped of result.cacheCleanup?.skipped ?? [])
21162
+ log2.warn(`Inactive cache retained: ${skipped}`);
21163
+ for (const partial of result.cacheCleanup?.partial ?? []) {
21164
+ changed += 1;
21165
+ log2.warn(`Partially cleared ${partial.path}: removed ${partial.removed_entries} file/directory entries before repair stopped`);
21166
+ }
21167
+ for (const warning of result.cacheCleanup?.warnings ?? [])
21168
+ log2.warn(warning);
21169
+ if (result.cacheCleanup?.error) {
21170
+ errors += 1;
21171
+ log2.error(`Inactive cache cleanup refused: ${result.cacheCleanup.error}`);
21172
+ }
20698
21173
  return { changed, errors };
20699
21174
  }
20700
21175
  function logDoctorIssues(report, heading) {
@@ -20719,7 +21194,28 @@ function logDoctorIssues(report, heading) {
20719
21194
  function formatDoctorStorageStatus(h) {
20720
21195
  const state = h.storageDir.exists ? h.storageDir.path : `${h.storageDir.path} (${h.pluginRegistered ? "not yet created (lazy — created on first tool call)" : "not created"})`;
20721
21196
  const legacyDuplication = formatLegacyDuplication(h.storageDir.legacyDuplication);
20722
- return `${state} (${[formatStorageSizes(h.storageDir.sizesByKey), legacyDuplication].filter((part) => Boolean(part)).join("; ")})`;
21197
+ let cacheWarning = "";
21198
+ if (h.storageDir.cacheRoot) {
21199
+ try {
21200
+ const parent = realpathSync8(dirname12(h.storageDir.cacheRoot));
21201
+ let durable = h.storageDir.path;
21202
+ while (!existsSync30(durable) && dirname12(durable) !== durable)
21203
+ durable = dirname12(durable);
21204
+ let registered = false;
21205
+ const history = join33(h.storageDir.path, "retention/cache-parents.json");
21206
+ if (existsSync30(history)) {
21207
+ const saved = JSON.parse(readFileSync17(history, "utf8"));
21208
+ registered = saved.parents?.some((entry) => entry.path === parent) === true;
21209
+ }
21210
+ if (!registered && statSync19(parent).dev === statSync19(durable).dev) {
21211
+ cacheWarning = "; cache_dir_on_storage_device: first enrollment requires another volume";
21212
+ }
21213
+ } catch {}
21214
+ }
21215
+ return `${state}${h.storageDir.cacheRoot ? `; cache root: ${h.storageDir.cacheRoot} (configured; unavailable roots disable caches)${cacheWarning}` : ""} (${[
21216
+ formatStorageSizes(h.storageDir.sizesByKey),
21217
+ legacyDuplication
21218
+ ].filter((part) => Boolean(part)).join("; ")})`;
20723
21219
  }
20724
21220
  async function confirmBinaryDownloadDespitePluginSkew(report, argv) {
20725
21221
  const skews = findPluginCliVersionSkews(report);
@@ -20911,18 +21407,18 @@ function deriveIssueTitleFromBody(body) {
20911
21407
  function writeIssueReviewFile(body) {
20912
21408
  let reviewDir = null;
20913
21409
  try {
20914
- reviewDir = mkdtempSync2(join31(tmpdir5(), "aft-issue-"));
21410
+ reviewDir = mkdtempSync2(join33(tmpdir5(), "aft-issue-"));
20915
21411
  if (process.platform !== "win32") {
20916
- chmodSync3(reviewDir, 448);
21412
+ chmodSync4(reviewDir, 448);
20917
21413
  }
20918
- const outPath = join31(reviewDir, "issue.md");
21414
+ const outPath = join33(reviewDir, "issue.md");
20919
21415
  writeFileSync5(outPath, `${body}
20920
21416
  `, { encoding: "utf8", mode: 384, flag: "wx" });
20921
- return { path: outPath, realPath: realpathSync7(outPath) };
21417
+ return { path: outPath, realPath: realpathSync8(outPath) };
20922
21418
  } catch (err) {
20923
21419
  if (reviewDir) {
20924
21420
  try {
20925
- rmSync7(reviewDir, { recursive: true, force: true });
21421
+ rmSync8(reviewDir, { recursive: true, force: true });
20926
21422
  } catch {}
20927
21423
  }
20928
21424
  log2.error(`Failed to write sanitized issue report: ${err instanceof Error ? err.message : String(err)}`);
@@ -20931,7 +21427,7 @@ function writeIssueReviewFile(body) {
20931
21427
  }
20932
21428
  function readReviewedIssueFile(reviewFile) {
20933
21429
  try {
20934
- const realPath = realpathSync7(reviewFile.path);
21430
+ const realPath = realpathSync8(reviewFile.path);
20935
21431
  if (realPath !== reviewFile.realPath) {
20936
21432
  log2.error(`Review file path changed before filing; refusing to read ${reviewFile.path}.`);
20937
21433
  return null;
@@ -20980,6 +21476,31 @@ function collectIssueLog(path, selectedSession) {
20980
21476
  `)
20981
21477
  };
20982
21478
  }
21479
+ function collectIssueEngineLog(path, selectedSession) {
21480
+ const directory = dirname12(path);
21481
+ const files = [];
21482
+ try {
21483
+ for (const name of readdirSync13(directory)) {
21484
+ if (!/^aft-\d+\.log(?:\.1)?$/.test(name))
21485
+ continue;
21486
+ try {
21487
+ const candidate = join33(directory, name);
21488
+ const stat = lstatSync5(candidate);
21489
+ if (stat.isFile())
21490
+ files.push({ path: candidate, modified: stat.mtimeMs });
21491
+ } catch {}
21492
+ }
21493
+ } catch {}
21494
+ files.sort((a, b) => b.modified - a.modified || a.path.localeCompare(b.path));
21495
+ const result = readLogHistory(files.map((file) => file.path), selectedSession);
21496
+ return {
21497
+ logText: result.lines.join(`
21498
+ `),
21499
+ errors: result.errors.join(`
21500
+ `),
21501
+ notice: result.boundHit ? "[scan limit reached; older engine lines may be missing]" : ""
21502
+ };
21503
+ }
20983
21504
  async function runIssueFlow(argv) {
20984
21505
  intro(`${CLI} doctor --issue`);
20985
21506
  if (!isInteractiveTerminal()) {
@@ -20998,19 +21519,32 @@ async function runIssueFlow(argv) {
20998
21519
  const selectedSession = await promptForIssueSession(adapters[0]);
20999
21520
  const selectedBareSessionId = selectedSession?.id.replace(/^ses_/, "") ?? null;
21000
21521
  const report = await collectDiagnostics(adapters);
21001
- const logResults = adapters.map((adapter) => {
21522
+ const logResults = adapters.flatMap((adapter) => {
21002
21523
  const path = adapter.getLogFile();
21003
21524
  const { logText, errors, notice } = collectIssueLog(path, selectedSession);
21004
- return {
21005
- section: `#### ${adapter.displayName} log (${path})
21525
+ const engine = collectIssueEngineLog(path, selectedSession);
21526
+ return [
21527
+ {
21528
+ section: `#### ${adapter.displayName} log (${path})
21006
21529
 
21007
21530
  ${notice}
21008
21531
  \`\`\`
21009
21532
  ${logText || "<no log output>"}
21010
21533
  \`\`\`
21011
21534
  `,
21012
- errors
21013
- };
21535
+ errors
21536
+ },
21537
+ {
21538
+ section: `#### Engine logs (${dirname12(path)})
21539
+
21540
+ ${engine.notice}
21541
+ \`\`\`
21542
+ ${engine.logText || "<no log output>"}
21543
+ \`\`\`
21544
+ `,
21545
+ errors: engine.errors
21546
+ }
21547
+ ];
21014
21548
  });
21015
21549
  const logSections = logResults.map((result) => result.section).join(`
21016
21550
  `);
@@ -21039,7 +21573,7 @@ ${logText || "<no log output>"}
21039
21573
  "",
21040
21574
  toolFailuresSection,
21041
21575
  "",
21042
- selectedSession ? `## Logs (last 200 lines for session ${selectedSession.id}; project ${selectedSession.projectRoot ?? "unknown"}, ${selectedSession.startedAt !== undefined ? new Date(selectedSession.startedAt).toISOString() : "unknown start"}–${new Date(selectedSession.lastActivity).toISOString()})` : "## Logs (last 200 lines per harness)",
21576
+ selectedSession ? `## Logs (last 200 lines per source for session ${selectedSession.id}; project ${selectedSession.projectRoot ?? "unknown"}, ${selectedSession.startedAt !== undefined ? new Date(selectedSession.startedAt).toISOString() : "unknown start"}–${new Date(selectedSession.lastActivity).toISOString()})` : "## Logs (last 200 lines per source)",
21043
21577
  logSections,
21044
21578
  "_Usernames and home paths have been stripped from this report._"
21045
21579
  ].join(`
@@ -21092,6 +21626,7 @@ https://github.com/cortexkit/aft/issues/new and paste the contents of ${outPath}
21092
21626
  var DOCTOR_CLEAR_TARGET_OPTIONS, DOCTOR_FORCE_CLEAR_TARGETS;
21093
21627
  var init_doctor = __esm(async () => {
21094
21628
  init_dist2();
21629
+ init_adapters();
21095
21630
  init_opencode();
21096
21631
  init_features();
21097
21632
  init_opencode2();
@@ -21142,24 +21677,24 @@ var init_doctor = __esm(async () => {
21142
21677
  });
21143
21678
 
21144
21679
  // src/commands/lsp.ts
21145
- import { existsSync as existsSync31, readdirSync as readdirSync13, statSync as statSync20 } from "node:fs";
21680
+ import { existsSync as existsSync31, readdirSync as readdirSync14, statSync as statSync20 } from "node:fs";
21146
21681
  import { createRequire as createRequire5 } from "node:module";
21147
- import { dirname as dirname12, join as join32, resolve as resolve15 } from "node:path";
21682
+ import { dirname as dirname13, join as join34, resolve as resolve15 } from "node:path";
21148
21683
  function findProjectRootForFile(filePath, fallbackCwd = process.cwd()) {
21149
21684
  const resolvedFile = resolve15(fallbackCwd, filePath);
21150
- let dir = dirname12(resolvedFile);
21685
+ let dir = dirname13(resolvedFile);
21151
21686
  try {
21152
21687
  if (existsSync31(resolvedFile) && statSync20(resolvedFile).isDirectory()) {
21153
21688
  dir = resolvedFile;
21154
21689
  }
21155
21690
  } catch {
21156
- dir = dirname12(resolvedFile);
21691
+ dir = dirname13(resolvedFile);
21157
21692
  }
21158
21693
  while (true) {
21159
- if (PROJECT_ROOT_MARKERS.some((marker) => existsSync31(join32(dir, marker)))) {
21694
+ if (PROJECT_ROOT_MARKERS.some((marker) => existsSync31(join34(dir, marker)))) {
21160
21695
  return dir;
21161
21696
  }
21162
- const parent = dirname12(dir);
21697
+ const parent = dirname13(dir);
21163
21698
  if (parent === dir)
21164
21699
  return resolve15(fallbackCwd);
21165
21700
  dir = parent;
@@ -21305,10 +21840,10 @@ function buildConfigureParams(adapter, projectRoot) {
21305
21840
  function inferLspPathsExtra(_lsp) {
21306
21841
  const paths = new Set;
21307
21842
  for (const entry of childDirs(getAftLspPackagesDir())) {
21308
- paths.add(join32(entry, "node_modules", ".bin"));
21843
+ paths.add(join34(entry, "node_modules", ".bin"));
21309
21844
  }
21310
21845
  for (const entry of childDirs(getAftLspBinariesDir())) {
21311
- paths.add(join32(entry, "bin"));
21846
+ paths.add(join34(entry, "bin"));
21312
21847
  }
21313
21848
  return [...paths];
21314
21849
  }
@@ -21316,7 +21851,7 @@ function childDirs(path) {
21316
21851
  if (!existsSync31(path))
21317
21852
  return [];
21318
21853
  try {
21319
- return readdirSync13(path).map((entry) => join32(path, entry)).filter((entry) => {
21854
+ return readdirSync14(path).map((entry) => join34(path, entry)).filter((entry) => {
21320
21855
  try {
21321
21856
  return statSync20(entry).isDirectory();
21322
21857
  } catch {
@@ -21360,7 +21895,7 @@ function typescriptPackageWarning(response) {
21360
21895
  if (!typescriptServerSpawned || !response.project_root || diagnosticsCount > 0)
21361
21896
  return null;
21362
21897
  try {
21363
- createRequire5(join32(response.project_root, "package.json")).resolve("typescript");
21898
+ createRequire5(join34(response.project_root, "package.json")).resolve("typescript");
21364
21899
  return null;
21365
21900
  } catch {
21366
21901
  return "typescript package not resolvable from project — server will produce no diagnostics";
@@ -21660,7 +22195,7 @@ function printHelp() {
21660
22195
  console.log(" doctor Check and fix configuration issues");
21661
22196
  console.log(" doctor --profile [seconds] Profile a running AFT daemon");
21662
22197
  console.log(" doctor lsp <file> Inspect LSP setup for one file");
21663
- console.log(" doctor --fix Auto-fix common issues (e.g. ONNX Runtime mismatch, retired config keys)");
22198
+ console.log(" doctor --fix Auto-fix common issues (e.g. ONNX Runtime mismatch, retired config keys)", " doctor --clean-old-caches Confirm removal of old storage-root caches after moving them");
21664
22199
  console.log(" doctor --reconfigure Rerun the feature wizard");
21665
22200
  console.log(" --verbose Also print AFT's internal log lines (always kept in aft-cli.log)");
21666
22201
  console.log(" doctor --clear Select caches to clear with an interactive prompt");
@@ -21708,6 +22243,10 @@ async function main() {
21708
22243
  return runCache(args);
21709
22244
  }
21710
22245
  if (command === "doctor") {
22246
+ if (args.includes("--clean-old-caches")) {
22247
+ await init_clean_old_caches();
22248
+ return runOldCacheCleanup(args);
22249
+ }
21711
22250
  if (args.includes("--profile")) {
21712
22251
  await init_doctor();
21713
22252
  return runDoctorProfile(args);