@cortexkit/aft-opencode 0.26.0 → 0.26.1

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
@@ -7803,7 +7803,7 @@ var require_src2 = __commonJS((exports, module) => {
7803
7803
  });
7804
7804
 
7805
7805
  // src/index.ts
7806
- import { existsSync as existsSync14, mkdirSync as mkdirSync12, readFileSync as readFileSync12, writeFileSync as writeFileSync11 } from "fs";
7806
+ import { existsSync as existsSync14, mkdirSync as mkdirSync12, readFileSync as readFileSync12, writeFileSync as writeFileSync12 } from "fs";
7807
7807
  import { createRequire as createRequire2 } from "module";
7808
7808
  import { homedir as homedir11 } from "os";
7809
7809
  import { join as join21 } from "path";
@@ -7820,12 +7820,22 @@ function getActiveLogger() {
7820
7820
  return loggerGlobal()[ACTIVE_LOGGER_SYMBOL];
7821
7821
  }
7822
7822
  function getLogFilePath() {
7823
- return getActiveLogger()?.getLogFilePath?.();
7823
+ try {
7824
+ return getActiveLogger()?.getLogFilePath?.();
7825
+ } catch (err) {
7826
+ console.error(`[aft-bridge] ERROR: active logger getLogFilePath threw: ${err instanceof Error ? err.message : String(err)}`);
7827
+ return;
7828
+ }
7824
7829
  }
