@cortexkit/aft-opencode 0.25.2 → 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";
@@ -7931,6 +7942,15 @@ function clampSemanticTimeout(configOverrides, bridgeTimeoutMs) {
7931
7942
  };
7932
7943
  }
7933
7944
 
7945
+ class BridgeReplacedDuringVersionCheck extends Error {
7946
+ newBinaryPath;
7947
+ constructor(newBinaryPath) {
7948
+ super(`Bridge binary replaced during version check: ${newBinaryPath}`);
7949
+ this.newBinaryPath = newBinaryPath;
7950
+ this.name = "BridgeReplacedDuringVersionCheck";
7951
+ }
7952
+ }
7953
+
7934
7954
  class BinaryBridge {
7935
7955
  static RESTART_RESET_MS = 5 * 60 * 1000;
7936
7956
  static STDERR_TAIL_MAX = 20;
@@ -7940,6 +7960,7 @@ class BinaryBridge {
7940
7960
  pending = new Map;
7941
7961
  nextId = 1;
7942
7962
  stdoutBuffer = "";
7963
+ stderrBuffer = "";
7943
7964
  stderrTail = [];
7944
7965
  _restartCount = 0;
7945
7966
  _shuttingDown = false;
@@ -7975,24 +7996,62 @@ class BinaryBridge {
7975
7996
  }
7976
7997
  logVia(message, meta) {
7977
7998
  const logger = this.logger ?? getActiveLogger();
7978
- if (logger)
7979
- logger.log(message, meta);
7980
- 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 {
7981
8007
  log(message, meta);
8008
+ }
7982
8009
  }
7983
8010
  warnVia(message, meta) {
7984
8011
  const logger = this.logger ?? getActiveLogger();
7985
- if (logger)
7986
- logger.warn(message, meta);
7987
- 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 {
7988
8020
  warn(message, meta);
8021
+ }
7989
8022
  }
7990
8023
  errorVia(message, meta) {
7991
8024
  const logger = this.logger ?? getActiveLogger();
7992
- if (logger)
7993
- logger.error(message, meta);
7994
- 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 {
7995
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);
7996
8055
  }
7997
8056
  get restartCount() {
7998
8057
  return this._restartCount;
@@ -8019,97 +8078,108 @@ class BinaryBridge {
8019
8078
  this.cachedStatus = snapshot;
8020
8079
  }
8021
8080
  async send(command, params = {}, options) {
8022
- if (this._shuttingDown) {
8023
- throw new Error(`${this.errorPrefix} Bridge is shutting down, cannot send "${command}"`);
8024
- }
8025
- if (Object.hasOwn(params, "id")) {
8026
- throw new Error("params cannot contain reserved key 'id'");
8027
- }
8028
- const requestSessionId = typeof params.session_id === "string" && params.session_id.length > 0 ? params.session_id : undefined;
8029
- this.ensureSpawned(requestSessionId);
8030
- if (requestSessionId && options?.configureWarningClient !== undefined) {
8031
- this.configureWarningClients.set(requestSessionId, options.configureWarningClient);
8032
- }
8033
- if (!this.configured) {
8034
- if (command !== "configure" && command !== "version") {
8035
- if (!this._configurePromise) {
8036
- const sessionIdForConfigure = typeof params.session_id === "string" ? params.session_id : undefined;
8037
- this._configurePromise = (async () => {
8038
- try {
8039
- const configResult = await this.send("configure", {
8040
- project_root: this.cwd,
8041
- ...this.configOverrides,
8042
- ...sessionIdForConfigure ? { session_id: sessionIdForConfigure } : {}
8043
- });
8044
- if (configResult.success === false) {
8045
- throw new Error(`${this.errorPrefix} Configure failed: ${configResult.message ?? "unknown error"}`);
8046
- }
8047
- await this.deliverConfigureWarnings(configResult, params, options);
8048
- await this.checkVersion();
8049
- if (!this.isAlive()) {
8050
- throw new Error(`${this.errorPrefix} Bridge died during version check. Check logs: ${getLogFilePath()}`);
8081
+ return this.sendWithVersionMismatchRetry(command, params, options, true);
8082
+ }
8083
+ async sendWithVersionMismatchRetry(command, params, options, canRetryAfterVersionSwap) {
8084
+ try {
8085
+ if (this._shuttingDown) {
8086
+ throw new Error(`${this.errorPrefix} Bridge is shutting down, cannot send "${command}"`);
8087
+ }
8088
+ if (Object.hasOwn(params, "id")) {
8089
+ throw new Error("params cannot contain reserved key 'id'");
8090
+ }
8091
+ const requestSessionId = typeof params.session_id === "string" && params.session_id.length > 0 ? params.session_id : undefined;
8092
+ this.ensureSpawned(requestSessionId);
8093
+ if (requestSessionId && options?.configureWarningClient !== undefined) {
8094
+ this.configureWarningClients.set(requestSessionId, options.configureWarningClient);
8095
+ }
8096
+ if (!this.configured) {
8097
+ if (command !== "configure" && command !== "version") {
8098
+ if (!this._configurePromise) {
8099
+ const sessionIdForConfigure = typeof params.session_id === "string" ? params.session_id : undefined;
8100
+ this._configurePromise = (async () => {
8101
+ try {
8102
+ const configResult = await this.send("configure", {
8103
+ project_root: this.cwd,
8104
+ ...this.configOverrides,
8105
+ ...sessionIdForConfigure ? { session_id: sessionIdForConfigure } : {}
8106
+ });
8107
+ if (configResult.success === false) {
8108
+ throw new Error(`${this.errorPrefix} Configure failed: ${configResult.message ?? "unknown error"}`);
8109
+ }
8110
+ await this.deliverConfigureWarnings(configResult, params, options);
8111
+ await this.checkVersion();
8112
+ if (!this.isAlive()) {
8113
+ throw new Error(`${this.errorPrefix} Bridge died during version check. Check logs: ${this.getLogFilePathVia()}`);
8114
+ }
8115
+ this.configured = true;
8116
+ } finally {
8117
+ this._configurePromise = null;
8051
8118
  }
8052
- this.configured = true;
8053
- } finally {
8054
- this._configurePromise = null;
8055
- }
8056
- })();
8057
- }
8058
- await this._configurePromise;
8059
- }
8060
- }
8061
- const id = String(this.nextId++);
8062
- let request;
8063
- if (Object.hasOwn(params, "command") || Object.hasOwn(params, "method")) {
8064
- const nested = { ...params };
8065
- const reserved = {};
8066
- for (const key of ["session_id", "lsp_hints"]) {
8067
- if (Object.hasOwn(nested, key)) {
8068
- reserved[key] = nested[key];
8069
- delete nested[key];
8119
+ })();
8120
+ }
8121
+ await this._configurePromise;
8122
+ }
8123
+ }
8124
+ const id = String(this.nextId++);
8125
+ let request;
8126
+ if (Object.hasOwn(params, "command") || Object.hasOwn(params, "method")) {
8127
+ const nested = { ...params };
8128
+ const reserved = {};
8129
+ for (const key of ["session_id", "lsp_hints"]) {
8130
+ if (Object.hasOwn(nested, key)) {
8131
+ reserved[key] = nested[key];
8132
+ delete nested[key];
8133
+ }
8070
8134
  }
8135
+ request = { id, command, ...reserved, params: nested };
8136
+ } else {
8137
+ request = { id, command, ...params };
8071
8138
  }
8072
- request = { id, command, ...reserved, params: nested };
8073
- } else {
8074
- request = { id, command, ...params };
8075
- }
8076
- const line = `${JSON.stringify(request)}
8139
+ const line = `${JSON.stringify(request)}
8077
8140
  `;
8078
- const effectiveTimeoutMs = options?.transportTimeoutMs ?? options?.timeoutMs ?? this.timeoutMs;
8079
- const keepBridgeOnTimeout = options?.keepBridgeOnTimeout === true;
8080
- return new Promise((resolve, reject) => {
8081
- const timer = setTimeout(() => {
8082
- this.pending.delete(id);
8083
- const restartSuffix = keepBridgeOnTimeout ? "" : " \u2014 restarting bridge";
8084
- const timeoutMsg = `Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms${restartSuffix}`;
8085
- if (requestSessionId) {
8086
- sessionWarn(requestSessionId, timeoutMsg);
8087
- } else {
8088
- warn(timeoutMsg);
8089
- }
8090
- reject(new Error(`${this.errorPrefix} Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
8091
- if (!keepBridgeOnTimeout) {
8092
- this.handleTimeout(requestSessionId);
8093
- }
8094
- }, effectiveTimeoutMs);
8095
- this.pending.set(id, { resolve, reject, timer, onProgress: options?.onProgress });
8096
- if (!this.process?.stdin?.writable) {
8097
- this.pending.delete(id);
8098
- clearTimeout(timer);
8099
- reject(new Error(`${this.errorPrefix} stdin not writable for command "${command}"`));
8100
- return;
8101
- }
8102
- this.process.stdin.write(line, (err) => {
8103
- if (err) {
8104
- const entry = this.pending.get(id);
8105
- if (entry) {
8106
- this.pending.delete(id);
8107
- clearTimeout(entry.timer);
8108
- entry.reject(new Error(`${this.errorPrefix} Failed to write to stdin: ${err.message}`));
8141
+ const effectiveTimeoutMs = options?.transportTimeoutMs ?? options?.timeoutMs ?? this.timeoutMs;
8142
+ const keepBridgeOnTimeout = options?.keepBridgeOnTimeout === true;
8143
+ return new Promise((resolve, reject) => {
8144
+ const timer = setTimeout(() => {
8145
+ this.pending.delete(id);
8146
+ const restartSuffix = keepBridgeOnTimeout ? "" : " \u2014 restarting bridge";
8147
+ const timeoutMsg = `Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms${restartSuffix}`;
8148
+ if (requestSessionId) {
8149
+ this.sessionWarnVia(requestSessionId, timeoutMsg);
8150
+ } else {
8151
+ this.warnVia(timeoutMsg);
8109
8152
  }
8153
+ reject(new Error(`${this.errorPrefix} Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
8154
+ if (!keepBridgeOnTimeout) {
8155
+ this.handleTimeout(requestSessionId);
8156
+ }
8157
+ }, effectiveTimeoutMs);
8158
+ this.pending.set(id, { resolve, reject, timer, onProgress: options?.onProgress });
8159
+ if (!this.process?.stdin?.writable) {
8160
+ this.pending.delete(id);
8161
+ clearTimeout(timer);
8162
+ reject(new Error(`${this.errorPrefix} stdin not writable for command "${command}"`));
8163
+ return;
8110
8164
  }
8165
+ this.process.stdin.write(line, (err) => {
8166
+ if (err) {
8167
+ const entry = this.pending.get(id);
8168
+ if (entry) {
8169
+ this.pending.delete(id);
8170
+ clearTimeout(entry.timer);
8171
+ entry.reject(new Error(`${this.errorPrefix} Failed to write to stdin: ${err.message}`));
8172
+ }
8173
+ }
8174
+ });
8111
8175
  });
8112
- });
8176
+ } catch (err) {
8177
+ if (err instanceof BridgeReplacedDuringVersionCheck && canRetryAfterVersionSwap && command !== "configure" && command !== "version") {
8178
+ this.logVia(`Retrying request "${command}" once after coordinated binary replacement: ${err.newBinaryPath}`);
8179
+ return this.sendWithVersionMismatchRetry(command, params, options, false);
8180
+ }
8181
+ throw err;
8182
+ }
8113
8183
  }
8114
8184
  async deliverConfigureWarnings(configResult, params, options) {
8115
8185
  if (!this.onConfigureWarnings || !Array.isArray(configResult.warnings))
@@ -8125,7 +8195,7 @@ class BinaryBridge {
8125
8195
  warnings: configResult.warnings
8126
8196
  });
8127
8197
  } catch (err) {
8128
- 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)}`);
8129
8199
  } finally {
8130
8200
  if (sessionId) {
8131
8201
  this.configureWarningClients.delete(sessionId);
@@ -8159,7 +8229,7 @@ class BinaryBridge {
8159
8229
  if (!snapshot || typeof snapshot !== "object" || Array.isArray(snapshot))
8160
8230
  return;
8161
8231
  this.cachedStatus = snapshot;
8162
- log("Received status_changed push frame; cached AFT status snapshot");
8232
+ this.logVia("Received status_changed push frame; cached AFT status snapshot");
8163
8233
  for (const listener of this.statusListeners) {
8164
8234
  this.deliverStatusSnapshot(listener, this.cachedStatus);
8165
8235
  }
@@ -8168,7 +8238,7 @@ class BinaryBridge {
8168
8238
  try {
8169
8239
  listener(snapshot);
8170
8240
  } catch (err) {
8171
- warn(`status listener threw: ${err instanceof Error ? err.message : String(err)}`);
8241
+ this.warnVia(`status listener threw: ${err instanceof Error ? err.message : String(err)}`);
8172
8242
  }
8173
8243
  }
8174
8244
  async shutdown() {
@@ -8186,7 +8256,7 @@ class BinaryBridge {
8186
8256
  }, 5000);
8187
8257
  proc.once("exit", () => {
8188
8258
  clearTimeout(forceKillTimer);
8189
- log("Process exited during shutdown");
8259
+ this.logVia("Process exited during shutdown");
8190
8260
  resolve();
8191
8261
  });
8192
8262
  proc.kill("SIGTERM");
@@ -8205,16 +8275,46 @@ class BinaryBridge {
8205
8275
  if (typeof binaryVersion !== "string") {
8206
8276
  throw new Error(`Binary did not report a version \u2014 likely too old (minVersion: ${this.minVersion})`);
8207
8277
  }
8208
- log(`Binary version: ${binaryVersion}`);
8278
+ this.logVia(`Binary version: ${binaryVersion}`);
8209
8279
  if (compareSemver(binaryVersion, this.minVersion) < 0) {
8210
- warn(`Binary version ${binaryVersion} is older than required ${this.minVersion}`);
8211
- this.onVersionMismatch?.(binaryVersion, this.minVersion);
8280
+ this.warnVia(`Binary version ${binaryVersion} is older than required ${this.minVersion}`);
8281
+ const replacementPath = await this.onVersionMismatch?.(binaryVersion, this.minVersion);
8282
+ if (replacementPath === undefined) {
8283
+ return;
8284
+ }
8285
+ if (replacementPath === null || replacementPath.length === 0) {
8286
+ throw new Error(`Binary version ${binaryVersion} is older than required ${this.minVersion}; no compatible replacement binary was provided`);
8287
+ }
8288
+ await this.replaceCurrentBinary(replacementPath);
8289
+ throw new BridgeReplacedDuringVersionCheck(replacementPath);
8212
8290
  }
8213
8291
  } catch (err) {
8214
- warn(`Version check failed: ${err.message}`);
8292
+ this.warnVia(`Version check failed: ${err.message}`);
8215
8293
  throw err;
8216
8294
  }
8217
8295
  }
8296
+ async replaceCurrentBinary(newBinaryPath) {
8297
+ this.binaryPath = newBinaryPath;
8298
+ this.configured = false;
8299
+ this.clearRestartResetTimer();
8300
+ this.rejectAllPending(new Error(`${this.errorPrefix} Bridge restarting with updated binary: ${newBinaryPath}`));
8301
+ if (!this.process)
8302
+ return;
8303
+ const proc = this.process;
8304
+ this.process = null;
8305
+ await new Promise((resolve) => {
8306
+ const forceKillTimer = setTimeout(() => {
8307
+ proc.kill("SIGKILL");
8308
+ resolve();
8309
+ }, 5000);
8310
+ proc.once("exit", () => {
8311
+ clearTimeout(forceKillTimer);
8312
+ this.logVia("Process exited during coordinated binary replacement");
8313
+ resolve();
8314
+ });
8315
+ proc.kill("SIGTERM");
8316
+ });
8317
+ }
8218
8318
  ensureSpawned(triggeringSessionId) {
8219
8319
  if (this.isAlive())
8220
8320
  return;
@@ -8222,9 +8322,9 @@ class BinaryBridge {
8222
8322
  }
8223
8323
  spawnProcess(triggeringSessionId) {
8224
8324
  if (triggeringSessionId) {
8225
- sessionLog(triggeringSessionId, `Spawning binary: ${this.binaryPath} (cwd: ${this.cwd})`);
8325
+ this.sessionLogVia(triggeringSessionId, `Spawning binary: ${this.binaryPath} (cwd: ${this.cwd})`);
8226
8326
  } else {
8227
- log(`Spawning binary: ${this.binaryPath} (cwd: ${this.cwd})`);
8327
+ this.logVia(`Spawning binary: ${this.binaryPath} (cwd: ${this.cwd})`);
8228
8328
  }
8229
8329
  const semantic = this.configOverrides.semantic;
8230
8330
  const semanticBackend = (() => {
@@ -8271,11 +8371,12 @@ class BinaryBridge {
8271
8371
  const remaining = stderrDecoder.end();
8272
8372
  if (remaining)
8273
8373
  this.onStderrData(remaining);
8374
+ this.flushStderrBuffer();
8274
8375
  });
8275
8376
  child.on("error", (err) => {
8276
8377
  if (this.process !== currentChild)
8277
8378
  return;
8278
- error(`Process error: ${err.message}${this.formatStderrTail()}`);
8379
+ this.errorVia(`Process error: ${err.message}${this.formatStderrTail()}`);
8279
8380
  this.handleCrash();
8280
8381
  });
8281
8382
  child.on("exit", (code, signal) => {
@@ -8283,7 +8384,7 @@ class BinaryBridge {
8283
8384
  return;
8284
8385
  if (this._shuttingDown)
8285
8386
  return;
8286
- log(`Process exited: code=${code}, signal=${signal}`);
8387
+ this.logVia(`Process exited: code=${code}, signal=${signal}`);
8287
8388
  if (signal === "SIGTERM" || signal === "SIGKILL" || signal === "SIGHUP" || signal === "SIGINT") {
8288
8389
  this.process = null;
8289
8390
  this.configured = false;
@@ -8295,6 +8396,7 @@ class BinaryBridge {
8295
8396
  });
8296
8397
  this.process = child;
8297
8398
  this.stdoutBuffer = "";
8399
+ this.stderrBuffer = "";
8298
8400
  this.stderrTail = [];
8299
8401
  }
8300
8402
  pushStderrLine(line) {
@@ -8304,16 +8406,28 @@ class BinaryBridge {
8304
8406
  }
8305
8407
  }
8306
8408
  onStderrData(data) {
8307
- const lines = data.trimEnd().split(`
8308
- `);
8309
- 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);
8310
8415
  if (!line)
8311
8416
  continue;
8312
8417
  const tagged = tagStderrLine(line);
8313
- log(tagged);
8418
+ this.logVia(tagged);
8314
8419
  this.pushStderrLine(tagged);
8315
8420
  }
8316
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
+ }
8317
8431
  formatStderrTail() {
8318
8432
  if (this.stderrTail.length === 0)
8319
8433
  return "";
@@ -8371,7 +8485,7 @@ class BinaryBridge {
8371
8485
  }
8372
8486
  if (response.type === "configure_warnings") {
8373
8487
  this.handleConfigureWarningsFrame(response).catch((err) => {
8374
- 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)}`);
8375
8489
  });
8376
8490
  continue;
8377
8491
  }
@@ -8389,10 +8503,10 @@ class BinaryBridge {
8389
8503
  this.scheduleRestartCountReset();
8390
8504
  entry.resolve(response);
8391
8505
  } else if (typeof response.type === "string") {
8392
- log(`Ignoring unknown stdout push frame type: ${response.type}`);
8506
+ this.logVia(`Ignoring unknown stdout push frame type: ${response.type}`);
8393
8507
  }
8394
8508
  } catch (_err) {
8395
- warn(`Failed to parse stdout line: ${line}`);
8509
+ this.warnVia(`Failed to parse stdout line: ${line}`);
8396
8510
  }
8397
8511
  }
8398
8512
  }
@@ -8406,17 +8520,17 @@ class BinaryBridge {
8406
8520
  this.configured = false;
8407
8521
  const tail = this.formatStderrTail();
8408
8522
  this.stderrTail = [];
8409
- 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()})`;
8410
8524
  if (tail) {
8411
8525
  if (triggeringSessionId) {
8412
- sessionError(triggeringSessionId, killedMsg);
8526
+ this.sessionErrorVia(triggeringSessionId, killedMsg);
8413
8527
  } else {
8414
- error(killedMsg);
8528
+ this.errorVia(killedMsg);
8415
8529
  }
8416
8530
  } else if (triggeringSessionId) {
8417
- sessionWarn(triggeringSessionId, killedMsg);
8531
+ this.sessionWarnVia(triggeringSessionId, killedMsg);
8418
8532
  } else {
8419
- warn(killedMsg);
8533
+ this.warnVia(killedMsg);
8420
8534
  }
8421
8535
  }
8422
8536
  handleCrash(cause) {
@@ -8429,25 +8543,25 @@ class BinaryBridge {
8429
8543
  this.configured = false;
8430
8544
  const tail = this.formatStderrTail();
8431
8545
  if (tail) {
8432
- error(`Binary crashed (restarts: ${this._restartCount})${cause ? `: ${cause.message}` : ""}.${tail}`);
8546
+ this.errorVia(`Binary crashed (restarts: ${this._restartCount})${cause ? `: ${cause.message}` : ""}.${tail}`);
8433
8547
  }
8434
- 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()})`));
8435
8549
  if (this._restartCount < this.maxRestarts) {
8436
8550
  const delay = 100 * 2 ** this._restartCount;
8437
8551
  this._restartCount++;
8438
- log(`Auto-restart #${this._restartCount} in ${delay}ms`);
8552
+ this.logVia(`Auto-restart #${this._restartCount} in ${delay}ms`);
8439
8553
  setTimeout(() => {
8440
8554
  if (!this._shuttingDown && !this.isAlive()) {
8441
8555
  try {
8442
8556
  this.spawnProcess();
8443
8557
  } catch (err) {
8444
- error(`Failed to restart: ${err.message}`);
8558
+ this.errorVia(`Failed to restart: ${err.message}`);
8445
8559
  }
8446
8560
  }
8447
8561
  }, delay);
8448
8562
  this.scheduleRestartCountReset();
8449
8563
  } else {
8450
- 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}`);
8451
8565
  this.scheduleRestartCountReset();
8452
8566
  }
8453
8567
  }
@@ -8473,9 +8587,12 @@ class BinaryBridge {
8473
8587
  }
8474
8588
  }
8475
8589
  // ../aft-bridge/dist/downloader.js
8476
- 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";
8477
8592
  import { homedir as homedir2 } from "os";
8478
8593
  import { join as join2 } from "path";
8594
+ import { Readable } from "stream";
8595
+ import { pipeline } from "stream/promises";
8479
8596
 
8480
8597
  // ../aft-bridge/dist/platform.js
8481
8598
  var PLATFORM_ARCH_MAP = {
@@ -8493,6 +8610,11 @@ var PLATFORM_ASSET_MAP = {
8493
8610
 
8494
8611
  // ../aft-bridge/dist/downloader.js
8495
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;
8496
8618
  function getCacheDir() {
8497
8619
  if (process.platform === "win32") {
8498
8620
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
@@ -8534,54 +8656,101 @@ async function downloadBinary(version) {
8534
8656
  const downloadUrl = `https://github.com/${REPO}/releases/download/${tag}/${assetName}`;
8535
8657
  const checksumUrl = `https://github.com/${REPO}/releases/download/${tag}/checksums.sha256`;
8536
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`;
8537
8666
  try {
8538
8667
  if (!existsSync(versionedCacheDir)) {
8539
8668
  mkdirSync(versionedCacheDir, { recursive: true });
8540
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);
8541
8680
  const [binaryResponse, checksumResponse] = await Promise.all([
8542
- fetch(downloadUrl, { redirect: "follow" }),
8543
- fetch(checksumUrl, { redirect: "follow" })
8681
+ fetch(downloadUrl, { redirect: "follow", signal: activeBinaryController.signal }),
8682
+ fetch(checksumUrl, { redirect: "follow", signal: activeChecksumController.signal })
8544
8683
  ]);
8545
8684
  if (!binaryResponse.ok) {
8546
8685
  throw new Error(`HTTP ${binaryResponse.status}: ${binaryResponse.statusText} (${downloadUrl})`);
8547
8686
  }
8548
- 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
+ }
8549
8694
  if (!checksumResponse.ok) {
8550
8695
  warn(`Checksum verification failed: no checksums.sha256 found for ${tag}. ` + "Binary download aborted for security reasons.");
8551
8696
  return null;
8552
8697
  }
8553
8698
  const checksumText = await checksumResponse.text();
8699
+ clearTimeout(checksumTimeout);
8700
+ checksumTimeout = null;
8554
8701
  const expectedHash = parseChecksumForAsset(checksumText, assetName);
8555
8702
  if (!expectedHash) {
8556
8703
  warn(`Checksum verification failed: checksums.sha256 found but no entry for ${assetName}. ` + "Binary download aborted for security reasons.");
8557
8704
  return null;
8558
8705
  }
8559
- const { createHash } = await import("crypto");
8560
- 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");
8561
8725
  if (actualHash !== expectedHash) {
8562
- 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.");
8563
8727
  }
8564
8728
  log(`Checksum verified (SHA-256: ${actualHash.slice(0, 16)}...)`);
8565
- const tmpPath = `${binaryPath}.tmp`;
8566
- const { writeFileSync } = await import("fs");
8567
- writeFileSync(tmpPath, Buffer.from(arrayBuffer));
8568
8729
  if (process.platform !== "win32") {
8569
8730
  chmodSync(tmpPath, 493);
8570
8731
  }
8571
- const { renameSync } = await import("fs");
8572
8732
  renameSync(tmpPath, binaryPath);
8573
8733
  log(`AFT binary ready at ${binaryPath}`);
8574
8734
  return binaryPath;
8575
8735
  } catch (err) {
8576
8736
  const msg = err instanceof Error ? err.message : String(err);
8577
8737
  error(`Failed to download AFT binary: ${msg}`);
8578
- const tmpPath = `${binaryPath}.tmp`;
8579
8738
  if (existsSync(tmpPath)) {
8580
8739
  try {
8581
8740
  unlinkSync(tmpPath);
8582
8741
  } catch {}
8583
8742
  }
8584
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?.();
8585
8754
  }
8586
8755
  }
8587
8756
  async function ensureBinary(version) {
@@ -8598,6 +8767,39 @@ async function ensureBinary(version) {
8598
8767
  log("No cached binary found, downloading latest...");
8599
8768
  return downloadBinary();
8600
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
+ }
8601
8803
  function parseChecksumForAsset(checksumText, assetName) {
8602
8804
  for (const line of checksumText.split(`
8603
8805
  `)) {
@@ -8612,9 +8814,12 @@ function parseChecksumForAsset(checksumText, assetName) {
8612
8814
  return null;
8613
8815
  }
8614
8816
  async function fetchLatestTag() {
8817
+ const controller = new AbortController;
8818
+ const timeout = setTimeout(() => controller.abort(), LATEST_TAG_TIMEOUT_MS);
8615
8819
  try {
8616
8820
  const response = await fetch(`https://api.github.com/repos/${REPO}/releases/latest`, {
8617
- headers: { Accept: "application/vnd.github.v3+json" }
8821
+ headers: { Accept: "application/vnd.github.v3+json" },
8822
+ signal: controller.signal
8618
8823
  });
8619
8824
  if (!response.ok)
8620
8825
  return null;
@@ -8622,18 +8827,20 @@ async function fetchLatestTag() {
8622
8827
  return data.tag_name ?? null;
8623
8828
  } catch {
8624
8829
  return null;
8830
+ } finally {
8831
+ clearTimeout(timeout);
8625
8832
  }
8626
8833
  }
8627
8834
  // ../aft-bridge/dist/onnx-runtime.js
8628
8835
  import { execFileSync } from "child_process";
8629
- import { createHash } from "crypto";
8630
- 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";
8631
8838
  import { dirname, join as join3, relative, resolve } from "path";
8632
- import { Readable } from "stream";
8633
- import { pipeline } from "stream/promises";
8839
+ import { Readable as Readable2 } from "stream";
8840
+ import { pipeline as pipeline2 } from "stream/promises";
8634
8841
  var ORT_VERSION = "1.24.4";
8635
8842
  var ORT_REPO = "microsoft/onnxruntime";
8636
- var MAX_DOWNLOAD_BYTES = 256 * 1024 * 1024;
8843
+ var MAX_DOWNLOAD_BYTES2 = 256 * 1024 * 1024;
8637
8844
  var MAX_EXTRACT_BYTES = 1 * 1024 * 1024 * 1024;
8638
8845
  var ONNX_LOCK_FILE = ".aft-onnx-installing";
8639
8846
  var ONNX_INSTALLED_META_FILE = ".aft-onnx-installed";
@@ -8754,7 +8961,7 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
8754
8961
  if (Number.isFinite(pid) && pid > 0) {
8755
8962
  if (process.platform === "win32") {
8756
8963
  try {
8757
- const ageMs = Date.now() - statSync(stagingDir).mtimeMs;
8964
+ const ageMs = Date.now() - statSync2(stagingDir).mtimeMs;
8758
8965
  abandoned = ageMs > STALE_LOCK_MS;
8759
8966
  } catch {
8760
8967
  abandoned = true;
@@ -8768,7 +8975,7 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
8768
8975
  if (abandoned) {
8769
8976
  log(`[onnx] removing abandoned staging dir ${stagingDir}`);
8770
8977
  try {
8771
- rmSync(stagingDir, { recursive: true, force: true });
8978
+ rmSync2(stagingDir, { recursive: true, force: true });
8772
8979
  } catch (err) {
8773
8980
  warn(`[onnx] failed to remove ${stagingDir}: ${err}`);
8774
8981
  }
@@ -8780,7 +8987,7 @@ function cleanupIncompleteTargetIfUnowned(ortDir) {
8780
8987
  try {
8781
8988
  if (existsSync2(ortDir) && !existsSync2(join3(ortDir, ONNX_INSTALLED_META_FILE))) {
8782
8989
  log(`[onnx] removing half-populated install dir ${ortDir} (no meta file)`);
8783
- rmSync(ortDir, { recursive: true, force: true });
8990
+ rmSync2(ortDir, { recursive: true, force: true });
8784
8991
  }
8785
8992
  } catch (err) {
8786
8993
  warn(`[onnx] failed to sweep ${ortDir}: ${err}`);
@@ -8860,24 +9067,24 @@ async function downloadFileWithCap(url, destPath) {
8860
9067
  throw new Error(`download failed (HTTP ${res.status})`);
8861
9068
  }
8862
9069
  const advertised = Number.parseInt(res.headers.get("content-length") ?? "", 10);
8863
- if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES) {
8864
- 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}`);
8865
9072
  }
8866
9073
  mkdirSync2(dirname(destPath), { recursive: true });
8867
9074
  let bytesWritten = 0;
8868
9075
  const guard = new TransformStream({
8869
9076
  transform(chunk, transformController) {
8870
9077
  bytesWritten += chunk.byteLength;
8871
- if (bytesWritten > MAX_DOWNLOAD_BYTES) {
8872
- 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)`));
8873
9080
  return;
8874
9081
  }
8875
9082
  transformController.enqueue(chunk);
8876
9083
  }
8877
9084
  });
8878
9085
  const guarded = res.body.pipeThrough(guard);
8879
- const nodeStream = Readable.fromWeb(guarded);
8880
- await pipeline(nodeStream, createWriteStream(destPath), { signal: controller.signal });
9086
+ const nodeStream = Readable2.fromWeb(guarded);
9087
+ await pipeline2(nodeStream, createWriteStream2(destPath), { signal: controller.signal });
8881
9088
  } catch (err) {
8882
9089
  try {
8883
9090
  unlinkSync2(destPath);
@@ -8976,16 +9183,16 @@ async function downloadOnnxRuntime(info, targetDir) {
8976
9183
  warn(`Could not hash newly-installed ONNX library at ${libPath}: ${err}`);
8977
9184
  }
8978
9185
  writeOnnxInstalledMeta(targetDir, ORT_VERSION, libHash, archiveSha256);
8979
- rmSync(tmpDir, { recursive: true, force: true });
9186
+ rmSync2(tmpDir, { recursive: true, force: true });
8980
9187
  log(`ONNX Runtime v${ORT_VERSION} installed to ${targetDir}`);
8981
9188
  return targetDir;
8982
9189
  } catch (err) {
8983
9190
  error(`Failed to download ONNX Runtime: ${err}`);
8984
9191
  try {
8985
- rmSync(tmpDir, { recursive: true, force: true });
9192
+ rmSync2(tmpDir, { recursive: true, force: true });
8986
9193
  } catch {}
8987
9194
  try {
8988
- rmSync(targetDir, { recursive: true, force: true });
9195
+ rmSync2(targetDir, { recursive: true, force: true });
8989
9196
  } catch {}
8990
9197
  return null;
8991
9198
  }
@@ -9002,7 +9209,7 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
9002
9209
  }
9003
9210
  } catch (copyErr) {
9004
9211
  if (requiredLibs.has(libFile)) {
9005
- rmSync(targetDir, { recursive: true, force: true });
9212
+ rmSync2(targetDir, { recursive: true, force: true });
9006
9213
  throw copyErr;
9007
9214
  }
9008
9215
  log(`ORT extract: failed to copy optional ${libFile}: ${copyErr}`);
@@ -9017,7 +9224,7 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
9017
9224
  symlinkSync(link.target, dst);
9018
9225
  } catch (symlinkErr) {
9019
9226
  if (requiredLibs.has(link.name)) {
9020
- rmSync(targetDir, { recursive: true, force: true });
9227
+ rmSync2(targetDir, { recursive: true, force: true });
9021
9228
  throw symlinkErr;
9022
9229
  }
9023
9230
  log(`ORT extract: failed to symlink optional ${link.name}: ${symlinkErr}`);
@@ -9025,7 +9232,7 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
9025
9232
  }
9026
9233
  const requiredPath = join3(targetDir, info.libName);
9027
9234
  if (!existsSync2(requiredPath)) {
9028
- rmSync(targetDir, { recursive: true, force: true });
9235
+ rmSync2(targetDir, { recursive: true, force: true });
9029
9236
  throw new Error(`Required ONNX Runtime library missing after install: ${requiredPath}`);
9030
9237
  }
9031
9238
  }
@@ -9058,7 +9265,7 @@ function writeOnnxInstalledMeta(installDir, version, sha256, archiveSha256) {
9058
9265
  function readOnnxInstalledMeta(installDir) {
9059
9266
  const path = join3(installDir, ONNX_INSTALLED_META_FILE);
9060
9267
  try {
9061
- if (!statSync(path).isFile())
9268
+ if (!statSync2(path).isFile())
9062
9269
  return null;
9063
9270
  const raw = readFileSync(path, "utf8");
9064
9271
  const parsed = JSON.parse(raw);
@@ -9075,20 +9282,20 @@ function readOnnxInstalledMeta(installDir) {
9075
9282
  }
9076
9283
  }
9077
9284
  function sha256File(path) {
9078
- const hash = createHash("sha256");
9285
+ const hash = createHash2("sha256");
9079
9286
  hash.update(readFileSync(path));
9080
9287
  return hash.digest("hex");
9081
9288
  }
9082
9289
  function acquireLock(lockPath) {
9083
9290
  const tryClaim = () => {
9084
9291
  try {
9085
- const fd = openSync(lockPath, "wx");
9292
+ const fd = openSync2(lockPath, "wx");
9086
9293
  try {
9087
9294
  writeFileSync(fd, `${process.pid}
9088
9295
  ${new Date().toISOString()}
9089
9296
  `);
9090
9297
  } finally {
9091
- closeSync(fd);
9298
+ closeSync2(fd);
9092
9299
  }
9093
9300
  return true;
9094
9301
  } catch (err) {
@@ -9109,7 +9316,7 @@ ${new Date().toISOString()}
9109
9316
  const parsed = Number.parseInt(firstLine, 10);
9110
9317
  if (Number.isFinite(parsed) && parsed > 0)
9111
9318
  owningPid = parsed;
9112
- lockMtimeMs = statSync(lockPath).mtimeMs;
9319
+ lockMtimeMs = statSync2(lockPath).mtimeMs;
9113
9320
  } catch {
9114
9321
  return tryClaim();
9115
9322
  }
@@ -9226,7 +9433,9 @@ class BridgePool {
9226
9433
  onVersionMismatch: options.onVersionMismatch,
9227
9434
  onConfigureWarnings: options.onConfigureWarnings,
9228
9435
  onBashCompletion: options.onBashCompletion,
9229
- onBashLongRunning: options.onBashLongRunning
9436
+ onBashLongRunning: options.onBashLongRunning,
9437
+ errorPrefix: options.errorPrefix,
9438
+ logger: options.logger
9230
9439
  };
9231
9440
  this.configOverrides = configOverrides;
9232
9441
  if (Number.isFinite(this.idleTimeoutMs)) {
@@ -9298,23 +9507,32 @@ class BridgePool {
9298
9507
  }
9299
9508
  async replaceBinary(newPath) {
9300
9509
  this.binaryPath = newPath;
9301
- const shutdowns = Array.from(this.bridges.values()).map((entry) => entry.bridge.shutdown());
9302
9510
  this.bridges.clear();
9303
- await Promise.allSettled(shutdowns);
9304
9511
  this.log(`Binary path updated to ${newPath}. All bridges cleared \u2014 next calls will use the new binary.`);
9512
+ return newPath;
9305
9513
  }
9306
9514
  log(message, meta) {
9307
9515
  const logger = this.logger ?? getActiveLogger();
9308
- if (logger)
9309
- logger.log(message, meta);
9310
- 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
9311
9524
  log(message, meta);
9312
9525
  }
9313
9526
  error(message, meta) {
9314
9527
  const logger = this.logger ?? getActiveLogger();
9315
- if (logger)
9316
- logger.error(message, meta);
9317
- 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
9318
9536
  error(message, meta);
9319
9537
  }
9320
9538
  setConfigureOverride(key, value) {
@@ -9341,7 +9559,7 @@ function normalizeKey(projectRoot) {
9341
9559
  }
9342
9560
  // ../aft-bridge/dist/resolver.js
9343
9561
  import { execSync, spawnSync } from "child_process";
9344
- 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";
9345
9563
  import { createRequire } from "module";
9346
9564
  import { homedir as homedir4 } from "os";
9347
9565
  import { join as join4 } from "path";
@@ -9352,7 +9570,9 @@ function readBinaryVersion(binaryPath) {
9352
9570
  stdio: ["pipe", "pipe", "pipe"],
9353
9571
  timeout: 5000
9354
9572
  });
9355
- const rawVersion = result.stdout?.trim();
9573
+ const stdoutVersion = result.stdout?.trim();
9574
+ const stderrVersion = result.stderr?.trim();
9575
+ const rawVersion = stdoutVersion || stderrVersion;
9356
9576
  if (!rawVersion)
9357
9577
  return null;
9358
9578
  return rawVersion.replace(/^aft\s+/, "");
@@ -9370,15 +9590,19 @@ function copyToVersionedCache(npmBinaryPath, knownVersion) {
9370
9590
  const versionedDir = join4(cacheDir, tag);
9371
9591
  const ext = process.platform === "win32" ? ".exe" : "";
9372
9592
  const cachedPath = join4(versionedDir, `aft${ext}`);
9373
- if (existsSync3(cachedPath))
9374
- 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
+ }
9375
9599
  mkdirSync3(versionedDir, { recursive: true });
9376
- const tmpPath = `${cachedPath}.tmp`;
9600
+ const tmpPath = `${cachedPath}.${process.pid}.${Date.now()}.tmp`;
9377
9601
  copyFileSync2(npmBinaryPath, tmpPath);
9378
9602
  if (process.platform !== "win32") {
9379
9603
  chmodSync3(tmpPath, 493);
9380
9604
  }
9381
- renameSync(tmpPath, cachedPath);
9605
+ renameSync2(tmpPath, cachedPath);
9382
9606
  log(`Copied npm binary to versioned cache: ${cachedPath}`);
9383
9607
  return cachedPath;
9384
9608
  } catch (err) {
@@ -9386,6 +9610,17 @@ function copyToVersionedCache(npmBinaryPath, knownVersion) {
9386
9610
  return null;
9387
9611
  }
9388
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
+ }
9389
9624
  function platformKey(platform = process.platform, arch = process.arch) {
9390
9625
  const archMap = PLATFORM_ARCH_MAP[platform];
9391
9626
  if (!archMap) {
@@ -9410,7 +9645,7 @@ function findBinarySync(expectedVersion) {
9410
9645
  if (pluginVersion) {
9411
9646
  const tag = pluginVersion.startsWith("v") ? pluginVersion : `v${pluginVersion}`;
9412
9647
  const versionCached = getCachedBinaryPath(tag);
9413
- if (versionCached)
9648
+ if (versionCached && isExpectedCachedBinary(versionCached, pluginVersion))
9414
9649
  return versionCached;
9415
9650
  }
9416
9651
  try {
@@ -9420,10 +9655,12 @@ function findBinarySync(expectedVersion) {
9420
9655
  const resolved = req.resolve(packageBin);
9421
9656
  if (existsSync3(resolved)) {
9422
9657
  const npmVersion = readBinaryVersion(resolved);
9423
- 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)) {
9424
9661
  warn(`npm platform package binary v${npmVersion} does not match plugin v${pluginVersion}; skipping (continuing to PATH lookup)`);
9425
9662
  } else {
9426
- const copied = copyToVersionedCache(resolved, npmVersion ?? undefined);
9663
+ const copied = copyToVersionedCache(resolved, npmVersion);
9427
9664
  return copied ?? resolved;
9428
9665
  }
9429
9666
  }
@@ -9472,7 +9709,7 @@ async function findBinary(expectedVersion) {
9472
9709
  `));
9473
9710
  }
9474
9711
  // ../aft-bridge/dist/url-fetch.js
9475
- import { createHash as createHash2 } from "crypto";
9712
+ import { createHash as createHash3 } from "crypto";
9476
9713
  import { lookup } from "dns/promises";
9477
9714
  import { existsSync as existsSync4, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "fs";
9478
9715
  import { isIP } from "net";
@@ -9486,7 +9723,7 @@ function cacheDir(storageDir) {
9486
9723
  return join5(storageDir, "url_cache");
9487
9724
  }
9488
9725
  function hashUrl(url) {
9489
- return createHash2("sha256").update(url).digest("hex").slice(0, 16);
9726
+ return createHash3("sha256").update(url).digest("hex").slice(0, 16);
9490
9727
  }
9491
9728
  function metaPath(storageDir, hash) {
9492
9729
  return join5(cacheDir(storageDir), `${hash}.meta.json`);
@@ -9688,6 +9925,7 @@ async function fetchUrlToTempFile(url, storageDir, options = {}) {
9688
9925
  throw new Error(`Only http:// and https:// URLs are supported, got: ${parsed.protocol}`);
9689
9926
  }
9690
9927
  const allowPrivate = options.allowPrivate === true;
9928
+ await assertPublicUrl(parsed, allowPrivate, options.lookup);
9691
9929
  const dir = cacheDir(storageDir);
9692
9930
  mkdirSync4(dir, { recursive: true });
9693
9931
  const hash = hashUrl(url);
@@ -9743,8 +9981,8 @@ async function fetchUrlToTempFile(url, storageDir, options = {}) {
9743
9981
  const contentFile = contentPath(storageDir, hash, extension);
9744
9982
  const tmpContent = `${contentFile}.tmp-${process.pid}`;
9745
9983
  writeFileSync2(tmpContent, body);
9746
- const { renameSync: renameSync2 } = await import("fs");
9747
- renameSync2(tmpContent, contentFile);
9984
+ const { renameSync: renameSync3 } = await import("fs");
9985
+ renameSync3(tmpContent, contentFile);
9748
9986
  const meta = {
9749
9987
  url,
9750
9988
  contentType,
@@ -9753,7 +9991,7 @@ async function fetchUrlToTempFile(url, storageDir, options = {}) {
9753
9991
  };
9754
9992
  const tmpMeta = `${metaFile}.tmp-${process.pid}`;
9755
9993
  writeFileSync2(tmpMeta, JSON.stringify(meta));
9756
- renameSync2(tmpMeta, metaFile);
9994
+ renameSync3(tmpMeta, metaFile);
9757
9995
  log(`URL cached (${total} bytes): ${url}`);
9758
9996
  return contentFile;
9759
9997
  }
@@ -9907,24 +10145,24 @@ function warn2(message, data) {
9907
10145
  function error2(message, data) {
9908
10146
  write("ERROR", message, data);
9909
10147
  }
9910
- function sessionLog2(sessionId, message, data) {
10148
+ function sessionLog(sessionId, message, data) {
9911
10149
  write("INFO", message, data, sessionId);
9912
10150
  }
9913
- function sessionWarn2(sessionId, message, data) {
10151
+ function sessionWarn(sessionId, message, data) {
9914
10152
  write("WARN", message, data, sessionId);
9915
10153
  }
9916
- function sessionError2(sessionId, message, data) {
10154
+ function sessionError(sessionId, message, data) {
9917
10155
  write("ERROR", message, data, sessionId);
9918
10156
  }
9919
10157
  var bridgeLogger = {
9920
10158
  log(message, meta) {
9921
- sessionLog2(meta?.sessionId, message);
10159
+ sessionLog(meta?.sessionId, message);
9922
10160
  },
9923
10161
  warn(message, meta) {
9924
- sessionWarn2(meta?.sessionId, message);
10162
+ sessionWarn(meta?.sessionId, message);
9925
10163
  },
9926
10164
  error(message, meta) {
9927
- sessionError2(meta?.sessionId, message);
10165
+ sessionError(meta?.sessionId, message);
9928
10166
  },
9929
10167
  getLogFilePath: () => logFile
9930
10168
  };
@@ -10019,6 +10257,7 @@ var sessionBgStates = new Map;
10019
10257
  var SESSION_BG_STATE_IDLE_TTL_MS = 60 * 60 * 1000;
10020
10258
  var DEBOUNCE_STEP_MS = 200;
10021
10259
  var DEBOUNCE_CAP_MS = 1000;
10260
+ var MAX_WAKE_SEND_ATTEMPTS = 5;
10022
10261
  var UNKNOWN_COMPLETION_TTL_MS = 5000;
10023
10262
  var UNKNOWN_COMPLETION_CAP = 32;
10024
10263
  var DEFAULT_SESSION_ID = "__default__";
@@ -10071,17 +10310,24 @@ async function appendInTurnBgCompletions(drainContext, output) {
10071
10310
  return;
10072
10311
  const state = stateFor(drainContext.sessionID);
10073
10312
  if (state.outstandingTaskIds.size === 0 && state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0) {
10074
- return;
10313
+ await drainCompletions(drainContext);
10314
+ if (state.outstandingTaskIds.size === 0 && state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0) {
10315
+ return;
10316
+ }
10075
10317
  }
10076
- if (state.outstandingTaskIds.size > 0) {
10318
+ if (state.outstandingTaskIds.size > 0 || !state.forcedDrainCompleted) {
10077
10319
  await drainCompletions(drainContext);
10078
10320
  }
10079
10321
  if (state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0)
10080
10322
  return;
10323
+ const deliveredCompletions = [...state.pendingCompletions];
10081
10324
  const reminder = formatCombinedSystemReminder(state.pendingCompletions, state.pendingLongRunning);
10082
10325
  output.output = appendReminder(output.output ?? "", reminder);
10083
10326
  state.pendingCompletions = [];
10084
10327
  state.pendingLongRunning = [];
10328
+ state.wakeRetryAttempts = 0;
10329
+ state.wakeHardStopped = false;
10330
+ await ackCompletions(drainContext, deliveredCompletions);
10085
10331
  if (state.debounceTimer) {
10086
10332
  clearTimeout(state.debounceTimer);
10087
10333
  state.debounceTimer = null;
@@ -10095,12 +10341,12 @@ async function handleIdleBgCompletions(drainContext) {
10095
10341
  }
10096
10342
  async function triggerWakeIfPending(drainContext, skipDrain) {
10097
10343
  const state = stateFor(drainContext.sessionID);
10098
- if (!skipDrain && state.outstandingTaskIds.size > 0) {
10344
+ if (!skipDrain && (state.outstandingTaskIds.size > 0 || !state.forcedDrainCompleted)) {
10099
10345
  await drainCompletions(drainContext);
10100
10346
  }
10101
10347
  if (state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0)
10102
10348
  return;
10103
- scheduleWake(state, async (reminder) => {
10349
+ scheduleWake(state, async (reminder, deliveredCompletions) => {
10104
10350
  const client = drainContext.client;
10105
10351
  if (typeof client.session?.promptAsync !== "function") {
10106
10352
  throw new Error("client.session.promptAsync is unavailable");
@@ -10124,8 +10370,9 @@ async function triggerWakeIfPending(drainContext, skipDrain) {
10124
10370
  path: { id: drainContext.sessionID },
10125
10371
  body
10126
10372
  });
10127
- }, (err) => {
10128
- 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)}`);
10129
10376
  });
10130
10377
  }
10131
10378
  function formatSystemReminder(completions) {
@@ -10176,19 +10423,40 @@ function extractSessionID(value) {
10176
10423
  return;
10177
10424
  }
10178
10425
  async function drainCompletions({ ctx, directory, sessionID }) {
10426
+ const state = stateFor(sessionID);
10179
10427
  try {
10180
10428
  const bridge = ctx.pool.getActiveBridgeForRoot(directory) ?? ctx.pool.getBridge(directory);
10181
10429
  const response = await bridge.send("bash_drain_completions", { session_id: sessionID });
10182
10430
  if (response.success === false) {
10183
- sessionWarn2(sessionID, `${LOG_PREFIX} drain failed: ${String(response.message ?? "unknown error")}`);
10431
+ sessionWarn(sessionID, `${LOG_PREFIX} drain failed: ${String(response.message ?? "unknown error")}`);
10184
10432
  return;
10185
10433
  }
10186
- 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
+ }
10187
10453
  } catch (err) {
10188
- 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)}`);
10189
10455
  }
10190
10456
  }
10191
10457
  function scheduleWake(state, sendWake, onSendFailure) {
10458
+ if (state.wakeHardStopped)
10459
+ return;
10192
10460
  const now = Date.now();
10193
10461
  const pendingCount = state.pendingCompletions.length + state.pendingLongRunning.length;
10194
10462
  if (state.debounceTimer && pendingCount <= state.scheduledCompletionCount) {
@@ -10217,13 +10485,22 @@ function scheduleWake(state, sendWake, onSendFailure) {
10217
10485
  const reminder = formatCombinedSystemReminder(pending, pendingLongRunning);
10218
10486
  state.pendingCompletions = [];
10219
10487
  state.pendingLongRunning = [];
10220
- sendWake(reminder).then(() => {
10488
+ sendWake(reminder, pending).then(() => {
10221
10489
  state.retryDelayMs = null;
10490
+ state.wakeRetryAttempts = 0;
10491
+ state.wakeHardStopped = false;
10222
10492
  }).catch((err) => {
10223
10493
  state.pendingCompletions = [...pending, ...state.pendingCompletions];
10224
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
+ }
10225
10502
  state.retryDelayMs = Math.min((delay || DEBOUNCE_STEP_MS) * 2, DEBOUNCE_CAP_MS);
10226
- onSendFailure(err);
10503
+ onSendFailure(err, false);
10227
10504
  scheduleWake(state, sendWake, onSendFailure);
10228
10505
  });
10229
10506
  }, delay);
@@ -10244,6 +10521,9 @@ function stateFor(sessionID) {
10244
10521
  scheduledFireAt: null,
10245
10522
  scheduledCompletionCount: 0,
10246
10523
  retryDelayMs: null,
10524
+ wakeRetryAttempts: 0,
10525
+ wakeHardStopped: false,
10526
+ forcedDrainCompleted: false,
10247
10527
  unknownCompletions: [],
10248
10528
  lastSeenAt: now
10249
10529
  };
@@ -10253,6 +10533,22 @@ function stateFor(sessionID) {
10253
10533
  }
10254
10534
  return state;
10255
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
+ }
10256
10552
  function cleanupIdleSessionStates(now) {
10257
10553
  const cutoff = now - SESSION_BG_STATE_IDLE_TTL_MS;
10258
10554
  for (const [sessionID, state] of sessionBgStates) {
@@ -10343,7 +10639,7 @@ function formatDuration(completion) {
10343
10639
 
10344
10640
  // src/config.ts
10345
10641
  var import_comment_json = __toESM(require_src2(), 1);
10346
- 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";
10347
10643
  import { homedir as homedir5 } from "os";
10348
10644
  import { join as join7 } from "path";
10349
10645
 
@@ -24122,7 +24418,7 @@ function migrateAftConfigFile(configPath, logger = { log: log2, warn: warn2 }) {
24122
24418
  ${serialized}` : serialized;
24123
24419
  tmpPath = `${configPath}.tmp.${process.pid}`;
24124
24420
  writeFileSync3(tmpPath, nextContent, "utf-8");
24125
- renameSync2(tmpPath, configPath);
24421
+ renameSync3(tmpPath, configPath);
24126
24422
  logger.log(`Migrated config at ${configPath}: removed ${oldKeys.join(", ")}`);
24127
24423
  return { migrated: true, oldKeys };
24128
24424
  } catch (err) {
@@ -24357,19 +24653,19 @@ function loadAftConfig(projectDirectory) {
24357
24653
  }
24358
24654
 
24359
24655
  // src/hooks/auto-update-checker/index.ts
24360
- 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";
24361
24657
  import { dirname as dirname4, join as join11 } from "path";
24362
24658
 
24363
24659
  // src/hooks/auto-update-checker/cache.ts
24364
24660
  var import_comment_json3 = __toESM(require_src2(), 1);
24365
24661
  import { spawn as spawn2 } from "child_process";
24366
- 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";
24367
24663
  import { tmpdir as tmpdir2 } from "os";
24368
24664
  import { basename, dirname as dirname3, join as join10 } from "path";
24369
24665
 
24370
24666
  // src/hooks/auto-update-checker/checker.ts
24371
24667
  var import_comment_json2 = __toESM(require_src2(), 1);
24372
- 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";
24373
24669
  import { homedir as homedir7 } from "os";
24374
24670
  import { dirname as dirname2, isAbsolute, join as join9, resolve as resolve2 } from "path";
24375
24671
  import { fileURLToPath } from "url";
@@ -24496,7 +24792,7 @@ function getLocalDevPath(directory) {
24496
24792
  }
24497
24793
  function findPackageJsonUp(startPath) {
24498
24794
  try {
24499
- const stat = statSync2(startPath);
24795
+ const stat = statSync3(startPath);
24500
24796
  let dir = stat.isDirectory() ? startPath : dirname2(startPath);
24501
24797
  for (let i = 0;i < 10; i++) {
24502
24798
  const pkgPath = join9(dir, "package.json");
@@ -24636,19 +24932,19 @@ function createAutoUpdateSnapshot(installDir, packageJsonPath, packageName) {
24636
24932
  function restoreAutoUpdateSnapshot(snapshot) {
24637
24933
  try {
24638
24934
  if (snapshot.packageJson === null)
24639
- rmSync2(snapshot.packageJsonPath, { force: true });
24935
+ rmSync3(snapshot.packageJsonPath, { force: true });
24640
24936
  else
24641
24937
  writeFileSync5(snapshot.packageJsonPath, snapshot.packageJson);
24642
24938
  if (snapshot.lockfile === null)
24643
- rmSync2(snapshot.lockfilePath, { force: true });
24939
+ rmSync3(snapshot.lockfilePath, { force: true });
24644
24940
  else
24645
24941
  writeFileSync5(snapshot.lockfilePath, snapshot.lockfile);
24646
- rmSync2(snapshot.packageDir, { recursive: true, force: true });
24942
+ rmSync3(snapshot.packageDir, { recursive: true, force: true });
24647
24943
  if (snapshot.stagedPackageDir) {
24648
24944
  cpSync(snapshot.stagedPackageDir, snapshot.packageDir, { recursive: true });
24649
24945
  }
24650
24946
  } finally {
24651
- rmSync2(snapshot.tempDir, { recursive: true, force: true });
24947
+ rmSync3(snapshot.tempDir, { recursive: true, force: true });
24652
24948
  }
24653
24949
  }
24654
24950
  function stripPackageNameFromPath(pathValue, packageName) {
@@ -24713,7 +25009,7 @@ function removeInstalledPackage(installDir, packageName) {
24713
25009
  const packageDir = join10(installDir, "node_modules", packageName);
24714
25010
  if (!existsSync7(packageDir))
24715
25011
  return false;
24716
- rmSync2(packageDir, { recursive: true, force: true });
25012
+ rmSync3(packageDir, { recursive: true, force: true });
24717
25013
  log2(`[auto-update-checker] Package removed: ${packageDir}`);
24718
25014
  return true;
24719
25015
  }
@@ -24798,7 +25094,7 @@ async function runNpmInstallSafe(installDir, options = {}) {
24798
25094
  if (!result && snapshot) {
24799
25095
  restoreAutoUpdateSnapshot(snapshot);
24800
25096
  } else if (snapshot) {
24801
- rmSync2(snapshot.tempDir, { recursive: true, force: true });
25097
+ rmSync3(snapshot.tempDir, { recursive: true, force: true });
24802
25098
  }
24803
25099
  return result;
24804
25100
  } catch (err) {
@@ -24882,7 +25178,7 @@ function claimCheckSlot(storageDir, intervalMs) {
24882
25178
  mkdirSync5(dirname4(file2), { recursive: true });
24883
25179
  const tmp = `${file2}.tmp.${process.pid}`;
24884
25180
  writeFileSync6(tmp, JSON.stringify({ lastCheckedMs: Date.now() }), "utf-8");
24885
- renameSync3(tmp, file2);
25181
+ renameSync4(tmp, file2);
24886
25182
  return true;
24887
25183
  } catch (err) {
24888
25184
  warn2(`[auto-update-checker] Could not coordinate via timestamp file: ${String(err)}`);
@@ -24969,17 +25265,17 @@ function showToast(ctx, title, message, variant = "info", duration3 = 3000) {
24969
25265
 
24970
25266
  // src/lsp-auto-install.ts
24971
25267
  import { spawn as spawn3 } from "child_process";
24972
- import { createHash as createHash3 } from "crypto";
24973
- 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";
24974
25270
  import { join as join14 } from "path";
24975
25271
 
24976
25272
  // src/lsp-cache.ts
24977
25273
  import {
24978
- closeSync as closeSync2,
25274
+ closeSync as closeSync3,
24979
25275
  mkdirSync as mkdirSync6,
24980
- openSync as openSync2,
25276
+ openSync as openSync3,
24981
25277
  readFileSync as readFileSync7,
24982
- statSync as statSync3,
25278
+ statSync as statSync4,
24983
25279
  unlinkSync as unlinkSync5,
24984
25280
  writeFileSync as writeFileSync7
24985
25281
  } from "fs";
@@ -25012,7 +25308,7 @@ function lspBinDir(npmPackage) {
25012
25308
  function isInstalled(npmPackage, binary) {
25013
25309
  for (const candidate of lspBinaryCandidates(binary)) {
25014
25310
  try {
25015
- if (statSync3(join12(lspBinDir(npmPackage), candidate)).isFile())
25311
+ if (statSync4(join12(lspBinDir(npmPackage), candidate)).isFile())
25016
25312
  return true;
25017
25313
  } catch {}
25018
25314
  }
@@ -25040,7 +25336,7 @@ function writeInstalledMetaIn(installDir, version2, sha256) {
25040
25336
  function readInstalledMetaIn(installDir) {
25041
25337
  const path2 = join12(installDir, INSTALLED_META_FILE);
25042
25338
  try {
25043
- if (!statSync3(path2).isFile())
25339
+ if (!statSync4(path2).isFile())
25044
25340
  return null;
25045
25341
  const raw = readFileSync7(path2, "utf8");
25046
25342
  const parsed = JSON.parse(raw);
@@ -25070,13 +25366,13 @@ function acquireInstallLock(lockKey) {
25070
25366
  const lock = lockPath(lockKey);
25071
25367
  const tryClaim = () => {
25072
25368
  try {
25073
- const fd = openSync2(lock, "wx");
25369
+ const fd = openSync3(lock, "wx");
25074
25370
  try {
25075
25371
  writeFileSync7(fd, `${process.pid}
25076
25372
  ${new Date().toISOString()}
25077
25373
  `);
25078
25374
  } finally {
25079
- closeSync2(fd);
25375
+ closeSync3(fd);
25080
25376
  }
25081
25377
  return true;
25082
25378
  } catch (err) {
@@ -25097,7 +25393,7 @@ ${new Date().toISOString()}
25097
25393
  const parsed = Number.parseInt(firstLine, 10);
25098
25394
  if (Number.isFinite(parsed) && parsed > 0)
25099
25395
  owningPid = parsed;
25100
- lockMtimeMs = statSync3(lock).mtimeMs;
25396
+ lockMtimeMs = statSync4(lock).mtimeMs;
25101
25397
  } catch {
25102
25398
  return tryClaim();
25103
25399
  }
@@ -25638,7 +25934,7 @@ function hashInstalledBinary(spec) {
25638
25934
  let pathToHash = null;
25639
25935
  for (const p of candidates) {
25640
25936
  try {
25641
- if (statSync4(p).isFile()) {
25937
+ if (statSync5(p).isFile()) {
25642
25938
  pathToHash = p;
25643
25939
  break;
25644
25940
  }
@@ -25648,7 +25944,7 @@ function hashInstalledBinary(spec) {
25648
25944
  reject(new Error(`installed binary not found at any of: ${candidates.join(", ")}`));
25649
25945
  return;
25650
25946
  }
25651
- const hash2 = createHash3("sha256");
25947
+ const hash2 = createHash4("sha256");
25652
25948
  const stream = createReadStream(pathToHash);
25653
25949
  stream.on("error", reject);
25654
25950
  stream.on("data", (chunk) => hash2.update(chunk));
@@ -25664,14 +25960,14 @@ function installedBinaryPath(spec) {
25664
25960
  ] : [lspBinaryPath(spec.npm, spec.binary)];
25665
25961
  for (const candidate of candidates) {
25666
25962
  try {
25667
- if (statSync4(candidate).isFile())
25963
+ if (statSync5(candidate).isFile())
25668
25964
  return candidate;
25669
25965
  } catch {}
25670
25966
  }
25671
25967
  return null;
25672
25968
  }
25673
25969
  function sha256OfFileSync(path2) {
25674
- return createHash3("sha256").update(readFileSync8(path2)).digest("hex");
25970
+ return createHash4("sha256").update(readFileSync8(path2)).digest("hex");
25675
25971
  }
25676
25972
  function quarantineCachedNpmInstall(spec, reason) {
25677
25973
  const packageDir = lspPackageDir(spec.npm);
@@ -25679,8 +25975,8 @@ function quarantineCachedNpmInstall(spec, reason) {
25679
25975
  warn2(`[lsp] tofu_mismatch ${spec.npm}: ${reason}; quarantining ${packageDir} -> ${dest}`);
25680
25976
  try {
25681
25977
  mkdirSync7(join14(dest, ".."), { recursive: true });
25682
- rmSync3(dest, { recursive: true, force: true });
25683
- renameSync4(packageDir, dest);
25978
+ rmSync4(dest, { recursive: true, force: true });
25979
+ renameSync5(packageDir, dest);
25684
25980
  } catch (err) {
25685
25981
  warn2(`[lsp] tofu_mismatch ${spec.npm}: failed to quarantine cache entry: ${err}`);
25686
25982
  }
@@ -25756,11 +26052,11 @@ function runAutoInstall(projectRoot, config2, fetchImpl = fetch) {
25756
26052
 
25757
26053
  // src/lsp-github-install.ts
25758
26054
  import { execFileSync as execFileSync2 } from "child_process";
25759
- import { createHash as createHash4, randomBytes } from "crypto";
26055
+ import { createHash as createHash5, randomBytes } from "crypto";
25760
26056
  import {
25761
26057
  copyFileSync as copyFileSync3,
25762
26058
  createReadStream as createReadStream2,
25763
- createWriteStream as createWriteStream2,
26059
+ createWriteStream as createWriteStream3,
25764
26060
  existsSync as existsSync10,
25765
26061
  lstatSync as lstatSync2,
25766
26062
  mkdirSync as mkdirSync8,
@@ -25768,14 +26064,15 @@ import {
25768
26064
  readFileSync as readFileSync9,
25769
26065
  readlinkSync as readlinkSync2,
25770
26066
  realpathSync as realpathSync3,
25771
- renameSync as renameSync5,
25772
- rmSync as rmSync4,
25773
- statSync as statSync5,
25774
- unlinkSync as unlinkSync6
26067
+ renameSync as renameSync6,
26068
+ rmSync as rmSync5,
26069
+ statSync as statSync6,
26070
+ unlinkSync as unlinkSync6,
26071
+ writeFileSync as writeFileSync8
25775
26072
  } from "fs";
25776
26073
  import { dirname as dirname5, join as join15, relative as relative2, resolve as resolve3 } from "path";
25777
- import { Readable as Readable2 } from "stream";
25778
- import { pipeline as pipeline2 } from "stream/promises";
26074
+ import { Readable as Readable3 } from "stream";
26075
+ import { pipeline as pipeline3 } from "stream/promises";
25779
26076
 
25780
26077
  // src/lsp-github-table.ts
25781
26078
  function exe(platform2, name) {
@@ -25874,6 +26171,7 @@ function ghBinDir(spec) {
25874
26171
  function ghExtractDir(spec) {
25875
26172
  return join15(ghPackageDir(spec), "extracted");
25876
26173
  }
26174
+ var INSTALLED_META_FILE2 = ".aft-installed";
25877
26175
  function ghBinaryPath(spec, platform2) {
25878
26176
  const ext = platform2 === "win32" ? ".exe" : "";
25879
26177
  return join15(ghBinDir(spec), `${spec.binary}${ext}`);
@@ -25881,7 +26179,7 @@ function ghBinaryPath(spec, platform2) {
25881
26179
  function isGithubInstalled(spec, platform2) {
25882
26180
  for (const candidate of ghBinaryCandidates(spec, platform2)) {
25883
26181
  try {
25884
- if (statSync5(join15(ghBinDir(spec), candidate)).isFile())
26182
+ if (statSync6(join15(ghBinDir(spec), candidate)).isFile())
25885
26183
  return true;
25886
26184
  } catch {}
25887
26185
  }
@@ -25892,11 +26190,45 @@ function ghBinaryCandidates(spec, platform2) {
25892
26190
  return [spec.binary];
25893
26191
  return [spec.binary, `${spec.binary}.cmd`, `${spec.binary}.exe`, `${spec.binary}.bat`];
25894
26192
  }
25895
- 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;
25896
26228
  var MAX_EXTRACT_BYTES2 = 1024 * 1024 * 1024;
25897
26229
  function sha256OfFile(path2) {
25898
26230
  return new Promise((resolve4, reject) => {
25899
- const hash2 = createHash4("sha256");
26231
+ const hash2 = createHash5("sha256");
25900
26232
  const stream = createReadStream2(path2);
25901
26233
  stream.on("error", reject);
25902
26234
  stream.on("data", (chunk) => hash2.update(chunk));
@@ -25904,7 +26236,7 @@ function sha256OfFile(path2) {
25904
26236
  });
25905
26237
  }
25906
26238
  function sha256OfFileSync2(path2) {
25907
- return createHash4("sha256").update(readFileSync9(path2)).digest("hex");
26239
+ return createHash5("sha256").update(readFileSync9(path2)).digest("hex");
25908
26240
  }
25909
26241
  async function fetchReleaseByTag(githubRepo, tag, fetchImpl, signal) {
25910
26242
  const candidates = [];
@@ -26067,8 +26399,8 @@ function assertAllowedDownloadUrl(rawUrl) {
26067
26399
  return parsed;
26068
26400
  }
26069
26401
  async function downloadFile(url2, destPath, fetchImpl, assetSize, signal) {
26070
- if (assetSize !== undefined && assetSize > MAX_DOWNLOAD_BYTES2) {
26071
- 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)`);
26072
26404
  }
26073
26405
  const timeout = controlledTimeoutSignal(120000, signal);
26074
26406
  try {
@@ -26077,24 +26409,24 @@ async function downloadFile(url2, destPath, fetchImpl, assetSize, signal) {
26077
26409
  throw new Error(`download failed (${res.status})`);
26078
26410
  }
26079
26411
  const advertised = Number.parseInt(res.headers.get("content-length") ?? "", 10);
26080
- if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES2) {
26081
- 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}`);
26082
26414
  }
26083
26415
  mkdirSync8(dirname5(destPath), { recursive: true });
26084
26416
  let bytesWritten = 0;
26085
26417
  const guard = new TransformStream({
26086
26418
  transform(chunk, controller) {
26087
26419
  bytesWritten += chunk.byteLength;
26088
- if (bytesWritten > MAX_DOWNLOAD_BYTES2) {
26089
- 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)`));
26090
26422
  return;
26091
26423
  }
26092
26424
  controller.enqueue(chunk);
26093
26425
  }
26094
26426
  });
26095
26427
  const guarded = res.body.pipeThrough(guard);
26096
- const nodeStream = Readable2.fromWeb(guarded);
26097
- await pipeline2(nodeStream, createWriteStream2(destPath), { signal: timeout.signal });
26428
+ const nodeStream = Readable3.fromWeb(guarded);
26429
+ await pipeline3(nodeStream, createWriteStream3(destPath), { signal: timeout.signal });
26098
26430
  } catch (err) {
26099
26431
  try {
26100
26432
  unlinkSync6(destPath);
@@ -26174,7 +26506,7 @@ function validateExtraction(stagingRoot) {
26174
26506
  };
26175
26507
  walk(realStagingRoot);
26176
26508
  }
26177
- function precheckArchiveSize(archivePath, archiveType) {
26509
+ function precheckArchiveContents(archivePath, archiveType) {
26178
26510
  let totalBytes = 0;
26179
26511
  if (archiveType === "zip") {
26180
26512
  const out = execFileSync2("unzip", ["-l", archivePath], { encoding: "utf8" });
@@ -26185,6 +26517,9 @@ function precheckArchiveSize(archivePath, archiveType) {
26185
26517
  const out = execFileSync2("tar", ["-tvf", archivePath], { encoding: "utf8" });
26186
26518
  for (const line of out.split(`
26187
26519
  `)) {
26520
+ if (line.startsWith("h")) {
26521
+ throw new Error(`archive contains hardlink entry: ${line.trim()}`);
26522
+ }
26188
26523
  const parts = line.trim().split(/\s+/);
26189
26524
  if (parts.length >= 6) {
26190
26525
  const numeric = parts.map((part) => Number.parseInt(part, 10)).filter((value) => Number.isFinite(value) && value >= 0);
@@ -26201,20 +26536,20 @@ function extractArchiveSafely(archivePath, destDir, archiveType) {
26201
26536
  const suffix = randomBytes(8).toString("hex");
26202
26537
  const stagingDir = `${destDir}.staging-${suffix}`;
26203
26538
  try {
26204
- rmSync4(stagingDir, { recursive: true, force: true });
26539
+ rmSync5(stagingDir, { recursive: true, force: true });
26205
26540
  } catch {}
26206
26541
  mkdirSync8(stagingDir, { recursive: true });
26207
26542
  try {
26208
- precheckArchiveSize(archivePath, archiveType);
26543
+ precheckArchiveContents(archivePath, archiveType);
26209
26544
  runPlatformExtractor(archivePath, stagingDir, archiveType);
26210
26545
  validateExtraction(stagingDir);
26211
26546
  try {
26212
- rmSync4(destDir, { recursive: true, force: true });
26547
+ rmSync5(destDir, { recursive: true, force: true });
26213
26548
  } catch {}
26214
- renameSync5(stagingDir, destDir);
26549
+ renameSync6(stagingDir, destDir);
26215
26550
  } catch (err) {
26216
26551
  try {
26217
- rmSync4(stagingDir, { recursive: true, force: true });
26552
+ rmSync5(stagingDir, { recursive: true, force: true });
26218
26553
  } catch {}
26219
26554
  throw err;
26220
26555
  }
@@ -26225,28 +26560,44 @@ function quarantineCachedGithubInstall(spec, reason) {
26225
26560
  warn2(`[lsp] tofu_mismatch ${spec.id}: ${reason}; quarantining ${packageDir} -> ${dest}`);
26226
26561
  try {
26227
26562
  mkdirSync8(dirname5(dest), { recursive: true });
26228
- rmSync4(dest, { recursive: true, force: true });
26229
- renameSync5(packageDir, dest);
26563
+ rmSync5(dest, { recursive: true, force: true });
26564
+ renameSync6(packageDir, dest);
26230
26565
  } catch (err) {
26231
26566
  warn2(`[lsp] tofu_mismatch ${spec.id}: failed to quarantine cache entry: ${err}`);
26232
26567
  }
26233
26568
  }
26234
26569
  function validateCachedGithubInstall(spec, platform2) {
26235
- const meta3 = readInstalledMetaIn(ghPackageDir(spec));
26570
+ const packageDir = ghPackageDir(spec);
26571
+ const meta3 = readGithubInstalledMetaIn(packageDir);
26236
26572
  const binaryPath = ghBinaryCandidates(spec, platform2).map((candidate) => join15(ghBinDir(spec), candidate)).find((candidate) => {
26237
26573
  try {
26238
- return statSync5(candidate).isFile();
26574
+ return statSync6(candidate).isFile();
26239
26575
  } catch {
26240
26576
  return false;
26241
26577
  }
26242
26578
  });
26243
- if (!meta3?.sha256 || !meta3.version || !isSafeVersion(meta3.version) || !binaryPath) {
26579
+ if (!meta3?.version || !isSafeVersion(meta3.version) || !binaryPath) {
26244
26580
  quarantineCachedGithubInstall(spec, "missing/unsafe metadata or binary");
26245
26581
  return false;
26246
26582
  }
26247
26583
  const currentHash = sha256OfFileSync2(binaryPath);
26248
- if (currentHash !== meta3.sha256) {
26249
- 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}`);
26250
26601
  return false;
26251
26602
  }
26252
26603
  return true;
@@ -26315,10 +26666,11 @@ async function downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl,
26315
26666
  return null;
26316
26667
  }
26317
26668
  log2(`[lsp] ${spec.id} ${tag} sha256=${archiveSha256}`);
26318
- const previousMeta = readInstalledMetaIn(ghPackageDir(spec));
26319
- if (previousMeta && previousMeta.version === tag && previousMeta.sha256) {
26320
- if (previousMeta.sha256 !== archiveSha256) {
26321
- 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.`);
26322
26674
  try {
26323
26675
  unlinkSync6(archivePath);
26324
26676
  } catch {}
@@ -26353,7 +26705,9 @@ async function downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl,
26353
26705
  return null;
26354
26706
  }
26355
26707
  log2(`[lsp] installed ${spec.id} ${tag} at ${targetBinary}`);
26356
- return archiveSha256;
26708
+ const binarySha256 = await sha256OfFile(targetBinary);
26709
+ log2(`[lsp] ${spec.id} ${tag} binary_sha256=${binarySha256}`);
26710
+ return { archiveSha256, binarySha256 };
26357
26711
  }
26358
26712
  async function ensureGithubInstalled(spec, config2, fetchImpl, platform2, arch, signal) {
26359
26713
  const outcome = await withInstallLock(spec.githubRepo, async () => {
@@ -26379,14 +26733,14 @@ async function ensureGithubInstalled(spec, config2, fetchImpl, platform2, arch,
26379
26733
  log2(`[lsp] reinstalling ${spec.id}@${tag}: no installed-version metadata recorded`);
26380
26734
  }
26381
26735
  }
26382
- 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) => {
26383
26737
  error2(`[lsp] github install ${spec.id} crashed: ${err}`);
26384
26738
  return null;
26385
26739
  });
26386
- if (!archiveSha256) {
26740
+ if (!hashes) {
26387
26741
  return { started: true, reason: "install failed (see plugin log)" };
26388
26742
  }
26389
- writeInstalledMetaIn(ghPackageDir(spec), tag, archiveSha256);
26743
+ writeGithubInstalledMetaIn(ghPackageDir(spec), tag, hashes.binarySha256, hashes.archiveSha256);
26390
26744
  return { started: true };
26391
26745
  });
26392
26746
  if (outcome === null) {
@@ -26581,7 +26935,7 @@ function normalizeToolMap(tools) {
26581
26935
  }
26582
26936
 
26583
26937
  // src/notifications.ts
26584
- 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";
26585
26939
  import { homedir as homedir9, platform as platform2 } from "os";
26586
26940
  import { join as join16 } from "path";
26587
26941
  function isTuiMode() {
@@ -26688,7 +27042,7 @@ async function sendIgnoredMessage(client, sessionId, text) {
26688
27042
  return true;
26689
27043
  }
26690
27044
  } catch (err) {
26691
- 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)}`);
26692
27046
  }
26693
27047
  return false;
26694
27048
  }
@@ -26714,7 +27068,7 @@ async function sendWarning(opts, message) {
26714
27068
  if (!sessionId)
26715
27069
  return;
26716
27070
  const text = `${WARNING_MARKER} ${message}`;
26717
- sessionLog2(sessionId, `[aft-plugin] sending warning to session ${sessionId}`);
27071
+ sessionLog(sessionId, `[aft-plugin] sending warning to session ${sessionId}`);
26718
27072
  await sendIgnoredMessage(opts.client, sessionId, text);
26719
27073
  }
26720
27074
  async function sendFeatureAnnouncement(opts, version2, features, storageDir) {
@@ -26737,13 +27091,13 @@ async function sendFeatureAnnouncement(opts, version2, features, storageDir) {
26737
27091
  return;
26738
27092
  const text = [`${FEATURE_MARKER} v${version2}:`, ...features.map((f) => ` \u2022 ${f}`)].join(`
26739
27093
  `);
26740
- sessionLog2(sessionId, `[aft-plugin] sending feature announcement for v${version2}`);
27094
+ sessionLog(sessionId, `[aft-plugin] sending feature announcement for v${version2}`);
26741
27095
  await sendIgnoredMessage(opts.client, sessionId, text);
26742
27096
  }
26743
27097
  if (storageDir) {
26744
27098
  try {
26745
27099
  mkdirSync9(storageDir, { recursive: true });
26746
- writeFileSync8(join16(storageDir, "last_announced_version"), version2);
27100
+ writeFileSync9(join16(storageDir, "last_announced_version"), version2);
26747
27101
  } catch {}
26748
27102
  }
26749
27103
  }
@@ -26771,9 +27125,9 @@ function writeWarnedTools(storageDir, warned) {
26771
27125
  mkdirSync9(storageDir, { recursive: true });
26772
27126
  const warnedToolsPath = join16(storageDir, WARNED_TOOLS_FILE);
26773
27127
  const tmpPath = join16(storageDir, `${WARNED_TOOLS_FILE}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
26774
- writeFileSync8(tmpPath, `${JSON.stringify(warned, null, 2)}
27128
+ writeFileSync9(tmpPath, `${JSON.stringify(warned, null, 2)}
26775
27129
  `);
26776
- renameSync6(tmpPath, warnedToolsPath);
27130
+ renameSync7(tmpPath, warnedToolsPath);
26777
27131
  } catch {}
26778
27132
  }
26779
27133
  async function withWarnedToolsLock(storageDir, fn) {
@@ -26785,7 +27139,7 @@ async function withWarnedToolsLock(storageDir, fn) {
26785
27139
  try {
26786
27140
  return await fn();
26787
27141
  } finally {
26788
- rmSync5(lockDir, { recursive: true, force: true });
27142
+ rmSync6(lockDir, { recursive: true, force: true });
26789
27143
  }
26790
27144
  } catch (err) {
26791
27145
  const code = err.code;
@@ -26886,7 +27240,7 @@ async function cleanupWarnings(opts) {
26886
27240
  }
26887
27241
  if (warningIds.length === 0)
26888
27242
  return;
26889
- sessionLog2(sessionId, `[aft-plugin] cleaning up ${warningIds.length} stale warning(s)`);
27243
+ sessionLog(sessionId, `[aft-plugin] cleaning up ${warningIds.length} stale warning(s)`);
26890
27244
  for (const id of warningIds) {
26891
27245
  await deleteMessage(effectiveServerUrl, sessionId, id);
26892
27246
  }
@@ -26894,16 +27248,16 @@ async function cleanupWarnings(opts) {
26894
27248
 
26895
27249
  // src/shared/rpc-server.ts
26896
27250
  import { randomBytes as randomBytes2 } from "crypto";
26897
- 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";
26898
27252
  import { createServer } from "http";
26899
27253
  import { dirname as dirname6 } from "path";
26900
27254
 
26901
27255
  // src/shared/rpc-utils.ts
26902
- import { createHash as createHash5 } from "crypto";
27256
+ import { createHash as createHash6 } from "crypto";
26903
27257
  import { join as join17 } from "path";
26904
27258
  function projectHash(directory) {
26905
27259
  const normalized = directory.replace(/\/+$/, "");
26906
- return createHash5("sha256").update(normalized).digest("hex").slice(0, 16);
27260
+ return createHash6("sha256").update(normalized).digest("hex").slice(0, 16);
26907
27261
  }
26908
27262
  function rpcPortFilePath(storageDir, directory) {
26909
27263
  const hash2 = projectHash(directory);
@@ -26943,11 +27297,11 @@ class AftRpcServer {
26943
27297
  const dir = dirname6(this.portFilePath);
26944
27298
  mkdirSync10(dir, { recursive: true, mode: 448 });
26945
27299
  const tmpPath = `${this.portFilePath}.tmp`;
26946
- writeFileSync9(tmpPath, JSON.stringify({ port: this.port, token: this.token }), {
27300
+ writeFileSync10(tmpPath, JSON.stringify({ port: this.port, token: this.token }), {
26947
27301
  encoding: "utf-8",
26948
27302
  mode: 384
26949
27303
  });
26950
- renameSync7(tmpPath, this.portFilePath);
27304
+ renameSync8(tmpPath, this.portFilePath);
26951
27305
  log2(`RPC server listening on 127.0.0.1:${this.port}`);
26952
27306
  } catch (err) {
26953
27307
  warn2(`Failed to write RPC port file: ${err}`);
@@ -27044,13 +27398,13 @@ async function getSessionDirectory(client, sessionId, fallbackDirectory) {
27044
27398
  }
27045
27399
  let dir = null;
27046
27400
  try {
27047
- const result = await sessionApi.get({ sessionID: sessionId, directory: fallbackDirectory });
27401
+ const result = await sessionApi.get({ path: { id: sessionId } });
27048
27402
  const session = result?.data ?? result;
27049
27403
  if (session && typeof session.directory === "string" && session.directory.length > 0) {
27050
27404
  dir = session.directory;
27051
27405
  }
27052
27406
  } catch (err) {
27053
- 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)}`);
27054
27408
  return null;
27055
27409
  }
27056
27410
  setCache(sessionId, dir);
@@ -27234,7 +27588,7 @@ function formatStatusMarkdown(status) {
27234
27588
 
27235
27589
  // src/shared/tui-config.ts
27236
27590
  var import_comment_json4 = __toESM(require_src2(), 1);
27237
- 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";
27238
27592
  import { dirname as dirname7, join as join19 } from "path";
27239
27593
 
27240
27594
  // src/shared/opencode-config-dir.ts
@@ -27291,7 +27645,7 @@ function ensureTuiPluginEntry() {
27291
27645
  plugins.push(PLUGIN_ENTRY);
27292
27646
  config2.plugin = plugins;
27293
27647
  mkdirSync11(dirname7(configPath), { recursive: true });
27294
- writeFileSync10(configPath, `${import_comment_json4.stringify(config2, null, 2)}
27648
+ writeFileSync11(configPath, `${import_comment_json4.stringify(config2, null, 2)}
27295
27649
  `);
27296
27650
  log2(`[aft-plugin] added TUI plugin entry to ${configPath}`);
27297
27651
  return true;
@@ -28170,7 +28524,7 @@ var CACHE_MAX_ENTRIES2 = 200;
28170
28524
  var cache2 = new Map;
28171
28525
  async function resolveIsSubagent(client, sessionId, _fallbackDirectory) {
28172
28526
  if (!sessionId) {
28173
- sessionLog2(undefined, "[subagent-detect] no sessionId provided \u2192 primary");
28527
+ sessionLog(undefined, "[subagent-detect] no sessionId provided \u2192 primary");
28174
28528
  return false;
28175
28529
  }
28176
28530
  const cached2 = cache2.get(sessionId);
@@ -28182,11 +28536,11 @@ async function resolveIsSubagent(client, sessionId, _fallbackDirectory) {
28182
28536
  const c = client;
28183
28537
  const sessionApi = c?.session;
28184
28538
  if (!sessionApi || typeof sessionApi.get !== "function") {
28185
- 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`);
28186
28540
  setCache2(sessionId, false);
28187
28541
  return false;
28188
28542
  }
28189
- 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})`);
28190
28544
  let isSubagent = false;
28191
28545
  let parentIdRaw;
28192
28546
  try {
@@ -28196,9 +28550,9 @@ async function resolveIsSubagent(client, sessionId, _fallbackDirectory) {
28196
28550
  const session = result?.data ?? result;
28197
28551
  parentIdRaw = session?.parentID;
28198
28552
  isSubagent = session !== undefined && typeof session.parentID === "string" && session.parentID.length > 0;
28199
- 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}`);
28200
28554
  } catch (err) {
28201
- 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)}`);
28202
28556
  return false;
28203
28557
  }
28204
28558
  setCache2(sessionId, isSubagent);
@@ -28266,7 +28620,7 @@ function createBashTool(ctx) {
28266
28620
  const requestedBackground = args.background === true;
28267
28621
  const effectiveBackground = isSubagent ? false : requestedBackground;
28268
28622
  if (isSubagent && requestedBackground) {
28269
- 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)");
28270
28624
  }
28271
28625
  const shellEnv = await ctx.plugin?.trigger?.("shell.env", { cwd, sessionID: context.sessionID, callID: getCallID(context) }, { env: {} });
28272
28626
  const data = await withPermissionLoop(ctx, context, {
@@ -29040,8 +29394,11 @@ function createEditTool(ctx, writeToolName = "write") {
29040
29394
  ...op,
29041
29395
  file: path4.isAbsolute(op.file) ? op.file : path4.resolve(context.directory, op.file)
29042
29396
  }));
29043
- const data2 = await callBridge(ctx, context, "transaction", { operations: resolvedOps });
29044
- return JSON.stringify(data2);
29397
+ const response = await callBridge(ctx, context, "transaction", { operations: resolvedOps });
29398
+ if (response.success === false) {
29399
+ throw new Error(response.message ?? "transaction failed");
29400
+ }
29401
+ return JSON.stringify(response);
29045
29402
  }
29046
29403
  const file2 = args.filePath;
29047
29404
  if (!file2)
@@ -29520,6 +29877,9 @@ function createDeleteTool(ctx) {
29520
29877
  files: absolutePaths,
29521
29878
  recursive
29522
29879
  });
29880
+ if (response.success === false) {
29881
+ throw new Error(response.message ?? "delete failed");
29882
+ }
29523
29883
  const deletedEntries = response.deleted ?? [];
29524
29884
  const skipped = response.skipped_files ?? [];
29525
29885
  const deleted = deletedEntries.map((entry) => entry.file);
@@ -29603,7 +29963,7 @@ function formatError2(error50) {
29603
29963
  }
29604
29964
  function aftPrefixedTools(ctx) {
29605
29965
  const aftEditTool = createEditTool(ctx, "aft_write");
29606
- return {
29966
+ const tools = {
29607
29967
  aft_read: createReadTool(ctx),
29608
29968
  aft_write: createWriteTool(ctx, "aft_edit"),
29609
29969
  aft_edit: {
@@ -29632,8 +29992,11 @@ function aftPrefixedTools(ctx) {
29632
29992
  create_dirs: normalizedArgs.create_dirs !== false,
29633
29993
  diagnostics: true
29634
29994
  };
29635
- const data = await callBridge(ctx, context, "write", writeParams);
29636
- return JSON.stringify(data);
29995
+ const response = await callBridge(ctx, context, "write", writeParams);
29996
+ if (response.success === false) {
29997
+ throw new Error(response.message ?? "write failed");
29998
+ }
29999
+ return JSON.stringify(response);
29637
30000
  }
29638
30001
  return aftEditTool.execute(normalizedArgs, context);
29639
30002
  }
@@ -29642,6 +30005,16 @@ function aftPrefixedTools(ctx) {
29642
30005
  aft_delete: createDeleteTool(ctx),
29643
30006
  aft_move: createMoveTool(ctx)
29644
30007
  };
30008
+ const bashRewrite = ctx.config.experimental?.bash?.rewrite === true;
30009
+ const bashCompress = ctx.config.experimental?.bash?.compress === true;
30010
+ const bashBackground = ctx.config.experimental?.bash?.background === true;
30011
+ const anyBashExperimental = bashRewrite || bashCompress || bashBackground;
30012
+ if (anyBashExperimental) {
30013
+ tools.aft_bash = createBashTool(ctx);
30014
+ tools.bash_status = createBashStatusTool(ctx);
30015
+ tools.bash_kill = createBashKillTool(ctx);
30016
+ }
30017
+ return tools;
29645
30018
  }
29646
30019
 
29647
30020
  // src/tools/imports.ts
@@ -30025,7 +30398,7 @@ async function queryLspHints(client, symbolName, directory, sessionId) {
30025
30398
  }
30026
30399
  return { symbols: hints };
30027
30400
  } catch (err) {
30028
- sessionWarn2(sessionId, `LSP query failed for "${symbolName}": ${err.message}`);
30401
+ sessionWarn(sessionId, `LSP query failed for "${symbolName}": ${err.message}`);
30029
30402
  return;
30030
30403
  }
30031
30404
  }
@@ -30814,29 +31187,37 @@ ${lines}
30814
31187
  } catch (err) {
30815
31188
  warn2(`[lsp] auto-install setup failed: ${err instanceof Error ? err.message : String(err)}`);
30816
31189
  }
30817
- let versionUpgradeAttempted = null;
31190
+ const versionUpgradePromises = new Map;
30818
31191
  const poolOptions = {
30819
31192
  errorPrefix: "[aft-plugin]",
30820
31193
  minVersion: PLUGIN_VERSION,
30821
- onVersionMismatch: (binaryVersion, minVersion) => {
30822
- if (versionUpgradeAttempted === binaryVersion) {
30823
- log2(`Version ${binaryVersion} < ${minVersion} but upgrade already attempted \u2014 continuing`);
30824
- return;
30825
- }
30826
- versionUpgradeAttempted = binaryVersion;
30827
- warn2(`WARNING: aft binary v${binaryVersion} is older than plugin v${minVersion}. ` + "Some features may not work. Attempting to download a compatible binary...");
30828
- ensureBinary(`v${minVersion}`).then((path7) => {
30829
- if (path7) {
31194
+ onVersionMismatch: async (binaryVersion, minVersion) => {
31195
+ const existing = versionUpgradePromises.get(minVersion);
31196
+ if (existing) {
31197
+ log2(`Version ${binaryVersion} < ${minVersion}; awaiting in-flight compatible binary upgrade`);
31198
+ return existing;
31199
+ }
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
+ }
30830
31208
  log2(`Found/downloaded compatible binary at ${path7}. Replacing running bridges...`);
30831
- pool.replaceBinary(path7).then(() => {
30832
- log2("Binary replaced successfully. New bridges will use the updated binary.");
30833
- }, (err) => error2("Failed to replace binary:", err));
30834
- } else {
30835
- warn2(`Could not find or download v${minVersion}. Continuing with v${binaryVersion}.`);
30836
- }
30837
- }, (err) => {
30838
- error2(`Auto-download failed: ${err.message}. Install manually: cargo install agent-file-tools@${minVersion}`);
30839
- });
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}`);
31214
+ return null;
31215
+ } finally {
31216
+ versionUpgradePromises.delete(minVersion);
31217
+ }
31218
+ })();
31219
+ versionUpgradePromises.set(minVersion, upgradePromise);
31220
+ return upgradePromise;
30840
31221
  },
30841
31222
  onConfigureWarnings: async ({ projectRoot, sessionId, client, warnings }) => {
30842
31223
  const pendingWarnings = sessionId ? drainPendingEagerWarnings(projectRoot) : [];
@@ -30970,7 +31351,7 @@ ${lines}
30970
31351
  if (storageDir && ANNOUNCEMENT_VERSION) {
30971
31352
  try {
30972
31353
  mkdirSync12(storageDir, { recursive: true });
30973
- writeFileSync11(join21(storageDir, "last_announced_version"), ANNOUNCEMENT_VERSION);
31354
+ writeFileSync12(join21(storageDir, "last_announced_version"), ANNOUNCEMENT_VERSION);
30974
31355
  } catch {}
30975
31356
  }
30976
31357
  return { success: true };