@cortexkit/aft-pi 0.52.2 → 0.54.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
@@ -8953,7 +9017,7 @@ class BinaryBridge {
8953
9017
  const keepBridgeOnTimeout = passive || options?.keepBridgeOnTimeout === true;
8954
9018
  let requestSentAt = Date.now();
8955
9019
  const child = this.process;
8956
- const response = await new Promise((resolve2, reject) => {
9020
+ const response = await new Promise((resolve3, reject) => {
8957
9021
  const timer = setTimeout(() => {
8958
9022
  const entry = this.pending.get(id);
8959
9023
  if (!entry)
@@ -8988,7 +9052,7 @@ class BinaryBridge {
8988
9052
  entry.reject(new Error(`${this.errorPrefix} Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
8989
9053
  this.handleTimeout(requestSessionId);
8990
9054
  }, effectiveTimeoutMs);
8991
- this.pending.set(id, { resolve: resolve2, reject, timer, onProgress: options?.onProgress, command });
9055
+ this.pending.set(id, { resolve: resolve3, reject, timer, onProgress: options?.onProgress, command });
8992
9056
  if (!child?.stdin?.writable) {
8993
9057
  this.pending.delete(id);
8994
9058
  clearTimeout(timer);
@@ -9106,15 +9170,15 @@ class BinaryBridge {
9106
9170
  if (this.process) {
9107
9171
  const proc = this.process;
9108
9172
  this.process = null;
9109
- return new Promise((resolve2) => {
9173
+ return new Promise((resolve3) => {
9110
9174
  const forceKillTimer = setTimeout(() => {
9111
9175
  proc.kill("SIGKILL");
9112
- resolve2();
9176
+ resolve3();
9113
9177
  }, 5000);
9114
9178
  proc.once("exit", () => {
9115
9179
  clearTimeout(forceKillTimer);
9116
9180
  this.logVia("Process exited during shutdown");
9117
- resolve2();
9181
+ resolve3();
9118
9182
  });
9119
9183
  proc.kill("SIGTERM");
9120
9184
  });
@@ -9162,15 +9226,15 @@ class BinaryBridge {
9162
9226
  return;
9163
9227
  const proc = this.process;
9164
9228
  this.process = null;
9165
- await new Promise((resolve2) => {
9229
+ await new Promise((resolve3) => {
9166
9230
  const forceKillTimer = setTimeout(() => {
9167
9231
  proc.kill("SIGKILL");
9168
- resolve2();
9232
+ resolve3();
9169
9233
  }, 5000);
9170
9234
  proc.once("exit", () => {
9171
9235
  clearTimeout(forceKillTimer);
9172
9236
  this.logVia("Process exited during coordinated binary replacement");
9173
- resolve2();
9237
+ resolve3();
9174
9238
  });
9175
9239
  proc.kill("SIGTERM");
9176
9240
  });
@@ -9200,7 +9264,7 @@ class BinaryBridge {
9200
9264
  })();
9201
9265
  const useFastembedBackend = semanticBackend === undefined || semanticBackend === "fastembed" || semanticBackend === "";
9202
9266
  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");
9267
+ const ortLibraryPath = ortDir == null ? null : join4(ortDir, process.platform === "win32" ? "onnxruntime.dll" : process.platform === "darwin" ? "libonnxruntime.dylib" : "libonnxruntime.so");
9204
9268
  const envPath = process.platform === "win32" && ortDir ? `${ortDir};${process.env.PATH ?? ""}` : process.env.PATH;
9205
9269
  const env = {
9206
9270
  ...process.env,
@@ -9208,7 +9272,7 @@ class BinaryBridge {
9208
9272
  };
9209
9273
  this.logVia(`bridge.spawnProcess: useFastembedBackend=${useFastembedBackend}, ` + `parentORT=${process.env.ORT_DYLIB_PATH ?? "(unset)"}, ` + `ortLibraryPath=${ortLibraryPath ?? "(none)"}`);
9210
9274
  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"));
9275
+ 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
9276
  if (process.env.ORT_DYLIB_PATH) {
9213
9277
  this.logVia(`ORT_DYLIB_PATH inherited from parent env: ${process.env.ORT_DYLIB_PATH}`);
9214
9278
  } else if (ortLibraryPath) {
@@ -9537,32 +9601,32 @@ class BinaryBridge {
9537
9601
  }
9538
9602
  }
9539
9603
  // ../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();
9604
+ import { homedir as homedir4 } from "node:os";
9605
+ import { join as join5 } from "node:path";
9606
+ function homeDir2(env) {
9607
+ return (process.platform === "win32" ? env.USERPROFILE || env.HOME : env.HOME) || homedir4();
9544
9608
  }
9545
9609
  function getAftCacheRoot(env = process.env) {
9546
9610
  if (env.AFT_CACHE_DIR)
9547
9611
  return env.AFT_CACHE_DIR;
9548
9612
  if (process.platform === "win32") {
9549
- const base2 = env.LOCALAPPDATA || env.APPDATA || join3(homeDir(env), "AppData", "Local");
9550
- return join3(base2, "aft");
9613
+ const base2 = env.LOCALAPPDATA || env.APPDATA || join5(homeDir2(env), "AppData", "Local");
9614
+ return join5(base2, "aft");
9551
9615
  }
9552
- const base = env.XDG_CACHE_HOME || join3(homeDir(env), ".cache");
9553
- return join3(base, "aft");
9616
+ const base = env.XDG_CACHE_HOME || join5(homeDir2(env), ".cache");
9617
+ return join5(base, "aft");
9554
9618
  }
9555
9619
  function getAftBinaryCacheDir(env = process.env) {
9556
- return join3(getAftCacheRoot(env), "bin");
9620
+ return join5(getAftCacheRoot(env), "bin");
9557
9621
  }
9558
9622
  function getAftLspPackagesDir(env = process.env) {
9559
- return join3(getAftCacheRoot(env), "lsp-packages");
9623
+ return join5(getAftCacheRoot(env), "lsp-packages");
9560
9624
  }
9561
9625
  function getAftLspBinariesDir(env = process.env) {
9562
- return join3(getAftCacheRoot(env), "lsp-binaries");
9626
+ return join5(getAftCacheRoot(env), "lsp-binaries");
9563
9627
  }
9564
9628
  // ../aft-bridge/dist/callgraph-format.js
9565
- import { homedir as homedir4 } from "node:os";
9629
+ import { homedir as homedir5 } from "node:os";
9566
9630
  var UNRESOLVED_SUMMARY_NAME_LIMIT = 10;
9567
9631
  var PLAIN_CALLGRAPH_THEME = {
9568
9632
  fg: (_role, text) => text
@@ -9585,7 +9649,7 @@ function asBoolean(value) {
9585
9649
  return typeof value === "boolean" ? value : undefined;
9586
9650
  }
9587
9651
  function shortenPath(path2) {
9588
- const home = homedir4();
9652
+ const home = homedir5();
9589
9653
  if (path2.startsWith(home))
9590
9654
  return `~${path2.slice(home.length)}`;
9591
9655
  return path2;
@@ -9922,7 +9986,7 @@ function isEmptyParam(value) {
9922
9986
  return false;
9923
9987
  }
9924
9988
  // ../aft-bridge/dist/config-keys.js
9925
- var OPENCODE_ONLY_KEYS = ["hoist_builtin_tools", "auto_update"];
9989
+ var OPENCODE_ONLY_KEYS = ["auto_update"];
9926
9990
  function stripHarnessSpecificConfigKeys(value, keys) {
9927
9991
  if (!value || typeof value !== "object" || Array.isArray(value))
9928
9992
  return value;
@@ -9932,26 +9996,26 @@ function stripHarnessSpecificConfigKeys(value, keys) {
9932
9996
  return stripped;
9933
9997
  }
9934
9998
  // ../aft-bridge/dist/config-tiers.js
9935
- import { existsSync, readFileSync as readFileSync2 } from "node:fs";
9936
- import { resolve as resolve2 } from "node:path";
9999
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
10000
+ import { resolve as resolve3 } from "node:path";
9937
10001
  function readConfigTiers(opts) {
9938
10002
  const tiers = [];
9939
10003
  try {
9940
- if (existsSync(opts.userConfigPath)) {
10004
+ if (existsSync2(opts.userConfigPath)) {
9941
10005
  const doc = readFileSync2(opts.userConfigPath, "utf-8");
9942
10006
  tiers.push({
9943
10007
  tier: "user",
9944
- source: resolve2(opts.userConfigPath),
10008
+ source: resolve3(opts.userConfigPath),
9945
10009
  doc
9946
10010
  });
9947
10011
  }
9948
10012
  } catch {}
9949
10013
  try {
9950
- if (existsSync(opts.projectConfigPath)) {
10014
+ if (existsSync2(opts.projectConfigPath)) {
9951
10015
  const doc = readFileSync2(opts.projectConfigPath, "utf-8");
9952
10016
  tiers.push({
9953
10017
  tier: "project",
9954
- source: resolve2(opts.projectConfigPath),
10018
+ source: resolve3(opts.projectConfigPath),
9955
10019
  doc
9956
10020
  });
9957
10021
  }
@@ -9967,9 +10031,9 @@ function formatDroppedKeyWarnings(dropped) {
9967
10031
  // ../aft-bridge/dist/downloader.js
9968
10032
  import { spawnSync } from "node:child_process";
9969
10033
  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";
10034
+ 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
10035
  import { hostname } from "node:os";
9972
- import { join as join4 } from "node:path";
10036
+ import { join as join6 } from "node:path";
9973
10037
  import { Readable } from "node:stream";
9974
10038
  import { pipeline } from "node:stream/promises";
9975
10039
 
@@ -10029,8 +10093,8 @@ function getBinaryName() {
10029
10093
  function getCachedBinaryPath(version) {
10030
10094
  if (!version)
10031
10095
  return null;
10032
- const binaryPath = join4(getAftBinaryCacheDir(), version, getBinaryName());
10033
- return existsSync2(binaryPath) ? binaryPath : null;
10096
+ const binaryPath = join6(getAftBinaryCacheDir(), version, getBinaryName());
10097
+ return existsSync3(binaryPath) ? binaryPath : null;
10034
10098
  }
10035
10099
  async function downloadBinary(version) {
10036
10100
  const archMap = PLATFORM_ARCH_MAP[process.platform] ?? {};
@@ -10046,16 +10110,16 @@ async function downloadBinary(version) {
10046
10110
  return null;
10047
10111
  }
10048
10112
  const tag = rawTag.startsWith("v") ? rawTag : `v${rawTag}`;
10049
- const versionedCacheDir = join4(getAftBinaryCacheDir(), tag);
10113
+ const versionedCacheDir = join6(getAftBinaryCacheDir(), tag);
10050
10114
  const binaryName = getBinaryName();
10051
- const binaryPath = join4(versionedCacheDir, binaryName);
10052
- if (existsSync2(binaryPath) && isExpectedCachedBinary(binaryPath, tag)) {
10115
+ const binaryPath = join6(versionedCacheDir, binaryName);
10116
+ if (existsSync3(binaryPath) && isExpectedCachedBinary(binaryPath, tag)) {
10053
10117
  return binaryPath;
10054
10118
  }
10055
10119
  const downloadUrl = `https://github.com/${REPO}/releases/download/${tag}/${assetName}`;
10056
10120
  const checksumUrl = `https://github.com/${REPO}/releases/download/${tag}/checksums.sha256`;
10057
10121
  log(`Downloading AFT binary (${tag}) for ${platformKey}...`);
10058
- const lockPath = join4(versionedCacheDir, ".download.lock");
10122
+ const lockPath = join6(versionedCacheDir, ".download.lock");
10059
10123
  let releaseLock = null;
10060
10124
  let binaryController = null;
10061
10125
  let checksumController = null;
@@ -10064,7 +10128,7 @@ async function downloadBinary(version) {
10064
10128
  const tmpPath = `${binaryPath}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`;
10065
10129
  const cleanUpPartialDownload = () => {
10066
10130
  try {
10067
- if (existsSync2(tmpPath))
10131
+ if (existsSync3(tmpPath))
10068
10132
  unlinkSync(tmpPath);
10069
10133
  } catch {}
10070
10134
  };
@@ -10088,12 +10152,12 @@ async function downloadBinary(version) {
10088
10152
  process.once("SIGINT", handleSigint);
10089
10153
  process.once("exit", handleExit);
10090
10154
  try {
10091
- if (!existsSync2(versionedCacheDir)) {
10155
+ if (!existsSync3(versionedCacheDir)) {
10092
10156
  mkdirSync(versionedCacheDir, { recursive: true });
10093
10157
  }
10094
10158
  releaseLock = await acquireDownloadLock(lockPath);
10095
10159
  sweepStaleDownloadTemps(versionedCacheDir, binaryName);
10096
- if (existsSync2(binaryPath) && isExpectedCachedBinary(binaryPath, tag)) {
10160
+ if (existsSync3(binaryPath) && isExpectedCachedBinary(binaryPath, tag)) {
10097
10161
  return binaryPath;
10098
10162
  }
10099
10163
  binaryController = new AbortController;
@@ -10158,7 +10222,7 @@ async function downloadBinary(version) {
10158
10222
  renameSync(tmpPath, binaryPath);
10159
10223
  }
10160
10224
  try {
10161
- if (existsSync2(tmpPath))
10225
+ if (existsSync3(tmpPath))
10162
10226
  unlinkSync(tmpPath);
10163
10227
  } catch {
10164
10228
  warn(`Could not clean up temporary download file ${tmpPath} — it can be removed manually.`);
@@ -10274,7 +10338,7 @@ function sweepStaleDownloadTemps(versionedCacheDir, binaryName) {
10274
10338
  if (!entry.isFile() || !entry.name.startsWith(tempPrefix) || !entry.name.endsWith(".tmp")) {
10275
10339
  continue;
10276
10340
  }
10277
- const tempPath = join4(versionedCacheDir, entry.name);
10341
+ const tempPath = join6(versionedCacheDir, entry.name);
10278
10342
  const ageMs = Date.now() - statSync2(tempPath).mtimeMs;
10279
10343
  if (Math.abs(ageMs) > DOWNLOAD_LOCK_STALE_MS)
10280
10344
  unlinkSync(tempPath);
@@ -10323,7 +10387,7 @@ async function acquireDownloadLock(lockPath, timing = {}) {
10323
10387
  if (Date.now() - startedAt > timeoutMs) {
10324
10388
  throw new Error(`Timed out waiting for download lock: ${lockPath}`);
10325
10389
  }
10326
- await new Promise((resolve3) => setTimeout(resolve3, pollIntervalMs));
10390
+ await new Promise((resolve4) => setTimeout(resolve4, pollIntervalMs));
10327
10391
  }
10328
10392
  }
10329
10393
  }
@@ -10361,51 +10425,6 @@ async function fetchLatestTag() {
10361
10425
  // ../aft-bridge/dist/durable-log.js
10362
10426
  import { appendFile, mkdir, rename, rm, stat } from "node:fs/promises";
10363
10427
  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
10428
  var DEFAULT_LOG_BYTES = 32 * 1024 * 1024;
10410
10429
  var DEFAULT_LOG_GENERATIONS = 1;
10411
10430
  class RotatingLogSink {
@@ -12040,7 +12059,7 @@ function pendingKey(handle, corr) {
12040
12059
  return handle ? `${handle.channel}:${handle.epoch}:${corr}` : `0:0:${corr}`;
12041
12060
  }
12042
12061
  // ../aft-bridge/dist/subc-transport.js
12043
- import { existsSync as existsSync3, statSync as statSync3 } from "node:fs";
12062
+ import { existsSync as existsSync4, statSync as statSync3 } from "node:fs";
12044
12063
 
12045
12064
  // ../aft-bridge/dist/lifecycle-registry.js
12046
12065
  import { lstat } from "node:fs/promises";
@@ -13049,7 +13068,7 @@ class SubcTransportPool {
13049
13068
  this.dormantRoots.delete(root);
13050
13069
  return true;
13051
13070
  }
13052
- const reclaimedMarkerPresent = existsSync3(`${root}.reclaimed`);
13071
+ const reclaimedMarkerPresent = existsSync4(`${root}.reclaimed`);
13053
13072
  if (reclaimedMarkerPresent) {
13054
13073
  this.markRootDormant(root);
13055
13074
  return false;
@@ -13881,14 +13900,14 @@ function stripJsoncSymbols(value) {
13881
13900
  }
13882
13901
  // ../aft-bridge/dist/migration.js
13883
13902
  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";
13903
+ 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
13904
  import { homedir as homedir8, tmpdir } from "node:os";
13886
- import { basename, dirname as dirname3, join as join8, resolve as resolve6 } from "node:path";
13905
+ import { basename, dirname as dirname3, join as join9, resolve as resolve6 } from "node:path";
13887
13906
 
13888
13907
  // ../aft-bridge/dist/paths.js
13889
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, renameSync as renameSync2, writeFileSync } from "node:fs";
13908
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync4, renameSync as renameSync2, writeFileSync } from "node:fs";
13890
13909
  import { homedir as homedir6 } from "node:os";
13891
- import { dirname as dirname2, isAbsolute as isAbsolute2, join as join6, resolve as resolve5 } from "node:path";
13910
+ import { dirname as dirname2, isAbsolute as isAbsolute2, join as join7, resolve as resolve5 } from "node:path";
13892
13911
  function homeDir3() {
13893
13912
  if (process.platform === "win32")
13894
13913
  return process.env.USERPROFILE || process.env.HOME || homedir6();
@@ -13898,16 +13917,16 @@ function configHome() {
13898
13917
  const xdg = process.env.XDG_CONFIG_HOME;
13899
13918
  if (xdg && isAbsolute2(xdg))
13900
13919
  return xdg;
13901
- return join6(homeDir3(), ".config");
13920
+ return join7(homeDir3(), ".config");
13902
13921
  }
13903
13922
  function legacyOpenCodeConfigDir() {
13904
13923
  const envDir = process.env.OPENCODE_CONFIG_DIR?.trim();
13905
13924
  if (envDir)
13906
13925
  return resolve5(envDir);
13907
- return join6(configHome(), "opencode");
13926
+ return join7(configHome(), "opencode");
13908
13927
  }
13909
13928
  function legacyPiAgentDir() {
13910
- return join6(homeDir3(), ".pi", "agent");
13929
+ return join7(homeDir3(), ".pi", "agent");
13911
13930
  }
13912
13931
  function legacySources(basePath, label, harness) {
13913
13932
  return [
@@ -13916,10 +13935,10 @@ function legacySources(basePath, label, harness) {
13916
13935
  ];
13917
13936
  }
13918
13937
  function resolveCortexKitUserConfigPath() {
13919
- return join6(configHome(), "cortexkit", "aft.jsonc");
13938
+ return join7(configHome(), "cortexkit", "aft.jsonc");
13920
13939
  }
13921
13940
  function resolveCortexKitProjectConfigPath(projectDirectory) {
13922
- return join6(projectDirectory, ".cortexkit", "aft.jsonc");
13941
+ return join7(projectDirectory, ".cortexkit", "aft.jsonc");
13923
13942
  }
13924
13943
  function resolveCortexKitConfigPaths(projectDirectory) {
13925
13944
  return {
@@ -13930,22 +13949,22 @@ function resolveCortexKitConfigPaths(projectDirectory) {
13930
13949
  function resolveLegacyAftConfigSources(projectDirectory) {
13931
13950
  return {
13932
13951
  user: [
13933
- ...legacySources(join6(legacyOpenCodeConfigDir(), "aft"), "OpenCode user", "opencode"),
13934
- ...legacySources(join6(legacyPiAgentDir(), "aft"), "Pi user", "pi")
13952
+ ...legacySources(join7(legacyOpenCodeConfigDir(), "aft"), "OpenCode user", "opencode"),
13953
+ ...legacySources(join7(legacyPiAgentDir(), "aft"), "Pi user", "pi")
13935
13954
  ],
13936
13955
  project: [
13937
- ...legacySources(join6(projectDirectory, ".opencode", "aft"), "OpenCode project", "opencode"),
13938
- ...legacySources(join6(projectDirectory, ".pi", "aft"), "Pi project", "pi")
13956
+ ...legacySources(join7(projectDirectory, ".opencode", "aft"), "OpenCode project", "opencode"),
13957
+ ...legacySources(join7(projectDirectory, ".pi", "aft"), "Pi project", "pi")
13939
13958
  ]
13940
13959
  };
13941
13960
  }
13942
13961
  function resolveHarnessStoragePath(storageRoot, harness, ...segments) {
13943
- return join6(storageRoot, harness, ...segments);
13962
+ return join7(storageRoot, harness, ...segments);
13944
13963
  }
13945
13964
  function repairRootScopedStorageFile(storageRoot, harness, fileName) {
13946
13965
  const harnessPath = resolveHarnessStoragePath(storageRoot, harness, fileName);
13947
- const rootPath = join6(storageRoot, fileName);
13948
- if (existsSync4(harnessPath) || !existsSync4(rootPath))
13966
+ const rootPath = join7(storageRoot, fileName);
13967
+ if (existsSync5(harnessPath) || !existsSync5(rootPath))
13949
13968
  return harnessPath;
13950
13969
  try {
13951
13970
  mkdirSync2(dirname2(harnessPath), { recursive: true });
@@ -13959,7 +13978,7 @@ function shouldShowAnnouncement(storageRoot, harness, currentVersion) {
13959
13978
  const versionFile = repairRootScopedStorageFile(storageRoot, harness, "last_announced_version");
13960
13979
  let lastVersion = "";
13961
13980
  try {
13962
- if (existsSync4(versionFile)) {
13981
+ if (existsSync5(versionFile)) {
13963
13982
  lastVersion = readFileSync4(versionFile, "utf-8").trim();
13964
13983
  }
13965
13984
  } catch {
@@ -14031,10 +14050,10 @@ function decodeFileUrl(target) {
14031
14050
 
14032
14051
  // ../aft-bridge/dist/resolver.js
14033
14052
  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";
14053
+ 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
14054
  import { createRequire as createRequire2 } from "node:module";
14036
14055
  import { homedir as homedir7 } from "node:os";
14037
- import { join as join7 } from "node:path";
14056
+ import { join as join8 } from "node:path";
14038
14057
  var ensureBinaryForResolver = ensureBinary;
14039
14058
  function copyToVersionedCache(npmBinaryPath, knownVersion) {
14040
14059
  try {
@@ -14043,10 +14062,10 @@ function copyToVersionedCache(npmBinaryPath, knownVersion) {
14043
14062
  return null;
14044
14063
  const tag = version.startsWith("v") ? version : `v${version}`;
14045
14064
  const cacheDir = getAftBinaryCacheDir();
14046
- const versionedDir = join7(cacheDir, tag);
14065
+ const versionedDir = join8(cacheDir, tag);
14047
14066
  const ext = process.platform === "win32" ? ".exe" : "";
14048
- const cachedPath = join7(versionedDir, `aft${ext}`);
14049
- if (existsSync5(cachedPath)) {
14067
+ const cachedPath = join8(versionedDir, `aft${ext}`);
14068
+ if (existsSync6(cachedPath)) {
14050
14069
  const cachedVersion = readBinaryVersion(cachedPath);
14051
14070
  if (cachedVersion === version)
14052
14071
  return cachedPath;
@@ -14058,7 +14077,7 @@ function copyToVersionedCache(npmBinaryPath, knownVersion) {
14058
14077
  if (process.platform !== "win32") {
14059
14078
  chmodSync2(tmpPath, 493);
14060
14079
  }
14061
- if (process.platform === "win32" && existsSync5(cachedPath)) {
14080
+ if (process.platform === "win32" && existsSync6(cachedPath)) {
14062
14081
  try {
14063
14082
  unlinkSync2(cachedPath);
14064
14083
  } catch {}
@@ -14078,8 +14097,8 @@ function homeDirFromEnv(env) {
14078
14097
  return (process.platform === "win32" ? env.USERPROFILE || env.HOME : env.HOME) || homedir7();
14079
14098
  }
14080
14099
  function cachedBinaryPathFromEnv(version, env, ext) {
14081
- const binaryPath = join7(getAftBinaryCacheDir(env), version, `aft${ext}`);
14082
- return existsSync5(binaryPath) ? binaryPath : null;
14100
+ const binaryPath = join8(getAftBinaryCacheDir(env), version, `aft${ext}`);
14101
+ return existsSync6(binaryPath) ? binaryPath : null;
14083
14102
  }
14084
14103
  function isExpectedCachedBinary2(binaryPath, expectedVersion) {
14085
14104
  const expected = normalizeBareVersion(expectedVersion);
@@ -14178,7 +14197,7 @@ function findBinarySyncInner(expectedVersion) {
14178
14197
  const packageBin = `@cortexkit/aft-${key}/bin/aft${ext}`;
14179
14198
  const req = createRequire2(import.meta.url);
14180
14199
  const resolved = req.resolve(packageBin);
14181
- if (existsSync5(resolved)) {
14200
+ if (existsSync6(resolved)) {
14182
14201
  const npmVersion = readBinaryVersion(resolved);
14183
14202
  if (npmVersion === null) {
14184
14203
  warn(`npm platform package binary at ${resolved} did not report a version; skipping (continuing to PATH lookup)`);
@@ -14207,8 +14226,8 @@ function findBinarySyncInner(expectedVersion) {
14207
14226
  return { path: usable, source: "PATH" };
14208
14227
  }
14209
14228
  } catch {}
14210
- const cargoPath = join7(homeDirFromEnv(env), ".cargo", "bin", `aft${ext}`);
14211
- if (existsSync5(cargoPath)) {
14229
+ const cargoPath = join8(homeDirFromEnv(env), ".cargo", "bin", `aft${ext}`);
14230
+ if (existsSync6(cargoPath)) {
14212
14231
  const usable = probeBinaryCandidate(cargoPath, "cargo", expectedVersion);
14213
14232
  if (usable)
14214
14233
  return { path: usable, source: "cargo" };
@@ -14254,9 +14273,9 @@ function dataHome2() {
14254
14273
  if (process.env.XDG_DATA_HOME)
14255
14274
  return process.env.XDG_DATA_HOME;
14256
14275
  if (process.platform === "win32") {
14257
- return process.env.LOCALAPPDATA || process.env.APPDATA || join8(homeDir4(), "AppData", "Local");
14276
+ return process.env.LOCALAPPDATA || process.env.APPDATA || join9(homeDir4(), "AppData", "Local");
14258
14277
  }
14259
- return join8(homeDir4(), ".local", "share");
14278
+ return join9(homeDir4(), ".local", "share");
14260
14279
  }
14261
14280
  function homeDir4() {
14262
14281
  if (process.platform === "win32")
@@ -14265,8 +14284,8 @@ function homeDir4() {
14265
14284
  }
14266
14285
  function resolveLegacyStorageRoot(harness) {
14267
14286
  if (harness === "pi")
14268
- return join8(homeDir4(), ".pi", "agent", "aft");
14269
- return join8(dataHome2(), "opencode", "storage", "plugin", "aft");
14287
+ return join9(homeDir4(), ".pi", "agent", "aft");
14288
+ return join9(dataHome2(), "opencode", "storage", "plugin", "aft");
14270
14289
  }
14271
14290
  function stripJsoncForParse(input) {
14272
14291
  let out = "";
@@ -14396,7 +14415,7 @@ function acquireConfigMigrationLock(lockDir) {
14396
14415
  }
14397
14416
  function atomicCopyConfigFile(sourcePath, targetPath) {
14398
14417
  mkdirSync4(dirname3(targetPath), { recursive: true });
14399
- const tmpPath = join8(dirname3(targetPath), `.${basename(targetPath)}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
14418
+ const tmpPath = join9(dirname3(targetPath), `.${basename(targetPath)}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
14400
14419
  let fd = null;
14401
14420
  try {
14402
14421
  fd = openSync3(tmpPath, "wx", 384);
@@ -14418,7 +14437,7 @@ function atomicCopyConfigFile(sourcePath, targetPath) {
14418
14437
  }
14419
14438
  function atomicWriteConfigFile(targetPath, content) {
14420
14439
  mkdirSync4(dirname3(targetPath), { recursive: true });
14421
- const tmpPath = join8(dirname3(targetPath), `.${basename(targetPath)}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
14440
+ const tmpPath = join9(dirname3(targetPath), `.${basename(targetPath)}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
14422
14441
  let fd = null;
14423
14442
  try {
14424
14443
  fd = openSync3(tmpPath, "wx", 384);
@@ -14440,11 +14459,11 @@ function atomicWriteConfigFile(targetPath, content) {
14440
14459
  }
14441
14460
  var MOVED_MARKER_SUFFIX = ".MOVED_READPLEASE";
14442
14461
  function nonClobberingSidecarPath(desiredPath) {
14443
- if (!existsSync6(desiredPath))
14462
+ if (!existsSync7(desiredPath))
14444
14463
  return desiredPath;
14445
14464
  for (let index = 1;index < Number.MAX_SAFE_INTEGER; index++) {
14446
14465
  const candidate = `${desiredPath}.${index}`;
14447
- if (!existsSync6(candidate))
14466
+ if (!existsSync7(candidate))
14448
14467
  return candidate;
14449
14468
  }
14450
14469
  throw new Error(`could not find available preservation sidecar path for ${desiredPath}`);
@@ -14538,7 +14557,7 @@ function visibleConfigMigrationWarning(scope, targetPath, paths, reason) {
14538
14557
  function migrateAftConfigFile(opts) {
14539
14558
  const warnings = [];
14540
14559
  const resolvedTarget = resolve6(opts.targetPath);
14541
- const existingSources = opts.legacySources.filter((source) => existsSync6(source.path) && resolve6(source.path) !== resolvedTarget);
14560
+ const existingSources = opts.legacySources.filter((source) => existsSync7(source.path) && resolve6(source.path) !== resolvedTarget);
14542
14561
  const info = opts.logger?.info ?? opts.logger?.log;
14543
14562
  if (existingSources.length === 0) {
14544
14563
  return { migrated: false, conflict: false, targetPath: opts.targetPath, warnings };
@@ -14550,7 +14569,7 @@ function migrateAftConfigFile(opts) {
14550
14569
  ...source,
14551
14570
  content: readFileSync5(source.path, "utf-8")
14552
14571
  }));
14553
- if (existsSync6(opts.targetPath)) {
14572
+ if (existsSync7(opts.targetPath)) {
14554
14573
  const targetContent = readFileSync5(opts.targetPath, "utf-8");
14555
14574
  for (const source of sources) {
14556
14575
  if (fileSemanticsMatch(source.content, targetContent))
@@ -14600,12 +14619,12 @@ function spawnErrorLabel(error2) {
14600
14619
  return [code, error2.message].filter(Boolean).join(": ");
14601
14620
  }
14602
14621
  function migrationLogPath(newRoot, harness, logger) {
14603
- const desired = join8(newRoot, "logs", "migration", `${harness}-${Date.now()}.jsonl`);
14622
+ const desired = join9(newRoot, "logs", "migration", `${harness}-${Date.now()}.jsonl`);
14604
14623
  try {
14605
14624
  mkdirSync4(dirname3(desired), { recursive: true });
14606
14625
  return desired;
14607
14626
  } catch (err) {
14608
- const fallback = join8(tmpdir(), `aft-migration-${harness}-${Date.now()}.jsonl`);
14627
+ const fallback = join9(tmpdir(), `aft-migration-${harness}-${Date.now()}.jsonl`);
14609
14628
  logger?.warn?.(`Failed to create AFT migration log directory ${dirname3(desired)}: ${err instanceof Error ? err.message : String(err)}. ` + `Using fallback log path ${fallback}.`);
14610
14629
  return fallback;
14611
14630
  }
@@ -14615,11 +14634,11 @@ async function ensureStorageMigrated(opts) {
14615
14634
  const newRoot = resolveCortexKitStorageRoot();
14616
14635
  const targetMarker = resolveHarnessStoragePath(newRoot, opts.harness, TARGET_MARKER);
14617
14636
  const info = opts.logger?.info ?? opts.logger?.log;
14618
- if (existsSync6(targetMarker)) {
14637
+ if (existsSync7(targetMarker)) {
14619
14638
  info?.(`AFT storage already migrated for ${opts.harness}; using ${newRoot}`);
14620
14639
  return;
14621
14640
  }
14622
- if (!existsSync6(legacyRoot)) {
14641
+ if (!existsSync7(legacyRoot)) {
14623
14642
  info?.(`AFT storage migration skipped for ${opts.harness}: no legacy data at ${legacyRoot}; ` + `using ${newRoot} for fresh install`);
14624
14643
  return;
14625
14644
  }
@@ -14669,7 +14688,7 @@ async function ensureStorageMigrated(opts) {
14669
14688
  // ../aft-bridge/dist/npm-resolver.js
14670
14689
  import { readdirSync as readdirSync2, statSync as statSync5 } from "node:fs";
14671
14690
  import { homedir as homedir9 } from "node:os";
14672
- import { delimiter, dirname as dirname4, isAbsolute as isAbsolute3, join as join9 } from "node:path";
14691
+ import { delimiter as delimiter2, dirname as dirname4, isAbsolute as isAbsolute3, join as join10 } from "node:path";
14673
14692
  function defaultDeps() {
14674
14693
  return {
14675
14694
  platform: process.platform,
@@ -14691,18 +14710,18 @@ function isFile(p) {
14691
14710
  function npmFromPath(deps) {
14692
14711
  const name = npmBinaryName(deps.platform);
14693
14712
  const raw = deps.env.PATH ?? deps.env.Path ?? "";
14694
- for (const entry of raw.split(delimiter)) {
14713
+ for (const entry of raw.split(delimiter2)) {
14695
14714
  const dir = entry.trim().replace(/^"|"$/g, "");
14696
14715
  if (!dir || !isAbsolute3(dir))
14697
14716
  continue;
14698
- if (isFile(join9(dir, name)))
14717
+ if (isFile(join10(dir, name)))
14699
14718
  return dir;
14700
14719
  }
14701
14720
  return null;
14702
14721
  }
14703
14722
  function npmAdjacentToNode(deps) {
14704
14723
  const dir = dirname4(deps.execPath);
14705
- return isFile(join9(dir, npmBinaryName(deps.platform))) ? dir : null;
14724
+ return isFile(join10(dir, npmBinaryName(deps.platform))) ? dir : null;
14706
14725
  }
14707
14726
  function highestVersionedNodeBin(installsDir, name) {
14708
14727
  let entries;
@@ -14711,8 +14730,8 @@ function highestVersionedNodeBin(installsDir, name) {
14711
14730
  } catch {
14712
14731
  return null;
14713
14732
  }
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;
14733
+ const candidates = entries.filter((v) => isFile(join10(installsDir, v, "bin", name))).sort((a, b) => compareVersionsDesc(a, b));
14734
+ return candidates.length > 0 ? join10(installsDir, candidates[0], "bin") : null;
14716
14735
  }
14717
14736
  function compareVersionsDesc(a, b) {
14718
14737
  const pa = a.replace(/^v/, "").split(".").map((n) => Number.parseInt(n, 10));
@@ -14737,22 +14756,22 @@ function wellKnownNpmDirs(deps) {
14737
14756
  const programFiles = env.ProgramFiles || "C:\\Program Files";
14738
14757
  const appData = env.APPDATA;
14739
14758
  const localAppData = env.LOCALAPPDATA;
14740
- push(join9(programFiles, "nodejs"));
14759
+ push(join10(programFiles, "nodejs"));
14741
14760
  if (appData)
14742
- push(join9(appData, "npm"));
14761
+ push(join10(appData, "npm"));
14743
14762
  if (localAppData)
14744
- push(join9(localAppData, "Volta", "bin"));
14763
+ push(join10(localAppData, "Volta", "bin"));
14745
14764
  if (env.NVM_SYMLINK)
14746
14765
  push(env.NVM_SYMLINK);
14747
14766
  } else {
14748
14767
  if (env.NVM_BIN)
14749
14768
  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")]);
14769
+ push(highestVersionedNodeBin(join10(home, ".nvm", "versions", "node"), name));
14770
+ push(highestVersionedNodeBin(join10(home, ".local", "share", "mise", "installs", "node"), name));
14771
+ push(highestVersionedNodeBin(join10(home, ".asdf", "installs", "nodejs"), name));
14772
+ push(join10(home, ".volta", "bin"));
14773
+ push(join10(home, ".asdf", "shims"));
14774
+ const systemDirs = deps.systemNpmDirs ?? (platform === "darwin" ? ["/opt/homebrew/bin", "/usr/local/bin"] : ["/usr/local/bin", "/usr/bin", join10(home, ".local", "bin")]);
14756
14775
  for (const dir of systemDirs)
14757
14776
  push(dir);
14758
14777
  }
@@ -14762,12 +14781,12 @@ function resolveNpm(deps = defaultDeps()) {
14762
14781
  const name = npmBinaryName(deps.platform);
14763
14782
  const onPath = npmFromPath(deps);
14764
14783
  if (onPath)
14765
- return { command: join9(onPath, name), binDir: onPath };
14784
+ return { command: join10(onPath, name), binDir: onPath };
14766
14785
  const adjacent = npmAdjacentToNode(deps);
14767
14786
  if (adjacent)
14768
- return { command: join9(adjacent, name), binDir: adjacent };
14787
+ return { command: join10(adjacent, name), binDir: adjacent };
14769
14788
  for (const dir of wellKnownNpmDirs(deps)) {
14770
- const candidate = join9(dir, name);
14789
+ const candidate = join10(dir, name);
14771
14790
  if (isFile(candidate))
14772
14791
  return { command: candidate, binDir: dir };
14773
14792
  }
@@ -14777,14 +14796,14 @@ function npmSpawnEnv(resolved, baseEnv = process.env) {
14777
14796
  if (!resolved.binDir)
14778
14797
  return { ...baseEnv };
14779
14798
  const existing = baseEnv.PATH ?? baseEnv.Path ?? "";
14780
- const next = existing ? `${resolved.binDir}${delimiter}${existing}` : resolved.binDir;
14799
+ const next = existing ? `${resolved.binDir}${delimiter2}${existing}` : resolved.binDir;
14781
14800
  return { ...baseEnv, PATH: next };
14782
14801
  }
14783
14802
  // ../aft-bridge/dist/onnx-runtime.js
14784
14803
  import { execFileSync } from "node:child_process";
14785
14804
  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";
14805
+ 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";
14806
+ import { basename as basename2, dirname as dirname5, isAbsolute as isAbsolute4, join as join11, relative as relative2, resolve as resolve7, win32 } from "node:path";
14788
14807
  import { Readable as Readable2 } from "node:stream";
14789
14808
  import { pipeline as pipeline2 } from "node:stream/promises";
14790
14809
  var ORT_VERSION = "1.24.4";
@@ -14847,11 +14866,11 @@ function getManualInstallHint() {
14847
14866
  }
14848
14867
  async function ensureOnnxRuntime(storageDir) {
14849
14868
  const info = getPlatformInfo();
14850
- const ortVersionDir = join10(storageDir, "onnxruntime", ORT_VERSION);
14869
+ const ortVersionDir = join11(storageDir, "onnxruntime", ORT_VERSION);
14851
14870
  const libName = info?.libName ?? "libonnxruntime.dylib";
14852
14871
  const resolvedOrtDir = resolveCachedOnnxRuntimeDir(ortVersionDir, libName);
14853
- const libPath = join10(resolvedOrtDir, libName);
14854
- if (existsSync7(libPath)) {
14872
+ const libPath = join11(resolvedOrtDir, libName);
14873
+ if (existsSync8(libPath)) {
14855
14874
  const meta = readOnnxInstalledMeta(ortVersionDir);
14856
14875
  if (meta?.sha256) {
14857
14876
  try {
@@ -14880,9 +14899,9 @@ async function ensureOnnxRuntime(storageDir) {
14880
14899
  warn(`ONNX Runtime auto-download not available for ${process.platform}/${process.arch}. Install manually: ${getManualInstallHint()}`);
14881
14900
  return null;
14882
14901
  }
14883
- const onnxBaseDir = join10(storageDir, "onnxruntime");
14902
+ const onnxBaseDir = join11(storageDir, "onnxruntime");
14884
14903
  mkdirSync5(onnxBaseDir, { recursive: true });
14885
- const lockPath = join10(onnxBaseDir, ONNX_LOCK_FILE);
14904
+ const lockPath = join11(onnxBaseDir, ONNX_LOCK_FILE);
14886
14905
  cleanupAbandonedStagingDirs(onnxBaseDir);
14887
14906
  if (!acquireLock(lockPath)) {
14888
14907
  warn(`ONNX Runtime install already in progress in another process (lock: ${lockPath}). Skipping.`);
@@ -14901,7 +14920,7 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
14901
14920
  for (const entry of entries) {
14902
14921
  if (!entry.startsWith(`${ORT_VERSION}.tmp.`))
14903
14922
  continue;
14904
- const stagingDir = join10(onnxBaseDir, entry);
14923
+ const stagingDir = join11(onnxBaseDir, entry);
14905
14924
  const parts = entry.split(".");
14906
14925
  const pidStr = parts[parts.length - 2];
14907
14926
  const pid = pidStr ? Number.parseInt(pidStr, 10) : NaN;
@@ -14938,7 +14957,7 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
14938
14957
  }
14939
14958
  function cleanupIncompleteTargetIfUnowned(ortDir) {
14940
14959
  try {
14941
- if (existsSync7(ortDir) && !existsSync7(join10(ortDir, ONNX_INSTALLED_META_FILE))) {
14960
+ if (existsSync8(ortDir) && !existsSync8(join11(ortDir, ONNX_INSTALLED_META_FILE))) {
14942
14961
  log(`[onnx] removing half-populated install dir ${ortDir} (no meta file)`);
14943
14962
  rmSync3(ortDir, { recursive: true, force: true });
14944
14963
  }
@@ -14982,8 +15001,8 @@ function detectOnnxVersion(libDir, libName) {
14982
15001
  if (version)
14983
15002
  return version;
14984
15003
  }
14985
- const base = join10(libDir, libName);
14986
- if (existsSync7(base)) {
15004
+ const base = join11(libDir, libName);
15005
+ if (existsSync8(base)) {
14987
15006
  try {
14988
15007
  const real = realpathSync2(base);
14989
15008
  const version = parseOnnxVersionFromPath(real) ?? parseOnnxVersionFromDirectoryPath(real);
@@ -15014,8 +15033,8 @@ function pathEnvValue() {
15014
15033
  return process.env.PATH ?? process.env.Path ?? process.env.path ?? "";
15015
15034
  }
15016
15035
  function pathEntriesForPlatform() {
15017
- const delimiter2 = process.platform === "win32" ? ";" : ":";
15018
- return pathEnvValue().split(delimiter2).map((entry) => entry.trim().replace(/^"|"$/g, "")).filter((entry) => {
15036
+ const delimiter3 = process.platform === "win32" ? ";" : ":";
15037
+ return pathEnvValue().split(delimiter3).map((entry) => entry.trim().replace(/^"|"$/g, "")).filter((entry) => {
15019
15038
  if (!entry || entry === "." || entry.includes("\x00"))
15020
15039
  return false;
15021
15040
  return isAbsolute4(entry) || win32.isAbsolute(entry);
@@ -15045,10 +15064,10 @@ function directoryContainsLibrary(dir, libName) {
15045
15064
  }
15046
15065
  }
15047
15066
  function resolveCachedOnnxRuntimeDir(ortVersionDir, libName) {
15048
- if (existsSync7(join10(ortVersionDir, libName)))
15067
+ if (existsSync8(join11(ortVersionDir, libName)))
15049
15068
  return ortVersionDir;
15050
- const libSubdir = join10(ortVersionDir, "lib");
15051
- if (existsSync7(join10(libSubdir, libName)))
15069
+ const libSubdir = join11(ortVersionDir, "lib");
15070
+ if (existsSync8(join11(libSubdir, libName)))
15052
15071
  return libSubdir;
15053
15072
  return ortVersionDir;
15054
15073
  }
@@ -15063,13 +15082,13 @@ function findSystemOnnxRuntime(libName) {
15063
15082
  } else if (process.platform === "win32") {
15064
15083
  const programFiles = process.env.ProgramFiles ?? "C:\\Program Files";
15065
15084
  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"), ...(() => {
15085
+ searchPaths.push(join11(programFiles, "onnxruntime", "lib"), join11(programFiles, "Microsoft ONNX Runtime", "lib"), join11(programFiles, "Microsoft Machine Learning", "lib"), join11(programFilesX86, "onnxruntime", "lib"), ...(() => {
15067
15086
  const nugetPaths = [];
15068
15087
  const userProfile = process.env.USERPROFILE ?? "";
15069
15088
  if (!userProfile)
15070
15089
  return nugetPaths;
15071
- const nugetPackageDir = join10(userProfile, ".nuget", "packages", "microsoft.ml.onnxruntime");
15072
- if (!existsSync7(nugetPackageDir))
15090
+ const nugetPackageDir = join11(userProfile, ".nuget", "packages", "microsoft.ml.onnxruntime");
15091
+ if (!existsSync8(nugetPackageDir))
15073
15092
  return nugetPaths;
15074
15093
  try {
15075
15094
  for (const entry of readdirSync3(nugetPackageDir, { withFileTypes: true })) {
@@ -15077,7 +15096,7 @@ function findSystemOnnxRuntime(libName) {
15077
15096
  continue;
15078
15097
  if (entry.name === "__globalPackagesFolder" || entry.name.startsWith("."))
15079
15098
  continue;
15080
- nugetPaths.push(join10(nugetPackageDir, entry.name, "runtimes", "win-x64", "native"), join10(nugetPackageDir, entry.name, "runtimes", "win-arm64", "native"));
15099
+ nugetPaths.push(join11(nugetPackageDir, entry.name, "runtimes", "win-x64", "native"), join11(nugetPackageDir, entry.name, "runtimes", "win-arm64", "native"));
15081
15100
  }
15082
15101
  } catch (err) {
15083
15102
  warn(`Failed to scan NuGet ONNX Runtime cache ${nugetPackageDir}: ${err instanceof Error ? err.message : String(err)}`);
@@ -15099,11 +15118,11 @@ function findSystemOnnxRuntime(libName) {
15099
15118
  });
15100
15119
  const unknownVersionPaths = [];
15101
15120
  for (const dir of uniquePaths) {
15102
- const libPath = join10(dir, libName);
15121
+ const libPath = join11(dir, libName);
15103
15122
  if (process.platform === "win32") {
15104
15123
  if (!directoryContainsLibrary(dir, libName))
15105
15124
  continue;
15106
- } else if (!existsSync7(libPath)) {
15125
+ } else if (!existsSync8(libPath)) {
15107
15126
  continue;
15108
15127
  }
15109
15128
  const version = detectOnnxVersion(dir, libName);
@@ -15169,7 +15188,7 @@ function validateExtractedTree(stagingRoot) {
15169
15188
  const walk = (dir) => {
15170
15189
  const entries = readdirSync3(dir);
15171
15190
  for (const entry of entries) {
15172
- const fullPath = join10(dir, entry);
15191
+ const fullPath = join11(dir, entry);
15173
15192
  const lst = lstatSync(fullPath);
15174
15193
  if (lst.isSymbolicLink()) {
15175
15194
  const linkTarget = readlinkSync(fullPath);
@@ -15204,7 +15223,7 @@ async function downloadOnnxRuntime(info, targetDir) {
15204
15223
  const tmpDir = `${targetDir}.tmp.${process.pid}.${Date.now().toString(36)}`;
15205
15224
  try {
15206
15225
  mkdirSync5(tmpDir, { recursive: true });
15207
- const archivePath = join10(tmpDir, `onnxruntime.${info.archiveType}`);
15226
+ const archivePath = join11(tmpDir, `onnxruntime.${info.archiveType}`);
15208
15227
  await downloadFileWithCap(url, archivePath);
15209
15228
  const archiveSha256 = sha256File(archivePath);
15210
15229
  log(`ONNX Runtime archive sha256=${archiveSha256}`);
@@ -15220,8 +15239,8 @@ async function downloadOnnxRuntime(info, targetDir) {
15220
15239
  unlinkSync4(archivePath);
15221
15240
  } catch {}
15222
15241
  validateExtractedTree(tmpDir);
15223
- const extractedDir = join10(tmpDir, info.assetName, "lib");
15224
- if (!existsSync7(extractedDir)) {
15242
+ const extractedDir = join11(tmpDir, info.assetName, "lib");
15243
+ if (!existsSync8(extractedDir)) {
15225
15244
  throw new Error(`Expected directory not found: ${extractedDir}`);
15226
15245
  }
15227
15246
  mkdirSync5(targetDir, { recursive: true });
@@ -15229,7 +15248,7 @@ async function downloadOnnxRuntime(info, targetDir) {
15229
15248
  const realFiles = [];
15230
15249
  const symlinks = [];
15231
15250
  for (const libFile of libFiles) {
15232
- const src = join10(extractedDir, libFile);
15251
+ const src = join11(extractedDir, libFile);
15233
15252
  try {
15234
15253
  const stat2 = lstatSync(src);
15235
15254
  log(`ORT extract: ${libFile} — isSymlink=${stat2.isSymbolicLink()}, isFile=${stat2.isFile()}, size=${stat2.size}`);
@@ -15244,7 +15263,7 @@ async function downloadOnnxRuntime(info, targetDir) {
15244
15263
  }
15245
15264
  }
15246
15265
  copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks);
15247
- const libPath = join10(targetDir, info.libName);
15266
+ const libPath = join11(targetDir, info.libName);
15248
15267
  let libHash = null;
15249
15268
  try {
15250
15269
  libHash = sha256File(libPath);
@@ -15269,8 +15288,8 @@ async function downloadOnnxRuntime(info, targetDir) {
15269
15288
  function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, copyFile = copyFileSync3) {
15270
15289
  const requiredLibs = new Set([info.libName]);
15271
15290
  for (const libFile of realFiles) {
15272
- const src = join10(extractedDir, libFile);
15273
- const dst = join10(targetDir, libFile);
15291
+ const src = join11(extractedDir, libFile);
15292
+ const dst = join11(targetDir, libFile);
15274
15293
  try {
15275
15294
  copyFile(src, dst);
15276
15295
  if (process.platform !== "win32") {
@@ -15286,11 +15305,11 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
15286
15305
  }
15287
15306
  const targetRoot = realpathSync2(targetDir);
15288
15307
  for (const link of symlinks) {
15289
- const dst = join10(targetDir, link.name);
15308
+ const dst = join11(targetDir, link.name);
15290
15309
  try {
15291
15310
  unlinkSync4(dst);
15292
15311
  } catch {}
15293
- const dstForContainment = join10(targetRoot, link.name);
15312
+ const dstForContainment = join11(targetRoot, link.name);
15294
15313
  const resolvedTarget = resolve7(dirname5(dstForContainment), link.target);
15295
15314
  if (!isPathInsideRoot(targetRoot, resolvedTarget)) {
15296
15315
  const message = `ONNX Runtime symlink ${link.name} points outside install dir: ${link.target}`;
@@ -15311,8 +15330,8 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
15311
15330
  log(`ORT extract: failed to symlink optional ${link.name}: ${symlinkErr}`);
15312
15331
  }
15313
15332
  }
15314
- const requiredPath = join10(targetDir, info.libName);
15315
- if (!existsSync7(requiredPath)) {
15333
+ const requiredPath = join11(targetDir, info.libName);
15334
+ if (!existsSync8(requiredPath)) {
15316
15335
  rmSync3(targetDir, { recursive: true, force: true });
15317
15336
  throw new Error(`Required ONNX Runtime library missing after install: ${requiredPath}`);
15318
15337
  }
@@ -15338,13 +15357,13 @@ function writeOnnxInstalledMeta(installDir, version, sha256, archiveSha256) {
15338
15357
  ...sha256 ? { sha256 } : {},
15339
15358
  archiveSha256
15340
15359
  };
15341
- writeFileSync3(join10(installDir, ONNX_INSTALLED_META_FILE), JSON.stringify(meta), "utf8");
15360
+ writeFileSync3(join11(installDir, ONNX_INSTALLED_META_FILE), JSON.stringify(meta), "utf8");
15342
15361
  } catch (err) {
15343
15362
  log(`[onnx] failed to write installed-meta in ${installDir}: ${err}`);
15344
15363
  }
15345
15364
  }
15346
15365
  function readOnnxInstalledMeta(installDir) {
15347
- const path2 = join10(installDir, ONNX_INSTALLED_META_FILE);
15366
+ const path2 = join11(installDir, ONNX_INSTALLED_META_FILE);
15348
15367
  try {
15349
15368
  if (!statSync6(path2).isFile())
15350
15369
  return null;
@@ -15750,7 +15769,7 @@ function editModesPresent(record) {
15750
15769
  const hasSymbol = isNonEmptyString(record.symbol);
15751
15770
  if (!hasSymbol) {
15752
15771
  delete record.symbol;
15753
- if (record.content === null || record.content === "")
15772
+ if (isNullOrEmptyString(record.content))
15754
15773
  delete record.content;
15755
15774
  } else if (record.content === null) {
15756
15775
  delete record.content;
@@ -15779,17 +15798,24 @@ function editModesPresent(record) {
15779
15798
  function isNonEmptyString(value) {
15780
15799
  return typeof value === "string" && value.length > 0;
15781
15800
  }
15801
+ function isNullOrEmptyString(value) {
15802
+ return value === null || value === "";
15803
+ }
15782
15804
  var OMIT_OPTIONAL_FIELDS_STEERING = "Omit unused optional fields entirely; do not send empty strings or empty arrays for them.";
15783
15805
  function isEditSentinelItem(item) {
15784
15806
  if (!item || typeof item !== "object" || Array.isArray(item))
15785
15807
  return false;
15786
15808
  const record = item;
15787
- if (record.oldString !== "")
15809
+ const oldStringEmpty = hasOwn(record, "oldString") && isNullOrEmptyString(record.oldString);
15810
+ if (!oldStringEmpty)
15811
+ return false;
15812
+ if (record.oldString === null && ["startLine", "endLine"].some((key) => hasOwn(record, key) && record[key] !== null)) {
15788
15813
  return false;
15789
- const newStringEmpty = !hasOwn(record, "newString") || record.newString === "" || record.newString === null;
15814
+ }
15815
+ const newStringEmpty = !hasOwn(record, "newString") || isNullOrEmptyString(record.newString);
15790
15816
  if (!newStringEmpty)
15791
15817
  return false;
15792
- return !hasOwn(record, "content") || record.content === "" || record.content === null;
15818
+ return !hasOwn(record, "content") || isNullOrEmptyString(record.content);
15793
15819
  }
15794
15820
  function normalizeEditArraySentinels(record) {
15795
15821
  const value = record.edits;
@@ -15844,6 +15870,18 @@ function parseEditArray(value) {
15844
15870
  }
15845
15871
  function stripLineRangeSentinels(item) {
15846
15872
  const hasRangeField = ["startLine", "endLine", "content"].some((key) => hasOwn(item, key));
15873
+ for (const key of [
15874
+ "oldString",
15875
+ "newString",
15876
+ "replaceAll",
15877
+ "occurrence",
15878
+ "startLine",
15879
+ "endLine",
15880
+ "content"
15881
+ ]) {
15882
+ if (item[key] === null)
15883
+ delete item[key];
15884
+ }
15847
15885
  if (!hasRangeField)
15848
15886
  return;
15849
15887
  if (item.oldString === "")
@@ -16556,17 +16594,17 @@ function collectStructuredExtras(response) {
16556
16594
  return stringifyData(extras);
16557
16595
  }
16558
16596
  // ../aft-bridge/dist/transport-factory.js
16559
- import { existsSync as existsSync8 } from "node:fs";
16597
+ import { existsSync as existsSync9 } from "node:fs";
16560
16598
  import { homedir as homedir11 } from "node:os";
16561
- import { isAbsolute as isAbsolute5, join as join11 } from "node:path";
16599
+ import { isAbsolute as isAbsolute5, join as join12 } from "node:path";
16562
16600
  function resolveConnectionFilePath(raw) {
16563
16601
  const trimmed = raw.trim();
16564
16602
  if (trimmed.startsWith("~")) {
16565
- return join11(homedir11(), trimmed.slice(1).replace(/^[/\\]/, ""));
16603
+ return join12(homedir11(), trimmed.slice(1).replace(/^[/\\]/, ""));
16566
16604
  }
16567
16605
  if (isAbsolute5(trimmed))
16568
16606
  return trimmed;
16569
- return join11(homedir11(), trimmed);
16607
+ return join12(homedir11(), trimmed);
16570
16608
  }
16571
16609
  var SUBC_CLIENT_REAPER_PROCESS_KEY = "subc_client_reaper";
16572
16610
  function booleanOption(value) {
@@ -16640,7 +16678,7 @@ async function createConcreteAftTransportPool(opts) {
16640
16678
  consumerIdentity: opts.subcConsumerIdentity,
16641
16679
  onBgEventsNudge: opts.onBgEventsNudge,
16642
16680
  onBgEventsNudgeRef: opts.onBgEventsNudgeRef,
16643
- lifecycleDemandCheck: opts.subcLifecycleDemandCheck ?? ((root) => existsSync8(root))
16681
+ lifecycleDemandCheck: opts.subcLifecycleDemandCheck ?? ((root) => existsSync9(root))
16644
16682
  });
16645
16683
  }
16646
16684
  return new BridgePool(opts.binaryPath, opts.poolOptions, opts.configOverrides);
@@ -17461,7 +17499,7 @@ import {
17461
17499
  // package.json
17462
17500
  var package_default = {
17463
17501
  name: "@cortexkit/aft-pi",
17464
- version: "0.52.2",
17502
+ version: "0.54.0",
17465
17503
  type: "module",
17466
17504
  description: "Pi coding agent extension for Agent File Tools (AFT) — tree-sitter and LSP-powered code analysis",
17467
17505
  main: "dist/index.js",
@@ -17484,19 +17522,19 @@ var package_default = {
17484
17522
  "test:unit": "bun test src/__tests__ --path-ignore-patterns 'src/__tests__/e2e/**'"
17485
17523
  },
17486
17524
  dependencies: {
17487
- "@cortexkit/aft-bridge": "0.52.2",
17525
+ "@cortexkit/aft-bridge": "0.54.0",
17488
17526
  "comment-json": "^5.0.0",
17489
17527
  diff: "^8.0.4",
17490
17528
  typebox: "1.1.38",
17491
17529
  zod: "^4.4.3"
17492
17530
  },
17493
17531
  optionalDependencies: {
17494
- "@cortexkit/aft-darwin-arm64": "0.52.2",
17495
- "@cortexkit/aft-darwin-x64": "0.52.2",
17496
- "@cortexkit/aft-linux-arm64": "0.52.2",
17497
- "@cortexkit/aft-linux-x64": "0.52.2",
17498
- "@cortexkit/aft-win32-arm64": "0.52.2",
17499
- "@cortexkit/aft-win32-x64": "0.52.2"
17532
+ "@cortexkit/aft-darwin-arm64": "0.54.0",
17533
+ "@cortexkit/aft-darwin-x64": "0.54.0",
17534
+ "@cortexkit/aft-linux-arm64": "0.54.0",
17535
+ "@cortexkit/aft-linux-x64": "0.54.0",
17536
+ "@cortexkit/aft-win32-arm64": "0.54.0",
17537
+ "@cortexkit/aft-win32-x64": "0.54.0"
17500
17538
  },
17501
17539
  devDependencies: {
17502
17540
  "@earendil-works/pi-coding-agent": "^0.80.2",
@@ -17640,13 +17678,16 @@ function coerceAftStatus(response) {
17640
17678
  project_root: readNullableString(response.project_root),
17641
17679
  canonical_root: readNullableString(response.canonical_root),
17642
17680
  cache_role: readString(response.cache_role, "not_initialized"),
17681
+ degraded: readBoolean(response.degraded),
17682
+ degraded_reasons: Array.isArray(response.degraded_reasons) ? response.degraded_reasons.filter((r) => typeof r === "string") : [],
17643
17683
  message: typeof response.message === "string" ? response.message : undefined,
17644
17684
  features: {
17645
17685
  format_on_edit: readBoolean(features.format_on_edit),
17646
17686
  validate_on_edit: readString(features.validate_on_edit, "off"),
17647
17687
  restrict_to_project_root: readBoolean(features.restrict_to_project_root),
17648
17688
  search_index: readBoolean(features.search_index ?? features.experimental_search_index),
17649
- semantic_search: readBoolean(features.semantic_search ?? features.experimental_semantic_search)
17689
+ semantic_search: readBoolean(features.semantic_search ?? features.experimental_semantic_search),
17690
+ callgraph_store: readBoolean(features.callgraph_store)
17650
17691
  },
17651
17692
  search_index: {
17652
17693
  status: readString(searchIndex.status, "unknown"),
@@ -17697,21 +17738,10 @@ function formatStatusDialogMessage(status) {
17697
17738
  `AFT version: ${status.version}`,
17698
17739
  `Project root: ${status.project_root ?? "(not configured)"}`,
17699
17740
  `Canonical root: ${status.canonical_root ?? "(not configured)"}`,
17700
- `Cache role: ${formatCacheRoleLabel(status.cache_role)}`,
17701
- "",
17702
- "Enabled features",
17703
- `- format_on_edit: ${formatFlag(status.features.format_on_edit)}`,
17704
- `- search_index: ${formatFlag(status.features.search_index)}`,
17705
- `- semantic_search: ${formatFlag(status.features.semantic_search)}`,
17706
- "",
17707
- "Search index",
17708
- `- status: ${status.search_index.status}`,
17709
- `- files: ${formatCount(status.search_index.files)}`,
17710
- `- trigrams: ${formatCount(status.search_index.trigrams)}`,
17711
- "",
17712
- "Semantic index",
17713
- `- status: ${formatSemanticIndexStatus(status.semantic_index.status, status.semantic_index.stage)}`
17741
+ `Cache role: ${formatCacheRoleLabel(status.cache_role)}`
17714
17742
  ];
17743
+ appendDegradedStatus(lines, status, false);
17744
+ lines.push("", "Enabled features", `- format_on_edit: ${formatFlag(status.features.format_on_edit)}`, `- search_index: ${formatFlag(status.features.search_index)}`, `- semantic_search: ${formatFlag(status.features.semantic_search)}`, `- callgraph_store: ${formatFlag(status.features.callgraph_store)}`, "", "Search index", `- status: ${status.search_index.status}`, `- files: ${formatCount(status.search_index.files)}`, `- trigrams: ${formatCount(status.search_index.trigrams)}`, "", "Semantic index", `- status: ${formatSemanticIndexStatus(status.semantic_index.status, status.semantic_index.stage)}`);
17715
17745
  const refreshing = formatSemanticRefreshing(status.semantic_index.refreshing_count);
17716
17746
  if (refreshing) {
17717
17747
  lines.push(`- ${refreshing}`);
@@ -17761,9 +17791,18 @@ function formatStatusDialogMessage(status) {
17761
17791
  return lines.join(`
17762
17792
  `);
17763
17793
  }
17794
+ function appendDegradedStatus(lines, status, markdown) {
17795
+ if (!status.degraded || status.degraded_reasons.length === 0)
17796
+ return;
17797
+ lines.push("", markdown ? "### Degraded mode" : "Degraded mode");
17798
+ for (const reason of status.degraded_reasons) {
17799
+ const detail = reason === "home_root" ? "project root is your home directory; heavy indexes are disabled" : reason;
17800
+ lines.push(`- ${detail}`);
17801
+ }
17802
+ }
17764
17803
 
17765
17804
  // src/tools/_shared.ts
17766
- import { existsSync as existsSync9 } from "node:fs";
17805
+ import { existsSync as existsSync10 } from "node:fs";
17767
17806
  import { Type } from "typebox";
17768
17807
  var optionalInt = (min, max, description = "(integer)") => Type.Optional(Type.Union([Type.Integer({ minimum: min, maximum: max }), Type.String()], {
17769
17808
  description
@@ -17771,7 +17810,8 @@ var optionalInt = (min, max, description = "(integer)") => Type.Optional(Type.Un
17771
17810
  function withPathAliasPreparation(tool) {
17772
17811
  const existing = tool.prepareArguments;
17773
17812
  const prepare = (args) => {
17774
- const prepared = tool.name === "edit" ? prepareCanonicalEditArguments(tool.name, args) : prepareCanonicalPathArguments(tool.name, args);
17813
+ const isEditTool = tool.name === "edit" || tool.name === "aft_edit";
17814
+ const prepared = isEditTool ? prepareCanonicalEditArguments("edit", args) : prepareCanonicalPathArguments(tool.name, args);
17775
17815
  return existing ? existing(prepared) : prepared;
17776
17816
  };
17777
17817
  return {
@@ -17783,7 +17823,7 @@ function withPathAliasPreparation(tool) {
17783
17823
  };
17784
17824
  }
17785
17825
  function bridgeFor(ctx, cwd) {
17786
- if (!existsSync9(cwd)) {
17826
+ if (!existsSync10(cwd)) {
17787
17827
  throw new Error(`project directory no longer exists: ${cwd} (stale restored session?)`);
17788
17828
  }
17789
17829
  return ctx.pool.getBridge(cwd);
@@ -17831,8 +17871,10 @@ async function callBridge(bridge, command, params = {}, extCtx, options) {
17831
17871
  return response;
17832
17872
  }
17833
17873
  async function callToolCall(bridge, name, rawArgs = {}, extCtx, options) {
17874
+ return callToolCallForSession(bridge, name, rawArgs, extCtx ? resolveSessionId(extCtx) : undefined, extCtx, options);
17875
+ }
17876
+ async function callToolCallForSession(bridge, name, rawArgs, sessionId, extCtx, options) {
17834
17877
  const timeoutMs = timeoutForCommand(name);
17835
- const sessionId = extCtx ? resolveSessionId(extCtx) : undefined;
17836
17878
  const sendOptions = {
17837
17879
  ...timeoutMs !== undefined ? { timeoutMs } : {},
17838
17880
  configureWarningClient: extCtx,
@@ -17998,7 +18040,7 @@ function renderInner(s, error3, theme, innerWidth) {
17998
18040
  lines.push(line);
17999
18041
  lines.push("");
18000
18042
  lines.push(theme.fg("muted", "Features"));
18001
- lines.push(` ${featureBadge("format_on_edit", s.features.format_on_edit, theme)} ${featureBadge("search_index", s.features.search_index, theme)} ${featureBadge("semantic_search", s.features.semantic_search, theme)}`);
18043
+ lines.push(` ${featureBadge("format_on_edit", s.features.format_on_edit, theme)} ${featureBadge("search_index", s.features.search_index, theme)} ${featureBadge("semantic_search", s.features.semantic_search, theme)} ${featureBadge("callgraph_store", s.features.callgraph_store, theme)}`);
18002
18044
  const compressionRows = formatCompressionStatusRows(s.compression);
18003
18045
  if (compressionRows.length > 0) {
18004
18046
  lines.push("");
@@ -18155,7 +18197,7 @@ function registerStatusCommand(pi, ctx) {
18155
18197
  }
18156
18198
 
18157
18199
  // src/config.ts
18158
- import { existsSync as existsSync10, readFileSync as readFileSync7, renameSync as renameSync5, unlinkSync as unlinkSync5, writeFileSync as writeFileSync4 } from "node:fs";
18200
+ import { existsSync as existsSync11, readFileSync as readFileSync7, renameSync as renameSync5, unlinkSync as unlinkSync5, writeFileSync as writeFileSync4 } from "node:fs";
18159
18201
  import { homedir as homedir12 } from "node:os";
18160
18202
  import { isAbsolute as isAbsolute6 } from "node:path";
18161
18203
  var import_comment_json = __toESM(require_src2(), 1);
@@ -19771,8 +19813,8 @@ function emoji() {
19771
19813
  }
19772
19814
  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])$/;
19773
19815
  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}|:))$/;
19774
- var mac = (delimiter2) => {
19775
- const escapedDelim = escapeRegex(delimiter2 ?? ":");
19816
+ var mac = (delimiter3) => {
19817
+ const escapedDelim = escapeRegex(delimiter3 ?? ":");
19776
19818
  return new RegExp(`^(?:[0-9A-F]{2}${escapedDelim}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${escapedDelim}){5}[0-9a-f]{2}$`);
19777
19819
  };
19778
19820
  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])$/;
@@ -32437,6 +32479,26 @@ function date4(params) {
32437
32479
  // ../../node_modules/.bun/zod@4.4.3/node_modules/zod/v4/classic/external.js
32438
32480
  config(en_default());
32439
32481
  // src/config.ts
32482
+ var ACTIVE_HARNESS = "pi";
32483
+ function isConfigRecord(value) {
32484
+ return value !== null && typeof value === "object" && !Array.isArray(value);
32485
+ }
32486
+ function normalizeConfigForActiveHarness(value) {
32487
+ const config2 = stripHarnessSpecificConfigKeys(value, OPENCODE_ONLY_KEYS);
32488
+ if (!isConfigRecord(config2) || !isConfigRecord(config2.harnesses))
32489
+ return config2;
32490
+ const override = config2.harnesses[ACTIVE_HARNESS];
32491
+ return {
32492
+ ...config2,
32493
+ harnesses: override === undefined ? {} : { [ACTIVE_HARNESS]: stripHarnessSpecificConfigKeys(override, OPENCODE_ONLY_KEYS) }
32494
+ };
32495
+ }
32496
+ function warnIgnoredNestedHarnesses(rawConfig, configPath) {
32497
+ const override = isConfigRecord(rawConfig.harnesses) ? rawConfig.harnesses[ACTIVE_HARNESS] : undefined;
32498
+ if (isConfigRecord(override) && Object.hasOwn(override, "harnesses")) {
32499
+ warn2(`Ignoring nested harnesses in harnesses.${ACTIVE_HARNESS} from ${configPath}; harness overrides cannot recurse`);
32500
+ }
32501
+ }
32440
32502
  var DEFAULT_INSPECT_DIAGNOSTICS_TIMEOUT_MS = 120000;
32441
32503
  var MIN_INSPECT_DIAGNOSTICS_TIMEOUT_MS = 1e4;
32442
32504
  var MAX_INSPECT_DIAGNOSTICS_TIMEOUT_MS = 600000;
@@ -32455,6 +32517,7 @@ function resolveBashConfig(config2) {
32455
32517
  const surfaceDefaultEnabled = surface !== "minimal";
32456
32518
  const reminderEnabled = (typeof top === "object" && top !== null ? top.long_running_reminder_enabled : undefined) ?? legacy?.long_running_reminder_enabled;
32457
32519
  const reminderInterval = (typeof top === "object" && top !== null ? top.long_running_reminder_interval_ms : undefined) ?? legacy?.long_running_reminder_interval_ms;
32520
+ const topDetachOnUserMessage = typeof top === "object" && top !== null ? top.detach_on_user_message ?? true : true;
32458
32521
  const rawForegroundWait = typeof top === "object" && top !== null ? top.foreground_wait_window_ms : undefined;
32459
32522
  const foregroundWaitWindowMs = Math.max(FOREGROUND_WAIT_WINDOW_MIN_MS, rawForegroundWait ?? FOREGROUND_WAIT_WINDOW_DEFAULT_MS);
32460
32523
  const base = {
@@ -32463,9 +32526,11 @@ function resolveBashConfig(config2) {
32463
32526
  compress: false,
32464
32527
  background: false,
32465
32528
  host_fallback: false,
32529
+ detach_on_user_message: true,
32466
32530
  long_running_reminder_enabled: reminderEnabled,
32467
32531
  long_running_reminder_interval_ms: reminderInterval,
32468
- foreground_wait_window_ms: foregroundWaitWindowMs
32532
+ foreground_wait_window_ms: foregroundWaitWindowMs,
32533
+ powershell_tool: typeof top === "object" && top !== null ? top.powershell_tool ?? false : false
32469
32534
  };
32470
32535
  if (top === false)
32471
32536
  return base;
@@ -32479,7 +32544,8 @@ function resolveBashConfig(config2) {
32479
32544
  rewrite: top.rewrite ?? true,
32480
32545
  compress: top.compress ?? true,
32481
32546
  background: top.background ?? true,
32482
- host_fallback: top.host_fallback ?? false
32547
+ host_fallback: top.host_fallback ?? false,
32548
+ detach_on_user_message: topDetachOnUserMessage
32483
32549
  };
32484
32550
  }
32485
32551
  const hasLegacyFeatureFlag = legacy && (legacy.rewrite !== undefined || legacy.compress !== undefined || legacy.background !== undefined);
@@ -32593,9 +32659,11 @@ var BashFeaturesSchema = exports_external.object({
32593
32659
  compress: exports_external.boolean().optional(),
32594
32660
  background: exports_external.boolean().optional(),
32595
32661
  host_fallback: exports_external.boolean().optional(),
32662
+ detach_on_user_message: exports_external.boolean().optional(),
32596
32663
  long_running_reminder_enabled: exports_external.boolean().optional(),
32597
32664
  long_running_reminder_interval_ms: exports_external.number().int().positive().optional(),
32598
- foreground_wait_window_ms: exports_external.number().int().positive().optional()
32665
+ foreground_wait_window_ms: exports_external.number().int().positive().optional(),
32666
+ powershell_tool: exports_external.boolean().optional()
32599
32667
  });
32600
32668
  var BashConfigSchema = exports_external.union([exports_external.boolean(), BashFeaturesSchema]);
32601
32669
  var SandboxConfigSchema = exports_external.object({
@@ -32638,7 +32706,7 @@ var BackupConfigSchema = exports_external.object({
32638
32706
  max_depth: exports_external.number().int().positive().optional(),
32639
32707
  max_file_size: exports_external.number().int().positive().optional()
32640
32708
  });
32641
- var AftConfigSchema = exports_external.preprocess((value) => stripHarnessSpecificConfigKeys(value, OPENCODE_ONLY_KEYS), exports_external.object({
32709
+ var AftConfigFieldsSchema = exports_external.object({
32642
32710
  $schema: exports_external.string().optional(),
32643
32711
  enabled: exports_external.boolean().optional(),
32644
32712
  edit_mode: exports_external.enum(["default", "hashline"]).optional(),
@@ -32648,6 +32716,7 @@ var AftConfigSchema = exports_external.preprocess((value) => stripHarnessSpecifi
32648
32716
  formatter: exports_external.record(exports_external.string(), FormatterEnum).optional(),
32649
32717
  checker: exports_external.record(exports_external.string(), CheckerEnum).optional(),
32650
32718
  configure_warnings_delivery: ConfigureWarningsDeliveryEnum.optional(),
32719
+ hoist_builtin_tools: exports_external.boolean().optional(),
32651
32720
  tool_surface: exports_external.enum(["minimal", "recommended", "all"]).optional(),
32652
32721
  disabled_tools: exports_external.array(exports_external.string()).optional(),
32653
32722
  restrict_to_project_root: exports_external.boolean().optional(),
@@ -32669,7 +32738,20 @@ var AftConfigSchema = exports_external.preprocess((value) => stripHarnessSpecifi
32669
32738
  subc: SubcConfigSchema.optional(),
32670
32739
  gh_shim: GhShimConfigSchema.optional(),
32671
32740
  git: GitConfigSchema.optional()
32741
+ });
32742
+ var HarnessOverrideSchema = exports_external.preprocess((value) => {
32743
+ if (!isConfigRecord(value) || !Object.hasOwn(value, "harnesses"))
32744
+ return value;
32745
+ const { harnesses: _nestedHarnesses, ...override } = value;
32746
+ return override;
32747
+ }, AftConfigFieldsSchema.strict());
32748
+ var AftConfigSchema = exports_external.preprocess(normalizeConfigForActiveHarness, AftConfigFieldsSchema.extend({
32749
+ harnesses: exports_external.record(exports_external.string(), HarnessOverrideSchema).optional()
32672
32750
  }).strict());
32751
+ function applyActiveHarnessOverride(config2) {
32752
+ const { harnesses: _harnesses, ...base } = config2;
32753
+ return { ...base, ...config2.harnesses?.[ACTIVE_HARNESS] };
32754
+ }
32673
32755
  var CONFIG_MIGRATIONS = [
32674
32756
  { oldKey: "experimental_search_index", newPath: ["search_index"] },
32675
32757
  { oldKey: "experimental_semantic_search", newPath: ["semantic_search"] },
@@ -32780,7 +32862,7 @@ function migrateExperimentalBashBlock(rawConfig, configPath, logger) {
32780
32862
  return movedKeys;
32781
32863
  }
32782
32864
  function migrateAftConfigFile2(configPath, logger = { log: log2, warn: warn2 }) {
32783
- if (!existsSync10(configPath)) {
32865
+ if (!existsSync11(configPath)) {
32784
32866
  return { migrated: false, oldKeys: [] };
32785
32867
  }
32786
32868
  let tmpPath = null;
@@ -32831,9 +32913,15 @@ function recordConfigParseFailure(configPath, errorMessage) {
32831
32913
  configLoadErrors.push({ path: configPath, message: errorMessage });
32832
32914
  warn2(formatConfigParseFailureMessage(configPath, errorMessage));
32833
32915
  }
32916
+ function warnIgnoredHarnessSpecificConfigKeys(config2, configPath) {
32917
+ const ignored = OPENCODE_ONLY_KEYS.filter((key) => Object.hasOwn(config2, key));
32918
+ if (ignored.length === 0)
32919
+ return;
32920
+ warn2(`Ignoring OpenCode-only config key${ignored.length === 1 ? "" : "s"} ${ignored.map((key) => `\`${key}\``).join(", ")} in ${configPath}; ${ignored.length === 1 ? "it has" : "they have"} no effect on the Pi harness.`);
32921
+ }
32834
32922
  function loadConfigFromPath(configPath) {
32835
32923
  try {
32836
- if (!existsSync10(configPath))
32924
+ if (!existsSync11(configPath))
32837
32925
  return null;
32838
32926
  const content = readFileSync7(configPath, "utf-8");
32839
32927
  const rawConfig = import_comment_json.parse(content);
@@ -32843,14 +32931,16 @@ function loadConfigFromPath(configPath) {
32843
32931
  }
32844
32932
  migrateRawConfig(rawConfig, configPath, { log: log2, warn: warn2 });
32845
32933
  const cleanConfig = stripJsoncSymbols(rawConfig);
32934
+ warnIgnoredHarnessSpecificConfigKeys(cleanConfig, configPath);
32935
+ warnIgnoredNestedHarnesses(cleanConfig, configPath);
32846
32936
  const result = AftConfigSchema.safeParse(cleanConfig);
32847
32937
  if (result.success) {
32848
32938
  log2(`Config loaded from ${configPath}`);
32849
- return result.data;
32939
+ return applyActiveHarnessOverride(result.data);
32850
32940
  }
32851
32941
  const errorMsg = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ");
32852
32942
  warn2(`Config validation error in ${configPath}: ${errorMsg}`);
32853
- return parseConfigPartially(cleanConfig);
32943
+ return applyActiveHarnessOverride(parseConfigPartially(cleanConfig));
32854
32944
  } catch (err) {
32855
32945
  const errorMsg = err instanceof Error ? err.message : String(err);
32856
32946
  error2(`Error loading config from ${configPath}: ${errorMsg}`);
@@ -33006,6 +33096,7 @@ var PROJECT_SAFE_TOP_LEVEL_FIELDS = new Set([
33006
33096
  "enabled",
33007
33097
  "edit_mode",
33008
33098
  "tool_surface",
33099
+ "hoist_builtin_tools",
33009
33100
  "format_on_edit",
33010
33101
  "validate_on_edit",
33011
33102
  "configure_warnings_delivery",
@@ -33165,7 +33256,7 @@ import { spawn as spawn3 } from "node:child_process";
33165
33256
  import { createHash as createHash4 } from "node:crypto";
33166
33257
  import {
33167
33258
  createReadStream,
33168
- existsSync as existsSync12,
33259
+ existsSync as existsSync13,
33169
33260
  mkdirSync as mkdirSync7,
33170
33261
  readFileSync as readFileSync10,
33171
33262
  renameSync as renameSync6,
@@ -33173,7 +33264,7 @@ import {
33173
33264
  statSync as statSync8,
33174
33265
  writeFileSync as writeFileSync6
33175
33266
  } from "node:fs";
33176
- import { join as join14 } from "node:path";
33267
+ import { join as join15 } from "node:path";
33177
33268
 
33178
33269
  // src/lsp-cache.ts
33179
33270
  import {
@@ -33185,23 +33276,23 @@ import {
33185
33276
  unlinkSync as unlinkSync6,
33186
33277
  writeFileSync as writeFileSync5
33187
33278
  } from "node:fs";
33188
- import { join as join12 } from "node:path";
33279
+ import { join as join13 } from "node:path";
33189
33280
  function lspCacheRoot() {
33190
33281
  return getAftLspPackagesDir();
33191
33282
  }
33192
33283
  function lspPackageDir(npmPackage) {
33193
- return join12(lspCacheRoot(), encodeURIComponent(npmPackage));
33284
+ return join13(lspCacheRoot(), encodeURIComponent(npmPackage));
33194
33285
  }
33195
33286
  function lspBinaryPath(npmPackage, binary) {
33196
- return join12(lspPackageDir(npmPackage), "node_modules", ".bin", binary);
33287
+ return join13(lspPackageDir(npmPackage), "node_modules", ".bin", binary);
33197
33288
  }
33198
33289
  function lspBinDir(npmPackage) {
33199
- return join12(lspPackageDir(npmPackage), "node_modules", ".bin");
33290
+ return join13(lspPackageDir(npmPackage), "node_modules", ".bin");
33200
33291
  }
33201
33292
  function isInstalled(npmPackage, binary) {
33202
33293
  for (const candidate of lspBinaryCandidates(binary)) {
33203
33294
  try {
33204
- if (statSync7(join12(lspBinDir(npmPackage), candidate)).isFile())
33295
+ if (statSync7(join13(lspBinDir(npmPackage), candidate)).isFile())
33205
33296
  return true;
33206
33297
  } catch {}
33207
33298
  }
@@ -33221,13 +33312,13 @@ function writeInstalledMetaIn(installDir, version2, sha256) {
33221
33312
  installedAt: new Date().toISOString(),
33222
33313
  ...sha256 ? { sha256 } : {}
33223
33314
  };
33224
- writeFileSync5(join12(installDir, INSTALLED_META_FILE), JSON.stringify(meta3), "utf8");
33315
+ writeFileSync5(join13(installDir, INSTALLED_META_FILE), JSON.stringify(meta3), "utf8");
33225
33316
  } catch (err) {
33226
33317
  log2(`[lsp-cache] failed to write installed-meta in ${installDir}: ${err}`);
33227
33318
  }
33228
33319
  }
33229
33320
  function readInstalledMetaIn(installDir) {
33230
- const path2 = join12(installDir, INSTALLED_META_FILE);
33321
+ const path2 = join13(installDir, INSTALLED_META_FILE);
33231
33322
  try {
33232
33323
  if (!statSync7(path2).isFile())
33233
33324
  return null;
@@ -33251,7 +33342,7 @@ function readInstalledMeta(packageKey) {
33251
33342
  return readInstalledMetaIn(lspPackageDir(packageKey));
33252
33343
  }
33253
33344
  function lockPath(npmPackage) {
33254
- return join12(lspPackageDir(npmPackage), ".aft-installing");
33345
+ return join13(lspPackageDir(npmPackage), ".aft-installing");
33255
33346
  }
33256
33347
  var STALE_LOCK_MS2 = 30 * 60 * 1000;
33257
33348
  function acquireInstallLock(lockKey) {
@@ -33358,7 +33449,7 @@ async function withInstallLock(lockKey, task) {
33358
33449
  }
33359
33450
  var VERSION_CHECK_FILE = ".aft-version-check";
33360
33451
  function readVersionCheck(npmPackage) {
33361
- const file2 = join12(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
33452
+ const file2 = join13(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
33362
33453
  try {
33363
33454
  const raw = readFileSync8(file2, "utf8");
33364
33455
  const parsed = JSON.parse(raw);
@@ -33375,7 +33466,7 @@ function readVersionCheck(npmPackage) {
33375
33466
  }
33376
33467
  function writeVersionCheck(npmPackage, latest) {
33377
33468
  mkdirSync6(lspPackageDir(npmPackage), { recursive: true });
33378
- const file2 = join12(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
33469
+ const file2 = join13(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
33379
33470
  const record2 = {
33380
33471
  last_checked: new Date().toISOString(),
33381
33472
  latest_eligible: latest
@@ -33537,8 +33628,8 @@ var NPM_LSP_TABLE = [
33537
33628
  ];
33538
33629
 
33539
33630
  // src/lsp-project-relevance.ts
33540
- import { existsSync as existsSync11, readdirSync as readdirSync4, readFileSync as readFileSync9 } from "node:fs";
33541
- import { join as join13 } from "node:path";
33631
+ import { existsSync as existsSync12, readdirSync as readdirSync4, readFileSync as readFileSync9 } from "node:fs";
33632
+ import { join as join14 } from "node:path";
33542
33633
  var MAX_WALK_DIRS = 200;
33543
33634
  var MAX_WALK_DEPTH = 4;
33544
33635
  var NOISE_DIRS = new Set([
@@ -33555,7 +33646,7 @@ function hasRootMarker(projectRoot, rootMarkers) {
33555
33646
  if (!rootMarkers)
33556
33647
  return false;
33557
33648
  for (const marker of rootMarkers) {
33558
- if (existsSync11(join13(projectRoot, marker)))
33649
+ if (existsSync12(join14(projectRoot, marker)))
33559
33650
  return true;
33560
33651
  }
33561
33652
  return false;
@@ -33579,7 +33670,7 @@ function hasPackageJsonDep(projectRoot, depNames) {
33579
33670
  }
33580
33671
  function readPackageJson(projectRoot) {
33581
33672
  try {
33582
- const raw = readFileSync9(join13(projectRoot, "package.json"), "utf8");
33673
+ const raw = readFileSync9(join14(projectRoot, "package.json"), "utf8");
33583
33674
  const parsed = JSON.parse(raw);
33584
33675
  if (typeof parsed !== "object" || parsed === null)
33585
33676
  return null;
@@ -33609,7 +33700,7 @@ function relevantExtensionsInProject(projectRoot, extToServer) {
33609
33700
  for (const entry of entries) {
33610
33701
  if (entry.isDirectory()) {
33611
33702
  if (current.depth < MAX_WALK_DEPTH && !NOISE_DIRS.has(entry.name.toLowerCase())) {
33612
- queue.push({ dir: join13(current.dir, entry.name), depth: current.depth + 1 });
33703
+ queue.push({ dir: join14(current.dir, entry.name), depth: current.depth + 1 });
33613
33704
  }
33614
33705
  continue;
33615
33706
  }
@@ -33736,8 +33827,8 @@ async function resolveTargetVersion(spec, config2, fetchImpl = fetch) {
33736
33827
  }
33737
33828
  function ensureInstallAnchor(cwd) {
33738
33829
  try {
33739
- const stub = join14(cwd, "package.json");
33740
- if (!existsSync12(stub)) {
33830
+ const stub = join15(cwd, "package.json");
33831
+ if (!existsSync13(stub)) {
33741
33832
  writeFileSync6(stub, `${JSON.stringify({ name: "aft-lsp-cache", version: "0.0.0", private: true })}
33742
33833
  `);
33743
33834
  }
@@ -33928,10 +34019,10 @@ function sha256OfFileSync(path2) {
33928
34019
  }
33929
34020
  function quarantineCachedNpmInstall(spec, reason) {
33930
34021
  const packageDir = lspPackageDir(spec.npm);
33931
- const dest = join14(packageDir, "..", ".quarantine", encodeURIComponent(spec.npm), `${Date.now()}`);
34022
+ const dest = join15(packageDir, "..", ".quarantine", encodeURIComponent(spec.npm), `${Date.now()}`);
33932
34023
  warn2(`[lsp] tofu_mismatch ${spec.npm}: ${reason}; quarantining ${packageDir} -> ${dest}`);
33933
34024
  try {
33934
- mkdirSync7(join14(dest, ".."), { recursive: true });
34025
+ mkdirSync7(join15(dest, ".."), { recursive: true });
33935
34026
  rmSync4(dest, { recursive: true, force: true });
33936
34027
  renameSync6(packageDir, dest);
33937
34028
  } catch (err) {
@@ -34019,7 +34110,7 @@ import {
34019
34110
  copyFileSync as copyFileSync4,
34020
34111
  createReadStream as createReadStream2,
34021
34112
  createWriteStream as createWriteStream3,
34022
- existsSync as existsSync13,
34113
+ existsSync as existsSync14,
34023
34114
  lstatSync as lstatSync2,
34024
34115
  mkdirSync as mkdirSync8,
34025
34116
  readdirSync as readdirSync5,
@@ -34032,7 +34123,7 @@ import {
34032
34123
  unlinkSync as unlinkSync7,
34033
34124
  writeFileSync as writeFileSync7
34034
34125
  } from "node:fs";
34035
- import { dirname as dirname6, join as join15, relative as relative3, resolve as resolve8 } from "node:path";
34126
+ import { dirname as dirname6, join as join16, relative as relative3, resolve as resolve8 } from "node:path";
34036
34127
  import { Readable as Readable3 } from "node:stream";
34037
34128
  import { pipeline as pipeline3 } from "node:stream/promises";
34038
34129
 
@@ -34125,23 +34216,23 @@ function ghCacheRoot() {
34125
34216
  return getAftLspBinariesDir();
34126
34217
  }
34127
34218
  function ghPackageDir(spec) {
34128
- return join15(ghCacheRoot(), spec.id);
34219
+ return join16(ghCacheRoot(), spec.id);
34129
34220
  }
34130
34221
  function ghBinDir(spec) {
34131
- return join15(ghPackageDir(spec), "bin");
34222
+ return join16(ghPackageDir(spec), "bin");
34132
34223
  }
34133
34224
  function ghExtractDir(spec) {
34134
- return join15(ghPackageDir(spec), "extracted");
34225
+ return join16(ghPackageDir(spec), "extracted");
34135
34226
  }
34136
34227
  var INSTALLED_META_FILE2 = ".aft-installed";
34137
34228
  function ghBinaryPath(spec, platform) {
34138
34229
  const ext = platform === "win32" ? ".exe" : "";
34139
- return join15(ghBinDir(spec), `${spec.binary}${ext}`);
34230
+ return join16(ghBinDir(spec), `${spec.binary}${ext}`);
34140
34231
  }
34141
34232
  function isGithubInstalled(spec, platform) {
34142
34233
  for (const candidate of ghBinaryCandidates(spec, platform)) {
34143
34234
  try {
34144
- if (statSync9(join15(ghBinDir(spec), candidate)).isFile())
34235
+ if (statSync9(join16(ghBinDir(spec), candidate)).isFile())
34145
34236
  return true;
34146
34237
  } catch {}
34147
34238
  }
@@ -34154,7 +34245,7 @@ function ghBinaryCandidates(spec, platform) {
34154
34245
  }
34155
34246
  function readGithubInstalledMetaIn(installDir) {
34156
34247
  try {
34157
- const path2 = join15(installDir, INSTALLED_META_FILE2);
34248
+ const path2 = join16(installDir, INSTALLED_META_FILE2);
34158
34249
  if (!statSync9(path2).isFile())
34159
34250
  return null;
34160
34251
  const parsed = JSON.parse(readFileSync11(path2, "utf8"));
@@ -34181,7 +34272,7 @@ function writeGithubInstalledMetaIn(installDir, version2, binarySha256, archiveS
34181
34272
  binarySha256,
34182
34273
  ...archiveSha256 ? { archiveSha256 } : {}
34183
34274
  };
34184
- writeFileSync7(join15(installDir, INSTALLED_META_FILE2), JSON.stringify(meta3), "utf8");
34275
+ writeFileSync7(join16(installDir, INSTALLED_META_FILE2), JSON.stringify(meta3), "utf8");
34185
34276
  } catch (err) {
34186
34277
  warn2(`[lsp] failed to write github installed metadata in ${installDir}: ${err}`);
34187
34278
  }
@@ -34430,7 +34521,7 @@ function validateExtraction(stagingRoot) {
34430
34521
  throw new Error(`failed to read staging dir ${dir}: ${err}`);
34431
34522
  }
34432
34523
  for (const entry of entries) {
34433
- const full = join15(dir, entry);
34524
+ const full = join16(dir, entry);
34434
34525
  let lst;
34435
34526
  try {
34436
34527
  lst = lstatSync2(full);
@@ -34518,7 +34609,7 @@ function extractArchiveSafely(archivePath, destDir, archiveType) {
34518
34609
  }
34519
34610
  function quarantineCachedGithubInstall(spec, reason) {
34520
34611
  const packageDir = ghPackageDir(spec);
34521
- const dest = join15(ghCacheRoot(), ".quarantine", encodeURIComponent(spec.id), `${Date.now()}`);
34612
+ const dest = join16(ghCacheRoot(), ".quarantine", encodeURIComponent(spec.id), `${Date.now()}`);
34522
34613
  warn2(`[lsp] tofu_mismatch ${spec.id}: ${reason}; quarantining ${packageDir} -> ${dest}`);
34523
34614
  try {
34524
34615
  mkdirSync8(dirname6(dest), { recursive: true });
@@ -34531,7 +34622,7 @@ function quarantineCachedGithubInstall(spec, reason) {
34531
34622
  function validateCachedGithubInstall(spec, platform) {
34532
34623
  const packageDir = ghPackageDir(spec);
34533
34624
  const meta3 = readGithubInstalledMetaIn(packageDir);
34534
- const binaryPath = ghBinaryCandidates(spec, platform).map((candidate) => join15(ghBinDir(spec), candidate)).find((candidate) => {
34625
+ const binaryPath = ghBinaryCandidates(spec, platform).map((candidate) => join16(ghBinDir(spec), candidate)).find((candidate) => {
34535
34626
  try {
34536
34627
  return statSync9(candidate).isFile();
34537
34628
  } catch {
@@ -34609,7 +34700,7 @@ async function downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl,
34609
34700
  }
34610
34701
  const pkgDir = ghPackageDir(spec);
34611
34702
  const extractDir = ghExtractDir(spec);
34612
- const archivePath = join15(pkgDir, expected.name);
34703
+ const archivePath = join16(pkgDir, expected.name);
34613
34704
  log2(`[lsp] downloading ${spec.id} ${tag} → ${matchingAsset.url}`);
34614
34705
  try {
34615
34706
  await downloadFile(matchingAsset.url, archivePath, fetchImpl, matchingAsset.size, signal);
@@ -34649,8 +34740,8 @@ async function downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl,
34649
34740
  unlinkSync7(archivePath);
34650
34741
  } catch {}
34651
34742
  }
34652
- const innerBinaryPath = join15(extractDir, spec.binaryPathInArchive(platform, arch, version2));
34653
- if (!existsSync13(innerBinaryPath)) {
34743
+ const innerBinaryPath = join16(extractDir, spec.binaryPathInArchive(platform, arch, version2));
34744
+ if (!existsSync14(innerBinaryPath)) {
34654
34745
  error2(`[lsp] ${spec.id}: extracted binary not found at ${innerBinaryPath}`);
34655
34746
  return null;
34656
34747
  }
@@ -34734,7 +34825,7 @@ function runGithubAutoInstall(relevantServers, config2, fetchImpl = fetch) {
34734
34825
  if (!host) {
34735
34826
  for (const spec of GITHUB_LSP_TABLE) {
34736
34827
  try {
34737
- if (existsSync13(ghBinDir(spec))) {
34828
+ if (existsSync14(ghBinDir(spec))) {
34738
34829
  cachedBinDirs.push(ghBinDir(spec));
34739
34830
  }
34740
34831
  } catch {}
@@ -34961,6 +35052,17 @@ function sendFeatureAnnouncement(version2, features, footer, storageDir) {
34961
35052
 
34962
35053
  // src/bash-wait-detach.ts
34963
35054
  var BASH_TRANSPORT_TIMEOUT_MS = 30000;
35055
+ var BASH_WAIT_DETACH_MAGIC_KEYWORD = "&detach";
35056
+ var EMPTY_DETACH_MESSAGE = "(requested background detach)";
35057
+ function stripUserMessageDetachKeyword(messageText) {
35058
+ if (!messageText.includes(BASH_WAIT_DETACH_MAGIC_KEYWORD))
35059
+ return messageText;
35060
+ const stripped = messageText.replaceAll(BASH_WAIT_DETACH_MAGIC_KEYWORD, "").replace(/[ \t]{2,}/g, " ");
35061
+ return stripped.trim() === "" ? EMPTY_DETACH_MESSAGE : stripped;
35062
+ }
35063
+ function shouldDetachBashWaitOnUserMessage(config2, messageText) {
35064
+ return resolveBashConfig(config2).detach_on_user_message || messageText.includes(BASH_WAIT_DETACH_MAGIC_KEYWORD);
35065
+ }
34964
35066
  async function sendBashWaitDetach(bridge, sessionID) {
34965
35067
  const response = await bridge.send("bash_wait_detach", { session_id: sessionID }, { keepBridgeOnTimeout: true, transportTimeoutMs: BASH_TRANSPORT_TIMEOUT_MS });
34966
35068
  if (response.success === false) {
@@ -35087,111 +35189,31 @@ import { Type as Type2 } from "typebox";
35087
35189
 
35088
35190
  // src/tools/diff-format.ts
35089
35191
  import { diffLines } from "diff";
35090
- var DEFAULT_CONTEXT_LINES = 4;
35091
- function formatDiffForPi(oldContent, newContent, contextLines = DEFAULT_CONTEXT_LINES) {
35092
- const parts = diffLines(oldContent, newContent);
35093
- const output = [];
35094
- const oldLines = oldContent.split(`
35095
- `);
35096
- const newLines = newContent.split(`
35097
- `);
35098
- const maxLineNum = Math.max(oldLines.length, newLines.length);
35099
- const lineNumWidth = String(maxLineNum).length;
35100
- const pad = (n) => String(n).padStart(lineNumWidth, " ");
35101
- const blank = " ".repeat(lineNumWidth);
35102
- let oldLineNum = 1;
35103
- let newLineNum = 1;
35104
- let lastWasChange = false;
35105
- let firstChangedLine;
35106
- for (let i = 0;i < parts.length; i++) {
35107
- const part = parts[i];
35108
- const raw = part.value.split(`
35109
- `);
35110
- if (raw[raw.length - 1] === "")
35111
- raw.pop();
35112
- if (part.added || part.removed) {
35113
- if (firstChangedLine === undefined)
35114
- firstChangedLine = newLineNum;
35115
- for (const line of raw) {
35116
- if (part.added) {
35117
- output.push(`+${pad(newLineNum)} ${line}`);
35118
- newLineNum++;
35119
- } else {
35120
- output.push(`-${pad(oldLineNum)} ${line}`);
35121
- oldLineNum++;
35122
- }
35123
- }
35124
- lastWasChange = true;
35125
- continue;
35126
- }
35127
- const nextIsChange = i < parts.length - 1 && (parts[i + 1].added || parts[i + 1].removed);
35128
- const hasLeading = lastWasChange;
35129
- const hasTrailing = nextIsChange;
35130
- if (hasLeading && hasTrailing) {
35131
- if (raw.length <= contextLines * 2) {
35132
- for (const line of raw) {
35133
- output.push(` ${pad(oldLineNum)} ${line}`);
35134
- oldLineNum++;
35135
- newLineNum++;
35136
- }
35137
- } else {
35138
- for (const line of raw.slice(0, contextLines)) {
35139
- output.push(` ${pad(oldLineNum)} ${line}`);
35140
- oldLineNum++;
35141
- newLineNum++;
35142
- }
35143
- const skipped = raw.length - contextLines * 2;
35144
- output.push(` ${blank} ...`);
35145
- oldLineNum += skipped;
35146
- newLineNum += skipped;
35147
- for (const line of raw.slice(raw.length - contextLines)) {
35148
- output.push(` ${pad(oldLineNum)} ${line}`);
35149
- oldLineNum++;
35150
- newLineNum++;
35151
- }
35152
- }
35153
- } else if (hasLeading) {
35154
- const shown = raw.slice(0, contextLines);
35155
- for (const line of shown) {
35156
- output.push(` ${pad(oldLineNum)} ${line}`);
35157
- oldLineNum++;
35158
- newLineNum++;
35159
- }
35160
- const skipped = raw.length - shown.length;
35161
- if (skipped > 0) {
35162
- output.push(` ${blank} ...`);
35163
- oldLineNum += skipped;
35164
- newLineNum += skipped;
35165
- }
35166
- } else if (hasTrailing) {
35167
- const shownCount = Math.min(contextLines, raw.length);
35168
- const shown = raw.slice(raw.length - shownCount);
35169
- const skipped = raw.length - shown.length;
35170
- if (skipped > 0) {
35171
- output.push(` ${blank} ...`);
35172
- oldLineNum += skipped;
35173
- newLineNum += skipped;
35174
- }
35175
- for (const line of shown) {
35176
- output.push(` ${pad(oldLineNum)} ${line}`);
35177
- oldLineNum++;
35178
- newLineNum++;
35179
- }
35180
- } else {
35181
- oldLineNum += raw.length;
35182
- newLineNum += raw.length;
35183
- }
35184
- lastWasChange = false;
35185
- }
35186
- return { diff: output.join(`
35187
- `), firstChangedLine };
35188
- }
35189
35192
 
35190
35193
  // src/tools/render-helpers.ts
35191
35194
  import { homedir as homedir13 } from "node:os";
35192
35195
  import { renderDiff } from "@earendil-works/pi-coding-agent";
35193
35196
  import { Container, Spacer, Text } from "@earendil-works/pi-tui";
35194
35197
  var NATIVE_COLLAPSED_RESULT_MAX_LINES = 5;
35198
+ function normalizeTerminalText(text) {
35199
+ return text.replace(/\r/g, "");
35200
+ }
35201
+
35202
+ class TerminalSafeComponent {
35203
+ component;
35204
+ constructor(component) {
35205
+ this.component = component;
35206
+ }
35207
+ render(width) {
35208
+ return this.component.render(width).map(normalizeTerminalText);
35209
+ }
35210
+ invalidate() {
35211
+ this.component.invalidate?.();
35212
+ }
35213
+ handleInput(data) {
35214
+ this.component.handleInput?.(data);
35215
+ }
35216
+ }
35195
35217
  function reuseText(last) {
35196
35218
  return last instanceof Text ? last : new Text("", 0, 0);
35197
35219
  }
@@ -35244,8 +35266,10 @@ function collapsibleResult({
35244
35266
  expanded,
35245
35267
  context
35246
35268
  }) {
35247
- if (expanded === true || full.render(200).length <= NATIVE_COLLAPSED_RESULT_MAX_LINES) {
35248
- return full;
35269
+ const renderedAtCollapseWidth = full.render(200);
35270
+ const terminalSafeFull = renderedAtCollapseWidth.some((line) => line.includes("\r")) ? new TerminalSafeComponent(full) : full;
35271
+ if (expanded === true || renderedAtCollapseWidth.length <= NATIVE_COLLAPSED_RESULT_MAX_LINES) {
35272
+ return terminalSafeFull;
35249
35273
  }
35250
35274
  const text = reuseText(context.lastComponent);
35251
35275
  text.setText(`
@@ -35356,18 +35380,122 @@ function formatUnifiedDiffForPi(unifiedDiff) {
35356
35380
  if (entries.length === 0)
35357
35381
  return "";
35358
35382
  const width = String(entries.reduce((max, entry) => Math.max(max, entry.line), 1)).length;
35359
- return entries.map((entry) => `${entry.prefix}${String(entry.line).padStart(width, " ")} ${entry.text}`).join(`
35360
- `);
35383
+ return normalizeTerminalText(entries.map((entry) => `${entry.prefix}${String(entry.line).padStart(width, " ")} ${entry.text}`).join(`
35384
+ `));
35361
35385
  }
35362
35386
  function renderUnifiedDiff(unifiedDiff) {
35363
35387
  const piDiff = formatUnifiedDiffForPi(unifiedDiff);
35364
35388
  if (!piDiff)
35365
35389
  return "";
35366
35390
  try {
35367
- return renderDiff(piDiff);
35391
+ return normalizeTerminalText(renderDiff(piDiff));
35368
35392
  } catch {
35369
- return piDiff;
35393
+ return normalizeTerminalText(piDiff);
35394
+ }
35395
+ }
35396
+
35397
+ // src/tools/diff-format.ts
35398
+ var DEFAULT_CONTEXT_LINES = 4;
35399
+ function formatDiffForPi(oldContent, newContent, contextLines = DEFAULT_CONTEXT_LINES) {
35400
+ const parts = diffLines(oldContent, newContent);
35401
+ const output = [];
35402
+ const oldLines = oldContent.split(`
35403
+ `);
35404
+ const newLines = newContent.split(`
35405
+ `);
35406
+ const maxLineNum = Math.max(oldLines.length, newLines.length);
35407
+ const lineNumWidth = String(maxLineNum).length;
35408
+ const pad = (n) => String(n).padStart(lineNumWidth, " ");
35409
+ const blank = " ".repeat(lineNumWidth);
35410
+ let oldLineNum = 1;
35411
+ let newLineNum = 1;
35412
+ let lastWasChange = false;
35413
+ let firstChangedLine;
35414
+ for (let i = 0;i < parts.length; i++) {
35415
+ const part = parts[i];
35416
+ const raw = part.value.split(`
35417
+ `);
35418
+ if (raw[raw.length - 1] === "")
35419
+ raw.pop();
35420
+ if (part.added || part.removed) {
35421
+ if (firstChangedLine === undefined)
35422
+ firstChangedLine = newLineNum;
35423
+ for (const line of raw) {
35424
+ if (part.added) {
35425
+ output.push(`+${pad(newLineNum)} ${line}`);
35426
+ newLineNum++;
35427
+ } else {
35428
+ output.push(`-${pad(oldLineNum)} ${line}`);
35429
+ oldLineNum++;
35430
+ }
35431
+ }
35432
+ lastWasChange = true;
35433
+ continue;
35434
+ }
35435
+ const nextIsChange = i < parts.length - 1 && (parts[i + 1].added || parts[i + 1].removed);
35436
+ const hasLeading = lastWasChange;
35437
+ const hasTrailing = nextIsChange;
35438
+ if (hasLeading && hasTrailing) {
35439
+ if (raw.length <= contextLines * 2) {
35440
+ for (const line of raw) {
35441
+ output.push(` ${pad(oldLineNum)} ${line}`);
35442
+ oldLineNum++;
35443
+ newLineNum++;
35444
+ }
35445
+ } else {
35446
+ for (const line of raw.slice(0, contextLines)) {
35447
+ output.push(` ${pad(oldLineNum)} ${line}`);
35448
+ oldLineNum++;
35449
+ newLineNum++;
35450
+ }
35451
+ const skipped = raw.length - contextLines * 2;
35452
+ output.push(` ${blank} ...`);
35453
+ oldLineNum += skipped;
35454
+ newLineNum += skipped;
35455
+ for (const line of raw.slice(raw.length - contextLines)) {
35456
+ output.push(` ${pad(oldLineNum)} ${line}`);
35457
+ oldLineNum++;
35458
+ newLineNum++;
35459
+ }
35460
+ }
35461
+ } else if (hasLeading) {
35462
+ const shown = raw.slice(0, contextLines);
35463
+ for (const line of shown) {
35464
+ output.push(` ${pad(oldLineNum)} ${line}`);
35465
+ oldLineNum++;
35466
+ newLineNum++;
35467
+ }
35468
+ const skipped = raw.length - shown.length;
35469
+ if (skipped > 0) {
35470
+ output.push(` ${blank} ...`);
35471
+ oldLineNum += skipped;
35472
+ newLineNum += skipped;
35473
+ }
35474
+ } else if (hasTrailing) {
35475
+ const shownCount = Math.min(contextLines, raw.length);
35476
+ const shown = raw.slice(raw.length - shownCount);
35477
+ const skipped = raw.length - shown.length;
35478
+ if (skipped > 0) {
35479
+ output.push(` ${blank} ...`);
35480
+ oldLineNum += skipped;
35481
+ newLineNum += skipped;
35482
+ }
35483
+ for (const line of shown) {
35484
+ output.push(` ${pad(oldLineNum)} ${line}`);
35485
+ oldLineNum++;
35486
+ newLineNum++;
35487
+ }
35488
+ } else {
35489
+ oldLineNum += raw.length;
35490
+ newLineNum += raw.length;
35491
+ }
35492
+ lastWasChange = false;
35370
35493
  }
35494
+ return {
35495
+ diff: normalizeTerminalText(output.join(`
35496
+ `)),
35497
+ firstChangedLine
35498
+ };
35371
35499
  }
35372
35500
 
35373
35501
  // src/tools/hoisted.ts
@@ -35606,13 +35734,18 @@ function renderReadCall(args, theme, context) {
35606
35734
  return text;
35607
35735
  }
35608
35736
  function registerHoistedTools(pi, ctx, surface) {
35737
+ const prefix = surface.hoistBuiltinTools === false ? "aft_" : "";
35738
+ const readName = `${prefix}read`;
35739
+ const writeName = `${prefix}write`;
35740
+ const editName = `${prefix}edit`;
35741
+ const grepName = `${prefix}grep`;
35609
35742
  if (surface.hoistRead) {
35610
35743
  pi.registerTool(withPathAliasPreparation({
35611
- name: "read",
35612
- label: "read",
35744
+ name: readName,
35745
+ label: readName,
35613
35746
  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.",
35614
35747
  promptSnippet: "Read file contents (supports offset/limit for large files)",
35615
- promptGuidelines: ["Use read to examine files instead of cat or sed."],
35748
+ promptGuidelines: [`Use ${readName} to examine files instead of cat or sed.`],
35616
35749
  parameters: ReadParams,
35617
35750
  async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
35618
35751
  const bridge = bridgeFor(ctx, extCtx.cwd);
@@ -35674,11 +35807,11 @@ PDFs aren't supported on the Pi harness yet.`, response);
35674
35807
  if (surface.hoistWrite) {
35675
35808
  const writeBackupText = ctx.config.backup?.enabled === false ? "Backup capture is disabled by user config." : "Existing files are backed up before overwriting (undo via aft_safety).";
35676
35809
  pi.registerTool(withPathAliasPreparation({
35677
- name: "write",
35678
- label: "write",
35679
- description: `Write content to a file, creating it and parent directories automatically. ${writeBackupText} Auto-formats when the project has a formatter configured. Uses \`path\`. For partial edits, use the \`edit\` tool.`,
35810
+ name: writeName,
35811
+ label: writeName,
35812
+ description: `Write content to a file, creating it and parent directories automatically. ${writeBackupText} Auto-formats when the project has a formatter configured. Uses \`path\`. For partial edits, use the \`${editName}\` tool.`,
35680
35813
  promptSnippet: "Create or overwrite files (uses path; auto-formats)",
35681
- promptGuidelines: ["Use write only for new files or complete rewrites."],
35814
+ promptGuidelines: [`Use ${writeName} only for new files or complete rewrites.`],
35682
35815
  parameters: WriteParams,
35683
35816
  async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
35684
35817
  const filePathArg = mutationFilePathArg(params);
@@ -35710,10 +35843,10 @@ PDFs aren't supported on the Pi harness yet.`, response);
35710
35843
  }
35711
35844
  if (surface.hoistEdit && ctx.hashlineEffective === true) {
35712
35845
  pi.registerTool({
35713
- name: "edit",
35714
- label: "edit",
35846
+ name: editName,
35847
+ label: editName,
35715
35848
  description: HASHLINE_EDIT_DESCRIPTION,
35716
- promptSnippet: "Apply a tagged hashline patch through the edit tool",
35849
+ promptSnippet: `Apply a tagged hashline patch through the ${editName} tool`,
35717
35850
  promptGuidelines: [
35718
35851
  "Read a file to obtain its current hashline tag before editing it.",
35719
35852
  "Pass exactly one patch string; paths and tags belong in patch section headers."
@@ -35721,8 +35854,9 @@ PDFs aren't supported on the Pi harness yet.`, response);
35721
35854
  parameters: HashlineEditParams,
35722
35855
  async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
35723
35856
  const bridge = bridgeFor(ctx, extCtx.cwd);
35857
+ const sessionId = resolveSessionId(extCtx);
35724
35858
  const rawArgs = { patch: params.patch };
35725
- const preflight = await callToolCall(bridge, "hashline_preflight", rawArgs, extCtx);
35859
+ const preflight = await callToolCallForSession(bridge, "hashline_preflight", rawArgs, sessionId, extCtx);
35726
35860
  if (preflight.success === false)
35727
35861
  throw toolErrorFromResponse("edit", preflight);
35728
35862
  for (const target of [
@@ -35733,10 +35867,12 @@ PDFs aren't supported on the Pi harness yet.`, response);
35733
35867
  restrictToProjectRoot: surface.restrictToProjectRoot
35734
35868
  });
35735
35869
  }
35736
- const preview = await callToolCall(bridge, "edit", rawArgs, extCtx, { preview: true });
35870
+ const preview = await callToolCallForSession(bridge, "edit", rawArgs, sessionId, extCtx, {
35871
+ preview: true
35872
+ });
35737
35873
  if (preview.success === false)
35738
35874
  throw toolErrorFromResponse("edit", preview);
35739
- const response = await callToolCall(bridge, "edit", rawArgs, extCtx);
35875
+ const response = await callToolCallForSession(bridge, "edit", rawArgs, sessionId, extCtx);
35740
35876
  if (response.success === false)
35741
35877
  throw toolErrorFromResponse("edit", response);
35742
35878
  return buildMutationResult(response);
@@ -35745,13 +35881,13 @@ PDFs aren't supported on the Pi harness yet.`, response);
35745
35881
  }
35746
35882
  if (surface.hoistEdit && ctx.hashlineEffective !== true) {
35747
35883
  pi.registerTool(withPathAliasPreparation({
35748
- name: "edit",
35749
- label: "edit",
35884
+ name: editName,
35885
+ label: editName,
35750
35886
  description: "Edit part of a file via `appendContent`, batch `edits[]`, or symbol plus `content`. Batch `{ oldString, newString, replaceAll: true }` replaces every match. Provide exactly one mode per call: appendContent, edits[], or symbol plus content (mixing modes is rejected).",
35751
35887
  promptSnippet: "Partial file edits via appendContent, edits[], or symbol plus content (exactly one mode per call).",
35752
35888
  promptGuidelines: [
35753
- "Prefer edit over write when changing part of an existing file.",
35754
- "Use appendContent when adding text to the end of a file.",
35889
+ `Prefer ${editName} over ${writeName} when changing part of an existing file.`,
35890
+ `Use ${editName} with appendContent when adding text to the end of a file.`,
35755
35891
  "Use edits[] for multiple atomic changes in one file.",
35756
35892
  "Include enough surrounding context in an edits[] find/replace item to make the match unique, or set replaceAll/occurrence explicitly."
35757
35893
  ],
@@ -35793,11 +35929,11 @@ PDFs aren't supported on the Pi harness yet.`, response);
35793
35929
  }
35794
35930
  if (surface.hoistGrep) {
35795
35931
  pi.registerTool(withPathAliasPreparation({
35796
- name: "grep",
35797
- label: "grep",
35932
+ name: grepName,
35933
+ label: grepName,
35798
35934
  description: "Search for a regex pattern across files. Uses AFT's trigram index inside the project root for fast repeated queries, and falls back to ripgrep for paths outside the project root.",
35799
35935
  promptSnippet: "Fast regex search across files (trigram-indexed inside the project root)",
35800
- promptGuidelines: ["Prefer grep over bash-invoked find/rg for in-project searches."],
35936
+ promptGuidelines: [`Prefer ${grepName} over bash-invoked find/rg for in-project searches.`],
35801
35937
  parameters: GrepParams,
35802
35938
  async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
35803
35939
  const bridge = bridgeFor(ctx, extCtx.cwd);
@@ -35980,7 +36116,7 @@ ${summary2}${suffix}`);
35980
36116
  } catch {
35981
36117
  renderedDiff = diff;
35982
36118
  }
35983
- container.addChild(new Text2(renderedDiff, 1, 0));
36119
+ container.addChild(new Text2(normalizeTerminalText(renderedDiff), 1, 0));
35984
36120
  const rawPath = context.args && typeof context.args === "object" && typeof context.args.path === "string" ? context.args.path : undefined;
35985
36121
  const summaryPath = rawPath ? ` ${shortenPath3(rawPath)}` : "";
35986
36122
  const summary = `edited${summaryPath} (+${details?.additions ?? 0}/-${details?.deletions ?? 0})`;
@@ -36286,7 +36422,7 @@ var BashBaseParams = {
36286
36422
  description: "Human-readable description shown in UI logs. Helps users understand what the command does without reading shell syntax."
36287
36423
  })),
36288
36424
  wait: Type4.Optional(Type4.Boolean({
36289
- 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."
36425
+ 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."
36290
36426
  })),
36291
36427
  sandbox: Type4.Optional(Type4.Literal("host", {
36292
36428
  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."
@@ -36381,11 +36517,17 @@ ${ask.command ?? ""}
36381
36517
  Working directory:
36382
36518
  ${ask.cwd ?? ""}`;
36383
36519
  }
36384
- async function callBashWithPermissionLoop(bridge, params, extCtx, options) {
36520
+ function piPowerShellAskText(command) {
36521
+ return `PowerShell commands are conservatively approved one at a time because AFT's POSIX shell scanner cannot safely interpret PowerShell syntax.
36522
+
36523
+ Exact command:
36524
+ ${command}`;
36525
+ }
36526
+ async function callBashWithPermissionLoop(bridge, params, extCtx, options, bridgeCommand = "bash") {
36385
36527
  const granted = Array.isArray(params.permissions_granted) ? params.permissions_granted.filter((value) => typeof value === "string") : [];
36386
36528
  for (let round = 0;round < 8; round++) {
36387
36529
  try {
36388
- return await callBashBridge(bridge, "bash", { ...params, ...granted.length > 0 ? { permissions_granted: granted } : {} }, extCtx, options);
36530
+ return await callBashBridge(bridge, bridgeCommand, { ...params, ...granted.length > 0 ? { permissions_granted: granted } : {} }, extCtx, options);
36389
36531
  } catch (error53) {
36390
36532
  if (!(error53 instanceof BridgeError)) {
36391
36533
  if (error53 instanceof Error && error53.message.includes("permission_required")) {
@@ -36396,19 +36538,30 @@ async function callBashWithPermissionLoop(bridge, params, extCtx, options) {
36396
36538
  if (error53.code !== "permission_required")
36397
36539
  throw error53;
36398
36540
  const asks = Array.isArray(error53.response?.asks) ? error53.response.asks : [];
36399
- if (asks.length === 0 || asks.some((ask) => ask.kind !== "escalation")) {
36400
- throw new Error("Permission ask reached Pi adapter without a host escalation grant — this is a bug.");
36401
- }
36402
- if (!extCtx.hasUI || typeof extCtx.ui?.confirm !== "function") {
36403
- throw new BridgeError("Permission denied: host escalation approval requires an interactive UI.", "permission_denied");
36541
+ if (asks.length === 0 || !extCtx.hasUI || typeof extCtx.ui?.confirm !== "function") {
36542
+ throw new BridgeError("Permission denied: command approval requires an interactive UI.", "permission_denied");
36404
36543
  }
36405
36544
  for (const ask of asks) {
36406
- const approved = await extCtx.ui.confirm("Run command unsandboxed on host?", piEscalationAskText(ask), { signal: extCtx.signal });
36545
+ if (ask.kind === "escalation") {
36546
+ const approved2 = await extCtx.ui.confirm("Run command unsandboxed on host?", piEscalationAskText(ask), { signal: extCtx.signal });
36547
+ if (!approved2) {
36548
+ throw new BridgeError("Permission denied: unsandboxed host execution was denied.", "permission_denied");
36549
+ }
36550
+ if (ask.grant_id && !granted.includes(ask.grant_id))
36551
+ granted.push(ask.grant_id);
36552
+ continue;
36553
+ }
36554
+ if (bridgeCommand !== "powershell") {
36555
+ throw new Error("Permission ask reached Pi adapter without a host escalation grant — this is a bug.");
36556
+ }
36557
+ const approved = await extCtx.ui.confirm("Allow PowerShell command?", piPowerShellAskText(String(params.command ?? "")), { signal: extCtx.signal });
36407
36558
  if (!approved) {
36408
- throw new BridgeError("Permission denied: unsandboxed host execution was denied.", "permission_denied");
36559
+ throw new BridgeError("Permission denied: PowerShell command was denied.", "permission_denied");
36560
+ }
36561
+ for (const grant of [...ask.always ?? [], ...ask.patterns ?? []]) {
36562
+ if (!granted.includes(grant))
36563
+ granted.push(grant);
36409
36564
  }
36410
- if (ask.grant_id && !granted.includes(ask.grant_id))
36411
- granted.push(ask.grant_id);
36412
36565
  }
36413
36566
  }
36414
36567
  }
@@ -36435,21 +36588,27 @@ function getBashSpawnHook(pi) {
36435
36588
  }
36436
36589
  return api2.hooks?.bashSpawn;
36437
36590
  }
36438
- function registerBashTool(pi, ctx, aftSearchRegistered = false) {
36439
- const spawnHook = getBashSpawnHook(pi);
36440
- const searchSteer = aftSearchRegistered ? "use `aft_search` (concepts, identifiers, regex, literals), `read`, `aft_outline`, or `aft_zoom` instead" : "use the `grep` tool, `read`, `aft_outline`, or `aft_zoom` instead";
36591
+ function registerBashTool(pi, ctx, aftSearchRegistered = false, registeredName = "bash", registerCompanions = true, shell = "bash") {
36592
+ const isPowerShell = shell === "powershell";
36593
+ const spawnHook = isPowerShell ? undefined : getBashSpawnHook(pi);
36594
+ const readToolName = registeredName === "bash" ? "read" : "aft_read";
36595
+ const grepToolName = registeredName === "bash" ? "grep" : "aft_grep";
36596
+ 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`;
36441
36597
  const bashCfg = resolveBashConfig(ctx.config);
36442
36598
  let hostFallbackActive = false;
36443
36599
  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`." : "";
36444
- 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).";
36600
+ 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)`.";
36601
+ 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).";
36445
36602
  pi.registerTool({
36446
- name: "bash",
36447
- label: "bash",
36448
- description: `Execute shell commands.${compressionSentence}${tasksSentence}
36603
+ name: registeredName,
36604
+ label: registeredName,
36605
+ description: isPowerShell ? `Execute PowerShell commands through AFT.${compressionSentence}${tasksSentence}
36606
+
36607
+ 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}
36449
36608
 
36450
36609
  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}.`,
36451
- 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)",
36452
- promptGuidelines: [
36610
+ 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)",
36611
+ promptGuidelines: isPowerShell ? ["Use PowerShell syntax. Every command requires explicit approval."] : [
36453
36612
  `DO NOT use bash for code search or exploration — ${searchSteer}.`,
36454
36613
  "Set compressed: false when you need ANSI color codes in the output.",
36455
36614
  "Piped commands run verbatim and show the pipeline's output; run test/build tools without pipes when you need AFT's summary."
@@ -36508,7 +36667,8 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
36508
36667
  foreground_orchestrate: true,
36509
36668
  block_to_completion: blockToCompletion,
36510
36669
  wait: requestedWait,
36511
- sandbox: params.sandbox
36670
+ sandbox: params.sandbox,
36671
+ ...isPowerShell ? { shell: "powershell" } : {}
36512
36672
  }, extCtx, {
36513
36673
  transportTimeoutMs: orchestratedTransportTimeoutMs(blockToCompletion, requestedWait, effectiveTimeout, foregroundWaitMs),
36514
36674
  onProgress: ({ text }) => {
@@ -36516,9 +36676,9 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
36516
36676
  const displayText = truncateToVisualLines(streamed, 100);
36517
36677
  onUpdate?.(bashResult(displayText, { streaming: true }));
36518
36678
  }
36519
- });
36679
+ }, isPowerShell ? "powershell" : "bash");
36520
36680
  } catch (error53) {
36521
- if (!bashCfg2.host_fallback || !isBashTransportDeadError(error53))
36681
+ if (isPowerShell || !bashCfg2.host_fallback || !isBashTransportDeadError(error53))
36522
36682
  throw error53;
36523
36683
  if (rawRequestedBackground) {
36524
36684
  throw new Error(`${BASH_HOST_FALLBACK_REFUSAL}; background:true is unsupported.`);
@@ -36565,7 +36725,7 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
36565
36725
  task_id: taskId
36566
36726
  };
36567
36727
  const output = response.output ?? "";
36568
- return bashResult(usedHostFallback ? output : withBashHints(output, bridgeCommand, aftSearchRegistered, extCtx.cwd), details);
36728
+ return bashResult(usedHostFallback || isPowerShell ? output : withBashHints(output, bridgeCommand, aftSearchRegistered, extCtx.cwd), details);
36569
36729
  },
36570
36730
  renderCall(args, theme, context) {
36571
36731
  return renderBashCall(args?.command, args?.description, theme, context);
@@ -36574,12 +36734,16 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
36574
36734
  return renderBashResult(result, theme, context, options);
36575
36735
  }
36576
36736
  });
36577
- if (bashCfg.background) {
36578
- pi.registerTool(createBashStatusTool(ctx));
36579
- pi.registerTool(createBashWatchTool(ctx));
36580
- pi.registerTool(createBashWriteTool(ctx));
36581
- pi.registerTool(createBashKillTool(ctx));
36582
- }
36737
+ if (registerCompanions)
36738
+ registerBashCompanionTools(pi, ctx);
36739
+ }
36740
+ function registerBashCompanionTools(pi, ctx) {
36741
+ if (!resolveBashConfig(ctx.config).background)
36742
+ return;
36743
+ pi.registerTool(createBashStatusTool(ctx));
36744
+ pi.registerTool(createBashWatchTool(ctx));
36745
+ pi.registerTool(createBashWriteTool(ctx));
36746
+ pi.registerTool(createBashKillTool(ctx));
36583
36747
  }
36584
36748
  function withBashHints(output, command, aftSearchRegistered, projectRoot) {
36585
36749
  return maybeAppendGrepSearchHint(maybeAppendConflictsHint(output), command, aftSearchRegistered, projectRoot);
@@ -37574,25 +37738,39 @@ function formatPhase(entry) {
37574
37738
  return details.length > 0 ? `${entry.id} (${details.join("; ")})` : entry.id;
37575
37739
  }
37576
37740
  function renderInspectTerminal(terminal, serverText) {
37577
- const lines = [terminal.kind];
37578
37741
  if (terminal.kind === "FRESH") {
37579
- lines.push(`wait-stamp: ${terminal.waitStampText ?? "not supplied"}`);
37742
+ const lines = [
37743
+ terminal.kind,
37744
+ `wait-stamp: ${terminal.waitStampText ?? "not supplied"}`
37745
+ ];
37746
+ lines.push(terminal.phases.length > 0 ? `completed phases:
37747
+ ${terminal.phases.map((phase) => `- ${formatPhase(phase)}`).join(`
37748
+ `)}` : "completed phases: none");
37749
+ if (serverText?.trim())
37750
+ lines.push(serverText);
37751
+ return lines.join(`
37752
+ `);
37580
37753
  }
37581
37754
  if (terminal.kind === "PHASE-FAILED") {
37582
- if (terminal.failedPhase)
37583
- lines.push(`failed phase: ${formatPhase(terminal.failedPhase)}`);
37584
- lines.push(`failure reason: ${terminal.failureReason ?? "not supplied"}`);
37585
- if (terminal.failureDetail)
37586
- lines.push(`failure detail: ${terminal.failureDetail}`);
37755
+ const detail = compactTerminalField(terminal.failureDetail, "not supplied");
37756
+ const reason = compactTerminalField(terminal.failureReason, "not supplied");
37757
+ return [
37758
+ `inspect could not complete: ${detail} (${reason}).`,
37759
+ `Completed phases: ${terminal.phases.length}. Retry, or narrow with sections=...`
37760
+ ].join(`
37761
+ `);
37587
37762
  }
37588
- lines.push(terminal.phases.length > 0 ? `completed phases:
37589
- ${terminal.phases.map((phase) => `- ${formatPhase(phase)}`).join(`
37590
- `)}` : "completed phases: none");
37591
- if (serverText?.trim())
37592
- lines.push(serverText);
37593
- return lines.join(`
37763
+ const phaseLabel = terminal.phases.length === 1 ? "phase" : "phases";
37764
+ return [
37765
+ `inspect was interrupted before it could complete (after ${terminal.phases.length} completed ${phaseLabel}); no fresh snapshot was produced.`,
37766
+ "Retry is safe; retry inspect, or narrow with sections=..."
37767
+ ].join(`
37594
37768
  `);
37595
37769
  }
37770
+ function compactTerminalField(value, fallback) {
37771
+ const compact = value?.trim().replace(/\s+/g, " ");
37772
+ return compact || fallback;
37773
+ }
37596
37774
  function diagnosticsSummaryPart(summary) {
37597
37775
  const section = asRecord3(summary?.diagnostics);
37598
37776
  if (!section)
@@ -37714,9 +37892,19 @@ function renderInspectCall(args, theme, context) {
37714
37892
  function renderInspectResult(result, theme, context, options = { expanded: true }) {
37715
37893
  const payload = extractStructuredPayload(result);
37716
37894
  const terminal = parseInspectTerminal(payload);
37895
+ if (terminal) {
37896
+ const sections2 = [renderInspectTerminal(terminal, asString2(asRecord3(payload)?.text))];
37897
+ return collapsibleResult({
37898
+ summary: sections2[0]?.split(`
37899
+ `)[0] ?? "inspect",
37900
+ full: renderSections(sections2, context),
37901
+ expanded: options.expanded,
37902
+ context
37903
+ });
37904
+ }
37717
37905
  if (context.isError)
37718
37906
  return renderErrorResult(result, "inspect failed", theme, context);
37719
- const sections = terminal ? [renderInspectTerminal(terminal, asString2(asRecord3(payload)?.text))] : buildInspectSections(payload, theme);
37907
+ const sections = buildInspectSections(payload, theme);
37720
37908
  return collapsibleResult({
37721
37909
  summary: sections[0] ?? "inspect completed",
37722
37910
  full: renderSections(sections, context),
@@ -38381,7 +38569,7 @@ var SafetyParams = Type12.Object({
38381
38569
  })),
38382
38570
  name: Type12.Optional(Type12.String({ description: "Checkpoint name (required for checkpoint, restore)" })),
38383
38571
  files: Type12.Optional(Type12.Array(Type12.String(), {
38384
- description: "Specific files for checkpoint (optional, defaults to all tracked)"
38572
+ description: "Specific files for checkpoint (optional, defaults to all backup-tracked files; explicit files may be untracked or gitignored)"
38385
38573
  }))
38386
38574
  });
38387
38575
  function buildSafetySections(args, payload, theme) {
@@ -38426,14 +38614,16 @@ function buildSafetySections(args, payload, theme) {
38426
38614
  `${theme.fg("muted", "files")} ${asNumber2(response.file_count) ?? 0}`,
38427
38615
  skipped.length > 0 ? `${theme.fg("warning", "skipped")}
38428
38616
  ${skipped.map((entry) => ` ↳ ${shortenPath2(asString2(entry.file) ?? "(file)")}: ${asString2(entry.error) ?? "unknown error"}`).join(`
38429
- `)}` : theme.fg("muted", "No skipped files.")
38430
- ];
38617
+ `)}` : theme.fg("muted", "No skipped files."),
38618
+ asString2(response.durability)
38619
+ ].filter((section) => Boolean(section));
38431
38620
  }
38432
38621
  if (args.op === "restore") {
38433
38622
  return [
38434
38623
  `${theme.fg("success", "checkpoint restored")} ${theme.fg("accent", asString2(response.name) ?? args.name ?? "(checkpoint)")}`,
38435
- `${theme.fg("muted", "files")} ${asNumber2(response.file_count) ?? 0}`
38436
- ];
38624
+ `${theme.fg("muted", "files")} ${asNumber2(response.file_count) ?? 0}`,
38625
+ asString2(response.durability)
38626
+ ].filter((section) => Boolean(section));
38437
38627
  }
38438
38628
  const checkpoints = asRecords2(response.checkpoints);
38439
38629
  const sections = [
@@ -38441,6 +38631,9 @@ ${skipped.map((entry) => ` ↳ ${shortenPath2(asString2(entry.file) ?? "(file)"
38441
38631
  ];
38442
38632
  if (checkpoints.length === 0) {
38443
38633
  sections.push(theme.fg("muted", "No checkpoints saved."));
38634
+ const durability2 = asString2(response.durability);
38635
+ if (durability2)
38636
+ sections.push(theme.fg("muted", durability2));
38444
38637
  return sections;
38445
38638
  }
38446
38639
  sections.push(checkpoints.map((checkpoint, index) => {
@@ -38450,6 +38643,9 @@ ${skipped.map((entry) => ` ↳ ${shortenPath2(asString2(entry.file) ?? "(file)"
38450
38643
  return `${index + 1}. ${name} ${theme.fg("muted", `${count} file${count === 1 ? "" : "s"} · ${created}`)}`;
38451
38644
  }).join(`
38452
38645
  `));
38646
+ const durability = asString2(response.durability);
38647
+ if (durability)
38648
+ sections.push(theme.fg("muted", durability));
38453
38649
  return sections;
38454
38650
  }
38455
38651
  function renderSafetyCall(args, theme, context) {
@@ -38699,15 +38895,38 @@ function registerSemanticTool(pi, ctx) {
38699
38895
 
38700
38896
  // src/tool-registration.ts
38701
38897
  var ALL_ONLY_TOOLS = new Set(["aft_callgraph", "aft_delete", "aft_move", "aft_refactor"]);
38702
- function resolvePiToolSurface(config2) {
38898
+ function resolvePiPowerShellFallback(config2) {
38899
+ return resolveBashConfig(config2).powershell_tool;
38900
+ }
38901
+ function piPowerShellEnabledFromHost(pi) {
38902
+ const api2 = pi;
38903
+ if (typeof api2.getActiveTools !== "function" || typeof api2.getAllTools !== "function") {
38904
+ return;
38905
+ }
38906
+ try {
38907
+ const tool = api2.getAllTools().find((candidate) => candidate.name === "powershell");
38908
+ const source = tool?.sourceInfo?.source ?? tool?.source;
38909
+ if (!tool || source === undefined)
38910
+ return;
38911
+ return source === "builtin" && api2.getActiveTools().some((active) => (typeof active === "string" ? active : active.name) === "powershell");
38912
+ } catch {
38913
+ return;
38914
+ }
38915
+ }
38916
+ function resolvePiToolSurface(config2, pi) {
38703
38917
  const surface = config2.tool_surface ?? "recommended";
38704
38918
  const disabled = new Set(config2.disabled_tools ?? []);
38919
+ const hoistBuiltinTools = config2.hoist_builtin_tools !== false;
38705
38920
  const ok = (name) => !disabled.has(name);
38921
+ const builtinToolEnabled = (bareName) => ok(hoistBuiltinTools ? bareName : `aft_${bareName}`);
38706
38922
  const allOnly = (name) => ALL_ONLY_TOOLS.has(name) && ok(name);
38707
38923
  const restrictToProjectRoot = config2.restrict_to_project_root ?? false;
38924
+ const powershellEnabled = (pi ? piPowerShellEnabledFromHost(pi) : undefined) ?? resolvePiPowerShellFallback(config2);
38708
38925
  if (surface === "minimal") {
38709
38926
  return {
38710
- hoistBash: ok("bash"),
38927
+ hoistBuiltinTools,
38928
+ hoistBash: builtinToolEnabled("bash"),
38929
+ hoistPowershell: powershellEnabled && builtinToolEnabled("powershell"),
38711
38930
  hoistRead: false,
38712
38931
  hoistWrite: false,
38713
38932
  hoistEdit: false,
@@ -38729,11 +38948,13 @@ function resolvePiToolSurface(config2) {
38729
38948
  };
38730
38949
  }
38731
38950
  const base = {
38732
- hoistBash: ok("bash"),
38733
- hoistRead: ok("read"),
38734
- hoistWrite: ok("write"),
38735
- hoistEdit: ok("edit"),
38736
- hoistGrep: ok("grep") && config2.search_index === true,
38951
+ hoistBuiltinTools,
38952
+ hoistBash: builtinToolEnabled("bash"),
38953
+ hoistPowershell: powershellEnabled && builtinToolEnabled("powershell"),
38954
+ hoistRead: builtinToolEnabled("read"),
38955
+ hoistWrite: builtinToolEnabled("write"),
38956
+ hoistEdit: builtinToolEnabled("edit"),
38957
+ hoistGrep: builtinToolEnabled("grep") && config2.search_index === true,
38737
38958
  restrictToProjectRoot,
38738
38959
  outline: ok("aft_outline"),
38739
38960
  zoom: ok("aft_zoom"),
@@ -38761,13 +38982,20 @@ function resolvePiToolSurface(config2) {
38761
38982
  return base;
38762
38983
  }
38763
38984
  function registerPiToolSurface(pi, ctx, surface) {
38764
- if (surface.hoistBash && resolveBashConfig(ctx.config).enabled) {
38765
- registerBashTool(pi, ctx, surface.semantic);
38985
+ const bashCfg = resolveBashConfig(ctx.config);
38986
+ const bashRegistered = surface.hoistBash && bashCfg.enabled;
38987
+ const powershellRegistered = surface.hoistPowershell && bashCfg.enabled;
38988
+ if (bashRegistered) {
38989
+ registerBashTool(pi, ctx, surface.semantic, surface.hoistBuiltinTools ? "bash" : "aft_bash", false);
38990
+ }
38991
+ if (powershellRegistered) {
38992
+ registerBashTool(pi, ctx, surface.semantic, surface.hoistBuiltinTools ? "powershell" : "aft_powershell", false, "powershell");
38766
38993
  }
38994
+ if (bashRegistered || powershellRegistered)
38995
+ registerBashCompanionTools(pi, ctx);
38767
38996
  registerHoistedTools(pi, ctx, surface);
38768
- if (surface.outline || surface.zoom) {
38997
+ if (surface.outline || surface.zoom)
38769
38998
  registerReadingTools(pi, ctx, surface);
38770
- }
38771
38999
  if (surface.semantic)
38772
39000
  registerSemanticTool(pi, ctx);
38773
39001
  if (surface.inspect)
@@ -38873,6 +39101,9 @@ function buildHintsFromConfig(config2, absentTools, hoistBuiltins) {
38873
39101
  });
38874
39102
  }
38875
39103
  function registerWorkflowHints(pi, config2, surface) {
39104
+ const hoistBuiltins = surface.hoistBuiltinTools;
39105
+ const grepName = hoistBuiltins ? "grep" : "aft_grep";
39106
+ const bashName = hoistBuiltins ? "bash" : "aft_bash";
38876
39107
  const absent = new Set;
38877
39108
  if (!surface.outline)
38878
39109
  absent.add("aft_outline");
@@ -38885,12 +39116,12 @@ function registerWorkflowHints(pi, config2, surface) {
38885
39116
  if (!surface.inspect)
38886
39117
  absent.add("aft_inspect");
38887
39118
  if (!surface.hoistGrep)
38888
- absent.add("grep");
39119
+ absent.add(grepName);
38889
39120
  if (!surface.hoistBash) {
38890
- absent.add("bash");
39121
+ absent.add(bashName);
38891
39122
  absent.add("bash_status");
38892
39123
  }
38893
- const hintsBlock = buildHintsFromConfig(config2, absent, true);
39124
+ const hintsBlock = buildHintsFromConfig(config2, absent, hoistBuiltins);
38894
39125
  if (!hintsBlock)
38895
39126
  return;
38896
39127
  log2(`Workflow hints injected (${hintsBlock.length} chars)`);
@@ -38947,7 +39178,7 @@ var PLUGIN_VERSION = (() => {
38947
39178
  return "0.0.0";
38948
39179
  }
38949
39180
  })();
38950
- var ANNOUNCEMENT_VERSION = "0.52.2";
39181
+ var ANNOUNCEMENT_VERSION = "0.54.0";
38951
39182
  var ANNOUNCEMENT_FEATURES = [
38952
39183
  "Callgraph stays consistent under load: edits arriving mid-rebuild can no longer be silently lost from navigation results.",
38953
39184
  "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).",
@@ -39266,7 +39497,7 @@ ${lines}
39266
39497
  });
39267
39498
  }
39268
39499
  pool.setConfigureOverride("harness", "pi");
39269
- const surface = resolvePiToolSurface(config2);
39500
+ const surface = resolvePiToolSurface(config2, pi);
39270
39501
  const hashlineEditRegistered = config2.edit_mode === "hashline" && surface.hoistEdit;
39271
39502
  pool.setConfigureOverride("edit_slot_survives", hashlineEditRegistered);
39272
39503
  pool.setConfigureOverride("aft_search_registered", surface.semantic);
@@ -39314,6 +39545,17 @@ ${lines}
39314
39545
  sendFeatureAnnouncement(ANNOUNCEMENT_VERSION, ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER, storageDir);
39315
39546
  }
39316
39547
  registerPiToolSurface(pi, ctx, surface);
39548
+ let powershellRegistered = surface.hoistPowershell && resolveBashConfig(config2).enabled;
39549
+ pi.on("session_start", () => {
39550
+ if (powershellRegistered)
39551
+ return;
39552
+ const liveSurface = resolvePiToolSurface(config2, pi);
39553
+ if (!liveSurface.hoistPowershell || !resolveBashConfig(config2).enabled)
39554
+ return;
39555
+ registerBashTool(pi, ctx, liveSurface.semantic, liveSurface.hoistBuiltinTools ? "powershell" : "aft_powershell", false, "powershell");
39556
+ registerBashCompanionTools(pi, ctx);
39557
+ powershellRegistered = true;
39558
+ });
39317
39559
  registerWorkflowHints(pi, config2, surface);
39318
39560
  registerStatusCommand(pi, ctx);
39319
39561
  pi.on("tool_result", async (event, extCtx) => {
@@ -39345,10 +39587,18 @@ ${lines}
39345
39587
  });
39346
39588
  }
39347
39589
  });
39348
- pi.on("input", (_event, extCtx) => {
39590
+ pi.on("input", (event, extCtx) => {
39349
39591
  const sessionId = resolveSessionId(extCtx);
39350
39592
  signalSyncWatchAbort(sessionId);
39351
- signalBashWaitDetachForProject(pool, extCtx.cwd, sessionId);
39593
+ const originalText = event.text;
39594
+ const transformedText = stripUserMessageDetachKeyword(originalText);
39595
+ const shouldDetach = shouldDetachBashWaitOnUserMessage(config2, originalText);
39596
+ if (shouldDetach) {
39597
+ signalBashWaitDetachForProject(pool, extCtx.cwd, sessionId);
39598
+ }
39599
+ if (transformedText !== originalText) {
39600
+ return { action: "transform", text: transformedText };
39601
+ }
39352
39602
  });
39353
39603
  const unregisterShutdownCleanup = registerShutdownCleanup(async () => {
39354
39604
  try {