@cortexkit/aft-pi 0.52.1 → 0.53.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.
Files changed (39) hide show
  1. package/README.md +6 -2
  2. package/dist/config.d.ts +46 -20
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/index.js +627 -402
  5. package/dist/shared/status.d.ts +3 -0
  6. package/dist/shared/status.d.ts.map +1 -1
  7. package/dist/tool-registration.d.ts +2 -0
  8. package/dist/tool-registration.d.ts.map +1 -1
  9. package/dist/tools/_shared.d.ts.map +1 -1
  10. package/dist/tools/ast.d.ts +2 -2
  11. package/dist/tools/ast.d.ts.map +1 -1
  12. package/dist/tools/bash.d.ts +10 -1
  13. package/dist/tools/bash.d.ts.map +1 -1
  14. package/dist/tools/conflicts.d.ts +3 -3
  15. package/dist/tools/conflicts.d.ts.map +1 -1
  16. package/dist/tools/diff-format.d.ts.map +1 -1
  17. package/dist/tools/fs.d.ts +2 -2
  18. package/dist/tools/fs.d.ts.map +1 -1
  19. package/dist/tools/hoisted.d.ts +7 -3
  20. package/dist/tools/hoisted.d.ts.map +1 -1
  21. package/dist/tools/imports.d.ts +2 -2
  22. package/dist/tools/imports.d.ts.map +1 -1
  23. package/dist/tools/inspect.d.ts +2 -2
  24. package/dist/tools/inspect.d.ts.map +1 -1
  25. package/dist/tools/navigate.d.ts +2 -2
  26. package/dist/tools/navigate.d.ts.map +1 -1
  27. package/dist/tools/reading.d.ts +3 -3
  28. package/dist/tools/reading.d.ts.map +1 -1
  29. package/dist/tools/refactor.d.ts +2 -2
  30. package/dist/tools/refactor.d.ts.map +1 -1
  31. package/dist/tools/render-helpers.d.ts +25 -1
  32. package/dist/tools/render-helpers.d.ts.map +1 -1
  33. package/dist/tools/safety.d.ts +2 -2
  34. package/dist/tools/safety.d.ts.map +1 -1
  35. package/dist/tools/semantic.d.ts +2 -2
  36. package/dist/tools/semantic.d.ts.map +1 -1
  37. package/dist/workflow-hints.d.ts +1 -0
  38. package/dist/workflow-hints.d.ts.map +1 -1
  39. package/package.json +8 -8
package/dist/index.js CHANGED
@@ -7948,7 +7948,7 @@ function error(message, meta) {
7948
7948
  }
7949
7949
  // ../aft-bridge/dist/bash-format.js
7950
7950
  function isTerminalStatus(status) {
7951
- return status === "completed" || status === "failed" || status === "killed" || status === "timed_out";
7951
+ return status === "completed" || status === "failed" || status === "killed" || status === "timed_out" || status === "fate_unknown";
7952
7952
  }
