@cortexkit/aft-pi 0.25.2 → 0.26.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -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";
@@ -30601,6 +30612,15 @@ function clampSemanticTimeout(configOverrides, bridgeTimeoutMs) {
30601
30612
  };
30602
30613
  }
30603
30614
 
30615
+ class BridgeReplacedDuringVersionCheck extends Error {
30616
+ newBinaryPath;
30617
+ constructor(newBinaryPath) {
30618
+ super(`Bridge binary replaced during version check: ${newBinaryPath}`);
30619
+ this.newBinaryPath = newBinaryPath;
30620
+ this.name = "BridgeReplacedDuringVersionCheck";
30621
+ }
30622
+ }
30623
+
30604
30624
  class BinaryBridge {
30605
30625
  static RESTART_RESET_MS = 5 * 60 * 1000;
30606
30626
  static STDERR_TAIL_MAX = 20;
@@ -30610,6 +30630,7 @@ class BinaryBridge {
30610
30630
  pending = new Map;
30611
30631
  nextId = 1;
30612
30632
  stdoutBuffer = "";
30633
+ stderrBuffer = "";
30613
30634
  stderrTail = [];
30614
30635
  _restartCount = 0;
30615
30636
  _shuttingDown = false;
@@ -30645,24 +30666,62 @@ class BinaryBridge {
30645
30666
  }
30646
30667
  logVia(message, meta) {
30647
30668
  const logger = this.logger ?? getActiveLogger();
30648
- if (logger)
30649
- logger.log(message, meta);
30650
- 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 {
30651
30677
  log(message, meta);
30678
+ }
30652
30679
  }
30653
30680
  warnVia(message, meta) {
30654
30681
  const logger = this.logger ?? getActiveLogger();
30655
- if (logger)
30656
- logger.warn(message, meta);
30657
- 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 {
30658
30690
  warn(message, meta);
30691
+ }
30659
30692
  }
30660
30693
  errorVia(message, meta) {
30661
30694
  const logger = this.logger ?? getActiveLogger();
30662
- if (logger)
30663
- logger.error(message, meta);
30664
- 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 {
30665
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);
30666
30725
  }
30667
30726
  get restartCount() {
30668
30727
  return this._restartCount;
@@ -30689,97 +30748,108 @@ class BinaryBridge {
30689
30748
  this.cachedStatus = snapshot;
30690
30749
  }
30691
30750
  async send(command, params = {}, options) {
30692
- if (this._shuttingDown) {
30693
- throw new Error(`${this.errorPrefix} Bridge is shutting down, cannot send "${command}"`);
30694
- }
30695
- if (Object.hasOwn(params, "id")) {
30696
- throw new Error("params cannot contain reserved key 'id'");
30697
- }
30698
- const requestSessionId = typeof params.session_id === "string" && params.session_id.length > 0 ? params.session_id : undefined;
30699
- this.ensureSpawned(requestSessionId);
30700
- if (requestSessionId && options?.configureWarningClient !== undefined) {
30701
- this.configureWarningClients.set(requestSessionId, options.configureWarningClient);
30702
- }
30703
- if (!this.configured) {
30704
- if (command !== "configure" && command !== "version") {
30705
- if (!this._configurePromise) {
30706
- const sessionIdForConfigure = typeof params.session_id === "string" ? params.session_id : undefined;
30707
- this._configurePromise = (async () => {
30708
- try {
30709
- const configResult = await this.send("configure", {
30710
- project_root: this.cwd,
30711
- ...this.configOverrides,
30712
- ...sessionIdForConfigure ? { session_id: sessionIdForConfigure } : {}
30713
- });
30714
- if (configResult.success === false) {
30715
- throw new Error(`${this.errorPrefix} Configure failed: ${configResult.message ?? "unknown error"}`);
30716
- }
30717
- await this.deliverConfigureWarnings(configResult, params, options);
30718
- await this.checkVersion();
30719
- if (!this.isAlive()) {
30720
- throw new Error(`${this.errorPrefix} Bridge died during version check. Check logs: ${getLogFilePath()}`);
30751
+ return this.sendWithVersionMismatchRetry(command, params, options, true);
30752
+ }
30753
+ async sendWithVersionMismatchRetry(command, params, options, canRetryAfterVersionSwap) {
30754
+ try {
30755
+ if (this._shuttingDown) {
30756
+ throw new Error(`${this.errorPrefix} Bridge is shutting down, cannot send "${command}"`);
30757
+ }
30758
+ if (Object.hasOwn(params, "id")) {
30759
+ throw new Error("params cannot contain reserved key 'id'");
30760
+ }
30761
+ const requestSessionId = typeof params.session_id === "string" && params.session_id.length > 0 ? params.session_id : undefined;
30762
+ this.ensureSpawned(requestSessionId);
30763
+ if (requestSessionId && options?.configureWarningClient !== undefined) {
30764
+ this.configureWarningClients.set(requestSessionId, options.configureWarningClient);
30765
+ }
30766
+ if (!this.configured) {
30767
+ if (command !== "configure" && command !== "version") {
30768
+ if (!this._configurePromise) {
30769
+ const sessionIdForConfigure = typeof params.session_id === "string" ? params.session_id : undefined;
30770
+ this._configurePromise = (async () => {
30771
+ try {
30772
+ const configResult = await this.send("configure", {
30773
+ project_root: this.cwd,
30774
+ ...this.configOverrides,
30775
+ ...sessionIdForConfigure ? { session_id: sessionIdForConfigure } : {}
30776
+ });
30777
+ if (configResult.success === false) {
30778
+ throw new Error(`${this.errorPrefix} Configure failed: ${configResult.message ?? "unknown error"}`);
30779
+ }
30780
+ await this.deliverConfigureWarnings(configResult, params, options);
30781
+ await this.checkVersion();
30782
+ if (!this.isAlive()) {
30783
+ throw new Error(`${this.errorPrefix} Bridge died during version check. Check logs: ${this.getLogFilePathVia()}`);
30784
+ }
30785
+ this.configured = true;
30786
+ } finally {
30787
+ this._configurePromise = null;
30721
30788
  }
30722
- this.configured = true;
30723
- } finally {
30724
- this._configurePromise = null;
30725
- }
30726
- })();
30789
+ })();
30790
+ }
30791
+ await this._configurePromise;
30727
30792
  }
30728
- await this._configurePromise;
30729
30793
  }
30730
- }
30731
- const id = String(this.nextId++);
30732
- let request;
30733
- if (Object.hasOwn(params, "command") || Object.hasOwn(params, "method")) {
30734
- const nested = { ...params };
30735
- const reserved = {};
30736
- for (const key of ["session_id", "lsp_hints"]) {
30737
- if (Object.hasOwn(nested, key)) {
30738
- reserved[key] = nested[key];
30739
- delete nested[key];
30794
+ const id = String(this.nextId++);
30795
+ let request;
30796
+ if (Object.hasOwn(params, "command") || Object.hasOwn(params, "method")) {
30797
+ const nested = { ...params };
30798
+ const reserved = {};
30799
+ for (const key of ["session_id", "lsp_hints"]) {
30800
+ if (Object.hasOwn(nested, key)) {
30801
+ reserved[key] = nested[key];
30802
+ delete nested[key];
30803
+ }
30740
30804
  }
30805
+ request = { id, command, ...reserved, params: nested };
30806
+ } else {
30807
+ request = { id, command, ...params };
30741
30808
  }
30742
- request = { id, command, ...reserved, params: nested };
30743
- } else {
30744
- request = { id, command, ...params };
30745
- }
30746
- const line = `${JSON.stringify(request)}
30809
+ const line = `${JSON.stringify(request)}
30747
30810
  `;
30748
- const effectiveTimeoutMs = options?.transportTimeoutMs ?? options?.timeoutMs ?? this.timeoutMs;
30749
- const keepBridgeOnTimeout = options?.keepBridgeOnTimeout === true;
30750
- return new Promise((resolve, reject) => {
30751
- const timer = setTimeout(() => {
30752
- this.pending.delete(id);
30753
- const restartSuffix = keepBridgeOnTimeout ? "" : " — restarting bridge";
30754
- const timeoutMsg = `Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms${restartSuffix}`;
30755
- if (requestSessionId) {
30756
- sessionWarn(requestSessionId, timeoutMsg);
30757
- } else {
30758
- warn(timeoutMsg);
30759
- }
30760
- reject(new Error(`${this.errorPrefix} Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
30761
- if (!keepBridgeOnTimeout) {
30762
- this.handleTimeout(requestSessionId);
30763
- }
30764
- }, effectiveTimeoutMs);
30765
- this.pending.set(id, { resolve, reject, timer, onProgress: options?.onProgress });
30766
- if (!this.process?.stdin?.writable) {
30767
- this.pending.delete(id);
30768
- clearTimeout(timer);
30769
- reject(new Error(`${this.errorPrefix} stdin not writable for command "${command}"`));
30770
- return;
30771
- }
30772
- this.process.stdin.write(line, (err) => {
30773
- if (err) {
30774
- const entry = this.pending.get(id);
30775
- if (entry) {
30776
- this.pending.delete(id);
30777
- clearTimeout(entry.timer);
30778
- entry.reject(new Error(`${this.errorPrefix} Failed to write to stdin: ${err.message}`));
30811
+ const effectiveTimeoutMs = options?.transportTimeoutMs ?? options?.timeoutMs ?? this.timeoutMs;
30812
+ const keepBridgeOnTimeout = options?.keepBridgeOnTimeout === true;
30813
+ return new Promise((resolve, reject) => {
30814
+ const timer = setTimeout(() => {
30815
+ this.pending.delete(id);
30816
+ const restartSuffix = keepBridgeOnTimeout ? "" : " — restarting bridge";
30817
+ const timeoutMsg = `Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms${restartSuffix}`;
30818
+ if (requestSessionId) {
30819
+ this.sessionWarnVia(requestSessionId, timeoutMsg);
30820
+ } else {
30821
+ this.warnVia(timeoutMsg);
30822
+ }
30823
+ reject(new Error(`${this.errorPrefix} Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
30824
+ if (!keepBridgeOnTimeout) {
30825
+ this.handleTimeout(requestSessionId);
30779
30826
  }
30827
+ }, effectiveTimeoutMs);
30828
+ this.pending.set(id, { resolve, reject, timer, onProgress: options?.onProgress });
30829
+ if (!this.process?.stdin?.writable) {
30830
+ this.pending.delete(id);
30831
+ clearTimeout(timer);
30832
+ reject(new Error(`${this.errorPrefix} stdin not writable for command "${command}"`));
30833
+ return;
30780
30834
  }
30835
+ this.process.stdin.write(line, (err) => {
30836
+ if (err) {
30837
+ const entry = this.pending.get(id);
30838
+ if (entry) {
30839
+ this.pending.delete(id);
30840
+ clearTimeout(entry.timer);
30841
+ entry.reject(new Error(`${this.errorPrefix} Failed to write to stdin: ${err.message}`));
30842
+ }
30843
+ }
30844
+ });
30781
30845
  });
30782
- });
30846
+ } catch (err) {
30847
+ if (err instanceof BridgeReplacedDuringVersionCheck && canRetryAfterVersionSwap && command !== "configure" && command !== "version") {
30848
+ this.logVia(`Retrying request "${command}" once after coordinated binary replacement: ${err.newBinaryPath}`);
30849
+ return this.sendWithVersionMismatchRetry(command, params, options, false);
30850
+ }
30851
+ throw err;
30852
+ }
30783
30853
  }
30784
30854
  async deliverConfigureWarnings(configResult, params, options) {
30785
30855
  if (!this.onConfigureWarnings || !Array.isArray(configResult.warnings))
@@ -30795,7 +30865,7 @@ class BinaryBridge {
30795
30865
  warnings: configResult.warnings
30796
30866
  });
30797
30867
  } catch (err) {
30798
- 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)}`);
30799
30869
  } finally {
30800
30870
  if (sessionId) {
30801
30871
  this.configureWarningClients.delete(sessionId);
@@ -30829,7 +30899,7 @@ class BinaryBridge {
30829
30899
  if (!snapshot || typeof snapshot !== "object" || Array.isArray(snapshot))
30830
30900
  return;
30831
30901
  this.cachedStatus = snapshot;
30832
- log("Received status_changed push frame; cached AFT status snapshot");
30902
+ this.logVia("Received status_changed push frame; cached AFT status snapshot");
30833
30903
  for (const listener of this.statusListeners) {
30834
30904
  this.deliverStatusSnapshot(listener, this.cachedStatus);
30835
30905
  }
@@ -30838,7 +30908,7 @@ class BinaryBridge {
30838
30908
  try {
30839
30909
  listener(snapshot);
30840
30910
  } catch (err) {
30841
- warn(`status listener threw: ${err instanceof Error ? err.message : String(err)}`);
30911
+ this.warnVia(`status listener threw: ${err instanceof Error ? err.message : String(err)}`);
30842
30912
  }
30843
30913
  }
30844
30914
  async shutdown() {
@@ -30856,7 +30926,7 @@ class BinaryBridge {
30856
30926
  }, 5000);
30857
30927
  proc.once("exit", () => {
30858
30928
  clearTimeout(forceKillTimer);
30859
- log("Process exited during shutdown");
30929
+ this.logVia("Process exited during shutdown");
30860
30930
  resolve();
30861
30931
  });
30862
30932
  proc.kill("SIGTERM");
@@ -30875,16 +30945,46 @@ class BinaryBridge {
30875
30945
  if (typeof binaryVersion !== "string") {
30876
30946
  throw new Error(`Binary did not report a version — likely too old (minVersion: ${this.minVersion})`);
30877
30947
  }
30878
- log(`Binary version: ${binaryVersion}`);
30948
+ this.logVia(`Binary version: ${binaryVersion}`);
30879
30949
  if (compareSemver(binaryVersion, this.minVersion) < 0) {
30880
- warn(`Binary version ${binaryVersion} is older than required ${this.minVersion}`);
30881
- this.onVersionMismatch?.(binaryVersion, this.minVersion);
30950
+ this.warnVia(`Binary version ${binaryVersion} is older than required ${this.minVersion}`);
30951
+ const replacementPath = await this.onVersionMismatch?.(binaryVersion, this.minVersion);
30952
+ if (replacementPath === undefined) {
30953
+ return;
30954
+ }
30955
+ if (replacementPath === null || replacementPath.length === 0) {
30956
+ throw new Error(`Binary version ${binaryVersion} is older than required ${this.minVersion}; no compatible replacement binary was provided`);
30957
+ }
30958
+ await this.replaceCurrentBinary(replacementPath);
30959
+ throw new BridgeReplacedDuringVersionCheck(replacementPath);
30882
30960
  }
30883
30961
  } catch (err) {
30884
- warn(`Version check failed: ${err.message}`);
30962
+ this.warnVia(`Version check failed: ${err.message}`);
30885
30963
  throw err;
30886
30964
  }
30887
30965
  }
30966
+ async replaceCurrentBinary(newBinaryPath) {
30967
+ this.binaryPath = newBinaryPath;
30968
+ this.configured = false;
30969
+ this.clearRestartResetTimer();
30970
+ this.rejectAllPending(new Error(`${this.errorPrefix} Bridge restarting with updated binary: ${newBinaryPath}`));
30971
+ if (!this.process)
30972
+ return;
30973
+ const proc = this.process;
30974
+ this.process = null;
30975
+ await new Promise((resolve) => {
30976
+ const forceKillTimer = setTimeout(() => {
30977
+ proc.kill("SIGKILL");
30978
+ resolve();
30979
+ }, 5000);
30980
+ proc.once("exit", () => {
30981
+ clearTimeout(forceKillTimer);
30982
+ this.logVia("Process exited during coordinated binary replacement");
30983
+ resolve();
30984
+ });
30985
+ proc.kill("SIGTERM");
30986
+ });
30987
+ }
30888
30988
  ensureSpawned(triggeringSessionId) {
30889
30989
  if (this.isAlive())
30890
30990
  return;
@@ -30892,9 +30992,9 @@ class BinaryBridge {
30892
30992
  }
30893
30993
  spawnProcess(triggeringSessionId) {
30894
30994
  if (triggeringSessionId) {
30895
- sessionLog(triggeringSessionId, `Spawning binary: ${this.binaryPath} (cwd: ${this.cwd})`);
30995
+ this.sessionLogVia(triggeringSessionId, `Spawning binary: ${this.binaryPath} (cwd: ${this.cwd})`);
30896
30996
  } else {
30897
- log(`Spawning binary: ${this.binaryPath} (cwd: ${this.cwd})`);
30997
+ this.logVia(`Spawning binary: ${this.binaryPath} (cwd: ${this.cwd})`);
30898
30998
  }
30899
30999
  const semantic = this.configOverrides.semantic;
30900
31000
  const semanticBackend = (() => {
@@ -30941,11 +31041,12 @@ class BinaryBridge {
30941
31041
  const remaining = stderrDecoder.end();
30942
31042
  if (remaining)
30943
31043
  this.onStderrData(remaining);
31044
+ this.flushStderrBuffer();
30944
31045
  });
30945
31046
  child.on("error", (err) => {
30946
31047
  if (this.process !== currentChild)
30947
31048
  return;
30948
- error(`Process error: ${err.message}${this.formatStderrTail()}`);
31049
+ this.errorVia(`Process error: ${err.message}${this.formatStderrTail()}`);
30949
31050
  this.handleCrash();
30950
31051
  });
30951
31052
  child.on("exit", (code, signal) => {
@@ -30953,7 +31054,7 @@ class BinaryBridge {
30953
31054
  return;
30954
31055
  if (this._shuttingDown)
30955
31056
  return;
30956
- log(`Process exited: code=${code}, signal=${signal}`);
31057
+ this.logVia(`Process exited: code=${code}, signal=${signal}`);
30957
31058
  if (signal === "SIGTERM" || signal === "SIGKILL" || signal === "SIGHUP" || signal === "SIGINT") {
30958
31059
  this.process = null;
30959
31060
  this.configured = false;
@@ -30965,6 +31066,7 @@ class BinaryBridge {
30965
31066
  });
30966
31067
  this.process = child;
30967
31068
  this.stdoutBuffer = "";
31069
+ this.stderrBuffer = "";
30968
31070
  this.stderrTail = [];
30969
31071
  }
30970
31072
  pushStderrLine(line) {
@@ -30974,16 +31076,28 @@ class BinaryBridge {
30974
31076
  }
30975
31077
  }
30976
31078
  onStderrData(data) {
30977
- const lines = data.trimEnd().split(`
30978
- `);
30979
- 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);
30980
31085
  if (!line)
30981
31086
  continue;
30982
31087
  const tagged = tagStderrLine(line);
30983
- log(tagged);
31088
+ this.logVia(tagged);
30984
31089
  this.pushStderrLine(tagged);
30985
31090
  }
30986
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
+ }
30987
31101
  formatStderrTail() {
30988
31102
  if (this.stderrTail.length === 0)
30989
31103
  return "";
@@ -31041,7 +31155,7 @@ class BinaryBridge {
31041
31155
  }
31042
31156
  if (response.type === "configure_warnings") {
31043
31157
  this.handleConfigureWarningsFrame(response).catch((err) => {
31044
- 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)}`);
31045
31159
  });
31046
31160
  continue;
31047
31161
  }
@@ -31059,10 +31173,10 @@ class BinaryBridge {
31059
31173
  this.scheduleRestartCountReset();
31060
31174
  entry.resolve(response);
31061
31175
  } else if (typeof response.type === "string") {
31062
- log(`Ignoring unknown stdout push frame type: ${response.type}`);
31176
+ this.logVia(`Ignoring unknown stdout push frame type: ${response.type}`);
31063
31177
  }
31064
31178
  } catch (_err) {
31065
- warn(`Failed to parse stdout line: ${line}`);
31179
+ this.warnVia(`Failed to parse stdout line: ${line}`);
31066
31180
  }
31067
31181
  }
31068
31182
  }
@@ -31076,17 +31190,17 @@ class BinaryBridge {
31076
31190
  this.configured = false;
31077
31191
  const tail = this.formatStderrTail();
31078
31192
  this.stderrTail = [];
31079
- 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()})`;
31080
31194
  if (tail) {
31081
31195
  if (triggeringSessionId) {
31082
- sessionError(triggeringSessionId, killedMsg);
31196
+ this.sessionErrorVia(triggeringSessionId, killedMsg);
31083
31197
  } else {
31084
- error(killedMsg);
31198
+ this.errorVia(killedMsg);
31085
31199
  }
31086
31200
  } else if (triggeringSessionId) {
31087
- sessionWarn(triggeringSessionId, killedMsg);
31201
+ this.sessionWarnVia(triggeringSessionId, killedMsg);
31088
31202
  } else {
31089
- warn(killedMsg);
31203
+ this.warnVia(killedMsg);
31090
31204
  }
31091
31205
  }
31092
31206
  handleCrash(cause) {
@@ -31099,25 +31213,25 @@ class BinaryBridge {
31099
31213
  this.configured = false;
31100
31214
  const tail = this.formatStderrTail();
31101
31215
  if (tail) {
31102
- error(`Binary crashed (restarts: ${this._restartCount})${cause ? `: ${cause.message}` : ""}.${tail}`);
31216
+ this.errorVia(`Binary crashed (restarts: ${this._restartCount})${cause ? `: ${cause.message}` : ""}.${tail}`);
31103
31217
  }
31104
- 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()})`));
31105
31219
  if (this._restartCount < this.maxRestarts) {
31106
31220
  const delay = 100 * 2 ** this._restartCount;
31107
31221
  this._restartCount++;
31108
- log(`Auto-restart #${this._restartCount} in ${delay}ms`);
31222
+ this.logVia(`Auto-restart #${this._restartCount} in ${delay}ms`);
31109
31223
  setTimeout(() => {
31110
31224
  if (!this._shuttingDown && !this.isAlive()) {
31111
31225
  try {
31112
31226
  this.spawnProcess();
31113
31227
  } catch (err) {
31114
- error(`Failed to restart: ${err.message}`);
31228
+ this.errorVia(`Failed to restart: ${err.message}`);
31115
31229
  }
31116
31230
  }
31117
31231
  }, delay);
31118
31232
  this.scheduleRestartCountReset();
31119
31233
  } else {
31120
- 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}`);
31121
31235
  this.scheduleRestartCountReset();
31122
31236
  }
31123
31237
  }
@@ -31143,9 +31257,12 @@ class BinaryBridge {
31143
31257
  }
31144
31258
  }
31145
31259
  // ../aft-bridge/dist/downloader.js
31146
- 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";
31147
31262
  import { homedir as homedir2 } from "node:os";
31148
31263
  import { join as join2 } from "node:path";
31264
+ import { Readable } from "node:stream";
31265
+ import { pipeline } from "node:stream/promises";
31149
31266
 
31150
31267
  // ../aft-bridge/dist/platform.js
31151
31268
  var PLATFORM_ARCH_MAP = {
@@ -31163,6 +31280,11 @@ var PLATFORM_ASSET_MAP = {
31163
31280
 
31164
31281
  // ../aft-bridge/dist/downloader.js
31165
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;
31166
31288
  function getCacheDir() {
31167
31289
  if (process.platform === "win32") {
31168
31290
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
@@ -31204,54 +31326,101 @@ async function downloadBinary(version) {
31204
31326
  const downloadUrl = `https://github.com/${REPO}/releases/download/${tag}/${assetName}`;
31205
31327
  const checksumUrl = `https://github.com/${REPO}/releases/download/${tag}/checksums.sha256`;
31206
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`;
31207
31336
  try {
31208
31337
  if (!existsSync(versionedCacheDir)) {
31209
31338
  mkdirSync(versionedCacheDir, { recursive: true });
31210
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);
31211
31350
  const [binaryResponse, checksumResponse] = await Promise.all([
31212
- fetch(downloadUrl, { redirect: "follow" }),
31213
- fetch(checksumUrl, { redirect: "follow" })
31351
+ fetch(downloadUrl, { redirect: "follow", signal: activeBinaryController.signal }),
31352
+ fetch(checksumUrl, { redirect: "follow", signal: activeChecksumController.signal })
31214
31353
  ]);
31215
31354
  if (!binaryResponse.ok) {
31216
31355
  throw new Error(`HTTP ${binaryResponse.status}: ${binaryResponse.statusText} (${downloadUrl})`);
31217
31356
  }
31218
- 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
+ }
31219
31364
  if (!checksumResponse.ok) {
31220
31365
  warn(`Checksum verification failed: no checksums.sha256 found for ${tag}. ` + "Binary download aborted for security reasons.");
31221
31366
  return null;
31222
31367
  }
31223
31368
  const checksumText = await checksumResponse.text();
31369
+ clearTimeout(checksumTimeout);
31370
+ checksumTimeout = null;
31224
31371
  const expectedHash = parseChecksumForAsset(checksumText, assetName);
31225
31372
  if (!expectedHash) {
31226
31373
  warn(`Checksum verification failed: checksums.sha256 found but no entry for ${assetName}. ` + "Binary download aborted for security reasons.");
31227
31374
  return null;
31228
31375
  }
31229
- const { createHash } = await import("node:crypto");
31230
- 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");
31231
31395
  if (actualHash !== expectedHash) {
31232
- 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.");
31233
31397
  }
31234
31398
  log(`Checksum verified (SHA-256: ${actualHash.slice(0, 16)}...)`);
31235
- const tmpPath = `${binaryPath}.tmp`;
31236
- const { writeFileSync } = await import("node:fs");
31237
- writeFileSync(tmpPath, Buffer.from(arrayBuffer));
31238
31399
  if (process.platform !== "win32") {
31239
31400
  chmodSync(tmpPath, 493);
31240
31401
  }
31241
- const { renameSync } = await import("node:fs");
31242
31402
  renameSync(tmpPath, binaryPath);
31243
31403
  log(`AFT binary ready at ${binaryPath}`);
31244
31404
  return binaryPath;
31245
31405
  } catch (err) {
31246
31406
  const msg = err instanceof Error ? err.message : String(err);
31247
31407
  error(`Failed to download AFT binary: ${msg}`);
31248
- const tmpPath = `${binaryPath}.tmp`;
31249
31408
  if (existsSync(tmpPath)) {
31250
31409
  try {
31251
31410
  unlinkSync(tmpPath);
31252
31411
  } catch {}
31253
31412
  }
31254
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?.();
31255
31424
  }
31256
31425
  }
31257
31426
  async function ensureBinary(version) {
@@ -31268,6 +31437,39 @@ async function ensureBinary(version) {
31268
31437
  log("No cached binary found, downloading latest...");
31269
31438
  return downloadBinary();
31270
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
+ }
31271
31473
  function parseChecksumForAsset(checksumText, assetName) {
31272
31474
  for (const line of checksumText.split(`
31273
31475
  `)) {
@@ -31282,9 +31484,12 @@ function parseChecksumForAsset(checksumText, assetName) {
31282
31484
  return null;
31283
31485
  }
31284
31486
  async function fetchLatestTag() {
31487
+ const controller = new AbortController;
31488
+ const timeout = setTimeout(() => controller.abort(), LATEST_TAG_TIMEOUT_MS);
31285
31489
  try {
31286
31490
  const response = await fetch(`https://api.github.com/repos/${REPO}/releases/latest`, {
31287
- headers: { Accept: "application/vnd.github.v3+json" }
31491
+ headers: { Accept: "application/vnd.github.v3+json" },
31492
+ signal: controller.signal
31288
31493
  });
31289
31494
  if (!response.ok)
31290
31495
  return null;
@@ -31292,18 +31497,20 @@ async function fetchLatestTag() {
31292
31497
  return data.tag_name ?? null;
31293
31498
  } catch {
31294
31499
  return null;
31500
+ } finally {
31501
+ clearTimeout(timeout);
31295
31502
  }
31296
31503
  }
31297
31504
  // ../aft-bridge/dist/onnx-runtime.js
31298
31505
  import { execFileSync } from "node:child_process";
31299
- import { createHash } from "node:crypto";
31300
- 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";
31301
31508
  import { dirname, join as join3, relative, resolve } from "node:path";
31302
- import { Readable } from "node:stream";
31303
- import { pipeline } from "node:stream/promises";
31509
+ import { Readable as Readable2 } from "node:stream";
31510
+ import { pipeline as pipeline2 } from "node:stream/promises";
31304
31511
  var ORT_VERSION = "1.24.4";
31305
31512
  var ORT_REPO = "microsoft/onnxruntime";
31306
- var MAX_DOWNLOAD_BYTES = 256 * 1024 * 1024;
31513
+ var MAX_DOWNLOAD_BYTES2 = 256 * 1024 * 1024;
31307
31514
  var MAX_EXTRACT_BYTES = 1 * 1024 * 1024 * 1024;
31308
31515
  var ONNX_LOCK_FILE = ".aft-onnx-installing";
31309
31516
  var ONNX_INSTALLED_META_FILE = ".aft-onnx-installed";
@@ -31421,7 +31628,7 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
31421
31628
  if (Number.isFinite(pid) && pid > 0) {
31422
31629
  if (process.platform === "win32") {
31423
31630
  try {
31424
- const ageMs = Date.now() - statSync(stagingDir).mtimeMs;
31631
+ const ageMs = Date.now() - statSync2(stagingDir).mtimeMs;
31425
31632
  abandoned = ageMs > STALE_LOCK_MS;
31426
31633
  } catch {
31427
31634
  abandoned = true;
@@ -31435,7 +31642,7 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
31435
31642
  if (abandoned) {
31436
31643
  log(`[onnx] removing abandoned staging dir ${stagingDir}`);
31437
31644
  try {
31438
- rmSync(stagingDir, { recursive: true, force: true });
31645
+ rmSync2(stagingDir, { recursive: true, force: true });
31439
31646
  } catch (err) {
31440
31647
  warn(`[onnx] failed to remove ${stagingDir}: ${err}`);
31441
31648
  }
@@ -31447,7 +31654,7 @@ function cleanupIncompleteTargetIfUnowned(ortDir) {
31447
31654
  try {
31448
31655
  if (existsSync2(ortDir) && !existsSync2(join3(ortDir, ONNX_INSTALLED_META_FILE))) {
31449
31656
  log(`[onnx] removing half-populated install dir ${ortDir} (no meta file)`);
31450
- rmSync(ortDir, { recursive: true, force: true });
31657
+ rmSync2(ortDir, { recursive: true, force: true });
31451
31658
  }
31452
31659
  } catch (err) {
31453
31660
  warn(`[onnx] failed to sweep ${ortDir}: ${err}`);
@@ -31527,24 +31734,24 @@ async function downloadFileWithCap(url, destPath) {
31527
31734
  throw new Error(`download failed (HTTP ${res.status})`);
31528
31735
  }
31529
31736
  const advertised = Number.parseInt(res.headers.get("content-length") ?? "", 10);
31530
- if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES) {
31531
- 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}`);
31532
31739
  }
31533
31740
  mkdirSync2(dirname(destPath), { recursive: true });
31534
31741
  let bytesWritten = 0;
31535
31742
  const guard = new TransformStream({
31536
31743
  transform(chunk, transformController) {
31537
31744
  bytesWritten += chunk.byteLength;
31538
- if (bytesWritten > MAX_DOWNLOAD_BYTES) {
31539
- 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)`));
31540
31747
  return;
31541
31748
  }
31542
31749
  transformController.enqueue(chunk);
31543
31750
  }
31544
31751
  });
31545
31752
  const guarded = res.body.pipeThrough(guard);
31546
- const nodeStream = Readable.fromWeb(guarded);
31547
- await pipeline(nodeStream, createWriteStream(destPath), { signal: controller.signal });
31753
+ const nodeStream = Readable2.fromWeb(guarded);
31754
+ await pipeline2(nodeStream, createWriteStream2(destPath), { signal: controller.signal });
31548
31755
  } catch (err) {
31549
31756
  try {
31550
31757
  unlinkSync2(destPath);
@@ -31643,16 +31850,16 @@ async function downloadOnnxRuntime(info, targetDir) {
31643
31850
  warn(`Could not hash newly-installed ONNX library at ${libPath}: ${err}`);
31644
31851
  }
31645
31852
  writeOnnxInstalledMeta(targetDir, ORT_VERSION, libHash, archiveSha256);
31646
- rmSync(tmpDir, { recursive: true, force: true });
31853
+ rmSync2(tmpDir, { recursive: true, force: true });
31647
31854
  log(`ONNX Runtime v${ORT_VERSION} installed to ${targetDir}`);
31648
31855
  return targetDir;
31649
31856
  } catch (err) {
31650
31857
  error(`Failed to download ONNX Runtime: ${err}`);
31651
31858
  try {
31652
- rmSync(tmpDir, { recursive: true, force: true });
31859
+ rmSync2(tmpDir, { recursive: true, force: true });
31653
31860
  } catch {}
31654
31861
  try {
31655
- rmSync(targetDir, { recursive: true, force: true });
31862
+ rmSync2(targetDir, { recursive: true, force: true });
31656
31863
  } catch {}
31657
31864
  return null;
31658
31865
  }
@@ -31669,7 +31876,7 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
31669
31876
  }
31670
31877
  } catch (copyErr) {
31671
31878
  if (requiredLibs.has(libFile)) {
31672
- rmSync(targetDir, { recursive: true, force: true });
31879
+ rmSync2(targetDir, { recursive: true, force: true });
31673
31880
  throw copyErr;
31674
31881
  }
31675
31882
  log(`ORT extract: failed to copy optional ${libFile}: ${copyErr}`);
@@ -31684,7 +31891,7 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
31684
31891
  symlinkSync(link.target, dst);
31685
31892
  } catch (symlinkErr) {
31686
31893
  if (requiredLibs.has(link.name)) {
31687
- rmSync(targetDir, { recursive: true, force: true });
31894
+ rmSync2(targetDir, { recursive: true, force: true });
31688
31895
  throw symlinkErr;
31689
31896
  }
31690
31897
  log(`ORT extract: failed to symlink optional ${link.name}: ${symlinkErr}`);
@@ -31692,7 +31899,7 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
31692
31899
  }
31693
31900
  const requiredPath = join3(targetDir, info.libName);
31694
31901
  if (!existsSync2(requiredPath)) {
31695
- rmSync(targetDir, { recursive: true, force: true });
31902
+ rmSync2(targetDir, { recursive: true, force: true });
31696
31903
  throw new Error(`Required ONNX Runtime library missing after install: ${requiredPath}`);
31697
31904
  }
31698
31905
  }
@@ -31725,7 +31932,7 @@ function writeOnnxInstalledMeta(installDir, version, sha256, archiveSha256) {
31725
31932
  function readOnnxInstalledMeta(installDir) {
31726
31933
  const path = join3(installDir, ONNX_INSTALLED_META_FILE);
31727
31934
  try {
31728
- if (!statSync(path).isFile())
31935
+ if (!statSync2(path).isFile())
31729
31936
  return null;
31730
31937
  const raw = readFileSync(path, "utf8");
31731
31938
  const parsed = JSON.parse(raw);
@@ -31742,20 +31949,20 @@ function readOnnxInstalledMeta(installDir) {
31742
31949
  }
31743
31950
  }
31744
31951
  function sha256File(path) {
31745
- const hash = createHash("sha256");
31952
+ const hash = createHash2("sha256");
31746
31953
  hash.update(readFileSync(path));
31747
31954
  return hash.digest("hex");
31748
31955
  }
31749
31956
  function acquireLock(lockPath) {
31750
31957
  const tryClaim = () => {
31751
31958
  try {
31752
- const fd = openSync(lockPath, "wx");
31959
+ const fd = openSync2(lockPath, "wx");
31753
31960
  try {
31754
31961
  writeFileSync(fd, `${process.pid}
31755
31962
  ${new Date().toISOString()}
31756
31963
  `);
31757
31964
  } finally {
31758
- closeSync(fd);
31965
+ closeSync2(fd);
31759
31966
  }
31760
31967
  return true;
31761
31968
  } catch (err) {
@@ -31776,7 +31983,7 @@ ${new Date().toISOString()}
31776
31983
  const parsed = Number.parseInt(firstLine, 10);
31777
31984
  if (Number.isFinite(parsed) && parsed > 0)
31778
31985
  owningPid = parsed;
31779
- lockMtimeMs = statSync(lockPath).mtimeMs;
31986
+ lockMtimeMs = statSync2(lockPath).mtimeMs;
31780
31987
  } catch {
31781
31988
  return tryClaim();
31782
31989
  }
@@ -31893,7 +32100,9 @@ class BridgePool {
31893
32100
  onVersionMismatch: options.onVersionMismatch,
31894
32101
  onConfigureWarnings: options.onConfigureWarnings,
31895
32102
  onBashCompletion: options.onBashCompletion,
31896
- onBashLongRunning: options.onBashLongRunning
32103
+ onBashLongRunning: options.onBashLongRunning,
32104
+ errorPrefix: options.errorPrefix,
32105
+ logger: options.logger
31897
32106
  };
31898
32107
  this.configOverrides = configOverrides;
31899
32108
  if (Number.isFinite(this.idleTimeoutMs)) {
@@ -31965,23 +32174,32 @@ class BridgePool {
31965
32174
  }
31966
32175
  async replaceBinary(newPath) {
31967
32176
  this.binaryPath = newPath;
31968
- const shutdowns = Array.from(this.bridges.values()).map((entry) => entry.bridge.shutdown());
31969
32177
  this.bridges.clear();
31970
- await Promise.allSettled(shutdowns);
31971
32178
  this.log(`Binary path updated to ${newPath}. All bridges cleared — next calls will use the new binary.`);
32179
+ return newPath;
31972
32180
  }
31973
32181
  log(message, meta) {
31974
32182
  const logger = this.logger ?? getActiveLogger();
31975
- if (logger)
31976
- logger.log(message, meta);
31977
- else
32183
+ if (logger) {
32184
+ try {
32185
+ logger.log(message, meta);
32186
+ } catch (err) {
32187
+ console.error(`[aft-bridge] ERROR: pool logger log threw: ${err instanceof Error ? err.message : String(err)}`);
32188
+ console.error(`[aft-bridge] ${message}`);
32189
+ }
32190
+ } else
31978
32191
  log(message, meta);
31979
32192
  }
31980
32193
  error(message, meta) {
31981
32194
  const logger = this.logger ?? getActiveLogger();
31982
- if (logger)
31983
- logger.error(message, meta);
31984
- else
32195
+ if (logger) {
32196
+ try {
32197
+ logger.error(message, meta);
32198
+ } catch (err) {
32199
+ console.error(`[aft-bridge] ERROR: pool logger error threw: ${err instanceof Error ? err.message : String(err)}`);
32200
+ console.error(`[aft-bridge] ERROR: ${message}`);
32201
+ }
32202
+ } else
31985
32203
  error(message, meta);
31986
32204
  }
31987
32205
  setConfigureOverride(key, value) {
@@ -32008,7 +32226,7 @@ function normalizeKey(projectRoot) {
32008
32226
  }
32009
32227
  // ../aft-bridge/dist/resolver.js
32010
32228
  import { execSync, spawnSync } from "node:child_process";
32011
- import { chmodSync as chmodSync3, copyFileSync as copyFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync3, renameSync } from "node:fs";
32229
+ import { chmodSync as chmodSync3, copyFileSync as copyFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync3, renameSync as renameSync2 } from "node:fs";
32012
32230
  import { createRequire as createRequire2 } from "node:module";
32013
32231
  import { homedir as homedir4 } from "node:os";
32014
32232
  import { join as join4 } from "node:path";
@@ -32019,7 +32237,9 @@ function readBinaryVersion(binaryPath) {
32019
32237
  stdio: ["pipe", "pipe", "pipe"],
32020
32238
  timeout: 5000
32021
32239
  });
32022
- const rawVersion = result.stdout?.trim();
32240
+ const stdoutVersion = result.stdout?.trim();
32241
+ const stderrVersion = result.stderr?.trim();
32242
+ const rawVersion = stdoutVersion || stderrVersion;
32023
32243
  if (!rawVersion)
32024
32244
  return null;
32025
32245
  return rawVersion.replace(/^aft\s+/, "");
@@ -32037,15 +32257,19 @@ function copyToVersionedCache(npmBinaryPath, knownVersion) {
32037
32257
  const versionedDir = join4(cacheDir, tag);
32038
32258
  const ext = process.platform === "win32" ? ".exe" : "";
32039
32259
  const cachedPath = join4(versionedDir, `aft${ext}`);
32040
- if (existsSync3(cachedPath))
32041
- return cachedPath;
32260
+ if (existsSync3(cachedPath)) {
32261
+ const cachedVersion = readBinaryVersion(cachedPath);
32262
+ if (cachedVersion === version)
32263
+ return cachedPath;
32264
+ warn(`Cached binary at ${cachedPath} reports ${cachedVersion ?? "no version"}, expected ${version}; refreshing from npm package`);
32265
+ }
32042
32266
  mkdirSync3(versionedDir, { recursive: true });
32043
- const tmpPath = `${cachedPath}.tmp`;
32267
+ const tmpPath = `${cachedPath}.${process.pid}.${Date.now()}.tmp`;
32044
32268
  copyFileSync2(npmBinaryPath, tmpPath);
32045
32269
  if (process.platform !== "win32") {
32046
32270
  chmodSync3(tmpPath, 493);
32047
32271
  }
32048
- renameSync(tmpPath, cachedPath);
32272
+ renameSync2(tmpPath, cachedPath);
32049
32273
  log(`Copied npm binary to versioned cache: ${cachedPath}`);
32050
32274
  return cachedPath;
32051
32275
  } catch (err) {
@@ -32053,6 +32277,17 @@ function copyToVersionedCache(npmBinaryPath, knownVersion) {
32053
32277
  return null;
32054
32278
  }
32055
32279
  }
32280
+ function normalizeBareVersion(version) {
32281
+ return version.startsWith("v") ? version.slice(1) : version;
32282
+ }
32283
+ function isExpectedCachedBinary(binaryPath, expectedVersion) {
32284
+ const expected = normalizeBareVersion(expectedVersion);
32285
+ const actual = readBinaryVersion(binaryPath);
32286
+ if (actual === expected)
32287
+ return true;
32288
+ warn(`Cached binary at ${binaryPath} reports ${actual ?? "no version"}, expected ${expected}; skipping cache candidate`);
32289
+ return false;
32290
+ }
32056
32291
  function platformKey(platform = process.platform, arch = process.arch) {
32057
32292
  const archMap = PLATFORM_ARCH_MAP[platform];
32058
32293
  if (!archMap) {
@@ -32077,7 +32312,7 @@ function findBinarySync(expectedVersion) {
32077
32312
  if (pluginVersion) {
32078
32313
  const tag = pluginVersion.startsWith("v") ? pluginVersion : `v${pluginVersion}`;
32079
32314
  const versionCached = getCachedBinaryPath(tag);
32080
- if (versionCached)
32315
+ if (versionCached && isExpectedCachedBinary(versionCached, pluginVersion))
32081
32316
  return versionCached;
32082
32317
  }
32083
32318
  try {
@@ -32087,10 +32322,12 @@ function findBinarySync(expectedVersion) {
32087
32322
  const resolved = req.resolve(packageBin);
32088
32323
  if (existsSync3(resolved)) {
32089
32324
  const npmVersion = readBinaryVersion(resolved);
32090
- if (pluginVersion && npmVersion && npmVersion !== pluginVersion) {
32325
+ if (npmVersion === null) {
32326
+ warn(`npm platform package binary at ${resolved} did not report a version; skipping (continuing to PATH lookup)`);
32327
+ } else if (pluginVersion && npmVersion !== normalizeBareVersion(pluginVersion)) {
32091
32328
  warn(`npm platform package binary v${npmVersion} does not match plugin v${pluginVersion}; skipping (continuing to PATH lookup)`);
32092
32329
  } else {
32093
- const copied = copyToVersionedCache(resolved, npmVersion ?? undefined);
32330
+ const copied = copyToVersionedCache(resolved, npmVersion);
32094
32331
  return copied ?? resolved;
32095
32332
  }
32096
32333
  }
@@ -32139,7 +32376,7 @@ async function findBinary(expectedVersion) {
32139
32376
  `));
32140
32377
  }
32141
32378
  // ../aft-bridge/dist/url-fetch.js
32142
- import { createHash as createHash2 } from "node:crypto";
32379
+ import { createHash as createHash3 } from "node:crypto";
32143
32380
  import { lookup } from "node:dns/promises";
32144
32381
  import { existsSync as existsSync4, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "node:fs";
32145
32382
  import { isIP } from "node:net";
@@ -32296,7 +32533,7 @@ function cacheDir(storageDir) {
32296
32533
  return join5(storageDir, "url_cache");
32297
32534
  }
32298
32535
  function hashUrl(url) {
32299
- return createHash2("sha256").update(url).digest("hex").slice(0, 16);
32536
+ return createHash3("sha256").update(url).digest("hex").slice(0, 16);
32300
32537
  }
32301
32538
  function metaPath(storageDir, hash) {
32302
32539
  return join5(cacheDir(storageDir), `${hash}.meta.json`);
@@ -32498,6 +32735,7 @@ async function fetchUrlToTempFile(url, storageDir, options = {}) {
32498
32735
  throw new Error(`Only http:// and https:// URLs are supported, got: ${parsed.protocol}`);
32499
32736
  }
32500
32737
  const allowPrivate = options.allowPrivate === true;
32738
+ await assertPublicUrl(parsed, allowPrivate, options.lookup);
32501
32739
  const dir = cacheDir(storageDir);
32502
32740
  mkdirSync4(dir, { recursive: true });
32503
32741
  const hash = hashUrl(url);
@@ -32553,8 +32791,8 @@ async function fetchUrlToTempFile(url, storageDir, options = {}) {
32553
32791
  const contentFile = contentPath(storageDir, hash, extension);
32554
32792
  const tmpContent = `${contentFile}.tmp-${process.pid}`;
32555
32793
  writeFileSync2(tmpContent, body);
32556
- const { renameSync: renameSync2 } = await import("node:fs");
32557
- renameSync2(tmpContent, contentFile);
32794
+ const { renameSync: renameSync3 } = await import("node:fs");
32795
+ renameSync3(tmpContent, contentFile);
32558
32796
  const meta = {
32559
32797
  url,
32560
32798
  contentType,
@@ -32563,7 +32801,7 @@ async function fetchUrlToTempFile(url, storageDir, options = {}) {
32563
32801
  };
32564
32802
  const tmpMeta = `${metaFile}.tmp-${process.pid}`;
32565
32803
  writeFileSync2(tmpMeta, JSON.stringify(meta));
32566
- renameSync2(tmpMeta, metaFile);
32804
+ renameSync3(tmpMeta, metaFile);
32567
32805
  log(`URL cached (${total} bytes): ${url}`);
32568
32806
  return contentFile;
32569
32807
  }
@@ -32681,31 +32919,31 @@ function warn2(message, data) {
32681
32919
  function error2(message, data) {
32682
32920
  write("ERROR", message, data);
32683
32921
  }
32684
- function sessionLog2(sessionId, message, data) {
32922
+ function sessionLog(sessionId, message, data) {
32685
32923
  write("INFO", message, data, sessionId);
32686
32924
  }
32687
- function sessionWarn2(sessionId, message, data) {
32925
+ function sessionWarn(sessionId, message, data) {
32688
32926
  write("WARN", message, data, sessionId);
32689
32927
  }
32690
- function sessionError2(sessionId, message, data) {
32928
+ function sessionError(sessionId, message, data) {
32691
32929
  write("ERROR", message, data, sessionId);
32692
32930
  }
32693
32931
  var bridgeLogger = {
32694
32932
  log(message, meta) {
32695
32933
  if (meta?.sessionId)
32696
- sessionLog2(meta.sessionId, message);
32934
+ sessionLog(meta.sessionId, message);
32697
32935
  else
32698
32936
  write("INFO", message);
32699
32937
  },
32700
32938
  warn(message, meta) {
32701
32939
  if (meta?.sessionId)
32702
- sessionWarn2(meta.sessionId, message);
32940
+ sessionWarn(meta.sessionId, message);
32703
32941
  else
32704
32942
  write("WARN", message);
32705
32943
  },
32706
32944
  error(message, meta) {
32707
32945
  if (meta?.sessionId)
32708
- sessionError2(meta.sessionId, message);
32946
+ sessionError(meta.sessionId, message);
32709
32947
  else
32710
32948
  write("ERROR", message);
32711
32949
  },
@@ -32717,6 +32955,7 @@ var sessionBgStates = new Map;
32717
32955
  var SESSION_BG_STATE_IDLE_TTL_MS = 60 * 60 * 1000;
32718
32956
  var DEBOUNCE_STEP_MS = 200;
32719
32957
  var DEBOUNCE_CAP_MS = 1000;
32958
+ var MAX_WAKE_SEND_ATTEMPTS = 5;
32720
32959
  var UNKNOWN_COMPLETION_TTL_MS = 5000;
32721
32960
  var UNKNOWN_COMPLETION_CAP = 32;
32722
32961
  var DEFAULT_SESSION_ID = "__default__";
@@ -32766,16 +33005,22 @@ async function handlePushedBgLongRunning(drainContext, reminder) {
32766
33005
  }
32767
33006
  async function appendToolResultBgCompletions(drainContext, content) {
32768
33007
  const state = stateFor(drainContext.sessionID);
33008
+ if (state.outstandingTaskIds.size === 0 && state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0)
33009
+ await drainCompletions(drainContext);
32769
33010
  if (state.outstandingTaskIds.size === 0 && state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0)
32770
33011
  return;
32771
- if (state.outstandingTaskIds.size > 0) {
33012
+ if (state.outstandingTaskIds.size > 0 || !state.forcedDrainCompleted) {
32772
33013
  await drainCompletions(drainContext);
32773
33014
  }
32774
33015
  if (state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0)
32775
33016
  return;
33017
+ const deliveredCompletions = [...state.pendingCompletions];
32776
33018
  const reminder = formatCombinedSystemReminder(state.pendingCompletions, state.pendingLongRunning);
32777
33019
  state.pendingCompletions = [];
32778
33020
  state.pendingLongRunning = [];
33021
+ state.wakeRetryAttempts = 0;
33022
+ state.wakeHardStopped = false;
33023
+ await ackCompletions(drainContext, deliveredCompletions);
32779
33024
  if (state.debounceTimer) {
32780
33025
  clearTimeout(state.debounceTimer);
32781
33026
  state.debounceTimer = null;
@@ -32790,15 +33035,16 @@ async function handleTurnEndBgCompletions(drainContext) {
32790
33035
  }
32791
33036
  async function triggerWakeIfPending(drainContext, skipDrain) {
32792
33037
  const state = stateFor(drainContext.sessionID);
32793
- if (!skipDrain && state.outstandingTaskIds.size > 0) {
33038
+ if (!skipDrain && (state.outstandingTaskIds.size > 0 || !state.forcedDrainCompleted)) {
32794
33039
  await drainCompletions(drainContext);
32795
33040
  }
32796
33041
  if (state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0)
32797
33042
  return;
32798
- scheduleWake(state, async (reminder) => {
33043
+ scheduleWake(state, async (reminder, deliveredCompletions) => {
32799
33044
  drainContext.runtime.sendUserMessage(reminder, { deliverAs: "steer" });
32800
- }, (err) => {
32801
- sessionWarn2(drainContext.sessionID ?? "", `${LOG_PREFIX} wake send failed: ${err instanceof Error ? err.message : String(err)}`);
33045
+ await ackCompletions(drainContext, deliveredCompletions);
33046
+ }, (err, hardStopped) => {
33047
+ 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)}`);
32802
33048
  });
32803
33049
  }
32804
33050
  function formatSystemReminder(completions) {
@@ -32831,20 +33077,39 @@ function formatCombinedSystemReminder(completions, longRunning) {
32831
33077
  ${formatLongRunningReminder(longRunning)}`;
32832
33078
  }
32833
33079
  async function drainCompletions({ ctx, directory, sessionID }) {
33080
+ const state = stateFor(sessionID);
32834
33081
  try {
32835
33082
  const bridge = ctx.pool.getActiveBridgeForRoot(directory) ?? ctx.pool.getBridge(directory);
32836
33083
  const params = sessionID ? { session_id: sessionID } : {};
32837
33084
  const response = await bridge.send("bash_drain_completions", params);
32838
33085
  if (response.success === false) {
32839
- sessionWarn2(sessionID ?? "", `${LOG_PREFIX} drain failed: ${String(response.message ?? "unknown error")}`);
33086
+ sessionWarn(sessionID ?? "", `${LOG_PREFIX} drain failed: ${String(response.message ?? "unknown error")}`);
32840
33087
  return;
32841
33088
  }
32842
- ingestBgCompletions(sessionID, response.bg_completions);
33089
+ state.forcedDrainCompleted = true;
33090
+ ingestDrainedBgCompletions(sessionID, response.bg_completions);
33091
+ } catch (err) {
33092
+ sessionWarn(sessionID ?? "", `${LOG_PREFIX} drain failed: ${err instanceof Error ? err.message : String(err)}`);
33093
+ }
33094
+ }
33095
+ async function ackCompletions({ ctx, directory, sessionID }, completions) {
33096
+ const taskIds = [...new Set(completions.map((completion) => completion.task_id))];
33097
+ if (taskIds.length === 0)
33098
+ return;
33099
+ try {
33100
+ const bridge = ctx.pool.getActiveBridgeForRoot(directory) ?? ctx.pool.getBridge(directory);
33101
+ const params = sessionID ? { session_id: sessionID, task_ids: taskIds } : { task_ids: taskIds };
33102
+ const response = await bridge.send("bash_ack_completions", params);
33103
+ if (response.success === false) {
33104
+ sessionWarn(sessionID ?? "", `${LOG_PREFIX} ack failed: ${String(response.message ?? "unknown error")}`);
33105
+ }
32843
33106
  } catch (err) {
32844
- sessionWarn2(sessionID ?? "", `${LOG_PREFIX} drain failed: ${err instanceof Error ? err.message : String(err)}`);
33107
+ sessionWarn(sessionID ?? "", `${LOG_PREFIX} ack failed: ${err instanceof Error ? err.message : String(err)}`);
32845
33108
  }
32846
33109
  }
32847
33110
  function scheduleWake(state, sendWake, onSendFailure) {
33111
+ if (state.wakeHardStopped)
33112
+ return;
32848
33113
  const now = Date.now();
32849
33114
  const pendingCount = state.pendingCompletions.length + state.pendingLongRunning.length;
32850
33115
  if (state.debounceTimer && pendingCount <= state.scheduledCompletionCount) {
@@ -32873,13 +33138,22 @@ function scheduleWake(state, sendWake, onSendFailure) {
32873
33138
  const reminder = formatCombinedSystemReminder(pending, pendingLongRunning);
32874
33139
  state.pendingCompletions = [];
32875
33140
  state.pendingLongRunning = [];
32876
- sendWake(reminder).then(() => {
33141
+ sendWake(reminder, pending).then(() => {
32877
33142
  state.retryDelayMs = null;
33143
+ state.wakeRetryAttempts = 0;
33144
+ state.wakeHardStopped = false;
32878
33145
  }).catch((err) => {
32879
33146
  state.pendingCompletions = [...pending, ...state.pendingCompletions];
32880
33147
  state.pendingLongRunning = [...pendingLongRunning, ...state.pendingLongRunning];
33148
+ state.wakeRetryAttempts += 1;
33149
+ if (state.wakeRetryAttempts >= MAX_WAKE_SEND_ATTEMPTS) {
33150
+ state.retryDelayMs = null;
33151
+ state.wakeHardStopped = true;
33152
+ onSendFailure(err, true);
33153
+ return;
33154
+ }
32881
33155
  state.retryDelayMs = Math.min((delay || DEBOUNCE_STEP_MS) * 2, DEBOUNCE_CAP_MS);
32882
- onSendFailure(err);
33156
+ onSendFailure(err, false);
32883
33157
  scheduleWake(state, sendWake, onSendFailure);
32884
33158
  });
32885
33159
  }, delay);
@@ -32900,6 +33174,9 @@ function stateFor(sessionID) {
32900
33174
  scheduledFireAt: null,
32901
33175
  scheduledCompletionCount: 0,
32902
33176
  retryDelayMs: null,
33177
+ wakeRetryAttempts: 0,
33178
+ wakeHardStopped: false,
33179
+ forcedDrainCompleted: false,
32903
33180
  unknownCompletions: [],
32904
33181
  lastSeenAt: now
32905
33182
  };
@@ -32909,6 +33186,22 @@ function stateFor(sessionID) {
32909
33186
  }
32910
33187
  return state;
32911
33188
  }
33189
+ function ingestDrainedBgCompletions(sessionID, completions) {
33190
+ if (!Array.isArray(completions) || completions.length === 0)
33191
+ return [];
33192
+ const state = stateFor(sessionID);
33193
+ const accepted = [];
33194
+ for (const completion of completions) {
33195
+ if (!isBgCompletion(completion))
33196
+ continue;
33197
+ state.outstandingTaskIds.delete(completion.task_id);
33198
+ if (!state.pendingCompletions.some((pending) => pending.task_id === completion.task_id) && !accepted.some((pending) => pending.task_id === completion.task_id)) {
33199
+ accepted.push(completion);
33200
+ }
33201
+ }
33202
+ state.pendingCompletions.push(...accepted);
33203
+ return accepted;
33204
+ }
32912
33205
  function cleanupIdleSessionStates(now = Date.now()) {
32913
33206
  const cutoff = now - SESSION_BG_STATE_IDLE_TTL_MS;
32914
33207
  for (const [sessionID, state] of sessionBgStates) {
@@ -33001,7 +33294,7 @@ import {
33001
33294
  // package.json
33002
33295
  var package_default = {
33003
33296
  name: "@cortexkit/aft-pi",
33004
- version: "0.25.2",
33297
+ version: "0.26.1",
33005
33298
  type: "module",
33006
33299
  description: "Pi coding agent extension for Agent File Tools (AFT) — tree-sitter and LSP-powered code analysis",
33007
33300
  main: "dist/index.js",
@@ -33023,18 +33316,18 @@ var package_default = {
33023
33316
  prepublishOnly: "bun run build"
33024
33317
  },
33025
33318
  dependencies: {
33026
- "@cortexkit/aft-bridge": "0.25.2",
33319
+ "@cortexkit/aft-bridge": "0.26.1",
33027
33320
  typebox: "^1.1.24",
33028
33321
  "comment-json": "^5.0.0",
33029
33322
  diff: "^8.0.4",
33030
33323
  zod: "^4.1.8"
33031
33324
  },
33032
33325
  optionalDependencies: {
33033
- "@cortexkit/aft-darwin-arm64": "0.25.2",
33034
- "@cortexkit/aft-darwin-x64": "0.25.2",
33035
- "@cortexkit/aft-linux-arm64": "0.25.2",
33036
- "@cortexkit/aft-linux-x64": "0.25.2",
33037
- "@cortexkit/aft-win32-x64": "0.25.2"
33326
+ "@cortexkit/aft-darwin-arm64": "0.26.1",
33327
+ "@cortexkit/aft-darwin-x64": "0.26.1",
33328
+ "@cortexkit/aft-linux-arm64": "0.26.1",
33329
+ "@cortexkit/aft-linux-x64": "0.26.1",
33330
+ "@cortexkit/aft-win32-x64": "0.26.1"
33038
33331
  },
33039
33332
  devDependencies: {
33040
33333
  "@earendil-works/pi-coding-agent": "*",
@@ -33519,7 +33812,7 @@ function registerStatusCommand(pi, ctx) {
33519
33812
 
33520
33813
  // src/config.ts
33521
33814
  var import_comment_json = __toESM(require_src2(), 1);
33522
- import { existsSync as existsSync5, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync as unlinkSync4, writeFileSync as writeFileSync3 } from "node:fs";
33815
+ import { existsSync as existsSync5, readFileSync as readFileSync3, renameSync as renameSync3, unlinkSync as unlinkSync4, writeFileSync as writeFileSync3 } from "node:fs";
33523
33816
  import { homedir as homedir5 } from "node:os";
33524
33817
  import { join as join7 } from "node:path";
33525
33818
 
@@ -47294,7 +47587,7 @@ function migrateAftConfigFile(configPath, logger = { log: log2, warn: warn2 }) {
47294
47587
  ${serialized}` : serialized;
47295
47588
  tmpPath = `${configPath}.tmp.${process.pid}`;
47296
47589
  writeFileSync3(tmpPath, nextContent, "utf-8");
47297
- renameSync2(tmpPath, configPath);
47590
+ renameSync3(tmpPath, configPath);
47298
47591
  logger.log(`Migrated config at ${configPath}: removed ${oldKeys.join(", ")}`);
47299
47592
  return { migrated: true, oldKeys };
47300
47593
  } catch (err) {
@@ -47509,17 +47802,17 @@ function loadAftConfig(projectDirectory) {
47509
47802
 
47510
47803
  // src/lsp-auto-install.ts
47511
47804
  import { spawn as spawn2 } from "node:child_process";
47512
- import { createHash as createHash3 } from "node:crypto";
47513
- import { createReadStream, mkdirSync as mkdirSync6, readFileSync as readFileSync5, renameSync as renameSync3, rmSync as rmSync2, statSync as statSync3 } from "node:fs";
47805
+ import { createHash as createHash4 } from "node:crypto";
47806
+ import { createReadStream, mkdirSync as mkdirSync6, readFileSync as readFileSync5, renameSync as renameSync4, rmSync as rmSync3, statSync as statSync4 } from "node:fs";
47514
47807
  import { join as join10 } from "node:path";
47515
47808
 
47516
47809
  // src/lsp-cache.ts
47517
47810
  import {
47518
- closeSync as closeSync2,
47811
+ closeSync as closeSync3,
47519
47812
  mkdirSync as mkdirSync5,
47520
- openSync as openSync2,
47813
+ openSync as openSync3,
47521
47814
  readFileSync as readFileSync4,
47522
- statSync as statSync2,
47815
+ statSync as statSync3,
47523
47816
  unlinkSync as unlinkSync5,
47524
47817
  writeFileSync as writeFileSync4
47525
47818
  } from "node:fs";
@@ -47552,7 +47845,7 @@ function lspBinDir(npmPackage) {
47552
47845
  function isInstalled(npmPackage, binary) {
47553
47846
  for (const candidate of lspBinaryCandidates(binary)) {
47554
47847
  try {
47555
- if (statSync2(join8(lspBinDir(npmPackage), candidate)).isFile())
47848
+ if (statSync3(join8(lspBinDir(npmPackage), candidate)).isFile())
47556
47849
  return true;
47557
47850
  } catch {}
47558
47851
  }
@@ -47580,7 +47873,7 @@ function writeInstalledMetaIn(installDir, version2, sha256) {
47580
47873
  function readInstalledMetaIn(installDir) {
47581
47874
  const path2 = join8(installDir, INSTALLED_META_FILE);
47582
47875
  try {
47583
- if (!statSync2(path2).isFile())
47876
+ if (!statSync3(path2).isFile())
47584
47877
  return null;
47585
47878
  const raw = readFileSync4(path2, "utf8");
47586
47879
  const parsed = JSON.parse(raw);
@@ -47610,13 +47903,13 @@ function acquireInstallLock(lockKey) {
47610
47903
  const lock = lockPath(lockKey);
47611
47904
  const tryClaim = () => {
47612
47905
  try {
47613
- const fd = openSync2(lock, "wx");
47906
+ const fd = openSync3(lock, "wx");
47614
47907
  try {
47615
47908
  writeFileSync4(fd, `${process.pid}
47616
47909
  ${new Date().toISOString()}
47617
47910
  `);
47618
47911
  } finally {
47619
- closeSync2(fd);
47912
+ closeSync3(fd);
47620
47913
  }
47621
47914
  return true;
47622
47915
  } catch (err) {
@@ -47637,7 +47930,7 @@ ${new Date().toISOString()}
47637
47930
  const parsed = Number.parseInt(firstLine, 10);
47638
47931
  if (Number.isFinite(parsed) && parsed > 0)
47639
47932
  owningPid = parsed;
47640
- lockMtimeMs = statSync2(lock).mtimeMs;
47933
+ lockMtimeMs = statSync3(lock).mtimeMs;
47641
47934
  } catch {
47642
47935
  return tryClaim();
47643
47936
  }
@@ -48050,8 +48343,9 @@ function runInstall(spec, version2, cwd, signal) {
48050
48343
  resolve2(false);
48051
48344
  return;
48052
48345
  }
48053
- const child = spawn2("bun", ["add", target, "--cwd", cwd, "--ignore-scripts", "--silent"], {
48054
- stdio: ["ignore", "pipe", "pipe"]
48346
+ const child = spawn2("npm", ["install", "--no-save", "--ignore-scripts", "--silent", target], {
48347
+ stdio: ["ignore", "pipe", "pipe"],
48348
+ cwd
48055
48349
  });
48056
48350
  child.unref();
48057
48351
  let stderrBuf = "";
@@ -48178,7 +48472,7 @@ function hashInstalledBinary(spec) {
48178
48472
  let pathToHash = null;
48179
48473
  for (const p of candidates) {
48180
48474
  try {
48181
- if (statSync3(p).isFile()) {
48475
+ if (statSync4(p).isFile()) {
48182
48476
  pathToHash = p;
48183
48477
  break;
48184
48478
  }
@@ -48188,7 +48482,7 @@ function hashInstalledBinary(spec) {
48188
48482
  reject(new Error(`installed binary not found at any of: ${candidates.join(", ")}`));
48189
48483
  return;
48190
48484
  }
48191
- const hash2 = createHash3("sha256");
48485
+ const hash2 = createHash4("sha256");
48192
48486
  const stream = createReadStream(pathToHash);
48193
48487
  stream.on("error", reject);
48194
48488
  stream.on("data", (chunk) => hash2.update(chunk));
@@ -48204,14 +48498,14 @@ function installedBinaryPath(spec) {
48204
48498
  ] : [lspBinaryPath(spec.npm, spec.binary)];
48205
48499
  for (const candidate of candidates) {
48206
48500
  try {
48207
- if (statSync3(candidate).isFile())
48501
+ if (statSync4(candidate).isFile())
48208
48502
  return candidate;
48209
48503
  } catch {}
48210
48504
  }
48211
48505
  return null;
48212
48506
  }
48213
48507
  function sha256OfFileSync(path2) {
48214
- return createHash3("sha256").update(readFileSync5(path2)).digest("hex");
48508
+ return createHash4("sha256").update(readFileSync5(path2)).digest("hex");
48215
48509
  }
48216
48510
  function quarantineCachedNpmInstall(spec, reason) {
48217
48511
  const packageDir = lspPackageDir(spec.npm);
@@ -48219,8 +48513,8 @@ function quarantineCachedNpmInstall(spec, reason) {
48219
48513
  warn2(`[lsp] tofu_mismatch ${spec.npm}: ${reason}; quarantining ${packageDir} -> ${dest}`);
48220
48514
  try {
48221
48515
  mkdirSync6(join10(dest, ".."), { recursive: true });
48222
- rmSync2(dest, { recursive: true, force: true });
48223
- renameSync3(packageDir, dest);
48516
+ rmSync3(dest, { recursive: true, force: true });
48517
+ renameSync4(packageDir, dest);
48224
48518
  } catch (err) {
48225
48519
  warn2(`[lsp] tofu_mismatch ${spec.npm}: failed to quarantine cache entry: ${err}`);
48226
48520
  }
@@ -48296,11 +48590,11 @@ function runAutoInstall(projectRoot, config2, fetchImpl2 = fetch) {
48296
48590
 
48297
48591
  // src/lsp-github-install.ts
48298
48592
  import { execFileSync as execFileSync2 } from "node:child_process";
48299
- import { createHash as createHash4, randomBytes } from "node:crypto";
48593
+ import { createHash as createHash5, randomBytes } from "node:crypto";
48300
48594
  import {
48301
48595
  copyFileSync as copyFileSync3,
48302
48596
  createReadStream as createReadStream2,
48303
- createWriteStream as createWriteStream2,
48597
+ createWriteStream as createWriteStream3,
48304
48598
  existsSync as existsSync7,
48305
48599
  lstatSync as lstatSync2,
48306
48600
  mkdirSync as mkdirSync7,
@@ -48308,14 +48602,15 @@ import {
48308
48602
  readFileSync as readFileSync6,
48309
48603
  readlinkSync as readlinkSync2,
48310
48604
  realpathSync as realpathSync3,
48311
- renameSync as renameSync4,
48312
- rmSync as rmSync3,
48313
- statSync as statSync4,
48314
- unlinkSync as unlinkSync6
48605
+ renameSync as renameSync5,
48606
+ rmSync as rmSync4,
48607
+ statSync as statSync5,
48608
+ unlinkSync as unlinkSync6,
48609
+ writeFileSync as writeFileSync5
48315
48610
  } from "node:fs";
48316
48611
  import { dirname as dirname2, join as join11, relative as relative2, resolve as resolve2 } from "node:path";
48317
- import { Readable as Readable2 } from "node:stream";
48318
- import { pipeline as pipeline2 } from "node:stream/promises";
48612
+ import { Readable as Readable3 } from "node:stream";
48613
+ import { pipeline as pipeline3 } from "node:stream/promises";
48319
48614
 
48320
48615
  // src/lsp-github-table.ts
48321
48616
  function exe(platform, name) {
@@ -48414,6 +48709,7 @@ function ghBinDir(spec) {
48414
48709
  function ghExtractDir(spec) {
48415
48710
  return join11(ghPackageDir(spec), "extracted");
48416
48711
  }
48712
+ var INSTALLED_META_FILE2 = ".aft-installed";
48417
48713
  function ghBinaryPath(spec, platform) {
48418
48714
  const ext = platform === "win32" ? ".exe" : "";
48419
48715
  return join11(ghBinDir(spec), `${spec.binary}${ext}`);
@@ -48421,7 +48717,7 @@ function ghBinaryPath(spec, platform) {
48421
48717
  function isGithubInstalled(spec, platform) {
48422
48718
  for (const candidate of ghBinaryCandidates(spec, platform)) {
48423
48719
  try {
48424
- if (statSync4(join11(ghBinDir(spec), candidate)).isFile())
48720
+ if (statSync5(join11(ghBinDir(spec), candidate)).isFile())
48425
48721
  return true;
48426
48722
  } catch {}
48427
48723
  }
@@ -48432,11 +48728,45 @@ function ghBinaryCandidates(spec, platform) {
48432
48728
  return [spec.binary];
48433
48729
  return [spec.binary, `${spec.binary}.cmd`, `${spec.binary}.exe`, `${spec.binary}.bat`];
48434
48730
  }
48435
- var MAX_DOWNLOAD_BYTES2 = 256 * 1024 * 1024;
48731
+ function readGithubInstalledMetaIn(installDir) {
48732
+ try {
48733
+ const path2 = join11(installDir, INSTALLED_META_FILE2);
48734
+ if (!statSync5(path2).isFile())
48735
+ return null;
48736
+ const parsed = JSON.parse(readFileSync6(path2, "utf8"));
48737
+ if (typeof parsed.version !== "string" || parsed.version.length === 0)
48738
+ return null;
48739
+ return {
48740
+ version: parsed.version,
48741
+ installedAt: typeof parsed.installedAt === "string" ? parsed.installedAt : "",
48742
+ ...typeof parsed.sha256 === "string" && parsed.sha256.length > 0 ? { sha256: parsed.sha256 } : {},
48743
+ ...typeof parsed.binarySha256 === "string" && parsed.binarySha256.length > 0 ? { binarySha256: parsed.binarySha256 } : {},
48744
+ ...typeof parsed.archiveSha256 === "string" && parsed.archiveSha256.length > 0 ? { archiveSha256: parsed.archiveSha256 } : {}
48745
+ };
48746
+ } catch {
48747
+ return null;
48748
+ }
48749
+ }
48750
+ function writeGithubInstalledMetaIn(installDir, version2, binarySha256, archiveSha256) {
48751
+ try {
48752
+ mkdirSync7(installDir, { recursive: true });
48753
+ const meta3 = {
48754
+ version: version2,
48755
+ installedAt: new Date().toISOString(),
48756
+ sha256: binarySha256,
48757
+ binarySha256,
48758
+ ...archiveSha256 ? { archiveSha256 } : {}
48759
+ };
48760
+ writeFileSync5(join11(installDir, INSTALLED_META_FILE2), JSON.stringify(meta3), "utf8");
48761
+ } catch (err) {
48762
+ warn2(`[lsp] failed to write github installed metadata in ${installDir}: ${err}`);
48763
+ }
48764
+ }
48765
+ var MAX_DOWNLOAD_BYTES3 = 256 * 1024 * 1024;
48436
48766
  var MAX_EXTRACT_BYTES2 = 1024 * 1024 * 1024;
48437
48767
  function sha256OfFile(path2) {
48438
48768
  return new Promise((resolve3, reject) => {
48439
- const hash2 = createHash4("sha256");
48769
+ const hash2 = createHash5("sha256");
48440
48770
  const stream = createReadStream2(path2);
48441
48771
  stream.on("error", reject);
48442
48772
  stream.on("data", (chunk) => hash2.update(chunk));
@@ -48444,7 +48774,7 @@ function sha256OfFile(path2) {
48444
48774
  });
48445
48775
  }
48446
48776
  function sha256OfFileSync2(path2) {
48447
- return createHash4("sha256").update(readFileSync6(path2)).digest("hex");
48777
+ return createHash5("sha256").update(readFileSync6(path2)).digest("hex");
48448
48778
  }
48449
48779
  async function fetchReleaseByTag(githubRepo, tag, fetchImpl2, signal) {
48450
48780
  const candidates = [];
@@ -48607,8 +48937,8 @@ function assertAllowedDownloadUrl(rawUrl) {
48607
48937
  return parsed;
48608
48938
  }
48609
48939
  async function downloadFile(url2, destPath, fetchImpl2, assetSize, signal) {
48610
- if (assetSize !== undefined && assetSize > MAX_DOWNLOAD_BYTES2) {
48611
- throw new Error(`asset size ${assetSize} exceeds max ${MAX_DOWNLOAD_BYTES2} (set lsp.versions to pin a smaller release if this is wrong)`);
48940
+ if (assetSize !== undefined && assetSize > MAX_DOWNLOAD_BYTES3) {
48941
+ throw new Error(`asset size ${assetSize} exceeds max ${MAX_DOWNLOAD_BYTES3} (set lsp.versions to pin a smaller release if this is wrong)`);
48612
48942
  }
48613
48943
  const timeout = controlledTimeoutSignal(120000, signal);
48614
48944
  try {
@@ -48617,24 +48947,24 @@ async function downloadFile(url2, destPath, fetchImpl2, assetSize, signal) {
48617
48947
  throw new Error(`download failed (${res.status})`);
48618
48948
  }
48619
48949
  const advertised = Number.parseInt(res.headers.get("content-length") ?? "", 10);
48620
- if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES2) {
48621
- throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES2}`);
48950
+ if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES3) {
48951
+ throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES3}`);
48622
48952
  }
48623
48953
  mkdirSync7(dirname2(destPath), { recursive: true });
48624
48954
  let bytesWritten = 0;
48625
48955
  const guard = new TransformStream({
48626
48956
  transform(chunk, controller) {
48627
48957
  bytesWritten += chunk.byteLength;
48628
- if (bytesWritten > MAX_DOWNLOAD_BYTES2) {
48629
- controller.error(new Error(`download exceeded ${MAX_DOWNLOAD_BYTES2} bytes after streaming (server lied about size or sent unbounded body)`));
48958
+ if (bytesWritten > MAX_DOWNLOAD_BYTES3) {
48959
+ controller.error(new Error(`download exceeded ${MAX_DOWNLOAD_BYTES3} bytes after streaming (server lied about size or sent unbounded body)`));
48630
48960
  return;
48631
48961
  }
48632
48962
  controller.enqueue(chunk);
48633
48963
  }
48634
48964
  });
48635
48965
  const guarded = res.body.pipeThrough(guard);
48636
- const nodeStream = Readable2.fromWeb(guarded);
48637
- await pipeline2(nodeStream, createWriteStream2(destPath), { signal: timeout.signal });
48966
+ const nodeStream = Readable3.fromWeb(guarded);
48967
+ await pipeline3(nodeStream, createWriteStream3(destPath), { signal: timeout.signal });
48638
48968
  } catch (err) {
48639
48969
  try {
48640
48970
  unlinkSync6(destPath);
@@ -48714,7 +49044,7 @@ function validateExtraction(stagingRoot) {
48714
49044
  };
48715
49045
  walk(realStagingRoot);
48716
49046
  }
48717
- function precheckArchiveSize(archivePath, archiveType) {
49047
+ function precheckArchiveContents(archivePath, archiveType) {
48718
49048
  let totalBytes = 0;
48719
49049
  if (archiveType === "zip") {
48720
49050
  const out = execFileSync2("unzip", ["-l", archivePath], { encoding: "utf8" });
@@ -48725,6 +49055,9 @@ function precheckArchiveSize(archivePath, archiveType) {
48725
49055
  const out = execFileSync2("tar", ["-tvf", archivePath], { encoding: "utf8" });
48726
49056
  for (const line of out.split(`
48727
49057
  `)) {
49058
+ if (line.startsWith("h")) {
49059
+ throw new Error(`archive contains hardlink entry: ${line.trim()}`);
49060
+ }
48728
49061
  const parts = line.trim().split(/\s+/);
48729
49062
  if (parts.length >= 6) {
48730
49063
  const numeric = parts.map((part) => Number.parseInt(part, 10)).filter((value) => Number.isFinite(value) && value >= 0);
@@ -48741,20 +49074,20 @@ function extractArchiveSafely(archivePath, destDir, archiveType) {
48741
49074
  const suffix = randomBytes(8).toString("hex");
48742
49075
  const stagingDir = `${destDir}.staging-${suffix}`;
48743
49076
  try {
48744
- rmSync3(stagingDir, { recursive: true, force: true });
49077
+ rmSync4(stagingDir, { recursive: true, force: true });
48745
49078
  } catch {}
48746
49079
  mkdirSync7(stagingDir, { recursive: true });
48747
49080
  try {
48748
- precheckArchiveSize(archivePath, archiveType);
49081
+ precheckArchiveContents(archivePath, archiveType);
48749
49082
  runPlatformExtractor(archivePath, stagingDir, archiveType);
48750
49083
  validateExtraction(stagingDir);
48751
49084
  try {
48752
- rmSync3(destDir, { recursive: true, force: true });
49085
+ rmSync4(destDir, { recursive: true, force: true });
48753
49086
  } catch {}
48754
- renameSync4(stagingDir, destDir);
49087
+ renameSync5(stagingDir, destDir);
48755
49088
  } catch (err) {
48756
49089
  try {
48757
- rmSync3(stagingDir, { recursive: true, force: true });
49090
+ rmSync4(stagingDir, { recursive: true, force: true });
48758
49091
  } catch {}
48759
49092
  throw err;
48760
49093
  }
@@ -48765,28 +49098,44 @@ function quarantineCachedGithubInstall(spec, reason) {
48765
49098
  warn2(`[lsp] tofu_mismatch ${spec.id}: ${reason}; quarantining ${packageDir} -> ${dest}`);
48766
49099
  try {
48767
49100
  mkdirSync7(dirname2(dest), { recursive: true });
48768
- rmSync3(dest, { recursive: true, force: true });
48769
- renameSync4(packageDir, dest);
49101
+ rmSync4(dest, { recursive: true, force: true });
49102
+ renameSync5(packageDir, dest);
48770
49103
  } catch (err) {
48771
49104
  warn2(`[lsp] tofu_mismatch ${spec.id}: failed to quarantine cache entry: ${err}`);
48772
49105
  }
48773
49106
  }
48774
49107
  function validateCachedGithubInstall(spec, platform) {
48775
- const meta3 = readInstalledMetaIn(ghPackageDir(spec));
49108
+ const packageDir = ghPackageDir(spec);
49109
+ const meta3 = readGithubInstalledMetaIn(packageDir);
48776
49110
  const binaryPath = ghBinaryCandidates(spec, platform).map((candidate) => join11(ghBinDir(spec), candidate)).find((candidate) => {
48777
49111
  try {
48778
- return statSync4(candidate).isFile();
49112
+ return statSync5(candidate).isFile();
48779
49113
  } catch {
48780
49114
  return false;
48781
49115
  }
48782
49116
  });
48783
- if (!meta3?.sha256 || !meta3.version || !isSafeVersion(meta3.version) || !binaryPath) {
49117
+ if (!meta3?.version || !isSafeVersion(meta3.version) || !binaryPath) {
48784
49118
  quarantineCachedGithubInstall(spec, "missing/unsafe metadata or binary");
48785
49119
  return false;
48786
49120
  }
48787
49121
  const currentHash = sha256OfFileSync2(binaryPath);
48788
- if (currentHash !== meta3.sha256) {
48789
- quarantineCachedGithubInstall(spec, `recorded ${meta3.sha256}, current ${currentHash}`);
49122
+ const recordedBinaryHash = meta3.binarySha256 ?? meta3.sha256;
49123
+ if (recordedBinaryHash && currentHash === recordedBinaryHash) {
49124
+ if (meta3.sha256 !== currentHash || meta3.binarySha256 !== currentHash) {
49125
+ writeGithubInstalledMetaIn(packageDir, meta3.version, currentHash, meta3.archiveSha256);
49126
+ }
49127
+ return true;
49128
+ }
49129
+ if (meta3.sha256 && !meta3.binarySha256 && !meta3.archiveSha256) {
49130
+ writeGithubInstalledMetaIn(packageDir, meta3.version, currentHash, meta3.sha256);
49131
+ return true;
49132
+ }
49133
+ if (!recordedBinaryHash) {
49134
+ quarantineCachedGithubInstall(spec, "missing binary sha256 metadata");
49135
+ return false;
49136
+ }
49137
+ if (currentHash !== recordedBinaryHash) {
49138
+ quarantineCachedGithubInstall(spec, `recorded ${recordedBinaryHash}, current ${currentHash}`);
48790
49139
  return false;
48791
49140
  }
48792
49141
  return true;
@@ -48855,10 +49204,11 @@ async function downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl2,
48855
49204
  return null;
48856
49205
  }
48857
49206
  log2(`[lsp] ${spec.id} ${tag} sha256=${archiveSha256}`);
48858
- const previousMeta = readInstalledMetaIn(ghPackageDir(spec));
48859
- if (previousMeta && previousMeta.version === tag && previousMeta.sha256) {
48860
- if (previousMeta.sha256 !== archiveSha256) {
48861
- 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.`);
49207
+ const previousMeta = readGithubInstalledMetaIn(ghPackageDir(spec));
49208
+ const previousArchiveSha256 = previousMeta?.archiveSha256 ?? (previousMeta?.binarySha256 ? undefined : previousMeta?.sha256);
49209
+ if (previousMeta && previousMeta.version === tag && previousArchiveSha256) {
49210
+ if (previousArchiveSha256 !== archiveSha256) {
49211
+ 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.`);
48862
49212
  try {
48863
49213
  unlinkSync6(archivePath);
48864
49214
  } catch {}
@@ -48893,7 +49243,9 @@ async function downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl2,
48893
49243
  return null;
48894
49244
  }
48895
49245
  log2(`[lsp] installed ${spec.id} ${tag} at ${targetBinary}`);
48896
- return archiveSha256;
49246
+ const binarySha256 = await sha256OfFile(targetBinary);
49247
+ log2(`[lsp] ${spec.id} ${tag} binary_sha256=${binarySha256}`);
49248
+ return { archiveSha256, binarySha256 };
48897
49249
  }
48898
49250
  async function ensureGithubInstalled(spec, config2, fetchImpl2, platform, arch, signal) {
48899
49251
  const outcome = await withInstallLock(spec.githubRepo, async () => {
@@ -48919,14 +49271,14 @@ async function ensureGithubInstalled(spec, config2, fetchImpl2, platform, arch,
48919
49271
  log2(`[lsp] reinstalling ${spec.id}@${tag}: no installed-version metadata recorded`);
48920
49272
  }
48921
49273
  }
48922
- const archiveSha256 = await downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl2, signal).catch((err) => {
49274
+ const hashes = await downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl2, signal).catch((err) => {
48923
49275
  error2(`[lsp] github install ${spec.id} crashed: ${err}`);
48924
49276
  return null;
48925
49277
  });
48926
- if (!archiveSha256) {
49278
+ if (!hashes) {
48927
49279
  return { started: true, reason: "install failed (see plugin log)" };
48928
49280
  }
48929
- writeInstalledMetaIn(ghPackageDir(spec), tag, archiveSha256);
49281
+ writeGithubInstalledMetaIn(ghPackageDir(spec), tag, hashes.binarySha256, hashes.archiveSha256);
48930
49282
  return { started: true };
48931
49283
  });
48932
49284
  if (outcome === null) {
@@ -49056,7 +49408,7 @@ function discoverRelevantGithubServers(projectRoot) {
49056
49408
  }
49057
49409
 
49058
49410
  // src/notifications.ts
49059
- import { existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync7, renameSync as renameSync5, rmSync as rmSync4, writeFileSync as writeFileSync5 } from "node:fs";
49411
+ import { existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync7, renameSync as renameSync6, rmSync as rmSync5, writeFileSync as writeFileSync6 } from "node:fs";
49060
49412
  import { join as join12 } from "node:path";
49061
49413
  var WARNING_MARKER = "\uD83D\uDD27 AFT: ⚠️";
49062
49414
  var FEATURE_MARKER = "\uD83D\uDD27 AFT: ✨";
@@ -49069,7 +49421,7 @@ function sendIgnoredMessage(client, sessionId, text) {
49069
49421
  typedClient.ui.notify(text, "warning");
49070
49422
  return true;
49071
49423
  } catch (err) {
49072
- sessionLog2(sessionId, `[aft-pi] notification send failed: ${err instanceof Error ? err.message : String(err)}`);
49424
+ sessionLog(sessionId, `[aft-pi] notification send failed: ${err instanceof Error ? err.message : String(err)}`);
49073
49425
  return false;
49074
49426
  }
49075
49427
  }
@@ -49097,9 +49449,9 @@ function writeWarnedTools(storageDir, warned) {
49097
49449
  mkdirSync8(storageDir, { recursive: true });
49098
49450
  const warnedToolsPath = join12(storageDir, WARNED_TOOLS_FILE);
49099
49451
  const tmpPath = join12(storageDir, `${WARNED_TOOLS_FILE}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
49100
- writeFileSync5(tmpPath, `${JSON.stringify(warned, null, 2)}
49452
+ writeFileSync6(tmpPath, `${JSON.stringify(warned, null, 2)}
49101
49453
  `);
49102
- renameSync5(tmpPath, warnedToolsPath);
49454
+ renameSync6(tmpPath, warnedToolsPath);
49103
49455
  } catch {}
49104
49456
  }
49105
49457
  async function withWarnedToolsLock(storageDir, fn) {
@@ -49111,7 +49463,7 @@ async function withWarnedToolsLock(storageDir, fn) {
49111
49463
  try {
49112
49464
  return await fn();
49113
49465
  } finally {
49114
- rmSync4(lockDir, { recursive: true, force: true });
49466
+ rmSync5(lockDir, { recursive: true, force: true });
49115
49467
  }
49116
49468
  } catch (err) {
49117
49469
  const code = err.code;
@@ -49198,7 +49550,7 @@ function sendFeatureAnnouncement(version2, features, storageDir) {
49198
49550
  `));
49199
49551
  try {
49200
49552
  mkdirSync8(storageDir, { recursive: true });
49201
- writeFileSync5(versionFile, version2);
49553
+ writeFileSync6(versionFile, version2);
49202
49554
  } catch {}
49203
49555
  }
49204
49556
 
@@ -50099,12 +50451,10 @@ function registerFsTools(pi, ctx, surface) {
50099
50451
  parameters: DeleteParams,
50100
50452
  async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
50101
50453
  const bridge = bridgeFor(ctx, extCtx.cwd);
50102
- const sessionId = resolveSessionId(extCtx);
50103
- const response = await bridge.send("delete_file", {
50454
+ const response = await callBridge(bridge, "delete_file", {
50104
50455
  files: params.files,
50105
- recursive: params.recursive === true,
50106
- ...sessionId ? { session_id: sessionId } : {}
50107
- });
50456
+ recursive: params.recursive === true
50457
+ }, extCtx);
50108
50458
  const deletedEntries = response.deleted ?? [];
50109
50459
  const skipped = response.skipped_files ?? [];
50110
50460
  const deleted = deletedEntries.map((entry) => entry.file);
@@ -51736,7 +52086,7 @@ function buildWorkflowHints(opts) {
51736
52086
  const hasNavigate = opts.toolSurface === "all" && !opts.absentTools.has("aft_navigate");
51737
52087
  const hasBgBash = opts.bashBackgroundEnabled && !opts.absentTools.has(bashName) && !opts.absentTools.has("bash_status");
51738
52088
  if (hasOutline && hasZoom) {
51739
- sections.push(`**Web/URL access**: \`aft_outline({ url })\` first for structure, then \`aft_zoom({ url, symbol: "<heading>" })\` for the specific section.`);
52089
+ sections.push(`**Web/URL access**: \`aft_outline({ target: "<url>" })\` first for structure, then \`aft_zoom({ target: "<url>", symbol: "<heading>" })\` for the specific section.`);
51740
52090
  }
51741
52091
  if (hasOutline && hasZoom && (hasGrep || hasSearch)) {
51742
52092
  const locator = hasGrep ? `\`${grepName}\`` : "`aft_search`";
@@ -51753,7 +52103,7 @@ function buildWorkflowHints(opts) {
51753
52103
  `));
51754
52104
  }
51755
52105
  if (hasBgBash) {
51756
- sections.push(`**Long-running commands** (builds, installs, full test suites): \`${bashName}({ background: true })\` returns immediately with a \`taskId\`. A completion reminder is delivered automatically — do not poll \`bash_status({ taskId })\`. Use \`bash_status\` only after the reminder arrives, or to inspect a task you already know is complete.`);
52106
+ sections.push(`**Long-running commands** (builds, installs, full test suites): \`${bashName}({ background: true })\` returns immediately with a \`task_id\`. A completion reminder is delivered automatically — do not poll \`bash_status({ task_id })\`. Use \`bash_status\` only after the reminder arrives, or to inspect a task you already know is complete.`);
51757
52107
  }
51758
52108
  if (sections.length === 0) {
51759
52109
  return null;
@@ -51802,6 +52152,42 @@ ${hintsBlock}` };
51802
52152
 
51803
52153
  // src/index.ts
51804
52154
  setActiveLogger(bridgeLogger);
52155
+ function createVersionMismatchHandler(getPool, ensureCompatibleBinary = ensureBinary) {
52156
+ const versionUpgradePromises = new Map;
52157
+ return async (binaryVersion, minVersion) => {
52158
+ const existing = versionUpgradePromises.get(minVersion);
52159
+ if (existing) {
52160
+ log2(`Version ${binaryVersion} < ${minVersion}; awaiting in-flight compatible binary upgrade`);
52161
+ return existing;
52162
+ }
52163
+ const upgradePromise = (async () => {
52164
+ warn2(`WARNING: aft binary v${binaryVersion} is older than plugin v${minVersion}. ` + "Some features may not work. Attempting to download a compatible binary...");
52165
+ try {
52166
+ const path2 = await ensureCompatibleBinary(`v${minVersion}`);
52167
+ if (!path2) {
52168
+ warn2(`Could not find or download v${minVersion}. Continuing with v${binaryVersion}.`);
52169
+ return null;
52170
+ }
52171
+ const pool = getPool();
52172
+ if (!pool) {
52173
+ warn2(`Found/downloaded compatible binary at ${path2}, but bridge pool is not ready.`);
52174
+ return null;
52175
+ }
52176
+ log2(`Found/downloaded compatible binary at ${path2}. Replacing running bridges...`);
52177
+ const replaced = await pool.replaceBinary(path2);
52178
+ log2("Binary replaced successfully. New bridges will use the updated binary.");
52179
+ return replaced;
52180
+ } catch (err) {
52181
+ error2(`Auto-download failed: ${err.message}. Install manually: cargo install agent-file-tools@${minVersion}`);
52182
+ return null;
52183
+ } finally {
52184
+ versionUpgradePromises.delete(minVersion);
52185
+ }
52186
+ })();
52187
+ versionUpgradePromises.set(minVersion, upgradePromise);
52188
+ return upgradePromise;
52189
+ };
52190
+ }
51805
52191
  var PLUGIN_VERSION = (() => {
51806
52192
  try {
51807
52193
  const req = createRequire3(import.meta.url);
@@ -52043,9 +52429,11 @@ ${lines}
52043
52429
  } catch (err) {
52044
52430
  warn2(`[lsp] auto-install setup failed: ${err instanceof Error ? err.message : String(err)}`);
52045
52431
  }
52432
+ let pool;
52046
52433
  const poolOptions = {
52047
52434
  errorPrefix: "[aft-pi]",
52048
52435
  minVersion: PLUGIN_VERSION,
52436
+ onVersionMismatch: createVersionMismatchHandler(() => pool),
52049
52437
  onConfigureWarnings: async ({ projectRoot, sessionId, client, warnings }) => {
52050
52438
  const pendingWarnings = sessionId ? drainPendingEagerWarnings(projectRoot) : [];
52051
52439
  await handleConfigureWarningsForSession({
@@ -52074,7 +52462,7 @@ ${lines}
52074
52462
  }, reminder);
52075
52463
  }
52076
52464
  };
52077
- const pool = new BridgePool(binaryPath, poolOptions, configOverrides);
52465
+ pool = new BridgePool(binaryPath, poolOptions, configOverrides);
52078
52466
  const ctx = { pool, config: config2, storageDir };
52079
52467
  if (onnxRuntimePromise) {
52080
52468
  onnxRuntimePromise.then((ortDylibDir) => {
@@ -52190,7 +52578,8 @@ ${lines}
52190
52578
  var __test__2 = {
52191
52579
  resolveToolSurface,
52192
52580
  handleConfigureWarningsForSession,
52193
- shouldPrepareOnnxRuntime
52581
+ shouldPrepareOnnxRuntime,
52582
+ createVersionMismatchHandler
52194
52583
  };
52195
52584
  export {
52196
52585
  src_default as default,