@cortexkit/aft-pi 0.26.0 → 0.26.2

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
@@ -6889,7 +6889,7 @@ ${len.toString(16)}\r
6889
6889
  // ../../node_modules/.bun/undici@8.2.0/node_modules/undici/lib/dispatcher/client-h2.js
6890
6890
  var require_client_h2 = __commonJS((exports, module) => {
6891
6891
  var assert = __require("node:assert");
6892
- var { pipeline: pipeline2 } = __require("node:stream");
6892
+ var { pipeline: pipeline3 } = __require("node:stream");
6893
6893
  var util = require_util();
6894
6894
  var {
6895
6895
  RequestContentLengthMismatchError,
@@ -7658,7 +7658,7 @@ var require_client_h2 = __commonJS((exports, module) => {
7658
7658
  }
7659
7659
  function writeStream(abort, socket, expectsPayload, h2stream, body, client, request, contentLength) {
7660
7660
  assert(contentLength !== 0 || client[kRunning] === 0, "stream body cannot be pipelined");
7661
- const pipe = pipeline2(body, h2stream, (err) => {
7661
+ const pipe = pipeline3(body, h2stream, (err) => {
7662
7662
  if (err) {
7663
7663
  util.destroy(pipe, err);
7664
7664
  abort(err);
@@ -10422,7 +10422,7 @@ var require_h2c_client = __commonJS((exports, module) => {
10422
10422
  var require_readable = __commonJS((exports, module) => {
10423
10423
  var assert = __require("node:assert");
10424
10424
  var { addAbortListener } = __require("node:events");
10425
- var { Readable: Readable2 } = __require("node:stream");
10425
+ var { Readable: Readable3 } = __require("node:stream");
10426
10426
  var { RequestAbortedError, NotSupportedError, InvalidArgumentError, AbortError } = require_errors();
10427
10427
  var util = require_util();
10428
10428
  var { ReadableStreamFrom } = require_util();
@@ -10436,7 +10436,7 @@ var require_readable = __commonJS((exports, module) => {
10436
10436
  var kBytesRead = Symbol("kBytesRead");
10437
10437
  var noop = () => {};
10438
10438
 
10439
- class BodyReadable extends Readable2 {
10439
+ class BodyReadable extends Readable3 {
10440
10440
  constructor({
10441
10441
  resume,
10442
10442
  abort,
@@ -10723,7 +10723,7 @@ var require_readable = __commonJS((exports, module) => {
10723
10723
  var require_api_request = __commonJS((exports, module) => {
10724
10724
  var assert = __require("node:assert");
10725
10725
  var { AsyncResource } = __require("node:async_hooks");
10726
- var { Readable: Readable2 } = require_readable();
10726
+ var { Readable: Readable3 } = require_readable();
10727
10727
  var { InvalidArgumentError, RequestAbortedError } = require_errors();
10728
10728
  var util = require_util();
10729
10729
  function noop() {}
@@ -10807,7 +10807,7 @@ var require_api_request = __commonJS((exports, module) => {
10807
10807
  const parsedHeaders = headers;
10808
10808
  const contentType = parsedHeaders?.["content-type"];
10809
10809
  const contentLength = parsedHeaders?.["content-length"];
10810
- const res = new Readable2({
10810
+ const res = new Readable3({
10811
10811
  resume: () => controller.resume(),
10812
10812
  abort: (reason) => controller.abort(reason),
10813
10813
  contentType,
@@ -11137,7 +11137,7 @@ var require_api_stream = __commonJS((exports, module) => {
11137
11137
  // ../../node_modules/.bun/undici@8.2.0/node_modules/undici/lib/api/api-pipeline.js
11138
11138
  var require_api_pipeline = __commonJS((exports, module) => {
11139
11139
  var {
11140
- Readable: Readable2,
11140
+ Readable: Readable3,
11141
11141
  Duplex,
11142
11142
  PassThrough
11143
11143
  } = __require("node:stream");
@@ -11153,7 +11153,7 @@ var require_api_pipeline = __commonJS((exports, module) => {
11153
11153
  function noop() {}
11154
11154
  var kResume = Symbol("resume");
11155
11155
 
11156
- class PipelineRequest extends Readable2 {
11156
+ class PipelineRequest extends Readable3 {
11157
11157
  constructor() {
11158
11158
  super({ autoDestroy: true });
11159
11159
  this[kResume] = null;
@@ -11171,7 +11171,7 @@ var require_api_pipeline = __commonJS((exports, module) => {
11171
11171
  }
11172
11172
  }
11173
11173
 
11174
- class PipelineResponse extends Readable2 {
11174
+ class PipelineResponse extends Readable3 {
11175
11175
  constructor(resume) {
11176
11176
  super({ autoDestroy: true });
11177
11177
  this[kResume] = resume;
@@ -11326,7 +11326,7 @@ var require_api_pipeline = __commonJS((exports, module) => {
11326
11326
  util.destroy(ret, err);
11327
11327
  }
11328
11328
  }
11329
- function pipeline2(opts, handler) {
11329
+ function pipeline3(opts, handler) {
11330
11330
  try {
11331
11331
  const pipelineHandler = new PipelineHandler(opts, handler);
11332
11332
  this.dispatch({ ...opts, body: pipelineHandler.req }, pipelineHandler);
@@ -11335,7 +11335,7 @@ var require_api_pipeline = __commonJS((exports, module) => {
11335
11335
  return new PassThrough().destroy(err);
11336
11336
  }
11337
11337
  }
11338
- module.exports = pipeline2;
11338
+ module.exports = pipeline3;
11339
11339
  });
11340
11340
 
11341
11341
  // ../../node_modules/.bun/undici@8.2.0/node_modules/undici/lib/api/api-upgrade.js
@@ -15358,7 +15358,7 @@ var require_cache_revalidation_handler = __commonJS((exports, module) => {
15358
15358
  // ../../node_modules/.bun/undici@8.2.0/node_modules/undici/lib/interceptor/cache.js
15359
15359
  var require_cache2 = __commonJS((exports, module) => {
15360
15360
  var assert = __require("node:assert");
15361
- var { Readable: Readable2 } = __require("node:stream");
15361
+ var { Readable: Readable3 } = __require("node:stream");
15362
15362
  var util = require_util();
15363
15363
  var CacheHandler = require_cache_handler();
15364
15364
  var MemoryCacheStore = require_memory_cache_store();
@@ -15454,7 +15454,7 @@ var require_cache2 = __commonJS((exports, module) => {
15454
15454
  return dispatch(opts, new CacheHandler(globalOpts, cacheKey, handler));
15455
15455
  }
15456
15456
  function sendCachedValue(handler, opts, result, age, context, isStale2) {
15457
- const stream = util.isStream(result.body) ? result.body : Readable2.from(result.body ?? []);
15457
+ const stream = util.isStream(result.body) ? result.body : Readable3.from(result.body ?? []);
15458
15458
  assert(!stream.destroyed, "stream should not be destroyed");
15459
15459
  assert(!stream.readableDidRead, "stream should not be readableDidRead");
15460
15460
  const controller = {
@@ -15668,7 +15668,7 @@ var require_cache2 = __commonJS((exports, module) => {
15668
15668
  // ../../node_modules/.bun/undici@8.2.0/node_modules/undici/lib/interceptor/decompress.js
15669
15669
  var require_decompress = __commonJS((exports, module) => {
15670
15670
  var { createInflate, createGunzip, createBrotliDecompress, createZstdDecompress } = __require("node:zlib");
15671
- var { pipeline: pipeline2 } = __require("node:stream");
15671
+ var { pipeline: pipeline3 } = __require("node:stream");
15672
15672
  var DecoratorHandler = require_decorator_handler();
15673
15673
  var supportedEncodings = {
15674
15674
  gzip: createGunzip,
@@ -15743,7 +15743,7 @@ var require_decompress = __commonJS((exports, module) => {
15743
15743
  #setupMultipleDecompressors(controller) {
15744
15744
  const lastDecompressor = this.#decompressors[this.#decompressors.length - 1];
15745
15745
  this.#setupDecompressorEvents(lastDecompressor, controller);
15746
- pipeline2(this.#decompressors, (err) => {
15746
+ pipeline3(this.#decompressors, (err) => {
15747
15747
  if (err) {
15748
15748
  super.onResponseError(controller, err);
15749
15749
  return;
@@ -18046,7 +18046,7 @@ var require_fetch = __commonJS((exports, module) => {
18046
18046
  subresourceSet
18047
18047
  } = require_constants3();
18048
18048
  var EE = __require("node:events");
18049
- var { Readable: Readable2, pipeline: pipeline2, finished, isErrored, isReadable } = __require("node:stream");
18049
+ var { Readable: Readable3, pipeline: pipeline3, finished, isErrored, isReadable } = __require("node:stream");
18050
18050
  var { addAbortListener, bufferToLowerCasedHeaderName } = require_util();
18051
18051
  var { dataURLProcessor, serializeAMimeType, minimizeSupportedMimeType } = require_data_url();
18052
18052
  var { getGlobalDispatcher } = require_global2();
@@ -18937,7 +18937,7 @@ var require_fetch = __commonJS((exports, module) => {
18937
18937
  const headersList = new HeadersList;
18938
18938
  appendHeadersListFromResponseHeaders(headersList, headers, rawHeaders);
18939
18939
  const location = headersList.get("location", true);
18940
- this.body = new Readable2({ read: () => controller.resume() });
18940
+ this.body = new Readable3({ read: () => controller.resume() });
18941
18941
  const willFollow = location && request.redirect === "follow" && redirectStatusSet.has(status);
18942
18942
  const decoders = [];
18943
18943
  if (request.method !== "HEAD" && request.method !== "CONNECT" && !nullBodyStatus.includes(status) && !willFollow) {
@@ -18981,7 +18981,7 @@ var require_fetch = __commonJS((exports, module) => {
18981
18981
  status,
18982
18982
  statusText,
18983
18983
  headersList,
18984
- body: decoders.length ? pipeline2(this.body, ...decoders, (err) => {
18984
+ body: decoders.length ? pipeline3(this.body, ...decoders, (err) => {
18985
18985
  if (err) {
18986
18986
  this.onResponseError(controller, err);
18987
18987
  }
@@ -22363,7 +22363,7 @@ ${value}`;
22363
22363
 
22364
22364
  // ../../node_modules/.bun/undici@8.2.0/node_modules/undici/lib/web/eventsource/eventsource.js
22365
22365
  var require_eventsource = __commonJS((exports, module) => {
22366
- var { pipeline: pipeline2 } = __require("node:stream");
22366
+ var { pipeline: pipeline3 } = __require("node:stream");
22367
22367
  var { fetching } = require_fetch();
22368
22368
  var { makeRequest } = require_request2();
22369
22369
  var { webidl } = require_webidl();
@@ -22492,7 +22492,7 @@ var require_eventsource = __commonJS((exports, module) => {
22492
22492
  this.dispatchEvent(createFastMessageEvent(event.type, event.options));
22493
22493
  }
22494
22494
  });
22495
- pipeline2(response.body.stream, eventSourceStream, (error2) => {
22495
+ pipeline3(response.body.stream, eventSourceStream, (error2) => {
22496
22496
  if (error2?.aborted === false) {
22497
22497
  this.close();
22498
22498
  this.dispatchEvent(new Event("error"));
@@ -30490,12 +30490,22 @@ function getActiveLogger() {
30490
30490
  return loggerGlobal()[ACTIVE_LOGGER_SYMBOL];
30491
30491
  }
30492
30492
  function getLogFilePath() {
30493
- return getActiveLogger()?.getLogFilePath?.();
30493
+ try {
30494
+ return getActiveLogger()?.getLogFilePath?.();
30495
+ } catch (err) {
30496
+ console.error(`[aft-bridge] ERROR: active logger getLogFilePath threw: ${err instanceof Error ? err.message : String(err)}`);
30497
+ return;
30498
+ }
30494
30499
  }
30495
30500
  function log(message, meta) {
30496
30501
  const active = getActiveLogger();
30497
30502
  if (active) {
30498
- active.log(message, meta);
30503
+ try {
30504
+ active.log(message, meta);
30505
+ } catch (err) {
30506
+ console.error(`[aft-bridge] ERROR: active logger log threw: ${err instanceof Error ? err.message : String(err)}`);
30507
+ console.error(`[aft-bridge] ${message}`);
30508
+ }
30499
30509
  } else {
30500
30510
  console.error(`[aft-bridge] ${message}`);
30501
30511
  }
@@ -30503,7 +30513,12 @@ function log(message, meta) {
30503
30513
  function warn(message, meta) {
30504
30514
  const active = getActiveLogger();
30505
30515
  if (active) {
30506
- active.warn(message, meta);
30516
+ try {
30517
+ active.warn(message, meta);
30518
+ } catch (err) {
30519
+ console.error(`[aft-bridge] ERROR: active logger warn threw: ${err instanceof Error ? err.message : String(err)}`);
30520
+ console.error(`[aft-bridge] WARN: ${message}`);
30521
+ }
30507
30522
  } else {
30508
30523
  console.error(`[aft-bridge] WARN: ${message}`);
30509
30524
  }
@@ -30511,20 +30526,16 @@ function warn(message, meta) {
30511
30526
  function error(message, meta) {
30512
30527
  const active = getActiveLogger();
30513
30528
  if (active) {
30514
- active.error(message, meta);
30529
+ try {
30530
+ active.error(message, meta);
30531
+ } catch (err) {
30532
+ console.error(`[aft-bridge] ERROR: active logger error threw: ${err instanceof Error ? err.message : String(err)}`);
30533
+ console.error(`[aft-bridge] ERROR: ${message}`);
30534
+ }
30515
30535
  } else {
30516
30536
  console.error(`[aft-bridge] ERROR: ${message}`);
30517
30537
  }
30518
30538
  }
30519
- function sessionLog(sessionId, message) {
30520
- log(message, sessionId ? { sessionId } : undefined);
30521
- }
30522
- function sessionWarn(sessionId, message) {
30523
- warn(message, sessionId ? { sessionId } : undefined);
30524
- }
30525
- function sessionError(sessionId, message) {
30526
- error(message, sessionId ? { sessionId } : undefined);
30527
- }
30528
30539
  // ../aft-bridge/dist/bridge.js
30529
30540
  import { spawn } from "node:child_process";
30530
30541
  import { homedir } from "node:os";
@@ -30619,6 +30630,7 @@ class BinaryBridge {
30619
30630
  pending = new Map;
30620
30631
  nextId = 1;
30621
30632
  stdoutBuffer = "";
30633
+ stderrBuffer = "";
30622
30634
  stderrTail = [];
30623
30635
  _restartCount = 0;
30624
30636
  _shuttingDown = false;
@@ -30654,24 +30666,62 @@ class BinaryBridge {
30654
30666
  }
30655
30667
  logVia(message, meta) {
30656
30668
  const logger = this.logger ?? getActiveLogger();
30657
- if (logger)
30658
- logger.log(message, meta);
30659
- else
30669
+ if (logger) {
30670
+ try {
30671
+ logger.log(message, meta);
30672
+ } catch (err) {
30673
+ console.error(`[aft-bridge] ERROR: logger log threw: ${err instanceof Error ? err.message : String(err)}`);
30674
+ console.error(`[aft-bridge] ${message}`);
30675
+ }
30676
+ } else {
30660
30677
  log(message, meta);
30678
+ }
30661
30679
  }
30662
30680
  warnVia(message, meta) {
30663
30681
  const logger = this.logger ?? getActiveLogger();
30664
- if (logger)
30665
- logger.warn(message, meta);
30666
- else
30682
+ if (logger) {
30683
+ try {
30684
+ logger.warn(message, meta);
30685
+ } catch (err) {
30686
+ console.error(`[aft-bridge] ERROR: logger warn threw: ${err instanceof Error ? err.message : String(err)}`);
30687
+ console.error(`[aft-bridge] WARN: ${message}`);
30688
+ }
30689
+ } else {
30667
30690
  warn(message, meta);
30691
+ }
30668
30692
  }
30669
30693
  errorVia(message, meta) {
30670
30694
  const logger = this.logger ?? getActiveLogger();
30671
- if (logger)
30672
- logger.error(message, meta);
30673
- else
30695
+ if (logger) {
30696
+ try {
30697
+ logger.error(message, meta);
30698
+ } catch (err) {
30699
+ console.error(`[aft-bridge] ERROR: logger error threw: ${err instanceof Error ? err.message : String(err)}`);
30700
+ console.error(`[aft-bridge] ERROR: ${message}`);
30701
+ }
30702
+ } else {
30674
30703
  error(message, meta);
30704
+ }
30705
+ }
30706
+ getLogFilePathVia() {
30707
+ if (this.logger?.getLogFilePath) {
30708
+ try {
30709
+ return this.logger.getLogFilePath();
30710
+ } catch (err) {
30711
+ console.error(`[aft-bridge] ERROR: logger getLogFilePath threw: ${err instanceof Error ? err.message : String(err)}`);
30712
+ return;
30713
+ }
30714
+ }
30715
+ return getLogFilePath();
30716
+ }
30717
+ sessionLogVia(sessionId, message) {
30718
+ this.logVia(message, sessionId ? { sessionId } : undefined);
30719
+ }
30720
+ sessionWarnVia(sessionId, message) {
30721
+ this.warnVia(message, sessionId ? { sessionId } : undefined);
30722
+ }
30723
+ sessionErrorVia(sessionId, message) {
30724
+ this.errorVia(message, sessionId ? { sessionId } : undefined);
30675
30725
  }
30676
30726
  get restartCount() {
30677
30727
  return this._restartCount;
@@ -30730,7 +30780,7 @@ class BinaryBridge {
30730
30780
  await this.deliverConfigureWarnings(configResult, params, options);
30731
30781
  await this.checkVersion();
30732
30782
  if (!this.isAlive()) {
30733
- throw new Error(`${this.errorPrefix} Bridge died during version check. Check logs: ${getLogFilePath()}`);
30783
+ throw new Error(`${this.errorPrefix} Bridge died during version check. Check logs: ${this.getLogFilePathVia()}`);
30734
30784
  }
30735
30785
  this.configured = true;
30736
30786
  } finally {
@@ -30766,9 +30816,9 @@ class BinaryBridge {
30766
30816
  const restartSuffix = keepBridgeOnTimeout ? "" : " — restarting bridge";
30767
30817
  const timeoutMsg = `Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms${restartSuffix}`;
30768
30818
  if (requestSessionId) {
30769
- sessionWarn(requestSessionId, timeoutMsg);
30819
+ this.sessionWarnVia(requestSessionId, timeoutMsg);
30770
30820
  } else {
30771
- warn(timeoutMsg);
30821
+ this.warnVia(timeoutMsg);
30772
30822
  }
30773
30823
  reject(new Error(`${this.errorPrefix} Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
30774
30824
  if (!keepBridgeOnTimeout) {
@@ -30795,7 +30845,7 @@ class BinaryBridge {
30795
30845
  });
30796
30846
  } catch (err) {
30797
30847
  if (err instanceof BridgeReplacedDuringVersionCheck && canRetryAfterVersionSwap && command !== "configure" && command !== "version") {
30798
- log(`Retrying request "${command}" once after coordinated binary replacement: ${err.newBinaryPath}`);
30848
+ this.logVia(`Retrying request "${command}" once after coordinated binary replacement: ${err.newBinaryPath}`);
30799
30849
  return this.sendWithVersionMismatchRetry(command, params, options, false);
30800
30850
  }
30801
30851
  throw err;
@@ -30815,7 +30865,7 @@ class BinaryBridge {
30815
30865
  warnings: configResult.warnings
30816
30866
  });
30817
30867
  } catch (err) {
30818
- warn(`configure warning delivery failed: ${err instanceof Error ? err.message : String(err)}`);
30868
+ this.warnVia(`configure warning delivery failed: ${err instanceof Error ? err.message : String(err)}`);
30819
30869
  } finally {
30820
30870
  if (sessionId) {
30821
30871
  this.configureWarningClients.delete(sessionId);
@@ -30849,7 +30899,7 @@ class BinaryBridge {
30849
30899
  if (!snapshot || typeof snapshot !== "object" || Array.isArray(snapshot))
30850
30900
  return;
30851
30901
  this.cachedStatus = snapshot;
30852
- log("Received status_changed push frame; cached AFT status snapshot");
30902
+ this.logVia("Received status_changed push frame; cached AFT status snapshot");
30853
30903
  for (const listener of this.statusListeners) {
30854
30904
  this.deliverStatusSnapshot(listener, this.cachedStatus);
30855
30905
  }
@@ -30858,7 +30908,7 @@ class BinaryBridge {
30858
30908
  try {
30859
30909
  listener(snapshot);
30860
30910
  } catch (err) {
30861
- warn(`status listener threw: ${err instanceof Error ? err.message : String(err)}`);
30911
+ this.warnVia(`status listener threw: ${err instanceof Error ? err.message : String(err)}`);
30862
30912
  }
30863
30913
  }
30864
30914
  async shutdown() {
@@ -30876,7 +30926,7 @@ class BinaryBridge {
30876
30926
  }, 5000);
30877
30927
  proc.once("exit", () => {
30878
30928
  clearTimeout(forceKillTimer);
30879
- log("Process exited during shutdown");
30929
+ this.logVia("Process exited during shutdown");
30880
30930
  resolve();
30881
30931
  });
30882
30932
  proc.kill("SIGTERM");
@@ -30895,9 +30945,9 @@ class BinaryBridge {
30895
30945
  if (typeof binaryVersion !== "string") {
30896
30946
  throw new Error(`Binary did not report a version — likely too old (minVersion: ${this.minVersion})`);
30897
30947
  }
30898
- log(`Binary version: ${binaryVersion}`);
30948
+ this.logVia(`Binary version: ${binaryVersion}`);
30899
30949
  if (compareSemver(binaryVersion, this.minVersion) < 0) {
30900
- warn(`Binary version ${binaryVersion} is older than required ${this.minVersion}`);
30950
+ this.warnVia(`Binary version ${binaryVersion} is older than required ${this.minVersion}`);
30901
30951
  const replacementPath = await this.onVersionMismatch?.(binaryVersion, this.minVersion);
30902
30952
  if (replacementPath === undefined) {
30903
30953
  return;
@@ -30909,7 +30959,7 @@ class BinaryBridge {
30909
30959
  throw new BridgeReplacedDuringVersionCheck(replacementPath);
30910
30960
  }
30911
30961
  } catch (err) {
30912
- warn(`Version check failed: ${err.message}`);
30962
+ this.warnVia(`Version check failed: ${err.message}`);
30913
30963
  throw err;
30914
30964
  }
30915
30965
  }
@@ -30929,7 +30979,7 @@ class BinaryBridge {
30929
30979
  }, 5000);
30930
30980
  proc.once("exit", () => {
30931
30981
  clearTimeout(forceKillTimer);
30932
- log("Process exited during coordinated binary replacement");
30982
+ this.logVia("Process exited during coordinated binary replacement");
30933
30983
  resolve();
30934
30984
  });
30935
30985
  proc.kill("SIGTERM");
@@ -30942,9 +30992,9 @@ class BinaryBridge {
30942
30992
  }
30943
30993
  spawnProcess(triggeringSessionId) {
30944
30994
  if (triggeringSessionId) {
30945
- sessionLog(triggeringSessionId, `Spawning binary: ${this.binaryPath} (cwd: ${this.cwd})`);
30995
+ this.sessionLogVia(triggeringSessionId, `Spawning binary: ${this.binaryPath} (cwd: ${this.cwd})`);
30946
30996
  } else {
30947
- log(`Spawning binary: ${this.binaryPath} (cwd: ${this.cwd})`);
30997
+ this.logVia(`Spawning binary: ${this.binaryPath} (cwd: ${this.cwd})`);
30948
30998
  }
30949
30999
  const semantic = this.configOverrides.semantic;
30950
31000
  const semanticBackend = (() => {
@@ -30991,11 +31041,12 @@ class BinaryBridge {
30991
31041
  const remaining = stderrDecoder.end();
30992
31042
  if (remaining)
30993
31043
  this.onStderrData(remaining);
31044
+ this.flushStderrBuffer();
30994
31045
  });
30995
31046
  child.on("error", (err) => {
30996
31047
  if (this.process !== currentChild)
30997
31048
  return;
30998
- error(`Process error: ${err.message}${this.formatStderrTail()}`);
31049
+ this.errorVia(`Process error: ${err.message}${this.formatStderrTail()}`);
30999
31050
  this.handleCrash();
31000
31051
  });
31001
31052
  child.on("exit", (code, signal) => {
@@ -31003,7 +31054,7 @@ class BinaryBridge {
31003
31054
  return;
31004
31055
  if (this._shuttingDown)
31005
31056
  return;
31006
- log(`Process exited: code=${code}, signal=${signal}`);
31057
+ this.logVia(`Process exited: code=${code}, signal=${signal}`);
31007
31058
  if (signal === "SIGTERM" || signal === "SIGKILL" || signal === "SIGHUP" || signal === "SIGINT") {
31008
31059
  this.process = null;
31009
31060
  this.configured = false;
@@ -31015,6 +31066,7 @@ class BinaryBridge {
31015
31066
  });
31016
31067
  this.process = child;
31017
31068
  this.stdoutBuffer = "";
31069
+ this.stderrBuffer = "";
31018
31070
  this.stderrTail = [];
31019
31071
  }
31020
31072
  pushStderrLine(line) {
@@ -31024,16 +31076,28 @@ class BinaryBridge {
31024
31076
  }
31025
31077
  }
31026
31078
  onStderrData(data) {
31027
- const lines = data.trimEnd().split(`
31028
- `);
31029
- for (const line of lines) {
31079
+ this.stderrBuffer += data;
31080
+ let newlineIdx;
31081
+ while ((newlineIdx = this.stderrBuffer.indexOf(`
31082
+ `)) !== -1) {
31083
+ const line = this.stderrBuffer.slice(0, newlineIdx).replace(/\r$/, "");
31084
+ this.stderrBuffer = this.stderrBuffer.slice(newlineIdx + 1);
31030
31085
  if (!line)
31031
31086
  continue;
31032
31087
  const tagged = tagStderrLine(line);
31033
- log(tagged);
31088
+ this.logVia(tagged);
31034
31089
  this.pushStderrLine(tagged);
31035
31090
  }
31036
31091
  }
31092
+ flushStderrBuffer() {
31093
+ const line = this.stderrBuffer.replace(/\r$/, "");
31094
+ this.stderrBuffer = "";
31095
+ if (!line)
31096
+ return;
31097
+ const tagged = tagStderrLine(line);
31098
+ this.logVia(tagged);
31099
+ this.pushStderrLine(tagged);
31100
+ }
31037
31101
  formatStderrTail() {
31038
31102
  if (this.stderrTail.length === 0)
31039
31103
  return "";
@@ -31091,7 +31155,7 @@ class BinaryBridge {
31091
31155
  }
31092
31156
  if (response.type === "configure_warnings") {
31093
31157
  this.handleConfigureWarningsFrame(response).catch((err) => {
31094
- warn(`configure warning delivery failed: ${err instanceof Error ? err.message : String(err)}`);
31158
+ this.warnVia(`configure warning delivery failed: ${err instanceof Error ? err.message : String(err)}`);
31095
31159
  });
31096
31160
  continue;
31097
31161
  }
@@ -31109,10 +31173,10 @@ class BinaryBridge {
31109
31173
  this.scheduleRestartCountReset();
31110
31174
  entry.resolve(response);
31111
31175
  } else if (typeof response.type === "string") {
31112
- log(`Ignoring unknown stdout push frame type: ${response.type}`);
31176
+ this.logVia(`Ignoring unknown stdout push frame type: ${response.type}`);
31113
31177
  }
31114
31178
  } catch (_err) {
31115
- warn(`Failed to parse stdout line: ${line}`);
31179
+ this.warnVia(`Failed to parse stdout line: ${line}`);
31116
31180
  }
31117
31181
  }
31118
31182
  }
@@ -31126,17 +31190,17 @@ class BinaryBridge {
31126
31190
  this.configured = false;
31127
31191
  const tail = this.formatStderrTail();
31128
31192
  this.stderrTail = [];
31129
- const killedMsg = tail ? `Bridge killed after timeout.${tail}` : `Bridge killed after timeout (see ${getLogFilePath()})`;
31193
+ const killedMsg = tail ? `Bridge killed after timeout.${tail}` : `Bridge killed after timeout (see ${this.getLogFilePathVia()})`;
31130
31194
  if (tail) {
31131
31195
  if (triggeringSessionId) {
31132
- sessionError(triggeringSessionId, killedMsg);
31196
+ this.sessionErrorVia(triggeringSessionId, killedMsg);
31133
31197
  } else {
31134
- error(killedMsg);
31198
+ this.errorVia(killedMsg);
31135
31199
  }
31136
31200
  } else if (triggeringSessionId) {
31137
- sessionWarn(triggeringSessionId, killedMsg);
31201
+ this.sessionWarnVia(triggeringSessionId, killedMsg);
31138
31202
  } else {
31139
- warn(killedMsg);
31203
+ this.warnVia(killedMsg);
31140
31204
  }
31141
31205
  }
31142
31206
  handleCrash(cause) {
@@ -31149,25 +31213,25 @@ class BinaryBridge {
31149
31213
  this.configured = false;
31150
31214
  const tail = this.formatStderrTail();
31151
31215
  if (tail) {
31152
- error(`Binary crashed (restarts: ${this._restartCount})${cause ? `: ${cause.message}` : ""}.${tail}`);
31216
+ this.errorVia(`Binary crashed (restarts: ${this._restartCount})${cause ? `: ${cause.message}` : ""}.${tail}`);
31153
31217
  }
31154
- this.rejectAllPending(new Error(`${this.errorPrefix} Binary crashed (restarts: ${this._restartCount})${cause ? `: ${cause.message}` : ""} (see ${getLogFilePath()})`));
31218
+ this.rejectAllPending(new Error(`${this.errorPrefix} Binary crashed (restarts: ${this._restartCount})${cause ? `: ${cause.message}` : ""} (see ${this.getLogFilePathVia()})`));
31155
31219
  if (this._restartCount < this.maxRestarts) {
31156
31220
  const delay = 100 * 2 ** this._restartCount;
31157
31221
  this._restartCount++;
31158
- log(`Auto-restart #${this._restartCount} in ${delay}ms`);
31222
+ this.logVia(`Auto-restart #${this._restartCount} in ${delay}ms`);
31159
31223
  setTimeout(() => {
31160
31224
  if (!this._shuttingDown && !this.isAlive()) {
31161
31225
  try {
31162
31226
  this.spawnProcess();
31163
31227
  } catch (err) {
31164
- error(`Failed to restart: ${err.message}`);
31228
+ this.errorVia(`Failed to restart: ${err.message}`);
31165
31229
  }
31166
31230
  }
31167
31231
  }, delay);
31168
31232
  this.scheduleRestartCountReset();
31169
31233
  } else {
31170
- error(`Max restarts (${this.maxRestarts}) reached, giving up. Logs: ${getLogFilePath()}${tail}`);
31234
+ this.errorVia(`Max restarts (${this.maxRestarts}) reached, giving up. Logs: ${this.getLogFilePathVia()}${tail}`);
31171
31235
  this.scheduleRestartCountReset();
31172
31236
  }
31173
31237
  }
@@ -31193,9 +31257,12 @@ class BinaryBridge {
31193
31257
  }
31194
31258
  }
31195
31259
  // ../aft-bridge/dist/downloader.js
31196
- import { chmodSync, existsSync, mkdirSync, unlinkSync } from "node:fs";
31260
+ import { createHash } from "node:crypto";
31261
+ import { chmodSync, closeSync, createWriteStream, existsSync, mkdirSync, openSync, renameSync, rmSync, statSync, unlinkSync } from "node:fs";
31197
31262
  import { homedir as homedir2 } from "node:os";
31198
31263
  import { join as join2 } from "node:path";
31264
+ import { Readable } from "node:stream";
31265
+ import { pipeline } from "node:stream/promises";
31199
31266
 
31200
31267
  // ../aft-bridge/dist/platform.js
31201
31268
  var PLATFORM_ARCH_MAP = {
@@ -31213,6 +31280,11 @@ var PLATFORM_ASSET_MAP = {
31213
31280
 
31214
31281
  // ../aft-bridge/dist/downloader.js
31215
31282
  var REPO = "cortexkit/aft";
31283
+ var DOWNLOAD_TIMEOUT_MS = 300000;
31284
+ var LATEST_TAG_TIMEOUT_MS = 30000;
31285
+ var MAX_DOWNLOAD_BYTES = 200 * 1024 * 1024;
31286
+ var DOWNLOAD_LOCK_TIMEOUT_MS = 120000;
31287
+ var DOWNLOAD_LOCK_STALE_MS = 10 * 60000;
31216
31288
  function getCacheDir() {
31217
31289
  if (process.platform === "win32") {
31218
31290
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
@@ -31254,54 +31326,101 @@ async function downloadBinary(version) {
31254
31326
  const downloadUrl = `https://github.com/${REPO}/releases/download/${tag}/${assetName}`;
31255
31327
  const checksumUrl = `https://github.com/${REPO}/releases/download/${tag}/checksums.sha256`;
31256
31328
  log(`Downloading AFT binary (${tag}) for ${platformKey}...`);
31329
+ const lockPath = join2(versionedCacheDir, ".download.lock");
31330
+ let releaseLock = null;
31331
+ let binaryController = null;
31332
+ let checksumController = null;
31333
+ let binaryTimeout = null;
31334
+ let checksumTimeout = null;
31335
+ const tmpPath = `${binaryPath}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`;
31257
31336
  try {
31258
31337
  if (!existsSync(versionedCacheDir)) {
31259
31338
  mkdirSync(versionedCacheDir, { recursive: true });
31260
31339
  }
31340
+ releaseLock = await acquireDownloadLock(lockPath);
31341
+ if (existsSync(binaryPath)) {
31342
+ return binaryPath;
31343
+ }
31344
+ binaryController = new AbortController;
31345
+ checksumController = new AbortController;
31346
+ const activeBinaryController = binaryController;
31347
+ const activeChecksumController = checksumController;
31348
+ binaryTimeout = setTimeout(() => activeBinaryController.abort(), DOWNLOAD_TIMEOUT_MS);
31349
+ checksumTimeout = setTimeout(() => activeChecksumController.abort(), DOWNLOAD_TIMEOUT_MS);
31261
31350
  const [binaryResponse, checksumResponse] = await Promise.all([
31262
- fetch(downloadUrl, { redirect: "follow" }),
31263
- fetch(checksumUrl, { redirect: "follow" })
31351
+ fetch(downloadUrl, { redirect: "follow", signal: activeBinaryController.signal }),
31352
+ fetch(checksumUrl, { redirect: "follow", signal: activeChecksumController.signal })
31264
31353
  ]);
31265
31354
  if (!binaryResponse.ok) {
31266
31355
  throw new Error(`HTTP ${binaryResponse.status}: ${binaryResponse.statusText} (${downloadUrl})`);
31267
31356
  }
31268
- const arrayBuffer = await binaryResponse.arrayBuffer();
31357
+ if (!binaryResponse.body) {
31358
+ throw new Error(`Download response for ${assetName} had no body`);
31359
+ }
31360
+ const advertised = Number.parseInt(binaryResponse.headers.get("content-length") ?? "", 10);
31361
+ if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES) {
31362
+ throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES}`);
31363
+ }
31269
31364
  if (!checksumResponse.ok) {
31270
31365
  warn(`Checksum verification failed: no checksums.sha256 found for ${tag}. ` + "Binary download aborted for security reasons.");
31271
31366
  return null;
31272
31367
  }
31273
31368
  const checksumText = await checksumResponse.text();
31369
+ clearTimeout(checksumTimeout);
31370
+ checksumTimeout = null;
31274
31371
  const expectedHash = parseChecksumForAsset(checksumText, assetName);
31275
31372
  if (!expectedHash) {
31276
31373
  warn(`Checksum verification failed: checksums.sha256 found but no entry for ${assetName}. ` + "Binary download aborted for security reasons.");
31277
31374
  return null;
31278
31375
  }
31279
- const { createHash } = await import("node:crypto");
31280
- const actualHash = createHash("sha256").update(Buffer.from(arrayBuffer)).digest("hex");
31376
+ const hash = createHash("sha256");
31377
+ let bytesWritten = 0;
31378
+ const guard = new TransformStream({
31379
+ transform(chunk, controller) {
31380
+ bytesWritten += chunk.byteLength;
31381
+ if (bytesWritten > MAX_DOWNLOAD_BYTES) {
31382
+ controller.error(new Error(`download exceeded ${MAX_DOWNLOAD_BYTES} bytes after streaming (server lied about size or sent unbounded body)`));
31383
+ return;
31384
+ }
31385
+ hash.update(chunk);
31386
+ controller.enqueue(chunk);
31387
+ }
31388
+ });
31389
+ const guarded = binaryResponse.body.pipeThrough(guard);
31390
+ const nodeStream = Readable.fromWeb(guarded);
31391
+ await pipeline(nodeStream, createWriteStream(tmpPath), { signal: binaryController.signal });
31392
+ clearTimeout(binaryTimeout);
31393
+ binaryTimeout = null;
31394
+ const actualHash = hash.digest("hex");
31281
31395
  if (actualHash !== expectedHash) {
31282
- throw new Error(`Checksum mismatch for ${assetName}: expected ${expectedHash}, got ${actualHash}. The binary may have been tampered with.`);
31396
+ throw new Error(`Checksum mismatch for ${assetName}: expected ${expectedHash}, got ${actualHash}. ` + "The binary may have been tampered with.");
31283
31397
  }
31284
31398
  log(`Checksum verified (SHA-256: ${actualHash.slice(0, 16)}...)`);
31285
- const tmpPath = `${binaryPath}.tmp`;
31286
- const { writeFileSync } = await import("node:fs");
31287
- writeFileSync(tmpPath, Buffer.from(arrayBuffer));
31288
31399
  if (process.platform !== "win32") {
31289
31400
  chmodSync(tmpPath, 493);
31290
31401
  }
31291
- const { renameSync } = await import("node:fs");
31292
31402
  renameSync(tmpPath, binaryPath);
31293
31403
  log(`AFT binary ready at ${binaryPath}`);
31294
31404
  return binaryPath;
31295
31405
  } catch (err) {
31296
31406
  const msg = err instanceof Error ? err.message : String(err);
31297
31407
  error(`Failed to download AFT binary: ${msg}`);
31298
- const tmpPath = `${binaryPath}.tmp`;
31299
31408
  if (existsSync(tmpPath)) {
31300
31409
  try {
31301
31410
  unlinkSync(tmpPath);
31302
31411
  } catch {}
31303
31412
  }
31304
31413
  return null;
31414
+ } finally {
31415
+ if (binaryTimeout) {
31416
+ binaryController?.abort();
31417
+ clearTimeout(binaryTimeout);
31418
+ }
31419
+ if (checksumTimeout) {
31420
+ checksumController?.abort();
31421
+ clearTimeout(checksumTimeout);
31422
+ }
31423
+ releaseLock?.();
31305
31424
  }
31306
31425
  }
31307
31426
  async function ensureBinary(version) {
@@ -31318,6 +31437,39 @@ async function ensureBinary(version) {
31318
31437
  log("No cached binary found, downloading latest...");
31319
31438
  return downloadBinary();
31320
31439
  }
31440
+ async function acquireDownloadLock(lockPath) {
31441
+ const startedAt = Date.now();
31442
+ while (true) {
31443
+ try {
31444
+ const fd = openSync(lockPath, "wx");
31445
+ return () => {
31446
+ try {
31447
+ closeSync(fd);
31448
+ } catch {}
31449
+ try {
31450
+ rmSync(lockPath, { force: true });
31451
+ } catch {}
31452
+ };
31453
+ } catch (err) {
31454
+ const code = err.code;
31455
+ if (code !== "EEXIST")
31456
+ throw err;
31457
+ try {
31458
+ const ageMs = Date.now() - statSync(lockPath).mtimeMs;
31459
+ if (ageMs > DOWNLOAD_LOCK_STALE_MS) {
31460
+ rmSync(lockPath, { force: true });
31461
+ continue;
31462
+ }
31463
+ } catch {
31464
+ continue;
31465
+ }
31466
+ if (Date.now() - startedAt > DOWNLOAD_LOCK_TIMEOUT_MS) {
31467
+ throw new Error(`Timed out waiting for download lock: ${lockPath}`);
31468
+ }
31469
+ await new Promise((resolve) => setTimeout(resolve, 100));
31470
+ }
31471
+ }
31472
+ }
31321
31473
  function parseChecksumForAsset(checksumText, assetName) {
31322
31474
  for (const line of checksumText.split(`
31323
31475
  `)) {
@@ -31332,9 +31484,12 @@ function parseChecksumForAsset(checksumText, assetName) {
31332
31484
  return null;
31333
31485
  }
31334
31486
  async function fetchLatestTag() {
31487
+ const controller = new AbortController;
31488
+ const timeout = setTimeout(() => controller.abort(), LATEST_TAG_TIMEOUT_MS);
31335
31489
  try {
31336
31490
  const response = await fetch(`https://api.github.com/repos/${REPO}/releases/latest`, {
31337
- headers: { Accept: "application/vnd.github.v3+json" }
31491
+ headers: { Accept: "application/vnd.github.v3+json" },
31492
+ signal: controller.signal
31338
31493
  });
31339
31494
  if (!response.ok)
31340
31495
  return null;
@@ -31342,18 +31497,20 @@ async function fetchLatestTag() {
31342
31497
  return data.tag_name ?? null;
31343
31498
  } catch {
31344
31499
  return null;
31500
+ } finally {
31501
+ clearTimeout(timeout);
31345
31502
  }
31346
31503
  }
31347
31504
  // ../aft-bridge/dist/onnx-runtime.js
31348
31505
  import { execFileSync } from "node:child_process";
31349
- import { createHash } from "node:crypto";
31350
- 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 "node:fs";
31506
+ import { createHash as createHash2 } from "node:crypto";
31507
+ 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 "node:fs";
31351
31508
  import { dirname, join as join3, relative, resolve } from "node:path";
31352
- import { Readable } from "node:stream";
31353
- import { pipeline } from "node:stream/promises";
31509
+ import { Readable as Readable2 } from "node:stream";
31510
+ import { pipeline as pipeline2 } from "node:stream/promises";
31354
31511
  var ORT_VERSION = "1.24.4";
31355
31512
  var ORT_REPO = "microsoft/onnxruntime";
31356
- var MAX_DOWNLOAD_BYTES = 256 * 1024 * 1024;
31513
+ var MAX_DOWNLOAD_BYTES2 = 256 * 1024 * 1024;
31357
31514
  var MAX_EXTRACT_BYTES = 1 * 1024 * 1024 * 1024;
31358
31515
  var ONNX_LOCK_FILE = ".aft-onnx-installing";
31359
31516
  var ONNX_INSTALLED_META_FILE = ".aft-onnx-installed";
@@ -31471,7 +31628,7 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
31471
31628
  if (Number.isFinite(pid) && pid > 0) {
31472
31629
  if (process.platform === "win32") {
31473
31630
  try {
31474
- const ageMs = Date.now() - statSync(stagingDir).mtimeMs;
31631
+ const ageMs = Date.now() - statSync2(stagingDir).mtimeMs;
31475
31632
  abandoned = ageMs > STALE_LOCK_MS;
31476
31633
  } catch {
31477
31634
  abandoned = true;
@@ -31485,7 +31642,7 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
31485
31642
  if (abandoned) {
31486
31643
  log(`[onnx] removing abandoned staging dir ${stagingDir}`);
31487
31644
  try {
31488
- rmSync(stagingDir, { recursive: true, force: true });
31645
+ rmSync2(stagingDir, { recursive: true, force: true });
31489
31646
  } catch (err) {
31490
31647
  warn(`[onnx] failed to remove ${stagingDir}: ${err}`);
31491
31648
  }
@@ -31497,7 +31654,7 @@ function cleanupIncompleteTargetIfUnowned(ortDir) {
31497
31654
  try {
31498
31655
  if (existsSync2(ortDir) && !existsSync2(join3(ortDir, ONNX_INSTALLED_META_FILE))) {
31499
31656
  log(`[onnx] removing half-populated install dir ${ortDir} (no meta file)`);
31500
- rmSync(ortDir, { recursive: true, force: true });
31657
+ rmSync2(ortDir, { recursive: true, force: true });
31501
31658
  }
31502
31659
  } catch (err) {
31503
31660
  warn(`[onnx] failed to sweep ${ortDir}: ${err}`);
@@ -31577,24 +31734,24 @@ async function downloadFileWithCap(url, destPath) {
31577
31734
  throw new Error(`download failed (HTTP ${res.status})`);
31578
31735
  }
31579
31736
  const advertised = Number.parseInt(res.headers.get("content-length") ?? "", 10);
31580
- if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES) {
31581
- throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES}`);
31737
+ if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES2) {
31738
+ throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES2}`);
31582
31739
  }
31583
31740
  mkdirSync2(dirname(destPath), { recursive: true });
31584
31741
  let bytesWritten = 0;
31585
31742
  const guard = new TransformStream({
31586
31743
  transform(chunk, transformController) {
31587
31744
  bytesWritten += chunk.byteLength;
31588
- if (bytesWritten > MAX_DOWNLOAD_BYTES) {
31589
- transformController.error(new Error(`download exceeded ${MAX_DOWNLOAD_BYTES} bytes after streaming (server lied about size or sent unbounded body)`));
31745
+ if (bytesWritten > MAX_DOWNLOAD_BYTES2) {
31746
+ transformController.error(new Error(`download exceeded ${MAX_DOWNLOAD_BYTES2} bytes after streaming (server lied about size or sent unbounded body)`));
31590
31747
  return;
31591
31748
  }
31592
31749
  transformController.enqueue(chunk);
31593
31750
  }
31594
31751
  });
31595
31752
  const guarded = res.body.pipeThrough(guard);
31596
- const nodeStream = Readable.fromWeb(guarded);
31597
- await pipeline(nodeStream, createWriteStream(destPath), { signal: controller.signal });
31753
+ const nodeStream = Readable2.fromWeb(guarded);
31754
+ await pipeline2(nodeStream, createWriteStream2(destPath), { signal: controller.signal });
31598
31755
  } catch (err) {
31599
31756
  try {
31600
31757
  unlinkSync2(destPath);
@@ -31693,16 +31850,16 @@ async function downloadOnnxRuntime(info, targetDir) {
31693
31850
  warn(`Could not hash newly-installed ONNX library at ${libPath}: ${err}`);
31694
31851
  }
31695
31852
  writeOnnxInstalledMeta(targetDir, ORT_VERSION, libHash, archiveSha256);
31696
- rmSync(tmpDir, { recursive: true, force: true });
31853
+ rmSync2(tmpDir, { recursive: true, force: true });
31697
31854
  log(`ONNX Runtime v${ORT_VERSION} installed to ${targetDir}`);
31698
31855
  return targetDir;
31699
31856
  } catch (err) {
31700
31857
  error(`Failed to download ONNX Runtime: ${err}`);
31701
31858
  try {
31702
- rmSync(tmpDir, { recursive: true, force: true });
31859
+ rmSync2(tmpDir, { recursive: true, force: true });
31703
31860
  } catch {}
31704
31861
  try {
31705
- rmSync(targetDir, { recursive: true, force: true });
31862
+ rmSync2(targetDir, { recursive: true, force: true });
31706
31863
  } catch {}
31707
31864
  return null;
31708
31865
  }
@@ -31719,7 +31876,7 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
31719
31876
  }
31720
31877
  } catch (copyErr) {
31721
31878
  if (requiredLibs.has(libFile)) {
31722
- rmSync(targetDir, { recursive: true, force: true });
31879
+ rmSync2(targetDir, { recursive: true, force: true });
31723
31880
  throw copyErr;
31724
31881
  }
31725
31882
  log(`ORT extract: failed to copy optional ${libFile}: ${copyErr}`);
@@ -31734,7 +31891,7 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
31734
31891
  symlinkSync(link.target, dst);
31735
31892
  } catch (symlinkErr) {
31736
31893
  if (requiredLibs.has(link.name)) {
31737
- rmSync(targetDir, { recursive: true, force: true });
31894
+ rmSync2(targetDir, { recursive: true, force: true });
31738
31895
  throw symlinkErr;
31739
31896
  }
31740
31897
  log(`ORT extract: failed to symlink optional ${link.name}: ${symlinkErr}`);
@@ -31742,7 +31899,7 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
31742
31899
  }
31743
31900
  const requiredPath = join3(targetDir, info.libName);
31744
31901
  if (!existsSync2(requiredPath)) {
31745
- rmSync(targetDir, { recursive: true, force: true });
31902
+ rmSync2(targetDir, { recursive: true, force: true });
31746
31903
  throw new Error(`Required ONNX Runtime library missing after install: ${requiredPath}`);
31747
31904
  }
31748
31905
  }
@@ -31775,7 +31932,7 @@ function writeOnnxInstalledMeta(installDir, version, sha256, archiveSha256) {
31775
31932
  function readOnnxInstalledMeta(installDir) {
31776
31933
  const path = join3(installDir, ONNX_INSTALLED_META_FILE);
31777
31934
  try {
31778
- if (!statSync(path).isFile())
31935
+ if (!statSync2(path).isFile())
31779
31936
  return null;
31780
31937
  const raw = readFileSync(path, "utf8");
31781
31938
  const parsed = JSON.parse(raw);
@@ -31792,20 +31949,20 @@ function readOnnxInstalledMeta(installDir) {
31792
31949
  }
31793
31950
  }
31794
31951
  function sha256File(path) {
31795
- const hash = createHash("sha256");
31952
+ const hash = createHash2("sha256");
31796
31953
  hash.update(readFileSync(path));
31797
31954
  return hash.digest("hex");
31798
31955
  }
31799
31956
  function acquireLock(lockPath) {
31800
31957
  const tryClaim = () => {
31801
31958
  try {
31802
- const fd = openSync(lockPath, "wx");
31959
+ const fd = openSync2(lockPath, "wx");
31803
31960
  try {
31804
31961
  writeFileSync(fd, `${process.pid}
31805
31962
  ${new Date().toISOString()}
31806
31963
  `);
31807
31964
  } finally {
31808
- closeSync(fd);
31965
+ closeSync2(fd);
31809
31966
  }
31810
31967
  return true;
31811
31968
  } catch (err) {
@@ -31826,7 +31983,7 @@ ${new Date().toISOString()}
31826
31983
  const parsed = Number.parseInt(firstLine, 10);
31827
31984
  if (Number.isFinite(parsed) && parsed > 0)
31828
31985
  owningPid = parsed;
31829
- lockMtimeMs = statSync(lockPath).mtimeMs;
31986
+ lockMtimeMs = statSync2(lockPath).mtimeMs;
31830
31987
  } catch {
31831
31988
  return tryClaim();
31832
31989
  }
@@ -31892,15 +32049,6 @@ import { homedir as homedir3 } from "node:os";
31892
32049
  var DEFAULT_IDLE_TIMEOUT_MS = Infinity;
31893
32050
  var DEFAULT_MAX_POOL_SIZE = 8;
31894
32051
  var CLEANUP_INTERVAL_MS = 60 * 1000;
31895
-
31896
- class HomeProjectRootError extends Error {
31897
- projectRoot;
31898
- constructor(projectRoot) {
31899
- super(`aft refuses to spawn a bridge with project_root=${projectRoot} (user home directory). ` + `Open OpenCode/Pi from a project subdirectory instead, or set the session's ` + `directory to a real project root.`);
31900
- this.projectRoot = projectRoot;
31901
- this.name = "HomeProjectRootError";
31902
- }
31903
- }
31904
32052
  function canonicalHomeDir() {
31905
32053
  try {
31906
32054
  const home = homedir3();
@@ -31943,7 +32091,9 @@ class BridgePool {
31943
32091
  onVersionMismatch: options.onVersionMismatch,
31944
32092
  onConfigureWarnings: options.onConfigureWarnings,
31945
32093
  onBashCompletion: options.onBashCompletion,
31946
- onBashLongRunning: options.onBashLongRunning
32094
+ onBashLongRunning: options.onBashLongRunning,
32095
+ errorPrefix: options.errorPrefix,
32096
+ logger: options.logger
31947
32097
  };
31948
32098
  this.configOverrides = configOverrides;
31949
32099
  if (Number.isFinite(this.idleTimeoutMs)) {
@@ -31961,9 +32111,6 @@ class BridgePool {
31961
32111
  }
31962
32112
  getBridge(projectRoot) {
31963
32113
  const key = normalizeKey(projectRoot);
31964
- if (isHomeDirectoryRoot(key)) {
31965
- throw new HomeProjectRootError(key);
31966
- }
31967
32114
  const existing = this.bridges.get(key);
31968
32115
  if (existing) {
31969
32116
  existing.lastUsed = Date.now();
@@ -32015,24 +32162,32 @@ class BridgePool {
32015
32162
  }
32016
32163
  async replaceBinary(newPath) {
32017
32164
  this.binaryPath = newPath;
32018
- const shutdowns = Array.from(this.bridges.values()).map((entry) => entry.bridge.shutdown());
32019
32165
  this.bridges.clear();
32020
- await Promise.allSettled(shutdowns);
32021
32166
  this.log(`Binary path updated to ${newPath}. All bridges cleared — next calls will use the new binary.`);
32022
32167
  return newPath;
32023
32168
  }
32024
32169
  log(message, meta) {
32025
32170
  const logger = this.logger ?? getActiveLogger();
32026
- if (logger)
32027
- logger.log(message, meta);
32028
- else
32171
+ if (logger) {
32172
+ try {
32173
+ logger.log(message, meta);
32174
+ } catch (err) {
32175
+ console.error(`[aft-bridge] ERROR: pool logger log threw: ${err instanceof Error ? err.message : String(err)}`);
32176
+ console.error(`[aft-bridge] ${message}`);
32177
+ }
32178
+ } else
32029
32179
  log(message, meta);
32030
32180
  }
32031
32181
  error(message, meta) {
32032
32182
  const logger = this.logger ?? getActiveLogger();
32033
- if (logger)
32034
- logger.error(message, meta);
32035
- else
32183
+ if (logger) {
32184
+ try {
32185
+ logger.error(message, meta);
32186
+ } catch (err) {
32187
+ console.error(`[aft-bridge] ERROR: pool logger error threw: ${err instanceof Error ? err.message : String(err)}`);
32188
+ console.error(`[aft-bridge] ERROR: ${message}`);
32189
+ }
32190
+ } else
32036
32191
  error(message, meta);
32037
32192
  }
32038
32193
  setConfigureOverride(key, value) {
@@ -32059,7 +32214,7 @@ function normalizeKey(projectRoot) {
32059
32214
  }
32060
32215
  // ../aft-bridge/dist/resolver.js
32061
32216
  import { execSync, spawnSync } from "node:child_process";
32062
- import { chmodSync as chmodSync3, copyFileSync as copyFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync3, renameSync } from "node:fs";
32217
+ import { chmodSync as chmodSync3, copyFileSync as copyFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync3, renameSync as renameSync2 } from "node:fs";
32063
32218
  import { createRequire as createRequire2 } from "node:module";
32064
32219
  import { homedir as homedir4 } from "node:os";
32065
32220
  import { join as join4 } from "node:path";
@@ -32090,15 +32245,19 @@ function copyToVersionedCache(npmBinaryPath, knownVersion) {
32090
32245
  const versionedDir = join4(cacheDir, tag);
32091
32246
  const ext = process.platform === "win32" ? ".exe" : "";
32092
32247
  const cachedPath = join4(versionedDir, `aft${ext}`);
32093
- if (existsSync3(cachedPath))
32094
- return cachedPath;
32248
+ if (existsSync3(cachedPath)) {
32249
+ const cachedVersion = readBinaryVersion(cachedPath);
32250
+ if (cachedVersion === version)
32251
+ return cachedPath;
32252
+ warn(`Cached binary at ${cachedPath} reports ${cachedVersion ?? "no version"}, expected ${version}; refreshing from npm package`);
32253
+ }
32095
32254
  mkdirSync3(versionedDir, { recursive: true });
32096
- const tmpPath = `${cachedPath}.tmp`;
32255
+ const tmpPath = `${cachedPath}.${process.pid}.${Date.now()}.tmp`;
32097
32256
  copyFileSync2(npmBinaryPath, tmpPath);
32098
32257
  if (process.platform !== "win32") {
32099
32258
  chmodSync3(tmpPath, 493);
32100
32259
  }
32101
- renameSync(tmpPath, cachedPath);
32260
+ renameSync2(tmpPath, cachedPath);
32102
32261
  log(`Copied npm binary to versioned cache: ${cachedPath}`);
32103
32262
  return cachedPath;
32104
32263
  } catch (err) {
@@ -32106,6 +32265,32 @@ function copyToVersionedCache(npmBinaryPath, knownVersion) {
32106
32265
  return null;
32107
32266
  }
32108
32267
  }
32268
+ function normalizeBareVersion(version) {
32269
+ return version.startsWith("v") ? version.slice(1) : version;
32270
+ }
32271
+ function homeDirFromEnv(env) {
32272
+ return (process.platform === "win32" ? env.USERPROFILE || env.HOME : env.HOME) || homedir4();
32273
+ }
32274
+ function cacheDirFromEnv(env) {
32275
+ if (process.platform === "win32") {
32276
+ const base2 = env.LOCALAPPDATA || env.APPDATA || join4(homeDirFromEnv(env), "AppData", "Local");
32277
+ return join4(base2, "aft", "bin");
32278
+ }
32279
+ const base = env.XDG_CACHE_HOME || join4(homeDirFromEnv(env), ".cache");
32280
+ return join4(base, "aft", "bin");
32281
+ }
32282
+ function cachedBinaryPathFromEnv(version, env, ext) {
32283
+ const binaryPath = join4(cacheDirFromEnv(env), version, `aft${ext}`);
32284
+ return existsSync3(binaryPath) ? binaryPath : null;
32285
+ }
32286
+ function isExpectedCachedBinary(binaryPath, expectedVersion) {
32287
+ const expected = normalizeBareVersion(expectedVersion);
32288
+ const actual = readBinaryVersion(binaryPath);
32289
+ if (actual === expected)
32290
+ return true;
32291
+ warn(`Cached binary at ${binaryPath} reports ${actual ?? "no version"}, expected ${expected}; skipping cache candidate`);
32292
+ return false;
32293
+ }
32109
32294
  function platformKey(platform = process.platform, arch = process.arch) {
32110
32295
  const archMap = PLATFORM_ARCH_MAP[platform];
32111
32296
  if (!archMap) {
@@ -32119,6 +32304,7 @@ function platformKey(platform = process.platform, arch = process.arch) {
32119
32304
  }
32120
32305
  function findBinarySync(expectedVersion) {
32121
32306
  const ext = process.platform === "win32" ? ".exe" : "";
32307
+ const env = { ...process.env };
32122
32308
  const pluginVersion = expectedVersion ?? (() => {
32123
32309
  try {
32124
32310
  const req = createRequire2(import.meta.url);
@@ -32129,8 +32315,8 @@ function findBinarySync(expectedVersion) {
32129
32315
  })();
32130
32316
  if (pluginVersion) {
32131
32317
  const tag = pluginVersion.startsWith("v") ? pluginVersion : `v${pluginVersion}`;
32132
- const versionCached = getCachedBinaryPath(tag);
32133
- if (versionCached)
32318
+ const versionCached = cachedBinaryPathFromEnv(tag, env, ext);
32319
+ if (versionCached && isExpectedCachedBinary(versionCached, pluginVersion))
32134
32320
  return versionCached;
32135
32321
  }
32136
32322
  try {
@@ -32140,10 +32326,12 @@ function findBinarySync(expectedVersion) {
32140
32326
  const resolved = req.resolve(packageBin);
32141
32327
  if (existsSync3(resolved)) {
32142
32328
  const npmVersion = readBinaryVersion(resolved);
32143
- if (pluginVersion && npmVersion && npmVersion !== pluginVersion) {
32329
+ if (npmVersion === null) {
32330
+ warn(`npm platform package binary at ${resolved} did not report a version; skipping (continuing to PATH lookup)`);
32331
+ } else if (pluginVersion && npmVersion !== normalizeBareVersion(pluginVersion)) {
32144
32332
  warn(`npm platform package binary v${npmVersion} does not match plugin v${pluginVersion}; skipping (continuing to PATH lookup)`);
32145
32333
  } else {
32146
- const copied = copyToVersionedCache(resolved, npmVersion ?? undefined);
32334
+ const copied = copyToVersionedCache(resolved, npmVersion);
32147
32335
  return copied ?? resolved;
32148
32336
  }
32149
32337
  }
@@ -32152,12 +32340,13 @@ function findBinarySync(expectedVersion) {
32152
32340
  const whichCmd = process.platform === "win32" ? "where aft" : "which aft";
32153
32341
  const result = execSync(whichCmd, {
32154
32342
  encoding: "utf-8",
32343
+ env,
32155
32344
  stdio: ["pipe", "pipe", "pipe"]
32156
32345
  }).trim();
32157
32346
  if (result)
32158
32347
  return result;
32159
32348
  } catch {}
32160
- const cargoPath = join4(homedir4(), ".cargo", "bin", `aft${ext}`);
32349
+ const cargoPath = join4(homeDirFromEnv(env), ".cargo", "bin", `aft${ext}`);
32161
32350
  if (existsSync3(cargoPath))
32162
32351
  return cargoPath;
32163
32352
  return null;
@@ -32192,7 +32381,7 @@ async function findBinary(expectedVersion) {
32192
32381
  `));
32193
32382
  }
32194
32383
  // ../aft-bridge/dist/url-fetch.js
32195
- import { createHash as createHash2 } from "node:crypto";
32384
+ import { createHash as createHash3 } from "node:crypto";
32196
32385
  import { lookup } from "node:dns/promises";
32197
32386
  import { existsSync as existsSync4, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "node:fs";
32198
32387
  import { isIP } from "node:net";
@@ -32349,7 +32538,7 @@ function cacheDir(storageDir) {
32349
32538
  return join5(storageDir, "url_cache");
32350
32539
  }
32351
32540
  function hashUrl(url) {
32352
- return createHash2("sha256").update(url).digest("hex").slice(0, 16);
32541
+ return createHash3("sha256").update(url).digest("hex").slice(0, 16);
32353
32542
  }
32354
32543
  function metaPath(storageDir, hash) {
32355
32544
  return join5(cacheDir(storageDir), `${hash}.meta.json`);
@@ -32607,8 +32796,8 @@ async function fetchUrlToTempFile(url, storageDir, options = {}) {
32607
32796
  const contentFile = contentPath(storageDir, hash, extension);
32608
32797
  const tmpContent = `${contentFile}.tmp-${process.pid}`;
32609
32798
  writeFileSync2(tmpContent, body);
32610
- const { renameSync: renameSync2 } = await import("node:fs");
32611
- renameSync2(tmpContent, contentFile);
32799
+ const { renameSync: renameSync3 } = await import("node:fs");
32800
+ renameSync3(tmpContent, contentFile);
32612
32801
  const meta = {
32613
32802
  url,
32614
32803
  contentType,
@@ -32617,7 +32806,7 @@ async function fetchUrlToTempFile(url, storageDir, options = {}) {
32617
32806
  };
32618
32807
  const tmpMeta = `${metaFile}.tmp-${process.pid}`;
32619
32808
  writeFileSync2(tmpMeta, JSON.stringify(meta));
32620
- renameSync2(tmpMeta, metaFile);
32809
+ renameSync3(tmpMeta, metaFile);
32621
32810
  log(`URL cached (${total} bytes): ${url}`);
32622
32811
  return contentFile;
32623
32812
  }
@@ -32735,31 +32924,31 @@ function warn2(message, data) {
32735
32924
  function error2(message, data) {
32736
32925
  write("ERROR", message, data);
32737
32926
  }
32738
- function sessionLog2(sessionId, message, data) {
32927
+ function sessionLog(sessionId, message, data) {
32739
32928
  write("INFO", message, data, sessionId);
32740
32929
  }
32741
- function sessionWarn2(sessionId, message, data) {
32930
+ function sessionWarn(sessionId, message, data) {
32742
32931
  write("WARN", message, data, sessionId);
32743
32932
  }
32744
- function sessionError2(sessionId, message, data) {
32933
+ function sessionError(sessionId, message, data) {
32745
32934
  write("ERROR", message, data, sessionId);
32746
32935
  }
32747
32936
  var bridgeLogger = {
32748
32937
  log(message, meta) {
32749
32938
  if (meta?.sessionId)
32750
- sessionLog2(meta.sessionId, message);
32939
+ sessionLog(meta.sessionId, message);
32751
32940
  else
32752
32941
  write("INFO", message);
32753
32942
  },
32754
32943
  warn(message, meta) {
32755
32944
  if (meta?.sessionId)
32756
- sessionWarn2(meta.sessionId, message);
32945
+ sessionWarn(meta.sessionId, message);
32757
32946
  else
32758
32947
  write("WARN", message);
32759
32948
  },
32760
32949
  error(message, meta) {
32761
32950
  if (meta?.sessionId)
32762
- sessionError2(meta.sessionId, message);
32951
+ sessionError(meta.sessionId, message);
32763
32952
  else
32764
32953
  write("ERROR", message);
32765
32954
  },
@@ -32771,6 +32960,7 @@ var sessionBgStates = new Map;
32771
32960
  var SESSION_BG_STATE_IDLE_TTL_MS = 60 * 60 * 1000;
32772
32961
  var DEBOUNCE_STEP_MS = 200;
32773
32962
  var DEBOUNCE_CAP_MS = 1000;
32963
+ var MAX_WAKE_SEND_ATTEMPTS = 5;
32774
32964
  var UNKNOWN_COMPLETION_TTL_MS = 5000;
32775
32965
  var UNKNOWN_COMPLETION_CAP = 32;
32776
32966
  var DEFAULT_SESSION_ID = "__default__";
@@ -32820,16 +33010,22 @@ async function handlePushedBgLongRunning(drainContext, reminder) {
32820
33010
  }
32821
33011
  async function appendToolResultBgCompletions(drainContext, content) {
32822
33012
  const state = stateFor(drainContext.sessionID);
33013
+ if (state.outstandingTaskIds.size === 0 && state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0)
33014
+ await drainCompletions(drainContext);
32823
33015
  if (state.outstandingTaskIds.size === 0 && state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0)
32824
33016
  return;
32825
- if (state.outstandingTaskIds.size > 0) {
33017
+ if (state.outstandingTaskIds.size > 0 || !state.forcedDrainCompleted) {
32826
33018
  await drainCompletions(drainContext);
32827
33019
  }
32828
33020
  if (state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0)
32829
33021
  return;
33022
+ const deliveredCompletions = [...state.pendingCompletions];
32830
33023
  const reminder = formatCombinedSystemReminder(state.pendingCompletions, state.pendingLongRunning);
32831
33024
  state.pendingCompletions = [];
32832
33025
  state.pendingLongRunning = [];
33026
+ state.wakeRetryAttempts = 0;
33027
+ state.wakeHardStopped = false;
33028
+ await ackCompletions(drainContext, deliveredCompletions);
32833
33029
  if (state.debounceTimer) {
32834
33030
  clearTimeout(state.debounceTimer);
32835
33031
  state.debounceTimer = null;
@@ -32844,15 +33040,16 @@ async function handleTurnEndBgCompletions(drainContext) {
32844
33040
  }
32845
33041
  async function triggerWakeIfPending(drainContext, skipDrain) {
32846
33042
  const state = stateFor(drainContext.sessionID);
32847
- if (!skipDrain && state.outstandingTaskIds.size > 0) {
33043
+ if (!skipDrain && (state.outstandingTaskIds.size > 0 || !state.forcedDrainCompleted)) {
32848
33044
  await drainCompletions(drainContext);
32849
33045
  }
32850
33046
  if (state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0)
32851
33047
  return;
32852
- scheduleWake(state, async (reminder) => {
33048
+ scheduleWake(state, async (reminder, deliveredCompletions) => {
32853
33049
  drainContext.runtime.sendUserMessage(reminder, { deliverAs: "steer" });
32854
- }, (err) => {
32855
- sessionWarn2(drainContext.sessionID ?? "", `${LOG_PREFIX} wake send failed: ${err instanceof Error ? err.message : String(err)}`);
33050
+ await ackCompletions(drainContext, deliveredCompletions);
33051
+ }, (err, hardStopped) => {
33052
+ 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)}`);
32856
33053
  });
32857
33054
  }
32858
33055
  function formatSystemReminder(completions) {
@@ -32885,20 +33082,39 @@ function formatCombinedSystemReminder(completions, longRunning) {
32885
33082
  ${formatLongRunningReminder(longRunning)}`;
32886
33083
  }
32887
33084
  async function drainCompletions({ ctx, directory, sessionID }) {
33085
+ const state = stateFor(sessionID);
32888
33086
  try {
32889
33087
  const bridge = ctx.pool.getActiveBridgeForRoot(directory) ?? ctx.pool.getBridge(directory);
32890
33088
  const params = sessionID ? { session_id: sessionID } : {};
32891
33089
  const response = await bridge.send("bash_drain_completions", params);
32892
33090
  if (response.success === false) {
32893
- sessionWarn2(sessionID ?? "", `${LOG_PREFIX} drain failed: ${String(response.message ?? "unknown error")}`);
33091
+ sessionWarn(sessionID ?? "", `${LOG_PREFIX} drain failed: ${String(response.message ?? "unknown error")}`);
32894
33092
  return;
32895
33093
  }
32896
- ingestBgCompletions(sessionID, response.bg_completions);
33094
+ state.forcedDrainCompleted = true;
33095
+ ingestDrainedBgCompletions(sessionID, response.bg_completions);
32897
33096
  } catch (err) {
32898
- sessionWarn2(sessionID ?? "", `${LOG_PREFIX} drain failed: ${err instanceof Error ? err.message : String(err)}`);
33097
+ sessionWarn(sessionID ?? "", `${LOG_PREFIX} drain failed: ${err instanceof Error ? err.message : String(err)}`);
33098
+ }
33099
+ }
33100
+ async function ackCompletions({ ctx, directory, sessionID }, completions) {
33101
+ const taskIds = [...new Set(completions.map((completion) => completion.task_id))];
33102
+ if (taskIds.length === 0)
33103
+ return;
33104
+ try {
33105
+ const bridge = ctx.pool.getActiveBridgeForRoot(directory) ?? ctx.pool.getBridge(directory);
33106
+ const params = sessionID ? { session_id: sessionID, task_ids: taskIds } : { task_ids: taskIds };
33107
+ const response = await bridge.send("bash_ack_completions", params);
33108
+ if (response.success === false) {
33109
+ sessionWarn(sessionID ?? "", `${LOG_PREFIX} ack failed: ${String(response.message ?? "unknown error")}`);
33110
+ }
33111
+ } catch (err) {
33112
+ sessionWarn(sessionID ?? "", `${LOG_PREFIX} ack failed: ${err instanceof Error ? err.message : String(err)}`);
32899
33113
  }
32900
33114
  }
32901
33115
  function scheduleWake(state, sendWake, onSendFailure) {
33116
+ if (state.wakeHardStopped)
33117
+ return;
32902
33118
  const now = Date.now();
32903
33119
  const pendingCount = state.pendingCompletions.length + state.pendingLongRunning.length;
32904
33120
  if (state.debounceTimer && pendingCount <= state.scheduledCompletionCount) {
@@ -32927,13 +33143,22 @@ function scheduleWake(state, sendWake, onSendFailure) {
32927
33143
  const reminder = formatCombinedSystemReminder(pending, pendingLongRunning);
32928
33144
  state.pendingCompletions = [];
32929
33145
  state.pendingLongRunning = [];
32930
- sendWake(reminder).then(() => {
33146
+ sendWake(reminder, pending).then(() => {
32931
33147
  state.retryDelayMs = null;
33148
+ state.wakeRetryAttempts = 0;
33149
+ state.wakeHardStopped = false;
32932
33150
  }).catch((err) => {
32933
33151
  state.pendingCompletions = [...pending, ...state.pendingCompletions];
32934
33152
  state.pendingLongRunning = [...pendingLongRunning, ...state.pendingLongRunning];
33153
+ state.wakeRetryAttempts += 1;
33154
+ if (state.wakeRetryAttempts >= MAX_WAKE_SEND_ATTEMPTS) {
33155
+ state.retryDelayMs = null;
33156
+ state.wakeHardStopped = true;
33157
+ onSendFailure(err, true);
33158
+ return;
33159
+ }
32935
33160
  state.retryDelayMs = Math.min((delay || DEBOUNCE_STEP_MS) * 2, DEBOUNCE_CAP_MS);
32936
- onSendFailure(err);
33161
+ onSendFailure(err, false);
32937
33162
  scheduleWake(state, sendWake, onSendFailure);
32938
33163
  });
32939
33164
  }, delay);
@@ -32954,6 +33179,9 @@ function stateFor(sessionID) {
32954
33179
  scheduledFireAt: null,
32955
33180
  scheduledCompletionCount: 0,
32956
33181
  retryDelayMs: null,
33182
+ wakeRetryAttempts: 0,
33183
+ wakeHardStopped: false,
33184
+ forcedDrainCompleted: false,
32957
33185
  unknownCompletions: [],
32958
33186
  lastSeenAt: now
32959
33187
  };
@@ -32963,6 +33191,22 @@ function stateFor(sessionID) {
32963
33191
  }
32964
33192
  return state;
32965
33193
  }
33194
+ function ingestDrainedBgCompletions(sessionID, completions) {
33195
+ if (!Array.isArray(completions) || completions.length === 0)
33196
+ return [];
33197
+ const state = stateFor(sessionID);
33198
+ const accepted = [];
33199
+ for (const completion of completions) {
33200
+ if (!isBgCompletion(completion))
33201
+ continue;
33202
+ state.outstandingTaskIds.delete(completion.task_id);
33203
+ if (!state.pendingCompletions.some((pending) => pending.task_id === completion.task_id) && !accepted.some((pending) => pending.task_id === completion.task_id)) {
33204
+ accepted.push(completion);
33205
+ }
33206
+ }
33207
+ state.pendingCompletions.push(...accepted);
33208
+ return accepted;
33209
+ }
32966
33210
  function cleanupIdleSessionStates(now = Date.now()) {
32967
33211
  const cutoff = now - SESSION_BG_STATE_IDLE_TTL_MS;
32968
33212
  for (const [sessionID, state] of sessionBgStates) {
@@ -33055,7 +33299,7 @@ import {
33055
33299
  // package.json
33056
33300
  var package_default = {
33057
33301
  name: "@cortexkit/aft-pi",
33058
- version: "0.26.0",
33302
+ version: "0.26.2",
33059
33303
  type: "module",
33060
33304
  description: "Pi coding agent extension for Agent File Tools (AFT) — tree-sitter and LSP-powered code analysis",
33061
33305
  main: "dist/index.js",
@@ -33077,18 +33321,18 @@ var package_default = {
33077
33321
  prepublishOnly: "bun run build"
33078
33322
  },
33079
33323
  dependencies: {
33080
- "@cortexkit/aft-bridge": "0.26.0",
33324
+ "@cortexkit/aft-bridge": "0.26.2",
33081
33325
  typebox: "^1.1.24",
33082
33326
  "comment-json": "^5.0.0",
33083
33327
  diff: "^8.0.4",
33084
33328
  zod: "^4.1.8"
33085
33329
  },
33086
33330
  optionalDependencies: {
33087
- "@cortexkit/aft-darwin-arm64": "0.26.0",
33088
- "@cortexkit/aft-darwin-x64": "0.26.0",
33089
- "@cortexkit/aft-linux-arm64": "0.26.0",
33090
- "@cortexkit/aft-linux-x64": "0.26.0",
33091
- "@cortexkit/aft-win32-x64": "0.26.0"
33331
+ "@cortexkit/aft-darwin-arm64": "0.26.2",
33332
+ "@cortexkit/aft-darwin-x64": "0.26.2",
33333
+ "@cortexkit/aft-linux-arm64": "0.26.2",
33334
+ "@cortexkit/aft-linux-x64": "0.26.2",
33335
+ "@cortexkit/aft-win32-x64": "0.26.2"
33092
33336
  },
33093
33337
  devDependencies: {
33094
33338
  "@earendil-works/pi-coding-agent": "*",
@@ -33573,7 +33817,7 @@ function registerStatusCommand(pi, ctx) {
33573
33817
 
33574
33818
  // src/config.ts
33575
33819
  var import_comment_json = __toESM(require_src2(), 1);
33576
- import { existsSync as existsSync5, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync as unlinkSync4, writeFileSync as writeFileSync3 } from "node:fs";
33820
+ import { existsSync as existsSync5, readFileSync as readFileSync3, renameSync as renameSync3, unlinkSync as unlinkSync4, writeFileSync as writeFileSync3 } from "node:fs";
33577
33821
  import { homedir as homedir5 } from "node:os";
33578
33822
  import { join as join7 } from "node:path";
33579
33823
 
@@ -47348,7 +47592,7 @@ function migrateAftConfigFile(configPath, logger = { log: log2, warn: warn2 }) {
47348
47592
  ${serialized}` : serialized;
47349
47593
  tmpPath = `${configPath}.tmp.${process.pid}`;
47350
47594
  writeFileSync3(tmpPath, nextContent, "utf-8");
47351
- renameSync2(tmpPath, configPath);
47595
+ renameSync3(tmpPath, configPath);
47352
47596
  logger.log(`Migrated config at ${configPath}: removed ${oldKeys.join(", ")}`);
47353
47597
  return { migrated: true, oldKeys };
47354
47598
  } catch (err) {
@@ -47563,17 +47807,17 @@ function loadAftConfig(projectDirectory) {
47563
47807
 
47564
47808
  // src/lsp-auto-install.ts
47565
47809
  import { spawn as spawn2 } from "node:child_process";
47566
- import { createHash as createHash3 } from "node:crypto";
47567
- import { createReadStream, mkdirSync as mkdirSync6, readFileSync as readFileSync5, renameSync as renameSync3, rmSync as rmSync2, statSync as statSync3 } from "node:fs";
47810
+ import { createHash as createHash4 } from "node:crypto";
47811
+ import { createReadStream, mkdirSync as mkdirSync6, readFileSync as readFileSync5, renameSync as renameSync4, rmSync as rmSync3, statSync as statSync4 } from "node:fs";
47568
47812
  import { join as join10 } from "node:path";
47569
47813
 
47570
47814
  // src/lsp-cache.ts
47571
47815
  import {
47572
- closeSync as closeSync2,
47816
+ closeSync as closeSync3,
47573
47817
  mkdirSync as mkdirSync5,
47574
- openSync as openSync2,
47818
+ openSync as openSync3,
47575
47819
  readFileSync as readFileSync4,
47576
- statSync as statSync2,
47820
+ statSync as statSync3,
47577
47821
  unlinkSync as unlinkSync5,
47578
47822
  writeFileSync as writeFileSync4
47579
47823
  } from "node:fs";
@@ -47606,7 +47850,7 @@ function lspBinDir(npmPackage) {
47606
47850
  function isInstalled(npmPackage, binary) {
47607
47851
  for (const candidate of lspBinaryCandidates(binary)) {
47608
47852
  try {
47609
- if (statSync2(join8(lspBinDir(npmPackage), candidate)).isFile())
47853
+ if (statSync3(join8(lspBinDir(npmPackage), candidate)).isFile())
47610
47854
  return true;
47611
47855
  } catch {}
47612
47856
  }
@@ -47634,7 +47878,7 @@ function writeInstalledMetaIn(installDir, version2, sha256) {
47634
47878
  function readInstalledMetaIn(installDir) {
47635
47879
  const path2 = join8(installDir, INSTALLED_META_FILE);
47636
47880
  try {
47637
- if (!statSync2(path2).isFile())
47881
+ if (!statSync3(path2).isFile())
47638
47882
  return null;
47639
47883
  const raw = readFileSync4(path2, "utf8");
47640
47884
  const parsed = JSON.parse(raw);
@@ -47664,13 +47908,13 @@ function acquireInstallLock(lockKey) {
47664
47908
  const lock = lockPath(lockKey);
47665
47909
  const tryClaim = () => {
47666
47910
  try {
47667
- const fd = openSync2(lock, "wx");
47911
+ const fd = openSync3(lock, "wx");
47668
47912
  try {
47669
47913
  writeFileSync4(fd, `${process.pid}
47670
47914
  ${new Date().toISOString()}
47671
47915
  `);
47672
47916
  } finally {
47673
- closeSync2(fd);
47917
+ closeSync3(fd);
47674
47918
  }
47675
47919
  return true;
47676
47920
  } catch (err) {
@@ -47691,7 +47935,7 @@ ${new Date().toISOString()}
47691
47935
  const parsed = Number.parseInt(firstLine, 10);
47692
47936
  if (Number.isFinite(parsed) && parsed > 0)
47693
47937
  owningPid = parsed;
47694
- lockMtimeMs = statSync2(lock).mtimeMs;
47938
+ lockMtimeMs = statSync3(lock).mtimeMs;
47695
47939
  } catch {
47696
47940
  return tryClaim();
47697
47941
  }
@@ -48233,7 +48477,7 @@ function hashInstalledBinary(spec) {
48233
48477
  let pathToHash = null;
48234
48478
  for (const p of candidates) {
48235
48479
  try {
48236
- if (statSync3(p).isFile()) {
48480
+ if (statSync4(p).isFile()) {
48237
48481
  pathToHash = p;
48238
48482
  break;
48239
48483
  }
@@ -48243,7 +48487,7 @@ function hashInstalledBinary(spec) {
48243
48487
  reject(new Error(`installed binary not found at any of: ${candidates.join(", ")}`));
48244
48488
  return;
48245
48489
  }
48246
- const hash2 = createHash3("sha256");
48490
+ const hash2 = createHash4("sha256");
48247
48491
  const stream = createReadStream(pathToHash);
48248
48492
  stream.on("error", reject);
48249
48493
  stream.on("data", (chunk) => hash2.update(chunk));
@@ -48259,14 +48503,14 @@ function installedBinaryPath(spec) {
48259
48503
  ] : [lspBinaryPath(spec.npm, spec.binary)];
48260
48504
  for (const candidate of candidates) {
48261
48505
  try {
48262
- if (statSync3(candidate).isFile())
48506
+ if (statSync4(candidate).isFile())
48263
48507
  return candidate;
48264
48508
  } catch {}
48265
48509
  }
48266
48510
  return null;
48267
48511
  }
48268
48512
  function sha256OfFileSync(path2) {
48269
- return createHash3("sha256").update(readFileSync5(path2)).digest("hex");
48513
+ return createHash4("sha256").update(readFileSync5(path2)).digest("hex");
48270
48514
  }
48271
48515
  function quarantineCachedNpmInstall(spec, reason) {
48272
48516
  const packageDir = lspPackageDir(spec.npm);
@@ -48274,8 +48518,8 @@ function quarantineCachedNpmInstall(spec, reason) {
48274
48518
  warn2(`[lsp] tofu_mismatch ${spec.npm}: ${reason}; quarantining ${packageDir} -> ${dest}`);
48275
48519
  try {
48276
48520
  mkdirSync6(join10(dest, ".."), { recursive: true });
48277
- rmSync2(dest, { recursive: true, force: true });
48278
- renameSync3(packageDir, dest);
48521
+ rmSync3(dest, { recursive: true, force: true });
48522
+ renameSync4(packageDir, dest);
48279
48523
  } catch (err) {
48280
48524
  warn2(`[lsp] tofu_mismatch ${spec.npm}: failed to quarantine cache entry: ${err}`);
48281
48525
  }
@@ -48351,11 +48595,11 @@ function runAutoInstall(projectRoot, config2, fetchImpl2 = fetch) {
48351
48595
 
48352
48596
  // src/lsp-github-install.ts
48353
48597
  import { execFileSync as execFileSync2 } from "node:child_process";
48354
- import { createHash as createHash4, randomBytes } from "node:crypto";
48598
+ import { createHash as createHash5, randomBytes } from "node:crypto";
48355
48599
  import {
48356
48600
  copyFileSync as copyFileSync3,
48357
48601
  createReadStream as createReadStream2,
48358
- createWriteStream as createWriteStream2,
48602
+ createWriteStream as createWriteStream3,
48359
48603
  existsSync as existsSync7,
48360
48604
  lstatSync as lstatSync2,
48361
48605
  mkdirSync as mkdirSync7,
@@ -48363,14 +48607,15 @@ import {
48363
48607
  readFileSync as readFileSync6,
48364
48608
  readlinkSync as readlinkSync2,
48365
48609
  realpathSync as realpathSync3,
48366
- renameSync as renameSync4,
48367
- rmSync as rmSync3,
48368
- statSync as statSync4,
48369
- unlinkSync as unlinkSync6
48610
+ renameSync as renameSync5,
48611
+ rmSync as rmSync4,
48612
+ statSync as statSync5,
48613
+ unlinkSync as unlinkSync6,
48614
+ writeFileSync as writeFileSync5
48370
48615
  } from "node:fs";
48371
48616
  import { dirname as dirname2, join as join11, relative as relative2, resolve as resolve2 } from "node:path";
48372
- import { Readable as Readable2 } from "node:stream";
48373
- import { pipeline as pipeline2 } from "node:stream/promises";
48617
+ import { Readable as Readable3 } from "node:stream";
48618
+ import { pipeline as pipeline3 } from "node:stream/promises";
48374
48619
 
48375
48620
  // src/lsp-github-table.ts
48376
48621
  function exe(platform, name) {
@@ -48469,6 +48714,7 @@ function ghBinDir(spec) {
48469
48714
  function ghExtractDir(spec) {
48470
48715
  return join11(ghPackageDir(spec), "extracted");
48471
48716
  }
48717
+ var INSTALLED_META_FILE2 = ".aft-installed";
48472
48718
  function ghBinaryPath(spec, platform) {
48473
48719
  const ext = platform === "win32" ? ".exe" : "";
48474
48720
  return join11(ghBinDir(spec), `${spec.binary}${ext}`);
@@ -48476,7 +48722,7 @@ function ghBinaryPath(spec, platform) {
48476
48722
  function isGithubInstalled(spec, platform) {
48477
48723
  for (const candidate of ghBinaryCandidates(spec, platform)) {
48478
48724
  try {
48479
- if (statSync4(join11(ghBinDir(spec), candidate)).isFile())
48725
+ if (statSync5(join11(ghBinDir(spec), candidate)).isFile())
48480
48726
  return true;
48481
48727
  } catch {}
48482
48728
  }
@@ -48487,11 +48733,45 @@ function ghBinaryCandidates(spec, platform) {
48487
48733
  return [spec.binary];
48488
48734
  return [spec.binary, `${spec.binary}.cmd`, `${spec.binary}.exe`, `${spec.binary}.bat`];
48489
48735
  }
48490
- var MAX_DOWNLOAD_BYTES2 = 256 * 1024 * 1024;
48736
+ function readGithubInstalledMetaIn(installDir) {
48737
+ try {
48738
+ const path2 = join11(installDir, INSTALLED_META_FILE2);
48739
+ if (!statSync5(path2).isFile())
48740
+ return null;
48741
+ const parsed = JSON.parse(readFileSync6(path2, "utf8"));
48742
+ if (typeof parsed.version !== "string" || parsed.version.length === 0)
48743
+ return null;
48744
+ return {
48745
+ version: parsed.version,
48746
+ installedAt: typeof parsed.installedAt === "string" ? parsed.installedAt : "",
48747
+ ...typeof parsed.sha256 === "string" && parsed.sha256.length > 0 ? { sha256: parsed.sha256 } : {},
48748
+ ...typeof parsed.binarySha256 === "string" && parsed.binarySha256.length > 0 ? { binarySha256: parsed.binarySha256 } : {},
48749
+ ...typeof parsed.archiveSha256 === "string" && parsed.archiveSha256.length > 0 ? { archiveSha256: parsed.archiveSha256 } : {}
48750
+ };
48751
+ } catch {
48752
+ return null;
48753
+ }
48754
+ }
48755
+ function writeGithubInstalledMetaIn(installDir, version2, binarySha256, archiveSha256) {
48756
+ try {
48757
+ mkdirSync7(installDir, { recursive: true });
48758
+ const meta3 = {
48759
+ version: version2,
48760
+ installedAt: new Date().toISOString(),
48761
+ sha256: binarySha256,
48762
+ binarySha256,
48763
+ ...archiveSha256 ? { archiveSha256 } : {}
48764
+ };
48765
+ writeFileSync5(join11(installDir, INSTALLED_META_FILE2), JSON.stringify(meta3), "utf8");
48766
+ } catch (err) {
48767
+ warn2(`[lsp] failed to write github installed metadata in ${installDir}: ${err}`);
48768
+ }
48769
+ }
48770
+ var MAX_DOWNLOAD_BYTES3 = 256 * 1024 * 1024;
48491
48771
  var MAX_EXTRACT_BYTES2 = 1024 * 1024 * 1024;
48492
48772
  function sha256OfFile(path2) {
48493
48773
  return new Promise((resolve3, reject) => {
48494
- const hash2 = createHash4("sha256");
48774
+ const hash2 = createHash5("sha256");
48495
48775
  const stream = createReadStream2(path2);
48496
48776
  stream.on("error", reject);
48497
48777
  stream.on("data", (chunk) => hash2.update(chunk));
@@ -48499,7 +48779,7 @@ function sha256OfFile(path2) {
48499
48779
  });
48500
48780
  }
48501
48781
  function sha256OfFileSync2(path2) {
48502
- return createHash4("sha256").update(readFileSync6(path2)).digest("hex");
48782
+ return createHash5("sha256").update(readFileSync6(path2)).digest("hex");
48503
48783
  }
48504
48784
  async function fetchReleaseByTag(githubRepo, tag, fetchImpl2, signal) {
48505
48785
  const candidates = [];
@@ -48662,8 +48942,8 @@ function assertAllowedDownloadUrl(rawUrl) {
48662
48942
  return parsed;
48663
48943
  }
48664
48944
  async function downloadFile(url2, destPath, fetchImpl2, assetSize, signal) {
48665
- if (assetSize !== undefined && assetSize > MAX_DOWNLOAD_BYTES2) {
48666
- throw new Error(`asset size ${assetSize} exceeds max ${MAX_DOWNLOAD_BYTES2} (set lsp.versions to pin a smaller release if this is wrong)`);
48945
+ if (assetSize !== undefined && assetSize > MAX_DOWNLOAD_BYTES3) {
48946
+ throw new Error(`asset size ${assetSize} exceeds max ${MAX_DOWNLOAD_BYTES3} (set lsp.versions to pin a smaller release if this is wrong)`);
48667
48947
  }
48668
48948
  const timeout = controlledTimeoutSignal(120000, signal);
48669
48949
  try {
@@ -48672,24 +48952,24 @@ async function downloadFile(url2, destPath, fetchImpl2, assetSize, signal) {
48672
48952
  throw new Error(`download failed (${res.status})`);
48673
48953
  }
48674
48954
  const advertised = Number.parseInt(res.headers.get("content-length") ?? "", 10);
48675
- if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES2) {
48676
- throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES2}`);
48955
+ if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES3) {
48956
+ throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES3}`);
48677
48957
  }
48678
48958
  mkdirSync7(dirname2(destPath), { recursive: true });
48679
48959
  let bytesWritten = 0;
48680
48960
  const guard = new TransformStream({
48681
48961
  transform(chunk, controller) {
48682
48962
  bytesWritten += chunk.byteLength;
48683
- if (bytesWritten > MAX_DOWNLOAD_BYTES2) {
48684
- controller.error(new Error(`download exceeded ${MAX_DOWNLOAD_BYTES2} bytes after streaming (server lied about size or sent unbounded body)`));
48963
+ if (bytesWritten > MAX_DOWNLOAD_BYTES3) {
48964
+ controller.error(new Error(`download exceeded ${MAX_DOWNLOAD_BYTES3} bytes after streaming (server lied about size or sent unbounded body)`));
48685
48965
  return;
48686
48966
  }
48687
48967
  controller.enqueue(chunk);
48688
48968
  }
48689
48969
  });
48690
48970
  const guarded = res.body.pipeThrough(guard);
48691
- const nodeStream = Readable2.fromWeb(guarded);
48692
- await pipeline2(nodeStream, createWriteStream2(destPath), { signal: timeout.signal });
48971
+ const nodeStream = Readable3.fromWeb(guarded);
48972
+ await pipeline3(nodeStream, createWriteStream3(destPath), { signal: timeout.signal });
48693
48973
  } catch (err) {
48694
48974
  try {
48695
48975
  unlinkSync6(destPath);
@@ -48769,7 +49049,7 @@ function validateExtraction(stagingRoot) {
48769
49049
  };
48770
49050
  walk(realStagingRoot);
48771
49051
  }
48772
- function precheckArchiveSize(archivePath, archiveType) {
49052
+ function precheckArchiveContents(archivePath, archiveType) {
48773
49053
  let totalBytes = 0;
48774
49054
  if (archiveType === "zip") {
48775
49055
  const out = execFileSync2("unzip", ["-l", archivePath], { encoding: "utf8" });
@@ -48780,6 +49060,9 @@ function precheckArchiveSize(archivePath, archiveType) {
48780
49060
  const out = execFileSync2("tar", ["-tvf", archivePath], { encoding: "utf8" });
48781
49061
  for (const line of out.split(`
48782
49062
  `)) {
49063
+ if (line.startsWith("h")) {
49064
+ throw new Error(`archive contains hardlink entry: ${line.trim()}`);
49065
+ }
48783
49066
  const parts = line.trim().split(/\s+/);
48784
49067
  if (parts.length >= 6) {
48785
49068
  const numeric = parts.map((part) => Number.parseInt(part, 10)).filter((value) => Number.isFinite(value) && value >= 0);
@@ -48796,20 +49079,20 @@ function extractArchiveSafely(archivePath, destDir, archiveType) {
48796
49079
  const suffix = randomBytes(8).toString("hex");
48797
49080
  const stagingDir = `${destDir}.staging-${suffix}`;
48798
49081
  try {
48799
- rmSync3(stagingDir, { recursive: true, force: true });
49082
+ rmSync4(stagingDir, { recursive: true, force: true });
48800
49083
  } catch {}
48801
49084
  mkdirSync7(stagingDir, { recursive: true });
48802
49085
  try {
48803
- precheckArchiveSize(archivePath, archiveType);
49086
+ precheckArchiveContents(archivePath, archiveType);
48804
49087
  runPlatformExtractor(archivePath, stagingDir, archiveType);
48805
49088
  validateExtraction(stagingDir);
48806
49089
  try {
48807
- rmSync3(destDir, { recursive: true, force: true });
49090
+ rmSync4(destDir, { recursive: true, force: true });
48808
49091
  } catch {}
48809
- renameSync4(stagingDir, destDir);
49092
+ renameSync5(stagingDir, destDir);
48810
49093
  } catch (err) {
48811
49094
  try {
48812
- rmSync3(stagingDir, { recursive: true, force: true });
49095
+ rmSync4(stagingDir, { recursive: true, force: true });
48813
49096
  } catch {}
48814
49097
  throw err;
48815
49098
  }
@@ -48820,28 +49103,44 @@ function quarantineCachedGithubInstall(spec, reason) {
48820
49103
  warn2(`[lsp] tofu_mismatch ${spec.id}: ${reason}; quarantining ${packageDir} -> ${dest}`);
48821
49104
  try {
48822
49105
  mkdirSync7(dirname2(dest), { recursive: true });
48823
- rmSync3(dest, { recursive: true, force: true });
48824
- renameSync4(packageDir, dest);
49106
+ rmSync4(dest, { recursive: true, force: true });
49107
+ renameSync5(packageDir, dest);
48825
49108
  } catch (err) {
48826
49109
  warn2(`[lsp] tofu_mismatch ${spec.id}: failed to quarantine cache entry: ${err}`);
48827
49110
  }
48828
49111
  }
48829
49112
  function validateCachedGithubInstall(spec, platform) {
48830
- const meta3 = readInstalledMetaIn(ghPackageDir(spec));
49113
+ const packageDir = ghPackageDir(spec);
49114
+ const meta3 = readGithubInstalledMetaIn(packageDir);
48831
49115
  const binaryPath = ghBinaryCandidates(spec, platform).map((candidate) => join11(ghBinDir(spec), candidate)).find((candidate) => {
48832
49116
  try {
48833
- return statSync4(candidate).isFile();
49117
+ return statSync5(candidate).isFile();
48834
49118
  } catch {
48835
49119
  return false;
48836
49120
  }
48837
49121
  });
48838
- if (!meta3?.sha256 || !meta3.version || !isSafeVersion(meta3.version) || !binaryPath) {
49122
+ if (!meta3?.version || !isSafeVersion(meta3.version) || !binaryPath) {
48839
49123
  quarantineCachedGithubInstall(spec, "missing/unsafe metadata or binary");
48840
49124
  return false;
48841
49125
  }
48842
49126
  const currentHash = sha256OfFileSync2(binaryPath);
48843
- if (currentHash !== meta3.sha256) {
48844
- quarantineCachedGithubInstall(spec, `recorded ${meta3.sha256}, current ${currentHash}`);
49127
+ const recordedBinaryHash = meta3.binarySha256 ?? meta3.sha256;
49128
+ if (recordedBinaryHash && currentHash === recordedBinaryHash) {
49129
+ if (meta3.sha256 !== currentHash || meta3.binarySha256 !== currentHash) {
49130
+ writeGithubInstalledMetaIn(packageDir, meta3.version, currentHash, meta3.archiveSha256);
49131
+ }
49132
+ return true;
49133
+ }
49134
+ if (meta3.sha256 && !meta3.binarySha256 && !meta3.archiveSha256) {
49135
+ writeGithubInstalledMetaIn(packageDir, meta3.version, currentHash, meta3.sha256);
49136
+ return true;
49137
+ }
49138
+ if (!recordedBinaryHash) {
49139
+ quarantineCachedGithubInstall(spec, "missing binary sha256 metadata");
49140
+ return false;
49141
+ }
49142
+ if (currentHash !== recordedBinaryHash) {
49143
+ quarantineCachedGithubInstall(spec, `recorded ${recordedBinaryHash}, current ${currentHash}`);
48845
49144
  return false;
48846
49145
  }
48847
49146
  return true;
@@ -48910,10 +49209,11 @@ async function downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl2,
48910
49209
  return null;
48911
49210
  }
48912
49211
  log2(`[lsp] ${spec.id} ${tag} sha256=${archiveSha256}`);
48913
- const previousMeta = readInstalledMetaIn(ghPackageDir(spec));
48914
- if (previousMeta && previousMeta.version === tag && previousMeta.sha256) {
48915
- if (previousMeta.sha256 !== archiveSha256) {
48916
- error2(`[lsp] ${spec.id} ${tag}: TOFU sha256 mismatch — refusing install. ` + `Previously installed sha256=${previousMeta.sha256}, downloaded sha256=${archiveSha256}. ` + `This means the published release for tag ${tag} changed. Investigate before proceeding.`);
49212
+ const previousMeta = readGithubInstalledMetaIn(ghPackageDir(spec));
49213
+ const previousArchiveSha256 = previousMeta?.archiveSha256 ?? (previousMeta?.binarySha256 ? undefined : previousMeta?.sha256);
49214
+ if (previousMeta && previousMeta.version === tag && previousArchiveSha256) {
49215
+ if (previousArchiveSha256 !== archiveSha256) {
49216
+ error2(`[lsp] ${spec.id} ${tag}: TOFU sha256 mismatch — refusing install. ` + `Previously installed archive sha256=${previousArchiveSha256}, downloaded sha256=${archiveSha256}. ` + `This means the published release for tag ${tag} changed. Investigate before proceeding.`);
48917
49217
  try {
48918
49218
  unlinkSync6(archivePath);
48919
49219
  } catch {}
@@ -48948,7 +49248,9 @@ async function downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl2,
48948
49248
  return null;
48949
49249
  }
48950
49250
  log2(`[lsp] installed ${spec.id} ${tag} at ${targetBinary}`);
48951
- return archiveSha256;
49251
+ const binarySha256 = await sha256OfFile(targetBinary);
49252
+ log2(`[lsp] ${spec.id} ${tag} binary_sha256=${binarySha256}`);
49253
+ return { archiveSha256, binarySha256 };
48952
49254
  }
48953
49255
  async function ensureGithubInstalled(spec, config2, fetchImpl2, platform, arch, signal) {
48954
49256
  const outcome = await withInstallLock(spec.githubRepo, async () => {
@@ -48974,14 +49276,14 @@ async function ensureGithubInstalled(spec, config2, fetchImpl2, platform, arch,
48974
49276
  log2(`[lsp] reinstalling ${spec.id}@${tag}: no installed-version metadata recorded`);
48975
49277
  }
48976
49278
  }
48977
- const archiveSha256 = await downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl2, signal).catch((err) => {
49279
+ const hashes = await downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl2, signal).catch((err) => {
48978
49280
  error2(`[lsp] github install ${spec.id} crashed: ${err}`);
48979
49281
  return null;
48980
49282
  });
48981
- if (!archiveSha256) {
49283
+ if (!hashes) {
48982
49284
  return { started: true, reason: "install failed (see plugin log)" };
48983
49285
  }
48984
- writeInstalledMetaIn(ghPackageDir(spec), tag, archiveSha256);
49286
+ writeGithubInstalledMetaIn(ghPackageDir(spec), tag, hashes.binarySha256, hashes.archiveSha256);
48985
49287
  return { started: true };
48986
49288
  });
48987
49289
  if (outcome === null) {
@@ -49111,7 +49413,7 @@ function discoverRelevantGithubServers(projectRoot) {
49111
49413
  }
49112
49414
 
49113
49415
  // src/notifications.ts
49114
- import { existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync7, renameSync as renameSync5, rmSync as rmSync4, writeFileSync as writeFileSync5 } from "node:fs";
49416
+ import { existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync7, renameSync as renameSync6, rmSync as rmSync5, writeFileSync as writeFileSync6 } from "node:fs";
49115
49417
  import { join as join12 } from "node:path";
49116
49418
  var WARNING_MARKER = "\uD83D\uDD27 AFT: ⚠️";
49117
49419
  var FEATURE_MARKER = "\uD83D\uDD27 AFT: ✨";
@@ -49124,7 +49426,7 @@ function sendIgnoredMessage(client, sessionId, text) {
49124
49426
  typedClient.ui.notify(text, "warning");
49125
49427
  return true;
49126
49428
  } catch (err) {
49127
- sessionLog2(sessionId, `[aft-pi] notification send failed: ${err instanceof Error ? err.message : String(err)}`);
49429
+ sessionLog(sessionId, `[aft-pi] notification send failed: ${err instanceof Error ? err.message : String(err)}`);
49128
49430
  return false;
49129
49431
  }
49130
49432
  }
@@ -49152,9 +49454,9 @@ function writeWarnedTools(storageDir, warned) {
49152
49454
  mkdirSync8(storageDir, { recursive: true });
49153
49455
  const warnedToolsPath = join12(storageDir, WARNED_TOOLS_FILE);
49154
49456
  const tmpPath = join12(storageDir, `${WARNED_TOOLS_FILE}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
49155
- writeFileSync5(tmpPath, `${JSON.stringify(warned, null, 2)}
49457
+ writeFileSync6(tmpPath, `${JSON.stringify(warned, null, 2)}
49156
49458
  `);
49157
- renameSync5(tmpPath, warnedToolsPath);
49459
+ renameSync6(tmpPath, warnedToolsPath);
49158
49460
  } catch {}
49159
49461
  }
49160
49462
  async function withWarnedToolsLock(storageDir, fn) {
@@ -49166,7 +49468,7 @@ async function withWarnedToolsLock(storageDir, fn) {
49166
49468
  try {
49167
49469
  return await fn();
49168
49470
  } finally {
49169
- rmSync4(lockDir, { recursive: true, force: true });
49471
+ rmSync5(lockDir, { recursive: true, force: true });
49170
49472
  }
49171
49473
  } catch (err) {
49172
49474
  const code = err.code;
@@ -49253,7 +49555,7 @@ function sendFeatureAnnouncement(version2, features, storageDir) {
49253
49555
  `));
49254
49556
  try {
49255
49557
  mkdirSync8(storageDir, { recursive: true });
49256
- writeFileSync5(versionFile, version2);
49558
+ writeFileSync6(versionFile, version2);
49257
49559
  } catch {}
49258
49560
  }
49259
49561
 
@@ -51856,33 +52158,39 @@ ${hintsBlock}` };
51856
52158
  // src/index.ts
51857
52159
  setActiveLogger(bridgeLogger);
51858
52160
  function createVersionMismatchHandler(getPool, ensureCompatibleBinary = ensureBinary) {
51859
- let versionUpgradeAttempted = null;
52161
+ const versionUpgradePromises = new Map;
51860
52162
  return async (binaryVersion, minVersion) => {
51861
- if (versionUpgradeAttempted === binaryVersion) {
51862
- log2(`Version ${binaryVersion} < ${minVersion} but upgrade already attempted — continuing`);
51863
- return null;
52163
+ const existing = versionUpgradePromises.get(minVersion);
52164
+ if (existing) {
52165
+ log2(`Version ${binaryVersion} < ${minVersion}; awaiting in-flight compatible binary upgrade`);
52166
+ return existing;
51864
52167
  }
51865
- versionUpgradeAttempted = binaryVersion;
51866
- warn2(`WARNING: aft binary v${binaryVersion} is older than plugin v${minVersion}. ` + "Some features may not work. Attempting to download a compatible binary...");
51867
- try {
51868
- const path2 = await ensureCompatibleBinary(`v${minVersion}`);
51869
- if (!path2) {
51870
- warn2(`Could not find or download v${minVersion}. Continuing with v${binaryVersion}.`);
51871
- return null;
51872
- }
51873
- const pool = getPool();
51874
- if (!pool) {
51875
- warn2(`Found/downloaded compatible binary at ${path2}, but bridge pool is not ready.`);
52168
+ const upgradePromise = (async () => {
52169
+ warn2(`WARNING: aft binary v${binaryVersion} is older than plugin v${minVersion}. ` + "Some features may not work. Attempting to download a compatible binary...");
52170
+ try {
52171
+ const path2 = await ensureCompatibleBinary(`v${minVersion}`);
52172
+ if (!path2) {
52173
+ warn2(`Could not find or download v${minVersion}. Continuing with v${binaryVersion}.`);
52174
+ return null;
52175
+ }
52176
+ const pool = getPool();
52177
+ if (!pool) {
52178
+ warn2(`Found/downloaded compatible binary at ${path2}, but bridge pool is not ready.`);
52179
+ return null;
52180
+ }
52181
+ log2(`Found/downloaded compatible binary at ${path2}. Replacing running bridges...`);
52182
+ const replaced = await pool.replaceBinary(path2);
52183
+ log2("Binary replaced successfully. New bridges will use the updated binary.");
52184
+ return replaced;
52185
+ } catch (err) {
52186
+ error2(`Auto-download failed: ${err.message}. Install manually: cargo install agent-file-tools@${minVersion}`);
51876
52187
  return null;
52188
+ } finally {
52189
+ versionUpgradePromises.delete(minVersion);
51877
52190
  }
51878
- log2(`Found/downloaded compatible binary at ${path2}. Replacing running bridges...`);
51879
- const replaced = await pool.replaceBinary(path2);
51880
- log2("Binary replaced successfully. New bridges will use the updated binary.");
51881
- return replaced;
51882
- } catch (err) {
51883
- error2(`Auto-download failed: ${err.message}. Install manually: cargo install agent-file-tools@${minVersion}`);
51884
- return null;
51885
- }
52191
+ })();
52192
+ versionUpgradePromises.set(minVersion, upgradePromise);
52193
+ return upgradePromise;
51886
52194
  };
51887
52195
  }
51888
52196
  var PLUGIN_VERSION = (() => {