7953
7953
  function sleep(ms) {
7954
7954
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -8491,6 +8491,7 @@ var TERMINAL_BASH_STATUSES = new Set([
8491
8491
  "failed",
8492
8492
  "killed",
8493
8493
  "timed_out",
8494
+ "fate_unknown",
8494
8495
  "cancelled",
8495
8496
  "timeout"
8496
8497
  ]);
@@ -9921,7 +9922,7 @@ function isEmptyParam(value) {
9921
9922
  return false;
9922
9923
  }
9923
9924
  // ../aft-bridge/dist/config-keys.js
9924
- var OPENCODE_ONLY_KEYS = ["hoist_builtin_tools", "auto_update"];
9925
+ var OPENCODE_ONLY_KEYS = ["auto_update"];
9925
9926
  function stripHarnessSpecificConfigKeys(value, keys) {
9926
9927
  if (!value || typeof value !== "object" || Array.isArray(value))
9927
9928
  return value;
@@ -10359,34 +10360,54 @@ async function fetchLatestTag() {
10359
10360
  }
10360
10361
  // ../aft-bridge/dist/durable-log.js
10361
10362
  import { appendFile, mkdir, rename, rm, stat } from "node:fs/promises";
10363
+ import { dirname } from "node:path";
10364
+
10365
+ // ../aft-bridge/dist/storage-paths.js
10362
10366
  import { homedir as homedir5 } from "node:os";
10363
- import { dirname, join as join5 } from "node:path";
10364
- var DEFAULT_LOG_BYTES = 32 * 1024 * 1024;
10365
- var DEFAULT_LOG_GENERATIONS = 1;
10367
+ import { join as join5, resolve as resolve3 } from "node:path";
10366
10368
  function homeDir2() {
10367
10369
  if (process.platform === "win32")
10368
10370
  return process.env.USERPROFILE || process.env.HOME || homedir5();
10369
10371
  return process.env.HOME || homedir5();
10370
10372
  }
10371
10373
  function dataHome() {
10372
- if (process.env.XDG_DATA_HOME)
10373
- return process.env.XDG_DATA_HOME;
10374
+ const xdg = process.env.XDG_DATA_HOME;
10375
+ if (xdg)
10376
+ return xdg;
10374
10377
  if (process.platform === "win32") {
10375
10378
  return process.env.LOCALAPPDATA || process.env.APPDATA || join5(homeDir2(), "AppData", "Local");
10376
10379
  }
10377
10380
  return join5(homeDir2(), ".local", "share");
10378
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
+ }
10379
10397
  function resolveAftStorageRoot(configuredRoot) {
10398
+ if (process.env.AFT_STORAGE_DIR)
10399
+ return resolveCortexKitStorageRoot();
10380
10400
  if (configuredRoot)
10381
10401
  return configuredRoot;
10382
- if (process.env.AFT_CACHE_DIR)
10383
- return join5(process.env.AFT_CACHE_DIR, "aft");
10384
- return join5(dataHome(), "cortexkit", "aft");
10402
+ return resolveCortexKitStorageRoot();
10385
10403
  }
10386
10404
  function resolveAftLogPath(filename, configuredRoot) {
10387
10405
  return join5(resolveAftStorageRoot(configuredRoot), "logs", filename);
10388
10406
  }
10389
10407
 
10408
+ // ../aft-bridge/dist/durable-log.js
10409
+ var DEFAULT_LOG_BYTES = 32 * 1024 * 1024;
10410
+ var DEFAULT_LOG_GENERATIONS = 1;
10390
10411
  class RotatingLogSink {
10391
10412
  path;
10392
10413
  maxBytes;
@@ -10758,7 +10779,7 @@ class SubcSocket {
10758
10779
  return this.sock.localPort ?? null;
10759
10780
  }
10760
10781
  static connect(host, port, deadlineMs) {
10761
- return new Promise((resolve3, reject) => {
10782
+ return new Promise((resolve4, reject) => {
10762
10783
  const sock = net.connect({ host, port });
10763
10784
  sock.setNoDelay(true);
10764
10785
  const timer = setTimeout(() => {
@@ -10767,7 +10788,7 @@ class SubcSocket {
10767
10788
  }, Math.max(0, deadlineMs - Date.now()));
10768
10789
  sock.once("connect", () => {
10769
10790
  clearTimeout(timer);
10770
- resolve3(new SubcSocket(sock));
10791
+ resolve4(new SubcSocket(sock));
10771
10792
  });
10772
10793
  sock.once("error", (err) => {
10773
10794
  clearTimeout(timer);
@@ -10799,7 +10820,7 @@ class SubcSocket {
10799
10820
  }
10800
10821
  if (n === 0)
10801
10822
  return Promise.resolve(new Uint8Array(0));
10802
- return new Promise((resolve3, reject) => {
10823
+ return new Promise((resolve4, reject) => {
10803
10824
  let timer = null;
10804
10825
  if (Number.isFinite(deadlineMs)) {
10805
10826
  const remaining = deadlineMs - Date.now();
@@ -10812,7 +10833,7 @@ class SubcSocket {
10812
10833
  reject(new SocketTimeoutError(`timed out waiting for ${n} bytes`));
10813
10834
  }, remaining);
10814
10835
  }
10815
- this.waiter = { need: n, resolve: resolve3, reject, timer };
10836
+ this.waiter = { need: n, resolve: resolve4, reject, timer };
10816
10837
  this.tryServe();
10817
10838
  });
10818
10839
  }
@@ -10848,7 +10869,7 @@ class SubcSocket {
10848
10869
  let queued = false;
10849
10870
  let settled = false;
10850
10871
  let timer = null;
10851
- const completed = new Promise((resolve3, reject) => {
10872
+ const completed = new Promise((resolve4, reject) => {
10852
10873
  const settle = (run) => {
10853
10874
  if (settled)
10854
10875
  return;
@@ -10872,7 +10893,7 @@ class SubcSocket {
10872
10893
  if (err) {
10873
10894
  reject(new SocketWriteQueuedError("subc socket reported a write error after bytes were handed to the socket", err instanceof Error ? err : new Error(String(err))));
10874
10895
  } else {
10875
- resolve3();
10896
+ resolve4();
10876
10897
  }
10877
10898
  });
10878
10899
  });
@@ -11295,15 +11316,15 @@ class SubcClient {
11295
11316
  const key = pendingKey(handle, corr);
11296
11317
  let subscriptionPending = null;
11297
11318
  let resolveClosed = null;
11298
- const closed = new Promise((resolve3, reject) => {
11319
+ const closed = new Promise((resolve4, reject) => {
11299
11320
  if (this.closedErr) {
11300
11321
  reject(this.closedErr);
11301
11322
  return;
11302
11323
  }
11303
- resolveClosed = resolve3;
11324
+ resolveClosed = resolve4;
11304
11325
  subscriptionPending = {
11305
11326
  handle,
11306
- resolve: () => resolve3(),
11327
+ resolve: () => resolve4(),
11307
11328
  reject,
11308
11329
  onProgress: onEvent,
11309
11330
  timer: null,
@@ -11392,11 +11413,11 @@ class SubcClient {
11392
11413
  const waiters = [];
11393
11414
  for (const pending of this.pending.values()) {
11394
11415
  if (pending.handle === handle && !pending.subscription) {
11395
- waiters.push(new Promise((resolve3) => {
11416
+ waiters.push(new Promise((resolve4) => {
11396
11417
  const previous = pending.onSettle;
11397
11418
  pending.onSettle = () => {
11398
11419
  previous?.();
11399
- resolve3();
11420
+ resolve4();
11400
11421
  };
11401
11422
  }));
11402
11423
  }
@@ -11452,11 +11473,11 @@ class SubcClient {
11452
11473
  const channel = handle?.channel ?? 0;
11453
11474
  const epoch = handle?.epoch ?? 0;
11454
11475
  const frame = buildFrame(FrameType.Request, buildFlags(false, priority, false, admission), channel, epoch, corr, body);
11455
- return new Promise((resolve3, reject) => {
11476
+ return new Promise((resolve4, reject) => {
11456
11477
  const ms = timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
11457
11478
  const pending = {
11458
11479
  handle,
11459
- resolve: resolve3,
11480
+ resolve: resolve4,
11460
11481
  reject,
11461
11482
  onProgress,
11462
11483
  timer: null,
@@ -11530,11 +11551,11 @@ class SubcClient {
11530
11551
  }
11531
11552
  return this.outcomeUnknownCallError("connection dropped before the managed call returned a response", error2);
11532
11553
  };
11533
- return new Promise((resolve3, reject) => {
11554
+ return new Promise((resolve4, reject) => {
11534
11555
  const ms = timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
11535
11556
  const pending = {
11536
11557
  handle,
11537
- resolve: resolve3,
11558
+ resolve: resolve4,
11538
11559
  reject,
11539
11560
  onProgress,
11540
11561
  timer: null,
@@ -11983,7 +12004,7 @@ function normalizeConnectOptions(opts) {
11983
12004
  identity: opts.identity,
11984
12005
  targetKind: opts.targetKind ?? DEFAULT_MANAGED_TARGET_KIND,
11985
12006
  reconnectBackoff: opts.reconnectBackoff ?? DEFAULT_RECONNECT_BACKOFF,
11986
- sleep: opts.sleep ?? ((ms) => new Promise((resolve3) => setTimeout(resolve3, ms))),
12007
+ sleep: opts.sleep ?? ((ms) => new Promise((resolve4) => setTimeout(resolve4, ms))),
11987
12008
  timeoutArbitrationGraceMs: opts.timeoutArbitrationGraceMs ?? TIMEOUT_ARBITRATION_GRACE_MS,
11988
12009
  livenessProbeWindowMs: opts.livenessProbeWindowMs ?? LIVENESS_PROBE_WINDOW_MS,
11989
12010
  onControlPush: opts.onControlPush
@@ -12442,14 +12463,14 @@ function getLifecycleRegistry() {
12442
12463
 
12443
12464
  // ../aft-bridge/dist/project-identity.js
12444
12465
  import { realpathSync } from "node:fs";
12445
- import { resolve as resolve3 } from "node:path";
12466
+ import { resolve as resolve4 } from "node:path";
12446
12467
  function canonicalizeProjectRoot(dir) {
12447
12468
  const trimmed = dir.replace(/[/\\]+$/, "");
12448
12469
  let canonical;
12449
12470
  try {
12450
12471
  canonical = realpathSync(trimmed);
12451
12472
  } catch {
12452
- canonical = resolve3(trimmed);
12473
+ canonical = resolve4(trimmed);
12453
12474
  }
12454
12475
  return normalizeWindowsRoot(canonical);
12455
12476
  }
@@ -12945,8 +12966,8 @@ class SubcTransportPool {
12945
12966
  this.connectFn = options.connect ?? ((opts) => SubcClient.connect(opts));
12946
12967
  this.onBgEventsNudge = options.onBgEventsNudge;
12947
12968
  this.onBgEventsNudgeRef = options.onBgEventsNudgeRef;
12948
- this.bgBackoffSleep = options.bgBackoffSleep ?? ((ms) => new Promise((resolve4) => setTimeout(resolve4, ms)));
12949
- this.routeRetrySleep = options.routeRetrySleep ?? ((ms) => new Promise((resolve4) => setTimeout(resolve4, ms)));
12969
+ this.bgBackoffSleep = options.bgBackoffSleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
12970
+ this.routeRetrySleep = options.routeRetrySleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
12950
12971
  this.bgDispatchProbeIntervalMs = options.bgDispatchProbeIntervalMs ?? BG_DISPATCH_PROBE_INTERVAL_MS;
12951
12972
  const lifecycle = options.lifecycle;
12952
12973
  const demandCheck = options.lifecycleDemandCheck ?? options.demandCheck ?? lifecycle?.demandCheck;
@@ -13862,12 +13883,12 @@ function stripJsoncSymbols(value) {
13862
13883
  import { spawnSync as spawnSync2 } from "node:child_process";
13863
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";
13864
13885
  import { homedir as homedir8, tmpdir } from "node:os";
13865
- import { basename, dirname as dirname3, join as join8, resolve as resolve5 } from "node:path";
13886
+ import { basename, dirname as dirname3, join as join8, resolve as resolve6 } from "node:path";
13866
13887
 
13867
13888
  // ../aft-bridge/dist/paths.js
13868
13889
  import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, renameSync as renameSync2, writeFileSync } from "node:fs";
13869
13890
  import { homedir as homedir6 } from "node:os";
13870
- import { dirname as dirname2, isAbsolute as isAbsolute2, join as join6, resolve as resolve4 } from "node:path";
13891
+ import { dirname as dirname2, isAbsolute as isAbsolute2, join as join6, resolve as resolve5 } from "node:path";
13871
13892
  function homeDir3() {
13872
13893
  if (process.platform === "win32")
13873
13894
  return process.env.USERPROFILE || process.env.HOME || homedir6();
@@ -13882,7 +13903,7 @@ function configHome() {
13882
13903
  function legacyOpenCodeConfigDir() {
13883
13904
  const envDir = process.env.OPENCODE_CONFIG_DIR?.trim();
13884
13905
  if (envDir)
13885
- return resolve4(envDir);
13906
+ return resolve5(envDir);
13886
13907
  return join6(configHome(), "opencode");
13887
13908
  }
13888
13909
  function legacyPiAgentDir() {
@@ -14247,9 +14268,6 @@ function resolveLegacyStorageRoot(harness) {
14247
14268
  return join8(homeDir4(), ".pi", "agent", "aft");
14248
14269
  return join8(dataHome2(), "opencode", "storage", "plugin", "aft");
14249
14270
  }
14250
- function resolveCortexKitStorageRoot() {
14251
- return join8(dataHome2(), "cortexkit", "aft");
14252
- }
14253
14271
  function stripJsoncForParse(input) {
14254
14272
  let out = "";
14255
14273
  let inString = false;
@@ -14519,8 +14537,8 @@ function visibleConfigMigrationWarning(scope, targetPath, paths, reason) {
14519
14537
  }
14520
14538
  function migrateAftConfigFile(opts) {
14521
14539
  const warnings = [];
14522
- const resolvedTarget = resolve5(opts.targetPath);
14523
- const existingSources = opts.legacySources.filter((source) => existsSync6(source.path) && resolve5(source.path) !== resolvedTarget);
14540
+ const resolvedTarget = resolve6(opts.targetPath);
14541
+ const existingSources = opts.legacySources.filter((source) => existsSync6(source.path) && resolve6(source.path) !== resolvedTarget);
14524
14542
  const info = opts.logger?.info ?? opts.logger?.log;
14525
14543
  if (existingSources.length === 0) {
14526
14544
  return { migrated: false, conflict: false, targetPath: opts.targetPath, warnings };
@@ -14766,7 +14784,7 @@ function npmSpawnEnv(resolved, baseEnv = process.env) {
14766
14784
  import { execFileSync } from "node:child_process";
14767
14785
  import { createHash as createHash3 } from "node:crypto";
14768
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";
14769
- import { basename as basename2, dirname as dirname5, isAbsolute as isAbsolute4, join as join10, relative as relative2, resolve as resolve6, win32 } from "node:path";
14787
+ import { basename as basename2, dirname as dirname5, isAbsolute as isAbsolute4, join as join10, relative as relative2, resolve as resolve7, win32 } from "node:path";
14770
14788
  import { Readable as Readable2 } from "node:stream";
14771
14789
  import { pipeline as pipeline2 } from "node:stream/promises";
14772
14790
  var ORT_VERSION = "1.24.4";
@@ -15071,7 +15089,7 @@ function findSystemOnnxRuntime(libName) {
15071
15089
  const normalizeCase = process.platform === "win32" || process.platform === "darwin";
15072
15090
  const seen = new Set;
15073
15091
  const uniquePaths = searchPaths.filter((p) => {
15074
- let key = resolve6(p).replace(/[/\\]+$/, "");
15092
+ let key = resolve7(p).replace(/[/\\]+$/, "");
15075
15093
  if (normalizeCase)
15076
15094
  key = key.toLowerCase();
15077
15095
  if (seen.has(key))
@@ -15155,7 +15173,7 @@ function validateExtractedTree(stagingRoot) {
15155
15173
  const lst = lstatSync(fullPath);
15156
15174
  if (lst.isSymbolicLink()) {
15157
15175
  const linkTarget = readlinkSync(fullPath);
15158
- const resolvedTarget = resolve6(dirname5(fullPath), linkTarget);
15176
+ const resolvedTarget = resolve7(dirname5(fullPath), linkTarget);
15159
15177
  const rel2 = relative2(realRoot, resolvedTarget);
15160
15178
  if (rel2.startsWith("..") || isAbsolute4(rel2) || win32.isAbsolute(rel2)) {
15161
15179
  throw new Error(`extracted symlink ${fullPath} points outside staging root: ${linkTarget}`);
@@ -15273,7 +15291,7 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
15273
15291
  unlinkSync4(dst);
15274
15292
  } catch {}
15275
15293
  const dstForContainment = join10(targetRoot, link.name);
15276
- const resolvedTarget = resolve6(dirname5(dstForContainment), link.target);
15294
+ const resolvedTarget = resolve7(dirname5(dstForContainment), link.target);
15277
15295
  if (!isPathInsideRoot(targetRoot, resolvedTarget)) {
15278
15296
  const message = `ONNX Runtime symlink ${link.name} points outside install dir: ${link.target}`;
15279
15297
  if (requiredLibs.has(link.name)) {
@@ -16340,7 +16358,7 @@ class RevivableTransportPool {
16340
16358
  const delay = Math.max(0, this.revivalRetryNotBefore - Date.now());
16341
16359
  if (delay > 0) {
16342
16360
  let scheduled;
16343
- scheduled = new Promise((resolve7) => setTimeout(resolve7, delay)).then(() => {
16361
+ scheduled = new Promise((resolve8) => setTimeout(resolve8, delay)).then(() => {
16344
16362
  if (this.revival === scheduled)
16345
16363
  this.revival = null;
16346
16364
  return this.ensureActivePool();
@@ -17443,7 +17461,7 @@ import {
17443
17461
  // package.json
17444
17462
  var package_default = {
17445
17463
  name: "@cortexkit/aft-pi",
17446
- version: "0.52.1",
17464
+ version: "0.53.0",
17447
17465
  type: "module",
17448
17466
  description: "Pi coding agent extension for Agent File Tools (AFT) — tree-sitter and LSP-powered code analysis",
17449
17467
  main: "dist/index.js",
@@ -17466,19 +17484,19 @@ var package_default = {
17466
17484
  "test:unit": "bun test src/__tests__ --path-ignore-patterns 'src/__tests__/e2e/**'"
17467
17485
  },
17468
17486
  dependencies: {
17469
- "@cortexkit/aft-bridge": "0.52.1",
17487
+ "@cortexkit/aft-bridge": "0.53.0",
17470
17488
  "comment-json": "^5.0.0",
17471
17489
  diff: "^8.0.4",
17472
17490
  typebox: "1.1.38",
17473
17491
  zod: "^4.4.3"
17474
17492
  },
17475
17493
  optionalDependencies: {
17476
- "@cortexkit/aft-darwin-arm64": "0.52.1",
17477
- "@cortexkit/aft-darwin-x64": "0.52.1",
17478
- "@cortexkit/aft-linux-arm64": "0.52.1",
17479
- "@cortexkit/aft-linux-x64": "0.52.1",
17480
- "@cortexkit/aft-win32-arm64": "0.52.1",
17481
- "@cortexkit/aft-win32-x64": "0.52.1"
17494
+ "@cortexkit/aft-darwin-arm64": "0.53.0",
17495
+ "@cortexkit/aft-darwin-x64": "0.53.0",
17496
+ "@cortexkit/aft-linux-arm64": "0.53.0",
17497
+ "@cortexkit/aft-linux-x64": "0.53.0",
17498
+ "@cortexkit/aft-win32-arm64": "0.53.0",
17499
+ "@cortexkit/aft-win32-x64": "0.53.0"
17482
17500
  },
17483
17501
  devDependencies: {
17484
17502
  "@earendil-works/pi-coding-agent": "^0.80.2",
@@ -17622,13 +17640,16 @@ function coerceAftStatus(response) {
17622
17640
  project_root: readNullableString(response.project_root),
17623
17641
  canonical_root: readNullableString(response.canonical_root),
17624
17642
  cache_role: readString(response.cache_role, "not_initialized"),
17643
+ degraded: readBoolean(response.degraded),
17644
+ degraded_reasons: Array.isArray(response.degraded_reasons) ? response.degraded_reasons.filter((r) => typeof r === "string") : [],
17625
17645
  message: typeof response.message === "string" ? response.message : undefined,
17626
17646
  features: {
17627
17647
  format_on_edit: readBoolean(features.format_on_edit),
17628
17648
  validate_on_edit: readString(features.validate_on_edit, "off"),
17629
17649
  restrict_to_project_root: readBoolean(features.restrict_to_project_root),
17630
17650
  search_index: readBoolean(features.search_index ?? features.experimental_search_index),
17631
- semantic_search: readBoolean(features.semantic_search ?? features.experimental_semantic_search)
17651
+ semantic_search: readBoolean(features.semantic_search ?? features.experimental_semantic_search),
17652
+ callgraph_store: readBoolean(features.callgraph_store)
17632
17653
  },
17633
17654
  search_index: {
17634
17655
  status: readString(searchIndex.status, "unknown"),
@@ -17679,21 +17700,10 @@ function formatStatusDialogMessage(status) {
17679
17700
  `AFT version: ${status.version}`,
17680
17701
  `Project root: ${status.project_root ?? "(not configured)"}`,
17681
17702
  `Canonical root: ${status.canonical_root ?? "(not configured)"}`,
17682
- `Cache role: ${formatCacheRoleLabel(status.cache_role)}`,
17683
- "",
17684
- "Enabled features",
17685
- `- format_on_edit: ${formatFlag(status.features.format_on_edit)}`,
17686
- `- search_index: ${formatFlag(status.features.search_index)}`,
17687
- `- semantic_search: ${formatFlag(status.features.semantic_search)}`,
17688
- "",
17689
- "Search index",
17690
- `- status: ${status.search_index.status}`,
17691
- `- files: ${formatCount(status.search_index.files)}`,
17692
- `- trigrams: ${formatCount(status.search_index.trigrams)}`,
17693
- "",
17694
- "Semantic index",
17695
- `- status: ${formatSemanticIndexStatus(status.semantic_index.status, status.semantic_index.stage)}`
17703
+ `Cache role: ${formatCacheRoleLabel(status.cache_role)}`
17696
17704
  ];
17705
+ appendDegradedStatus(lines, status, false);
17706
+ 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)}`);
17697
17707
  const refreshing = formatSemanticRefreshing(status.semantic_index.refreshing_count);
17698
17708
  if (refreshing) {
17699
17709
  lines.push(`- ${refreshing}`);
@@ -17743,6 +17753,15 @@ function formatStatusDialogMessage(status) {
17743
17753
  return lines.join(`
17744
17754
  `);
17745
17755
  }
17756
+ function appendDegradedStatus(lines, status, markdown) {
17757
+ if (!status.degraded || status.degraded_reasons.length === 0)
17758
+ return;
17759
+ lines.push("", markdown ? "### Degraded mode" : "Degraded mode");
17760
+ for (const reason of status.degraded_reasons) {
17761
+ const detail = reason === "home_root" ? "project root is your home directory; heavy indexes are disabled" : reason;
17762
+ lines.push(`- ${detail}`);
17763
+ }
17764
+ }
17746
17765
 
17747
17766
  // src/tools/_shared.ts
17748
17767
  import { existsSync as existsSync9 } from "node:fs";
@@ -17753,7 +17772,8 @@ var optionalInt = (min, max, description = "(integer)") => Type.Optional(Type.Un
17753
17772
  function withPathAliasPreparation(tool) {
17754
17773
  const existing = tool.prepareArguments;
17755
17774
  const prepare = (args) => {
17756
- const prepared = tool.name === "edit" ? prepareCanonicalEditArguments(tool.name, args) : prepareCanonicalPathArguments(tool.name, args);
17775
+ const isEditTool = tool.name === "edit" || tool.name === "aft_edit";
17776
+ const prepared = isEditTool ? prepareCanonicalEditArguments("edit", args) : prepareCanonicalPathArguments(tool.name, args);
17757
17777
  return existing ? existing(prepared) : prepared;
17758
17778
  };
17759
17779
  return {
@@ -17980,7 +18000,7 @@ function renderInner(s, error3, theme, innerWidth) {
17980
18000
  lines.push(line);
17981
18001
  lines.push("");
17982
18002
  lines.push(theme.fg("muted", "Features"));
17983
- 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)}`);
18003
+ 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)}`);
17984
18004
  const compressionRows = formatCompressionStatusRows(s.compression);
17985
18005
  if (compressionRows.length > 0) {
17986
18006
  lines.push("");
@@ -18138,6 +18158,8 @@ function registerStatusCommand(pi, ctx) {
18138
18158
 
18139
18159
  // src/config.ts
18140
18160
  import { existsSync as existsSync10, readFileSync as readFileSync7, renameSync as renameSync5, unlinkSync as unlinkSync5, writeFileSync as writeFileSync4 } from "node:fs";
18161
+ import { homedir as homedir12 } from "node:os";
18162
+ import { isAbsolute as isAbsolute6 } from "node:path";
18141
18163
  var import_comment_json = __toESM(require_src2(), 1);
18142
18164
 
18143
18165
  // ../../node_modules/.bun/zod@4.4.3/node_modules/zod/v4/classic/external.js
@@ -32501,6 +32523,32 @@ var CheckerEnum = exports_external.enum([
32501
32523
  "none"
32502
32524
  ]);
32503
32525
  var ConfigureWarningsDeliveryEnum = exports_external.enum(["toast", "log", "chat"]);
32526
+ var IndexKindSchema = exports_external.enum(["search", "semantic", "callgraph"]);
32527
+ function isSupportedAbsoluteIndexPath(path2) {
32528
+ if (path2 === "~" || path2.startsWith("~/") || path2.startsWith("~\\")) {
32529
+ return isAbsolute6(homedir12());
32530
+ }
32531
+ return isAbsolute6(path2);
32532
+ }
32533
+ var IndexRootSchema = exports_external.object({
32534
+ path: exports_external.string().refine(isSupportedAbsoluteIndexPath, {
32535
+ message: "index.roots path must be absolute after ~ expansion"
32536
+ }),
32537
+ indexes: exports_external.array(IndexKindSchema).min(1, "index.roots indexes must be non-empty")
32538
+ }).superRefine(({ indexes }, context) => {
32539
+ if (new Set(indexes).size !== indexes.length) {
32540
+ context.addIssue({
32541
+ code: "custom",
32542
+ message: "index.roots indexes must not contain duplicates"
32543
+ });
32544
+ }
32545
+ }).transform(({ path: path2, indexes }) => ({
32546
+ path: path2,
32547
+ indexes: ["search", "semantic", "callgraph"].filter((kind) => indexes.includes(kind) || kind === "search" && indexes.includes("semantic"))
32548
+ }));
32549
+ var IndexConfigSchema = exports_external.object({
32550
+ roots: exports_external.array(IndexRootSchema).optional()
32551
+ });
32504
32552
  var SemanticConfigSchema = exports_external.object({
32505
32553
  backend: exports_external.enum(["fastembed", "openai_compatible", "ollama"]).optional(),
32506
32554
  model: exports_external.string().trim().min(1).optional(),
@@ -32566,7 +32614,12 @@ var SubcConfigSchema = exports_external.object({
32566
32614
  connection_file: exports_external.string().optional()
32567
32615
  });
32568
32616
  var GhShimConfigSchema = exports_external.object({
32569
- enabled: exports_external.boolean().optional()
32617
+ enabled: exports_external.boolean().optional(),
32618
+ binary_path: exports_external.string().trim().refine(isAbsolute6, "gh_shim.binary_path must be absolute").optional()
32619
+ });
32620
+ var GitCoAuthorSchema = exports_external.string().trim().refine((value) => value === "off" || value === "auto" || /^[^<>\r\n]+\s+<[^<>\s@]+@[^<>\s@]+>$/.test(value), "git.co_author must be 'off', 'auto', or an explicit 'Name <email>' identity");
32621
+ var GitConfigSchema = exports_external.object({
32622
+ co_author: GitCoAuthorSchema.optional()
32570
32623
  });
32571
32624
  var InspectConfigSchema = exports_external.object({
32572
32625
  enabled: exports_external.boolean().optional(),
@@ -32597,10 +32650,12 @@ var AftConfigSchema = exports_external.preprocess((value) => stripHarnessSpecifi
32597
32650
  formatter: exports_external.record(exports_external.string(), FormatterEnum).optional(),
32598
32651
  checker: exports_external.record(exports_external.string(), CheckerEnum).optional(),
32599
32652
  configure_warnings_delivery: ConfigureWarningsDeliveryEnum.optional(),
32653
+ hoist_builtin_tools: exports_external.boolean().optional(),
32600
32654
  tool_surface: exports_external.enum(["minimal", "recommended", "all"]).optional(),
32601
32655
  disabled_tools: exports_external.array(exports_external.string()).optional(),
32602
32656
  restrict_to_project_root: exports_external.boolean().optional(),
32603
32657
  search_index: exports_external.boolean().optional(),
32658
+ index: IndexConfigSchema.optional(),
32604
32659
  semantic_search: exports_external.boolean().optional(),
32605
32660
  callgraph_store: exports_external.boolean().optional(),
32606
32661
  callgraph_chunk_size: exports_external.number().optional(),
@@ -32615,7 +32670,8 @@ var AftConfigSchema = exports_external.preprocess((value) => stripHarnessSpecifi
32615
32670
  semantic: SemanticConfigSchema.optional(),
32616
32671
  bridge: BridgeConfigSchema.optional(),
32617
32672
  subc: SubcConfigSchema.optional(),
32618
- gh_shim: GhShimConfigSchema.optional()
32673
+ gh_shim: GhShimConfigSchema.optional(),
32674
+ git: GitConfigSchema.optional()
32619
32675
  }).strict());
32620
32676
  var CONFIG_MIGRATIONS = [
32621
32677
  { oldKey: "experimental_search_index", newPath: ["search_index"] },
@@ -32778,6 +32834,12 @@ function recordConfigParseFailure(configPath, errorMessage) {
32778
32834
  configLoadErrors.push({ path: configPath, message: errorMessage });
32779
32835
  warn2(formatConfigParseFailureMessage(configPath, errorMessage));
32780
32836
  }
32837
+ function warnIgnoredHarnessSpecificConfigKeys(config2, configPath) {
32838
+ const ignored = OPENCODE_ONLY_KEYS.filter((key) => Object.hasOwn(config2, key));
32839
+ if (ignored.length === 0)
32840
+ return;
32841
+ 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.`);
32842
+ }
32781
32843
  function loadConfigFromPath(configPath) {
32782
32844
  try {
32783
32845
  if (!existsSync10(configPath))
@@ -32790,6 +32852,7 @@ function loadConfigFromPath(configPath) {
32790
32852
  }
32791
32853
  migrateRawConfig(rawConfig, configPath, { log: log2, warn: warn2 });
32792
32854
  const cleanConfig = stripJsoncSymbols(rawConfig);
32855
+ warnIgnoredHarnessSpecificConfigKeys(cleanConfig, configPath);
32793
32856
  const result = AftConfigSchema.safeParse(cleanConfig);
32794
32857
  if (result.success) {
32795
32858
  log2(`Config loaded from ${configPath}`);
@@ -32953,6 +33016,7 @@ var PROJECT_SAFE_TOP_LEVEL_FIELDS = new Set([
32953
33016
  "enabled",
32954
33017
  "edit_mode",
32955
33018
  "tool_surface",
33019
+ "hoist_builtin_tools",
32956
33020
  "format_on_edit",
32957
33021
  "validate_on_edit",
32958
33022
  "configure_warnings_delivery",
@@ -32962,6 +33026,7 @@ var PROJECT_SAFE_TOP_LEVEL_FIELDS = new Set([
32962
33026
  "callgraph_chunk_size",
32963
33027
  "inspect",
32964
33028
  "worktree",
33029
+ "git",
32965
33030
  "experimental",
32966
33031
  "bash"
32967
33032
  ]);
@@ -32984,6 +33049,8 @@ function getStrippedTopLevelKeys(override) {
32984
33049
  stripped.push("bridge");
32985
33050
  if (override.backup !== undefined)
32986
33051
  stripped.push("backup");
33052
+ if (override.index?.roots !== undefined)
33053
+ stripped.push("index.roots");
32987
33054
  if (override.sandbox?.enabled === false)
32988
33055
  stripped.push("sandbox.enabled");
32989
33056
  if (override.sandbox?.write_allow !== undefined)
@@ -33690,18 +33757,18 @@ function ensureInstallAnchor(cwd) {
33690
33757
  }
33691
33758
  }
33692
33759
  function runInstall(spec, version2, cwd, signal) {
33693
- return new Promise((resolve7) => {
33760
+ return new Promise((resolve8) => {
33694
33761
  const target = `${spec.npm}@${version2}`;
33695
33762
  log2(`[lsp] installing ${target} to ${cwd}`);
33696
33763
  if (signal?.aborted) {
33697
33764
  warn2(`[lsp] install ${target} aborted before spawn`);
33698
- resolve7(false);
33765
+ resolve8(false);
33699
33766
  return;
33700
33767
  }
33701
33768
  const npm = resolveNpm();
33702
33769
  if (!npm) {
33703
33770
  warn2(`[lsp] npm not found on PATH or known locations; cannot install ${target}`);
33704
- resolve7(false);
33771
+ resolve8(false);
33705
33772
  return;
33706
33773
  }
33707
33774
  ensureInstallAnchor(cwd);
@@ -33724,7 +33791,7 @@ function runInstall(spec, version2, cwd, signal) {
33724
33791
  return;
33725
33792
  settled = true;
33726
33793
  cleanup();
33727
- resolve7(ok);
33794
+ resolve8(ok);
33728
33795
  };
33729
33796
  const onAbort = () => {
33730
33797
  warn2(`[lsp] install ${target} aborted during shutdown`);
@@ -33825,7 +33892,7 @@ function cachedPackageDir(npmPackage) {
33825
33892
  return lspBinDir(npmPackage).replace(/[\\/]node_modules[\\/]\.bin[\\/]?$/, "");
33826
33893
  }
33827
33894
  function hashInstalledBinary(spec) {
33828
- return new Promise((resolve7, reject) => {
33895
+ return new Promise((resolve8, reject) => {
33829
33896
  const candidates = process.platform === "win32" ? [
33830
33897
  lspBinaryPath(spec.npm, spec.binary),
33831
33898
  lspBinaryPath(spec.npm, `${spec.binary}.cmd`),
@@ -33849,7 +33916,7 @@ function hashInstalledBinary(spec) {
33849
33916
  const stream = createReadStream(pathToHash);
33850
33917
  stream.on("error", reject);
33851
33918
  stream.on("data", (chunk) => hash2.update(chunk));
33852
- stream.on("end", () => resolve7(hash2.digest("hex")));
33919
+ stream.on("end", () => resolve8(hash2.digest("hex")));
33853
33920
  });
33854
33921
  }
33855
33922
  function installedBinaryPath(spec) {
@@ -33976,7 +34043,7 @@ import {
33976
34043
  unlinkSync as unlinkSync7,
33977
34044
  writeFileSync as writeFileSync7
33978
34045
  } from "node:fs";
33979
- import { dirname as dirname6, join as join15, relative as relative3, resolve as resolve7 } from "node:path";
34046
+ import { dirname as dirname6, join as join15, relative as relative3, resolve as resolve8 } from "node:path";
33980
34047
  import { Readable as Readable3 } from "node:stream";
33981
34048
  import { pipeline as pipeline3 } from "node:stream/promises";
33982
34049
 
@@ -34133,12 +34200,12 @@ function writeGithubInstalledMetaIn(installDir, version2, binarySha256, archiveS
34133
34200
  var MAX_DOWNLOAD_BYTES3 = 256 * 1024 * 1024;
34134
34201
  var MAX_EXTRACT_BYTES2 = 1024 * 1024 * 1024;
34135
34202
  function sha256OfFile(path2) {
34136
- return new Promise((resolve8, reject) => {
34203
+ return new Promise((resolve9, reject) => {
34137
34204
  const hash2 = createHash5("sha256");
34138
34205
  const stream = createReadStream2(path2);
34139
34206
  stream.on("error", reject);
34140
34207
  stream.on("data", (chunk) => hash2.update(chunk));
34141
- stream.on("end", () => resolve8(hash2.digest("hex")));
34208
+ stream.on("end", () => resolve9(hash2.digest("hex")));
34142
34209
  });
34143
34210
  }
34144
34211
  function sha256OfFileSync2(path2) {
@@ -34395,7 +34462,7 @@ function validateExtraction(stagingRoot) {
34395
34462
  throw new Error(`failed to realpath ${full}: ${err}`);
34396
34463
  }
34397
34464
  const rel = relative3(realStagingRoot, realFull);
34398
- if (rel.startsWith("..") || resolve7(realStagingRoot, rel) !== realFull) {
34465
+ if (rel.startsWith("..") || resolve8(realStagingRoot, rel) !== realFull) {
34399
34466
  throw new Error(`archive entry escapes staging root: ${full} → ${realFull} (zip-slip defense)`);
34400
34467
  }
34401
34468
  if (lst.isDirectory()) {
@@ -34978,8 +35045,8 @@ function installProcessHandlers() {
34978
35045
  }
34979
35046
  signalShutdownStarted = true;
34980
35047
  process.exitCode = SIGNAL_EXIT_CODES[sig];
34981
- const timeout = new Promise((resolve8) => {
34982
- setTimeout(resolve8, SIGNAL_CLEANUP_TIMEOUT_MS);
35048
+ const timeout = new Promise((resolve9) => {
35049
+ setTimeout(resolve9, SIGNAL_CLEANUP_TIMEOUT_MS);
34983
35050
  });
34984
35051
  Promise.race([runCleanups(sig), timeout]).finally(() => {
34985
35052
  process.exit(SIGNAL_EXIT_CODES[sig]);
@@ -35021,16 +35088,222 @@ import { Type as Type3 } from "typebox";
35021
35088
 
35022
35089
  // src/tools/hoisted.ts
35023
35090
  import { stat as stat2 } from "node:fs/promises";
35024
- import { homedir as homedir12 } from "node:os";
35025
- import { isAbsolute as isAbsolute6, relative as relative4, resolve as resolve8, sep } from "node:path";
35091
+ import { homedir as homedir14 } from "node:os";
35092
+ import { isAbsolute as isAbsolute7, relative as relative4, resolve as resolve9, sep } from "node:path";
35026
35093
  import {
35027
- renderDiff
35094
+ renderDiff as renderDiff2
35028
35095
  } from "@earendil-works/pi-coding-agent";
35029
- import { Container, Spacer, Text } from "@earendil-works/pi-tui";
35096
+ import { Container as Container2, Spacer as Spacer2, Text as Text2 } from "@earendil-works/pi-tui";
35030
35097
  import { Type as Type2 } from "typebox";
35031
35098
 
35032
35099
  // src/tools/diff-format.ts
35033
35100
  import { diffLines } from "diff";
35101
+
35102
+ // src/tools/render-helpers.ts
35103
+ import { homedir as homedir13 } from "node:os";
35104
+ import { renderDiff } from "@earendil-works/pi-coding-agent";
35105
+ import { Container, Spacer, Text } from "@earendil-works/pi-tui";
35106
+ var NATIVE_COLLAPSED_RESULT_MAX_LINES = 5;
35107
+ function normalizeTerminalText(text) {
35108
+ return text.replace(/\r/g, "");
35109
+ }
35110
+
35111
+ class TerminalSafeComponent {
35112
+ component;
35113
+ constructor(component) {
35114
+ this.component = component;
35115
+ }
35116
+ render(width) {
35117
+ return this.component.render(width).map(normalizeTerminalText);
35118
+ }
35119
+ invalidate() {
35120
+ this.component.invalidate?.();
35121
+ }
35122
+ handleInput(data) {
35123
+ this.component.handleInput?.(data);
35124
+ }
35125
+ }
35126
+ function reuseText(last) {
35127
+ return last instanceof Text ? last : new Text("", 0, 0);
35128
+ }
35129
+ function reuseContainer(last) {
35130
+ return last instanceof Container ? last : new Container;
35131
+ }
35132
+ function shortenPath2(path2) {
35133
+ const home = homedir13();
35134
+ if (path2.startsWith(home))
35135
+ return `~${path2.slice(home.length)}`;
35136
+ return path2;
35137
+ }
35138
+ function renderToolCall(toolName, summary, theme, context) {
35139
+ const text = reuseText(context.lastComponent);
35140
+ const suffix = summary ? ` ${summary}` : "";
35141
+ text.setText(`${theme.fg("toolTitle", theme.bold(toolName))}${suffix}`);
35142
+ return text;
35143
+ }
35144
+ function accentPath(theme, path2) {
35145
+ if (!path2)
35146
+ return theme.fg("toolOutput", "...");
35147
+ return theme.fg("accent", shortenPath2(path2));
35148
+ }
35149
+ function collectTextContent(result) {
35150
+ return result.content.filter((part) => part.type === "text").map((part) => part.text ?? "").join(`
35151
+ `).trim();
35152
+ }
35153
+ function extractStructuredPayload(result) {
35154
+ if (result.details !== undefined)
35155
+ return result.details;
35156
+ const text = collectTextContent(result);
35157
+ if (!text)
35158
+ return;
35159
+ try {
35160
+ return JSON.parse(text);
35161
+ } catch {
35162
+ return;
35163
+ }
35164
+ }
35165
+ function renderErrorResult(result, fallback, theme, context) {
35166
+ const text = reuseText(context.lastComponent);
35167
+ const message = collectTextContent(result) || fallback;
35168
+ text.setText(`
35169
+ ${theme.fg("error", message)}`);
35170
+ return text;
35171
+ }
35172
+ function collapsibleResult({
35173
+ summary,
35174
+ full,
35175
+ expanded,
35176
+ context
35177
+ }) {
35178
+ const renderedAtCollapseWidth = full.render(200);
35179
+ const terminalSafeFull = renderedAtCollapseWidth.some((line) => line.includes("\r")) ? new TerminalSafeComponent(full) : full;
35180
+ if (expanded === true || renderedAtCollapseWidth.length <= NATIVE_COLLAPSED_RESULT_MAX_LINES) {
35181
+ return terminalSafeFull;
35182
+ }
35183
+ const text = reuseText(context.lastComponent);
35184
+ text.setText(`
35185
+ ${summary.replace(/[\r\n]+/g, " ").trim()}`);
35186
+ return text;
35187
+ }
35188
+ function renderSections(sections, context) {
35189
+ const container = reuseContainer(context.lastComponent);
35190
+ container.clear();
35191
+ const visibleSections = sections.filter((section) => section.trim().length > 0);
35192
+ if (visibleSections.length === 0)
35193
+ return container;
35194
+ container.addChild(new Spacer(1));
35195
+ visibleSections.forEach((section, index) => {
35196
+ if (index > 0)
35197
+ container.addChild(new Spacer(1));
35198
+ container.addChild(new Text(section, 0, 0));
35199
+ });
35200
+ return container;
35201
+ }
35202
+ function asRecord3(value) {
35203
+ if (!value || typeof value !== "object" || Array.isArray(value))
35204
+ return;
35205
+ return value;
35206
+ }
35207
+ function asRecords2(value) {
35208
+ return Array.isArray(value) ? value.map(asRecord3).filter(Boolean) : [];
35209
+ }
35210
+ function asString2(value) {
35211
+ return typeof value === "string" ? value : undefined;
35212
+ }
35213
+ function asNumber2(value) {
35214
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
35215
+ }
35216
+ function formatValue(value) {
35217
+ if (Array.isArray(value))
35218
+ return value.map(formatValue).join(", ");
35219
+ if (typeof value === "string")
35220
+ return value;
35221
+ if (typeof value === "number" || typeof value === "boolean")
35222
+ return String(value);
35223
+ if (value === null || value === undefined)
35224
+ return "—";
35225
+ try {
35226
+ return JSON.stringify(value);
35227
+ } catch {
35228
+ return String(value);
35229
+ }
35230
+ }
35231
+ function groupByFile(items, getFile) {
35232
+ const groups = new Map;
35233
+ items.forEach((item) => {
35234
+ const file2 = getFile(item) ?? "(unknown file)";
35235
+ const current = groups.get(file2) ?? [];
35236
+ current.push(item);
35237
+ groups.set(file2, current);
35238
+ });
35239
+ return groups;
35240
+ }
35241
+ function formatTimestamp(value) {
35242
+ if (typeof value === "string" && value.length > 0)
35243
+ return value;
35244
+ if (typeof value !== "number" || !Number.isFinite(value))
35245
+ return;
35246
+ const millis = value > 1000000000000 ? value : value * 1000;
35247
+ const date5 = new Date(millis);
35248
+ if (Number.isNaN(date5.getTime()))
35249
+ return String(value);
35250
+ return date5.toISOString().replace("T", " ").replace(".000Z", "Z");
35251
+ }
35252
+ function formatUnifiedDiffForPi(unifiedDiff) {
35253
+ if (!unifiedDiff.trim())
35254
+ return "";
35255
+ const entries = [];
35256
+ const hunkHeader = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/;
35257
+ let oldLine = 1;
35258
+ let newLine = 1;
35259
+ for (const line of unifiedDiff.split(`
35260
+ `)) {
35261
+ if (!line)
35262
+ continue;
35263
+ if (line.startsWith("--- ") || line.startsWith("+++ "))
35264
+ continue;
35265
+ if (line.startsWith("\"))
35266
+ continue;
35267
+ const headerMatch = line.match(hunkHeader);
35268
+ if (headerMatch) {
35269
+ oldLine = Number(headerMatch[1]);
35270
+ newLine = Number(headerMatch[2]);
35271
+ continue;
35272
+ }
35273
+ if (line.startsWith("+") && !line.startsWith("+++")) {
35274
+ entries.push({ prefix: "+", line: newLine, text: line.slice(1) });
35275
+ newLine += 1;
35276
+ continue;
35277
+ }
35278
+ if (line.startsWith("-") && !line.startsWith("---")) {
35279
+ entries.push({ prefix: "-", line: oldLine, text: line.slice(1) });
35280
+ oldLine += 1;
35281
+ continue;
35282
+ }
35283
+ if (line.startsWith(" ")) {
35284
+ entries.push({ prefix: " ", line: oldLine, text: line.slice(1) });
35285
+ oldLine += 1;
35286
+ newLine += 1;
35287
+ }
35288
+ }
35289
+ if (entries.length === 0)
35290
+ return "";
35291
+ const width = String(entries.reduce((max, entry) => Math.max(max, entry.line), 1)).length;
35292
+ return normalizeTerminalText(entries.map((entry) => `${entry.prefix}${String(entry.line).padStart(width, " ")} ${entry.text}`).join(`
35293
+ `));
35294
+ }
35295
+ function renderUnifiedDiff(unifiedDiff) {
35296
+ const piDiff = formatUnifiedDiffForPi(unifiedDiff);
35297
+ if (!piDiff)
35298
+ return "";
35299
+ try {
35300
+ return normalizeTerminalText(renderDiff(piDiff));
35301
+ } catch {
35302
+ return normalizeTerminalText(piDiff);
35303
+ }
35304
+ }
35305
+
35306
+ // src/tools/diff-format.ts
35034
35307
  var DEFAULT_CONTEXT_LINES = 4;
35035
35308
  function formatDiffForPi(oldContent, newContent, contextLines = DEFAULT_CONTEXT_LINES) {
35036
35309
  const parts = diffLines(oldContent, newContent);
@@ -35127,8 +35400,11 @@ function formatDiffForPi(oldContent, newContent, contextLines = DEFAULT_CONTEXT_
35127
35400
  }
35128
35401
  lastWasChange = false;
35129
35402
  }
35130
- return { diff: output.join(`
35131
- `), firstChangedLine };
35403
+ return {
35404
+ diff: normalizeTerminalText(output.join(`
35405
+ `)),
35406
+ firstChangedLine
35407
+ };
35132
35408
  }
35133
35409
 
35134
35410
  // src/tools/hoisted.ts
@@ -35163,22 +35439,22 @@ function modelSupportsImages(extCtx) {
35163
35439
  var NON_VISION_IMAGE_NOTE = "[Current model does not support images. The image will be omitted from this request.]";
35164
35440
  function containsPath(parent, child) {
35165
35441
  const rel = relative4(parent, child);
35166
- return rel === "" || !rel.startsWith("..") && !isAbsolute6(rel);
35442
+ return rel === "" || !rel.startsWith("..") && !isAbsolute7(rel);
35167
35443
  }
35168
35444
  function normalizePathInput(path2) {
35169
35445
  const decoded = decodeFileUrl(path2);
35170
35446
  if (!decoded || !decoded.startsWith("~"))
35171
35447
  return decoded;
35172
35448
  if (decoded === "~")
35173
- return homedir12();
35449
+ return homedir14();
35174
35450
  if (decoded.startsWith(`~${sep}`) || decoded.startsWith("~/")) {
35175
- return resolve8(homedir12(), decoded.slice(2));
35451
+ return resolve9(homedir14(), decoded.slice(2));
35176
35452
  }
35177
35453
  return decoded;
35178
35454
  }
35179
35455
  function absoluteSearchPath(cwd, target) {
35180
35456
  const expanded = normalizePathInput(target);
35181
- return isAbsolute6(expanded) ? expanded : resolve8(cwd, expanded);
35457
+ return isAbsolute7(expanded) ? expanded : resolve9(cwd, expanded);
35182
35458
  }
35183
35459
  async function searchPathExists(cwd, target) {
35184
35460
  try {
@@ -35234,7 +35510,7 @@ async function assertExternalDirectoryPermission(extCtx, target, options = {}) {
35234
35510
  if (!target)
35235
35511
  return;
35236
35512
  const expanded = normalizePathInput(target);
35237
- const absoluteTarget = isAbsolute6(expanded) ? expanded : resolve8(extCtx.cwd, expanded);
35513
+ const absoluteTarget = isAbsolute7(expanded) ? expanded : resolve9(extCtx.cwd, expanded);
35238
35514
  if (containsPath(extCtx.cwd, absoluteTarget))
35239
35515
  return;
35240
35516
  if (options.restrictToProjectRoot === false)
@@ -35360,20 +35636,25 @@ function validateBatchEdits(edits) {
35360
35636
  });
35361
35637
  }
35362
35638
  function renderReadCall(args, theme, context) {
35363
- const text = reuseText(context.lastComponent);
35639
+ const text = reuseText2(context.lastComponent);
35364
35640
  const filePath = args ? readPathArg(args) : undefined;
35365
- const pathDisplay = filePath ? theme.fg("accent", shortenPath2(filePath)) : theme.fg("toolOutput", "...");
35641
+ const pathDisplay = filePath ? theme.fg("accent", shortenPath3(filePath)) : theme.fg("toolOutput", "...");
35366
35642
  text.setText(`${theme.fg("toolTitle", theme.bold("read"))} ${pathDisplay}`);
35367
35643
  return text;
35368
35644
  }
35369
35645
  function registerHoistedTools(pi, ctx, surface) {
35646
+ const prefix = surface.hoistBuiltinTools === false ? "aft_" : "";
35647
+ const readName = `${prefix}read`;
35648
+ const writeName = `${prefix}write`;
35649
+ const editName = `${prefix}edit`;
35650
+ const grepName = `${prefix}grep`;
35370
35651
  if (surface.hoistRead) {
35371
35652
  pi.registerTool(withPathAliasPreparation({
35372
- name: "read",
35373
- label: "read",
35653
+ name: readName,
35654
+ label: readName,
35374
35655
  description: "Read file contents with line numbers. Backed by AFT's indexed Rust reader — faster than the built-in `read` on large repos. Images are returned as attachments on vision-capable models; PDFs and non-vision models are not yet supported.",
35375
35656
  promptSnippet: "Read file contents (supports offset/limit for large files)",
35376
- promptGuidelines: ["Use read to examine files instead of cat or sed."],
35657
+ promptGuidelines: [`Use ${readName} to examine files instead of cat or sed.`],
35377
35658
  parameters: ReadParams,
35378
35659
  async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
35379
35660
  const bridge = bridgeFor(ctx, extCtx.cwd);
@@ -35435,11 +35716,11 @@ PDFs aren't supported on the Pi harness yet.`, response);
35435
35716
  if (surface.hoistWrite) {
35436
35717
  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).";
35437
35718
  pi.registerTool(withPathAliasPreparation({
35438
- name: "write",
35439
- label: "write",
35440
- 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.`,
35719
+ name: writeName,
35720
+ label: writeName,
35721
+ 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.`,
35441
35722
  promptSnippet: "Create or overwrite files (uses path; auto-formats)",
35442
- promptGuidelines: ["Use write only for new files or complete rewrites."],
35723
+ promptGuidelines: [`Use ${writeName} only for new files or complete rewrites.`],
35443
35724
  parameters: WriteParams,
35444
35725
  async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
35445
35726
  const filePathArg = mutationFilePathArg(params);
@@ -35464,17 +35745,17 @@ PDFs aren't supported on the Pi harness yet.`, response);
35464
35745
  renderCall(args, theme, context) {
35465
35746
  return renderMutationCall("write", mutationFilePathArg(args ?? {}), theme, context);
35466
35747
  },
35467
- renderResult(result, _options, theme, context) {
35468
- return renderMutationResult(result, theme, context);
35748
+ renderResult(result, options = { expanded: false, isPartial: false }, theme, context) {
35749
+ return renderMutationResult(result, theme, context, options);
35469
35750
  }
35470
35751
  }));
35471
35752
  }
35472
35753
  if (surface.hoistEdit && ctx.hashlineEffective === true) {
35473
35754
  pi.registerTool({
35474
- name: "edit",
35475
- label: "edit",
35755
+ name: editName,
35756
+ label: editName,
35476
35757
  description: HASHLINE_EDIT_DESCRIPTION,
35477
- promptSnippet: "Apply a tagged hashline patch through the edit tool",
35758
+ promptSnippet: `Apply a tagged hashline patch through the ${editName} tool`,
35478
35759
  promptGuidelines: [
35479
35760
  "Read a file to obtain its current hashline tag before editing it.",
35480
35761
  "Pass exactly one patch string; paths and tags belong in patch section headers."
@@ -35506,13 +35787,13 @@ PDFs aren't supported on the Pi harness yet.`, response);
35506
35787
  }
35507
35788
  if (surface.hoistEdit && ctx.hashlineEffective !== true) {
35508
35789
  pi.registerTool(withPathAliasPreparation({
35509
- name: "edit",
35510
- label: "edit",
35790
+ name: editName,
35791
+ label: editName,
35511
35792
  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).",
35512
35793
  promptSnippet: "Partial file edits via appendContent, edits[], or symbol plus content (exactly one mode per call).",
35513
35794
  promptGuidelines: [
35514
- "Prefer edit over write when changing part of an existing file.",
35515
- "Use appendContent when adding text to the end of a file.",
35795
+ `Prefer ${editName} over ${writeName} when changing part of an existing file.`,
35796
+ `Use ${editName} with appendContent when adding text to the end of a file.`,
35516
35797
  "Use edits[] for multiple atomic changes in one file.",
35517
35798
  "Include enough surrounding context in an edits[] find/replace item to make the match unique, or set replaceAll/occurrence explicitly."
35518
35799
  ],
@@ -35547,18 +35828,18 @@ PDFs aren't supported on the Pi harness yet.`, response);
35547
35828
  renderCall(args, theme, context) {
35548
35829
  return renderMutationCall("edit", mutationFilePathArg(args ?? {}), theme, context);
35549
35830
  },
35550
- renderResult(result, _options, theme, context) {
35551
- return renderMutationResult(result, theme, context);
35831
+ renderResult(result, options = { expanded: false, isPartial: false }, theme, context) {
35832
+ return renderMutationResult(result, theme, context, options);
35552
35833
  }
35553
35834
  }));
35554
35835
  }
35555
35836
  if (surface.hoistGrep) {
35556
35837
  pi.registerTool(withPathAliasPreparation({
35557
- name: "grep",
35558
- label: "grep",
35838
+ name: grepName,
35839
+ label: grepName,
35559
35840
  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.",
35560
35841
  promptSnippet: "Fast regex search across files (trigram-indexed inside the project root)",
35561
- promptGuidelines: ["Prefer grep over bash-invoked find/rg for in-project searches."],
35842
+ promptGuidelines: [`Prefer ${grepName} over bash-invoked find/rg for in-project searches.`],
35562
35843
  parameters: GrepParams,
35563
35844
  async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
35564
35845
  const bridge = bridgeFor(ctx, extCtx.cwd);
@@ -35688,23 +35969,23 @@ function formatDiagnosticsText(diagnostics) {
35688
35969
  return JSON.stringify(diagnostics, null, 2);
35689
35970
  }
35690
35971
  }
35691
- function reuseText(last) {
35692
- return last instanceof Text ? last : new Text("", 0, 0);
35972
+ function reuseText2(last) {
35973
+ return last instanceof Text2 ? last : new Text2("", 0, 0);
35693
35974
  }
35694
- function reuseContainer(last) {
35695
- return last instanceof Container ? last : new Container;
35975
+ function reuseContainer2(last) {
35976
+ return last instanceof Container2 ? last : new Container2;
35696
35977
  }
35697
35978
  function renderMutationCall(toolName, filePath, theme, context) {
35698
- const text = reuseText(context.lastComponent);
35699
- const pathDisplay = filePath ? theme.fg("accent", shortenPath2(filePath)) : theme.fg("toolOutput", "...");
35979
+ const text = reuseText2(context.lastComponent);
35980
+ const pathDisplay = filePath ? theme.fg("accent", shortenPath3(filePath)) : theme.fg("toolOutput", "...");
35700
35981
  text.setText(`${theme.fg("toolTitle", theme.bold(toolName))} ${pathDisplay}`);
35701
35982
  return text;
35702
35983
  }
35703
- function renderMutationResult(result, theme, context) {
35984
+ function renderMutationResult(result, theme, context, options = { expanded: true }) {
35704
35985
  if (context.isError) {
35705
35986
  const errorText = result.content.filter((c) => c.type === "text").map((c) => c.text ?? "").join(`
35706
35987
  `).trim();
35707
- const text = reuseText(context.lastComponent);
35988
+ const text = reuseText2(context.lastComponent);
35708
35989
  text.setText(`
35709
35990
  ${theme.fg("error", errorText || "edit failed")}`);
35710
35991
  return text;
@@ -35714,27 +35995,46 @@ ${theme.fg("error", errorText || "edit failed")}`);
35714
35995
  if (!diff) {
35715
35996
  const additions = details?.additions ?? 0;
35716
35997
  const deletions = details?.deletions ?? 0;
35717
- const text = reuseText(context.lastComponent);
35718
35998
  const countDetail = typeof details?.editsApplied === "number" && details.editsApplied > 1 ? `, ${details.editsApplied} edits` : typeof details?.replacements === "number" && details.replacements > 1 ? `, ${details.replacements} replacements` : "";
35719
- const summary = theme.fg("success", `+${additions}/-${deletions}${countDetail}`);
35999
+ const summary2 = theme.fg("success", `+${additions}/-${deletions}${countDetail}`);
35720
36000
  let suffix = "";
35721
36001
  if (details?.truncated) {
35722
36002
  suffix = ` ${theme.fg("muted", "(diff truncated)")}`;
35723
36003
  } else if (details?.noOp) {
35724
36004
  suffix = ` ${theme.fg("muted", "(no net change)")}`;
35725
36005
  }
35726
- text.setText(`
35727
- ${summary}${suffix}`);
35728
- return text;
36006
+ const full = reuseText2(context.lastComponent);
36007
+ full.setText(`
36008
+ ${summary2}${suffix}`);
36009
+ return collapsibleResult({
36010
+ summary: `${summary2}${suffix}`,
36011
+ full,
36012
+ expanded: options.expanded,
36013
+ context
36014
+ });
35729
36015
  }
35730
- const container = reuseContainer(context.lastComponent);
36016
+ const container = reuseContainer2(context.lastComponent);
35731
36017
  container.clear();
35732
- container.addChild(new Spacer(1));
35733
- container.addChild(new Text(renderDiff(diff), 1, 0));
35734
- return container;
36018
+ container.addChild(new Spacer2(1));
36019
+ let renderedDiff;
36020
+ try {
36021
+ renderedDiff = renderDiff2(diff);
36022
+ } catch {
36023
+ renderedDiff = diff;
36024
+ }
36025
+ container.addChild(new Text2(normalizeTerminalText(renderedDiff), 1, 0));
36026
+ const rawPath = context.args && typeof context.args === "object" && typeof context.args.path === "string" ? context.args.path : undefined;
36027
+ const summaryPath = rawPath ? ` ${shortenPath3(rawPath)}` : "";
36028
+ const summary = `edited${summaryPath} (+${details?.additions ?? 0}/-${details?.deletions ?? 0})`;
36029
+ return collapsibleResult({
36030
+ summary,
36031
+ full: container,
36032
+ expanded: options.expanded,
36033
+ context
36034
+ });
35735
36035
  }
35736
- function shortenPath2(path2) {
35737
- const home = homedir12();
36036
+ function shortenPath3(path2) {
36037
+ const home = homedir14();
35738
36038
  if (path2.startsWith(home))
35739
36039
  return `~${path2.slice(home.length)}`;
35740
36040
  return path2;
@@ -35750,174 +36050,6 @@ async function resolvePathArg(cwd, path2) {
35750
36050
  }
35751
36051
  }
35752
36052
 
35753
- // src/tools/render-helpers.ts
35754
- import { homedir as homedir13 } from "node:os";
35755
- import { renderDiff as renderDiff2 } from "@earendil-works/pi-coding-agent";
35756
- import { Container as Container2, Spacer as Spacer2, Text as Text2 } from "@earendil-works/pi-tui";
35757
- function reuseText2(last) {
35758
- return last instanceof Text2 ? last : new Text2("", 0, 0);
35759
- }
35760
- function reuseContainer2(last) {
35761
- return last instanceof Container2 ? last : new Container2;
35762
- }
35763
- function shortenPath3(path2) {
35764
- const home = homedir13();
35765
- if (path2.startsWith(home))
35766
- return `~${path2.slice(home.length)}`;
35767
- return path2;
35768
- }
35769
- function renderToolCall(toolName, summary, theme, context) {
35770
- const text = reuseText2(context.lastComponent);
35771
- const suffix = summary ? ` ${summary}` : "";
35772
- text.setText(`${theme.fg("toolTitle", theme.bold(toolName))}${suffix}`);
35773
- return text;
35774
- }
35775
- function accentPath(theme, path2) {
35776
- if (!path2)
35777
- return theme.fg("toolOutput", "...");
35778
- return theme.fg("accent", shortenPath3(path2));
35779
- }
35780
- function collectTextContent(result) {
35781
- return result.content.filter((part) => part.type === "text").map((part) => part.text ?? "").join(`
35782
- `).trim();
35783
- }
35784
- function extractStructuredPayload(result) {
35785
- if (result.details !== undefined)
35786
- return result.details;
35787
- const text = collectTextContent(result);
35788
- if (!text)
35789
- return;
35790
- try {
35791
- return JSON.parse(text);
35792
- } catch {
35793
- return;
35794
- }
35795
- }
35796
- function renderErrorResult(result, fallback, theme, context) {
35797
- const text = reuseText2(context.lastComponent);
35798
- const message = collectTextContent(result) || fallback;
35799
- text.setText(`
35800
- ${theme.fg("error", message)}`);
35801
- return text;
35802
- }
35803
- function renderSections(sections, context) {
35804
- const container = reuseContainer2(context.lastComponent);
35805
- container.clear();
35806
- const visibleSections = sections.filter((section) => section.trim().length > 0);
35807
- if (visibleSections.length === 0)
35808
- return container;
35809
- container.addChild(new Spacer2(1));
35810
- visibleSections.forEach((section, index) => {
35811
- if (index > 0)
35812
- container.addChild(new Spacer2(1));
35813
- container.addChild(new Text2(section, 0, 0));
35814
- });
35815
- return container;
35816
- }
35817
- function asRecord3(value) {
35818
- if (!value || typeof value !== "object" || Array.isArray(value))
35819
- return;
35820
- return value;
35821
- }
35822
- function asRecords2(value) {
35823
- return Array.isArray(value) ? value.map(asRecord3).filter(Boolean) : [];
35824
- }
35825
- function asString2(value) {
35826
- return typeof value === "string" ? value : undefined;
35827
- }
35828
- function asNumber2(value) {
35829
- return typeof value === "number" && Number.isFinite(value) ? value : undefined;
35830
- }
35831
- function formatValue(value) {
35832
- if (Array.isArray(value))
35833
- return value.map(formatValue).join(", ");
35834
- if (typeof value === "string")
35835
- return value;
35836
- if (typeof value === "number" || typeof value === "boolean")
35837
- return String(value);
35838
- if (value === null || value === undefined)
35839
- return "—";
35840
- try {
35841
- return JSON.stringify(value);
35842
- } catch {
35843
- return String(value);
35844
- }
35845
- }
35846
- function groupByFile(items, getFile) {
35847
- const groups = new Map;
35848
- items.forEach((item) => {
35849
- const file2 = getFile(item) ?? "(unknown file)";
35850
- const current = groups.get(file2) ?? [];
35851
- current.push(item);
35852
- groups.set(file2, current);
35853
- });
35854
- return groups;
35855
- }
35856
- function formatTimestamp(value) {
35857
- if (typeof value === "string" && value.length > 0)
35858
- return value;
35859
- if (typeof value !== "number" || !Number.isFinite(value))
35860
- return;
35861
- const millis = value > 1000000000000 ? value : value * 1000;
35862
- const date5 = new Date(millis);
35863
- if (Number.isNaN(date5.getTime()))
35864
- return String(value);
35865
- return date5.toISOString().replace("T", " ").replace(".000Z", "Z");
35866
- }
35867
- function formatUnifiedDiffForPi(unifiedDiff) {
35868
- if (!unifiedDiff.trim())
35869
- return "";
35870
- const entries = [];
35871
- const hunkHeader = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/;
35872
- let oldLine = 1;
35873
- let newLine = 1;
35874
- for (const line of unifiedDiff.split(`
35875
- `)) {
35876
- if (!line)
35877
- continue;
35878
- if (line.startsWith("--- ") || line.startsWith("+++ "))
35879
- continue;
35880
- if (line.startsWith("\"))
35881
- continue;
35882
- const headerMatch = line.match(hunkHeader);
35883
- if (headerMatch) {
35884
- oldLine = Number(headerMatch[1]);
35885
- newLine = Number(headerMatch[2]);
35886
- continue;
35887
- }
35888
- if (line.startsWith("+") && !line.startsWith("+++")) {
35889
- entries.push({ prefix: "+", line: newLine, text: line.slice(1) });
35890
- newLine += 1;
35891
- continue;
35892
- }
35893
- if (line.startsWith("-") && !line.startsWith("---")) {
35894
- entries.push({ prefix: "-", line: oldLine, text: line.slice(1) });
35895
- oldLine += 1;
35896
- continue;
35897
- }
35898
- if (line.startsWith(" ")) {
35899
- entries.push({ prefix: " ", line: oldLine, text: line.slice(1) });
35900
- oldLine += 1;
35901
- newLine += 1;
35902
- }
35903
- }
35904
- if (entries.length === 0)
35905
- return "";
35906
- const width = String(entries.reduce((max, entry) => Math.max(max, entry.line), 1)).length;
35907
- return entries.map((entry) => `${entry.prefix}${String(entry.line).padStart(width, " ")} ${entry.text}`).join(`
35908
- `);
35909
- }
35910
- function renderUnifiedDiff(unifiedDiff) {
35911
- const piDiff = formatUnifiedDiffForPi(unifiedDiff);
35912
- if (!piDiff)
35913
- return "";
35914
- try {
35915
- return renderDiff2(piDiff);
35916
- } catch {
35917
- return piDiff;
35918
- }
35919
- }
35920
-
35921
36053
  // src/tools/ast.ts
35922
36054
  var AstLang = StringEnum(["typescript", "tsx", "javascript", "python", "rust", "go", "pascal", "r", "objc"], {
35923
36055
  description: "Target language"
@@ -35995,7 +36127,7 @@ function buildAstSearchSections(payload, theme) {
35995
36127
  const grouped = groupByFile(matches, (match) => asString2(match.file));
35996
36128
  const sections = [header];
35997
36129
  for (const [file2, fileMatches] of grouped.entries()) {
35998
- const lines = [theme.fg("accent", shortenPath3(file2))];
36130
+ const lines = [theme.fg("accent", shortenPath2(file2))];
35999
36131
  fileMatches.forEach((match, index) => {
36000
36132
  const line = asNumber2(match.line) ?? 0;
36001
36133
  const column = asNumber2(match.column) ?? 0;
@@ -36042,7 +36174,7 @@ function buildAstReplaceSections(payload, theme) {
36042
36174
  return sections;
36043
36175
  }
36044
36176
  files.forEach((fileResult) => {
36045
- const file2 = shortenPath3(asString2(fileResult.file) ?? "(unknown file)");
36177
+ const file2 = shortenPath2(asString2(fileResult.file) ?? "(unknown file)");
36046
36178
  const replacements = asNumber2(fileResult.replacements) ?? 0;
36047
36179
  const error53 = asString2(fileResult.error);
36048
36180
  const diff = asString2(fileResult.diff);
@@ -36068,17 +36200,27 @@ function renderAstCall(toolName, args, theme, context) {
36068
36200
  const summary = toolName === "ast_grep_replace" ? `${lang} ${theme.fg("toolOutput", `${args.pattern} → ${args.rewrite}`)}` : `${lang} ${theme.fg("toolOutput", args.pattern)}`;
36069
36201
  return renderToolCall(toolName === "ast_grep_replace" ? "ast replace" : "ast search", summary, theme, context);
36070
36202
  }
36071
- function renderAstResult(toolName, result, theme, context) {
36203
+ function renderAstResult(toolName, result, theme, context, options = { expanded: true }) {
36072
36204
  if (context.isError) {
36073
36205
  return renderErrorResult(result, `${toolName} failed`, theme, context);
36074
36206
  }
36075
36207
  const payload = extractStructuredPayload(result);
36076
36208
  if (!payload) {
36077
- const text = collectTextContent(result);
36078
- return renderSections([text || theme.fg("muted", "No result.")], context);
36209
+ const text = collectTextContent(result) || theme.fg("muted", "No result.");
36210
+ return collapsibleResult({
36211
+ summary: text,
36212
+ full: renderSections([text], context),
36213
+ expanded: options.expanded,
36214
+ context
36215
+ });
36079
36216
  }
36080
36217
  const sections = toolName === "ast_grep_replace" ? buildAstReplaceSections(payload, theme) : buildAstSearchSections(payload, theme);
36081
- return renderSections(sections, context);
36218
+ return collapsibleResult({
36219
+ summary: sections[0] ?? `${toolName} completed`,
36220
+ full: renderSections(sections, context),
36221
+ expanded: options.expanded,
36222
+ context
36223
+ });
36082
36224
  }
36083
36225
  function registerAstTools(pi, ctx, surface) {
36084
36226
  if (surface.astSearch) {
@@ -36110,8 +36252,8 @@ function registerAstTools(pi, ctx, surface) {
36110
36252
  renderCall(args, theme, context) {
36111
36253
  return renderAstCall("ast_grep_search", args, theme, context);
36112
36254
  },
36113
- renderResult(result, _options, theme, context) {
36114
- return renderAstResult("ast_grep_search", result, theme, context);
36255
+ renderResult(result, options = { expanded: false, isPartial: false }, theme, context) {
36256
+ return renderAstResult("ast_grep_search", result, theme, context, options);
36115
36257
  }
36116
36258
  });
36117
36259
  }
@@ -36144,8 +36286,8 @@ function registerAstTools(pi, ctx, surface) {
36144
36286
  renderCall(args, theme, context) {
36145
36287
  return renderAstCall("ast_grep_replace", args, theme, context);
36146
36288
  },
36147
- renderResult(result, _options, theme, context) {
36148
- return renderAstResult("ast_grep_replace", result, theme, context);
36289
+ renderResult(result, options = { expanded: false, isPartial: false }, theme, context) {
36290
+ return renderAstResult("ast_grep_replace", result, theme, context, options);
36149
36291
  }
36150
36292
  });
36151
36293
  }
@@ -36335,16 +36477,18 @@ function getBashSpawnHook(pi) {
36335
36477
  }
36336
36478
  return api2.hooks?.bashSpawn;
36337
36479
  }
36338
- function registerBashTool(pi, ctx, aftSearchRegistered = false) {
36480
+ function registerBashTool(pi, ctx, aftSearchRegistered = false, registeredName = "bash", registerCompanions = true) {
36339
36481
  const spawnHook = getBashSpawnHook(pi);
36340
- 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";
36482
+ const readToolName = registeredName === "bash" ? "read" : "aft_read";
36483
+ const grepToolName = registeredName === "bash" ? "grep" : "aft_grep";
36484
+ 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`;
36341
36485
  const bashCfg = resolveBashConfig(ctx.config);
36342
36486
  let hostFallbackActive = false;
36343
36487
  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`." : "";
36344
36488
  const tasksSentence = bashCfg.background ? ' Commands run in the foreground and return inline; `wait: true` blocks until a long command finishes instead of auto-promoting, but detaches to background if you send a new message — use it when you need the result before doing anything else; keep it off otherwise so auto-promote can remind you while you work. Use `background: true` yourself ONLY when you have other useful work to do while it runs; then `bash_watch` waits on the task (sync blocks until exit/pattern, async notifies) and `bash_status` peeks at it — never background a command and immediately `bash_watch` it (that wastes a turn for what foreground returns in one), and never loop `bash_status` to wait. `pty: true` runs interactive programs (REPLs, TUIs), implies background, and is driven with `bash_status({ output_mode: "screen" })` plus `bash_write`.' : " Commands run in the foreground to completion; `timeout` is the hard kill cap (default 30 minutes).";
36345
36489
  pi.registerTool({
36346
- name: "bash",
36347
- label: "bash",
36490
+ name: registeredName,
36491
+ label: registeredName,
36348
36492
  description: `Execute shell commands.${compressionSentence}${tasksSentence}
36349
36493
 
36350
36494
  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}.`,
@@ -36470,16 +36614,20 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
36470
36614
  renderCall(args, theme, context) {
36471
36615
  return renderBashCall(args?.command, args?.description, theme, context);
36472
36616
  },
36473
- renderResult(result, _options, theme, context) {
36474
- return renderBashResult(result, theme, context);
36617
+ renderResult(result, options = { expanded: false, isPartial: false }, theme, context) {
36618
+ return renderBashResult(result, theme, context, options);
36475
36619
  }
36476
36620
  });
36477
- if (bashCfg.background) {
36478
- pi.registerTool(createBashStatusTool(ctx));
36479
- pi.registerTool(createBashWatchTool(ctx));
36480
- pi.registerTool(createBashWriteTool(ctx));
36481
- pi.registerTool(createBashKillTool(ctx));
36482
- }
36621
+ if (registerCompanions)
36622
+ registerBashCompanionTools(pi, ctx);
36623
+ }
36624
+ function registerBashCompanionTools(pi, ctx) {
36625
+ if (!resolveBashConfig(ctx.config).background)
36626
+ return;
36627
+ pi.registerTool(createBashStatusTool(ctx));
36628
+ pi.registerTool(createBashWatchTool(ctx));
36629
+ pi.registerTool(createBashWriteTool(ctx));
36630
+ pi.registerTool(createBashKillTool(ctx));
36483
36631
  }
36484
36632
  function withBashHints(output, command, aftSearchRegistered, projectRoot) {
36485
36633
  return maybeAppendGrepSearchHint(maybeAppendConflictsHint(output), command, aftSearchRegistered, projectRoot);
@@ -36908,7 +37056,7 @@ function renderBashCall(command, description, theme, context) {
36908
37056
  text.setText(`${theme.fg("toolTitle", theme.bold("bash"))} ${theme.fg("accent", display)}`);
36909
37057
  return text;
36910
37058
  }
36911
- function renderBashResult(result, theme, context) {
37059
+ function renderBashResult(result, theme, context, options = { expanded: true }) {
36912
37060
  if (context.isError) {
36913
37061
  const errorText = result.content.filter((c) => c.type === "text").map((c) => c.text ?? "").join(`
36914
37062
  `).trim();
@@ -36955,7 +37103,14 @@ ${theme.fg("error", errorText || "bash failed")}`);
36955
37103
  const truncText = theme.fg("warning", "(output truncated)");
36956
37104
  container.addChild(new Text3(truncText, 1, 0));
36957
37105
  }
36958
- return container;
37106
+ const firstOutputLine = rawOutput.split(/\r?\n/, 1)[0] || "(no output)";
37107
+ const exitSummary = exitCode === undefined ? "completed" : `exit ${exitCode}`;
37108
+ return collapsibleResult({
37109
+ summary: `${exitSummary}: ${firstOutputLine}`,
37110
+ full: container,
37111
+ expanded: options.expanded,
37112
+ context
37113
+ });
36959
37114
  }
36960
37115
  function shortenCommand(command) {
36961
37116
  if (command.length <= 60)
@@ -36987,14 +37142,19 @@ function buildConflictSections(text) {
36987
37142
  sections.push(...rest.map((section) => section.trim()).filter(Boolean));
36988
37143
  return sections;
36989
37144
  }
36990
- function renderConflictResult(text, theme, context) {
37145
+ function renderConflictResult(text, theme, context, options = { expanded: true }) {
36991
37146
  const sections = buildConflictSections(text).map((section, index) => index === 0 ? theme.fg("warning", section) : section);
36992
- return renderSections(sections, context);
37147
+ return collapsibleResult({
37148
+ summary: sections[0] ?? "No merge conflicts found.",
37149
+ full: renderSections(sections, context),
37150
+ expanded: options.expanded,
37151
+ context
37152
+ });
36993
37153
  }
36994
- function renderConflictToolResult(result, theme, context) {
37154
+ function renderConflictToolResult(result, theme, context, options = { expanded: true }) {
36995
37155
  if (context.isError)
36996
37156
  return renderErrorResult(result, "conflicts failed", theme, context);
36997
- return renderConflictResult(collectTextContent(result), theme, context);
37157
+ return renderConflictResult(collectTextContent(result), theme, context, options);
36998
37158
  }
36999
37159
  function registerConflictsTool(pi, ctx) {
37000
37160
  pi.registerTool(withPathAliasPreparation({
@@ -37021,8 +37181,8 @@ function registerConflictsTool(pi, ctx) {
37021
37181
  renderCall(_args, theme, context) {
37022
37182
  return renderConflictCall(theme, context);
37023
37183
  },
37024
- renderResult(result, _options, theme, context) {
37025
- return renderConflictToolResult(result, theme, context);
37184
+ renderResult(result, options = { expanded: false, isPartial: false }, theme, context) {
37185
+ return renderConflictToolResult(result, theme, context, options);
37026
37186
  }
37027
37187
  }));
37028
37188
  }
@@ -37065,7 +37225,7 @@ function renderFsCall(toolName, args, theme, context) {
37065
37225
  const moveArgs = args;
37066
37226
  return renderToolCall("move", `${accentPath(theme, moveArgs.path)} ${theme.fg("muted", "→")} ${accentPath(theme, moveArgs.destination)}`, theme, context);
37067
37227
  }
37068
- function renderFsResult(toolName, args, result, theme, context) {
37228
+ function renderFsResult(toolName, args, result, theme, context, options = { expanded: true }) {
37069
37229
  if (context.isError) {
37070
37230
  return renderErrorResult(result, `${toolName} failed`, theme, context);
37071
37231
  }
@@ -37076,22 +37236,33 @@ function renderFsResult(toolName, args, result, theme, context) {
37076
37236
  const skipped = data.skipped_files ?? [];
37077
37237
  const lines = [];
37078
37238
  for (const entry of deletedPaths) {
37079
- lines.push(`${theme.fg("success", "✓ deleted")} ${theme.fg("accent", shortenPath3(entry))}`);
37239
+ lines.push(`${theme.fg("success", "✓ deleted")} ${theme.fg("accent", shortenPath2(entry))}`);
37080
37240
  }
37081
37241
  for (const entry of skipped) {
37082
- lines.push(`${theme.fg("error", "✗ skipped")} ${theme.fg("accent", shortenPath3(entry.file))} ${theme.fg("muted", `(${entry.reason})`)}`);
37242
+ lines.push(`${theme.fg("error", "✗ skipped")} ${theme.fg("accent", shortenPath2(entry.file))} ${theme.fg("muted", `(${entry.reason})`)}`);
37083
37243
  }
37084
37244
  if (lines.length === 0) {
37085
37245
  lines.push(theme.fg("muted", "(no files deleted)"));
37086
37246
  }
37087
- return renderSections([lines.join(`
37088
- `)], context);
37247
+ return collapsibleResult({
37248
+ summary: `${deletedPaths.length} deleted, ${skipped.length} skipped`,
37249
+ full: renderSections([lines.join(`
37250
+ `)], context),
37251
+ expanded: options.expanded,
37252
+ context
37253
+ });
37089
37254
  }
37090
37255
  const moveArgs = args;
37091
- return renderSections([
37092
- `${theme.fg("success", "✓ moved")} ${theme.fg("accent", shortenPath3(moveArgs.path ?? ""))}`,
37093
- `${theme.fg("muted", "to")} ${theme.fg("accent", shortenPath3(moveArgs.destination))}`
37094
- ], context);
37256
+ const sections = [
37257
+ `${theme.fg("success", "✓ moved")} ${theme.fg("accent", shortenPath2(moveArgs.path ?? ""))}`,
37258
+ `${theme.fg("muted", "to")} ${theme.fg("accent", shortenPath2(moveArgs.destination))}`
37259
+ ];
37260
+ return collapsibleResult({
37261
+ summary: `moved ${shortenPath2(moveArgs.path ?? "")} to ${shortenPath2(moveArgs.destination)}`,
37262
+ full: renderSections(sections, context),
37263
+ expanded: options.expanded,
37264
+ context
37265
+ });
37095
37266
  }
37096
37267
  function registerFsTools(pi, ctx, surface) {
37097
37268
  const backupsDisabled = ctx.config.backup?.enabled === false;
@@ -37137,8 +37308,8 @@ function registerFsTools(pi, ctx, surface) {
37137
37308
  renderCall(args, theme, context) {
37138
37309
  return renderFsCall("aft_delete", args, theme, context);
37139
37310
  },
37140
- renderResult(result, _options, theme, context) {
37141
- return renderFsResult("aft_delete", context.args, result, theme, context);
37311
+ renderResult(result, options = { expanded: false, isPartial: false }, theme, context) {
37312
+ return renderFsResult("aft_delete", context.args, result, theme, context, options);
37142
37313
  }
37143
37314
  }));
37144
37315
  }
@@ -37170,8 +37341,8 @@ function registerFsTools(pi, ctx, surface) {
37170
37341
  renderCall(args, theme, context) {
37171
37342
  return renderFsCall("aft_move", args, theme, context);
37172
37343
  },
37173
- renderResult(result, _options, theme, context) {
37174
- return renderFsResult("aft_move", context.args, result, theme, context);
37344
+ renderResult(result, options = { expanded: false, isPartial: false }, theme, context) {
37345
+ return renderFsResult("aft_move", context.args, result, theme, context, options);
37175
37346
  }
37176
37347
  }));
37177
37348
  }
@@ -37245,11 +37416,17 @@ function renderImportCall(args, theme, context) {
37245
37416
  ].filter(Boolean).join(" ");
37246
37417
  return renderToolCall("import", summary, theme, context);
37247
37418
  }
37248
- function renderImportResult(result, args, theme, context) {
37419
+ function renderImportResult(result, args, theme, context, options = { expanded: true }) {
37249
37420
  if (context.isError)
37250
37421
  return renderErrorResult(result, "import failed", theme, context);
37251
37422
  const payload = extractStructuredPayload(result);
37252
- return renderSections(buildImportSections(args, payload, theme), context);
37423
+ const sections = buildImportSections(args, payload, theme);
37424
+ return collapsibleResult({
37425
+ summary: sections[0] ?? `${args.op} import completed`,
37426
+ full: renderSections(sections, context),
37427
+ expanded: options.expanded,
37428
+ context
37429
+ });
37253
37430
  }
37254
37431
  function registerImportTools(pi, ctx) {
37255
37432
  const organizeRecovery = ctx.config.backup?.enabled === false ? "Backup capture is disabled by user config; review broad cleanup changes before proceeding." : "Use aft_safety checkpoint/undo before broad cleanup.";
@@ -37297,8 +37474,8 @@ function registerImportTools(pi, ctx) {
37297
37474
  renderCall(args, theme, context) {
37298
37475
  return renderImportCall(args, theme, context);
37299
37476
  },
37300
- renderResult(result, _options, theme, context) {
37301
- return renderImportResult(result, context.args, theme, context);
37477
+ renderResult(result, options = { expanded: false, isPartial: false }, theme, context) {
37478
+ return renderImportResult(result, context.args, theme, context, options);
37302
37479
  }
37303
37480
  }));
37304
37481
  }
@@ -37582,14 +37759,18 @@ function renderInspectCall(args, theme, context) {
37582
37759
  const summary = [sections, scope, args.topK ? `topK=${args.topK}` : undefined].filter(Boolean).join(" ");
37583
37760
  return renderToolCall("inspect", summary ? theme.fg("toolOutput", summary) : undefined, theme, context);
37584
37761
  }
37585
- function renderInspectResult(result, theme, context) {
37762
+ function renderInspectResult(result, theme, context, options = { expanded: true }) {
37586
37763
  const payload = extractStructuredPayload(result);
37587
37764
  const terminal = parseInspectTerminal(payload);
37588
- if (terminal)
37589
- return renderSections([renderInspectTerminal(terminal, asString2(asRecord3(payload)?.text))], context);
37590
37765
  if (context.isError)
37591
37766
  return renderErrorResult(result, "inspect failed", theme, context);
37592
- return renderSections(buildInspectSections(payload, theme), context);
37767
+ const sections = terminal ? [renderInspectTerminal(terminal, asString2(asRecord3(payload)?.text))] : buildInspectSections(payload, theme);
37768
+ return collapsibleResult({
37769
+ summary: sections[0] ?? "inspect completed",
37770
+ full: renderSections(sections, context),
37771
+ expanded: options.expanded,
37772
+ context
37773
+ });
37593
37774
  }
37594
37775
  function registerInspectTool(pi, ctx) {
37595
37776
  pi.registerTool({
@@ -37624,8 +37805,8 @@ function registerInspectTool(pi, ctx) {
37624
37805
  renderCall(args, theme, context) {
37625
37806
  return renderInspectCall(args, theme, context);
37626
37807
  },
37627
- renderResult(result, _options, theme, context) {
37628
- return renderInspectResult(result, theme, context);
37808
+ renderResult(result, options = { expanded: false, isPartial: false }, theme, context) {
37809
+ return renderInspectResult(result, theme, context, options);
37629
37810
  }
37630
37811
  });
37631
37812
  }
@@ -37676,10 +37857,16 @@ function renderNavigateCall(args, theme, context) {
37676
37857
  ].filter(Boolean).join(" ");
37677
37858
  return renderToolCall("callgraph", summary, theme, context);
37678
37859
  }
37679
- function renderNavigateResult(result, args, theme, context) {
37860
+ function renderNavigateResult(result, args, theme, context, options = { expanded: true }) {
37680
37861
  if (context.isError)
37681
37862
  return renderErrorResult(result, "navigate failed", theme, context);
37682
- return renderSections(buildNavigateSections(args, extractStructuredPayload(result), theme), context);
37863
+ const sections = buildNavigateSections(args, extractStructuredPayload(result), theme);
37864
+ return collapsibleResult({
37865
+ summary: sections[0] ?? `${args.op} completed`,
37866
+ full: renderSections(sections, context),
37867
+ expanded: options.expanded,
37868
+ context
37869
+ });
37683
37870
  }
37684
37871
  function registerNavigateTool(pi, ctx) {
37685
37872
  pi.registerTool(withPathAliasPreparation({
@@ -37747,8 +37934,8 @@ function registerNavigateTool(pi, ctx) {
37747
37934
  renderCall(args, theme, context) {
37748
37935
  return renderNavigateCall(args, theme, context);
37749
37936
  },
37750
- renderResult(result, _options, theme, context) {
37751
- return renderNavigateResult(result, context.args, theme, context);
37937
+ renderResult(result, options = { expanded: false, isPartial: false }, theme, context) {
37938
+ return renderNavigateResult(result, context.args, theme, context, options);
37752
37939
  }
37753
37940
  }));
37754
37941
  }
@@ -37825,7 +38012,7 @@ function renderZoomRecord(record2, targetLabel, theme) {
37825
38012
  const range = asRecord3(record2.range);
37826
38013
  const startLine = range && typeof range.start_line === "number" ? range.start_line : undefined;
37827
38014
  const endLine = range && typeof range.end_line === "number" ? range.end_line : undefined;
37828
- const location = startLine !== undefined ? `${shortenPath3(targetLabel)}:${startLine}${endLine && endLine !== startLine ? `-${endLine}` : ""}` : shortenPath3(targetLabel);
38015
+ const location = startLine !== undefined ? `${shortenPath2(targetLabel)}:${startLine}${endLine && endLine !== startLine ? `-${endLine}` : ""}` : shortenPath2(targetLabel);
37829
38016
  const lines = [`${theme.fg("accent", name)} ${theme.fg("muted", `[${kind}] ${location}`)}`];
37830
38017
  const content = asString2(record2.content);
37831
38018
  if (content) {
@@ -37862,7 +38049,7 @@ function buildZoomSections(args, payload, theme) {
37862
38049
  const name = asString2(record2.name) ?? asString2(response.name) ?? "(unknown symbol)";
37863
38050
  const itemTargetLabel = asString2(record2.targetLabel) ?? zoomTargetLabel(args);
37864
38051
  if (response.success === false || record2.success === false) {
37865
- const location = record2.targetLabel ? ` in ${shortenPath3(itemTargetLabel)}` : "";
38052
+ const location = record2.targetLabel ? ` in ${shortenPath2(itemTargetLabel)}` : "";
37866
38053
  return theme.fg("error", `Symbol "${name}" not found${location}: ${asString2(response.message) ?? asString2(record2.error) ?? "zoom failed"}`);
37867
38054
  }
37868
38055
  if (record2.response) {
@@ -37871,7 +38058,7 @@ function buildZoomSections(args, payload, theme) {
37871
38058
  const content = asString2(record2.content);
37872
38059
  if (content) {
37873
38060
  return [
37874
- `${theme.fg("accent", name)} ${theme.fg("muted", shortenPath3(itemTargetLabel))}`,
38061
+ `${theme.fg("accent", name)} ${theme.fg("muted", shortenPath2(itemTargetLabel))}`,
37875
38062
  content
37876
38063
  ].filter(Boolean).join(`
37877
38064
  `);
@@ -37894,10 +38081,16 @@ function renderOutlineCall(args, theme, context) {
37894
38081
  const summary = Array.isArray(args.target) ? theme.fg("accent", `${args.target.length} ${args.files ? "directories" : "files"}`) : typeof args.target === "string" ? `${accentPath(theme, args.target)}${args.files ? " files" : ""}` : undefined;
37895
38082
  return renderToolCall("outline", summary, theme, context);
37896
38083
  }
37897
- function renderOutlineResult(result, theme, context) {
38084
+ function renderOutlineResult(result, theme, context, options = { expanded: true }) {
37898
38085
  if (context.isError)
37899
38086
  return renderErrorResult(result, "outline failed", theme, context);
37900
- return renderSections(buildOutlineSections(collectTextContent(result), theme), context);
38087
+ const sections = buildOutlineSections(collectTextContent(result), theme);
38088
+ return collapsibleResult({
38089
+ summary: sections[0] ?? "outline completed",
38090
+ full: renderSections(sections, context),
38091
+ expanded: options.expanded,
38092
+ context
38093
+ });
37901
38094
  }
37902
38095
  function renderZoomCall(args, theme, context) {
37903
38096
  const symbols = args.symbols;
@@ -37916,10 +38109,16 @@ function renderZoomCall(args, theme, context) {
37916
38109
  }
37917
38110
  return renderToolCall("zoom", `${accentPath(theme, zoomTargetLabel(args))} ${summary}`, theme, context);
37918
38111
  }
37919
- function renderZoomResult(result, args, theme, context) {
38112
+ function renderZoomResult(result, args, theme, context, options = { expanded: true }) {
37920
38113
  if (context.isError)
37921
38114
  return renderErrorResult(result, "zoom failed", theme, context);
37922
- return renderSections(buildZoomSections(args, extractStructuredPayload(result), theme), context);
38115
+ const sections = buildZoomSections(args, extractStructuredPayload(result), theme);
38116
+ return collapsibleResult({
38117
+ summary: sections[0] ?? "zoom completed",
38118
+ full: renderSections(sections, context),
38119
+ expanded: options.expanded,
38120
+ context
38121
+ });
37923
38122
  }
37924
38123
  function registerReadingTools(pi, ctx, surface) {
37925
38124
  if (surface.outline) {
@@ -37981,8 +38180,8 @@ Pass a single \`target\`:
37981
38180
  renderCall(args, theme, context) {
37982
38181
  return renderOutlineCall(args, theme, context);
37983
38182
  },
37984
- renderResult(result, _options, theme, context) {
37985
- return renderOutlineResult(result, theme, context);
38183
+ renderResult(result, options = { expanded: false, isPartial: false }, theme, context) {
38184
+ return renderOutlineResult(result, theme, context, options);
37986
38185
  }
37987
38186
  }));
37988
38187
  }
@@ -38073,8 +38272,8 @@ Pass a single \`target\`:
38073
38272
  renderCall(args, theme, context) {
38074
38273
  return renderZoomCall(args, theme, context);
38075
38274
  },
38076
- renderResult(result, _options, theme, context) {
38077
- return renderZoomResult(result, context.args, theme, context);
38275
+ renderResult(result, options = { expanded: false, isPartial: false }, theme, context) {
38276
+ return renderZoomResult(result, context.args, theme, context, options);
38078
38277
  }
38079
38278
  }));
38080
38279
  }
@@ -38106,21 +38305,21 @@ function buildRefactorSections(args, payload, theme) {
38106
38305
  `${theme.fg("success", "moved symbol")} ${theme.fg("toolOutput", args.symbol ?? "(symbol)")}`,
38107
38306
  `${theme.fg("muted", "files modified")} ${asNumber2(response.files_modified) ?? results.length}`,
38108
38307
  `${theme.fg("muted", "consumers updated")} ${asNumber2(response.consumers_updated) ?? 0}`,
38109
- results.length > 0 ? results.map((entry) => ` ↳ ${shortenPath3(asString2(entry.file) ?? "(unknown file)")}`).join(`
38308
+ results.length > 0 ? results.map((entry) => ` ↳ ${shortenPath2(asString2(entry.file) ?? "(unknown file)")}`).join(`
38110
38309
  `) : theme.fg("muted", "No files reported.")
38111
38310
  ];
38112
38311
  }
38113
38312
  if (args.op === "extract") {
38114
38313
  return [
38115
38314
  `${theme.fg("success", "extracted")} ${theme.fg("toolOutput", asString2(response.name) ?? args.name ?? "(function)")}`,
38116
- `${theme.fg("muted", "file")} ${theme.fg("accent", shortenPath3(asString2(response.file) ?? args.path ?? ""))}`,
38315
+ `${theme.fg("muted", "file")} ${theme.fg("accent", shortenPath2(asString2(response.file) ?? args.path ?? ""))}`,
38117
38316
  `${theme.fg("muted", "params")} ${Array.isArray(response.parameters) ? response.parameters.join(", ") || "none" : "none"}`,
38118
38317
  `${theme.fg("muted", "return type")} ${asString2(response.return_type) ?? "unknown"}`
38119
38318
  ];
38120
38319
  }
38121
38320
  return [
38122
38321
  `${theme.fg("success", "inlined")} ${theme.fg("toolOutput", asString2(response.symbol) ?? args.symbol ?? "(symbol)")}`,
38123
- `${theme.fg("muted", "file")} ${theme.fg("accent", shortenPath3(asString2(response.file) ?? args.path ?? ""))}`,
38322
+ `${theme.fg("muted", "file")} ${theme.fg("accent", shortenPath2(asString2(response.file) ?? args.path ?? ""))}`,
38124
38323
  `${theme.fg("muted", "context")} ${asString2(response.call_context) ?? "unknown"}`,
38125
38324
  `${theme.fg("muted", "substitutions")} ${asNumber2(response.substitutions) ?? 0}`
38126
38325
  ];
@@ -38133,10 +38332,16 @@ function renderRefactorCall(args, theme, context) {
38133
38332
  ].filter(Boolean).join(" ");
38134
38333
  return renderToolCall("refactor", summary, theme, context);
38135
38334
  }
38136
- function renderRefactorResult(result, args, theme, context) {
38335
+ function renderRefactorResult(result, args, theme, context, options = { expanded: true }) {
38137
38336
  if (context.isError)
38138
38337
  return renderErrorResult(result, "refactor failed", theme, context);
38139
- return renderSections(buildRefactorSections(args, extractStructuredPayload(result), theme), context);
38338
+ const sections = buildRefactorSections(args, extractStructuredPayload(result), theme);
38339
+ return collapsibleResult({
38340
+ summary: sections[0] ?? `${args.op} completed`,
38341
+ full: renderSections(sections, context),
38342
+ expanded: options.expanded,
38343
+ context
38344
+ });
38140
38345
  }
38141
38346
  function registerRefactorTool(pi, ctx) {
38142
38347
  pi.registerTool(withPathAliasPreparation({
@@ -38203,8 +38408,8 @@ function registerRefactorTool(pi, ctx) {
38203
38408
  renderCall(args, theme, context) {
38204
38409
  return renderRefactorCall(args, theme, context);
38205
38410
  },
38206
- renderResult(result, _options, theme, context) {
38207
- return renderRefactorResult(result, context.args, theme, context);
38411
+ renderResult(result, options = { expanded: false, isPartial: false }, theme, context) {
38412
+ return renderRefactorResult(result, context.args, theme, context, options);
38208
38413
  }
38209
38414
  }));
38210
38415
  }
@@ -38239,14 +38444,14 @@ function buildSafetySections(args, payload, theme) {
38239
38444
  ];
38240
38445
  }
38241
38446
  return [
38242
- `${theme.fg("success", "restored")} ${theme.fg("accent", shortenPath3(asString2(response.path) ?? args.path ?? "(file)"))}`,
38447
+ `${theme.fg("success", "restored")} ${theme.fg("accent", shortenPath2(asString2(response.path) ?? args.path ?? "(file)"))}`,
38243
38448
  `${theme.fg("muted", "backup")} ${asString2(response.backup_id) ?? "—"}`
38244
38449
  ];
38245
38450
  }
38246
38451
  if (args.op === "history") {
38247
38452
  const entries = asRecords2(response.entries);
38248
38453
  const sections2 = [
38249
- theme.fg("accent", shortenPath3(asString2(response.file) ?? args.path ?? "(file)"))
38454
+ theme.fg("accent", shortenPath2(asString2(response.file) ?? args.path ?? "(file)"))
38250
38455
  ];
38251
38456
  if (entries.length === 0) {
38252
38457
  sections2.push(theme.fg("muted", "No history entries."));
@@ -38268,7 +38473,7 @@ function buildSafetySections(args, payload, theme) {
38268
38473
  `${theme.fg("success", "checkpoint created")} ${theme.fg("accent", asString2(response.name) ?? args.name ?? "(checkpoint)")}`,
38269
38474
  `${theme.fg("muted", "files")} ${asNumber2(response.file_count) ?? 0}`,
38270
38475
  skipped.length > 0 ? `${theme.fg("warning", "skipped")}
38271
- ${skipped.map((entry) => ` ↳ ${shortenPath3(asString2(entry.file) ?? "(file)")}: ${asString2(entry.error) ?? "unknown error"}`).join(`
38476
+ ${skipped.map((entry) => ` ↳ ${shortenPath2(asString2(entry.file) ?? "(file)")}: ${asString2(entry.error) ?? "unknown error"}`).join(`
38272
38477
  `)}` : theme.fg("muted", "No skipped files.")
38273
38478
  ];
38274
38479
  }
@@ -38300,10 +38505,16 @@ function renderSafetyCall(args, theme, context) {
38300
38505
  const summary = [theme.fg("accent", args.op), target ? accentPath(theme, target) : undefined].filter(Boolean).join(" ");
38301
38506
  return renderToolCall("safety", summary, theme, context);
38302
38507
  }
38303
- function renderSafetyResult(result, args, theme, context) {
38508
+ function renderSafetyResult(result, args, theme, context, options = { expanded: true }) {
38304
38509
  if (context.isError)
38305
38510
  return renderErrorResult(result, "safety failed", theme, context);
38306
- return renderSections(buildSafetySections(args, extractStructuredPayload(result), theme), context);
38511
+ const sections = buildSafetySections(args, extractStructuredPayload(result), theme);
38512
+ return collapsibleResult({
38513
+ summary: `${args.op}: ${sections[0] ?? "completed"}`,
38514
+ full: renderSections(sections, context),
38515
+ expanded: options.expanded,
38516
+ context
38517
+ });
38307
38518
  }
38308
38519
  function registerSafetyTool(pi, ctx) {
38309
38520
  pi.registerTool(withPathAliasPreparation({
@@ -38372,8 +38583,8 @@ function registerSafetyTool(pi, ctx) {
38372
38583
  renderCall(args, theme, context) {
38373
38584
  return renderSafetyCall(args, theme, context);
38374
38585
  },
38375
- renderResult(result, _options, theme, context) {
38376
- return renderSafetyResult(result, context.args, theme, context);
38586
+ renderResult(result, options = { expanded: false, isPartial: false }, theme, context) {
38587
+ return renderSafetyResult(result, context.args, theme, context, options);
38377
38588
  }
38378
38589
  }));
38379
38590
  }
@@ -38440,7 +38651,7 @@ function buildSemanticSections(args, payload, theme) {
38440
38651
  }
38441
38652
  const grouped = groupByFile(results, (result) => asString2(result.file));
38442
38653
  for (const [file2, fileResults] of grouped.entries()) {
38443
- const lines = [theme.fg("accent", shortenPath3(file2))];
38654
+ const lines = [theme.fg("accent", shortenPath2(file2))];
38444
38655
  fileResults.forEach((result) => {
38445
38656
  if (asString2(result.kind) === "GrepLine") {
38446
38657
  const line = asNumber2(result.line);
@@ -38487,10 +38698,16 @@ function buildSemanticSections(args, payload, theme) {
38487
38698
  function renderSemanticCall(args, theme, context) {
38488
38699
  return renderToolCall("search", theme.fg("toolOutput", args.query), theme, context);
38489
38700
  }
38490
- function renderSemanticResult(result, args, theme, context) {
38701
+ function renderSemanticResult(result, args, theme, context, options = { expanded: true }) {
38491
38702
  if (context.isError)
38492
38703
  return renderErrorResult(result, "search failed", theme, context);
38493
- return renderSections(buildSemanticSections(args, extractStructuredPayload(result), theme), context);
38704
+ const sections = buildSemanticSections(args, extractStructuredPayload(result), theme);
38705
+ return collapsibleResult({
38706
+ summary: sections[0] ?? "search completed",
38707
+ full: renderSections(sections, context),
38708
+ expanded: options.expanded,
38709
+ context
38710
+ });
38494
38711
  }
38495
38712
  function registerSemanticTool(pi, ctx) {
38496
38713
  pi.registerTool(withPathAliasPreparation({
@@ -38522,8 +38739,8 @@ function registerSemanticTool(pi, ctx) {
38522
38739
  renderCall(args, theme, context) {
38523
38740
  return renderSemanticCall(args, theme, context);
38524
38741
  },
38525
- renderResult(result, _options, theme, context) {
38526
- return renderSemanticResult(result, context.args, theme, context);
38742
+ renderResult(result, options = { expanded: false, isPartial: false }, theme, context) {
38743
+ return renderSemanticResult(result, context.args, theme, context, options);
38527
38744
  }
38528
38745
  }));
38529
38746
  }
@@ -38533,12 +38750,15 @@ var ALL_ONLY_TOOLS = new Set(["aft_callgraph", "aft_delete", "aft_move", "aft_re
38533
38750
  function resolvePiToolSurface(config2) {
38534
38751
  const surface = config2.tool_surface ?? "recommended";
38535
38752
  const disabled = new Set(config2.disabled_tools ?? []);
38753
+ const hoistBuiltinTools = config2.hoist_builtin_tools !== false;
38536
38754
  const ok = (name) => !disabled.has(name);
38755
+ const builtinToolEnabled = (bareName) => ok(hoistBuiltinTools ? bareName : `aft_${bareName}`);
38537
38756
  const allOnly = (name) => ALL_ONLY_TOOLS.has(name) && ok(name);
38538
38757
  const restrictToProjectRoot = config2.restrict_to_project_root ?? false;
38539
38758
  if (surface === "minimal") {
38540
38759
  return {
38541
- hoistBash: ok("bash"),
38760
+ hoistBuiltinTools,
38761
+ hoistBash: builtinToolEnabled("bash"),
38542
38762
  hoistRead: false,
38543
38763
  hoistWrite: false,
38544
38764
  hoistEdit: false,
@@ -38560,11 +38780,12 @@ function resolvePiToolSurface(config2) {
38560
38780
  };
38561
38781
  }
38562
38782
  const base = {
38563
- hoistBash: ok("bash"),
38564
- hoistRead: ok("read"),
38565
- hoistWrite: ok("write"),
38566
- hoistEdit: ok("edit"),
38567
- hoistGrep: ok("grep") && config2.search_index === true,
38783
+ hoistBuiltinTools,
38784
+ hoistBash: builtinToolEnabled("bash"),
38785
+ hoistRead: builtinToolEnabled("read"),
38786
+ hoistWrite: builtinToolEnabled("write"),
38787
+ hoistEdit: builtinToolEnabled("edit"),
38788
+ hoistGrep: builtinToolEnabled("grep") && config2.search_index === true,
38568
38789
  restrictToProjectRoot,
38569
38790
  outline: ok("aft_outline"),
38570
38791
  zoom: ok("aft_zoom"),
@@ -38593,7 +38814,8 @@ function resolvePiToolSurface(config2) {
38593
38814
  }
38594
38815
  function registerPiToolSurface(pi, ctx, surface) {
38595
38816
  if (surface.hoistBash && resolveBashConfig(ctx.config).enabled) {
38596
- registerBashTool(pi, ctx, surface.semantic);
38817
+ registerBashTool(pi, ctx, surface.semantic, surface.hoistBuiltinTools ? "bash" : "aft_bash", false);
38818
+ registerBashCompanionTools(pi, ctx);
38597
38819
  }
38598
38820
  registerHoistedTools(pi, ctx, surface);
38599
38821
  if (surface.outline || surface.zoom) {
@@ -38704,6 +38926,9 @@ function buildHintsFromConfig(config2, absentTools, hoistBuiltins) {
38704
38926
  });
38705
38927
  }
38706
38928
  function registerWorkflowHints(pi, config2, surface) {
38929
+ const hoistBuiltins = surface.hoistBuiltinTools;
38930
+ const grepName = hoistBuiltins ? "grep" : "aft_grep";
38931
+ const bashName = hoistBuiltins ? "bash" : "aft_bash";
38707
38932
  const absent = new Set;
38708
38933
  if (!surface.outline)
38709
38934
  absent.add("aft_outline");
@@ -38716,12 +38941,12 @@ function registerWorkflowHints(pi, config2, surface) {
38716
38941
  if (!surface.inspect)
38717
38942
  absent.add("aft_inspect");
38718
38943
  if (!surface.hoistGrep)
38719
- absent.add("grep");
38944
+ absent.add(grepName);
38720
38945
  if (!surface.hoistBash) {
38721
- absent.add("bash");
38946
+ absent.add(bashName);
38722
38947
  absent.add("bash_status");
38723
38948
  }
38724
- const hintsBlock = buildHintsFromConfig(config2, absent, true);
38949
+ const hintsBlock = buildHintsFromConfig(config2, absent, hoistBuiltins);
38725
38950
  if (!hintsBlock)
38726
38951
  return;
38727
38952
  log2(`Workflow hints injected (${hintsBlock.length} chars)`);
@@ -38778,7 +39003,7 @@ var PLUGIN_VERSION = (() => {
38778
39003
  return "0.0.0";
38779
39004
  }
38780
39005
  })();
38781
- var ANNOUNCEMENT_VERSION = "0.52.1";
39006
+ var ANNOUNCEMENT_VERSION = "0.53.0";
38782
39007
  var ANNOUNCEMENT_FEATURES = [
38783
39008
  "Callgraph stays consistent under load: edits arriving mid-rebuild can no longer be silently lost from navigation results.",
38784
39009
  "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).",
@@ -39129,7 +39354,7 @@ ${lines}
39129
39354
  if (onnxRuntimePromise) {
39130
39355
  await Promise.race([
39131
39356
  onnxRuntimePromise,
39132
- new Promise((resolve9) => setTimeout(() => resolve9(null), 60000))
39357
+ new Promise((resolve10) => setTimeout(() => resolve10(null), 60000))
39133
39358
  ]);
39134
39359
  }
39135
39360
  const bridge = pool.getBridge(cwd);