@cortexkit/aft-pi 0.53.0 → 0.55.0

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
@@ -7711,9 +7711,9 @@ var require_stringify = __commonJS(function(exports, module) {
7711
7711
  value
7712
7712
  } = comment;
7713
7713
  const line_breaks_before = blank_lines > 0 ? blank_lines + (inline ? 0 : 1) : null;
7714
- const delimiter2 = line_breaks_before > 0 ? repeat_line_breaks(line_breaks_before, deeper_gap) : inline ? SPACE : i === 0 && symbol_tag === PREFIX_BEFORE_ALL && deeper_gap === EMPTY ? EMPTY : LF + deeper_gap;
7714
+ const delimiter3 = line_breaks_before > 0 ? repeat_line_breaks(line_breaks_before, deeper_gap) : inline ? SPACE : i === 0 && symbol_tag === PREFIX_BEFORE_ALL && deeper_gap === EMPTY ? EMPTY : LF + deeper_gap;
7715
7715
  const is_line_comment = type === "LineComment";
7716
- str += delimiter2 + comment_stringify(value, is_line_comment);
7716
+ str += delimiter3 + comment_stringify(value, is_line_comment);
7717
7717
  blank_lines = 0;
7718
7718
  last_comment = comment;
7719
7719
  });
@@ -7730,10 +7730,10 @@ var require_stringify = __commonJS(function(exports, module) {
7730
7730
  replacer = null;
7731
7731
  indent = EMPTY;
7732
7732
  };
7733
- var join12 = (one, two, gap) => one ? two ? one + two.trim() + LF + gap : one.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(one)), gap) : two ? two.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(two)), gap) : EMPTY;
7733
+ var join13 = (one, two, gap) => one ? two ? one + two.trim() + LF + gap : one.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(one)), gap) : two ? two.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(two)), gap) : EMPTY;
7734
7734
  var join_content = (inside, value, gap) => {
7735
7735
  const comment = process_comments(value, PREFIX_BEFORE, gap + indent, true);
7736
- return join12(comment, inside, gap);
7736
+ return join13(comment, inside, gap);
7737
7737
  };