7825
7830
  function log(message, meta) {
7826
7831
  const active = getActiveLogger();
7827
7832
  if (active) {
7828
- active.log(message, meta);
7833
+ try {
7834
+ active.log(message, meta);
7835
+ } catch (err) {
7836
+ console.error(`[aft-bridge] ERROR: active logger log threw: ${err instanceof Error ? err.message : String(err)}`);
7837
+ console.error(`[aft-bridge] ${message}`);
7838
+ }
7829
7839
  } else {
7830
7840
  console.error(`[aft-bridge] ${message}`);
7831
7841
  }
@@ -7833,7 +7843,12 @@ function log(message, meta) {
7833
7843
  function warn(message, meta) {
7834
7844
  const active = getActiveLogger();
7835
7845
  if (active) {
7836
- active.warn(message, meta);
7846
+ try {
7847
+ active.warn(message, meta);
7848
+ } catch (err) {
7849
+ console.error(`[aft-bridge] ERROR: active logger warn threw: ${err instanceof Error ? err.message : String(err)}`);
7850
+ console.error(`[aft-bridge] WARN: ${message}`);
7851
+ }
7837
7852
  } else {
7838
7853
  console.error(`[aft-bridge] WARN: ${message}`);
7839
7854
  }
@@ -7841,20 +7856,16 @@ function warn(message, meta) {
7841
7856
  function error(message, meta) {
7842
7857
  const active = getActiveLogger();
7843
7858
  if (active) {
7844
- active.error(message, meta);
7859
+ try {
7860
+ active.error(message, meta);
7861
+ } catch (err) {
7862
+ console.error(`[aft-bridge] ERROR: active logger error threw: ${err instanceof Error ? err.message : String(err)}`);
7863
+ console.error(`[aft-bridge] ERROR: ${message}`);
7864
+ }
7845
7865
  } else {
7846
7866
  console.error(`[aft-bridge] ERROR: ${message}`);
7847
7867
  }
7848
7868
  }
7849
- function sessionLog(sessionId, message) {
7850
- log(message, sessionId ? { sessionId } : undefined);
7851
- }
7852
- function sessionWarn(sessionId, message) {
7853
- warn(message, sessionId ? { sessionId } : undefined);
7854
- }
7855
- function sessionError(sessionId, message) {
7856
- error(message, sessionId ? { sessionId } : undefined);
7857
- }
7858
7869
  // ../aft-bridge/dist/bridge.js
7859
7870
  import { spawn } from "child_process";
7860
7871
  import { homedir } from "os";
@@ -7949,6 +7960,7 @@ class BinaryBridge {
7949
7960
  pending = new Map;
7950
7961
  nextId = 1;
7951
7962
  stdoutBuffer = "";
7963
+ stderrBuffer = "";
7952
7964
  stderrTail = [];
7953
7965
  _restartCount = 0;
7954
7966
  _shuttingDown = false;
@@ -7984,24 +7996,62 @@ class BinaryBridge {
7984
7996
  }
7985
7997
  logVia(message, meta) {
7986
7998
  const logger = this.logger ?? getActiveLogger();
7987
- if (logger)
7988
- logger.log(message, meta);
7989
- else
7999
+ if (logger) {
8000
+ try {
8001
+ logger.log(message, meta);
8002
+ } catch (err) {
8003
+ console.error(`[aft-bridge] ERROR: logger log threw: ${err instanceof Error ? err.message : String(err)}`);
8004
+ console.error(`[aft-bridge] ${message}`);
8005
+ }
8006
+ } else {
7990
8007
  log(message, meta);
8008
+ }
7991
8009
  }
7992
8010
  warnVia(message, meta) {
7993
8011
  const logger = this.logger ?? getActiveLogger();
7994
- if (logger)
7995
- logger.warn(message, meta);
7996
- else
8012
+ if (logger) {
8013
+ try {
8014
+ logger.warn(message, meta);
8015
+ } catch (err) {
8016
+ console.error(`[aft-bridge] ERROR: logger warn threw: ${err instanceof Error ? err.message : String(err)}`);
8017
+ console.error(`[aft-bridge] WARN: ${message}`);
8018
+ }
8019
+ } else {
7997
8020
  warn(message, meta);
8021
+ }
7998
8022
  }
7999
8023
  errorVia(message, meta) {
8000
8024
  const logger = this.logger ?? getActiveLogger();
8001
- if (logger)
8002
- logger.error(message, meta);
8003
- else
8025
+ if (logger) {
8026
+ try {
8027
+ logger.error(message, meta);
8028
+ } catch (err) {
8029
+ console.error(`[aft-bridge] ERROR: logger error threw: ${err instanceof Error ? err.message : String(err)}`);
8030
+ console.error(`[aft-bridge] ERROR: ${message}`);
8031
+ }
8032
+ } else {
8004
8033
  error(message, meta);
8034
+ }
8035
+ }
8036
+ getLogFilePathVia() {
8037
+ if (this.logger?.getLogFilePath) {
8038
+ try {
8039
+ return this.logger.getLogFilePath();
8040
+ } catch (err) {
8041
+ console.error(`[aft-bridge] ERROR: logger getLogFilePath threw: ${err instanceof Error ? err.message : String(err)}`);
8042
+ return;
8043
+ }
8044
+ }
8045
+ return getLogFilePath();
8046
+ }
8047
+ sessionLogVia(sessionId, message) {
8048
+ this.logVia(message, sessionId ? { sessionId } : undefined);
8049
+ }
8050
+ sessionWarnVia(sessionId, message) {
8051
+ this.warnVia(message, sessionId ? { sessionId } : undefined);
8052
+ }
8053
+ sessionErrorVia(sessionId, message) {
8054
+ this.errorVia(message, sessionId ? { sessionId } : undefined);
8005
8055
  }
8006
8056
  get restartCount() {
8007
8057
  return this._restartCount;
@@ -8060,7 +8110,7 @@ class BinaryBridge {
8060
8110
  await this.deliverConfigureWarnings(configResult, params, options);
8061
8111
  await this.checkVersion();
8062
8112
  if (!this.isAlive()) {
8063
- throw new Error(`${this.errorPrefix} Bridge died during version check. Check logs: ${getLogFilePath()}`);
8113
+ throw new Error(`${this.errorPrefix} Bridge died during version check. Check logs: ${this.getLogFilePathVia()}`);
8064
8114
  }
8065
8115
  this.configured = true;
8066
8116
  } finally {
@@ -8096,9 +8146,9 @@ class BinaryBridge {
8096
8146
  const restartSuffix = keepBridgeOnTimeout ? "" : " \u2014 restarting bridge";
8097
8147
  const timeoutMsg = `Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms${restartSuffix}`;
8098
8148
  if (requestSessionId) {
8099
- sessionWarn(requestSessionId, timeoutMsg);
8149
+ this.sessionWarnVia(requestSessionId, timeoutMsg);
8100
8150
  } else {
8101
- warn(timeoutMsg);
8151
+ this.warnVia(timeoutMsg);
8102
8152
  }
8103
8153
  reject(new Error(`${this.errorPrefix} Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
8104
8154
  if (!keepBridgeOnTimeout) {
@@ -8125,7 +8175,7 @@ class BinaryBridge {
8125
8175
  });
8126
8176
  } catch (err) {
8127
8177
  if (err instanceof BridgeReplacedDuringVersionCheck && canRetryAfterVersionSwap && command !== "configure" && command !== "version") {
8128
- log(`Retrying request "${command}" once after coordinated binary replacement: ${err.newBinaryPath}`);
8178
+ this.logVia(`Retrying request "${command}" once after coordinated binary replacement: ${err.newBinaryPath}`);
8129
8179
  return this.sendWithVersionMismatchRetry(command, params, options, false);
8130
8180
  }
8131
8181
  throw err;
@@ -8145,7 +8195,7 @@ class BinaryBridge {
8145
8195
  warnings: configResult.warnings
8146
8196
  });
8147
8197
  } catch (err) {
8148
- warn(`configure warning delivery failed: ${err instanceof Error ? err.message : String(err)}`);
8198
+ this.warnVia(`configure warning delivery failed: ${err instanceof Error ? err.message : String(err)}`);
8149
8199
  } finally {
8150
8200
  if (sessionId) {
8151
8201
  this.configureWarningClients.delete(sessionId);
@@ -8179,7 +8229,7 @@ class BinaryBridge {
8179
8229
  if (!snapshot || typeof snapshot !== "object" || Array.isArray(snapshot))
8180
8230
  return;
8181
8231
  this.cachedStatus = snapshot;
8182
- log("Received status_changed push frame; cached AFT status snapshot");
8232
+ this.logVia("Received status_changed push frame; cached AFT status snapshot");
8183
8233
  for (const listener of this.statusListeners) {
8184
8234
  this.deliverStatusSnapshot(listener, this.cachedStatus);
8185
8235
  }
@@ -8188,7 +8238,7 @@ class BinaryBridge {
8188
8238
  try {
8189
8239
  listener(snapshot);
8190
8240
  } catch (err) {
8191
- warn(`status listener threw: ${err instanceof Error ? err.message : String(err)}`);
8241
+ this.warnVia(`status listener threw: ${err instanceof Error ? err.message : String(err)}`);
8192
8242
  }
8193
8243
  }
8194
8244
  async shutdown() {
@@ -8206,7 +8256,7 @@ class BinaryBridge {
8206
8256
  }, 5000);
8207
8257
  proc.once("exit", () => {
8208
8258
  clearTimeout(forceKillTimer);
8209
- log("Process exited during shutdown");
8259
+ this.logVia("Process exited during shutdown");
8210
8260
  resolve();
8211
8261
  });
8212
8262
  proc.kill("SIGTERM");
@@ -8225,9 +8275,9 @@ class BinaryBridge {
8225
8275
  if (typeof binaryVersion !== "string") {
8226
8276
  throw new Error(`Binary did not report a version \u2014 likely too old (minVersion: ${this.minVersion})`);
8227
8277
  }
8228
- log(`Binary version: ${binaryVersion}`);
8278
+ this.logVia(`Binary version: ${binaryVersion}`);
8229
8279
  if (compareSemver(binaryVersion, this.minVersion) < 0) {
8230
- warn(`Binary version ${binaryVersion} is older than required ${this.minVersion}`);
8280
+ this.warnVia(`Binary version ${binaryVersion} is older than required ${this.minVersion}`);
8231
8281
  const replacementPath = await this.onVersionMismatch?.(binaryVersion, this.minVersion);
8232
8282
  if (replacementPath === undefined) {
8233
8283
  return;
@@ -8239,7 +8289,7 @@ class BinaryBridge {
8239
8289
  throw new BridgeReplacedDuringVersionCheck(replacementPath);
8240
8290
  }
8241
8291
  } catch (err) {
8242
- warn(`Version check failed: ${err.message}`);
8292
+ this.warnVia(`Version check failed: ${err.message}`);
8243
8293
  throw err;
8244
8294
  }
8245
8295
  }
@@ -8259,7 +8309,7 @@ class BinaryBridge {
8259
8309
  }, 5000);
8260
8310
  proc.once("exit", () => {
8261
8311
  clearTimeout(forceKillTimer);
8262
- log("Process exited during coordinated binary replacement");
8312
+ this.logVia("Process exited during coordinated binary replacement");
8263
8313
  resolve();
8264
8314
  });
8265
8315
  proc.kill("SIGTERM");
@@ -8272,9 +8322,9 @@ class BinaryBridge {
8272
8322
  }
8273
8323
  spawnProcess(triggeringSessionId) {
8274
8324
  if (triggeringSessionId) {
8275
- sessionLog(triggeringSessionId, `Spawning binary: ${this.binaryPath} (cwd: ${this.cwd})`);
8325
+ this.sessionLogVia(triggeringSessionId, `Spawning binary: ${this.binaryPath} (cwd: ${this.cwd})`);
8276
8326
  } else {
8277
- log(`Spawning binary: ${this.binaryPath} (cwd: ${this.cwd})`);
8327
+ this.logVia(`Spawning binary: ${this.binaryPath} (cwd: ${this.cwd})`);
8278
8328
  }
8279
8329
  const semantic = this.configOverrides.semantic;
8280
8330
  const semanticBackend = (() => {
@@ -8321,11 +8371,12 @@ class BinaryBridge {
8321
8371
  const remaining = stderrDecoder.end();
8322
8372
  if (remaining)
8323
8373
  this.onStderrData(remaining);
8374
+ this.flushStderrBuffer();
8324
8375
  });
8325
8376
  child.on("error", (err) => {
8326
8377
  if (this.process !== currentChild)
8327
8378
  return;
8328
- error(`Process error: ${err.message}${this.formatStderrTail()}`);
8379
+ this.errorVia(`Process error: ${err.message}${this.formatStderrTail()}`);
8329
8380
  this.handleCrash();
8330
8381
  });
8331
8382
  child.on("exit", (code, signal) => {
@@ -8333,7 +8384,7 @@ class BinaryBridge {
8333
8384
  return;
8334
8385
  if (this._shuttingDown)
8335
8386
  return;
8336
- log(`Process exited: code=${code}, signal=${signal}`);
8387
+ this.logVia(`Process exited: code=${code}, signal=${signal}`);
8337
8388
  if (signal === "SIGTERM" || signal === "SIGKILL" || signal === "SIGHUP" || signal === "SIGINT") {
8338
8389
  this.process = null;
8339
8390
  this.configured = false;
@@ -8345,6 +8396,7 @@ class BinaryBridge {
8345
8396
  });
8346
8397
  this.process = child;
8347
8398
  this.stdoutBuffer = "";
8399
+ this.stderrBuffer = "";
8348
8400
  this.stderrTail = [];
8349
8401
  }
8350
8402
  pushStderrLine(line) {
@@ -8354,16 +8406,28 @@ class BinaryBridge {
8354
8406
  }
8355
8407
  }
8356
8408
  onStderrData(data) {
8357
- const lines = data.trimEnd().split(`
8358
- `);
8359
- for (const line of lines) {
8409
+ this.stderrBuffer += data;
8410
+ let newlineIdx;
8411
+ while ((newlineIdx = this.stderrBuffer.indexOf(`
8412
+ `)) !== -1) {
8413
+ const line = this.stderrBuffer.slice(0, newlineIdx).replace(/\r$/, "");
8414
+ this.stderrBuffer = this.stderrBuffer.slice(newlineIdx + 1);
8360
8415
  if (!line)
8361
8416
  continue;
8362
8417
  const tagged = tagStderrLine(line);
8363
- log(tagged);
8418
+ this.logVia(tagged);
8364
8419
  this.pushStderrLine(tagged);
8365
8420
  }
8366
8421
  }
8422
+ flushStderrBuffer() {
8423
+ const line = this.stderrBuffer.replace(/\r$/, "");
8424
+ this.stderrBuffer = "";
8425
+ if (!line)
8426
+ return;
8427
+ const tagged = tagStderrLine(line);
8428
+ this.logVia(tagged);
8429
+ this.pushStderrLine(tagged);
8430
+ }
8367
8431
  formatStderrTail() {
8368
8432
  if (this.stderrTail.length === 0)
8369
8433
  return "";
@@ -8421,7 +8485,7 @@ class BinaryBridge {
8421
8485
  }
8422
8486
  if (response.type === "configure_warnings") {
8423
8487
  this.handleConfigureWarningsFrame(response).catch((err) => {
8424
- warn(`configure warning delivery failed: ${err instanceof Error ? err.message : String(err)}`);
8488
+ this.warnVia(`configure warning delivery failed: ${err instanceof Error ? err.message : String(err)}`);
8425
8489
  });
8426
8490
  continue;
8427
8491
  }
@@ -8439,10 +8503,10 @@ class BinaryBridge {
8439
8503
  this.scheduleRestartCountReset();
8440
8504
  entry.resolve(response);
8441
8505
  } else if (typeof response.type === "string") {
8442
- log(`Ignoring unknown stdout push frame type: ${response.type}`);
8506
+ this.logVia(`Ignoring unknown stdout push frame type: ${response.type}`);
8443
8507
  }
8444
8508
  } catch (_err) {
8445
- warn(`Failed to parse stdout line: ${line}`);
8509
+ this.warnVia(`Failed to parse stdout line: ${line}`);
8446
8510
  }
8447
8511
  }
8448
8512
  }
@@ -8456,17 +8520,17 @@ class BinaryBridge {
8456
8520
  this.configured = false;
8457
8521
  const tail = this.formatStderrTail();
8458
8522
  this.stderrTail = [];
8459
- const killedMsg = tail ? `Bridge killed after timeout.${tail}` : `Bridge killed after timeout (see ${getLogFilePath()})`;
8523
+ const killedMsg = tail ? `Bridge killed after timeout.${tail}` : `Bridge killed after timeout (see ${this.getLogFilePathVia()})`;
8460
8524
  if (tail) {
8461
8525
  if (triggeringSessionId) {
8462
- sessionError(triggeringSessionId, killedMsg);
8526
+ this.sessionErrorVia(triggeringSessionId, killedMsg);
8463
8527
  } else {
8464
- error(killedMsg);
8528
+ this.errorVia(killedMsg);
8465
8529
  }
8466
8530
  } else if (triggeringSessionId) {
8467
- sessionWarn(triggeringSessionId, killedMsg);
8531
+ this.sessionWarnVia(triggeringSessionId, killedMsg);
8468
8532
  } else {
8469
- warn(killedMsg);
8533
+ this.warnVia(killedMsg);
8470
8534
  }
8471
8535
  }
8472
8536
  handleCrash(cause) {
@@ -8479,25 +8543,25 @@ class BinaryBridge {
8479
8543
  this.configured = false;
8480
8544
  const tail = this.formatStderrTail();
8481
8545
  if (tail) {
8482
- error(`Binary crashed (restarts: ${this._restartCount})${cause ? `: ${cause.message}` : ""}.${tail}`);
8546
+ this.errorVia(`Binary crashed (restarts: ${this._restartCount})${cause ? `: ${cause.message}` : ""}.${tail}`);
8483
8547
  }
8484
- this.rejectAllPending(new Error(`${this.errorPrefix} Binary crashed (restarts: ${this._restartCount})${cause ? `: ${cause.message}` : ""} (see ${getLogFilePath()})`));
8548
+ this.rejectAllPending(new Error(`${this.errorPrefix} Binary crashed (restarts: ${this._restartCount})${cause ? `: ${cause.message}` : ""} (see ${this.getLogFilePathVia()})`));
8485
8549
  if (this._restartCount < this.maxRestarts) {
8486
8550
  const delay = 100 * 2 ** this._restartCount;
8487
8551
  this._restartCount++;
8488
- log(`Auto-restart #${this._restartCount} in ${delay}ms`);
8552
+ this.logVia(`Auto-restart #${this._restartCount} in ${delay}ms`);
8489
8553
  setTimeout(() => {
8490
8554
  if (!this._shuttingDown && !this.isAlive()) {
8491
8555
  try {
8492
8556
  this.spawnProcess();
8493
8557
  } catch (err) {
8494
- error(`Failed to restart: ${err.message}`);
8558
+ this.errorVia(`Failed to restart: ${err.message}`);
8495
8559
  }
8496
8560
  }
8497
8561
  }, delay);
8498
8562
  this.scheduleRestartCountReset();
8499
8563
  } else {
8500
- error(`Max restarts (${this.maxRestarts}) reached, giving up. Logs: ${getLogFilePath()}${tail}`);
8564
+ this.errorVia(`Max restarts (${this.maxRestarts}) reached, giving up. Logs: ${this.getLogFilePathVia()}${tail}`);
8501
8565
  this.scheduleRestartCountReset();
8502
8566
  }
8503
8567
  }
@@ -8523,9 +8587,12 @@ class BinaryBridge {
8523
8587
  }
8524
8588
  }
8525
8589
  // ../aft-bridge/dist/downloader.js
8526
- import { chmodSync, existsSync, mkdirSync, unlinkSync } from "fs";
8590
+ import { createHash } from "crypto";
8591
+ import { chmodSync, closeSync, createWriteStream, existsSync, mkdirSync, openSync, renameSync, rmSync, statSync, unlinkSync } from "fs";
8527
8592
  import { homedir as homedir2 } from "os";
8528
8593
  import { join as join2 } from "path";
8594
+ import { Readable } from "stream";
8595
+ import { pipeline } from "stream/promises";
8529
8596
 
8530
8597
  // ../aft-bridge/dist/platform.js
8531
8598
  var PLATFORM_ARCH_MAP = {
@@ -8543,6 +8610,11 @@ var PLATFORM_ASSET_MAP = {
8543
8610
 
8544
8611
  // ../aft-bridge/dist/downloader.js
8545
8612
  var REPO = "cortexkit/aft";
8613
+ var DOWNLOAD_TIMEOUT_MS = 300000;
8614
+ var LATEST_TAG_TIMEOUT_MS = 30000;
8615
+ var MAX_DOWNLOAD_BYTES = 200 * 1024 * 1024;
8616
+ var DOWNLOAD_LOCK_TIMEOUT_MS = 120000;
8617
+ var DOWNLOAD_LOCK_STALE_MS = 10 * 60000;
8546
8618
  function getCacheDir() {
8547
8619
  if (process.platform === "win32") {
8548
8620
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
@@ -8584,54 +8656,101 @@ async function downloadBinary(version) {
8584
8656
  const downloadUrl = `https://github.com/${REPO}/releases/download/${tag}/${assetName}`;
8585
8657
  const checksumUrl = `https://github.com/${REPO}/releases/download/${tag}/checksums.sha256`;
8586
8658
  log(`Downloading AFT binary (${tag}) for ${platformKey}...`);
8659
+ const lockPath = join2(versionedCacheDir, ".download.lock");
8660
+ let releaseLock = null;
8661
+ let binaryController = null;
8662
+ let checksumController = null;
8663
+ let binaryTimeout = null;
8664
+ let checksumTimeout = null;
8665
+ const tmpPath = `${binaryPath}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`;
8587
8666
  try {
8588
8667
  if (!existsSync(versionedCacheDir)) {
8589
8668
  mkdirSync(versionedCacheDir, { recursive: true });
8590
8669
  }
8670
+ releaseLock = await acquireDownloadLock(lockPath);
8671
+ if (existsSync(binaryPath)) {
8672
+ return binaryPath;
8673
+ }
8674
+ binaryController = new AbortController;
8675
+ checksumController = new AbortController;
8676
+ const activeBinaryController = binaryController;
8677
+ const activeChecksumController = checksumController;
8678
+ binaryTimeout = setTimeout(() => activeBinaryController.abort(), DOWNLOAD_TIMEOUT_MS);
8679
+ checksumTimeout = setTimeout(() => activeChecksumController.abort(), DOWNLOAD_TIMEOUT_MS);
8591
8680
  const [binaryResponse, checksumResponse] = await Promise.all([
8592
- fetch(downloadUrl, { redirect: "follow" }),
8593
- fetch(checksumUrl, { redirect: "follow" })
8681
+ fetch(downloadUrl, { redirect: "follow", signal: activeBinaryController.signal }),
8682
+ fetch(checksumUrl, { redirect: "follow", signal: activeChecksumController.signal })
8594
8683
  ]);
8595
8684
  if (!binaryResponse.ok) {
8596
8685
  throw new Error(`HTTP ${binaryResponse.status}: ${binaryResponse.statusText} (${downloadUrl})`);
8597
8686
  }
8598
- const arrayBuffer = await binaryResponse.arrayBuffer();
8687
+ if (!binaryResponse.body) {
8688
+ throw new Error(`Download response for ${assetName} had no body`);
8689
+ }
8690
+ const advertised = Number.parseInt(binaryResponse.headers.get("content-length") ?? "", 10);
8691
+ if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES) {
8692
+ throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES}`);
8693
+ }
8599
8694
  if (!checksumResponse.ok) {
8600
8695
  warn(`Checksum verification failed: no checksums.sha256 found for ${tag}. ` + "Binary download aborted for security reasons.");
8601
8696
  return null;
8602
8697
  }
8603
8698
  const checksumText = await checksumResponse.text();
8699
+ clearTimeout(checksumTimeout);
8700
+ checksumTimeout = null;
8604
8701
  const expectedHash = parseChecksumForAsset(checksumText, assetName);
8605
8702
  if (!expectedHash) {
8606
8703
  warn(`Checksum verification failed: checksums.sha256 found but no entry for ${assetName}. ` + "Binary download aborted for security reasons.");
8607
8704
  return null;
8608
8705
  }
8609
- const { createHash } = await import("crypto");
8610
- const actualHash = createHash("sha256").update(Buffer.from(arrayBuffer)).digest("hex");
8706
+ const hash = createHash("sha256");
8707
+ let bytesWritten = 0;
8708
+ const guard = new TransformStream({
8709
+ transform(chunk, controller) {
8710
+ bytesWritten += chunk.byteLength;
8711
+ if (bytesWritten > MAX_DOWNLOAD_BYTES) {
8712
+ controller.error(new Error(`download exceeded ${MAX_DOWNLOAD_BYTES} bytes after streaming (server lied about size or sent unbounded body)`));
8713
+ return;
8714
+ }
8715
+ hash.update(chunk);
8716
+ controller.enqueue(chunk);
8717
+ }
8718
+ });
8719
+ const guarded = binaryResponse.body.pipeThrough(guard);
8720
+ const nodeStream = Readable.fromWeb(guarded);
8721
+ await pipeline(nodeStream, createWriteStream(tmpPath), { signal: binaryController.signal });
8722
+ clearTimeout(binaryTimeout);
8723
+ binaryTimeout = null;
8724
+ const actualHash = hash.digest("hex");
8611
8725
  if (actualHash !== expectedHash) {
8612
- throw new Error(`Checksum mismatch for ${assetName}: expected ${expectedHash}, got ${actualHash}. The binary may have been tampered with.`);
8726
+ throw new Error(`Checksum mismatch for ${assetName}: expected ${expectedHash}, got ${actualHash}. ` + "The binary may have been tampered with.");
8613
8727
  }
8614
8728
  log(`Checksum verified (SHA-256: ${actualHash.slice(0, 16)}...)`);
8615
- const tmpPath = `${binaryPath}.tmp`;
8616
- const { writeFileSync } = await import("fs");
8617
- writeFileSync(tmpPath, Buffer.from(arrayBuffer));
8618
8729
  if (process.platform !== "win32") {
8619
8730
  chmodSync(tmpPath, 493);
8620
8731
  }
8621
- const { renameSync } = await import("fs");
8622
8732
  renameSync(tmpPath, binaryPath);
8623
8733
  log(`AFT binary ready at ${binaryPath}`);
8624
8734
  return binaryPath;
8625
8735
  } catch (err) {
8626
8736
  const msg = err instanceof Error ? err.message : String(err);
8627
8737
  error(`Failed to download AFT binary: ${msg}`);
8628
- const tmpPath = `${binaryPath}.tmp`;
8629
8738
  if (existsSync(tmpPath)) {
8630
8739
  try {
8631
8740
  unlinkSync(tmpPath);
8632
8741
  } catch {}
8633
8742
  }
8634
8743
  return null;
8744
+ } finally {
8745
+ if (binaryTimeout) {
8746
+ binaryController?.abort();
8747
+ clearTimeout(binaryTimeout);
8748
+ }
8749
+ if (checksumTimeout) {
8750
+ checksumController?.abort();
8751
+ clearTimeout(checksumTimeout);
8752
+ }
8753
+ releaseLock?.();
8635
8754
  }
8636
8755
  }
8637
8756
  async function ensureBinary(version) {
@@ -8648,6 +8767,39 @@ async function ensureBinary(version) {
8648
8767
  log("No cached binary found, downloading latest...");
8649
8768
  return downloadBinary();
8650
8769
  }
8770
+ async function acquireDownloadLock(lockPath) {
8771
+ const startedAt = Date.now();
8772
+ while (true) {
8773
+ try {
8774
+ const fd = openSync(lockPath, "wx");
8775
+ return () => {
8776
+ try {
8777
+ closeSync(fd);
8778
+ } catch {}
8779
+ try {
8780
+ rmSync(lockPath, { force: true });
8781
+ } catch {}
8782
+ };
8783
+ } catch (err) {
8784
+ const code = err.code;
8785
+ if (code !== "EEXIST")
8786
+ throw err;
8787
+ try {
8788
+ const ageMs = Date.now() - statSync(lockPath).mtimeMs;
8789
+ if (ageMs > DOWNLOAD_LOCK_STALE_MS) {
8790
+ rmSync(lockPath, { force: true });
8791
+ continue;
8792
+ }
8793
+ } catch {
8794
+ continue;
8795
+ }
8796
+ if (Date.now() - startedAt > DOWNLOAD_LOCK_TIMEOUT_MS) {
8797
+ throw new Error(`Timed out waiting for download lock: ${lockPath}`);
8798
+ }
8799
+ await new Promise((resolve) => setTimeout(resolve, 100));
8800
+ }
8801
+ }
8802
+ }
8651
8803
  function parseChecksumForAsset(checksumText, assetName) {
8652
8804
  for (const line of checksumText.split(`
8653
8805
  `)) {
@@ -8662,9 +8814,12 @@ function parseChecksumForAsset(checksumText, assetName) {
8662
8814
  return null;
8663
8815
  }
8664
8816
  async function fetchLatestTag() {
8817
+ const controller = new AbortController;
8818
+ const timeout = setTimeout(() => controller.abort(), LATEST_TAG_TIMEOUT_MS);
8665
8819
  try {
8666
8820
  const response = await fetch(`https://api.github.com/repos/${REPO}/releases/latest`, {
8667
- headers: { Accept: "application/vnd.github.v3+json" }
8821
+ headers: { Accept: "application/vnd.github.v3+json" },
8822
+ signal: controller.signal
8668
8823
  });
8669
8824
  if (!response.ok)
8670
8825
  return null;
@@ -8672,18 +8827,20 @@ async function fetchLatestTag() {
8672
8827
  return data.tag_name ?? null;
8673
8828
  } catch {
8674
8829
  return null;
8830
+ } finally {
8831
+ clearTimeout(timeout);
8675
8832
  }
8676
8833
  }
8677
8834
  // ../aft-bridge/dist/onnx-runtime.js
8678
8835
  import { execFileSync } from "child_process";
8679
- import { createHash } from "crypto";
8680
- import { chmodSync as chmodSync2, closeSync, copyFileSync, createWriteStream, existsSync as existsSync2, lstatSync, mkdirSync as mkdirSync2, openSync, readdirSync, readFileSync, readlinkSync, realpathSync, rmSync, statSync, symlinkSync, unlinkSync as unlinkSync2, writeFileSync } from "fs";
8836
+ import { createHash as createHash2 } from "crypto";
8837
+ import { chmodSync as chmodSync2, closeSync as closeSync2, copyFileSync, createWriteStream as createWriteStream2, existsSync as existsSync2, lstatSync, mkdirSync as mkdirSync2, openSync as openSync2, readdirSync, readFileSync, readlinkSync, realpathSync, rmSync as rmSync2, statSync as statSync2, symlinkSync, unlinkSync as unlinkSync2, writeFileSync } from "fs";
8681
8838
  import { dirname, join as join3, relative, resolve } from "path";
8682
- import { Readable } from "stream";
8683
- import { pipeline } from "stream/promises";
8839
+ import { Readable as Readable2 } from "stream";
8840
+ import { pipeline as pipeline2 } from "stream/promises";
8684
8841
  var ORT_VERSION = "1.24.4";
8685
8842
  var ORT_REPO = "microsoft/onnxruntime";
8686
- var MAX_DOWNLOAD_BYTES = 256 * 1024 * 1024;
8843
+ var MAX_DOWNLOAD_BYTES2 = 256 * 1024 * 1024;
8687
8844
  var MAX_EXTRACT_BYTES = 1 * 1024 * 1024 * 1024;
8688
8845
  var ONNX_LOCK_FILE = ".aft-onnx-installing";
8689
8846
  var ONNX_INSTALLED_META_FILE = ".aft-onnx-installed";
@@ -8804,7 +8961,7 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
8804
8961
  if (Number.isFinite(pid) && pid > 0) {
8805
8962
  if (process.platform === "win32") {
8806
8963
  try {
8807
- const ageMs = Date.now() - statSync(stagingDir).mtimeMs;
8964
+ const ageMs = Date.now() - statSync2(stagingDir).mtimeMs;
8808
8965
  abandoned = ageMs > STALE_LOCK_MS;
8809
8966
  } catch {
8810
8967
  abandoned = true;
@@ -8818,7 +8975,7 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
8818
8975
  if (abandoned) {
8819
8976
  log(`[onnx] removing abandoned staging dir ${stagingDir}`);
8820
8977
  try {
8821
- rmSync(stagingDir, { recursive: true, force: true });
8978
+ rmSync2(stagingDir, { recursive: true, force: true });
8822
8979
  } catch (err) {
8823
8980
  warn(`[onnx] failed to remove ${stagingDir}: ${err}`);
8824
8981
  }
@@ -8830,7 +8987,7 @@ function cleanupIncompleteTargetIfUnowned(ortDir) {
8830
8987
  try {
8831
8988
  if (existsSync2(ortDir) && !existsSync2(join3(ortDir, ONNX_INSTALLED_META_FILE))) {
8832
8989
  log(`[onnx] removing half-populated install dir ${ortDir} (no meta file)`);
8833
- rmSync(ortDir, { recursive: true, force: true });
8990
+ rmSync2(ortDir, { recursive: true, force: true });
8834
8991
  }
8835
8992
  } catch (err) {
8836
8993
  warn(`[onnx] failed to sweep ${ortDir}: ${err}`);
@@ -8910,24 +9067,24 @@ async function downloadFileWithCap(url, destPath) {
8910
9067
  throw new Error(`download failed (HTTP ${res.status})`);
8911
9068
  }
8912
9069
  const advertised = Number.parseInt(res.headers.get("content-length") ?? "", 10);
8913
- if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES) {
8914
- throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES}`);
9070
+ if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES2) {
9071
+ throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES2}`);
8915
9072
  }
8916
9073
  mkdirSync2(dirname(destPath), { recursive: true });
8917
9074
  let bytesWritten = 0;
8918
9075
  const guard = new TransformStream({
8919
9076
  transform(chunk, transformController) {
8920
9077
  bytesWritten += chunk.byteLength;
8921
- if (bytesWritten > MAX_DOWNLOAD_BYTES) {
8922
- transformController.error(new Error(`download exceeded ${MAX_DOWNLOAD_BYTES} bytes after streaming (server lied about size or sent unbounded body)`));
9078
+ if (bytesWritten > MAX_DOWNLOAD_BYTES2) {
9079
+ transformController.error(new Error(`download exceeded ${MAX_DOWNLOAD_BYTES2} bytes after streaming (server lied about size or sent unbounded body)`));
8923
9080
  return;
8924
9081
  }
8925
9082
  transformController.enqueue(chunk);
8926
9083
  }
8927
9084
  });
8928
9085
  const guarded = res.body.pipeThrough(guard);
8929
- const nodeStream = Readable.fromWeb(guarded);
8930
- await pipeline(nodeStream, createWriteStream(destPath), { signal: controller.signal });
9086
+ const nodeStream = Readable2.fromWeb(guarded);
9087
+ await pipeline2(nodeStream, createWriteStream2(destPath), { signal: controller.signal });
8931
9088
  } catch (err) {
8932
9089
  try {
8933
9090
  unlinkSync2(destPath);
@@ -9026,16 +9183,16 @@ async function downloadOnnxRuntime(info, targetDir) {
9026
9183
  warn(`Could not hash newly-installed ONNX library at ${libPath}: ${err}`);
9027
9184
  }
9028
9185
  writeOnnxInstalledMeta(targetDir, ORT_VERSION, libHash, archiveSha256);
9029
- rmSync(tmpDir, { recursive: true, force: true });
9186
+ rmSync2(tmpDir, { recursive: true, force: true });
9030
9187
  log(`ONNX Runtime v${ORT_VERSION} installed to ${targetDir}`);
9031
9188
  return targetDir;
9032
9189
  } catch (err) {
9033
9190
  error(`Failed to download ONNX Runtime: ${err}`);
9034
9191
  try {
9035
- rmSync(tmpDir, { recursive: true, force: true });
9192
+ rmSync2(tmpDir, { recursive: true, force: true });
9036
9193
  } catch {}
9037
9194
  try {
9038
- rmSync(targetDir, { recursive: true, force: true });
9195
+ rmSync2(targetDir, { recursive: true, force: true });
9039
9196
  } catch {}
9040
9197
  return null;
9041
9198
  }
@@ -9052,7 +9209,7 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
9052
9209
  }
9053
9210
  } catch (copyErr) {
9054
9211
  if (requiredLibs.has(libFile)) {
9055
- rmSync(targetDir, { recursive: true, force: true });
9212
+ rmSync2(targetDir, { recursive: true, force: true });
9056
9213
  throw copyErr;
9057
9214
  }
9058
9215
  log(`ORT extract: failed to copy optional ${libFile}: ${copyErr}`);
@@ -9067,7 +9224,7 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
9067
9224
  symlinkSync(link.target, dst);
9068
9225
  } catch (symlinkErr) {
9069
9226
  if (requiredLibs.has(link.name)) {
9070
- rmSync(targetDir, { recursive: true, force: true });
9227
+ rmSync2(targetDir, { recursive: true, force: true });
9071
9228
  throw symlinkErr;
9072
9229
  }
9073
9230
  log(`ORT extract: failed to symlink optional ${link.name}: ${symlinkErr}`);
@@ -9075,7 +9232,7 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
9075
9232
  }
9076
9233
  const requiredPath = join3(targetDir, info.libName);
9077
9234
  if (!existsSync2(requiredPath)) {
9078
- rmSync(targetDir, { recursive: true, force: true });
9235
+ rmSync2(targetDir, { recursive: true, force: true });
9079
9236
  throw new Error(`Required ONNX Runtime library missing after install: ${requiredPath}`);
9080
9237
  }
9081
9238
  }
@@ -9108,7 +9265,7 @@ function writeOnnxInstalledMeta(installDir, version, sha256, archiveSha256) {
9108
9265
  function readOnnxInstalledMeta(installDir) {
9109
9266
  const path = join3(installDir, ONNX_INSTALLED_META_FILE);
9110
9267
  try {
9111
- if (!statSync(path).isFile())
9268
+ if (!statSync2(path).isFile())
9112
9269
  return null;
9113
9270
  const raw = readFileSync(path, "utf8");
9114
9271
  const parsed = JSON.parse(raw);
@@ -9125,20 +9282,20 @@ function readOnnxInstalledMeta(installDir) {
9125
9282
  }
9126
9283
  }
9127
9284
  function sha256File(path) {
9128
- const hash = createHash("sha256");
9285
+ const hash = createHash2("sha256");
9129
9286
  hash.update(readFileSync(path));
9130
9287
  return hash.digest("hex");
9131
9288
  }
9132
9289
  function acquireLock(lockPath) {
9133
9290
  const tryClaim = () => {
9134
9291
  try {
9135
- const fd = openSync(lockPath, "wx");
9292
+ const fd = openSync2(lockPath, "wx");
9136
9293
  try {
9137
9294
  writeFileSync(fd, `${process.pid}
9138
9295
  ${new Date().toISOString()}
9139
9296
  `);
9140
9297
  } finally {
9141
- closeSync(fd);
9298
+ closeSync2(fd);
9142
9299
  }
9143
9300
  return true;
9144
9301
  } catch (err) {
@@ -9159,7 +9316,7 @@ ${new Date().toISOString()}
9159
9316
  const parsed = Number.parseInt(firstLine, 10);
9160
9317
  if (Number.isFinite(parsed) && parsed > 0)
9161
9318
  owningPid = parsed;
9162
- lockMtimeMs = statSync(lockPath).mtimeMs;
9319
+ lockMtimeMs = statSync2(lockPath).mtimeMs;
9163
9320
  } catch {
9164
9321
  return tryClaim();
9165
9322
  }
@@ -9276,7 +9433,9 @@ class BridgePool {
9276
9433
  onVersionMismatch: options.onVersionMismatch,
9277
9434
  onConfigureWarnings: options.onConfigureWarnings,
9278
9435
  onBashCompletion: options.onBashCompletion,
9279
- onBashLongRunning: options.onBashLongRunning
9436
+ onBashLongRunning: options.onBashLongRunning,
9437
+ errorPrefix: options.errorPrefix,
9438
+ logger: options.logger
9280
9439
  };
9281
9440
  this.configOverrides = configOverrides;
9282
9441
  if (Number.isFinite(this.idleTimeoutMs)) {
@@ -9348,24 +9507,32 @@ class BridgePool {
9348
9507
  }
9349
9508
  async replaceBinary(newPath) {
9350
9509
  this.binaryPath = newPath;
9351
- const shutdowns = Array.from(this.bridges.values()).map((entry) => entry.bridge.shutdown());
9352
9510
  this.bridges.clear();
9353
- await Promise.allSettled(shutdowns);
9354
9511
  this.log(`Binary path updated to ${newPath}. All bridges cleared \u2014 next calls will use the new binary.`);
9355
9512
  return newPath;
9356
9513
  }
9357
9514
  log(message, meta) {
9358
9515
  const logger = this.logger ?? getActiveLogger();
9359
- if (logger)
9360
- logger.log(message, meta);
9361
- else
9516
+ if (logger) {
9517
+ try {
9518
+ logger.log(message, meta);
9519
+ } catch (err) {
9520
+ console.error(`[aft-bridge] ERROR: pool logger log threw: ${err instanceof Error ? err.message : String(err)}`);
9521
+ console.error(`[aft-bridge] ${message}`);
9522
+ }
9523
+ } else
9362
9524
  log(message, meta);
9363
9525
  }
9364
9526
  error(message, meta) {
9365
9527
  const logger = this.logger ?? getActiveLogger();
9366
- if (logger)
9367
- logger.error(message, meta);
9368
- else
9528
+ if (logger) {
9529
+ try {
9530
+ logger.error(message, meta);
9531
+ } catch (err) {
9532
+ console.error(`[aft-bridge] ERROR: pool logger error threw: ${err instanceof Error ? err.message : String(err)}`);
9533
+ console.error(`[aft-bridge] ERROR: ${message}`);
9534
+ }
9535
+ } else
9369
9536
  error(message, meta);
9370
9537
  }
9371
9538
  setConfigureOverride(key, value) {
@@ -9392,7 +9559,7 @@ function normalizeKey(projectRoot) {
9392
9559
  }
9393
9560
  // ../aft-bridge/dist/resolver.js
9394
9561
  import { execSync, spawnSync } from "child_process";
9395
- import { chmodSync as chmodSync3, copyFileSync as copyFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync3, renameSync } from "fs";
9562
+ import { chmodSync as chmodSync3, copyFileSync as copyFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync3, renameSync as renameSync2 } from "fs";
9396
9563
  import { createRequire } from "module";
9397
9564
  import { homedir as homedir4 } from "os";
9398
9565
  import { join as join4 } from "path";
@@ -9423,15 +9590,19 @@ function copyToVersionedCache(npmBinaryPath, knownVersion) {
9423
9590
  const versionedDir = join4(cacheDir, tag);
9424
9591
  const ext = process.platform === "win32" ? ".exe" : "";
9425
9592
  const cachedPath = join4(versionedDir, `aft${ext}`);
9426
- if (existsSync3(cachedPath))
9427
- return cachedPath;
9593
+ if (existsSync3(cachedPath)) {
9594
+ const cachedVersion = readBinaryVersion(cachedPath);
9595
+ if (cachedVersion === version)
9596
+ return cachedPath;
9597
+ warn(`Cached binary at ${cachedPath} reports ${cachedVersion ?? "no version"}, expected ${version}; refreshing from npm package`);
9598
+ }
9428
9599
  mkdirSync3(versionedDir, { recursive: true });
9429
- const tmpPath = `${cachedPath}.tmp`;
9600
+ const tmpPath = `${cachedPath}.${process.pid}.${Date.now()}.tmp`;
9430
9601
  copyFileSync2(npmBinaryPath, tmpPath);
9431
9602
  if (process.platform !== "win32") {
9432
9603
  chmodSync3(tmpPath, 493);
9433
9604
  }
9434
- renameSync(tmpPath, cachedPath);
9605
+ renameSync2(tmpPath, cachedPath);
9435
9606
  log(`Copied npm binary to versioned cache: ${cachedPath}`);
9436
9607
  return cachedPath;
9437
9608
  } catch (err) {
@@ -9439,6 +9610,17 @@ function copyToVersionedCache(npmBinaryPath, knownVersion) {
9439
9610
  return null;
9440
9611
  }
9441
9612
  }
9613
+ function normalizeBareVersion(version) {
9614
+ return version.startsWith("v") ? version.slice(1) : version;
9615
+ }
9616
+ function isExpectedCachedBinary(binaryPath, expectedVersion) {
9617
+ const expected = normalizeBareVersion(expectedVersion);
9618
+ const actual = readBinaryVersion(binaryPath);
9619
+ if (actual === expected)
9620
+ return true;
9621
+ warn(`Cached binary at ${binaryPath} reports ${actual ?? "no version"}, expected ${expected}; skipping cache candidate`);
9622
+ return false;
9623
+ }
9442
9624
  function platformKey(platform = process.platform, arch = process.arch) {
9443
9625
  const archMap = PLATFORM_ARCH_MAP[platform];
9444
9626
  if (!archMap) {
@@ -9463,7 +9645,7 @@ function findBinarySync(expectedVersion) {
9463
9645
  if (pluginVersion) {
9464
9646
  const tag = pluginVersion.startsWith("v") ? pluginVersion : `v${pluginVersion}`;
9465
9647
  const versionCached = getCachedBinaryPath(tag);
9466
- if (versionCached)
9648
+ if (versionCached && isExpectedCachedBinary(versionCached, pluginVersion))
9467
9649
  return versionCached;
9468
9650
  }
9469
9651
  try {
@@ -9473,10 +9655,12 @@ function findBinarySync(expectedVersion) {
9473
9655
  const resolved = req.resolve(packageBin);
9474
9656
  if (existsSync3(resolved)) {
9475
9657
  const npmVersion = readBinaryVersion(resolved);
9476
- if (pluginVersion && npmVersion && npmVersion !== pluginVersion) {
9658
+ if (npmVersion === null) {
9659
+ warn(`npm platform package binary at ${resolved} did not report a version; skipping (continuing to PATH lookup)`);
9660
+ } else if (pluginVersion && npmVersion !== normalizeBareVersion(pluginVersion)) {
9477
9661
  warn(`npm platform package binary v${npmVersion} does not match plugin v${pluginVersion}; skipping (continuing to PATH lookup)`);
9478
9662
  } else {
9479
- const copied = copyToVersionedCache(resolved, npmVersion ?? undefined);
9663
+ const copied = copyToVersionedCache(resolved, npmVersion);
9480
9664
  return copied ?? resolved;
9481
9665
  }
9482
9666
  }
@@ -9525,7 +9709,7 @@ async function findBinary(expectedVersion) {
9525
9709
  `));
9526
9710
  }
9527
9711
  // ../aft-bridge/dist/url-fetch.js
9528
- import { createHash as createHash2 } from "crypto";
9712
+ import { createHash as createHash3 } from "crypto";
9529
9713
  import { lookup } from "dns/promises";
9530
9714
  import { existsSync as existsSync4, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "fs";
9531
9715
  import { isIP } from "net";
@@ -9539,7 +9723,7 @@ function cacheDir(storageDir) {
9539
9723
  return join5(storageDir, "url_cache");
9540
9724
  }
9541
9725
  function hashUrl(url) {
9542
- return createHash2("sha256").update(url).digest("hex").slice(0, 16);
9726
+ return createHash3("sha256").update(url).digest("hex").slice(0, 16);
9543
9727
  }
9544
9728
  function metaPath(storageDir, hash) {
9545
9729
  return join5(cacheDir(storageDir), `${hash}.meta.json`);
@@ -9797,8 +9981,8 @@ async function fetchUrlToTempFile(url, storageDir, options = {}) {
9797
9981
  const contentFile = contentPath(storageDir, hash, extension);
9798
9982
  const tmpContent = `${contentFile}.tmp-${process.pid}`;
9799
9983
  writeFileSync2(tmpContent, body);
9800
- const { renameSync: renameSync2 } = await import("fs");
9801
- renameSync2(tmpContent, contentFile);
9984
+ const { renameSync: renameSync3 } = await import("fs");
9985
+ renameSync3(tmpContent, contentFile);
9802
9986
  const meta = {
9803
9987
  url,
9804
9988
  contentType,
@@ -9807,7 +9991,7 @@ async function fetchUrlToTempFile(url, storageDir, options = {}) {
9807
9991
  };
9808
9992
  const tmpMeta = `${metaFile}.tmp-${process.pid}`;
9809
9993
  writeFileSync2(tmpMeta, JSON.stringify(meta));
9810
- renameSync2(tmpMeta, metaFile);
9994
+ renameSync3(tmpMeta, metaFile);
9811
9995
  log(`URL cached (${total} bytes): ${url}`);
9812
9996
  return contentFile;
9813
9997
  }
@@ -9961,24 +10145,24 @@ function warn2(message, data) {
9961
10145
  function error2(message, data) {
9962
10146
  write("ERROR", message, data);
9963
10147
  }
9964
- function sessionLog2(sessionId, message, data) {
10148
+ function sessionLog(sessionId, message, data) {
9965
10149
  write("INFO", message, data, sessionId);
9966
10150
  }
9967
- function sessionWarn2(sessionId, message, data) {
10151
+ function sessionWarn(sessionId, message, data) {
9968
10152
  write("WARN", message, data, sessionId);
9969
10153
  }
9970
- function sessionError2(sessionId, message, data) {
10154
+ function sessionError(sessionId, message, data) {
9971
10155
  write("ERROR", message, data, sessionId);
9972
10156
  }
9973
10157
  var bridgeLogger = {
9974
10158
  log(message, meta) {
9975
- sessionLog2(meta?.sessionId, message);
10159
+ sessionLog(meta?.sessionId, message);
9976
10160
  },
9977
10161
  warn(message, meta) {
9978
- sessionWarn2(meta?.sessionId, message);
10162
+ sessionWarn(meta?.sessionId, message);
9979
10163
  },
9980
10164
  error(message, meta) {
9981
- sessionError2(meta?.sessionId, message);
10165
+ sessionError(meta?.sessionId, message);
9982
10166
  },
9983
10167
  getLogFilePath: () => logFile
9984
10168
  };
@@ -10073,6 +10257,7 @@ var sessionBgStates = new Map;
10073
10257
  var SESSION_BG_STATE_IDLE_TTL_MS = 60 * 60 * 1000;
10074
10258
  var DEBOUNCE_STEP_MS = 200;
10075
10259
  var DEBOUNCE_CAP_MS = 1000;
10260
+ var MAX_WAKE_SEND_ATTEMPTS = 5;
10076
10261
  var UNKNOWN_COMPLETION_TTL_MS = 5000;
10077
10262
  var UNKNOWN_COMPLETION_CAP = 32;
10078
10263
  var DEFAULT_SESSION_ID = "__default__";
@@ -10125,17 +10310,24 @@ async function appendInTurnBgCompletions(drainContext, output) {
10125
10310
  return;
10126
10311
  const state = stateFor(drainContext.sessionID);
10127
10312
  if (state.outstandingTaskIds.size === 0 && state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0) {
10128
- return;
10313
+ await drainCompletions(drainContext);
10314
+ if (state.outstandingTaskIds.size === 0 && state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0) {
10315
+ return;
10316
+ }
10129
10317
  }
10130
- if (state.outstandingTaskIds.size > 0) {
10318
+ if (state.outstandingTaskIds.size > 0 || !state.forcedDrainCompleted) {
10131
10319
  await drainCompletions(drainContext);
10132
10320
  }
10133
10321
  if (state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0)
10134
10322
  return;
10323
+ const deliveredCompletions = [...state.pendingCompletions];
10135
10324
  const reminder = formatCombinedSystemReminder(state.pendingCompletions, state.pendingLongRunning);
10136
10325
  output.output = appendReminder(output.output ?? "", reminder);
10137
10326
  state.pendingCompletions = [];
10138
10327
  state.pendingLongRunning = [];
10328
+ state.wakeRetryAttempts = 0;
10329
+ state.wakeHardStopped = false;
10330
+ await ackCompletions(drainContext, deliveredCompletions);
10139
10331
  if (state.debounceTimer) {
10140
10332
  clearTimeout(state.debounceTimer);
10141
10333
  state.debounceTimer = null;
@@ -10149,12 +10341,12 @@ async function handleIdleBgCompletions(drainContext) {
10149
10341
  }
10150
10342
  async function triggerWakeIfPending(drainContext, skipDrain) {
10151
10343
  const state = stateFor(drainContext.sessionID);
10152
- if (!skipDrain && state.outstandingTaskIds.size > 0) {
10344
+ if (!skipDrain && (state.outstandingTaskIds.size > 0 || !state.forcedDrainCompleted)) {
10153
10345
  await drainCompletions(drainContext);
10154
10346
  }
10155
10347
  if (state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0)
10156
10348
  return;
10157
- scheduleWake(state, async (reminder) => {
10349
+ scheduleWake(state, async (reminder, deliveredCompletions) => {
10158
10350
  const client = drainContext.client;
10159
10351
  if (typeof client.session?.promptAsync !== "function") {
10160
10352
  throw new Error("client.session.promptAsync is unavailable");
@@ -10178,8 +10370,9 @@ async function triggerWakeIfPending(drainContext, skipDrain) {
10178
10370
  path: { id: drainContext.sessionID },
10179
10371
  body
10180
10372
  });
10181
- }, (err) => {
10182
- sessionWarn2(drainContext.sessionID, `${LOG_PREFIX} wake send failed: ${err instanceof Error ? err.message : String(err)}`);
10373
+ await ackCompletions(drainContext, deliveredCompletions);
10374
+ }, (err, hardStopped) => {
10375
+ sessionWarn(drainContext.sessionID, hardStopped ? `${LOG_PREFIX} wake send failed ${MAX_WAKE_SEND_ATTEMPTS} times; stopping retries: ${err instanceof Error ? err.message : String(err)}` : `${LOG_PREFIX} wake send failed: ${err instanceof Error ? err.message : String(err)}`);
10183
10376
  });
10184
10377
  }
10185
10378
  function formatSystemReminder(completions) {
@@ -10230,19 +10423,40 @@ function extractSessionID(value) {
10230
10423
  return;
10231
10424
  }
10232
10425
  async function drainCompletions({ ctx, directory, sessionID }) {
10426
+ const state = stateFor(sessionID);
10233
10427
  try {
10234
10428
  const bridge = ctx.pool.getActiveBridgeForRoot(directory) ?? ctx.pool.getBridge(directory);
10235
10429
  const response = await bridge.send("bash_drain_completions", { session_id: sessionID });
10236
10430
  if (response.success === false) {
10237
- sessionWarn2(sessionID, `${LOG_PREFIX} drain failed: ${String(response.message ?? "unknown error")}`);
10431
+ sessionWarn(sessionID, `${LOG_PREFIX} drain failed: ${String(response.message ?? "unknown error")}`);
10238
10432
  return;
10239
10433
  }
10240
- ingestBgCompletions(sessionID, response.bg_completions);
10434
+ state.forcedDrainCompleted = true;
10435
+ ingestDrainedBgCompletions(sessionID, response.bg_completions);
10436
+ } catch (err) {
10437
+ sessionWarn(sessionID, `${LOG_PREFIX} drain failed: ${err instanceof Error ? err.message : String(err)}`);
10438
+ }
10439
+ }
10440
+ async function ackCompletions({ ctx, directory, sessionID }, completions) {
10441
+ const taskIds = [...new Set(completions.map((completion) => completion.task_id))];
10442
+ if (taskIds.length === 0)
10443
+ return;
10444
+ try {
10445
+ const bridge = ctx.pool.getActiveBridgeForRoot(directory) ?? ctx.pool.getBridge(directory);
10446
+ const response = await bridge.send("bash_ack_completions", {
10447
+ session_id: sessionID,
10448
+ task_ids: taskIds
10449
+ });
10450
+ if (response.success === false) {
10451
+ sessionWarn(sessionID, `${LOG_PREFIX} ack failed: ${String(response.message ?? "unknown error")}`);
10452
+ }
10241
10453
  } catch (err) {
10242
- sessionWarn2(sessionID, `${LOG_PREFIX} drain failed: ${err instanceof Error ? err.message : String(err)}`);
10454
+ sessionWarn(sessionID, `${LOG_PREFIX} ack failed: ${err instanceof Error ? err.message : String(err)}`);
10243
10455
  }
10244
10456
  }
10245
10457
  function scheduleWake(state, sendWake, onSendFailure) {
10458
+ if (state.wakeHardStopped)
10459
+ return;
10246
10460
  const now = Date.now();
10247
10461
  const pendingCount = state.pendingCompletions.length + state.pendingLongRunning.length;
10248
10462
  if (state.debounceTimer && pendingCount <= state.scheduledCompletionCount) {
@@ -10271,13 +10485,22 @@ function scheduleWake(state, sendWake, onSendFailure) {
10271
10485
  const reminder = formatCombinedSystemReminder(pending, pendingLongRunning);
10272
10486
  state.pendingCompletions = [];
10273
10487
  state.pendingLongRunning = [];
10274
- sendWake(reminder).then(() => {
10488
+ sendWake(reminder, pending).then(() => {
10275
10489
  state.retryDelayMs = null;
10490
+ state.wakeRetryAttempts = 0;
10491
+ state.wakeHardStopped = false;
10276
10492
  }).catch((err) => {
10277
10493
  state.pendingCompletions = [...pending, ...state.pendingCompletions];
10278
10494
  state.pendingLongRunning = [...pendingLongRunning, ...state.pendingLongRunning];
10495
+ state.wakeRetryAttempts += 1;
10496
+ if (state.wakeRetryAttempts >= MAX_WAKE_SEND_ATTEMPTS) {
10497
+ state.retryDelayMs = null;
10498
+ state.wakeHardStopped = true;
10499
+ onSendFailure(err, true);
10500
+ return;
10501
+ }
10279
10502
  state.retryDelayMs = Math.min((delay || DEBOUNCE_STEP_MS) * 2, DEBOUNCE_CAP_MS);
10280
- onSendFailure(err);
10503
+ onSendFailure(err, false);
10281
10504
  scheduleWake(state, sendWake, onSendFailure);
10282
10505
  });
10283
10506
  }, delay);
@@ -10298,6 +10521,9 @@ function stateFor(sessionID) {
10298
10521
  scheduledFireAt: null,
10299
10522
  scheduledCompletionCount: 0,
10300
10523
  retryDelayMs: null,
10524
+ wakeRetryAttempts: 0,
10525
+ wakeHardStopped: false,
10526
+ forcedDrainCompleted: false,
10301
10527
  unknownCompletions: [],
10302
10528
  lastSeenAt: now
10303
10529
  };
@@ -10307,6 +10533,22 @@ function stateFor(sessionID) {
10307
10533
  }
10308
10534
  return state;
10309
10535
  }
10536
+ function ingestDrainedBgCompletions(sessionID, completions) {
10537
+ if (!Array.isArray(completions) || completions.length === 0)
10538
+ return [];
10539
+ const state = stateFor(sessionID);
10540
+ const accepted = [];
10541
+ for (const completion of completions) {
10542
+ if (!isBgCompletion(completion))
10543
+ continue;
10544
+ state.outstandingTaskIds.delete(completion.task_id);
10545
+ if (!state.pendingCompletions.some((pending) => pending.task_id === completion.task_id) && !accepted.some((pending) => pending.task_id === completion.task_id)) {
10546
+ accepted.push(completion);
10547
+ }
10548
+ }
10549
+ state.pendingCompletions.push(...accepted);
10550
+ return accepted;
10551
+ }
10310
10552
  function cleanupIdleSessionStates(now) {
10311
10553
  const cutoff = now - SESSION_BG_STATE_IDLE_TTL_MS;
10312
10554
  for (const [sessionID, state] of sessionBgStates) {
@@ -10397,7 +10639,7 @@ function formatDuration(completion) {
10397
10639
 
10398
10640
  // src/config.ts
10399
10641
  var import_comment_json = __toESM(require_src2(), 1);
10400
- import { existsSync as existsSync5, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync as unlinkSync4, writeFileSync as writeFileSync3 } from "fs";
10642
+ import { existsSync as existsSync5, readFileSync as readFileSync3, renameSync as renameSync3, unlinkSync as unlinkSync4, writeFileSync as writeFileSync3 } from "fs";
10401
10643
  import { homedir as homedir5 } from "os";
10402
10644
  import { join as join7 } from "path";
10403
10645
 
@@ -24176,7 +24418,7 @@ function migrateAftConfigFile(configPath, logger = { log: log2, warn: warn2 }) {
24176
24418
  ${serialized}` : serialized;
24177
24419
  tmpPath = `${configPath}.tmp.${process.pid}`;
24178
24420
  writeFileSync3(tmpPath, nextContent, "utf-8");
24179
- renameSync2(tmpPath, configPath);
24421
+ renameSync3(tmpPath, configPath);
24180
24422
  logger.log(`Migrated config at ${configPath}: removed ${oldKeys.join(", ")}`);
24181
24423
  return { migrated: true, oldKeys };
24182
24424
  } catch (err) {
@@ -24411,19 +24653,19 @@ function loadAftConfig(projectDirectory) {
24411
24653
  }
24412
24654
 
24413
24655
  // src/hooks/auto-update-checker/index.ts
24414
- import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync6, renameSync as renameSync3, writeFileSync as writeFileSync6 } from "fs";
24656
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync6, renameSync as renameSync4, writeFileSync as writeFileSync6 } from "fs";
24415
24657
  import { dirname as dirname4, join as join11 } from "path";
24416
24658
 
24417
24659
  // src/hooks/auto-update-checker/cache.ts
24418
24660
  var import_comment_json3 = __toESM(require_src2(), 1);
24419
24661
  import { spawn as spawn2 } from "child_process";
24420
- import { cpSync, existsSync as existsSync7, mkdtempSync, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
24662
+ import { cpSync, existsSync as existsSync7, mkdtempSync, readFileSync as readFileSync5, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "fs";
24421
24663
  import { tmpdir as tmpdir2 } from "os";
24422
24664
  import { basename, dirname as dirname3, join as join10 } from "path";
24423
24665
 
24424
24666
  // src/hooks/auto-update-checker/checker.ts
24425
24667
  var import_comment_json2 = __toESM(require_src2(), 1);
24426
- import { existsSync as existsSync6, readFileSync as readFileSync4, statSync as statSync2, writeFileSync as writeFileSync4 } from "fs";
24668
+ import { existsSync as existsSync6, readFileSync as readFileSync4, statSync as statSync3, writeFileSync as writeFileSync4 } from "fs";
24427
24669
  import { homedir as homedir7 } from "os";
24428
24670
  import { dirname as dirname2, isAbsolute, join as join9, resolve as resolve2 } from "path";
24429
24671
  import { fileURLToPath } from "url";
@@ -24550,7 +24792,7 @@ function getLocalDevPath(directory) {
24550
24792
  }
24551
24793
  function findPackageJsonUp(startPath) {
24552
24794
  try {
24553
- const stat = statSync2(startPath);
24795
+ const stat = statSync3(startPath);
24554
24796
  let dir = stat.isDirectory() ? startPath : dirname2(startPath);
24555
24797
  for (let i = 0;i < 10; i++) {
24556
24798
  const pkgPath = join9(dir, "package.json");
@@ -24690,19 +24932,19 @@ function createAutoUpdateSnapshot(installDir, packageJsonPath, packageName) {
24690
24932
  function restoreAutoUpdateSnapshot(snapshot) {
24691
24933
  try {
24692
24934
  if (snapshot.packageJson === null)
24693
- rmSync2(snapshot.packageJsonPath, { force: true });
24935
+ rmSync3(snapshot.packageJsonPath, { force: true });
24694
24936
  else
24695
24937
  writeFileSync5(snapshot.packageJsonPath, snapshot.packageJson);
24696
24938
  if (snapshot.lockfile === null)
24697
- rmSync2(snapshot.lockfilePath, { force: true });
24939
+ rmSync3(snapshot.lockfilePath, { force: true });
24698
24940
  else
24699
24941
  writeFileSync5(snapshot.lockfilePath, snapshot.lockfile);
24700
- rmSync2(snapshot.packageDir, { recursive: true, force: true });
24942
+ rmSync3(snapshot.packageDir, { recursive: true, force: true });
24701
24943
  if (snapshot.stagedPackageDir) {
24702
24944
  cpSync(snapshot.stagedPackageDir, snapshot.packageDir, { recursive: true });
24703
24945
  }
24704
24946
  } finally {
24705
- rmSync2(snapshot.tempDir, { recursive: true, force: true });
24947
+ rmSync3(snapshot.tempDir, { recursive: true, force: true });
24706
24948
  }
24707
24949
  }
24708
24950
  function stripPackageNameFromPath(pathValue, packageName) {
@@ -24767,7 +25009,7 @@ function removeInstalledPackage(installDir, packageName) {
24767
25009
  const packageDir = join10(installDir, "node_modules", packageName);
24768
25010
  if (!existsSync7(packageDir))
24769
25011
  return false;
24770
- rmSync2(packageDir, { recursive: true, force: true });
25012
+ rmSync3(packageDir, { recursive: true, force: true });
24771
25013
  log2(`[auto-update-checker] Package removed: ${packageDir}`);
24772
25014
  return true;
24773
25015
  }
@@ -24852,7 +25094,7 @@ async function runNpmInstallSafe(installDir, options = {}) {
24852
25094
  if (!result && snapshot) {
24853
25095
  restoreAutoUpdateSnapshot(snapshot);
24854
25096
  } else if (snapshot) {
24855
- rmSync2(snapshot.tempDir, { recursive: true, force: true });
25097
+ rmSync3(snapshot.tempDir, { recursive: true, force: true });
24856
25098
  }
24857
25099
  return result;
24858
25100
  } catch (err) {
@@ -24936,7 +25178,7 @@ function claimCheckSlot(storageDir, intervalMs) {
24936
25178
  mkdirSync5(dirname4(file2), { recursive: true });
24937
25179
  const tmp = `${file2}.tmp.${process.pid}`;
24938
25180
  writeFileSync6(tmp, JSON.stringify({ lastCheckedMs: Date.now() }), "utf-8");
24939
- renameSync3(tmp, file2);
25181
+ renameSync4(tmp, file2);
24940
25182
  return true;
24941
25183
  } catch (err) {
24942
25184
  warn2(`[auto-update-checker] Could not coordinate via timestamp file: ${String(err)}`);
@@ -25023,17 +25265,17 @@ function showToast(ctx, title, message, variant = "info", duration3 = 3000) {
25023
25265
 
25024
25266
  // src/lsp-auto-install.ts
25025
25267
  import { spawn as spawn3 } from "child_process";
25026
- import { createHash as createHash3 } from "crypto";
25027
- import { createReadStream, mkdirSync as mkdirSync7, readFileSync as readFileSync8, renameSync as renameSync4, rmSync as rmSync3, statSync as statSync4 } from "fs";
25268
+ import { createHash as createHash4 } from "crypto";
25269
+ import { createReadStream, mkdirSync as mkdirSync7, readFileSync as readFileSync8, renameSync as renameSync5, rmSync as rmSync4, statSync as statSync5 } from "fs";
25028
25270
  import { join as join14 } from "path";
25029
25271
 
25030
25272
  // src/lsp-cache.ts
25031
25273
  import {
25032
- closeSync as closeSync2,
25274
+ closeSync as closeSync3,
25033
25275
  mkdirSync as mkdirSync6,
25034
- openSync as openSync2,
25276
+ openSync as openSync3,
25035
25277
  readFileSync as readFileSync7,
25036
- statSync as statSync3,
25278
+ statSync as statSync4,
25037
25279
  unlinkSync as unlinkSync5,
25038
25280
  writeFileSync as writeFileSync7
25039
25281
  } from "fs";
@@ -25066,7 +25308,7 @@ function lspBinDir(npmPackage) {
25066
25308
  function isInstalled(npmPackage, binary) {
25067
25309
  for (const candidate of lspBinaryCandidates(binary)) {
25068
25310
  try {
25069
- if (statSync3(join12(lspBinDir(npmPackage), candidate)).isFile())
25311
+ if (statSync4(join12(lspBinDir(npmPackage), candidate)).isFile())
25070
25312
  return true;
25071
25313
  } catch {}
25072
25314
  }
@@ -25094,7 +25336,7 @@ function writeInstalledMetaIn(installDir, version2, sha256) {
25094
25336
  function readInstalledMetaIn(installDir) {
25095
25337
  const path2 = join12(installDir, INSTALLED_META_FILE);
25096
25338
  try {
25097
- if (!statSync3(path2).isFile())
25339
+ if (!statSync4(path2).isFile())
25098
25340
  return null;
25099
25341
  const raw = readFileSync7(path2, "utf8");
25100
25342
  const parsed = JSON.parse(raw);
@@ -25124,13 +25366,13 @@ function acquireInstallLock(lockKey) {
25124
25366
  const lock = lockPath(lockKey);
25125
25367
  const tryClaim = () => {
25126
25368
  try {
25127
- const fd = openSync2(lock, "wx");
25369
+ const fd = openSync3(lock, "wx");
25128
25370
  try {
25129
25371
  writeFileSync7(fd, `${process.pid}
25130
25372
  ${new Date().toISOString()}
25131
25373
  `);
25132
25374
  } finally {
25133
- closeSync2(fd);
25375
+ closeSync3(fd);
25134
25376
  }
25135
25377
  return true;
25136
25378
  } catch (err) {
@@ -25151,7 +25393,7 @@ ${new Date().toISOString()}
25151
25393
  const parsed = Number.parseInt(firstLine, 10);
25152
25394
  if (Number.isFinite(parsed) && parsed > 0)
25153
25395
  owningPid = parsed;
25154
- lockMtimeMs = statSync3(lock).mtimeMs;
25396
+ lockMtimeMs = statSync4(lock).mtimeMs;
25155
25397
  } catch {
25156
25398
  return tryClaim();
25157
25399
  }
@@ -25692,7 +25934,7 @@ function hashInstalledBinary(spec) {
25692
25934
  let pathToHash = null;
25693
25935
  for (const p of candidates) {
25694
25936
  try {
25695
- if (statSync4(p).isFile()) {
25937
+ if (statSync5(p).isFile()) {
25696
25938
  pathToHash = p;
25697
25939
  break;
25698
25940
  }
@@ -25702,7 +25944,7 @@ function hashInstalledBinary(spec) {
25702
25944
  reject(new Error(`installed binary not found at any of: ${candidates.join(", ")}`));
25703
25945
  return;
25704
25946
  }
25705
- const hash2 = createHash3("sha256");
25947
+ const hash2 = createHash4("sha256");
25706
25948
  const stream = createReadStream(pathToHash);
25707
25949
  stream.on("error", reject);
25708
25950
  stream.on("data", (chunk) => hash2.update(chunk));
@@ -25718,14 +25960,14 @@ function installedBinaryPath(spec) {
25718
25960
  ] : [lspBinaryPath(spec.npm, spec.binary)];
25719
25961
  for (const candidate of candidates) {
25720
25962
  try {
25721
- if (statSync4(candidate).isFile())
25963
+ if (statSync5(candidate).isFile())
25722
25964
  return candidate;
25723
25965
  } catch {}
25724
25966
  }
25725
25967
  return null;
25726
25968
  }
25727
25969
  function sha256OfFileSync(path2) {
25728
- return createHash3("sha256").update(readFileSync8(path2)).digest("hex");
25970
+ return createHash4("sha256").update(readFileSync8(path2)).digest("hex");
25729
25971
  }
25730
25972
  function quarantineCachedNpmInstall(spec, reason) {
25731
25973
  const packageDir = lspPackageDir(spec.npm);
@@ -25733,8 +25975,8 @@ function quarantineCachedNpmInstall(spec, reason) {
25733
25975
  warn2(`[lsp] tofu_mismatch ${spec.npm}: ${reason}; quarantining ${packageDir} -> ${dest}`);
25734
25976
  try {
25735
25977
  mkdirSync7(join14(dest, ".."), { recursive: true });
25736
- rmSync3(dest, { recursive: true, force: true });
25737
- renameSync4(packageDir, dest);
25978
+ rmSync4(dest, { recursive: true, force: true });
25979
+ renameSync5(packageDir, dest);
25738
25980
  } catch (err) {
25739
25981
  warn2(`[lsp] tofu_mismatch ${spec.npm}: failed to quarantine cache entry: ${err}`);
25740
25982
  }
@@ -25810,11 +26052,11 @@ function runAutoInstall(projectRoot, config2, fetchImpl = fetch) {
25810
26052
 
25811
26053
  // src/lsp-github-install.ts
25812
26054
  import { execFileSync as execFileSync2 } from "child_process";
25813
- import { createHash as createHash4, randomBytes } from "crypto";
26055
+ import { createHash as createHash5, randomBytes } from "crypto";
25814
26056
  import {
25815
26057
  copyFileSync as copyFileSync3,
25816
26058
  createReadStream as createReadStream2,
25817
- createWriteStream as createWriteStream2,
26059
+ createWriteStream as createWriteStream3,
25818
26060
  existsSync as existsSync10,
25819
26061
  lstatSync as lstatSync2,
25820
26062
  mkdirSync as mkdirSync8,
@@ -25822,14 +26064,15 @@ import {
25822
26064
  readFileSync as readFileSync9,
25823
26065
  readlinkSync as readlinkSync2,
25824
26066
  realpathSync as realpathSync3,
25825
- renameSync as renameSync5,
25826
- rmSync as rmSync4,
25827
- statSync as statSync5,
25828
- unlinkSync as unlinkSync6
26067
+ renameSync as renameSync6,
26068
+ rmSync as rmSync5,
26069
+ statSync as statSync6,
26070
+ unlinkSync as unlinkSync6,
26071
+ writeFileSync as writeFileSync8
25829
26072
  } from "fs";
25830
26073
  import { dirname as dirname5, join as join15, relative as relative2, resolve as resolve3 } from "path";
25831
- import { Readable as Readable2 } from "stream";
25832
- import { pipeline as pipeline2 } from "stream/promises";
26074
+ import { Readable as Readable3 } from "stream";
26075
+ import { pipeline as pipeline3 } from "stream/promises";
25833
26076
 
25834
26077
  // src/lsp-github-table.ts
25835
26078
  function exe(platform2, name) {
@@ -25928,6 +26171,7 @@ function ghBinDir(spec) {
25928
26171
  function ghExtractDir(spec) {
25929
26172
  return join15(ghPackageDir(spec), "extracted");
25930
26173
  }
26174
+ var INSTALLED_META_FILE2 = ".aft-installed";
25931
26175
  function ghBinaryPath(spec, platform2) {
25932
26176
  const ext = platform2 === "win32" ? ".exe" : "";
25933
26177
  return join15(ghBinDir(spec), `${spec.binary}${ext}`);
@@ -25935,7 +26179,7 @@ function ghBinaryPath(spec, platform2) {
25935
26179
  function isGithubInstalled(spec, platform2) {
25936
26180
  for (const candidate of ghBinaryCandidates(spec, platform2)) {
25937
26181
  try {
25938
- if (statSync5(join15(ghBinDir(spec), candidate)).isFile())
26182
+ if (statSync6(join15(ghBinDir(spec), candidate)).isFile())
25939
26183
  return true;
25940
26184
  } catch {}
25941
26185
  }
@@ -25946,11 +26190,45 @@ function ghBinaryCandidates(spec, platform2) {
25946
26190
  return [spec.binary];
25947
26191
  return [spec.binary, `${spec.binary}.cmd`, `${spec.binary}.exe`, `${spec.binary}.bat`];
25948
26192
  }
25949
- var MAX_DOWNLOAD_BYTES2 = 256 * 1024 * 1024;
26193
+ function readGithubInstalledMetaIn(installDir) {
26194
+ try {
26195
+ const path2 = join15(installDir, INSTALLED_META_FILE2);
26196
+ if (!statSync6(path2).isFile())
26197
+ return null;
26198
+ const parsed = JSON.parse(readFileSync9(path2, "utf8"));
26199
+ if (typeof parsed.version !== "string" || parsed.version.length === 0)
26200
+ return null;
26201
+ return {
26202
+ version: parsed.version,
26203
+ installedAt: typeof parsed.installedAt === "string" ? parsed.installedAt : "",
26204
+ ...typeof parsed.sha256 === "string" && parsed.sha256.length > 0 ? { sha256: parsed.sha256 } : {},
26205
+ ...typeof parsed.binarySha256 === "string" && parsed.binarySha256.length > 0 ? { binarySha256: parsed.binarySha256 } : {},
26206
+ ...typeof parsed.archiveSha256 === "string" && parsed.archiveSha256.length > 0 ? { archiveSha256: parsed.archiveSha256 } : {}
26207
+ };
26208
+ } catch {
26209
+ return null;
26210
+ }
26211
+ }
26212
+ function writeGithubInstalledMetaIn(installDir, version2, binarySha256, archiveSha256) {
26213
+ try {
26214
+ mkdirSync8(installDir, { recursive: true });
26215
+ const meta3 = {
26216
+ version: version2,
26217
+ installedAt: new Date().toISOString(),
26218
+ sha256: binarySha256,
26219
+ binarySha256,
26220
+ ...archiveSha256 ? { archiveSha256 } : {}
26221
+ };
26222
+ writeFileSync8(join15(installDir, INSTALLED_META_FILE2), JSON.stringify(meta3), "utf8");
26223
+ } catch (err) {
26224
+ warn2(`[lsp] failed to write github installed metadata in ${installDir}: ${err}`);
26225
+ }
26226
+ }
26227
+ var MAX_DOWNLOAD_BYTES3 = 256 * 1024 * 1024;
25950
26228
  var MAX_EXTRACT_BYTES2 = 1024 * 1024 * 1024;
25951
26229
  function sha256OfFile(path2) {
25952
26230
  return new Promise((resolve4, reject) => {
25953
- const hash2 = createHash4("sha256");
26231
+ const hash2 = createHash5("sha256");
25954
26232
  const stream = createReadStream2(path2);
25955
26233
  stream.on("error", reject);
25956
26234
  stream.on("data", (chunk) => hash2.update(chunk));
@@ -25958,7 +26236,7 @@ function sha256OfFile(path2) {
25958
26236
  });
25959
26237
  }
25960
26238
  function sha256OfFileSync2(path2) {
25961
- return createHash4("sha256").update(readFileSync9(path2)).digest("hex");
26239
+ return createHash5("sha256").update(readFileSync9(path2)).digest("hex");
25962
26240
  }
25963
26241
  async function fetchReleaseByTag(githubRepo, tag, fetchImpl, signal) {
25964
26242
  const candidates = [];
@@ -26121,8 +26399,8 @@ function assertAllowedDownloadUrl(rawUrl) {
26121
26399
  return parsed;
26122
26400
  }
26123
26401
  async function downloadFile(url2, destPath, fetchImpl, assetSize, signal) {
26124
- if (assetSize !== undefined && assetSize > MAX_DOWNLOAD_BYTES2) {
26125
- throw new Error(`asset size ${assetSize} exceeds max ${MAX_DOWNLOAD_BYTES2} (set lsp.versions to pin a smaller release if this is wrong)`);
26402
+ if (assetSize !== undefined && assetSize > MAX_DOWNLOAD_BYTES3) {
26403
+ throw new Error(`asset size ${assetSize} exceeds max ${MAX_DOWNLOAD_BYTES3} (set lsp.versions to pin a smaller release if this is wrong)`);
26126
26404
  }
26127
26405
  const timeout = controlledTimeoutSignal(120000, signal);
26128
26406
  try {
@@ -26131,24 +26409,24 @@ async function downloadFile(url2, destPath, fetchImpl, assetSize, signal) {
26131
26409
  throw new Error(`download failed (${res.status})`);
26132
26410
  }
26133
26411
  const advertised = Number.parseInt(res.headers.get("content-length") ?? "", 10);
26134
- if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES2) {
26135
- throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES2}`);
26412
+ if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES3) {
26413
+ throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES3}`);
26136
26414
  }
26137
26415
  mkdirSync8(dirname5(destPath), { recursive: true });
26138
26416
  let bytesWritten = 0;
26139
26417
  const guard = new TransformStream({
26140
26418
  transform(chunk, controller) {
26141
26419
  bytesWritten += chunk.byteLength;
26142
- if (bytesWritten > MAX_DOWNLOAD_BYTES2) {
26143
- controller.error(new Error(`download exceeded ${MAX_DOWNLOAD_BYTES2} bytes after streaming (server lied about size or sent unbounded body)`));
26420
+ if (bytesWritten > MAX_DOWNLOAD_BYTES3) {
26421
+ controller.error(new Error(`download exceeded ${MAX_DOWNLOAD_BYTES3} bytes after streaming (server lied about size or sent unbounded body)`));
26144
26422
  return;
26145
26423
  }
26146
26424
  controller.enqueue(chunk);
26147
26425
  }
26148
26426
  });
26149
26427
  const guarded = res.body.pipeThrough(guard);
26150
- const nodeStream = Readable2.fromWeb(guarded);
26151
- await pipeline2(nodeStream, createWriteStream2(destPath), { signal: timeout.signal });
26428
+ const nodeStream = Readable3.fromWeb(guarded);
26429
+ await pipeline3(nodeStream, createWriteStream3(destPath), { signal: timeout.signal });
26152
26430
  } catch (err) {
26153
26431
  try {
26154
26432
  unlinkSync6(destPath);
@@ -26228,7 +26506,7 @@ function validateExtraction(stagingRoot) {
26228
26506
  };
26229
26507
  walk(realStagingRoot);
26230
26508
  }
26231
- function precheckArchiveSize(archivePath, archiveType) {
26509
+ function precheckArchiveContents(archivePath, archiveType) {
26232
26510
  let totalBytes = 0;
26233
26511
  if (archiveType === "zip") {
26234
26512
  const out = execFileSync2("unzip", ["-l", archivePath], { encoding: "utf8" });
@@ -26239,6 +26517,9 @@ function precheckArchiveSize(archivePath, archiveType) {
26239
26517
  const out = execFileSync2("tar", ["-tvf", archivePath], { encoding: "utf8" });
26240
26518
  for (const line of out.split(`
26241
26519
  `)) {
26520
+ if (line.startsWith("h")) {
26521
+ throw new Error(`archive contains hardlink entry: ${line.trim()}`);
26522
+ }
26242
26523
  const parts = line.trim().split(/\s+/);
26243
26524
  if (parts.length >= 6) {
26244
26525
  const numeric = parts.map((part) => Number.parseInt(part, 10)).filter((value) => Number.isFinite(value) && value >= 0);
@@ -26255,20 +26536,20 @@ function extractArchiveSafely(archivePath, destDir, archiveType) {
26255
26536
  const suffix = randomBytes(8).toString("hex");
26256
26537
  const stagingDir = `${destDir}.staging-${suffix}`;
26257
26538
  try {
26258
- rmSync4(stagingDir, { recursive: true, force: true });
26539
+ rmSync5(stagingDir, { recursive: true, force: true });
26259
26540
  } catch {}
26260
26541
  mkdirSync8(stagingDir, { recursive: true });
26261
26542
  try {
26262
- precheckArchiveSize(archivePath, archiveType);
26543
+ precheckArchiveContents(archivePath, archiveType);
26263
26544
  runPlatformExtractor(archivePath, stagingDir, archiveType);
26264
26545
  validateExtraction(stagingDir);
26265
26546
  try {
26266
- rmSync4(destDir, { recursive: true, force: true });
26547
+ rmSync5(destDir, { recursive: true, force: true });
26267
26548
  } catch {}
26268
- renameSync5(stagingDir, destDir);
26549
+ renameSync6(stagingDir, destDir);
26269
26550
  } catch (err) {
26270
26551
  try {
26271
- rmSync4(stagingDir, { recursive: true, force: true });
26552
+ rmSync5(stagingDir, { recursive: true, force: true });
26272
26553
  } catch {}
26273
26554
  throw err;
26274
26555
  }
@@ -26279,28 +26560,44 @@ function quarantineCachedGithubInstall(spec, reason) {
26279
26560
  warn2(`[lsp] tofu_mismatch ${spec.id}: ${reason}; quarantining ${packageDir} -> ${dest}`);
26280
26561
  try {
26281
26562
  mkdirSync8(dirname5(dest), { recursive: true });
26282
- rmSync4(dest, { recursive: true, force: true });
26283
- renameSync5(packageDir, dest);
26563
+ rmSync5(dest, { recursive: true, force: true });
26564
+ renameSync6(packageDir, dest);
26284
26565
  } catch (err) {
26285
26566
  warn2(`[lsp] tofu_mismatch ${spec.id}: failed to quarantine cache entry: ${err}`);
26286
26567
  }
26287
26568
  }
26288
26569
  function validateCachedGithubInstall(spec, platform2) {
26289
- const meta3 = readInstalledMetaIn(ghPackageDir(spec));
26570
+ const packageDir = ghPackageDir(spec);
26571
+ const meta3 = readGithubInstalledMetaIn(packageDir);
26290
26572
  const binaryPath = ghBinaryCandidates(spec, platform2).map((candidate) => join15(ghBinDir(spec), candidate)).find((candidate) => {
26291
26573
  try {
26292
- return statSync5(candidate).isFile();
26574
+ return statSync6(candidate).isFile();
26293
26575
  } catch {
26294
26576
  return false;
26295
26577
  }
26296
26578
  });
26297
- if (!meta3?.sha256 || !meta3.version || !isSafeVersion(meta3.version) || !binaryPath) {
26579
+ if (!meta3?.version || !isSafeVersion(meta3.version) || !binaryPath) {
26298
26580
  quarantineCachedGithubInstall(spec, "missing/unsafe metadata or binary");
26299
26581
  return false;
26300
26582
  }
26301
26583
  const currentHash = sha256OfFileSync2(binaryPath);
26302
- if (currentHash !== meta3.sha256) {
26303
- quarantineCachedGithubInstall(spec, `recorded ${meta3.sha256}, current ${currentHash}`);
26584
+ const recordedBinaryHash = meta3.binarySha256 ?? meta3.sha256;
26585
+ if (recordedBinaryHash && currentHash === recordedBinaryHash) {
26586
+ if (meta3.sha256 !== currentHash || meta3.binarySha256 !== currentHash) {
26587
+ writeGithubInstalledMetaIn(packageDir, meta3.version, currentHash, meta3.archiveSha256);
26588
+ }
26589
+ return true;
26590
+ }
26591
+ if (meta3.sha256 && !meta3.binarySha256 && !meta3.archiveSha256) {
26592
+ writeGithubInstalledMetaIn(packageDir, meta3.version, currentHash, meta3.sha256);
26593
+ return true;
26594
+ }
26595
+ if (!recordedBinaryHash) {
26596
+ quarantineCachedGithubInstall(spec, "missing binary sha256 metadata");
26597
+ return false;
26598
+ }
26599
+ if (currentHash !== recordedBinaryHash) {
26600
+ quarantineCachedGithubInstall(spec, `recorded ${recordedBinaryHash}, current ${currentHash}`);
26304
26601
  return false;
26305
26602
  }
26306
26603
  return true;
@@ -26369,10 +26666,11 @@ async function downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl,
26369
26666
  return null;
26370
26667
  }
26371
26668
  log2(`[lsp] ${spec.id} ${tag} sha256=${archiveSha256}`);
26372
- const previousMeta = readInstalledMetaIn(ghPackageDir(spec));
26373
- if (previousMeta && previousMeta.version === tag && previousMeta.sha256) {
26374
- if (previousMeta.sha256 !== archiveSha256) {
26375
- error2(`[lsp] ${spec.id} ${tag}: TOFU sha256 mismatch \u2014 refusing install. ` + `Previously installed sha256=${previousMeta.sha256}, downloaded sha256=${archiveSha256}. ` + `This means the published release for tag ${tag} changed. Investigate before proceeding. ` + `Run \`aft doctor --clear\` to wipe the cache and force a fresh install if you've verified the change.`);
26669
+ const previousMeta = readGithubInstalledMetaIn(ghPackageDir(spec));
26670
+ const previousArchiveSha256 = previousMeta?.archiveSha256 ?? (previousMeta?.binarySha256 ? undefined : previousMeta?.sha256);
26671
+ if (previousMeta && previousMeta.version === tag && previousArchiveSha256) {
26672
+ if (previousArchiveSha256 !== archiveSha256) {
26673
+ error2(`[lsp] ${spec.id} ${tag}: TOFU sha256 mismatch \u2014 refusing install. ` + `Previously installed archive sha256=${previousArchiveSha256}, downloaded sha256=${archiveSha256}. ` + `This means the published release for tag ${tag} changed. Investigate before proceeding. ` + `Run \`aft doctor --clear\` to wipe the cache and force a fresh install if you've verified the change.`);
26376
26674
  try {
26377
26675
  unlinkSync6(archivePath);
26378
26676
  } catch {}
@@ -26407,7 +26705,9 @@ async function downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl,
26407
26705
  return null;
26408
26706
  }
26409
26707
  log2(`[lsp] installed ${spec.id} ${tag} at ${targetBinary}`);
26410
- return archiveSha256;
26708
+ const binarySha256 = await sha256OfFile(targetBinary);
26709
+ log2(`[lsp] ${spec.id} ${tag} binary_sha256=${binarySha256}`);
26710
+ return { archiveSha256, binarySha256 };
26411
26711
  }
26412
26712
  async function ensureGithubInstalled(spec, config2, fetchImpl, platform2, arch, signal) {
26413
26713
  const outcome = await withInstallLock(spec.githubRepo, async () => {
@@ -26433,14 +26733,14 @@ async function ensureGithubInstalled(spec, config2, fetchImpl, platform2, arch,
26433
26733
  log2(`[lsp] reinstalling ${spec.id}@${tag}: no installed-version metadata recorded`);
26434
26734
  }
26435
26735
  }
26436
- const archiveSha256 = await downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl, signal).catch((err) => {
26736
+ const hashes = await downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl, signal).catch((err) => {
26437
26737
  error2(`[lsp] github install ${spec.id} crashed: ${err}`);
26438
26738
  return null;
26439
26739
  });
26440
- if (!archiveSha256) {
26740
+ if (!hashes) {
26441
26741
  return { started: true, reason: "install failed (see plugin log)" };
26442
26742
  }
26443
- writeInstalledMetaIn(ghPackageDir(spec), tag, archiveSha256);
26743
+ writeGithubInstalledMetaIn(ghPackageDir(spec), tag, hashes.binarySha256, hashes.archiveSha256);
26444
26744
  return { started: true };
26445
26745
  });
26446
26746
  if (outcome === null) {
@@ -26635,7 +26935,7 @@ function normalizeToolMap(tools) {
26635
26935
  }
26636
26936
 
26637
26937
  // src/notifications.ts
26638
- import { existsSync as existsSync11, mkdirSync as mkdirSync9, readFileSync as readFileSync10, renameSync as renameSync6, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
26938
+ import { existsSync as existsSync11, mkdirSync as mkdirSync9, readFileSync as readFileSync10, renameSync as renameSync7, rmSync as rmSync6, writeFileSync as writeFileSync9 } from "fs";
26639
26939
  import { homedir as homedir9, platform as platform2 } from "os";
26640
26940
  import { join as join16 } from "path";
26641
26941
  function isTuiMode() {
@@ -26742,7 +27042,7 @@ async function sendIgnoredMessage(client, sessionId, text) {
26742
27042
  return true;
26743
27043
  }
26744
27044
  } catch (err) {
26745
- sessionLog2(sessionId, `[aft-plugin] notification send failed: ${err instanceof Error ? err.message : String(err)}`);
27045
+ sessionLog(sessionId, `[aft-plugin] notification send failed: ${err instanceof Error ? err.message : String(err)}`);
26746
27046
  }
26747
27047
  return false;
26748
27048
  }
@@ -26768,7 +27068,7 @@ async function sendWarning(opts, message) {
26768
27068
  if (!sessionId)
26769
27069
  return;
26770
27070
  const text = `${WARNING_MARKER} ${message}`;
26771
- sessionLog2(sessionId, `[aft-plugin] sending warning to session ${sessionId}`);
27071
+ sessionLog(sessionId, `[aft-plugin] sending warning to session ${sessionId}`);
26772
27072
  await sendIgnoredMessage(opts.client, sessionId, text);
26773
27073
  }
26774
27074
  async function sendFeatureAnnouncement(opts, version2, features, storageDir) {
@@ -26791,13 +27091,13 @@ async function sendFeatureAnnouncement(opts, version2, features, storageDir) {
26791
27091
  return;
26792
27092
  const text = [`${FEATURE_MARKER} v${version2}:`, ...features.map((f) => ` \u2022 ${f}`)].join(`
26793
27093
  `);
26794
- sessionLog2(sessionId, `[aft-plugin] sending feature announcement for v${version2}`);
27094
+ sessionLog(sessionId, `[aft-plugin] sending feature announcement for v${version2}`);
26795
27095
  await sendIgnoredMessage(opts.client, sessionId, text);
26796
27096
  }
26797
27097
  if (storageDir) {
26798
27098
  try {
26799
27099
  mkdirSync9(storageDir, { recursive: true });
26800
- writeFileSync8(join16(storageDir, "last_announced_version"), version2);
27100
+ writeFileSync9(join16(storageDir, "last_announced_version"), version2);
26801
27101
  } catch {}
26802
27102
  }
26803
27103
  }
@@ -26825,9 +27125,9 @@ function writeWarnedTools(storageDir, warned) {
26825
27125
  mkdirSync9(storageDir, { recursive: true });
26826
27126
  const warnedToolsPath = join16(storageDir, WARNED_TOOLS_FILE);
26827
27127
  const tmpPath = join16(storageDir, `${WARNED_TOOLS_FILE}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
26828
- writeFileSync8(tmpPath, `${JSON.stringify(warned, null, 2)}
27128
+ writeFileSync9(tmpPath, `${JSON.stringify(warned, null, 2)}
26829
27129
  `);
26830
- renameSync6(tmpPath, warnedToolsPath);
27130
+ renameSync7(tmpPath, warnedToolsPath);
26831
27131
  } catch {}
26832
27132
  }
26833
27133
  async function withWarnedToolsLock(storageDir, fn) {
@@ -26839,7 +27139,7 @@ async function withWarnedToolsLock(storageDir, fn) {
26839
27139
  try {
26840
27140
  return await fn();
26841
27141
  } finally {
26842
- rmSync5(lockDir, { recursive: true, force: true });
27142
+ rmSync6(lockDir, { recursive: true, force: true });
26843
27143
  }
26844
27144
  } catch (err) {
26845
27145
  const code = err.code;
@@ -26940,7 +27240,7 @@ async function cleanupWarnings(opts) {
26940
27240
  }
26941
27241
  if (warningIds.length === 0)
26942
27242
  return;
26943
- sessionLog2(sessionId, `[aft-plugin] cleaning up ${warningIds.length} stale warning(s)`);
27243
+ sessionLog(sessionId, `[aft-plugin] cleaning up ${warningIds.length} stale warning(s)`);
26944
27244
  for (const id of warningIds) {
26945
27245
  await deleteMessage(effectiveServerUrl, sessionId, id);
26946
27246
  }
@@ -26948,16 +27248,16 @@ async function cleanupWarnings(opts) {
26948
27248
 
26949
27249
  // src/shared/rpc-server.ts
26950
27250
  import { randomBytes as randomBytes2 } from "crypto";
26951
- import { mkdirSync as mkdirSync10, renameSync as renameSync7, unlinkSync as unlinkSync7, writeFileSync as writeFileSync9 } from "fs";
27251
+ import { mkdirSync as mkdirSync10, renameSync as renameSync8, unlinkSync as unlinkSync7, writeFileSync as writeFileSync10 } from "fs";
26952
27252
  import { createServer } from "http";
26953
27253
  import { dirname as dirname6 } from "path";
26954
27254
 
26955
27255
  // src/shared/rpc-utils.ts
26956
- import { createHash as createHash5 } from "crypto";
27256
+ import { createHash as createHash6 } from "crypto";
26957
27257
  import { join as join17 } from "path";
26958
27258
  function projectHash(directory) {
26959
27259
  const normalized = directory.replace(/\/+$/, "");
26960
- return createHash5("sha256").update(normalized).digest("hex").slice(0, 16);
27260
+ return createHash6("sha256").update(normalized).digest("hex").slice(0, 16);
26961
27261
  }
26962
27262
  function rpcPortFilePath(storageDir, directory) {
26963
27263
  const hash2 = projectHash(directory);
@@ -26997,11 +27297,11 @@ class AftRpcServer {
26997
27297
  const dir = dirname6(this.portFilePath);
26998
27298
  mkdirSync10(dir, { recursive: true, mode: 448 });
26999
27299
  const tmpPath = `${this.portFilePath}.tmp`;
27000
- writeFileSync9(tmpPath, JSON.stringify({ port: this.port, token: this.token }), {
27300
+ writeFileSync10(tmpPath, JSON.stringify({ port: this.port, token: this.token }), {
27001
27301
  encoding: "utf-8",
27002
27302
  mode: 384
27003
27303
  });
27004
- renameSync7(tmpPath, this.portFilePath);
27304
+ renameSync8(tmpPath, this.portFilePath);
27005
27305
  log2(`RPC server listening on 127.0.0.1:${this.port}`);
27006
27306
  } catch (err) {
27007
27307
  warn2(`Failed to write RPC port file: ${err}`);
@@ -27104,7 +27404,7 @@ async function getSessionDirectory(client, sessionId, fallbackDirectory) {
27104
27404
  dir = session.directory;
27105
27405
  }
27106
27406
  } catch (err) {
27107
- sessionWarn2(sessionId, `[aft-plugin] session.get lookup failed: ${err instanceof Error ? err.message : String(err)}`);
27407
+ sessionWarn(sessionId, `[aft-plugin] session.get lookup failed: ${err instanceof Error ? err.message : String(err)}`);
27108
27408
  return null;
27109
27409
  }
27110
27410
  setCache(sessionId, dir);
@@ -27288,7 +27588,7 @@ function formatStatusMarkdown(status) {
27288
27588
 
27289
27589
  // src/shared/tui-config.ts
27290
27590
  var import_comment_json4 = __toESM(require_src2(), 1);
27291
- import { existsSync as existsSync12, mkdirSync as mkdirSync11, readFileSync as readFileSync11, writeFileSync as writeFileSync10 } from "fs";
27591
+ import { existsSync as existsSync12, mkdirSync as mkdirSync11, readFileSync as readFileSync11, writeFileSync as writeFileSync11 } from "fs";
27292
27592
  import { dirname as dirname7, join as join19 } from "path";
27293
27593
 
27294
27594
  // src/shared/opencode-config-dir.ts
@@ -27345,7 +27645,7 @@ function ensureTuiPluginEntry() {
27345
27645
  plugins.push(PLUGIN_ENTRY);
27346
27646
  config2.plugin = plugins;
27347
27647
  mkdirSync11(dirname7(configPath), { recursive: true });
27348
- writeFileSync10(configPath, `${import_comment_json4.stringify(config2, null, 2)}
27648
+ writeFileSync11(configPath, `${import_comment_json4.stringify(config2, null, 2)}
27349
27649
  `);
27350
27650
  log2(`[aft-plugin] added TUI plugin entry to ${configPath}`);
27351
27651
  return true;
@@ -28224,7 +28524,7 @@ var CACHE_MAX_ENTRIES2 = 200;
28224
28524
  var cache2 = new Map;
28225
28525
  async function resolveIsSubagent(client, sessionId, _fallbackDirectory) {
28226
28526
  if (!sessionId) {
28227
- sessionLog2(undefined, "[subagent-detect] no sessionId provided \u2192 primary");
28527
+ sessionLog(undefined, "[subagent-detect] no sessionId provided \u2192 primary");
28228
28528
  return false;
28229
28529
  }
28230
28530
  const cached2 = cache2.get(sessionId);
@@ -28236,11 +28536,11 @@ async function resolveIsSubagent(client, sessionId, _fallbackDirectory) {
28236
28536
  const c = client;
28237
28537
  const sessionApi = c?.session;
28238
28538
  if (!sessionApi || typeof sessionApi.get !== "function") {
28239
- sessionLog2(sessionId, `[subagent-detect] client.session.get unavailable (client=${typeof client}, session=${typeof sessionApi}, get=${typeof sessionApi?.get}) \u2192 caching as primary`);
28539
+ sessionLog(sessionId, `[subagent-detect] client.session.get unavailable (client=${typeof client}, session=${typeof sessionApi}, get=${typeof sessionApi?.get}) \u2192 caching as primary`);
28240
28540
  setCache2(sessionId, false);
28241
28541
  return false;
28242
28542
  }
28243
- sessionLog2(sessionId, `[subagent-detect] cache miss, calling client.session.get(id=${sessionId})`);
28543
+ sessionLog(sessionId, `[subagent-detect] cache miss, calling client.session.get(id=${sessionId})`);
28244
28544
  let isSubagent = false;
28245
28545
  let parentIdRaw;
28246
28546
  try {
@@ -28250,9 +28550,9 @@ async function resolveIsSubagent(client, sessionId, _fallbackDirectory) {
28250
28550
  const session = result?.data ?? result;
28251
28551
  parentIdRaw = session?.parentID;
28252
28552
  isSubagent = session !== undefined && typeof session.parentID === "string" && session.parentID.length > 0;
28253
- sessionLog2(sessionId, `[subagent-detect] SDK returned session=${session !== undefined ? "present" : "undefined"}, parentID=${JSON.stringify(parentIdRaw)} \u2192 isSubagent=${isSubagent}`);
28553
+ sessionLog(sessionId, `[subagent-detect] SDK returned session=${session !== undefined ? "present" : "undefined"}, parentID=${JSON.stringify(parentIdRaw)} \u2192 isSubagent=${isSubagent}`);
28254
28554
  } catch (err) {
28255
- sessionWarn2(sessionId, `[subagent-detect] SDK lookup failed: ${err instanceof Error ? err.message : String(err)}`);
28555
+ sessionWarn(sessionId, `[subagent-detect] SDK lookup failed: ${err instanceof Error ? err.message : String(err)}`);
28256
28556
  return false;
28257
28557
  }
28258
28558
  setCache2(sessionId, isSubagent);
@@ -28320,7 +28620,7 @@ function createBashTool(ctx) {
28320
28620
  const requestedBackground = args.background === true;
28321
28621
  const effectiveBackground = isSubagent ? false : requestedBackground;
28322
28622
  if (isSubagent && requestedBackground) {
28323
- sessionLog2(context.sessionID, "[bash] subagent + background:true \u2192 converting to foreground (subagent would lose task_id)");
28623
+ sessionLog(context.sessionID, "[bash] subagent + background:true \u2192 converting to foreground (subagent would lose task_id)");
28324
28624
  }
28325
28625
  const shellEnv = await ctx.plugin?.trigger?.("shell.env", { cwd, sessionID: context.sessionID, callID: getCallID(context) }, { env: {} });
28326
28626
  const data = await withPermissionLoop(ctx, context, {
@@ -30098,7 +30398,7 @@ async function queryLspHints(client, symbolName, directory, sessionId) {
30098
30398
  }
30099
30399
  return { symbols: hints };
30100
30400
  } catch (err) {
30101
- sessionWarn2(sessionId, `LSP query failed for "${symbolName}": ${err.message}`);
30401
+ sessionWarn(sessionId, `LSP query failed for "${symbolName}": ${err.message}`);
30102
30402
  return;
30103
30403
  }
30104
30404
  }
@@ -30887,31 +31187,37 @@ ${lines}
30887
31187
  } catch (err) {
30888
31188
  warn2(`[lsp] auto-install setup failed: ${err instanceof Error ? err.message : String(err)}`);
30889
31189
  }
30890
- let versionUpgradeAttempted = null;
31190
+ const versionUpgradePromises = new Map;
30891
31191
  const poolOptions = {
30892
31192
  errorPrefix: "[aft-plugin]",
30893
31193
  minVersion: PLUGIN_VERSION,
30894
31194
  onVersionMismatch: async (binaryVersion, minVersion) => {
30895
- if (versionUpgradeAttempted === binaryVersion) {
30896
- log2(`Version ${binaryVersion} < ${minVersion} but upgrade already attempted \u2014 continuing`);
30897
- return null;
31195
+ const existing = versionUpgradePromises.get(minVersion);
31196
+ if (existing) {
31197
+ log2(`Version ${binaryVersion} < ${minVersion}; awaiting in-flight compatible binary upgrade`);
31198
+ return existing;
30898
31199
  }
30899
- versionUpgradeAttempted = binaryVersion;
30900
- warn2(`WARNING: aft binary v${binaryVersion} is older than plugin v${minVersion}. ` + "Some features may not work. Attempting to download a compatible binary...");
30901
- try {
30902
- const path7 = await ensureBinary(`v${minVersion}`);
30903
- if (!path7) {
30904
- warn2(`Could not find or download v${minVersion}. Continuing with v${binaryVersion}.`);
31200
+ const upgradePromise = (async () => {
31201
+ warn2(`WARNING: aft binary v${binaryVersion} is older than plugin v${minVersion}. ` + "Some features may not work. Attempting to download a compatible binary...");
31202
+ try {
31203
+ const path7 = await ensureBinary(`v${minVersion}`);
31204
+ if (!path7) {
31205
+ warn2(`Could not find or download v${minVersion}. Continuing with v${binaryVersion}.`);
31206
+ return null;
31207
+ }
31208
+ log2(`Found/downloaded compatible binary at ${path7}. Replacing running bridges...`);
31209
+ const replaced = await pool.replaceBinary(path7);
31210
+ log2("Binary replaced successfully. New bridges will use the updated binary.");
31211
+ return replaced;
31212
+ } catch (err) {
31213
+ error2(`Auto-download failed: ${err.message}. Install manually: cargo install agent-file-tools@${minVersion}`);
30905
31214
  return null;
31215
+ } finally {
31216
+ versionUpgradePromises.delete(minVersion);
30906
31217
  }
30907
- log2(`Found/downloaded compatible binary at ${path7}. Replacing running bridges...`);
30908
- const replaced = await pool.replaceBinary(path7);
30909
- log2("Binary replaced successfully. New bridges will use the updated binary.");
30910
- return replaced;
30911
- } catch (err) {
30912
- error2(`Auto-download failed: ${err.message}. Install manually: cargo install agent-file-tools@${minVersion}`);
30913
- return null;
30914
- }
31218
+ })();
31219
+ versionUpgradePromises.set(minVersion, upgradePromise);
31220
+ return upgradePromise;
30915
31221
  },
30916
31222
  onConfigureWarnings: async ({ projectRoot, sessionId, client, warnings }) => {
30917
31223
  const pendingWarnings = sessionId ? drainPendingEagerWarnings(projectRoot) : [];
@@ -31045,7 +31351,7 @@ ${lines}
31045
31351
  if (storageDir && ANNOUNCEMENT_VERSION) {
31046
31352
  try {
31047
31353
  mkdirSync12(storageDir, { recursive: true });
31048
- writeFileSync11(join21(storageDir, "last_announced_version"), ANNOUNCEMENT_VERSION);
31354
+ writeFileSync12(join21(storageDir, "last_announced_version"), ANNOUNCEMENT_VERSION);
31049
31355
  } catch {}
31050
31356
  }
31051
31357
  return { success: true };