7738
7738
  var stringify_string = (holder, key, value) => {
7739
7739
  const raw = get_raw_string_literal(holder, key);
@@ -7755,13 +7755,13 @@ var require_stringify = __commonJS(function(exports, module) {
7755
7755
  if (i !== 0) {
7756
7756
  inside += COMMA;
7757
7757
  }
7758
- const before = join12(after_comma, process_comments(value, BEFORE(i), deeper_gap), deeper_gap);
7758
+ const before = join13(after_comma, process_comments(value, BEFORE(i), deeper_gap), deeper_gap);
7759
7759
  inside += before || LF + deeper_gap;
7760
7760
  inside += stringify(i, value, deeper_gap) || STR_NULL;
7761
7761
  inside += process_comments(value, AFTER_VALUE(i), deeper_gap);
7762
7762
  after_comma = process_comments(value, AFTER(i), deeper_gap);
7763
7763
  }
7764
- inside += join12(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
7764
+ inside += join13(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
7765
7765
  return BRACKET_OPEN + join_content(inside, value, gap) + BRACKET_CLOSE;
7766
7766
  };
7767
7767
  var object_stringify = (value, gap) => {
@@ -7782,13 +7782,13 @@ var require_stringify = __commonJS(function(exports, module) {
7782
7782
  inside += COMMA;
7783
7783
  }
7784
7784
  first = false;
7785
- const before = join12(after_comma, process_comments(value, BEFORE(key), deeper_gap), deeper_gap);
7785
+ const before = join13(after_comma, process_comments(value, BEFORE(key), deeper_gap), deeper_gap);
7786
7786
  inside += before || LF + deeper_gap;
7787
7787
  inside += quote(key) + process_comments(value, AFTER_PROP(key), deeper_gap) + COLON + process_comments(value, AFTER_COLON(key), deeper_gap) + SPACE + sv + process_comments(value, AFTER_VALUE(key), deeper_gap);
7788
7788
  after_comma = process_comments(value, AFTER(key), deeper_gap);
7789
7789
  };
7790
7790
  keys.forEach(iteratee);
7791
- inside += join12(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
7791
+ inside += join13(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
7792
7792
  return CURLY_BRACKET_OPEN + join_content(inside, value, gap) + CURLY_BRACKET_CLOSE;
7793
7793
  };
7794
7794
  function stringify(key, holder, gap) {
@@ -8329,10 +8329,68 @@ function skipSpaces(input, start) {
8329
8329
  }
8330
8330
  // ../aft-bridge/dist/bash-host-fallback.js
8331
8331
  import { spawn } from "node:child_process";
8332
+ import { existsSync } from "node:fs";
8333
+ import { delimiter, join as join3 } from "node:path";
8334
+
8335
+ // ../aft-bridge/dist/storage-paths.js
8336
+ import { homedir as homedir2 } from "node:os";
8337
+ import { join as join2, resolve as resolve2 } from "node:path";
8338
+ function homeDir() {
8339
+ if (process.platform === "win32")
8340
+ return process.env.USERPROFILE || process.env.HOME || homedir2();
8341
+ return process.env.HOME || homedir2();
8342
+ }
8343
+ function dataHome() {
8344
+ const xdg = process.env.XDG_DATA_HOME;
8345
+ if (xdg)
8346
+ return xdg;
8347
+ if (process.platform === "win32") {
8348
+ return process.env.LOCALAPPDATA || process.env.APPDATA || join2(homeDir(), "AppData", "Local");
8349
+ }
8350
+ return join2(homeDir(), ".local", "share");
8351
+ }
8352
+ function resolveStoragePath(raw) {
8353
+ let expanded = raw;
8354
+ if (raw === "~") {
8355
+ expanded = homeDir();
8356
+ } else if (raw.startsWith("~/") || raw.startsWith("~\\")) {
8357
+ expanded = join2(homeDir(), raw.slice(2));
8358
+ }
8359
+ return resolve2(expanded);
8360
+ }
8361
+ function resolveCortexKitStorageRoot() {
8362
+ const override = process.env.AFT_STORAGE_DIR;
8363
+ if (override)
8364
+ return resolveStoragePath(override);
8365
+ return resolveStoragePath(join2(dataHome(), "cortexkit", "aft"));
8366
+ }
8367
+ function resolveAftStorageRoot(configuredRoot) {
8368
+ if (process.env.AFT_STORAGE_DIR)
8369
+ return resolveCortexKitStorageRoot();
8370
+ if (configuredRoot)
8371
+ return configuredRoot;
8372
+ return resolveCortexKitStorageRoot();
8373
+ }
8374
+ function resolveAftLogPath(filename, configuredRoot) {
8375
+ return join2(resolveAftStorageRoot(configuredRoot), "logs", filename);
8376
+ }
8377
+
8378
+ // ../aft-bridge/dist/bash-host-fallback.js
8332
8379
  var BASH_HOST_FALLBACK_BANNER = "[AFT host fallback - module transport down; no rewrites/compression/background]";
8333
8380
  var BASH_HOST_FALLBACK_MAX_OUTPUT_BYTES = 100 * 1024;
8334
8381
  var BASH_HOST_FALLBACK_MAX_TIMEOUT_MS = 10 * 60 * 1000;
8335
8382
  var BASH_HOST_FALLBACK_REFUSAL = "AFT transport is down; only foreground execution is available in host fallback";
8383
+ function hostFallbackPathWithShims(env) {
8384
+ const storageRoot = env.AFT_STORAGE_DIR ? resolveStoragePath(env.AFT_STORAGE_DIR) : resolveCortexKitStorageRoot();
8385
+ const shimsDir = join3(storageRoot, "shims");
8386
+ if (!existsSync(join3(shimsDir, "gh")))
8387
+ return env.PATH;
8388
+ const inherited = env.PATH ?? "";
8389
+ const entries = inherited.split(delimiter).filter((entry) => entry.length > 0);
8390
+ if (entries[0] === shimsDir)
8391
+ return inherited;
8392
+ return [shimsDir, ...entries.filter((entry) => entry !== shimsDir)].join(delimiter);
8393
+ }
8336
8394
  function bashHostFallbackAskPattern(command, cwd) {
8337
8395
  return `AFT UNAVAILABLE - host fallback execution:
8338
8396
 
@@ -8365,11 +8423,17 @@ async function runBashHostFallback(options) {
8365
8423
  if (options.signal?.aborted) {
8366
8424
  throw new DOMException("The host fallback command was aborted", "AbortError");
8367
8425
  }
8368
- return await new Promise((resolve2, reject) => {
8426
+ return await new Promise((resolve3, reject) => {
8369
8427
  const child = spawn(options.command, {
8370
8428
  cwd: options.projectRoot,
8371
8429
  shell: true,
8372
- env: { ...process.env, ...options.env },
8430
+ env: (() => {
8431
+ const merged = { ...process.env, ...options.env };
8432
+ const path2 = hostFallbackPathWithShims(merged);
8433
+ if (path2 !== undefined)
8434
+ merged.PATH = path2;
8435
+ return merged;
8436
+ })(),
8373
8437
  stdio: ["ignore", "pipe", "pipe"],
8374
8438
  detached: process.platform !== "win32",
8375
8439
  windowsHide: true
@@ -8432,7 +8496,7 @@ async function runBashHostFallback(options) {
8432
8496
  return;
8433
8497
  }
8434
8498
  const exitCode = timedOut ? 124 : code ?? 1;
8435
- resolve2({
8499
+ resolve3({
8436
8500
  success: true,
8437
8501
  output: renderOutput(Buffer.concat(chunks), exitCode),
8438
8502
  exit_code: exitCode,
@@ -8452,8 +8516,8 @@ function resolveBashKillTimeout(modelTimeout, foregroundWaitMs) {
8452
8516
  import { spawn as spawn2 } from "node:child_process";
8453
8517
  import { createHash } from "node:crypto";
8454
8518
  import { readFileSync } from "node:fs";
8455
- import { homedir as homedir2 } from "node:os";
8456
- import { join as join2 } from "node:path";
8519
+ import { homedir as homedir3 } from "node:os";
8520
+ import { join as join4 } from "node:path";
8457
8521
  import { StringDecoder } from "node:string_decoder";
8458
8522
 
8459
8523
  // ../aft-bridge/dist/command-timeouts.js
@@ -8859,6 +8923,21 @@ class BinaryBridge {
8859
8923
  }
8860
8924
  return this.sendWithVersionMismatchRetry(command, dispatchParams, options, true);
8861
8925
  }
8926
+ listenForRequestAbort(requestId, signal) {
8927
+ if (!signal)
8928
+ return () => {};
8929
+ let fired = false;
8930
+ const onAbort = () => {
8931
+ if (fired)
8932
+ return;
8933
+ fired = true;
8934
+ this.send("cancel_request", { id: requestId }, { keepBridgeOnTimeout: true }).catch(() => {});
8935
+ };
8936
+ signal.addEventListener("abort", onAbort, { once: true });
8937
+ if (signal.aborted)
8938
+ onAbort();
8939
+ return () => signal.removeEventListener("abort", onAbort);
8940
+ }
8862
8941
  async toolCall(sessionId, name, rawArgs = {}, options) {
8863
8942
  const params = { name, arguments: rawArgs };
8864
8943
  if (sessionId)
@@ -8881,7 +8960,7 @@ class BinaryBridge {
8881
8960
  if (this._shuttingDown) {
8882
8961
  throw new BridgeTransportUnavailableError(`${this.errorPrefix} Bridge is shutting down, cannot send "${command}"`);
8883
8962
  }
8884
- if (Object.hasOwn(params, "id")) {
8963
+ if (Object.hasOwn(params, "id") && command !== "cancel_request") {
8885
8964
  throw new Error("params cannot contain reserved key 'id'");
8886
8965
  }
8887
8966
  const requestSessionId = typeof params.session_id === "string" && params.session_id.length > 0 ? params.session_id : undefined;
@@ -8935,7 +9014,7 @@ class BinaryBridge {
8935
9014
  }
8936
9015
  const id = String(this.nextId++);
8937
9016
  let request;
8938
- if (Object.hasOwn(params, "command") || Object.hasOwn(params, "method")) {
9017
+ if (command === "cancel_request" || Object.hasOwn(params, "command") || Object.hasOwn(params, "method")) {
8939
9018
  const nested = { ...params };
8940
9019
  const reserved = {};
8941
9020
  for (const key of ["session_id", "lsp_hints"]) {
@@ -8953,13 +9032,14 @@ class BinaryBridge {
8953
9032
  const keepBridgeOnTimeout = passive || options?.keepBridgeOnTimeout === true;
8954
9033
  let requestSentAt = Date.now();
8955
9034
  const child = this.process;
8956
- const response = await new Promise((resolve2, reject) => {
9035
+ const response = await new Promise((resolve3, reject) => {
8957
9036
  const timer = setTimeout(() => {
8958
- const entry = this.pending.get(id);
8959
- if (!entry)
9037
+ const entry2 = this.pending.get(id);
9038
+ if (!entry2)
8960
9039
  return;
8961
9040
  this.pending.delete(id);
8962
- clearTimeout(entry.timer);
9041
+ clearTimeout(entry2.timer);
9042
+ entry2.onSettled?.();
8963
9043
  if (keepBridgeOnTimeout) {
8964
9044
  const timeoutMsg2 = `Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`;
8965
9045
  if (requestSessionId) {
@@ -8967,7 +9047,7 @@ class BinaryBridge {
8967
9047
  } else {
8968
9048
  this.warnVia(timeoutMsg2);
8969
9049
  }
8970
- entry.reject(new BridgeTransportTimeoutError(command, effectiveTimeoutMs, `${this.errorPrefix} Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
9050
+ entry2.reject(new BridgeTransportTimeoutError(command, effectiveTimeoutMs, `${this.errorPrefix} Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
8971
9051
  return;
8972
9052
  }
8973
9053
  const childActiveSinceRequest = this.lastChildActivityAt > requestSentAt;
@@ -8982,13 +9062,20 @@ class BinaryBridge {
8982
9062
  this.warnVia(timeoutMsg);
8983
9063
  }
8984
9064
  if (keepWarm) {
8985
- entry.reject(new Error(`${this.errorPrefix} request "${command}" timed out after ${effectiveTimeoutMs}ms (bridge busy/under load); bridge kept warm — retry`));
9065
+ entry2.reject(new Error(`${this.errorPrefix} request "${command}" timed out after ${effectiveTimeoutMs}ms (bridge busy/under load); bridge kept warm — retry`));
8986
9066
  return;
8987
9067
  }
8988
- entry.reject(new Error(`${this.errorPrefix} Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
9068
+ entry2.reject(new Error(`${this.errorPrefix} Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
8989
9069
  this.handleTimeout(requestSessionId);
8990
9070
  }, effectiveTimeoutMs);
8991
- this.pending.set(id, { resolve: resolve2, reject, timer, onProgress: options?.onProgress, command });
9071
+ const entry = {
9072
+ resolve: resolve3,
9073
+ reject,
9074
+ timer,
9075
+ onProgress: options?.onProgress,
9076
+ command
9077
+ };
9078
+ this.pending.set(id, entry);
8992
9079
  if (!child?.stdin?.writable) {
8993
9080
  this.pending.delete(id);
8994
9081
  clearTimeout(timer);
@@ -9002,11 +9089,12 @@ class BinaryBridge {
9002
9089
  const handleWriteFailure = (cause) => {
9003
9090
  const writeError = cause instanceof Error ? cause : new Error(String(cause));
9004
9091
  const error2 = new BridgeTransportUnknownOutcomeError(`${this.errorPrefix} Failed to write to stdin: ${writeError.message}`, { cause: writeError });
9005
- const entry = this.pending.get(id);
9006
- if (entry) {
9092
+ const entry2 = this.pending.get(id);
9093
+ if (entry2) {
9007
9094
  this.pending.delete(id);
9008
- clearTimeout(entry.timer);
9009
- entry.reject(error2);
9095
+ clearTimeout(entry2.timer);
9096
+ entry2.onSettled?.();
9097
+ entry2.reject(error2);
9010
9098
  }
9011
9099
  if (this.process === child)
9012
9100
  this.invalidateTransportProcess(error2);
@@ -9019,6 +9107,9 @@ class BinaryBridge {
9019
9107
  } catch (err) {
9020
9108
  handleWriteFailure(err);
9021
9109
  }
9110
+ if (this.pending.has(id)) {
9111
+ entry.onSettled = this.listenForRequestAbort(id, options?.abortSignal);
9112
+ }
9022
9113
  });
9023
9114
  if (command === "configure" && response.success === true && options?.markConfiguredOnSuccess !== false) {
9024
9115
  this.configured = true;
@@ -9106,15 +9197,15 @@ class BinaryBridge {
9106
9197
  if (this.process) {
9107
9198
  const proc = this.process;
9108
9199
  this.process = null;
9109
- return new Promise((resolve2) => {
9200
+ return new Promise((resolve3) => {
9110
9201
  const forceKillTimer = setTimeout(() => {
9111
9202
  proc.kill("SIGKILL");
9112
- resolve2();
9203
+ resolve3();
9113
9204
  }, 5000);
9114
9205
  proc.once("exit", () => {
9115
9206
  clearTimeout(forceKillTimer);
9116
9207
  this.logVia("Process exited during shutdown");
9117
- resolve2();
9208
+ resolve3();
9118
9209
  });
9119
9210
  proc.kill("SIGTERM");
9120
9211
  });
@@ -9162,15 +9253,15 @@ class BinaryBridge {
9162
9253
  return;
9163
9254
  const proc = this.process;
9164
9255
  this.process = null;
9165
- await new Promise((resolve2) => {
9256
+ await new Promise((resolve3) => {
9166
9257
  const forceKillTimer = setTimeout(() => {
9167
9258
  proc.kill("SIGKILL");
9168
- resolve2();
9259
+ resolve3();
9169
9260
  }, 5000);
9170
9261
  proc.once("exit", () => {
9171
9262
  clearTimeout(forceKillTimer);
9172
9263
  this.logVia("Process exited during coordinated binary replacement");
9173
- resolve2();
9264
+ resolve3();
9174
9265
  });
9175
9266
  proc.kill("SIGTERM");
9176
9267
  });
@@ -9200,7 +9291,7 @@ class BinaryBridge {
9200
9291
  })();
9201
9292
  const useFastembedBackend = semanticBackend === undefined || semanticBackend === "fastembed" || semanticBackend === "";
9202
9293
  const ortDir = typeof this.configOverrides._ort_dylib_dir === "string" && useFastembedBackend ? this.configOverrides._ort_dylib_dir : null;
9203
- const ortLibraryPath = ortDir == null ? null : join2(ortDir, process.platform === "win32" ? "onnxruntime.dll" : process.platform === "darwin" ? "libonnxruntime.dylib" : "libonnxruntime.so");
9294
+ const ortLibraryPath = ortDir == null ? null : join4(ortDir, process.platform === "win32" ? "onnxruntime.dll" : process.platform === "darwin" ? "libonnxruntime.dylib" : "libonnxruntime.so");
9204
9295
  const envPath = process.platform === "win32" && ortDir ? `${ortDir};${process.env.PATH ?? ""}` : process.env.PATH;
9205
9296
  const env = {
9206
9297
  ...process.env,
@@ -9208,7 +9299,7 @@ class BinaryBridge {
9208
9299
  };
9209
9300
  this.logVia(`bridge.spawnProcess: useFastembedBackend=${useFastembedBackend}, ` + `parentORT=${process.env.ORT_DYLIB_PATH ?? "(unset)"}, ` + `ortLibraryPath=${ortLibraryPath ?? "(none)"}`);
9210
9301
  if (useFastembedBackend) {
9211
- env.FASTEMBED_CACHE_DIR = process.env.FASTEMBED_CACHE_DIR || (typeof this.configOverrides.storage_dir === "string" ? join2(this.configOverrides.storage_dir, "semantic", "models") : join2(homedir2() || "", ".cache", "fastembed"));
9302
+ env.FASTEMBED_CACHE_DIR = process.env.FASTEMBED_CACHE_DIR || (typeof this.configOverrides.storage_dir === "string" ? join4(this.configOverrides.storage_dir, "semantic", "models") : join4(homedir3() || "", ".cache", "fastembed"));
9212
9303
  if (process.env.ORT_DYLIB_PATH) {
9213
9304
  this.logVia(`ORT_DYLIB_PATH inherited from parent env: ${process.env.ORT_DYLIB_PATH}`);
9214
9305
  } else if (ortLibraryPath) {
@@ -9387,6 +9478,7 @@ class BinaryBridge {
9387
9478
  if (requestId && entry) {
9388
9479
  this.pending.delete(requestId);
9389
9480
  clearTimeout(entry.timer);
9481
+ entry.onSettled?.();
9390
9482
  entry.resolve({
9391
9483
  success: false,
9392
9484
  code: "permission_required",
@@ -9428,6 +9520,7 @@ class BinaryBridge {
9428
9520
  return;
9429
9521
  this.pending.delete(id);
9430
9522
  clearTimeout(entry.timer);
9523
+ entry.onSettled?.();
9431
9524
  this.consecutiveRequestTimeouts = 0;
9432
9525
  this.scheduleRestartCountReset();
9433
9526
  this.accountForBashTaskResponse(entry.command, response);
@@ -9518,6 +9611,7 @@ class BinaryBridge {
9518
9611
  rejectAllPending(error2) {
9519
9612
  for (const [_id, entry] of this.pending) {
9520
9613
  clearTimeout(entry.timer);
9614
+ entry.onSettled?.();
9521
9615
  entry.reject(error2);
9522
9616
  }
9523
9617
  this.pending.clear();
@@ -9537,32 +9631,32 @@ class BinaryBridge {
9537
9631
  }
9538
9632
  }
9539
9633
  // ../aft-bridge/dist/cache-paths.js
9540
- import { homedir as homedir3 } from "node:os";
9541
- import { join as join3 } from "node:path";
9542
- function homeDir(env) {
9543
- return (process.platform === "win32" ? env.USERPROFILE || env.HOME : env.HOME) || homedir3();
9634
+ import { homedir as homedir4 } from "node:os";
9635
+ import { join as join5 } from "node:path";
9636
+ function homeDir2(env) {
9637
+ return (process.platform === "win32" ? env.USERPROFILE || env.HOME : env.HOME) || homedir4();
9544
9638
  }
9545
9639
  function getAftCacheRoot(env = process.env) {
9546
9640
  if (env.AFT_CACHE_DIR)
9547
9641
  return env.AFT_CACHE_DIR;
9548
9642
  if (process.platform === "win32") {
9549
- const base2 = env.LOCALAPPDATA || env.APPDATA || join3(homeDir(env), "AppData", "Local");
9550
- return join3(base2, "aft");
9643
+ const base2 = env.LOCALAPPDATA || env.APPDATA || join5(homeDir2(env), "AppData", "Local");
9644
+ return join5(base2, "aft");
9551
9645
  }
9552
- const base = env.XDG_CACHE_HOME || join3(homeDir(env), ".cache");
9553
- return join3(base, "aft");
9646
+ const base = env.XDG_CACHE_HOME || join5(homeDir2(env), ".cache");
9647
+ return join5(base, "aft");
9554
9648
  }
9555
9649
  function getAftBinaryCacheDir(env = process.env) {
9556
- return join3(getAftCacheRoot(env), "bin");
9650
+ return join5(getAftCacheRoot(env), "bin");
9557
9651
  }
9558
9652
  function getAftLspPackagesDir(env = process.env) {
9559
- return join3(getAftCacheRoot(env), "lsp-packages");
9653
+ return join5(getAftCacheRoot(env), "lsp-packages");
9560
9654
  }
9561
9655
  function getAftLspBinariesDir(env = process.env) {
9562
- return join3(getAftCacheRoot(env), "lsp-binaries");
9656
+ return join5(getAftCacheRoot(env), "lsp-binaries");
9563
9657
  }
9564
9658
  // ../aft-bridge/dist/callgraph-format.js
9565
- import { homedir as homedir4 } from "node:os";
9659
+ import { homedir as homedir5 } from "node:os";
9566
9660
  var UNRESOLVED_SUMMARY_NAME_LIMIT = 10;
9567
9661
  var PLAIN_CALLGRAPH_THEME = {
9568
9662
  fg: (_role, text) => text
@@ -9585,7 +9679,7 @@ function asBoolean(value) {
9585
9679
  return typeof value === "boolean" ? value : undefined;
9586
9680
  }
9587
9681
  function shortenPath(path2) {
9588
- const home = homedir4();
9682
+ const home = homedir5();
9589
9683
  if (path2.startsWith(home))
9590
9684
  return `~${path2.slice(home.length)}`;
9591
9685
  return path2;
@@ -9932,26 +10026,26 @@ function stripHarnessSpecificConfigKeys(value, keys) {
9932
10026
  return stripped;
9933
10027
  }
9934
10028
  // ../aft-bridge/dist/config-tiers.js
9935
- import { existsSync, readFileSync as readFileSync2 } from "node:fs";
9936
- import { resolve as resolve2 } from "node:path";
10029
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
10030
+ import { resolve as resolve3 } from "node:path";
9937
10031
  function readConfigTiers(opts) {
9938
10032
  const tiers = [];
9939
10033
  try {
9940
- if (existsSync(opts.userConfigPath)) {
10034
+ if (existsSync2(opts.userConfigPath)) {
9941
10035
  const doc = readFileSync2(opts.userConfigPath, "utf-8");
9942
10036
  tiers.push({
9943
10037
  tier: "user",
9944
- source: resolve2(opts.userConfigPath),
10038
+ source: resolve3(opts.userConfigPath),
9945
10039
  doc
9946
10040
  });
9947
10041
  }
9948
10042
  } catch {}
9949
10043
  try {
9950
- if (existsSync(opts.projectConfigPath)) {
10044
+ if (existsSync2(opts.projectConfigPath)) {
9951
10045
  const doc = readFileSync2(opts.projectConfigPath, "utf-8");
9952
10046
  tiers.push({
9953
10047
  tier: "project",
9954
- source: resolve2(opts.projectConfigPath),
10048
+ source: resolve3(opts.projectConfigPath),
9955
10049
  doc
9956
10050
  });
9957
10051
  }
@@ -9967,9 +10061,9 @@ function formatDroppedKeyWarnings(dropped) {
9967
10061
  // ../aft-bridge/dist/downloader.js
9968
10062
  import { spawnSync } from "node:child_process";
9969
10063
  import { createHash as createHash2, randomUUID } from "node:crypto";
9970
- import { chmodSync, closeSync, copyFileSync, createWriteStream, existsSync as existsSync2, mkdirSync, openSync, readdirSync, readFileSync as readFileSync3, renameSync, rmSync, statSync as statSync2, unlinkSync, writeSync } from "node:fs";
10064
+ import { chmodSync, closeSync, copyFileSync, createWriteStream, existsSync as existsSync3, mkdirSync, openSync, readdirSync, readFileSync as readFileSync3, renameSync, rmSync, statSync as statSync2, unlinkSync, writeSync } from "node:fs";
9971
10065
  import { hostname } from "node:os";
9972
- import { join as join4 } from "node:path";
10066
+ import { join as join6 } from "node:path";
9973
10067
  import { Readable } from "node:stream";
9974
10068
  import { pipeline } from "node:stream/promises";
9975
10069
 
@@ -10029,8 +10123,8 @@ function getBinaryName() {
10029
10123
  function getCachedBinaryPath(version) {
10030
10124
  if (!version)
10031
10125
  return null;
10032
- const binaryPath = join4(getAftBinaryCacheDir(), version, getBinaryName());
10033
- return existsSync2(binaryPath) ? binaryPath : null;
10126
+ const binaryPath = join6(getAftBinaryCacheDir(), version, getBinaryName());
10127
+ return existsSync3(binaryPath) ? binaryPath : null;
10034
10128
  }
10035
10129
  async function downloadBinary(version) {
10036
10130
  const archMap = PLATFORM_ARCH_MAP[process.platform] ?? {};
@@ -10046,16 +10140,16 @@ async function downloadBinary(version) {
10046
10140
  return null;
10047
10141
  }
10048
10142
  const tag = rawTag.startsWith("v") ? rawTag : `v${rawTag}`;
10049
- const versionedCacheDir = join4(getAftBinaryCacheDir(), tag);
10143
+ const versionedCacheDir = join6(getAftBinaryCacheDir(), tag);
10050
10144
  const binaryName = getBinaryName();
10051
- const binaryPath = join4(versionedCacheDir, binaryName);
10052
- if (existsSync2(binaryPath) && isExpectedCachedBinary(binaryPath, tag)) {
10145
+ const binaryPath = join6(versionedCacheDir, binaryName);
10146
+ if (existsSync3(binaryPath) && isExpectedCachedBinary(binaryPath, tag)) {
10053
10147
  return binaryPath;
10054
10148
  }
10055
10149
  const downloadUrl = `https://github.com/${REPO}/releases/download/${tag}/${assetName}`;
10056
10150
  const checksumUrl = `https://github.com/${REPO}/releases/download/${tag}/checksums.sha256`;
10057
10151
  log(`Downloading AFT binary (${tag}) for ${platformKey}...`);
10058
- const lockPath = join4(versionedCacheDir, ".download.lock");
10152
+ const lockPath = join6(versionedCacheDir, ".download.lock");
10059
10153
  let releaseLock = null;
10060
10154
  let binaryController = null;
10061
10155
  let checksumController = null;
@@ -10064,7 +10158,7 @@ async function downloadBinary(version) {
10064
10158
  const tmpPath = `${binaryPath}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`;
10065
10159
  const cleanUpPartialDownload = () => {
10066
10160
  try {
10067
- if (existsSync2(tmpPath))
10161
+ if (existsSync3(tmpPath))
10068
10162
  unlinkSync(tmpPath);
10069
10163
  } catch {}
10070
10164
  };
@@ -10088,12 +10182,12 @@ async function downloadBinary(version) {
10088
10182
  process.once("SIGINT", handleSigint);
10089
10183
  process.once("exit", handleExit);
10090
10184
  try {
10091
- if (!existsSync2(versionedCacheDir)) {
10185
+ if (!existsSync3(versionedCacheDir)) {
10092
10186
  mkdirSync(versionedCacheDir, { recursive: true });
10093
10187
  }
10094
10188
  releaseLock = await acquireDownloadLock(lockPath);
10095
10189
  sweepStaleDownloadTemps(versionedCacheDir, binaryName);
10096
- if (existsSync2(binaryPath) && isExpectedCachedBinary(binaryPath, tag)) {
10190
+ if (existsSync3(binaryPath) && isExpectedCachedBinary(binaryPath, tag)) {
10097
10191
  return binaryPath;
10098
10192
  }
10099
10193
  binaryController = new AbortController;
@@ -10158,7 +10252,7 @@ async function downloadBinary(version) {
10158
10252
  renameSync(tmpPath, binaryPath);
10159
10253
  }
10160
10254
  try {
10161
- if (existsSync2(tmpPath))
10255
+ if (existsSync3(tmpPath))
10162
10256
  unlinkSync(tmpPath);
10163
10257
  } catch {
10164
10258
  warn(`Could not clean up temporary download file ${tmpPath} — it can be removed manually.`);
@@ -10274,7 +10368,7 @@ function sweepStaleDownloadTemps(versionedCacheDir, binaryName) {
10274
10368
  if (!entry.isFile() || !entry.name.startsWith(tempPrefix) || !entry.name.endsWith(".tmp")) {
10275
10369
  continue;
10276
10370
  }
10277
- const tempPath = join4(versionedCacheDir, entry.name);
10371
+ const tempPath = join6(versionedCacheDir, entry.name);
10278
10372
  const ageMs = Date.now() - statSync2(tempPath).mtimeMs;
10279
10373
  if (Math.abs(ageMs) > DOWNLOAD_LOCK_STALE_MS)
10280
10374
  unlinkSync(tempPath);
@@ -10323,7 +10417,7 @@ async function acquireDownloadLock(lockPath, timing = {}) {
10323
10417
  if (Date.now() - startedAt > timeoutMs) {
10324
10418
  throw new Error(`Timed out waiting for download lock: ${lockPath}`);
10325
10419
  }
10326
- await new Promise((resolve3) => setTimeout(resolve3, pollIntervalMs));
10420
+ await new Promise((resolve4) => setTimeout(resolve4, pollIntervalMs));
10327
10421
  }
10328
10422
  }
10329
10423
  }
@@ -10361,51 +10455,6 @@ async function fetchLatestTag() {
10361
10455
  // ../aft-bridge/dist/durable-log.js
10362
10456
  import { appendFile, mkdir, rename, rm, stat } from "node:fs/promises";
10363
10457
  import { dirname } from "node:path";
10364
-
10365
- // ../aft-bridge/dist/storage-paths.js
10366
- import { homedir as homedir5 } from "node:os";
10367
- import { join as join5, resolve as resolve3 } from "node:path";
10368
- function homeDir2() {
10369
- if (process.platform === "win32")
10370
- return process.env.USERPROFILE || process.env.HOME || homedir5();
10371
- return process.env.HOME || homedir5();
10372
- }
10373
- function dataHome() {
10374
- const xdg = process.env.XDG_DATA_HOME;
10375
- if (xdg)
10376
- return xdg;
10377
- if (process.platform === "win32") {
10378
- return process.env.LOCALAPPDATA || process.env.APPDATA || join5(homeDir2(), "AppData", "Local");
10379
- }
10380
- return join5(homeDir2(), ".local", "share");
10381
- }
10382
- function resolveStoragePath(raw) {
10383
- let expanded = raw;
10384
- if (raw === "~") {
10385
- expanded = homeDir2();
10386
- } else if (raw.startsWith("~/") || raw.startsWith("~\\")) {
10387
- expanded = join5(homeDir2(), raw.slice(2));
10388
- }
10389
- return resolve3(expanded);
10390
- }
10391
- function resolveCortexKitStorageRoot() {
10392
- const override = process.env.AFT_STORAGE_DIR;
10393
- if (override)
10394
- return resolveStoragePath(override);
10395
- return resolveStoragePath(join5(dataHome(), "cortexkit", "aft"));
10396
- }
10397
- function resolveAftStorageRoot(configuredRoot) {
10398
- if (process.env.AFT_STORAGE_DIR)
10399
- return resolveCortexKitStorageRoot();
10400
- if (configuredRoot)
10401
- return configuredRoot;
10402
- return resolveCortexKitStorageRoot();
10403
- }
10404
- function resolveAftLogPath(filename, configuredRoot) {
10405
- return join5(resolveAftStorageRoot(configuredRoot), "logs", filename);
10406
- }
10407
-
10408
- // ../aft-bridge/dist/durable-log.js
10409
10458
  var DEFAULT_LOG_BYTES = 32 * 1024 * 1024;
10410
10459
  var DEFAULT_LOG_GENERATIONS = 1;
10411
10460
  class RotatingLogSink {
@@ -12040,7 +12089,7 @@ function pendingKey(handle, corr) {
12040
12089
  return handle ? `${handle.channel}:${handle.epoch}:${corr}` : `0:0:${corr}`;
12041
12090
  }
12042
12091
  // ../aft-bridge/dist/subc-transport.js
12043
- import { existsSync as existsSync3, statSync as statSync3 } from "node:fs";
12092
+ import { existsSync as existsSync4, statSync as statSync3 } from "node:fs";
12044
12093
 
12045
12094
  // ../aft-bridge/dist/lifecycle-registry.js
12046
12095
  import { lstat } from "node:fs/promises";
@@ -13049,7 +13098,7 @@ class SubcTransportPool {
13049
13098
  this.dormantRoots.delete(root);
13050
13099
  return true;
13051
13100
  }
13052
- const reclaimedMarkerPresent = existsSync3(`${root}.reclaimed`);
13101
+ const reclaimedMarkerPresent = existsSync4(`${root}.reclaimed`);
13053
13102
  if (reclaimedMarkerPresent) {
13054
13103
  this.markRootDormant(root);
13055
13104
  return false;
@@ -13881,14 +13930,14 @@ function stripJsoncSymbols(value) {
13881
13930
  }
13882
13931
  // ../aft-bridge/dist/migration.js
13883
13932
  import { spawnSync as spawnSync2 } from "node:child_process";
13884
- import { closeSync as closeSync3, existsSync as existsSync6, mkdirSync as mkdirSync4, openSync as openSync3, readFileSync as readFileSync5, renameSync as renameSync4, rmSync as rmSync2, statSync as statSync4, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "node:fs";
13933
+ import { closeSync as closeSync3, existsSync as existsSync7, mkdirSync as mkdirSync4, openSync as openSync3, readFileSync as readFileSync5, renameSync as renameSync4, rmSync as rmSync2, statSync as statSync4, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "node:fs";
13885
13934
  import { homedir as homedir8, tmpdir } from "node:os";
13886
- import { basename, dirname as dirname3, join as join8, resolve as resolve6 } from "node:path";
13935
+ import { basename, dirname as dirname3, join as join9, resolve as resolve6 } from "node:path";
13887
13936
 
13888
13937
  // ../aft-bridge/dist/paths.js
13889
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, renameSync as renameSync2, writeFileSync } from "node:fs";
13938
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync4, renameSync as renameSync2, writeFileSync } from "node:fs";
13890
13939
  import { homedir as homedir6 } from "node:os";
13891
- import { dirname as dirname2, isAbsolute as isAbsolute2, join as join6, resolve as resolve5 } from "node:path";
13940
+ import { dirname as dirname2, isAbsolute as isAbsolute2, join as join7, resolve as resolve5 } from "node:path";
13892
13941
  function homeDir3() {
13893
13942
  if (process.platform === "win32")
13894
13943
  return process.env.USERPROFILE || process.env.HOME || homedir6();
@@ -13898,16 +13947,16 @@ function configHome() {
13898
13947
  const xdg = process.env.XDG_CONFIG_HOME;
13899
13948
  if (xdg && isAbsolute2(xdg))
13900
13949
  return xdg;
13901
- return join6(homeDir3(), ".config");
13950
+ return join7(homeDir3(), ".config");
13902
13951
  }
13903
13952
  function legacyOpenCodeConfigDir() {
13904
13953
  const envDir = process.env.OPENCODE_CONFIG_DIR?.trim();
13905
13954
  if (envDir)
13906
13955
  return resolve5(envDir);
13907
- return join6(configHome(), "opencode");
13956
+ return join7(configHome(), "opencode");
13908
13957
  }
13909
13958
  function legacyPiAgentDir() {
13910
- return join6(homeDir3(), ".pi", "agent");
13959
+ return join7(homeDir3(), ".pi", "agent");
13911
13960
  }
13912
13961
  function legacySources(basePath, label, harness) {
13913
13962
  return [
@@ -13916,10 +13965,10 @@ function legacySources(basePath, label, harness) {
13916
13965
  ];
13917
13966
  }
13918
13967
  function resolveCortexKitUserConfigPath() {
13919
- return join6(configHome(), "cortexkit", "aft.jsonc");
13968
+ return join7(configHome(), "cortexkit", "aft.jsonc");
13920
13969
  }
13921
13970
  function resolveCortexKitProjectConfigPath(projectDirectory) {
13922
- return join6(projectDirectory, ".cortexkit", "aft.jsonc");
13971
+ return join7(projectDirectory, ".cortexkit", "aft.jsonc");
13923
13972
  }
13924
13973
  function resolveCortexKitConfigPaths(projectDirectory) {
13925
13974
  return {
@@ -13930,22 +13979,22 @@ function resolveCortexKitConfigPaths(projectDirectory) {
13930
13979
  function resolveLegacyAftConfigSources(projectDirectory) {
13931
13980
  return {
13932
13981
  user: [
13933
- ...legacySources(join6(legacyOpenCodeConfigDir(), "aft"), "OpenCode user", "opencode"),
13934
- ...legacySources(join6(legacyPiAgentDir(), "aft"), "Pi user", "pi")
13982
+ ...legacySources(join7(legacyOpenCodeConfigDir(), "aft"), "OpenCode user", "opencode"),
13983
+ ...legacySources(join7(legacyPiAgentDir(), "aft"), "Pi user", "pi")
13935
13984
  ],
13936
13985
  project: [
13937
- ...legacySources(join6(projectDirectory, ".opencode", "aft"), "OpenCode project", "opencode"),
13938
- ...legacySources(join6(projectDirectory, ".pi", "aft"), "Pi project", "pi")
13986
+ ...legacySources(join7(projectDirectory, ".opencode", "aft"), "OpenCode project", "opencode"),
13987
+ ...legacySources(join7(projectDirectory, ".pi", "aft"), "Pi project", "pi")
13939
13988
  ]
13940
13989
  };
13941
13990
  }
13942
13991
  function resolveHarnessStoragePath(storageRoot, harness, ...segments) {
13943
- return join6(storageRoot, harness, ...segments);
13992
+ return join7(storageRoot, harness, ...segments);
13944
13993
  }
13945
13994
  function repairRootScopedStorageFile(storageRoot, harness, fileName) {
13946
13995
  const harnessPath = resolveHarnessStoragePath(storageRoot, harness, fileName);
13947
- const rootPath = join6(storageRoot, fileName);
13948
- if (existsSync4(harnessPath) || !existsSync4(rootPath))
13996
+ const rootPath = join7(storageRoot, fileName);
13997
+ if (existsSync5(harnessPath) || !existsSync5(rootPath))
13949
13998
  return harnessPath;
13950
13999
  try {
13951
14000
  mkdirSync2(dirname2(harnessPath), { recursive: true });
@@ -13959,7 +14008,7 @@ function shouldShowAnnouncement(storageRoot, harness, currentVersion) {
13959
14008
  const versionFile = repairRootScopedStorageFile(storageRoot, harness, "last_announced_version");
13960
14009
  let lastVersion = "";
13961
14010
  try {
13962
- if (existsSync4(versionFile)) {
14011
+ if (existsSync5(versionFile)) {
13963
14012
  lastVersion = readFileSync4(versionFile, "utf-8").trim();
13964
14013
  }
13965
14014
  } catch {
@@ -14031,10 +14080,10 @@ function decodeFileUrl(target) {
14031
14080
 
14032
14081
  // ../aft-bridge/dist/resolver.js
14033
14082
  import { execSync } from "node:child_process";
14034
- import { chmodSync as chmodSync2, closeSync as closeSync2, copyFileSync as copyFileSync2, existsSync as existsSync5, mkdirSync as mkdirSync3, openSync as openSync2, readSync, renameSync as renameSync3, unlinkSync as unlinkSync2 } from "node:fs";
14083
+ import { chmodSync as chmodSync2, closeSync as closeSync2, copyFileSync as copyFileSync2, existsSync as existsSync6, mkdirSync as mkdirSync3, openSync as openSync2, readSync, renameSync as renameSync3, unlinkSync as unlinkSync2 } from "node:fs";
14035
14084
  import { createRequire as createRequire2 } from "node:module";
14036
14085
  import { homedir as homedir7 } from "node:os";
14037
- import { join as join7 } from "node:path";
14086
+ import { join as join8 } from "node:path";
14038
14087
  var ensureBinaryForResolver = ensureBinary;
14039
14088
  function copyToVersionedCache(npmBinaryPath, knownVersion) {
14040
14089
  try {
@@ -14043,10 +14092,10 @@ function copyToVersionedCache(npmBinaryPath, knownVersion) {
14043
14092
  return null;
14044
14093
  const tag = version.startsWith("v") ? version : `v${version}`;
14045
14094
  const cacheDir = getAftBinaryCacheDir();
14046
- const versionedDir = join7(cacheDir, tag);
14095
+ const versionedDir = join8(cacheDir, tag);
14047
14096
  const ext = process.platform === "win32" ? ".exe" : "";
14048
- const cachedPath = join7(versionedDir, `aft${ext}`);
14049
- if (existsSync5(cachedPath)) {
14097
+ const cachedPath = join8(versionedDir, `aft${ext}`);
14098
+ if (existsSync6(cachedPath)) {
14050
14099
  const cachedVersion = readBinaryVersion(cachedPath);
14051
14100
  if (cachedVersion === version)
14052
14101
  return cachedPath;
@@ -14058,7 +14107,7 @@ function copyToVersionedCache(npmBinaryPath, knownVersion) {
14058
14107
  if (process.platform !== "win32") {
14059
14108
  chmodSync2(tmpPath, 493);
14060
14109
  }
14061
- if (process.platform === "win32" && existsSync5(cachedPath)) {
14110
+ if (process.platform === "win32" && existsSync6(cachedPath)) {
14062
14111
  try {
14063
14112
  unlinkSync2(cachedPath);
14064
14113
  } catch {}
@@ -14078,8 +14127,8 @@ function homeDirFromEnv(env) {
14078
14127
  return (process.platform === "win32" ? env.USERPROFILE || env.HOME : env.HOME) || homedir7();
14079
14128
  }
14080
14129
  function cachedBinaryPathFromEnv(version, env, ext) {
14081
- const binaryPath = join7(getAftBinaryCacheDir(env), version, `aft${ext}`);
14082
- return existsSync5(binaryPath) ? binaryPath : null;
14130
+ const binaryPath = join8(getAftBinaryCacheDir(env), version, `aft${ext}`);
14131
+ return existsSync6(binaryPath) ? binaryPath : null;
14083
14132
  }
14084
14133
  function isExpectedCachedBinary2(binaryPath, expectedVersion) {
14085
14134
  const expected = normalizeBareVersion(expectedVersion);
@@ -14178,7 +14227,7 @@ function findBinarySyncInner(expectedVersion) {
14178
14227
  const packageBin = `@cortexkit/aft-${key}/bin/aft${ext}`;
14179
14228
  const req = createRequire2(import.meta.url);
14180
14229
  const resolved = req.resolve(packageBin);
14181
- if (existsSync5(resolved)) {
14230
+ if (existsSync6(resolved)) {
14182
14231
  const npmVersion = readBinaryVersion(resolved);
14183
14232
  if (npmVersion === null) {
14184
14233
  warn(`npm platform package binary at ${resolved} did not report a version; skipping (continuing to PATH lookup)`);
@@ -14207,8 +14256,8 @@ function findBinarySyncInner(expectedVersion) {
14207
14256
  return { path: usable, source: "PATH" };
14208
14257
  }
14209
14258
  } catch {}
14210
- const cargoPath = join7(homeDirFromEnv(env), ".cargo", "bin", `aft${ext}`);
14211
- if (existsSync5(cargoPath)) {
14259
+ const cargoPath = join8(homeDirFromEnv(env), ".cargo", "bin", `aft${ext}`);
14260
+ if (existsSync6(cargoPath)) {
14212
14261
  const usable = probeBinaryCandidate(cargoPath, "cargo", expectedVersion);
14213
14262
  if (usable)
14214
14263
  return { path: usable, source: "cargo" };
@@ -14254,9 +14303,9 @@ function dataHome2() {
14254
14303
  if (process.env.XDG_DATA_HOME)
14255
14304
  return process.env.XDG_DATA_HOME;
14256
14305
  if (process.platform === "win32") {
14257
- return process.env.LOCALAPPDATA || process.env.APPDATA || join8(homeDir4(), "AppData", "Local");
14306
+ return process.env.LOCALAPPDATA || process.env.APPDATA || join9(homeDir4(), "AppData", "Local");
14258
14307
  }
14259
- return join8(homeDir4(), ".local", "share");
14308
+ return join9(homeDir4(), ".local", "share");
14260
14309
  }
14261
14310
  function homeDir4() {
14262
14311
  if (process.platform === "win32")
@@ -14265,8 +14314,8 @@ function homeDir4() {
14265
14314
  }
14266
14315
  function resolveLegacyStorageRoot(harness) {
14267
14316
  if (harness === "pi")
14268
- return join8(homeDir4(), ".pi", "agent", "aft");
14269
- return join8(dataHome2(), "opencode", "storage", "plugin", "aft");
14317
+ return join9(homeDir4(), ".pi", "agent", "aft");
14318
+ return join9(dataHome2(), "opencode", "storage", "plugin", "aft");
14270
14319
  }
14271
14320
  function stripJsoncForParse(input) {
14272
14321
  let out = "";
@@ -14396,7 +14445,7 @@ function acquireConfigMigrationLock(lockDir) {
14396
14445
  }
14397
14446
  function atomicCopyConfigFile(sourcePath, targetPath) {
14398
14447
  mkdirSync4(dirname3(targetPath), { recursive: true });
14399
- const tmpPath = join8(dirname3(targetPath), `.${basename(targetPath)}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
14448
+ const tmpPath = join9(dirname3(targetPath), `.${basename(targetPath)}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
14400
14449
  let fd = null;
14401
14450
  try {
14402
14451
  fd = openSync3(tmpPath, "wx", 384);
@@ -14418,7 +14467,7 @@ function atomicCopyConfigFile(sourcePath, targetPath) {
14418
14467
  }
14419
14468
  function atomicWriteConfigFile(targetPath, content) {
14420
14469
  mkdirSync4(dirname3(targetPath), { recursive: true });
14421
- const tmpPath = join8(dirname3(targetPath), `.${basename(targetPath)}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
14470
+ const tmpPath = join9(dirname3(targetPath), `.${basename(targetPath)}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
14422
14471
  let fd = null;
14423
14472
  try {
14424
14473
  fd = openSync3(tmpPath, "wx", 384);
@@ -14440,11 +14489,11 @@ function atomicWriteConfigFile(targetPath, content) {
14440
14489
  }
14441
14490
  var MOVED_MARKER_SUFFIX = ".MOVED_READPLEASE";
14442
14491
  function nonClobberingSidecarPath(desiredPath) {
14443
- if (!existsSync6(desiredPath))
14492
+ if (!existsSync7(desiredPath))
14444
14493
  return desiredPath;
14445
14494
  for (let index = 1;index < Number.MAX_SAFE_INTEGER; index++) {
14446
14495
  const candidate = `${desiredPath}.${index}`;
14447
- if (!existsSync6(candidate))
14496
+ if (!existsSync7(candidate))
14448
14497
  return candidate;
14449
14498
  }
14450
14499
  throw new Error(`could not find available preservation sidecar path for ${desiredPath}`);
@@ -14538,7 +14587,7 @@ function visibleConfigMigrationWarning(scope, targetPath, paths, reason) {
14538
14587
  function migrateAftConfigFile(opts) {
14539
14588
  const warnings = [];
14540
14589
  const resolvedTarget = resolve6(opts.targetPath);
14541
- const existingSources = opts.legacySources.filter((source) => existsSync6(source.path) && resolve6(source.path) !== resolvedTarget);
14590
+ const existingSources = opts.legacySources.filter((source) => existsSync7(source.path) && resolve6(source.path) !== resolvedTarget);
14542
14591
  const info = opts.logger?.info ?? opts.logger?.log;
14543
14592
  if (existingSources.length === 0) {
14544
14593
  return { migrated: false, conflict: false, targetPath: opts.targetPath, warnings };
@@ -14550,7 +14599,7 @@ function migrateAftConfigFile(opts) {
14550
14599
  ...source,
14551
14600
  content: readFileSync5(source.path, "utf-8")
14552
14601
  }));
14553
- if (existsSync6(opts.targetPath)) {
14602
+ if (existsSync7(opts.targetPath)) {
14554
14603
  const targetContent = readFileSync5(opts.targetPath, "utf-8");
14555
14604
  for (const source of sources) {
14556
14605
  if (fileSemanticsMatch(source.content, targetContent))
@@ -14600,12 +14649,12 @@ function spawnErrorLabel(error2) {
14600
14649
  return [code, error2.message].filter(Boolean).join(": ");
14601
14650
  }
14602
14651
  function migrationLogPath(newRoot, harness, logger) {
14603
- const desired = join8(newRoot, "logs", "migration", `${harness}-${Date.now()}.jsonl`);
14652
+ const desired = join9(newRoot, "logs", "migration", `${harness}-${Date.now()}.jsonl`);
14604
14653
  try {
14605
14654
  mkdirSync4(dirname3(desired), { recursive: true });
14606
14655
  return desired;
14607
14656
  } catch (err) {
14608
- const fallback = join8(tmpdir(), `aft-migration-${harness}-${Date.now()}.jsonl`);
14657
+ const fallback = join9(tmpdir(), `aft-migration-${harness}-${Date.now()}.jsonl`);
14609
14658
  logger?.warn?.(`Failed to create AFT migration log directory ${dirname3(desired)}: ${err instanceof Error ? err.message : String(err)}. ` + `Using fallback log path ${fallback}.`);
14610
14659
  return fallback;
14611
14660
  }
@@ -14615,11 +14664,11 @@ async function ensureStorageMigrated(opts) {
14615
14664
  const newRoot = resolveCortexKitStorageRoot();
14616
14665
  const targetMarker = resolveHarnessStoragePath(newRoot, opts.harness, TARGET_MARKER);
14617
14666
  const info = opts.logger?.info ?? opts.logger?.log;
14618
- if (existsSync6(targetMarker)) {
14667
+ if (existsSync7(targetMarker)) {
14619
14668
  info?.(`AFT storage already migrated for ${opts.harness}; using ${newRoot}`);
14620
14669
  return;
14621
14670
  }
14622
- if (!existsSync6(legacyRoot)) {
14671
+ if (!existsSync7(legacyRoot)) {
14623
14672
  info?.(`AFT storage migration skipped for ${opts.harness}: no legacy data at ${legacyRoot}; ` + `using ${newRoot} for fresh install`);
14624
14673
  return;
14625
14674
  }
@@ -14667,9 +14716,10 @@ async function ensureStorageMigrated(opts) {
14667
14716
  throw new Error(`AFT storage migration failed (${detail}). ` + `Harness: ${opts.harness}. Legacy: ${legacyRoot}. Target: ${newRoot}. ` + `See log: ${logPath}. ` + `Plugin load aborted to prevent legacy/new state divergence.` + (stderrTail ? ` Stderr tail: ${stderrTail}` : "") + (stdoutTail ? ` Stdout tail: ${stdoutTail}` : ""));
14668
14717
  }
14669
14718
  // ../aft-bridge/dist/npm-resolver.js
14719
+ import { spawn as spawn3, spawnSync as spawnSync3 } from "node:child_process";
14670
14720
  import { readdirSync as readdirSync2, statSync as statSync5 } from "node:fs";
14671
14721
  import { homedir as homedir9 } from "node:os";
14672
- import { delimiter, dirname as dirname4, isAbsolute as isAbsolute3, join as join9 } from "node:path";
14722
+ import { delimiter as delimiter2, dirname as dirname4, isAbsolute as isAbsolute3, join as join10 } from "node:path";
14673
14723
  function defaultDeps() {
14674
14724
  return {
14675
14725
  platform: process.platform,
@@ -14691,18 +14741,18 @@ function isFile(p) {
14691
14741
  function npmFromPath(deps) {
14692
14742
  const name = npmBinaryName(deps.platform);
14693
14743
  const raw = deps.env.PATH ?? deps.env.Path ?? "";
14694
- for (const entry of raw.split(delimiter)) {
14744
+ for (const entry of raw.split(delimiter2)) {
14695
14745
  const dir = entry.trim().replace(/^"|"$/g, "");
14696
14746
  if (!dir || !isAbsolute3(dir))
14697
14747
  continue;
14698
- if (isFile(join9(dir, name)))
14748
+ if (isFile(join10(dir, name)))
14699
14749
  return dir;
14700
14750
  }
14701
14751
  return null;
14702
14752
  }
14703
14753
  function npmAdjacentToNode(deps) {
14704
14754
  const dir = dirname4(deps.execPath);
14705
- return isFile(join9(dir, npmBinaryName(deps.platform))) ? dir : null;
14755
+ return isFile(join10(dir, npmBinaryName(deps.platform))) ? dir : null;
14706
14756
  }
14707
14757
  function highestVersionedNodeBin(installsDir, name) {
14708
14758
  let entries;
@@ -14711,8 +14761,8 @@ function highestVersionedNodeBin(installsDir, name) {
14711
14761
  } catch {
14712
14762
  return null;
14713
14763
  }
14714
- const candidates = entries.filter((v) => isFile(join9(installsDir, v, "bin", name))).sort((a, b) => compareVersionsDesc(a, b));
14715
- return candidates.length > 0 ? join9(installsDir, candidates[0], "bin") : null;
14764
+ const candidates = entries.filter((v) => isFile(join10(installsDir, v, "bin", name))).sort((a, b) => compareVersionsDesc(a, b));
14765
+ return candidates.length > 0 ? join10(installsDir, candidates[0], "bin") : null;
14716
14766
  }
14717
14767
  function compareVersionsDesc(a, b) {
14718
14768
  const pa = a.replace(/^v/, "").split(".").map((n) => Number.parseInt(n, 10));
@@ -14737,22 +14787,22 @@ function wellKnownNpmDirs(deps) {
14737
14787
  const programFiles = env.ProgramFiles || "C:\\Program Files";
14738
14788
  const appData = env.APPDATA;
14739
14789
  const localAppData = env.LOCALAPPDATA;
14740
- push(join9(programFiles, "nodejs"));
14790
+ push(join10(programFiles, "nodejs"));
14741
14791
  if (appData)
14742
- push(join9(appData, "npm"));
14792
+ push(join10(appData, "npm"));
14743
14793
  if (localAppData)
14744
- push(join9(localAppData, "Volta", "bin"));
14794
+ push(join10(localAppData, "Volta", "bin"));
14745
14795
  if (env.NVM_SYMLINK)
14746
14796
  push(env.NVM_SYMLINK);
14747
14797
  } else {
14748
14798
  if (env.NVM_BIN)
14749
14799
  push(env.NVM_BIN);
14750
- push(highestVersionedNodeBin(join9(home, ".nvm", "versions", "node"), name));
14751
- push(highestVersionedNodeBin(join9(home, ".local", "share", "mise", "installs", "node"), name));
14752
- push(highestVersionedNodeBin(join9(home, ".asdf", "installs", "nodejs"), name));
14753
- push(join9(home, ".volta", "bin"));
14754
- push(join9(home, ".asdf", "shims"));
14755
- const systemDirs = deps.systemNpmDirs ?? (platform === "darwin" ? ["/opt/homebrew/bin", "/usr/local/bin"] : ["/usr/local/bin", "/usr/bin", join9(home, ".local", "bin")]);
14800
+ push(highestVersionedNodeBin(join10(home, ".nvm", "versions", "node"), name));
14801
+ push(highestVersionedNodeBin(join10(home, ".local", "share", "mise", "installs", "node"), name));
14802
+ push(highestVersionedNodeBin(join10(home, ".asdf", "installs", "nodejs"), name));
14803
+ push(join10(home, ".volta", "bin"));
14804
+ push(join10(home, ".asdf", "shims"));
14805
+ const systemDirs = deps.systemNpmDirs ?? (platform === "darwin" ? ["/opt/homebrew/bin", "/usr/local/bin"] : ["/usr/local/bin", "/usr/bin", join10(home, ".local", "bin")]);
14756
14806
  for (const dir of systemDirs)
14757
14807
  push(dir);
14758
14808
  }
@@ -14762,29 +14812,212 @@ function resolveNpm(deps = defaultDeps()) {
14762
14812
  const name = npmBinaryName(deps.platform);
14763
14813
  const onPath = npmFromPath(deps);
14764
14814
  if (onPath)
14765
- return { command: join9(onPath, name), binDir: onPath };
14815
+ return { command: join10(onPath, name), binDir: onPath };
14766
14816
  const adjacent = npmAdjacentToNode(deps);
14767
14817
  if (adjacent)
14768
- return { command: join9(adjacent, name), binDir: adjacent };
14818
+ return { command: join10(adjacent, name), binDir: adjacent };
14769
14819
  for (const dir of wellKnownNpmDirs(deps)) {
14770
- const candidate = join9(dir, name);
14820
+ const candidate = join10(dir, name);
14771
14821
  if (isFile(candidate))
14772
14822
  return { command: candidate, binDir: dir };
14773
14823
  }
14774
14824
  return null;
14775
14825
  }
14826
+ function quoteCmdArgument(value) {
14827
+ if (/[\0\r\n"%]/.test(value)) {
14828
+ throw new Error(`npm argument cannot be represented safely for cmd.exe: ${JSON.stringify(value)}`);
14829
+ }
14830
+ return `"${value}"`;
14831
+ }
14832
+ function npmInvocation(resolved, npmArgs, platform = process.platform, env = process.env) {
14833
+ if (platform !== "win32" || !/\.(?:cmd|bat)$/i.test(resolved.command)) {
14834
+ return { command: resolved.command, args: [...npmArgs] };
14835
+ }
14836
+ if (/[\0\r\n"]/.test(resolved.command)) {
14837
+ throw new Error(`npm command cannot be represented safely for cmd.exe: ${JSON.stringify(resolved.command)}`);
14838
+ }
14839
+ const commandEnvName = "AFT_NPM_COMMAND";
14840
+ const quotedArgs = npmArgs.map(quoteCmdArgument);
14841
+ const commandLine = `""%${commandEnvName}%"${quotedArgs.length > 0 ? ` ${quotedArgs.join(" ")}` : ""}"`;
14842
+ return {
14843
+ command: env.ComSpec ?? env.COMSPEC ?? "cmd.exe",
14844
+ args: ["/d", "/s", "/v:off", "/c", commandLine],
14845
+ env: { [commandEnvName]: resolved.command },
14846
+ windowsVerbatimArguments: true,
14847
+ windowsCmdShim: true
14848
+ };
14849
+ }
14850
+
14851
+ class NpmTerminationUnknownError extends Error {
14852
+ code = "npm_termination_unknown";
14853
+ constructor(detail) {
14854
+ super(`npm process-tree termination could not be confirmed: ${detail}`);
14855
+ this.name = "NpmTerminationUnknownError";
14856
+ }
14857
+ }
14858
+ function terminateDirectNpmChild(child, gracePeriodMs) {
14859
+ if (child.exitCode !== null || child.signalCode !== null)
14860
+ return Promise.resolve();
14861
+ return new Promise((resolve7, reject) => {
14862
+ let settled = false;
14863
+ let forceTimer = null;
14864
+ let confirmationTimer = null;
14865
+ let signalFailure = null;
14866
+ const cleanup = () => {
14867
+ if (forceTimer)
14868
+ clearTimeout(forceTimer);
14869
+ if (confirmationTimer)
14870
+ clearTimeout(confirmationTimer);
14871
+ child.removeListener("exit", finish);
14872
+ child.removeListener("error", onChildError);
14873
+ };
14874
+ const finish = () => {
14875
+ if (settled)
14876
+ return;
14877
+ settled = true;
14878
+ cleanup();
14879
+ resolve7();
14880
+ };
14881
+ const fail = () => {
14882
+ if (settled)
14883
+ return;
14884
+ if (child.exitCode !== null || child.signalCode !== null) {
14885
+ finish();
14886
+ return;
14887
+ }
14888
+ settled = true;
14889
+ cleanup();
14890
+ reject(new NpmTerminationUnknownError(signalFailure ?? `direct npm child did not exit after SIGKILL within ${gracePeriodMs}ms`));
14891
+ };
14892
+ const onChildError = (error2) => {
14893
+ signalFailure = `direct npm child error during termination: ${String(error2)}`;
14894
+ };
14895
+ const signal = (value) => {
14896
+ try {
14897
+ if (!child.kill(value)) {
14898
+ signalFailure = `direct npm child rejected ${value ?? "SIGTERM"}`;
14899
+ }
14900
+ } catch (error2) {
14901
+ signalFailure = `direct npm child ${value ?? "SIGTERM"} failed: ${String(error2)}`;
14902
+ }
14903
+ };
14904
+ child.once("exit", finish);
14905
+ child.on("error", onChildError);
14906
+ forceTimer = setTimeout(() => {
14907
+ signal("SIGKILL");
14908
+ confirmationTimer = setTimeout(fail, gracePeriodMs);
14909
+ }, gracePeriodMs);
14910
+ signal();
14911
+ if (child.exitCode !== null || child.signalCode !== null)
14912
+ finish();
14913
+ });
14914
+ }
14915
+ function terminateNpmProcessTree(child, invocation, env = process.env, gracePeriodMs = 5000) {
14916
+ if (!invocation.windowsCmdShim)
14917
+ return terminateDirectNpmChild(child, gracePeriodMs);
14918
+ const exitedSuccessfully = () => child.exitCode === 0 && child.signalCode === null;
14919
+ if (exitedSuccessfully())
14920
+ return Promise.resolve();
14921
+ if (child.pid === undefined) {
14922
+ return Promise.reject(new NpmTerminationUnknownError("cmd.exe child has no process ID"));
14923
+ }
14924
+ return new Promise((resolve7, reject) => {
14925
+ let settled = false;
14926
+ let childExited = child.exitCode !== null || child.signalCode !== null;
14927
+ let treeKillConfirmed = false;
14928
+ let treeKillFailure = null;
14929
+ let killer = null;
14930
+ const timeout = setTimeout(() => {
14931
+ try {
14932
+ killer?.kill();
14933
+ } catch {}
14934
+ if (treeKillConfirmed || exitedSuccessfully()) {
14935
+ settled = true;
14936
+ cleanup();
14937
+ resolve7();
14938
+ return;
14939
+ }
14940
+ fail(treeKillFailure ?? `taskkill.exe did not finish within ${gracePeriodMs}ms`);
14941
+ }, gracePeriodMs);
14942
+ const cleanup = () => {
14943
+ clearTimeout(timeout);
14944
+ child.removeListener("exit", onChildExit);
14945
+ };
14946
+ const succeedIfConfirmed = () => {
14947
+ if (settled || !childExited || !treeKillConfirmed)
14948
+ return;
14949
+ settled = true;
14950
+ cleanup();
14951
+ resolve7();
14952
+ };
14953
+ const fail = (detail) => {
14954
+ if (settled)
14955
+ return;
14956
+ settled = true;
14957
+ cleanup();
14958
+ reject(new NpmTerminationUnknownError(detail));
14959
+ };
14960
+ function onChildExit() {
14961
+ childExited = true;
14962
+ if (treeKillFailure !== null) {
14963
+ if (exitedSuccessfully()) {
14964
+ settled = true;
14965
+ cleanup();
14966
+ resolve7();
14967
+ } else {
14968
+ fail(treeKillFailure);
14969
+ }
14970
+ return;
14971
+ }
14972
+ succeedIfConfirmed();
14973
+ }
14974
+ const recordTreeKillFailure = (detail) => {
14975
+ if (settled)
14976
+ return;
14977
+ if (exitedSuccessfully()) {
14978
+ settled = true;
14979
+ cleanup();
14980
+ resolve7();
14981
+ return;
14982
+ }
14983
+ treeKillFailure = detail;
14984
+ if (childExited)
14985
+ fail(detail);
14986
+ };
14987
+ child.once("exit", onChildExit);
14988
+ const systemRoot = env.SystemRoot ?? env.SYSTEMROOT;
14989
+ const taskkill = systemRoot ? join10(systemRoot, "System32", "taskkill.exe") : "taskkill.exe";
14990
+ try {
14991
+ killer = spawn3(taskkill, ["/pid", String(child.pid), "/t", "/f"], {
14992
+ stdio: "ignore",
14993
+ windowsHide: true
14994
+ });
14995
+ killer.once("error", (error2) => recordTreeKillFailure(`taskkill.exe failed to start: ${String(error2)}`));
14996
+ killer.once("exit", (code) => {
14997
+ if (code !== 0) {
14998
+ recordTreeKillFailure(`taskkill.exe exited with code ${code ?? "unknown"}`);
14999
+ return;
15000
+ }
15001
+ treeKillConfirmed = true;
15002
+ succeedIfConfirmed();
15003
+ });
15004
+ } catch (error2) {
15005
+ recordTreeKillFailure(`taskkill.exe failed to start: ${String(error2)}`);
15006
+ }
15007
+ });
15008
+ }
14776
15009
  function npmSpawnEnv(resolved, baseEnv = process.env) {
14777
15010
  if (!resolved.binDir)
14778
15011
  return { ...baseEnv };
14779
15012
  const existing = baseEnv.PATH ?? baseEnv.Path ?? "";
14780
- const next = existing ? `${resolved.binDir}${delimiter}${existing}` : resolved.binDir;
15013
+ const next = existing ? `${resolved.binDir}${delimiter2}${existing}` : resolved.binDir;
14781
15014
  return { ...baseEnv, PATH: next };
14782
15015
  }
14783
15016
  // ../aft-bridge/dist/onnx-runtime.js
14784
15017
  import { execFileSync } from "node:child_process";
14785
15018
  import { createHash as createHash3 } from "node:crypto";
14786
- import { chmodSync as chmodSync3, closeSync as closeSync4, copyFileSync as copyFileSync3, createWriteStream as createWriteStream2, existsSync as existsSync7, lstatSync, mkdirSync as mkdirSync5, openSync as openSync4, readdirSync as readdirSync3, readFileSync as readFileSync6, readlinkSync, realpathSync as realpathSync2, rmSync as rmSync3, statSync as statSync6, symlinkSync, unlinkSync as unlinkSync4, writeFileSync as writeFileSync3 } from "node:fs";
14787
- import { basename as basename2, dirname as dirname5, isAbsolute as isAbsolute4, join as join10, relative as relative2, resolve as resolve7, win32 } from "node:path";
15019
+ import { chmodSync as chmodSync3, closeSync as closeSync4, copyFileSync as copyFileSync3, createWriteStream as createWriteStream2, existsSync as existsSync8, lstatSync, mkdirSync as mkdirSync5, openSync as openSync4, readdirSync as readdirSync3, readFileSync as readFileSync6, readlinkSync, realpathSync as realpathSync2, rmSync as rmSync3, statSync as statSync6, symlinkSync, unlinkSync as unlinkSync4, writeFileSync as writeFileSync3 } from "node:fs";
15020
+ import { basename as basename2, dirname as dirname5, isAbsolute as isAbsolute4, join as join11, relative as relative2, resolve as resolve7, win32 } from "node:path";
14788
15021
  import { Readable as Readable2 } from "node:stream";
14789
15022
  import { pipeline as pipeline2 } from "node:stream/promises";
14790
15023
  var ORT_VERSION = "1.24.4";
@@ -14847,11 +15080,11 @@ function getManualInstallHint() {
14847
15080
  }
14848
15081
  async function ensureOnnxRuntime(storageDir) {
14849
15082
  const info = getPlatformInfo();
14850
- const ortVersionDir = join10(storageDir, "onnxruntime", ORT_VERSION);
15083
+ const ortVersionDir = join11(storageDir, "onnxruntime", ORT_VERSION);
14851
15084
  const libName = info?.libName ?? "libonnxruntime.dylib";
14852
15085
  const resolvedOrtDir = resolveCachedOnnxRuntimeDir(ortVersionDir, libName);
14853
- const libPath = join10(resolvedOrtDir, libName);
14854
- if (existsSync7(libPath)) {
15086
+ const libPath = join11(resolvedOrtDir, libName);
15087
+ if (existsSync8(libPath)) {
14855
15088
  const meta = readOnnxInstalledMeta(ortVersionDir);
14856
15089
  if (meta?.sha256) {
14857
15090
  try {
@@ -14880,9 +15113,9 @@ async function ensureOnnxRuntime(storageDir) {
14880
15113
  warn(`ONNX Runtime auto-download not available for ${process.platform}/${process.arch}. Install manually: ${getManualInstallHint()}`);
14881
15114
  return null;
14882
15115
  }
14883
- const onnxBaseDir = join10(storageDir, "onnxruntime");
15116
+ const onnxBaseDir = join11(storageDir, "onnxruntime");
14884
15117
  mkdirSync5(onnxBaseDir, { recursive: true });
14885
- const lockPath = join10(onnxBaseDir, ONNX_LOCK_FILE);
15118
+ const lockPath = join11(onnxBaseDir, ONNX_LOCK_FILE);
14886
15119
  cleanupAbandonedStagingDirs(onnxBaseDir);
14887
15120
  if (!acquireLock(lockPath)) {
14888
15121
  warn(`ONNX Runtime install already in progress in another process (lock: ${lockPath}). Skipping.`);
@@ -14901,7 +15134,7 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
14901
15134
  for (const entry of entries) {
14902
15135
  if (!entry.startsWith(`${ORT_VERSION}.tmp.`))
14903
15136
  continue;
14904
- const stagingDir = join10(onnxBaseDir, entry);
15137
+ const stagingDir = join11(onnxBaseDir, entry);
14905
15138
  const parts = entry.split(".");
14906
15139
  const pidStr = parts[parts.length - 2];
14907
15140
  const pid = pidStr ? Number.parseInt(pidStr, 10) : NaN;
@@ -14938,7 +15171,7 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
14938
15171
  }
14939
15172
  function cleanupIncompleteTargetIfUnowned(ortDir) {
14940
15173
  try {
14941
- if (existsSync7(ortDir) && !existsSync7(join10(ortDir, ONNX_INSTALLED_META_FILE))) {
15174
+ if (existsSync8(ortDir) && !existsSync8(join11(ortDir, ONNX_INSTALLED_META_FILE))) {
14942
15175
  log(`[onnx] removing half-populated install dir ${ortDir} (no meta file)`);
14943
15176
  rmSync3(ortDir, { recursive: true, force: true });
14944
15177
  }
@@ -14982,8 +15215,8 @@ function detectOnnxVersion(libDir, libName) {
14982
15215
  if (version)
14983
15216
  return version;
14984
15217
  }
14985
- const base = join10(libDir, libName);
14986
- if (existsSync7(base)) {
15218
+ const base = join11(libDir, libName);
15219
+ if (existsSync8(base)) {
14987
15220
  try {
14988
15221
  const real = realpathSync2(base);
14989
15222
  const version = parseOnnxVersionFromPath(real) ?? parseOnnxVersionFromDirectoryPath(real);
@@ -15014,8 +15247,8 @@ function pathEnvValue() {
15014
15247
  return process.env.PATH ?? process.env.Path ?? process.env.path ?? "";
15015
15248
  }
15016
15249
  function pathEntriesForPlatform() {
15017
- const delimiter2 = process.platform === "win32" ? ";" : ":";
15018
- return pathEnvValue().split(delimiter2).map((entry) => entry.trim().replace(/^"|"$/g, "")).filter((entry) => {
15250
+ const delimiter3 = process.platform === "win32" ? ";" : ":";
15251
+ return pathEnvValue().split(delimiter3).map((entry) => entry.trim().replace(/^"|"$/g, "")).filter((entry) => {
15019
15252
  if (!entry || entry === "." || entry.includes("\x00"))
15020
15253
  return false;
15021
15254
  return isAbsolute4(entry) || win32.isAbsolute(entry);
@@ -15045,10 +15278,10 @@ function directoryContainsLibrary(dir, libName) {
15045
15278
  }
15046
15279
  }
15047
15280
  function resolveCachedOnnxRuntimeDir(ortVersionDir, libName) {
15048
- if (existsSync7(join10(ortVersionDir, libName)))
15281
+ if (existsSync8(join11(ortVersionDir, libName)))
15049
15282
  return ortVersionDir;
15050
- const libSubdir = join10(ortVersionDir, "lib");
15051
- if (existsSync7(join10(libSubdir, libName)))
15283
+ const libSubdir = join11(ortVersionDir, "lib");
15284
+ if (existsSync8(join11(libSubdir, libName)))
15052
15285
  return libSubdir;
15053
15286
  return ortVersionDir;
15054
15287
  }
@@ -15063,13 +15296,13 @@ function findSystemOnnxRuntime(libName) {
15063
15296
  } else if (process.platform === "win32") {
15064
15297
  const programFiles = process.env.ProgramFiles ?? "C:\\Program Files";
15065
15298
  const programFilesX86 = process.env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)";
15066
- searchPaths.push(join10(programFiles, "onnxruntime", "lib"), join10(programFiles, "Microsoft ONNX Runtime", "lib"), join10(programFiles, "Microsoft Machine Learning", "lib"), join10(programFilesX86, "onnxruntime", "lib"), ...(() => {
15299
+ searchPaths.push(join11(programFiles, "onnxruntime", "lib"), join11(programFiles, "Microsoft ONNX Runtime", "lib"), join11(programFiles, "Microsoft Machine Learning", "lib"), join11(programFilesX86, "onnxruntime", "lib"), ...(() => {
15067
15300
  const nugetPaths = [];
15068
15301
  const userProfile = process.env.USERPROFILE ?? "";
15069
15302
  if (!userProfile)
15070
15303
  return nugetPaths;
15071
- const nugetPackageDir = join10(userProfile, ".nuget", "packages", "microsoft.ml.onnxruntime");
15072
- if (!existsSync7(nugetPackageDir))
15304
+ const nugetPackageDir = join11(userProfile, ".nuget", "packages", "microsoft.ml.onnxruntime");
15305
+ if (!existsSync8(nugetPackageDir))
15073
15306
  return nugetPaths;
15074
15307
  try {
15075
15308
  for (const entry of readdirSync3(nugetPackageDir, { withFileTypes: true })) {
@@ -15077,7 +15310,7 @@ function findSystemOnnxRuntime(libName) {
15077
15310
  continue;
15078
15311
  if (entry.name === "__globalPackagesFolder" || entry.name.startsWith("."))
15079
15312
  continue;
15080
- nugetPaths.push(join10(nugetPackageDir, entry.name, "runtimes", "win-x64", "native"), join10(nugetPackageDir, entry.name, "runtimes", "win-arm64", "native"));
15313
+ nugetPaths.push(join11(nugetPackageDir, entry.name, "runtimes", "win-x64", "native"), join11(nugetPackageDir, entry.name, "runtimes", "win-arm64", "native"));
15081
15314
  }
15082
15315
  } catch (err) {
15083
15316
  warn(`Failed to scan NuGet ONNX Runtime cache ${nugetPackageDir}: ${err instanceof Error ? err.message : String(err)}`);
@@ -15099,11 +15332,11 @@ function findSystemOnnxRuntime(libName) {
15099
15332
  });
15100
15333
  const unknownVersionPaths = [];
15101
15334
  for (const dir of uniquePaths) {
15102
- const libPath = join10(dir, libName);
15335
+ const libPath = join11(dir, libName);
15103
15336
  if (process.platform === "win32") {
15104
15337
  if (!directoryContainsLibrary(dir, libName))
15105
15338
  continue;
15106
- } else if (!existsSync7(libPath)) {
15339
+ } else if (!existsSync8(libPath)) {
15107
15340
  continue;
15108
15341
  }
15109
15342
  const version = detectOnnxVersion(dir, libName);
@@ -15169,7 +15402,7 @@ function validateExtractedTree(stagingRoot) {
15169
15402
  const walk = (dir) => {
15170
15403
  const entries = readdirSync3(dir);
15171
15404
  for (const entry of entries) {
15172
- const fullPath = join10(dir, entry);
15405
+ const fullPath = join11(dir, entry);
15173
15406
  const lst = lstatSync(fullPath);
15174
15407
  if (lst.isSymbolicLink()) {
15175
15408
  const linkTarget = readlinkSync(fullPath);
@@ -15204,7 +15437,7 @@ async function downloadOnnxRuntime(info, targetDir) {
15204
15437
  const tmpDir = `${targetDir}.tmp.${process.pid}.${Date.now().toString(36)}`;
15205
15438
  try {
15206
15439
  mkdirSync5(tmpDir, { recursive: true });
15207
- const archivePath = join10(tmpDir, `onnxruntime.${info.archiveType}`);
15440
+ const archivePath = join11(tmpDir, `onnxruntime.${info.archiveType}`);
15208
15441
  await downloadFileWithCap(url, archivePath);
15209
15442
  const archiveSha256 = sha256File(archivePath);
15210
15443
  log(`ONNX Runtime archive sha256=${archiveSha256}`);
@@ -15220,8 +15453,8 @@ async function downloadOnnxRuntime(info, targetDir) {
15220
15453
  unlinkSync4(archivePath);
15221
15454
  } catch {}
15222
15455
  validateExtractedTree(tmpDir);
15223
- const extractedDir = join10(tmpDir, info.assetName, "lib");
15224
- if (!existsSync7(extractedDir)) {
15456
+ const extractedDir = join11(tmpDir, info.assetName, "lib");
15457
+ if (!existsSync8(extractedDir)) {
15225
15458
  throw new Error(`Expected directory not found: ${extractedDir}`);
15226
15459
  }
15227
15460
  mkdirSync5(targetDir, { recursive: true });
@@ -15229,7 +15462,7 @@ async function downloadOnnxRuntime(info, targetDir) {
15229
15462
  const realFiles = [];
15230
15463
  const symlinks = [];
15231
15464
  for (const libFile of libFiles) {
15232
- const src = join10(extractedDir, libFile);
15465
+ const src = join11(extractedDir, libFile);
15233
15466
  try {
15234
15467
  const stat2 = lstatSync(src);
15235
15468
  log(`ORT extract: ${libFile} — isSymlink=${stat2.isSymbolicLink()}, isFile=${stat2.isFile()}, size=${stat2.size}`);
@@ -15244,7 +15477,7 @@ async function downloadOnnxRuntime(info, targetDir) {
15244
15477
  }
15245
15478
  }
15246
15479
  copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks);
15247
- const libPath = join10(targetDir, info.libName);
15480
+ const libPath = join11(targetDir, info.libName);
15248
15481
  let libHash = null;
15249
15482
  try {
15250
15483
  libHash = sha256File(libPath);
@@ -15269,8 +15502,8 @@ async function downloadOnnxRuntime(info, targetDir) {
15269
15502
  function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, copyFile = copyFileSync3) {
15270
15503
  const requiredLibs = new Set([info.libName]);
15271
15504
  for (const libFile of realFiles) {
15272
- const src = join10(extractedDir, libFile);
15273
- const dst = join10(targetDir, libFile);
15505
+ const src = join11(extractedDir, libFile);
15506
+ const dst = join11(targetDir, libFile);
15274
15507
  try {
15275
15508
  copyFile(src, dst);
15276
15509
  if (process.platform !== "win32") {
@@ -15286,11 +15519,11 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
15286
15519
  }
15287
15520
  const targetRoot = realpathSync2(targetDir);
15288
15521
  for (const link of symlinks) {
15289
- const dst = join10(targetDir, link.name);
15522
+ const dst = join11(targetDir, link.name);
15290
15523
  try {
15291
15524
  unlinkSync4(dst);
15292
15525
  } catch {}
15293
- const dstForContainment = join10(targetRoot, link.name);
15526
+ const dstForContainment = join11(targetRoot, link.name);
15294
15527
  const resolvedTarget = resolve7(dirname5(dstForContainment), link.target);
15295
15528
  if (!isPathInsideRoot(targetRoot, resolvedTarget)) {
15296
15529
  const message = `ONNX Runtime symlink ${link.name} points outside install dir: ${link.target}`;
@@ -15311,8 +15544,8 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
15311
15544
  log(`ORT extract: failed to symlink optional ${link.name}: ${symlinkErr}`);
15312
15545
  }
15313
15546
  }
15314
- const requiredPath = join10(targetDir, info.libName);
15315
- if (!existsSync7(requiredPath)) {
15547
+ const requiredPath = join11(targetDir, info.libName);
15548
+ if (!existsSync8(requiredPath)) {
15316
15549
  rmSync3(targetDir, { recursive: true, force: true });
15317
15550
  throw new Error(`Required ONNX Runtime library missing after install: ${requiredPath}`);
15318
15551
  }
@@ -15338,13 +15571,13 @@ function writeOnnxInstalledMeta(installDir, version, sha256, archiveSha256) {
15338
15571
  ...sha256 ? { sha256 } : {},
15339
15572
  archiveSha256
15340
15573
  };
15341
- writeFileSync3(join10(installDir, ONNX_INSTALLED_META_FILE), JSON.stringify(meta), "utf8");
15574
+ writeFileSync3(join11(installDir, ONNX_INSTALLED_META_FILE), JSON.stringify(meta), "utf8");
15342
15575
  } catch (err) {
15343
15576
  log(`[onnx] failed to write installed-meta in ${installDir}: ${err}`);
15344
15577
  }
15345
15578
  }
15346
15579
  function readOnnxInstalledMeta(installDir) {
15347
- const path2 = join10(installDir, ONNX_INSTALLED_META_FILE);
15580
+ const path2 = join11(installDir, ONNX_INSTALLED_META_FILE);
15348
15581
  try {
15349
15582
  if (!statSync6(path2).isFile())
15350
15583
  return null;
@@ -15750,7 +15983,7 @@ function editModesPresent(record) {
15750
15983
  const hasSymbol = isNonEmptyString(record.symbol);
15751
15984
  if (!hasSymbol) {
15752
15985
  delete record.symbol;
15753
- if (record.content === null || record.content === "")
15986
+ if (isNullOrEmptyString(record.content))
15754
15987
  delete record.content;
15755
15988
  } else if (record.content === null) {
15756
15989
  delete record.content;
@@ -15779,17 +16012,27 @@ function editModesPresent(record) {
15779
16012
  function isNonEmptyString(value) {
15780
16013
  return typeof value === "string" && value.length > 0;
15781
16014
  }
16015
+ function isNullOrEmptyString(value) {
16016
+ return value === null || value === "";
16017
+ }
15782
16018
  var OMIT_OPTIONAL_FIELDS_STEERING = "Omit unused optional fields entirely; do not send empty strings or empty arrays for them.";
15783
16019
  function isEditSentinelItem(item) {
15784
16020
  if (!item || typeof item !== "object" || Array.isArray(item))
15785
16021
  return false;
15786
16022
  const record = item;
15787
- if (record.oldString !== "")
16023
+ const oldStringEmpty = hasOwn(record, "oldString") && isNullOrEmptyString(record.oldString);
16024
+ if (!oldStringEmpty)
15788
16025
  return false;
15789
- const newStringEmpty = !hasOwn(record, "newString") || record.newString === "" || record.newString === null;
15790
- if (!newStringEmpty)
16026
+ if (record.oldString === null && ["startLine", "endLine"].some((key) => hasOwn(record, key) && record[key] !== null)) {
15791
16027
  return false;
15792
- return !hasOwn(record, "content") || record.content === "" || record.content === null;
16028
+ }
16029
+ return (!hasOwn(record, "newString") || isNullOrEmptyString(record.newString)) && (!hasOwn(record, "content") || isNullOrEmptyString(record.content)) && (!hasOwn(record, "replaceAll") || record.replaceAll === null || record.replaceAll === false) && isDefaultOccurrence(record.occurrence);
16030
+ }
16031
+ function hasMeaningfulFindPayload(item) {
16032
+ return isNonEmptyString(item.oldString);
16033
+ }
16034
+ function isDefaultOccurrence(value) {
16035
+ return value === undefined || value === null || value === 1;
15793
16036
  }
15794
16037
  function normalizeEditArraySentinels(record) {
15795
16038
  const value = record.edits;
@@ -15842,9 +16085,33 @@ function parseEditArray(value) {
15842
16085
  }
15843
16086
  return value;
15844
16087
  }
15845
- function stripLineRangeSentinels(item) {
15846
- const hasRangeField = ["startLine", "endLine", "content"].some((key) => hasOwn(item, key));
15847
- if (!hasRangeField)
16088
+ function normalizeEditItemSentinels(item) {
16089
+ const hadRangeFields = ["startLine", "endLine", "content"].some((key) => hasOwn(item, key));
16090
+ for (const key of [
16091
+ "oldString",
16092
+ "newString",
16093
+ "replaceAll",
16094
+ "occurrence",
16095
+ "startLine",
16096
+ "endLine",
16097
+ "content"
16098
+ ]) {
16099
+ if (item[key] === null)
16100
+ delete item[key];
16101
+ }
16102
+ const contentIsEmpty = item.content === "";
16103
+ if (hasMeaningfulFindPayload(item) && (!hasOwn(item, "content") || contentIsEmpty)) {
16104
+ for (const key of ["startLine", "endLine", "content"])
16105
+ delete item[key];
16106
+ if (hadRangeFields) {
16107
+ if (item.replaceAll === false)
16108
+ delete item.replaceAll;
16109
+ if (hasOwn(item, "occurrence") && isDefaultOccurrence(item.occurrence))
16110
+ delete item.occurrence;
16111
+ }
16112
+ return;
16113
+ }
16114
+ if (!isNonEmptyString(item.content))
15848
16115
  return;
15849
16116
  if (item.oldString === "")
15850
16117
  delete item.oldString;
@@ -15852,7 +16119,7 @@ function stripLineRangeSentinels(item) {
15852
16119
  delete item.newString;
15853
16120
  if (item.replaceAll === false)
15854
16121
  delete item.replaceAll;
15855
- if (item.occurrence === 1)
16122
+ if (hasOwn(item, "occurrence") && isDefaultOccurrence(item.occurrence))
15856
16123
  delete item.occurrence;
15857
16124
  }
15858
16125
  function normalizeEditItem(value, index) {
@@ -15863,7 +16130,7 @@ function normalizeEditItem(value, index) {
15863
16130
  const item = copyOwnProperties(source);
15864
16131
  normalizeItemAlias(item, "oldString", "oldText");
15865
16132
  normalizeItemAlias(item, "newString", "newText");
15866
- stripLineRangeSentinels(item);
16133
+ normalizeEditItemSentinels(item);
15867
16134
  const hasFindField = ["oldString", "newString", "replaceAll", "occurrence"].some((key) => hasOwn(item, key));
15868
16135
  const hasRangeField = ["startLine", "endLine", "content"].some((key) => hasOwn(item, key));
15869
16136
  if (hasFindField && hasRangeField) {
@@ -16556,17 +16823,17 @@ function collectStructuredExtras(response) {
16556
16823
  return stringifyData(extras);
16557
16824
  }
16558
16825
  // ../aft-bridge/dist/transport-factory.js
16559
- import { existsSync as existsSync8 } from "node:fs";
16826
+ import { existsSync as existsSync9 } from "node:fs";
16560
16827
  import { homedir as homedir11 } from "node:os";
16561
- import { isAbsolute as isAbsolute5, join as join11 } from "node:path";
16828
+ import { isAbsolute as isAbsolute5, join as join12 } from "node:path";
16562
16829
  function resolveConnectionFilePath(raw) {
16563
16830
  const trimmed = raw.trim();
16564
16831
  if (trimmed.startsWith("~")) {
16565
- return join11(homedir11(), trimmed.slice(1).replace(/^[/\\]/, ""));
16832
+ return join12(homedir11(), trimmed.slice(1).replace(/^[/\\]/, ""));
16566
16833
  }
16567
16834
  if (isAbsolute5(trimmed))
16568
16835
  return trimmed;
16569
- return join11(homedir11(), trimmed);
16836
+ return join12(homedir11(), trimmed);
16570
16837
  }
16571
16838
  var SUBC_CLIENT_REAPER_PROCESS_KEY = "subc_client_reaper";
16572
16839
  function booleanOption(value) {
@@ -16640,7 +16907,7 @@ async function createConcreteAftTransportPool(opts) {
16640
16907
  consumerIdentity: opts.subcConsumerIdentity,
16641
16908
  onBgEventsNudge: opts.onBgEventsNudge,
16642
16909
  onBgEventsNudgeRef: opts.onBgEventsNudgeRef,
16643
- lifecycleDemandCheck: opts.subcLifecycleDemandCheck ?? ((root) => existsSync8(root))
16910
+ lifecycleDemandCheck: opts.subcLifecycleDemandCheck ?? ((root) => existsSync9(root))
16644
16911
  });
16645
16912
  }
16646
16913
  return new BridgePool(opts.binaryPath, opts.poolOptions, opts.configOverrides);
@@ -16751,6 +17018,8 @@ var UNKNOWN_COMPLETION_CAP = 32;
16751
17018
  var DEFAULT_SESSION_ID2 = "__default__";
16752
17019
  var LOG_PREFIX = "[aft-pi] bg-notifications:";
16753
17020
  var SUBC_NUDGE_LOG_INTERVAL_MS = 60000;
17021
+ var DEFAULT_BG_HOP_TIMEOUT_MS = 15000;
17022
+ var bgHopTimeoutMs = DEFAULT_BG_HOP_TIMEOUT_MS;
16754
17023
  var subcNudgesInFlight = new Map;
16755
17024
  var subcNudgeLogState = new Map;
16756
17025
  function normalizeSessionID(sessionID) {
@@ -16974,11 +17243,21 @@ function handleSubcBgEventsNudge(drainContext) {
16974
17243
  }
16975
17244
  async function handleSubcBgEventsNudgeOnce(drainContext) {
16976
17245
  bridgeForDrain(drainContext);
16977
- stateFor(drainContext.sessionID).wakeDeferredTaskIds.clear();
17246
+ const state = stateFor(drainContext.sessionID);
17247
+ state.wakeDeferredTaskIds.clear();
17248
+ rearmHardStoppedWake(state, drainContext);
16978
17249
  await triggerWakeIfPending(drainContext, false, true, true);
16979
17250
  }
16980
17251
  function logSubcNudgeLifecycle(drainContext, kind, message) {
16981
- const key = `${kind}\x00${drainContext.directory}\x00${drainContext.sessionID}`;
17252
+ logDeliveryHop(drainContext, kind, message, {
17253
+ event: "subc_bg_nudge_delivery",
17254
+ cause: kind
17255
+ });
17256
+ }
17257
+ function logDeliveryHop(drainContext, kind, message, data, level = "info") {
17258
+ const sessionID = normalizeSessionID(drainContext.sessionID);
17259
+ const taskKey = typeof data.task_id === "string" ? data.task_id : Array.isArray(data.task_ids) ? data.task_ids.join(",") : "";
17260
+ const key = `${kind}\x00${taskKey}\x00${drainContext.directory}\x00${sessionID}`;
16982
17261
  const now = Date.now();
16983
17262
  const state = subcNudgeLogState.get(key);
16984
17263
  if (state && now - state.lastEmittedAt < SUBC_NUDGE_LOG_INTERVAL_MS) {
@@ -16987,13 +17266,48 @@ function logSubcNudgeLifecycle(drainContext, kind, message) {
16987
17266
  }
16988
17267
  const suppressed = state?.suppressed ?? 0;
16989
17268
  subcNudgeLogState.set(key, { lastEmittedAt: now, suppressed: 0 });
16990
- sessionLog(drainContext.sessionID, `${LOG_PREFIX} ${message}`, {
16991
- event: "subc_bg_nudge_delivery",
16992
- cause: kind,
17269
+ const payload = {
17270
+ ...data,
16993
17271
  canonical_root: drainContext.directory,
16994
17272
  suppressed
17273
+ };
17274
+ if (level === "warn") {
17275
+ sessionWarn(sessionID, `${LOG_PREFIX} ${message}`, payload);
17276
+ } else {
17277
+ sessionLog(sessionID, `${LOG_PREFIX} ${message}`, payload);
17278
+ }
17279
+ }
17280
+ function logPerTaskDeliveryHop(drainContext, kind, message, taskIDs, data, level = "info") {
17281
+ for (const taskID of taskIDs) {
17282
+ logDeliveryHop(drainContext, kind, message, { ...data, task_id: taskID, task_ids: taskIDs }, level);
17283
+ }
17284
+ }
17285
+ function rearmHardStoppedWake(state, drainContext) {
17286
+ if (!state.wakeHardStopped)
17287
+ return;
17288
+ state.wakeHardStopped = false;
17289
+ state.wakeRetryAttempts = 0;
17290
+ state.retryDelayMs = null;
17291
+ logDeliveryHop(drainContext, "inject-rearmed", "delivery retry re-armed", {
17292
+ event: "bash_completion_inject_rearmed",
17293
+ task_ids: state.pendingCompletions.map((completion) => completion.task_id)
16995
17294
  });
16996
17295
  }
17296
+ async function withBgHopTimeout(operation, label) {
17297
+ let timer;
17298
+ try {
17299
+ return await Promise.race([
17300
+ operation,
17301
+ new Promise((_, reject) => {
17302
+ timer = setTimeout(() => reject(new Error(`${label} timed out after ${bgHopTimeoutMs}ms`)), bgHopTimeoutMs);
17303
+ timer.unref?.();
17304
+ })
17305
+ ]);
17306
+ } finally {
17307
+ if (timer)
17308
+ clearTimeout(timer);
17309
+ }
17310
+ }
16997
17311
  async function triggerWakeIfPending(drainContext, skipDrain, includeDeferredCompletions = true, forceDrain = false) {
16998
17312
  const state = stateFor(drainContext.sessionID);
16999
17313
  if (!skipDrain && (forceDrain || state.outstandingTaskIds.size > 0 || !state.forcedDrainCompleted)) {
@@ -17003,8 +17317,25 @@ async function triggerWakeIfPending(drainContext, skipDrain, includeDeferredComp
17003
17317
  if (!hasWakeEligiblePending(state, includeDeferredCompletions))
17004
17318
  return;
17005
17319
  scheduleWake(state, async (reminder, deliveredCompletions) => {
17006
- drainContext.runtime.sendUserMessage(reminder, { deliverAs: "steer" });
17007
17320
  const ackTargetIds = deliveredCompletions.map((completion) => completion.task_id);
17321
+ logPerTaskDeliveryHop(drainContext, "inject-start", "session inject start", ackTargetIds, {
17322
+ event: "bash_completion_wake_send_user_message_start",
17323
+ attempt: state.wakeRetryAttempts + 1
17324
+ });
17325
+ try {
17326
+ drainContext.runtime.sendUserMessage(reminder, { deliverAs: "steer" });
17327
+ } catch (err) {
17328
+ logPerTaskDeliveryHop(drainContext, "inject-error", "session inject failed", ackTargetIds, {
17329
+ event: "bash_completion_wake_send_user_message_error",
17330
+ attempt: state.wakeRetryAttempts + 1,
17331
+ cause: err instanceof Error ? err.message : String(err)
17332
+ }, "warn");
17333
+ throw err;
17334
+ }
17335
+ logPerTaskDeliveryHop(drainContext, "inject-ok", "session inject ok", ackTargetIds, {
17336
+ event: "bash_completion_wake_send_user_message_ok",
17337
+ attempt: state.wakeRetryAttempts + 1
17338
+ });
17008
17339
  markDeliveredAwaitingAck(state, ackTargetIds);
17009
17340
  const acked = await ackCompletions(drainContext, deliveredCompletions);
17010
17341
  if (acked)
@@ -17020,7 +17351,12 @@ async function triggerWakeIfPending(drainContext, skipDrain, includeDeferredComp
17020
17351
  });
17021
17352
  return;
17022
17353
  }
17023
- 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)}`);
17354
+ if (hardStopped) {
17355
+ logDeliveryHop(drainContext, "inject-hard-stopped", "delivery retries paused", {
17356
+ event: "bash_completion_inject_hard_stopped",
17357
+ cause: err instanceof Error ? err.message : String(err)
17358
+ });
17359
+ }
17024
17360
  }, includeDeferredCompletions);
17025
17361
  }
17026
17362
  function formatSystemReminder(completions) {
@@ -17090,12 +17426,21 @@ async function drainCompletions(drainContext) {
17090
17426
  const bridge = bridgeForDrain(drainContext);
17091
17427
  const state = stateFor(drainContext.sessionID);
17092
17428
  const params = drainContext.sessionID ? { session_id: drainContext.sessionID } : {};
17093
- const response = await bridge.send("bash_drain_completions", params);
17429
+ const response = await withBgHopTimeout(bridge.send("bash_drain_completions", params, { timeoutMs: bgHopTimeoutMs }), "bash_drain_completions");
17094
17430
  if (response.success === false) {
17095
- sessionWarn(drainContext.sessionID ?? "", `${LOG_PREFIX} drain failed: ${String(response.message ?? "unknown error")}`);
17431
+ logDeliveryHop(drainContext, "drain-error", "drain failed", {
17432
+ event: "bash_completion_drain_error",
17433
+ cause: String(response.message ?? "unknown error")
17434
+ }, "warn");
17096
17435
  return;
17097
17436
  }
17098
17437
  state.forcedDrainCompleted = true;
17438
+ const drainedTaskIds = Array.isArray(response.bg_completions) ? response.bg_completions.filter(isBgCompletion).map((completion) => completion.task_id) : [];
17439
+ logDeliveryHop(drainContext, "drain-ok", "drain ok", {
17440
+ event: "bash_completion_drain_ok",
17441
+ count: drainedTaskIds.length,
17442
+ task_ids: drainedTaskIds
17443
+ });
17099
17444
  const reackTaskIds = reconcileAwaitingAck(state, response.bg_completions);
17100
17445
  ingestDrainedBgCompletions(drainContext.sessionID, response.bg_completions);
17101
17446
  if (reackTaskIds.length > 0) {
@@ -17111,7 +17456,10 @@ async function drainCompletions(drainContext) {
17111
17456
  } catch (err) {
17112
17457
  if (drainContext.nudgeRef && isTerminalNudgeError(err))
17113
17458
  throw err;
17114
- sessionWarn(drainContext.sessionID ?? "", `${LOG_PREFIX} drain failed: ${err instanceof Error ? err.message : String(err)}`);
17459
+ logDeliveryHop(drainContext, "drain-error", "drain failed", {
17460
+ event: "bash_completion_drain_error",
17461
+ cause: err instanceof Error ? err.message : String(err)
17462
+ }, "warn");
17115
17463
  }
17116
17464
  }
17117
17465
  async function ackCompletions(drainContext, completions) {
@@ -17121,16 +17469,25 @@ async function ackCompletions(drainContext, completions) {
17121
17469
  try {
17122
17470
  const bridge = bridgeForDrain(drainContext);
17123
17471
  const params = drainContext.sessionID ? { session_id: drainContext.sessionID, task_ids: taskIds } : { task_ids: taskIds };
17124
- const response = await bridge.send("bash_ack_completions", params);
17472
+ const response = await withBgHopTimeout(bridge.send("bash_ack_completions", params, { timeoutMs: bgHopTimeoutMs }), "bash_ack_completions");
17125
17473
  if (response.success === false) {
17126
- sessionWarn(drainContext.sessionID ?? "", `${LOG_PREFIX} ack failed: ${String(response.message ?? "unknown error")}`);
17474
+ logPerTaskDeliveryHop(drainContext, "ack-error", "ack failed", taskIds, {
17475
+ event: "bash_completion_ack_error",
17476
+ cause: String(response.message ?? "unknown error")
17477
+ }, "warn");
17127
17478
  return false;
17128
17479
  }
17480
+ logPerTaskDeliveryHop(drainContext, "ack-ok", "ack ok", taskIds, {
17481
+ event: "bash_completion_ack_ok"
17482
+ });
17129
17483
  return true;
17130
17484
  } catch (err) {
17131
17485
  if (drainContext.nudgeRef && isTerminalNudgeError(err))
17132
17486
  throw err;
17133
- sessionWarn(drainContext.sessionID ?? "", `${LOG_PREFIX} ack failed: ${err instanceof Error ? err.message : String(err)}`);
17487
+ logPerTaskDeliveryHop(drainContext, "ack-error", "ack failed", taskIds, {
17488
+ event: "bash_completion_ack_error",
17489
+ cause: err instanceof Error ? err.message : String(err)
17490
+ }, "warn");
17134
17491
  return false;
17135
17492
  }
17136
17493
  }
@@ -17461,7 +17818,7 @@ import {
17461
17818
  // package.json
17462
17819
  var package_default = {
17463
17820
  name: "@cortexkit/aft-pi",
17464
- version: "0.53.0",
17821
+ version: "0.55.0",
17465
17822
  type: "module",
17466
17823
  description: "Pi coding agent extension for Agent File Tools (AFT) — tree-sitter and LSP-powered code analysis",
17467
17824
  main: "dist/index.js",
@@ -17484,19 +17841,19 @@ var package_default = {
17484
17841
  "test:unit": "bun test src/__tests__ --path-ignore-patterns 'src/__tests__/e2e/**'"
17485
17842
  },
17486
17843
  dependencies: {
17487
- "@cortexkit/aft-bridge": "0.53.0",
17844
+ "@cortexkit/aft-bridge": "0.55.0",
17488
17845
  "comment-json": "^5.0.0",
17489
17846
  diff: "^8.0.4",
17490
17847
  typebox: "1.1.38",
17491
17848
  zod: "^4.4.3"
17492
17849
  },
17493
17850
  optionalDependencies: {
17494
- "@cortexkit/aft-darwin-arm64": "0.53.0",
17495
- "@cortexkit/aft-darwin-x64": "0.53.0",
17496
- "@cortexkit/aft-linux-arm64": "0.53.0",
17497
- "@cortexkit/aft-linux-x64": "0.53.0",
17498
- "@cortexkit/aft-win32-arm64": "0.53.0",
17499
- "@cortexkit/aft-win32-x64": "0.53.0"
17851
+ "@cortexkit/aft-darwin-arm64": "0.55.0",
17852
+ "@cortexkit/aft-darwin-x64": "0.55.0",
17853
+ "@cortexkit/aft-linux-arm64": "0.55.0",
17854
+ "@cortexkit/aft-linux-x64": "0.55.0",
17855
+ "@cortexkit/aft-win32-arm64": "0.55.0",
17856
+ "@cortexkit/aft-win32-x64": "0.55.0"
17500
17857
  },
17501
17858
  devDependencies: {
17502
17859
  "@earendil-works/pi-coding-agent": "^0.80.2",
@@ -17764,7 +18121,7 @@ function appendDegradedStatus(lines, status, markdown) {
17764
18121
  }
17765
18122
 
17766
18123
  // src/tools/_shared.ts
17767
- import { existsSync as existsSync9 } from "node:fs";
18124
+ import { existsSync as existsSync10 } from "node:fs";
17768
18125
  import { Type } from "typebox";
17769
18126
  var optionalInt = (min, max, description = "(integer)") => Type.Optional(Type.Union([Type.Integer({ minimum: min, maximum: max }), Type.String()], {
17770
18127
  description
@@ -17785,7 +18142,7 @@ function withPathAliasPreparation(tool) {
17785
18142
  };
17786
18143
  }
17787
18144
  function bridgeFor(ctx, cwd) {
17788
- if (!existsSync9(cwd)) {
18145
+ if (!existsSync10(cwd)) {
17789
18146
  throw new Error(`project directory no longer exists: ${cwd} (stale restored session?)`);
17790
18147
  }
17791
18148
  return ctx.pool.getBridge(cwd);
@@ -17833,8 +18190,10 @@ async function callBridge(bridge, command, params = {}, extCtx, options) {
17833
18190
  return response;
17834
18191
  }
17835
18192
  async function callToolCall(bridge, name, rawArgs = {}, extCtx, options) {
18193
+ return callToolCallForSession(bridge, name, rawArgs, extCtx ? resolveSessionId(extCtx) : undefined, extCtx, options);
18194
+ }
18195
+ async function callToolCallForSession(bridge, name, rawArgs, sessionId, extCtx, options) {
17836
18196
  const timeoutMs = timeoutForCommand(name);
17837
- const sessionId = extCtx ? resolveSessionId(extCtx) : undefined;
17838
18197
  const sendOptions = {
17839
18198
  ...timeoutMs !== undefined ? { timeoutMs } : {},
17840
18199
  configureWarningClient: extCtx,
@@ -18157,7 +18516,7 @@ function registerStatusCommand(pi, ctx) {
18157
18516
  }
18158
18517
 
18159
18518
  // src/config.ts
18160
- import { existsSync as existsSync10, readFileSync as readFileSync7, renameSync as renameSync5, unlinkSync as unlinkSync5, writeFileSync as writeFileSync4 } from "node:fs";
18519
+ import { existsSync as existsSync11, readFileSync as readFileSync7, renameSync as renameSync5, unlinkSync as unlinkSync5, writeFileSync as writeFileSync4 } from "node:fs";
18161
18520
  import { homedir as homedir12 } from "node:os";
18162
18521
  import { isAbsolute as isAbsolute6 } from "node:path";
18163
18522
  var import_comment_json = __toESM(require_src2(), 1);
@@ -19773,8 +20132,8 @@ function emoji() {
19773
20132
  }
19774
20133
  var ipv4 = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/;
19775
20134
  var ipv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/;
19776
- var mac = (delimiter2) => {
19777
- const escapedDelim = escapeRegex(delimiter2 ?? ":");
20135
+ var mac = (delimiter3) => {
20136
+ const escapedDelim = escapeRegex(delimiter3 ?? ":");
19778
20137
  return new RegExp(`^(?:[0-9A-F]{2}${escapedDelim}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${escapedDelim}){5}[0-9a-f]{2}$`);
19779
20138
  };
19780
20139
  var cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/;
@@ -32439,6 +32798,26 @@ function date4(params) {
32439
32798
  // ../../node_modules/.bun/zod@4.4.3/node_modules/zod/v4/classic/external.js
32440
32799
  config(en_default());
32441
32800
  // src/config.ts
32801
+ var ACTIVE_HARNESS = "pi";
32802
+ function isConfigRecord(value) {
32803
+ return value !== null && typeof value === "object" && !Array.isArray(value);
32804
+ }
32805
+ function normalizeConfigForActiveHarness(value) {
32806
+ const config2 = stripHarnessSpecificConfigKeys(value, OPENCODE_ONLY_KEYS);
32807
+ if (!isConfigRecord(config2) || !isConfigRecord(config2.harnesses))
32808
+ return config2;
32809
+ const override = config2.harnesses[ACTIVE_HARNESS];
32810
+ return {
32811
+ ...config2,
32812
+ harnesses: override === undefined ? {} : { [ACTIVE_HARNESS]: stripHarnessSpecificConfigKeys(override, OPENCODE_ONLY_KEYS) }
32813
+ };
32814
+ }
32815
+ function warnIgnoredNestedHarnesses(rawConfig, configPath) {
32816
+ const override = isConfigRecord(rawConfig.harnesses) ? rawConfig.harnesses[ACTIVE_HARNESS] : undefined;
32817
+ if (isConfigRecord(override) && Object.hasOwn(override, "harnesses")) {
32818
+ warn2(`Ignoring nested harnesses in harnesses.${ACTIVE_HARNESS} from ${configPath}; harness overrides cannot recurse`);
32819
+ }
32820
+ }
32442
32821
  var DEFAULT_INSPECT_DIAGNOSTICS_TIMEOUT_MS = 120000;
32443
32822
  var MIN_INSPECT_DIAGNOSTICS_TIMEOUT_MS = 1e4;
32444
32823
  var MAX_INSPECT_DIAGNOSTICS_TIMEOUT_MS = 600000;
@@ -32457,6 +32836,7 @@ function resolveBashConfig(config2) {
32457
32836
  const surfaceDefaultEnabled = surface !== "minimal";
32458
32837
  const reminderEnabled = (typeof top === "object" && top !== null ? top.long_running_reminder_enabled : undefined) ?? legacy?.long_running_reminder_enabled;
32459
32838
  const reminderInterval = (typeof top === "object" && top !== null ? top.long_running_reminder_interval_ms : undefined) ?? legacy?.long_running_reminder_interval_ms;
32839
+ const topDetachOnUserMessage = typeof top === "object" && top !== null ? top.detach_on_user_message ?? true : true;
32460
32840
  const rawForegroundWait = typeof top === "object" && top !== null ? top.foreground_wait_window_ms : undefined;
32461
32841
  const foregroundWaitWindowMs = Math.max(FOREGROUND_WAIT_WINDOW_MIN_MS, rawForegroundWait ?? FOREGROUND_WAIT_WINDOW_DEFAULT_MS);
32462
32842
  const base = {
@@ -32465,9 +32845,11 @@ function resolveBashConfig(config2) {
32465
32845
  compress: false,
32466
32846
  background: false,
32467
32847
  host_fallback: false,
32848
+ detach_on_user_message: true,
32468
32849
  long_running_reminder_enabled: reminderEnabled,
32469
32850
  long_running_reminder_interval_ms: reminderInterval,
32470
- foreground_wait_window_ms: foregroundWaitWindowMs
32851
+ foreground_wait_window_ms: foregroundWaitWindowMs,
32852
+ powershell_tool: typeof top === "object" && top !== null ? top.powershell_tool ?? false : false
32471
32853
  };
32472
32854
  if (top === false)
32473
32855
  return base;
@@ -32481,7 +32863,8 @@ function resolveBashConfig(config2) {
32481
32863
  rewrite: top.rewrite ?? true,
32482
32864
  compress: top.compress ?? true,
32483
32865
  background: top.background ?? true,
32484
- host_fallback: top.host_fallback ?? false
32866
+ host_fallback: top.host_fallback ?? false,
32867
+ detach_on_user_message: topDetachOnUserMessage
32485
32868
  };
32486
32869
  }
32487
32870
  const hasLegacyFeatureFlag = legacy && (legacy.rewrite !== undefined || legacy.compress !== undefined || legacy.background !== undefined);
@@ -32595,9 +32978,11 @@ var BashFeaturesSchema = exports_external.object({
32595
32978
  compress: exports_external.boolean().optional(),
32596
32979
  background: exports_external.boolean().optional(),
32597
32980
  host_fallback: exports_external.boolean().optional(),
32981
+ detach_on_user_message: exports_external.boolean().optional(),
32598
32982
  long_running_reminder_enabled: exports_external.boolean().optional(),
32599
32983
  long_running_reminder_interval_ms: exports_external.number().int().positive().optional(),
32600
- foreground_wait_window_ms: exports_external.number().int().positive().optional()
32984
+ foreground_wait_window_ms: exports_external.number().int().positive().optional(),
32985
+ powershell_tool: exports_external.boolean().optional()
32601
32986
  });
32602
32987
  var BashConfigSchema = exports_external.union([exports_external.boolean(), BashFeaturesSchema]);
32603
32988
  var SandboxConfigSchema = exports_external.object({
@@ -32617,6 +33002,9 @@ var GhShimConfigSchema = exports_external.object({
32617
33002
  enabled: exports_external.boolean().optional(),
32618
33003
  binary_path: exports_external.string().trim().refine(isAbsolute6, "gh_shim.binary_path must be absolute").optional()
32619
33004
  });
33005
+ var GhReadConfigSchema = exports_external.object({
33006
+ enabled: exports_external.boolean().optional()
33007
+ });
32620
33008
  var GitCoAuthorSchema = exports_external.string().trim().refine((value) => value === "off" || value === "auto" || /^[^<>\r\n]+\s+<[^<>\s@]+@[^<>\s@]+>$/.test(value), "git.co_author must be 'off', 'auto', or an explicit 'Name <email>' identity");
32621
33009
  var GitConfigSchema = exports_external.object({
32622
33010
  co_author: GitCoAuthorSchema.optional()
@@ -32640,7 +33028,7 @@ var BackupConfigSchema = exports_external.object({
32640
33028
  max_depth: exports_external.number().int().positive().optional(),
32641
33029
  max_file_size: exports_external.number().int().positive().optional()
32642
33030
  });
32643
- var AftConfigSchema = exports_external.preprocess((value) => stripHarnessSpecificConfigKeys(value, OPENCODE_ONLY_KEYS), exports_external.object({
33031
+ var AftConfigFieldsSchema = exports_external.object({
32644
33032
  $schema: exports_external.string().optional(),
32645
33033
  enabled: exports_external.boolean().optional(),
32646
33034
  edit_mode: exports_external.enum(["default", "hashline"]).optional(),
@@ -32671,8 +33059,22 @@ var AftConfigSchema = exports_external.preprocess((value) => stripHarnessSpecifi
32671
33059
  bridge: BridgeConfigSchema.optional(),
32672
33060
  subc: SubcConfigSchema.optional(),
32673
33061
  gh_shim: GhShimConfigSchema.optional(),
33062
+ gh_read: GhReadConfigSchema.optional(),
32674
33063
  git: GitConfigSchema.optional()
33064
+ });
33065
+ var HarnessOverrideSchema = exports_external.preprocess((value) => {
33066
+ if (!isConfigRecord(value) || !Object.hasOwn(value, "harnesses"))
33067
+ return value;
33068
+ const { harnesses: _nestedHarnesses, ...override } = value;
33069
+ return override;
33070
+ }, AftConfigFieldsSchema.strict());
33071
+ var AftConfigSchema = exports_external.preprocess(normalizeConfigForActiveHarness, AftConfigFieldsSchema.extend({
33072
+ harnesses: exports_external.record(exports_external.string(), HarnessOverrideSchema).optional()
32675
33073
  }).strict());
33074
+ function applyActiveHarnessOverride(config2) {
33075
+ const { harnesses: _harnesses, ...base } = config2;
33076
+ return { ...base, ...config2.harnesses?.[ACTIVE_HARNESS] };
33077
+ }
32676
33078
  var CONFIG_MIGRATIONS = [
32677
33079
  { oldKey: "experimental_search_index", newPath: ["search_index"] },
32678
33080
  { oldKey: "experimental_semantic_search", newPath: ["semantic_search"] },
@@ -32783,7 +33185,7 @@ function migrateExperimentalBashBlock(rawConfig, configPath, logger) {
32783
33185
  return movedKeys;
32784
33186
  }
32785
33187
  function migrateAftConfigFile2(configPath, logger = { log: log2, warn: warn2 }) {
32786
- if (!existsSync10(configPath)) {
33188
+ if (!existsSync11(configPath)) {
32787
33189
  return { migrated: false, oldKeys: [] };
32788
33190
  }
32789
33191
  let tmpPath = null;
@@ -32842,7 +33244,7 @@ function warnIgnoredHarnessSpecificConfigKeys(config2, configPath) {
32842
33244
  }
32843
33245
  function loadConfigFromPath(configPath) {
32844
33246
  try {
32845
- if (!existsSync10(configPath))
33247
+ if (!existsSync11(configPath))
32846
33248
  return null;
32847
33249
  const content = readFileSync7(configPath, "utf-8");
32848
33250
  const rawConfig = import_comment_json.parse(content);
@@ -32853,14 +33255,15 @@ function loadConfigFromPath(configPath) {
32853
33255
  migrateRawConfig(rawConfig, configPath, { log: log2, warn: warn2 });
32854
33256
  const cleanConfig = stripJsoncSymbols(rawConfig);
32855
33257
  warnIgnoredHarnessSpecificConfigKeys(cleanConfig, configPath);
33258
+ warnIgnoredNestedHarnesses(cleanConfig, configPath);
32856
33259
  const result = AftConfigSchema.safeParse(cleanConfig);
32857
33260
  if (result.success) {
32858
33261
  log2(`Config loaded from ${configPath}`);
32859
- return result.data;
33262
+ return applyActiveHarnessOverride(result.data);
32860
33263
  }
32861
33264
  const errorMsg = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ");
32862
33265
  warn2(`Config validation error in ${configPath}: ${errorMsg}`);
32863
- return parseConfigPartially(cleanConfig);
33266
+ return applyActiveHarnessOverride(parseConfigPartially(cleanConfig));
32864
33267
  } catch (err) {
32865
33268
  const errorMsg = err instanceof Error ? err.message : String(err);
32866
33269
  error2(`Error loading config from ${configPath}: ${errorMsg}`);
@@ -33059,6 +33462,8 @@ function getStrippedTopLevelKeys(override) {
33059
33462
  stripped.push("subc");
33060
33463
  if (override.gh_shim !== undefined)
33061
33464
  stripped.push("gh_shim");
33465
+ if (override.gh_read !== undefined)
33466
+ stripped.push("gh_read");
33062
33467
  if (override.disabled_tools?.includes("aft_safety"))
33063
33468
  stripped.push("disabled_tools.aft_safety");
33064
33469
  return stripped;
@@ -33172,11 +33577,11 @@ function loadAftConfig(projectDirectory) {
33172
33577
  }
33173
33578
 
33174
33579
  // src/lsp-auto-install.ts
33175
- import { spawn as spawn3 } from "node:child_process";
33580
+ import { spawn as spawn4 } from "node:child_process";
33176
33581
  import { createHash as createHash4 } from "node:crypto";
33177
33582
  import {
33178
33583
  createReadStream,
33179
- existsSync as existsSync12,
33584
+ existsSync as existsSync13,
33180
33585
  mkdirSync as mkdirSync7,
33181
33586
  readFileSync as readFileSync10,
33182
33587
  renameSync as renameSync6,
@@ -33184,9 +33589,10 @@ import {
33184
33589
  statSync as statSync8,
33185
33590
  writeFileSync as writeFileSync6
33186
33591
  } from "node:fs";
33187
- import { join as join14 } from "node:path";
33592
+ import { join as join15 } from "node:path";
33188
33593
 
33189
33594
  // src/lsp-cache.ts
33595
+ import { randomUUID as randomUUID2 } from "node:crypto";
33190
33596
  import {
33191
33597
  closeSync as closeSync5,
33192
33598
  mkdirSync as mkdirSync6,
@@ -33196,23 +33602,23 @@ import {
33196
33602
  unlinkSync as unlinkSync6,
33197
33603
  writeFileSync as writeFileSync5
33198
33604
  } from "node:fs";
33199
- import { join as join12 } from "node:path";
33605
+ import { join as join13 } from "node:path";
33200
33606
  function lspCacheRoot() {
33201
33607
  return getAftLspPackagesDir();
33202
33608
  }
33203
33609
  function lspPackageDir(npmPackage) {
33204
- return join12(lspCacheRoot(), encodeURIComponent(npmPackage));
33610
+ return join13(lspCacheRoot(), encodeURIComponent(npmPackage));
33205
33611
  }
33206
33612
  function lspBinaryPath(npmPackage, binary) {
33207
- return join12(lspPackageDir(npmPackage), "node_modules", ".bin", binary);
33613
+ return join13(lspPackageDir(npmPackage), "node_modules", ".bin", binary);
33208
33614
  }
33209
33615
  function lspBinDir(npmPackage) {
33210
- return join12(lspPackageDir(npmPackage), "node_modules", ".bin");
33616
+ return join13(lspPackageDir(npmPackage), "node_modules", ".bin");
33211
33617
  }
33212
33618
  function isInstalled(npmPackage, binary) {
33213
33619
  for (const candidate of lspBinaryCandidates(binary)) {
33214
33620
  try {
33215
- if (statSync7(join12(lspBinDir(npmPackage), candidate)).isFile())
33621
+ if (statSync7(join13(lspBinDir(npmPackage), candidate)).isFile())
33216
33622
  return true;
33217
33623
  } catch {}
33218
33624
  }
@@ -33232,13 +33638,13 @@ function writeInstalledMetaIn(installDir, version2, sha256) {
33232
33638
  installedAt: new Date().toISOString(),
33233
33639
  ...sha256 ? { sha256 } : {}
33234
33640
  };
33235
- writeFileSync5(join12(installDir, INSTALLED_META_FILE), JSON.stringify(meta3), "utf8");
33641
+ writeFileSync5(join13(installDir, INSTALLED_META_FILE), JSON.stringify(meta3), "utf8");
33236
33642
  } catch (err) {
33237
33643
  log2(`[lsp-cache] failed to write installed-meta in ${installDir}: ${err}`);
33238
33644
  }
33239
33645
  }
33240
33646
  function readInstalledMetaIn(installDir) {
33241
- const path2 = join12(installDir, INSTALLED_META_FILE);
33647
+ const path2 = join13(installDir, INSTALLED_META_FILE);
33242
33648
  try {
33243
33649
  if (!statSync7(path2).isFile())
33244
33650
  return null;
@@ -33262,33 +33668,52 @@ function readInstalledMeta(packageKey) {
33262
33668
  return readInstalledMetaIn(lspPackageDir(packageKey));
33263
33669
  }
33264
33670
  function lockPath(npmPackage) {
33265
- return join12(lspPackageDir(npmPackage), ".aft-installing");
33671
+ return join13(lspPackageDir(npmPackage), ".aft-installing");
33266
33672
  }
33267
33673
  var STALE_LOCK_MS2 = 30 * 60 * 1000;
33268
33674
  function acquireInstallLock(lockKey) {
33269
33675
  mkdirSync6(lspPackageDir(lockKey), { recursive: true });
33270
33676
  const lock = lockPath(lockKey);
33677
+ const token = randomUUID2();
33271
33678
  const tryClaim = () => {
33272
33679
  try {
33273
33680
  const fd = openSync5(lock, "wx");
33274
33681
  try {
33275
33682
  writeFileSync5(fd, `${process.pid}
33276
33683
  ${new Date().toISOString()}
33684
+ ${token}
33277
33685
  `);
33278
33686
  } finally {
33279
33687
  closeSync5(fd);
33280
33688
  }
33281
- return true;
33689
+ let retained = false;
33690
+ let released = false;
33691
+ return {
33692
+ retain() {
33693
+ retained = true;
33694
+ },
33695
+ release() {
33696
+ if (released)
33697
+ return;
33698
+ released = true;
33699
+ if (retained) {
33700
+ warn2(`[lsp] retaining install lock for ${lockKey} after unconfirmed npm termination; ` + `cross-process retries remain blocked until stale-lock recovery`);
33701
+ return;
33702
+ }
33703
+ releaseInstallLock(lockKey, token);
33704
+ }
33705
+ };
33282
33706
  } catch (err) {
33283
33707
  const code = err.code;
33284
33708
  if (code === "EEXIST")
33285
- return false;
33709
+ return null;
33286
33710
  warn2(`[lsp] unexpected error acquiring install lock for ${lockKey}: ${err}`);
33287
- return false;
33711
+ return null;
33288
33712
  }
33289
33713
  };
33290
- if (tryClaim())
33291
- return true;
33714
+ const claimed = tryClaim();
33715
+ if (claimed)
33716
+ return claimed;
33292
33717
  let owningPid = null;
33293
33718
  let lockMtimeMs = 0;
33294
33719
  try {
@@ -33306,7 +33731,7 @@ ${new Date().toISOString()}
33306
33731
  const skipLiveness = process.platform === "win32";
33307
33732
  const ownerAlive = !skipLiveness && owningPid !== null && isProcessAlive3(owningPid);
33308
33733
  if (skipLiveness ? ageWithinFresh : ownerAlive && ageWithinFresh) {
33309
- return false;
33734
+ return null;
33310
33735
  }
33311
33736
  log2(`[lsp] reclaiming install lock for ${lockKey} (owner_pid=${owningPid ?? "unknown"}, alive=${ownerAlive}, age_ms=${age})`);
33312
33737
  try {
@@ -33325,16 +33750,18 @@ function isProcessAlive3(pid) {
33325
33750
  return true;
33326
33751
  }
33327
33752
  }
33328
- function releaseInstallLock(lockKey) {
33753
+ function releaseInstallLock(lockKey, acquisitionToken) {
33329
33754
  const lock = lockPath(lockKey);
33330
33755
  try {
33331
33756
  let owningPid = null;
33757
+ let owningToken = "";
33332
33758
  try {
33333
33759
  const raw = readFileSync8(lock, "utf8");
33334
- const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
33335
- const parsed = Number.parseInt(firstLine, 10);
33760
+ const lines = raw.split(/\r?\n/);
33761
+ const parsed = Number.parseInt(lines[0]?.trim() ?? "", 10);
33336
33762
  if (Number.isFinite(parsed) && parsed > 0)
33337
33763
  owningPid = parsed;
33764
+ owningToken = lines[2]?.trim() ?? "";
33338
33765
  } catch (readErr) {
33339
33766
  const code = readErr.code;
33340
33767
  if (code === "ENOENT")
@@ -33342,8 +33769,8 @@ function releaseInstallLock(lockKey) {
33342
33769
  warn2(`[lsp] could not read install lock for ${lockKey} during release: ${readErr}`);
33343
33770
  return;
33344
33771
  }
33345
- if (owningPid !== process.pid) {
33346
- log2(`[lsp] not releasing install lock for ${lockKey}: owned by pid ${owningPid ?? "unknown"} (we are ${process.pid})`);
33772
+ if (owningPid !== process.pid || owningToken !== acquisitionToken) {
33773
+ log2(`[lsp] not releasing install lock for ${lockKey}: acquisition ownership changed`);
33347
33774
  return;
33348
33775
  }
33349
33776
  try {
@@ -33359,17 +33786,18 @@ function releaseInstallLock(lockKey) {
33359
33786
  }
33360
33787
  }
33361
33788
  async function withInstallLock(lockKey, task) {
33362
- if (!acquireInstallLock(lockKey))
33789
+ const lease = acquireInstallLock(lockKey);
33790
+ if (!lease)
33363
33791
  return null;
33364
33792
  try {
33365
- return await task();
33793
+ return await task(lease);
33366
33794
  } finally {
33367
- releaseInstallLock(lockKey);
33795
+ lease.release();
33368
33796
  }
33369
33797
  }
33370
33798
  var VERSION_CHECK_FILE = ".aft-version-check";
33371
33799
  function readVersionCheck(npmPackage) {
33372
- const file2 = join12(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
33800
+ const file2 = join13(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
33373
33801
  try {
33374
33802
  const raw = readFileSync8(file2, "utf8");
33375
33803
  const parsed = JSON.parse(raw);
@@ -33386,7 +33814,7 @@ function readVersionCheck(npmPackage) {
33386
33814
  }
33387
33815
  function writeVersionCheck(npmPackage, latest) {
33388
33816
  mkdirSync6(lspPackageDir(npmPackage), { recursive: true });
33389
- const file2 = join12(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
33817
+ const file2 = join13(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
33390
33818
  const record2 = {
33391
33819
  last_checked: new Date().toISOString(),
33392
33820
  latest_eligible: latest
@@ -33548,8 +33976,8 @@ var NPM_LSP_TABLE = [
33548
33976
  ];
33549
33977
 
33550
33978
  // src/lsp-project-relevance.ts
33551
- import { existsSync as existsSync11, readdirSync as readdirSync4, readFileSync as readFileSync9 } from "node:fs";
33552
- import { join as join13 } from "node:path";
33979
+ import { existsSync as existsSync12, readdirSync as readdirSync4, readFileSync as readFileSync9 } from "node:fs";
33980
+ import { join as join14 } from "node:path";
33553
33981
  var MAX_WALK_DIRS = 200;
33554
33982
  var MAX_WALK_DEPTH = 4;
33555
33983
  var NOISE_DIRS = new Set([
@@ -33566,7 +33994,7 @@ function hasRootMarker(projectRoot, rootMarkers) {
33566
33994
  if (!rootMarkers)
33567
33995
  return false;
33568
33996
  for (const marker of rootMarkers) {
33569
- if (existsSync11(join13(projectRoot, marker)))
33997
+ if (existsSync12(join14(projectRoot, marker)))
33570
33998
  return true;
33571
33999
  }
33572
34000
  return false;
@@ -33590,7 +34018,7 @@ function hasPackageJsonDep(projectRoot, depNames) {
33590
34018
  }
33591
34019
  function readPackageJson(projectRoot) {
33592
34020
  try {
33593
- const raw = readFileSync9(join13(projectRoot, "package.json"), "utf8");
34021
+ const raw = readFileSync9(join14(projectRoot, "package.json"), "utf8");
33594
34022
  const parsed = JSON.parse(raw);
33595
34023
  if (typeof parsed !== "object" || parsed === null)
33596
34024
  return null;
@@ -33620,7 +34048,7 @@ function relevantExtensionsInProject(projectRoot, extToServer) {
33620
34048
  for (const entry of entries) {
33621
34049
  if (entry.isDirectory()) {
33622
34050
  if (current.depth < MAX_WALK_DEPTH && !NOISE_DIRS.has(entry.name.toLowerCase())) {
33623
- queue.push({ dir: join13(current.dir, entry.name), depth: current.depth + 1 });
34051
+ queue.push({ dir: join14(current.dir, entry.name), depth: current.depth + 1 });
33624
34052
  }
33625
34053
  continue;
33626
34054
  }
@@ -33709,6 +34137,7 @@ function buildExtensionMap(specs) {
33709
34137
  return byExt;
33710
34138
  }
33711
34139
  var inFlightAutoInstalls = new Set;
34140
+ var quarantinedNpmInstalls = new Set;
33712
34141
  function trackInFlightAutoInstall(controller, promise2) {
33713
34142
  const entry = { controller, promise: promise2 };
33714
34143
  inFlightAutoInstalls.add(entry);
@@ -33747,8 +34176,8 @@ async function resolveTargetVersion(spec, config2, fetchImpl = fetch) {
33747
34176
  }
33748
34177
  function ensureInstallAnchor(cwd) {
33749
34178
  try {
33750
- const stub = join14(cwd, "package.json");
33751
- if (!existsSync12(stub)) {
34179
+ const stub = join15(cwd, "package.json");
34180
+ if (!existsSync13(stub)) {
33752
34181
  writeFileSync6(stub, `${JSON.stringify({ name: "aft-lsp-cache", version: "0.0.0", private: true })}
33753
34182
  `);
33754
34183
  }
@@ -33756,7 +34185,7 @@ function ensureInstallAnchor(cwd) {
33756
34185
  warn2(`[lsp] could not write package.json stub in ${cwd}: ${err}`);
33757
34186
  }
33758
34187
  }
33759
- function runInstall(spec, version2, cwd, signal) {
34188
+ function runInstall(spec, version2, cwd, installLock, signal) {
33760
34189
  return new Promise((resolve8) => {
33761
34190
  const target = `${spec.npm}@${version2}`;
33762
34191
  log2(`[lsp] installing ${target} to ${cwd}`);
@@ -33772,19 +34201,25 @@ function runInstall(spec, version2, cwd, signal) {
33772
34201
  return;
33773
34202
  }
33774
34203
  ensureInstallAnchor(cwd);
33775
- const child = spawn3(npm.command, ["install", "--no-save", "--ignore-scripts", "--silent", target], {
34204
+ const invocation = npmInvocation(npm, [
34205
+ "install",
34206
+ "--no-save",
34207
+ "--ignore-scripts",
34208
+ "--silent",
34209
+ target
34210
+ ]);
34211
+ const child = spawn4(invocation.command, invocation.args, {
33776
34212
  stdio: ["ignore", "pipe", "pipe"],
33777
34213
  cwd,
33778
- env: npmSpawnEnv(npm)
34214
+ env: { ...npmSpawnEnv(npm), ...invocation.env },
34215
+ windowsVerbatimArguments: invocation.windowsVerbatimArguments
33779
34216
  });
33780
34217
  child.unref();
33781
34218
  let stderrBuf = "";
33782
34219
  let settled = false;
33783
- let killTimer = null;
34220
+ let terminationPromise = null;
33784
34221
  const cleanup = () => {
33785
34222
  signal?.removeEventListener("abort", onAbort);
33786
- if (killTimer)
33787
- clearTimeout(killTimer);
33788
34223
  };
33789
34224
  const finish = (ok) => {
33790
34225
  if (settled)
@@ -33793,14 +34228,31 @@ function runInstall(spec, version2, cwd, signal) {
33793
34228
  cleanup();
33794
34229
  resolve8(ok);
33795
34230
  };
34231
+ const childCompletedSuccessfully = () => child.exitCode === 0 && child.signalCode === null;
34232
+ const finishAfterConfirmedTermination = () => {
34233
+ const completedSuccessfully = childCompletedSuccessfully();
34234
+ if (completedSuccessfully)
34235
+ log2(`[lsp] installed ${target}`);
34236
+ finish(completedSuccessfully);
34237
+ };
33796
34238
  const onAbort = () => {
34239
+ if (childCompletedSuccessfully()) {
34240
+ finishAfterConfirmedTermination();
34241
+ return;
34242
+ }
33797
34243
  warn2(`[lsp] install ${target} aborted during shutdown`);
33798
- child.kill("SIGTERM");
33799
- killTimer = setTimeout(() => {
33800
- if (!settled)
33801
- child.kill("SIGKILL");
33802
- }, 5000);
33803
- killTimer.unref?.();
34244
+ terminationPromise ??= terminateNpmProcessTree(child, invocation);
34245
+ terminationPromise.then(finishAfterConfirmedTermination, (terminationError) => {
34246
+ if (child.pid === undefined) {
34247
+ error2(`[lsp] install ${target} failed to spawn during shutdown: ${terminationError}`);
34248
+ finish(false);
34249
+ return;
34250
+ }
34251
+ quarantinedNpmInstalls.add(spec.npm);
34252
+ installLock.retain();
34253
+ error2(`[lsp] install ${target} termination outcome unknown; quarantining retries for this session: ${String(terminationError)}`);
34254
+ finish(false);
34255
+ });
33804
34256
  };
33805
34257
  signal?.addEventListener("abort", onAbort, { once: true });
33806
34258
  if (signal?.aborted)
@@ -33814,10 +34266,16 @@ function runInstall(spec, version2, cwd, signal) {
33814
34266
  }
33815
34267
  });
33816
34268
  child.on("error", (err) => {
33817
- error2(`[lsp] install ${target} failed to spawn: ${err}`);
34269
+ if (settled)
34270
+ return;
34271
+ error2(`[lsp] install ${target} child-process error: ${err}`);
34272
+ if (terminationPromise && child.pid !== undefined)
34273
+ return;
33818
34274
  finish(false);
33819
34275
  });
33820
34276
  child.on("exit", (code) => {
34277
+ if (settled || terminationPromise)
34278
+ return;
33821
34279
  if (code === 0) {
33822
34280
  log2(`[lsp] installed ${target}`);
33823
34281
  finish(true);
@@ -33830,7 +34288,13 @@ ${stderrBuf.trim()}`);
33830
34288
  });
33831
34289
  }
33832
34290
  async function ensureServerInstalled(spec, config2, fetchImpl, signal) {
33833
- const outcome = await withInstallLock(spec.npm, async () => {
34291
+ if (quarantinedNpmInstalls.has(spec.npm)) {
34292
+ return {
34293
+ started: false,
34294
+ reason: "install blocked after unconfirmed npm termination; restart the host to retry"
34295
+ };
34296
+ }
34297
+ const outcome = await withInstallLock(spec.npm, async (installLock) => {
33834
34298
  const { version: version2, probe } = await resolveTargetVersion(spec, config2, fetchImpl);
33835
34299
  if (!version2) {
33836
34300
  const installed = isInstalled(spec.npm, spec.binary);
@@ -33866,7 +34330,7 @@ async function ensureServerInstalled(spec, config2, fetchImpl, signal) {
33866
34330
  log2(`[lsp] reinstalling ${spec.npm}@${version2}: no installed-version metadata recorded`);
33867
34331
  }
33868
34332
  }
33869
- const ok = await runInstall(spec, version2, cachedPackageDir(spec.npm), signal).catch((err) => {
34333
+ const ok = await runInstall(spec, version2, cachedPackageDir(spec.npm), installLock, signal).catch((err) => {
33870
34334
  error2(`[lsp] background install ${spec.npm} crashed: ${err}`);
33871
34335
  return false;
33872
34336
  });
@@ -33939,10 +34403,10 @@ function sha256OfFileSync(path2) {
33939
34403
  }
33940
34404
  function quarantineCachedNpmInstall(spec, reason) {
33941
34405
  const packageDir = lspPackageDir(spec.npm);
33942
- const dest = join14(packageDir, "..", ".quarantine", encodeURIComponent(spec.npm), `${Date.now()}`);
34406
+ const dest = join15(packageDir, "..", ".quarantine", encodeURIComponent(spec.npm), `${Date.now()}`);
33943
34407
  warn2(`[lsp] tofu_mismatch ${spec.npm}: ${reason}; quarantining ${packageDir} -> ${dest}`);
33944
34408
  try {
33945
- mkdirSync7(join14(dest, ".."), { recursive: true });
34409
+ mkdirSync7(join15(dest, ".."), { recursive: true });
33946
34410
  rmSync4(dest, { recursive: true, force: true });
33947
34411
  renameSync6(packageDir, dest);
33948
34412
  } catch (err) {
@@ -34030,7 +34494,7 @@ import {
34030
34494
  copyFileSync as copyFileSync4,
34031
34495
  createReadStream as createReadStream2,
34032
34496
  createWriteStream as createWriteStream3,
34033
- existsSync as existsSync13,
34497
+ existsSync as existsSync14,
34034
34498
  lstatSync as lstatSync2,
34035
34499
  mkdirSync as mkdirSync8,
34036
34500
  readdirSync as readdirSync5,
@@ -34043,7 +34507,7 @@ import {
34043
34507
  unlinkSync as unlinkSync7,
34044
34508
  writeFileSync as writeFileSync7
34045
34509
  } from "node:fs";
34046
- import { dirname as dirname6, join as join15, relative as relative3, resolve as resolve8 } from "node:path";
34510
+ import { dirname as dirname6, join as join16, relative as relative3, resolve as resolve8 } from "node:path";
34047
34511
  import { Readable as Readable3 } from "node:stream";
34048
34512
  import { pipeline as pipeline3 } from "node:stream/promises";
34049
34513
 
@@ -34136,23 +34600,23 @@ function ghCacheRoot() {
34136
34600
  return getAftLspBinariesDir();
34137
34601
  }
34138
34602
  function ghPackageDir(spec) {
34139
- return join15(ghCacheRoot(), spec.id);
34603
+ return join16(ghCacheRoot(), spec.id);
34140
34604
  }
34141
34605
  function ghBinDir(spec) {
34142
- return join15(ghPackageDir(spec), "bin");
34606
+ return join16(ghPackageDir(spec), "bin");
34143
34607
  }
34144
34608
  function ghExtractDir(spec) {
34145
- return join15(ghPackageDir(spec), "extracted");
34609
+ return join16(ghPackageDir(spec), "extracted");
34146
34610
  }
34147
34611
  var INSTALLED_META_FILE2 = ".aft-installed";
34148
34612
  function ghBinaryPath(spec, platform) {
34149
34613
  const ext = platform === "win32" ? ".exe" : "";
34150
- return join15(ghBinDir(spec), `${spec.binary}${ext}`);
34614
+ return join16(ghBinDir(spec), `${spec.binary}${ext}`);
34151
34615
  }
34152
34616
  function isGithubInstalled(spec, platform) {
34153
34617
  for (const candidate of ghBinaryCandidates(spec, platform)) {
34154
34618
  try {
34155
- if (statSync9(join15(ghBinDir(spec), candidate)).isFile())
34619
+ if (statSync9(join16(ghBinDir(spec), candidate)).isFile())
34156
34620
  return true;
34157
34621
  } catch {}
34158
34622
  }
@@ -34165,7 +34629,7 @@ function ghBinaryCandidates(spec, platform) {
34165
34629
  }
34166
34630
  function readGithubInstalledMetaIn(installDir) {
34167
34631
  try {
34168
- const path2 = join15(installDir, INSTALLED_META_FILE2);
34632
+ const path2 = join16(installDir, INSTALLED_META_FILE2);
34169
34633
  if (!statSync9(path2).isFile())
34170
34634
  return null;
34171
34635
  const parsed = JSON.parse(readFileSync11(path2, "utf8"));
@@ -34192,7 +34656,7 @@ function writeGithubInstalledMetaIn(installDir, version2, binarySha256, archiveS
34192
34656
  binarySha256,
34193
34657
  ...archiveSha256 ? { archiveSha256 } : {}
34194
34658
  };
34195
- writeFileSync7(join15(installDir, INSTALLED_META_FILE2), JSON.stringify(meta3), "utf8");
34659
+ writeFileSync7(join16(installDir, INSTALLED_META_FILE2), JSON.stringify(meta3), "utf8");
34196
34660
  } catch (err) {
34197
34661
  warn2(`[lsp] failed to write github installed metadata in ${installDir}: ${err}`);
34198
34662
  }
@@ -34441,7 +34905,7 @@ function validateExtraction(stagingRoot) {
34441
34905
  throw new Error(`failed to read staging dir ${dir}: ${err}`);
34442
34906
  }
34443
34907
  for (const entry of entries) {
34444
- const full = join15(dir, entry);
34908
+ const full = join16(dir, entry);
34445
34909
  let lst;
34446
34910
  try {
34447
34911
  lst = lstatSync2(full);
@@ -34529,7 +34993,7 @@ function extractArchiveSafely(archivePath, destDir, archiveType) {
34529
34993
  }
34530
34994
  function quarantineCachedGithubInstall(spec, reason) {
34531
34995
  const packageDir = ghPackageDir(spec);
34532
- const dest = join15(ghCacheRoot(), ".quarantine", encodeURIComponent(spec.id), `${Date.now()}`);
34996
+ const dest = join16(ghCacheRoot(), ".quarantine", encodeURIComponent(spec.id), `${Date.now()}`);
34533
34997
  warn2(`[lsp] tofu_mismatch ${spec.id}: ${reason}; quarantining ${packageDir} -> ${dest}`);
34534
34998
  try {
34535
34999
  mkdirSync8(dirname6(dest), { recursive: true });
@@ -34542,7 +35006,7 @@ function quarantineCachedGithubInstall(spec, reason) {
34542
35006
  function validateCachedGithubInstall(spec, platform) {
34543
35007
  const packageDir = ghPackageDir(spec);
34544
35008
  const meta3 = readGithubInstalledMetaIn(packageDir);
34545
- const binaryPath = ghBinaryCandidates(spec, platform).map((candidate) => join15(ghBinDir(spec), candidate)).find((candidate) => {
35009
+ const binaryPath = ghBinaryCandidates(spec, platform).map((candidate) => join16(ghBinDir(spec), candidate)).find((candidate) => {
34546
35010
  try {
34547
35011
  return statSync9(candidate).isFile();
34548
35012
  } catch {
@@ -34620,7 +35084,7 @@ async function downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl,
34620
35084
  }
34621
35085
  const pkgDir = ghPackageDir(spec);
34622
35086
  const extractDir = ghExtractDir(spec);
34623
- const archivePath = join15(pkgDir, expected.name);
35087
+ const archivePath = join16(pkgDir, expected.name);
34624
35088
  log2(`[lsp] downloading ${spec.id} ${tag} → ${matchingAsset.url}`);
34625
35089
  try {
34626
35090
  await downloadFile(matchingAsset.url, archivePath, fetchImpl, matchingAsset.size, signal);
@@ -34660,8 +35124,8 @@ async function downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl,
34660
35124
  unlinkSync7(archivePath);
34661
35125
  } catch {}
34662
35126
  }
34663
- const innerBinaryPath = join15(extractDir, spec.binaryPathInArchive(platform, arch, version2));
34664
- if (!existsSync13(innerBinaryPath)) {
35127
+ const innerBinaryPath = join16(extractDir, spec.binaryPathInArchive(platform, arch, version2));
35128
+ if (!existsSync14(innerBinaryPath)) {
34665
35129
  error2(`[lsp] ${spec.id}: extracted binary not found at ${innerBinaryPath}`);
34666
35130
  return null;
34667
35131
  }
@@ -34745,7 +35209,7 @@ function runGithubAutoInstall(relevantServers, config2, fetchImpl = fetch) {
34745
35209
  if (!host) {
34746
35210
  for (const spec of GITHUB_LSP_TABLE) {
34747
35211
  try {
34748
- if (existsSync13(ghBinDir(spec))) {
35212
+ if (existsSync14(ghBinDir(spec))) {
34749
35213
  cachedBinDirs.push(ghBinDir(spec));
34750
35214
  }
34751
35215
  } catch {}
@@ -34972,6 +35436,26 @@ function sendFeatureAnnouncement(version2, features, footer, storageDir) {
34972
35436
 
34973
35437
  // src/bash-wait-detach.ts
34974
35438
  var BASH_TRANSPORT_TIMEOUT_MS = 30000;
35439
+ var BASH_WAIT_DETACH_MAGIC_KEYWORD = "&detach";
35440
+ var EMPTY_DETACH_MESSAGE = "(requested background detach)";
35441
+ var STANDALONE_DETACH_KEYWORD_SOURCE = `(^|[^\\p{L}\\p{N}_])${BASH_WAIT_DETACH_MAGIC_KEYWORD}(?![\\p{L}\\p{N}_])`;
35442
+ var STANDALONE_DETACH_KEYWORD_PATTERN = new RegExp(STANDALONE_DETACH_KEYWORD_SOURCE, "u");
35443
+ var STANDALONE_DETACH_KEYWORDS_PATTERN = new RegExp(STANDALONE_DETACH_KEYWORD_SOURCE, "gu");
35444
+ function containsStandaloneDetachKeyword(messageText) {
35445
+ return STANDALONE_DETACH_KEYWORD_PATTERN.test(messageText);
35446
+ }
35447
+ function stripStandaloneDetachKeywords(messageText) {
35448
+ return messageText.replace(STANDALONE_DETACH_KEYWORDS_PATTERN, "$1");
35449
+ }
35450
+ function stripUserMessageDetachKeyword(messageText) {
35451
+ if (!containsStandaloneDetachKeyword(messageText))
35452
+ return messageText;
35453
+ const stripped = stripStandaloneDetachKeywords(messageText).replace(/[ \t]{2,}/g, " ");
35454
+ return stripped.trim() === "" ? EMPTY_DETACH_MESSAGE : stripped;
35455
+ }
35456
+ function shouldDetachBashWaitOnUserMessage(config2, messageText) {
35457
+ return resolveBashConfig(config2).detach_on_user_message || containsStandaloneDetachKeyword(messageText);
35458
+ }
34975
35459
  async function sendBashWaitDetach(bridge, sessionID) {
34976
35460
  const response = await bridge.send("bash_wait_detach", { session_id: sessionID }, { keepBridgeOnTimeout: true, transportTimeoutMs: BASH_TRANSPORT_TIMEOUT_MS });
34977
35461
  if (response.success === false) {
@@ -35433,8 +35917,16 @@ function formatReadAttachmentText(attachment) {
35433
35917
  }
35434
35918
  return `Read attachment [${mime}]${size ? ` ${size}` : ""}`;
35435
35919
  }
35920
+ var ISSUE_AND_PR_READ_DESCRIPTION = "GitHub issues and pull requests can be read with `issue://NUMBER` and `pr://NUMBER` (or `issue://OWNER/REPO/NUMBER` and `pr://OWNER/REPO/NUMBER`).";
35921
+ var READ_DESCRIPTION = "Read file contents with line numbers. Backed by AFT's indexed Rust reader — faster than the built-in `read` on large repos. Images are returned as attachments on vision-capable models; PDFs and non-vision models are not yet supported.";
35922
+ function readDescription(ghReadEnabled) {
35923
+ return ghReadEnabled ? `${READ_DESCRIPTION} ${ISSUE_AND_PR_READ_DESCRIPTION}` : READ_DESCRIPTION;
35924
+ }
35925
+ function visionCapabilityForPiModel(extCtx) {
35926
+ return Array.isArray(extCtx.model?.input) ? extCtx.model.input.includes("image") : undefined;
35927
+ }
35436
35928
  function modelSupportsImages(extCtx) {
35437
- return Array.isArray(extCtx.model?.input) && extCtx.model.input.includes("image");
35929
+ return visionCapabilityForPiModel(extCtx) === true;
35438
35930
  }
35439
35931
  var NON_VISION_IMAGE_NOTE = "[Current model does not support images. The image will be omitted from this request.]";
35440
35932
  function containsPath(parent, child) {
@@ -35652,7 +36144,7 @@ function registerHoistedTools(pi, ctx, surface) {
35652
36144
  pi.registerTool(withPathAliasPreparation({
35653
36145
  name: readName,
35654
36146
  label: readName,
35655
- description: "Read file contents with line numbers. Backed by AFT's indexed Rust reader — faster than the built-in `read` on large repos. Images are returned as attachments on vision-capable models; PDFs and non-vision models are not yet supported.",
36147
+ description: readDescription(ctx.config.gh_read?.enabled === true),
35656
36148
  promptSnippet: "Read file contents (supports offset/limit for large files)",
35657
36149
  promptGuidelines: [`Use ${readName} to examine files instead of cat or sed.`],
35658
36150
  parameters: ReadParams,
@@ -35680,6 +36172,9 @@ function registerHoistedTools(pi, ctx, surface) {
35680
36172
  rawArgs.offset = offset;
35681
36173
  if (limit !== undefined)
35682
36174
  rawArgs.limit = limit;
36175
+ const visionCapability = visionCapabilityForPiModel(extCtx);
36176
+ if (visionCapability !== undefined)
36177
+ rawArgs.vision_capability = visionCapability;
35683
36178
  const response = await callToolCall(bridge, "read", rawArgs, extCtx);
35684
36179
  if (response.success === false) {
35685
36180
  throw new Error(response.text || response.message || "read failed");
@@ -35763,8 +36258,9 @@ PDFs aren't supported on the Pi harness yet.`, response);
35763
36258
  parameters: HashlineEditParams,
35764
36259
  async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
35765
36260
  const bridge = bridgeFor(ctx, extCtx.cwd);
36261
+ const sessionId = resolveSessionId(extCtx);
35766
36262
  const rawArgs = { patch: params.patch };
35767
- const preflight = await callToolCall(bridge, "hashline_preflight", rawArgs, extCtx);
36263
+ const preflight = await callToolCallForSession(bridge, "hashline_preflight", rawArgs, sessionId, extCtx);
35768
36264
  if (preflight.success === false)
35769
36265
  throw toolErrorFromResponse("edit", preflight);
35770
36266
  for (const target of [
@@ -35775,10 +36271,12 @@ PDFs aren't supported on the Pi harness yet.`, response);
35775
36271
  restrictToProjectRoot: surface.restrictToProjectRoot
35776
36272
  });
35777
36273
  }
35778
- const preview = await callToolCall(bridge, "edit", rawArgs, extCtx, { preview: true });
36274
+ const preview = await callToolCallForSession(bridge, "edit", rawArgs, sessionId, extCtx, {
36275
+ preview: true
36276
+ });
35779
36277
  if (preview.success === false)
35780
36278
  throw toolErrorFromResponse("edit", preview);
35781
- const response = await callToolCall(bridge, "edit", rawArgs, extCtx);
36279
+ const response = await callToolCallForSession(bridge, "edit", rawArgs, sessionId, extCtx);
35782
36280
  if (response.success === false)
35783
36281
  throw toolErrorFromResponse("edit", response);
35784
36282
  return buildMutationResult(response);
@@ -36328,7 +36826,7 @@ var BashBaseParams = {
36328
36826
  description: "Human-readable description shown in UI logs. Helps users understand what the command does without reading shell syntax."
36329
36827
  })),
36330
36828
  wait: Type4.Optional(Type4.Boolean({
36331
- description: "When true, run in the foreground without auto-promoting and wait until the command finishes or reaches its timeout; if you send a new message, the wait detaches to background. Use only when you know the result is required before doing anything else."
36829
+ description: "When true, run in the foreground without auto-promoting and wait until the command finishes or reaches its timeout; a new user message detaches by default, while `bash.detach_on_user_message: false` keeps it blocking unless the message contains the literal `&detach`. The token is stripped before delivery; the rest of the message is preserved, and a token-only message becomes `(requested background detach)`. Use only when you know the result is required before doing anything else."
36332
36830
  })),
36333
36831
  sandbox: Type4.Optional(Type4.Literal("host", {
36334
36832
  description: "Request one-command approval to run unsandboxed on the host; use only when native sandboxing blocks required work, and note that it is a no-op when sandboxing is disabled."
@@ -36423,11 +36921,17 @@ ${ask.command ?? ""}
36423
36921
  Working directory:
36424
36922
  ${ask.cwd ?? ""}`;
36425
36923
  }
36426
- async function callBashWithPermissionLoop(bridge, params, extCtx, options) {
36924
+ function piPowerShellAskText(command) {
36925
+ return `PowerShell commands are conservatively approved one at a time because AFT's POSIX shell scanner cannot safely interpret PowerShell syntax.
36926
+
36927
+ Exact command:
36928
+ ${command}`;
36929
+ }
36930
+ async function callBashWithPermissionLoop(bridge, params, extCtx, options, bridgeCommand = "bash") {
36427
36931
  const granted = Array.isArray(params.permissions_granted) ? params.permissions_granted.filter((value) => typeof value === "string") : [];
36428
36932
  for (let round = 0;round < 8; round++) {
36429
36933
  try {
36430
- return await callBashBridge(bridge, "bash", { ...params, ...granted.length > 0 ? { permissions_granted: granted } : {} }, extCtx, options);
36934
+ return await callBashBridge(bridge, bridgeCommand, { ...params, ...granted.length > 0 ? { permissions_granted: granted } : {} }, extCtx, options);
36431
36935
  } catch (error53) {
36432
36936
  if (!(error53 instanceof BridgeError)) {
36433
36937
  if (error53 instanceof Error && error53.message.includes("permission_required")) {
@@ -36438,19 +36942,30 @@ async function callBashWithPermissionLoop(bridge, params, extCtx, options) {
36438
36942
  if (error53.code !== "permission_required")
36439
36943
  throw error53;
36440
36944
  const asks = Array.isArray(error53.response?.asks) ? error53.response.asks : [];
36441
- if (asks.length === 0 || asks.some((ask) => ask.kind !== "escalation")) {
36442
- throw new Error("Permission ask reached Pi adapter without a host escalation grant — this is a bug.");
36443
- }
36444
- if (!extCtx.hasUI || typeof extCtx.ui?.confirm !== "function") {
36445
- throw new BridgeError("Permission denied: host escalation approval requires an interactive UI.", "permission_denied");
36945
+ if (asks.length === 0 || !extCtx.hasUI || typeof extCtx.ui?.confirm !== "function") {
36946
+ throw new BridgeError("Permission denied: command approval requires an interactive UI.", "permission_denied");
36446
36947
  }
36447
36948
  for (const ask of asks) {
36448
- const approved = await extCtx.ui.confirm("Run command unsandboxed on host?", piEscalationAskText(ask), { signal: extCtx.signal });
36949
+ if (ask.kind === "escalation") {
36950
+ const approved2 = await extCtx.ui.confirm("Run command unsandboxed on host?", piEscalationAskText(ask), { signal: extCtx.signal });
36951
+ if (!approved2) {
36952
+ throw new BridgeError("Permission denied: unsandboxed host execution was denied.", "permission_denied");
36953
+ }
36954
+ if (ask.grant_id && !granted.includes(ask.grant_id))
36955
+ granted.push(ask.grant_id);
36956
+ continue;
36957
+ }
36958
+ if (bridgeCommand !== "powershell") {
36959
+ throw new Error("Permission ask reached Pi adapter without a host escalation grant — this is a bug.");
36960
+ }
36961
+ const approved = await extCtx.ui.confirm("Allow PowerShell command?", piPowerShellAskText(String(params.command ?? "")), { signal: extCtx.signal });
36449
36962
  if (!approved) {
36450
- throw new BridgeError("Permission denied: unsandboxed host execution was denied.", "permission_denied");
36963
+ throw new BridgeError("Permission denied: PowerShell command was denied.", "permission_denied");
36964
+ }
36965
+ for (const grant of [...ask.always ?? [], ...ask.patterns ?? []]) {
36966
+ if (!granted.includes(grant))
36967
+ granted.push(grant);
36451
36968
  }
36452
- if (ask.grant_id && !granted.includes(ask.grant_id))
36453
- granted.push(ask.grant_id);
36454
36969
  }
36455
36970
  }
36456
36971
  }
@@ -36477,23 +36992,27 @@ function getBashSpawnHook(pi) {
36477
36992
  }
36478
36993
  return api2.hooks?.bashSpawn;
36479
36994
  }
36480
- function registerBashTool(pi, ctx, aftSearchRegistered = false, registeredName = "bash", registerCompanions = true) {
36481
- const spawnHook = getBashSpawnHook(pi);
36995
+ function registerBashTool(pi, ctx, aftSearchRegistered = false, registeredName = "bash", registerCompanions = true, shell = "bash") {
36996
+ const isPowerShell = shell === "powershell";
36997
+ const spawnHook = isPowerShell ? undefined : getBashSpawnHook(pi);
36482
36998
  const readToolName = registeredName === "bash" ? "read" : "aft_read";
36483
36999
  const grepToolName = registeredName === "bash" ? "grep" : "aft_grep";
36484
37000
  const searchSteer = aftSearchRegistered ? `use \`aft_search\` (concepts, identifiers, regex, literals), \`${readToolName}\`, \`aft_outline\`, or \`aft_zoom\` instead` : `use the \`${grepToolName}\` tool, \`${readToolName}\`, \`aft_outline\`, or \`aft_zoom\` instead`;
36485
37001
  const bashCfg = resolveBashConfig(ctx.config);
36486
37002
  let hostFallbackActive = false;
36487
37003
  const compressionSentence = bashCfg.compress ? " Output is compressed by default; pass `compressed: false` for raw output. Piped commands run verbatim and show the pipeline's output; for AFT's test/build summary, run the runner without `| head`, `| tail`, or `| grep`." : "";
36488
- const tasksSentence = bashCfg.background ? ' Commands run in the foreground and return inline; `wait: true` blocks until a long command finishes instead of auto-promoting, but detaches to background if you send a new message — use it when you need the result before doing anything else; keep it off otherwise so auto-promote can remind you while you work. Use `background: true` yourself ONLY when you have other useful work to do while it runs; then `bash_watch` waits on the task (sync blocks until exit/pattern, async notifies) and `bash_status` peeks at it — never background a command and immediately `bash_watch` it (that wastes a turn for what foreground returns in one), and never loop `bash_status` to wait. `pty: true` runs interactive programs (REPLs, TUIs), implies background, and is driven with `bash_status({ output_mode: "screen" })` plus `bash_write`.' : " Commands run in the foreground to completion; `timeout` is the hard kill cap (default 30 minutes).";
37004
+ const detachSentence = bashCfg.detach_on_user_message ? "A new user message detaches this wait. Set `bash.detach_on_user_message: false` to keep it blocking; even then, a message containing the literal `&detach` forces detachment, and the token is stripped before delivery; the rest of the message is preserved, while a token-only message becomes `(requested background detach)`." : "Because `bash.detach_on_user_message` is false, a new user message leaves this wait blocking; include the literal `&detach` anywhere to force detachment, and the token is stripped before delivery; the rest of the message is preserved, while a token-only message becomes `(requested background detach)`.";
37005
+ const tasksSentence = bashCfg.background ? ` Commands run in the foreground and return inline; \`wait: true\` blocks until a long command finishes instead of auto-promoting; ${detachSentence} Use it when you need the result before doing anything else; keep it off otherwise so auto-promote can remind you while you work. Use \`background: true\` yourself ONLY when you have other useful work to do while it runs; then \`bash_watch\` waits on the task (sync blocks until exit/pattern, async notifies) and \`bash_status\` peeks at it — never background a command and immediately \`bash_watch\` it (that wastes a turn for what foreground returns in one), and never loop \`bash_status\` to wait. \`pty: true\` runs interactive programs (REPLs, TUIs), implies background, and is driven with \`bash_status({ output_mode: "screen" })\` plus \`bash_write\`.` : " Commands run in the foreground to completion; `timeout` is the hard kill cap (default 30 minutes).";
36489
37006
  pi.registerTool({
36490
37007
  name: registeredName,
36491
37008
  label: registeredName,
36492
- description: `Execute shell commands.${compressionSentence}${tasksSentence}
37009
+ description: isPowerShell ? `Execute PowerShell commands through AFT.${compressionSentence}${tasksSentence}
37010
+
37011
+ PowerShell syntax is not analyzed as POSIX shell. Each command requires explicit approval so syntax AFT cannot safely interpret is never auto-allowed.` : `Execute shell commands.${compressionSentence}${tasksSentence}
36493
37012
 
36494
37013
  DO NOT use bash for code search or code exploration. If you are about to run grep, rg, sed, awk, find, or cat through bash to locate or read code: STOP — ${searchSteer}.`,
36495
- promptSnippet: bashCfg.background ? "Run shell commands (timeout in milliseconds; supports workdir, background tasks, compressed output, PTY mode)" : "Run shell commands (timeout in milliseconds; supports workdir and compressed output)",
36496
- promptGuidelines: [
37014
+ promptSnippet: isPowerShell ? "Run PowerShell commands (timeout in milliseconds; supports workdir, background tasks, compressed output, PTY mode)" : bashCfg.background ? "Run shell commands (timeout in milliseconds; supports workdir, background tasks, compressed output, PTY mode)" : "Run shell commands (timeout in milliseconds; supports workdir and compressed output)",
37015
+ promptGuidelines: isPowerShell ? ["Use PowerShell syntax. Every command requires explicit approval."] : [
36497
37016
  `DO NOT use bash for code search or exploration — ${searchSteer}.`,
36498
37017
  "Set compressed: false when you need ANSI color codes in the output.",
36499
37018
  "Piped commands run verbatim and show the pipeline's output; run test/build tools without pipes when you need AFT's summary."
@@ -36552,7 +37071,8 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
36552
37071
  foreground_orchestrate: true,
36553
37072
  block_to_completion: blockToCompletion,
36554
37073
  wait: requestedWait,
36555
- sandbox: params.sandbox
37074
+ sandbox: params.sandbox,
37075
+ ...isPowerShell ? { shell: "powershell" } : {}
36556
37076
  }, extCtx, {
36557
37077
  transportTimeoutMs: orchestratedTransportTimeoutMs(blockToCompletion, requestedWait, effectiveTimeout, foregroundWaitMs),
36558
37078
  onProgress: ({ text }) => {
@@ -36560,9 +37080,9 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
36560
37080
  const displayText = truncateToVisualLines(streamed, 100);
36561
37081
  onUpdate?.(bashResult(displayText, { streaming: true }));
36562
37082
  }
36563
- });
37083
+ }, isPowerShell ? "powershell" : "bash");
36564
37084
  } catch (error53) {
36565
- if (!bashCfg2.host_fallback || !isBashTransportDeadError(error53))
37085
+ if (isPowerShell || !bashCfg2.host_fallback || !isBashTransportDeadError(error53))
36566
37086
  throw error53;
36567
37087
  if (rawRequestedBackground) {
36568
37088
  throw new Error(`${BASH_HOST_FALLBACK_REFUSAL}; background:true is unsupported.`);
@@ -36609,7 +37129,7 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
36609
37129
  task_id: taskId
36610
37130
  };
36611
37131
  const output = response.output ?? "";
36612
- return bashResult(usedHostFallback ? output : withBashHints(output, bridgeCommand, aftSearchRegistered, extCtx.cwd), details);
37132
+ return bashResult(usedHostFallback || isPowerShell ? output : withBashHints(output, bridgeCommand, aftSearchRegistered, extCtx.cwd), details);
36613
37133
  },
36614
37134
  renderCall(args, theme, context) {
36615
37135
  return renderBashCall(args?.command, args?.description, theme, context);
@@ -37622,25 +38142,39 @@ function formatPhase(entry) {
37622
38142
  return details.length > 0 ? `${entry.id} (${details.join("; ")})` : entry.id;
37623
38143
  }
37624
38144
  function renderInspectTerminal(terminal, serverText) {
37625
- const lines = [terminal.kind];
37626
38145
  if (terminal.kind === "FRESH") {
37627
- lines.push(`wait-stamp: ${terminal.waitStampText ?? "not supplied"}`);
38146
+ const lines = [
38147
+ terminal.kind,
38148
+ `wait-stamp: ${terminal.waitStampText ?? "not supplied"}`
38149
+ ];
38150
+ lines.push(terminal.phases.length > 0 ? `completed phases:
38151
+ ${terminal.phases.map((phase) => `- ${formatPhase(phase)}`).join(`
38152
+ `)}` : "completed phases: none");
38153
+ if (serverText?.trim())
38154
+ lines.push(serverText);
38155
+ return lines.join(`
38156
+ `);
37628
38157
  }
37629
38158
  if (terminal.kind === "PHASE-FAILED") {
37630
- if (terminal.failedPhase)
37631
- lines.push(`failed phase: ${formatPhase(terminal.failedPhase)}`);
37632
- lines.push(`failure reason: ${terminal.failureReason ?? "not supplied"}`);
37633
- if (terminal.failureDetail)
37634
- lines.push(`failure detail: ${terminal.failureDetail}`);
38159
+ const detail = compactTerminalField(terminal.failureDetail, "not supplied");
38160
+ const reason = compactTerminalField(terminal.failureReason, "not supplied");
38161
+ return [
38162
+ `inspect could not complete: ${detail} (${reason}).`,
38163
+ `Completed phases: ${terminal.phases.length}. Retry, or narrow with sections=...`
38164
+ ].join(`
38165
+ `);
37635
38166
  }
37636
- lines.push(terminal.phases.length > 0 ? `completed phases:
37637
- ${terminal.phases.map((phase) => `- ${formatPhase(phase)}`).join(`
37638
- `)}` : "completed phases: none");
37639
- if (serverText?.trim())
37640
- lines.push(serverText);
37641
- return lines.join(`
38167
+ const phaseLabel = terminal.phases.length === 1 ? "phase" : "phases";
38168
+ return [
38169
+ `inspect was interrupted before it could complete (after ${terminal.phases.length} completed ${phaseLabel}); no fresh snapshot was produced.`,
38170
+ "Retry is safe; retry inspect, or narrow with sections=..."
38171
+ ].join(`
37642
38172
  `);
37643
38173
  }
38174
+ function compactTerminalField(value, fallback) {
38175
+ const compact = value?.trim().replace(/\s+/g, " ");
38176
+ return compact || fallback;
38177
+ }
37644
38178
  function diagnosticsSummaryPart(summary) {
37645
38179
  const section = asRecord3(summary?.diagnostics);
37646
38180
  if (!section)
@@ -37762,9 +38296,19 @@ function renderInspectCall(args, theme, context) {
37762
38296
  function renderInspectResult(result, theme, context, options = { expanded: true }) {
37763
38297
  const payload = extractStructuredPayload(result);
37764
38298
  const terminal = parseInspectTerminal(payload);
38299
+ if (terminal) {
38300
+ const sections2 = [renderInspectTerminal(terminal, asString2(asRecord3(payload)?.text))];
38301
+ return collapsibleResult({
38302
+ summary: sections2[0]?.split(`
38303
+ `)[0] ?? "inspect",
38304
+ full: renderSections(sections2, context),
38305
+ expanded: options.expanded,
38306
+ context
38307
+ });
38308
+ }
37765
38309
  if (context.isError)
37766
38310
  return renderErrorResult(result, "inspect failed", theme, context);
37767
- const sections = terminal ? [renderInspectTerminal(terminal, asString2(asRecord3(payload)?.text))] : buildInspectSections(payload, theme);
38311
+ const sections = buildInspectSections(payload, theme);
37768
38312
  return collapsibleResult({
37769
38313
  summary: sections[0] ?? "inspect completed",
37770
38314
  full: renderSections(sections, context),
@@ -38429,7 +38973,7 @@ var SafetyParams = Type12.Object({
38429
38973
  })),
38430
38974
  name: Type12.Optional(Type12.String({ description: "Checkpoint name (required for checkpoint, restore)" })),
38431
38975
  files: Type12.Optional(Type12.Array(Type12.String(), {
38432
- description: "Specific files for checkpoint (optional, defaults to all tracked)"
38976
+ description: "Specific files for checkpoint (optional, defaults to all backup-tracked files; explicit files may be untracked or gitignored)"
38433
38977
  }))
38434
38978
  });
38435
38979
  function buildSafetySections(args, payload, theme) {
@@ -38474,14 +39018,16 @@ function buildSafetySections(args, payload, theme) {
38474
39018
  `${theme.fg("muted", "files")} ${asNumber2(response.file_count) ?? 0}`,
38475
39019
  skipped.length > 0 ? `${theme.fg("warning", "skipped")}
38476
39020
  ${skipped.map((entry) => ` ↳ ${shortenPath2(asString2(entry.file) ?? "(file)")}: ${asString2(entry.error) ?? "unknown error"}`).join(`
38477
- `)}` : theme.fg("muted", "No skipped files.")
38478
- ];
39021
+ `)}` : theme.fg("muted", "No skipped files."),
39022
+ asString2(response.durability)
39023
+ ].filter((section) => Boolean(section));
38479
39024
  }
38480
39025
  if (args.op === "restore") {
38481
39026
  return [
38482
39027
  `${theme.fg("success", "checkpoint restored")} ${theme.fg("accent", asString2(response.name) ?? args.name ?? "(checkpoint)")}`,
38483
- `${theme.fg("muted", "files")} ${asNumber2(response.file_count) ?? 0}`
38484
- ];
39028
+ `${theme.fg("muted", "files")} ${asNumber2(response.file_count) ?? 0}`,
39029
+ asString2(response.durability)
39030
+ ].filter((section) => Boolean(section));
38485
39031
  }
38486
39032
  const checkpoints = asRecords2(response.checkpoints);
38487
39033
  const sections = [
@@ -38489,6 +39035,9 @@ ${skipped.map((entry) => ` ↳ ${shortenPath2(asString2(entry.file) ?? "(file)"
38489
39035
  ];
38490
39036
  if (checkpoints.length === 0) {
38491
39037
  sections.push(theme.fg("muted", "No checkpoints saved."));
39038
+ const durability2 = asString2(response.durability);
39039
+ if (durability2)
39040
+ sections.push(theme.fg("muted", durability2));
38492
39041
  return sections;
38493
39042
  }
38494
39043
  sections.push(checkpoints.map((checkpoint, index) => {
@@ -38498,6 +39047,9 @@ ${skipped.map((entry) => ` ↳ ${shortenPath2(asString2(entry.file) ?? "(file)"
38498
39047
  return `${index + 1}. ${name} ${theme.fg("muted", `${count} file${count === 1 ? "" : "s"} · ${created}`)}`;
38499
39048
  }).join(`
38500
39049
  `));
39050
+ const durability = asString2(response.durability);
39051
+ if (durability)
39052
+ sections.push(theme.fg("muted", durability));
38501
39053
  return sections;
38502
39054
  }
38503
39055
  function renderSafetyCall(args, theme, context) {
@@ -38718,7 +39270,7 @@ function registerSemanticTool(pi, ctx) {
38718
39270
  ].join(`
38719
39271
  `),
38720
39272
  parameters: SearchParams2,
38721
- async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
39273
+ async execute(_toolCallId, params, signal, _onUpdate, extCtx) {
38722
39274
  if (isEmptyParam(params.query) || typeof params.query !== "string" || params.query.trim().length === 0) {
38723
39275
  throw new Error("semantic_search: invalid params: `query` must be a non-empty string");
38724
39276
  }
@@ -38730,7 +39282,7 @@ function registerSemanticTool(pi, ctx) {
38730
39282
  req.includeTests = params.includeTests;
38731
39283
  if (params.path !== undefined)
38732
39284
  req.path = params.path;
38733
- const response = await callToolCall(bridge, "search", req, extCtx);
39285
+ const response = await callToolCall(bridge, "search", req, extCtx, signal ? { abortSignal: signal } : undefined);
38734
39286
  if (response.success === false) {
38735
39287
  throw new Error(response.text || response.message || "search failed");
38736
39288
  }
@@ -38747,7 +39299,25 @@ function registerSemanticTool(pi, ctx) {
38747
39299
 
38748
39300
  // src/tool-registration.ts
38749
39301
  var ALL_ONLY_TOOLS = new Set(["aft_callgraph", "aft_delete", "aft_move", "aft_refactor"]);
38750
- function resolvePiToolSurface(config2) {
39302
+ function resolvePiPowerShellFallback(config2) {
39303
+ return resolveBashConfig(config2).powershell_tool;
39304
+ }
39305
+ function piPowerShellEnabledFromHost(pi) {
39306
+ const api2 = pi;
39307
+ if (typeof api2.getActiveTools !== "function" || typeof api2.getAllTools !== "function") {
39308
+ return;
39309
+ }
39310
+ try {
39311
+ const tool = api2.getAllTools().find((candidate) => candidate.name === "powershell");
39312
+ const source = tool?.sourceInfo?.source ?? tool?.source;
39313
+ if (!tool || source === undefined)
39314
+ return;
39315
+ return source === "builtin" && api2.getActiveTools().some((active) => (typeof active === "string" ? active : active.name) === "powershell");
39316
+ } catch {
39317
+ return;
39318
+ }
39319
+ }
39320
+ function resolvePiToolSurface(config2, pi) {
38751
39321
  const surface = config2.tool_surface ?? "recommended";
38752
39322
  const disabled = new Set(config2.disabled_tools ?? []);
38753
39323
  const hoistBuiltinTools = config2.hoist_builtin_tools !== false;
@@ -38755,10 +39325,12 @@ function resolvePiToolSurface(config2) {
38755
39325
  const builtinToolEnabled = (bareName) => ok(hoistBuiltinTools ? bareName : `aft_${bareName}`);
38756
39326
  const allOnly = (name) => ALL_ONLY_TOOLS.has(name) && ok(name);
38757
39327
  const restrictToProjectRoot = config2.restrict_to_project_root ?? false;
39328
+ const powershellEnabled = (pi ? piPowerShellEnabledFromHost(pi) : undefined) ?? resolvePiPowerShellFallback(config2);
38758
39329
  if (surface === "minimal") {
38759
39330
  return {
38760
39331
  hoistBuiltinTools,
38761
39332
  hoistBash: builtinToolEnabled("bash"),
39333
+ hoistPowershell: powershellEnabled && builtinToolEnabled("powershell"),
38762
39334
  hoistRead: false,
38763
39335
  hoistWrite: false,
38764
39336
  hoistEdit: false,
@@ -38782,6 +39354,7 @@ function resolvePiToolSurface(config2) {
38782
39354
  const base = {
38783
39355
  hoistBuiltinTools,
38784
39356
  hoistBash: builtinToolEnabled("bash"),
39357
+ hoistPowershell: powershellEnabled && builtinToolEnabled("powershell"),
38785
39358
  hoistRead: builtinToolEnabled("read"),
38786
39359
  hoistWrite: builtinToolEnabled("write"),
38787
39360
  hoistEdit: builtinToolEnabled("edit"),
@@ -38813,14 +39386,20 @@ function resolvePiToolSurface(config2) {
38813
39386
  return base;
38814
39387
  }
38815
39388
  function registerPiToolSurface(pi, ctx, surface) {
38816
- if (surface.hoistBash && resolveBashConfig(ctx.config).enabled) {
39389
+ const bashCfg = resolveBashConfig(ctx.config);
39390
+ const bashRegistered = surface.hoistBash && bashCfg.enabled;
39391
+ const powershellRegistered = surface.hoistPowershell && bashCfg.enabled;
39392
+ if (bashRegistered) {
38817
39393
  registerBashTool(pi, ctx, surface.semantic, surface.hoistBuiltinTools ? "bash" : "aft_bash", false);
38818
- registerBashCompanionTools(pi, ctx);
38819
39394
  }
39395
+ if (powershellRegistered) {
39396
+ registerBashTool(pi, ctx, surface.semantic, surface.hoistBuiltinTools ? "powershell" : "aft_powershell", false, "powershell");
39397
+ }
39398
+ if (bashRegistered || powershellRegistered)
39399
+ registerBashCompanionTools(pi, ctx);
38820
39400
  registerHoistedTools(pi, ctx, surface);
38821
- if (surface.outline || surface.zoom) {
39401
+ if (surface.outline || surface.zoom)
38822
39402
  registerReadingTools(pi, ctx, surface);
38823
- }
38824
39403
  if (surface.semantic)
38825
39404
  registerSemanticTool(pi, ctx);
38826
39405
  if (surface.inspect)
@@ -39003,7 +39582,7 @@ var PLUGIN_VERSION = (() => {
39003
39582
  return "0.0.0";
39004
39583
  }
39005
39584
  })();
39006
- var ANNOUNCEMENT_VERSION = "0.53.0";
39585
+ var ANNOUNCEMENT_VERSION = "0.55.0";
39007
39586
  var ANNOUNCEMENT_FEATURES = [
39008
39587
  "Callgraph stays consistent under load: edits arriving mid-rebuild can no longer be silently lost from navigation results.",
39009
39588
  "aft_zoom handles real-world .jsonc: files with comment banners above the opening brace now resolve, and not-found errors point at the segment that actually failed (thanks @iceteaSA).",
@@ -39322,7 +39901,7 @@ ${lines}
39322
39901
  });
39323
39902
  }
39324
39903
  pool.setConfigureOverride("harness", "pi");
39325
- const surface = resolvePiToolSurface(config2);
39904
+ const surface = resolvePiToolSurface(config2, pi);
39326
39905
  const hashlineEditRegistered = config2.edit_mode === "hashline" && surface.hoistEdit;
39327
39906
  pool.setConfigureOverride("edit_slot_survives", hashlineEditRegistered);
39328
39907
  pool.setConfigureOverride("aft_search_registered", surface.semantic);
@@ -39370,6 +39949,17 @@ ${lines}
39370
39949
  sendFeatureAnnouncement(ANNOUNCEMENT_VERSION, ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER, storageDir);
39371
39950
  }
39372
39951
  registerPiToolSurface(pi, ctx, surface);
39952
+ let powershellRegistered = surface.hoistPowershell && resolveBashConfig(config2).enabled;
39953
+ pi.on("session_start", () => {
39954
+ if (powershellRegistered)
39955
+ return;
39956
+ const liveSurface = resolvePiToolSurface(config2, pi);
39957
+ if (!liveSurface.hoistPowershell || !resolveBashConfig(config2).enabled)
39958
+ return;
39959
+ registerBashTool(pi, ctx, liveSurface.semantic, liveSurface.hoistBuiltinTools ? "powershell" : "aft_powershell", false, "powershell");
39960
+ registerBashCompanionTools(pi, ctx);
39961
+ powershellRegistered = true;
39962
+ });
39373
39963
  registerWorkflowHints(pi, config2, surface);
39374
39964
  registerStatusCommand(pi, ctx);
39375
39965
  pi.on("tool_result", async (event, extCtx) => {
@@ -39401,10 +39991,18 @@ ${lines}
39401
39991
  });
39402
39992
  }
39403
39993
  });
39404
- pi.on("input", (_event, extCtx) => {
39994
+ pi.on("input", (event, extCtx) => {
39405
39995
  const sessionId = resolveSessionId(extCtx);
39406
39996
  signalSyncWatchAbort(sessionId);
39407
- signalBashWaitDetachForProject(pool, extCtx.cwd, sessionId);
39997
+ const originalText = event.text;
39998
+ const transformedText = stripUserMessageDetachKeyword(originalText);
39999
+ const shouldDetach = shouldDetachBashWaitOnUserMessage(config2, originalText);
40000
+ if (shouldDetach) {
40001
+ signalBashWaitDetachForProject(pool, extCtx.cwd, sessionId);
40002
+ }
40003
+ if (transformedText !== originalText) {
40004
+ return { action: "transform", text: transformedText };
40005
+ }
39408
40006
  });
39409
40007
  const unregisterShutdownCleanup = registerShutdownCleanup(async () => {
39410
40008
  try {