@cortexkit/aft-pi 0.50.3 → 0.51.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -8325,6 +8325,120 @@ function skipSpaces(input, start) {
8325
8325
  index++;
8326
8326
  return index;
8327
8327
  }
8328
+ // ../aft-bridge/dist/bash-host-fallback.js
8329
+ import { spawn } from "node:child_process";
8330
+ var BASH_HOST_FALLBACK_BANNER = "[AFT host fallback - module transport down; no rewrites/compression/background]";
8331
+ var BASH_HOST_FALLBACK_MAX_OUTPUT_BYTES = 100 * 1024;
8332
+ var BASH_HOST_FALLBACK_MAX_TIMEOUT_MS = 10 * 60 * 1000;
8333
+ var BASH_HOST_FALLBACK_REFUSAL = "AFT transport is down; only foreground execution is available in host fallback";
8334
+ function bashHostFallbackAskPattern(command, cwd) {
8335
+ return `AFT UNAVAILABLE - host fallback execution:
8336
+
8337
+ Exact command:
8338
+ ${command}
8339
+
8340
+ Working directory:
8341
+ ${cwd}`;
8342
+ }
8343
+ function appendTail(chunks, chunk) {
8344
+ const combined = Buffer.concat([...chunks, chunk]);
8345
+ if (combined.byteLength <= BASH_HOST_FALLBACK_MAX_OUTPUT_BYTES) {
8346
+ return { chunks: [combined], truncated: false };
8347
+ }
8348
+ return {
8349
+ chunks: [combined.subarray(combined.byteLength - BASH_HOST_FALLBACK_MAX_OUTPUT_BYTES)],
8350
+ truncated: true
8351
+ };
8352
+ }
8353
+ function renderOutput(output, exitCode) {
8354
+ const body = output.toString("utf8");
8355
+ const separator = body.length === 0 || body.endsWith(`
8356
+ `) ? "" : `
8357
+ `;
8358
+ return `${BASH_HOST_FALLBACK_BANNER}
8359
+ ${body}${separator}[exit code: ${exitCode}]`;
8360
+ }
8361
+ async function runBashHostFallback(options) {
8362
+ const timeoutMs = Math.min(Math.max(1, options.timeoutMs ?? BASH_HOST_FALLBACK_MAX_TIMEOUT_MS), BASH_HOST_FALLBACK_MAX_TIMEOUT_MS);
8363
+ if (options.signal?.aborted) {
8364
+ throw new DOMException("The host fallback command was aborted", "AbortError");
8365
+ }
8366
+ return await new Promise((resolve2, reject) => {
8367
+ const child = spawn(options.command, {
8368
+ cwd: options.projectRoot,
8369
+ shell: true,
8370
+ env: { ...process.env, ...options.env },
8371
+ stdio: ["ignore", "pipe", "pipe"],
8372
+ detached: process.platform !== "win32",
8373
+ windowsHide: true
8374
+ });
8375
+ let chunks = [];
8376
+ let truncated = false;
8377
+ let timedOut = false;
8378
+ let aborted = false;
8379
+ let settled = false;
8380
+ let abortForceTimer;
8381
+ const capture = (chunk) => {
8382
+ const next = appendTail(chunks, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
8383
+ chunks = next.chunks;
8384
+ truncated ||= next.truncated;
8385
+ };
8386
+ child.stdout?.on("data", capture);
8387
+ child.stderr?.on("data", capture);
8388
+ const kill = (signal) => {
8389
+ if (child.exitCode !== null || child.signalCode !== null)
8390
+ return;
8391
+ if (process.platform !== "win32" && child.pid !== undefined) {
8392
+ try {
8393
+ process.kill(-child.pid, signal);
8394
+ return;
8395
+ } catch {}
8396
+ }
8397
+ child.kill(signal);
8398
+ };
8399
+ const onAbort = () => {
8400
+ aborted = true;
8401
+ kill("SIGTERM");
8402
+ abortForceTimer = setTimeout(() => kill("SIGKILL"), 250);
8403
+ abortForceTimer.unref?.();
8404
+ };
8405
+ options.signal?.addEventListener("abort", onAbort, { once: true });
8406
+ const timer = setTimeout(() => {
8407
+ timedOut = true;
8408
+ kill("SIGKILL");
8409
+ }, timeoutMs);
8410
+ const cleanup = () => {
8411
+ clearTimeout(timer);
8412
+ if (abortForceTimer !== undefined)
8413
+ clearTimeout(abortForceTimer);
8414
+ options.signal?.removeEventListener("abort", onAbort);
8415
+ };
8416
+ child.once("error", (error2) => {
8417
+ if (settled)
8418
+ return;
8419
+ settled = true;
8420
+ cleanup();
8421
+ reject(error2);
8422
+ });
8423
+ child.once("close", (code) => {
8424
+ if (settled)
8425
+ return;
8426
+ settled = true;
8427
+ cleanup();
8428
+ if (aborted) {
8429
+ reject(new DOMException("The host fallback command was aborted", "AbortError"));
8430
+ return;
8431
+ }
8432
+ const exitCode = timedOut ? 124 : code ?? 1;
8433
+ resolve2({
8434
+ success: true,
8435
+ output: renderOutput(Buffer.concat(chunks), exitCode),
8436
+ exit_code: exitCode,
8437
+ truncated
8438
+ });
8439
+ });
8440
+ });
8441
+ }
8328
8442
  // ../aft-bridge/dist/bash-timeout.js
8329
8443
  function resolveBashKillTimeout(modelTimeout, foregroundWaitMs) {
8330
8444
  if (modelTimeout !== undefined && modelTimeout >= foregroundWaitMs) {
@@ -8333,7 +8447,7 @@ function resolveBashKillTimeout(modelTimeout, foregroundWaitMs) {
8333
8447
  return;
8334
8448
  }
8335
8449
  // ../aft-bridge/dist/bridge.js
8336
- import { spawn } from "node:child_process";
8450
+ import { spawn as spawn2 } from "node:child_process";
8337
8451
  import { createHash } from "node:crypto";
8338
8452
  import { readFileSync } from "node:fs";
8339
8453
  import { homedir as homedir2 } from "node:os";
@@ -8363,51 +8477,6 @@ function isPassiveCommand(command) {
8363
8477
  return PASSIVE_COMMANDS.has(command);
8364
8478
  }
8365
8479
 
8366
- // ../aft-bridge/dist/status-bar.js
8367
- var STATUS_BAR_HEARTBEAT_CALLS = 15;
8368
- function createStatusBarEmitState() {
8369
- return { callsSinceEmit: 0 };
8370
- }
8371
- function parseStatusBarCounts(value) {
8372
- if (!value || typeof value !== "object")
8373
- return;
8374
- const record = value;
8375
- const num = (key) => {
8376
- const raw = record[key];
8377
- return typeof raw === "number" && Number.isFinite(raw) ? raw : 0;
8378
- };
8379
- return {
8380
- errors: num("errors"),
8381
- warnings: num("warnings"),
8382
- dead_code: num("dead_code"),
8383
- unused_exports: num("unused_exports"),
8384
- duplicates: num("duplicates"),
8385
- todos: num("todos"),
8386
- tier2_stale: record.tier2_stale === true,
8387
- ...typeof record.line === "string" ? { line: record.line } : {}
8388
- };
8389
- }
8390
- function countsEqual(a, b) {
8391
- return a.errors === b.errors && a.warnings === b.warnings && a.dead_code === b.dead_code && a.unused_exports === b.unused_exports && a.duplicates === b.duplicates && a.todos === b.todos && a.tier2_stale === b.tier2_stale && a.line === b.line;
8392
- }
8393
- function shouldEmitStatusBar(state, next) {
8394
- const changed = state.last === undefined || !countsEqual(state.last, next);
8395
- state.callsSinceEmit += 1;
8396
- const heartbeat = state.callsSinceEmit >= STATUS_BAR_HEARTBEAT_CALLS;
8397
- if (changed || heartbeat) {
8398
- state.last = next;
8399
- state.callsSinceEmit = 0;
8400
- return true;
8401
- }
8402
- return false;
8403
- }
8404
- function formatStatusBar(counts) {
8405
- if (counts.line !== undefined)
8406
- return counts.line;
8407
- const staleMark = counts.tier2_stale ? "~" : "";
8408
- return `[AFT E${counts.errors} W${counts.warnings} | ` + `${staleMark}D${counts.dead_code} U${counts.unused_exports} C${counts.duplicates} | ` + `T${counts.todos}]`;
8409
- }
8410
-
8411
8480
  // ../aft-bridge/dist/bridge.js
8412
8481
  var DEFAULT_BRIDGE_TIMEOUT_MS = 30000;
8413
8482
  var BRIDGE_HANG_TIMEOUT_THRESHOLD = 2;
@@ -8536,6 +8605,14 @@ class BridgeTransportTimeoutError extends Error {
8536
8605
  this.name = "BridgeTransportTimeoutError";
8537
8606
  }
8538
8607
  }
8608
+
8609
+ class BridgeTransportUnavailableError extends Error {
8610
+ code = "bridge_transport_unavailable";
8611
+ constructor(message, options) {
8612
+ super(message, options);
8613
+ this.name = "BridgeTransportUnavailableError";
8614
+ }
8615
+ }
8539
8616
  function isBridgeTransportTimeout(err) {
8540
8617
  return err instanceof Error && err.code === "transport_timeout";
8541
8618
  }
@@ -8578,7 +8655,6 @@ class BinaryBridge {
8578
8655
  configureWarningClients = new Map;
8579
8656
  restartResetTimer = null;
8580
8657
  lastChildActivityAt = 0;
8581
- lastStatusBar;
8582
8658
  consecutiveRequestTimeouts = 0;
8583
8659
  errorPrefix;
8584
8660
  logger;
@@ -8678,6 +8754,20 @@ class BinaryBridge {
8678
8754
  isAlive() {
8679
8755
  return this.process !== null && this.process.exitCode === null && !this.process.killed;
8680
8756
  }
8757
+ invalidateTransportProcess(error2) {
8758
+ const proc = this.process;
8759
+ if (!proc)
8760
+ return;
8761
+ this.process = null;
8762
+ this.spawnedBinaryFingerprint = null;
8763
+ if (proc.exitCode === null && !proc.killed) {
8764
+ proc.kill("SIGKILL");
8765
+ }
8766
+ this.clearRestartResetTimer();
8767
+ this.configured = false;
8768
+ this.outstandingBackgroundTaskIds.clear();
8769
+ this.rejectAllPending(error2);
8770
+ }
8681
8771
  hasPendingRequests() {
8682
8772
  return this.pending.size > 0;
8683
8773
  }
@@ -8779,7 +8869,7 @@ class BinaryBridge {
8779
8869
  throw new Error(`${this.errorPrefix} Bridge is retiring after the on-disk binary changed; retry to respawn on the updated binary`);
8780
8870
  }
8781
8871
  if (this._shuttingDown) {
8782
- throw new Error(`${this.errorPrefix} Bridge is shutting down, cannot send "${command}"`);
8872
+ throw new BridgeTransportUnavailableError(`${this.errorPrefix} Bridge is shutting down, cannot send "${command}"`);
8783
8873
  }
8784
8874
  if (Object.hasOwn(params, "id")) {
8785
8875
  throw new Error("params cannot contain reserved key 'id'");
@@ -8813,7 +8903,7 @@ class BinaryBridge {
8813
8903
  await this.deliverConfigureWarnings(configResult, params, options);
8814
8904
  await this.checkVersion(implicitTransportOptions);
8815
8905
  if (!this.isAlive()) {
8816
- throw new Error(`${this.errorPrefix} Bridge died during version check. Check logs: ${this.getLogFilePathVia()}`);
8906
+ throw new BridgeTransportUnavailableError(`${this.errorPrefix} Bridge died during version check. Check logs: ${this.getLogFilePathVia()}`);
8817
8907
  }
8818
8908
  this.configured = true;
8819
8909
  } finally {
@@ -8843,6 +8933,7 @@ class BinaryBridge {
8843
8933
  `;
8844
8934
  const keepBridgeOnTimeout = passive || options?.keepBridgeOnTimeout === true;
8845
8935
  let requestSentAt = Date.now();
8936
+ const child = this.process;
8846
8937
  const response = await new Promise((resolve2, reject) => {
8847
8938
  const timer = setTimeout(() => {
8848
8939
  const entry = this.pending.get(id);
@@ -8879,21 +8970,27 @@ class BinaryBridge {
8879
8970
  this.handleTimeout(requestSessionId);
8880
8971
  }, effectiveTimeoutMs);
8881
8972
  this.pending.set(id, { resolve: resolve2, reject, timer, onProgress: options?.onProgress, command });
8882
- if (!this.process?.stdin?.writable) {
8973
+ if (!child?.stdin?.writable) {
8883
8974
  this.pending.delete(id);
8884
8975
  clearTimeout(timer);
8885
- reject(new Error(`${this.errorPrefix} stdin not writable for command "${command}"`));
8976
+ const error2 = new BridgeTransportUnavailableError(`${this.errorPrefix} stdin not writable for command "${command}"`);
8977
+ reject(error2);
8978
+ if (this.process === child)
8979
+ this.invalidateTransportProcess(error2);
8886
8980
  return;
8887
8981
  }
8888
8982
  requestSentAt = Date.now();
8889
- this.process.stdin.write(line, (err) => {
8983
+ child.stdin.write(line, (err) => {
8890
8984
  if (err) {
8985
+ const error2 = new BridgeTransportUnavailableError(`${this.errorPrefix} Failed to write to stdin: ${err.message}`, { cause: err });
8891
8986
  const entry = this.pending.get(id);
8892
8987
  if (entry) {
8893
8988
  this.pending.delete(id);
8894
8989
  clearTimeout(entry.timer);
8895
- entry.reject(new Error(`${this.errorPrefix} Failed to write to stdin: ${err.message}`));
8990
+ entry.reject(error2);
8896
8991
  }
8992
+ if (this.process === child)
8993
+ this.invalidateTransportProcess(error2);
8897
8994
  }
8898
8995
  });
8899
8996
  });
@@ -9053,13 +9150,15 @@ class BinaryBridge {
9053
9150
  });
9054
9151
  }
9055
9152
  ensureSpawned(triggeringSessionId) {
9056
- if (this.isAlive())
9153
+ if (this.isAlive() && this.process?.stdin?.writable)
9057
9154
  return;
9155
+ if (this.process !== null) {
9156
+ this.invalidateTransportProcess(new BridgeTransportUnavailableError(`${this.errorPrefix} Child stdin is not writable`));
9157
+ }
9058
9158
  this.spawnProcess(triggeringSessionId);
9059
9159
  }
9060
9160
  spawnProcess(triggeringSessionId) {
9061
9161
  this._retiringDueToBinaryChange = false;
9062
- this.lastStatusBar = undefined;
9063
9162
  if (triggeringSessionId) {
9064
9163
  this.sessionLogVia(triggeringSessionId, `Spawning binary: ${this.binaryPath} (cwd: ${this.cwd})`);
9065
9164
  } else {
@@ -9100,7 +9199,7 @@ class BinaryBridge {
9100
9199
  }
9101
9200
  }
9102
9201
  }
9103
- const child = spawn(this.binaryPath, [], {
9202
+ const child = spawn2(this.binaryPath, [], {
9104
9203
  cwd: this.cwd,
9105
9204
  stdio: ["pipe", "pipe", "pipe"],
9106
9205
  env
@@ -9130,7 +9229,7 @@ class BinaryBridge {
9130
9229
  if (this.process !== currentChild)
9131
9230
  return;
9132
9231
  this.errorVia(`Process error: ${err.message}${this.formatStderrTail()}`);
9133
- this.handleCrash();
9232
+ this.handleCrash(err);
9134
9233
  });
9135
9234
  child.on("exit", (code, signal) => {
9136
9235
  if (this.process !== currentChild)
@@ -9143,7 +9242,7 @@ class BinaryBridge {
9143
9242
  this.process = null;
9144
9243
  this.configured = false;
9145
9244
  this.clearRestartResetTimer();
9146
- this.rejectAllPending(new Error(`${this.errorPrefix} Binary killed by ${signal}`));
9245
+ this.rejectAllPending(new BridgeTransportUnavailableError(`${this.errorPrefix} Binary killed by ${signal}`));
9147
9246
  return;
9148
9247
  }
9149
9248
  this.handleCrash();
@@ -9298,7 +9397,6 @@ class BinaryBridge {
9298
9397
  this.consecutiveRequestTimeouts = 0;
9299
9398
  this.scheduleRestartCountReset();
9300
9399
  this.accountForBashTaskResponse(entry.command, response);
9301
- this.captureStatusBar(response);
9302
9400
  entry.resolve(response);
9303
9401
  } else if (typeof response.type === "string") {
9304
9402
  this.logVia(`Ignoring unknown stdout push frame type: ${response.type}`);
@@ -9319,14 +9417,6 @@ class BinaryBridge {
9319
9417
  this.outstandingBackgroundTaskIds.add(taskId);
9320
9418
  }
9321
9419
  }
9322
- captureStatusBar(response) {
9323
- const parsed = parseStatusBarCounts(response.status_bar);
9324
- if (parsed)
9325
- this.lastStatusBar = parsed;
9326
- }
9327
- getStatusBar() {
9328
- return this.lastStatusBar;
9329
- }
9330
9420
  handleTimeout(triggeringSessionId) {
9331
9421
  this.consecutiveRequestTimeouts = 0;
9332
9422
  this.spawnedBinaryFingerprint = null;
@@ -9367,7 +9457,7 @@ class BinaryBridge {
9367
9457
  if (tail) {
9368
9458
  this.errorVia(`Binary crashed (restarts: ${this._restartCount})${cause ? `: ${cause.message}` : ""}.${tail}`);
9369
9459
  }
9370
- this.rejectAllPending(new Error(`${this.errorPrefix} Binary crashed (restarts: ${this._restartCount})${cause ? `: ${cause.message}` : ""} (see ${this.getLogFilePathVia()})`));
9460
+ this.rejectAllPending(new BridgeTransportUnavailableError(`${this.errorPrefix} Binary crashed (restarts: ${this._restartCount})${cause ? `: ${cause.message}` : ""} (see ${this.getLogFilePathVia()})`, { cause }));
9371
9461
  if (this._retiringDueToBinaryChange) {
9372
9462
  this.logVia("Binary exited while retiring after an on-disk update; skipping auto-restart");
9373
9463
  return;
@@ -13030,6 +13120,12 @@ function normalizeWindowsRoot(p) {
13030
13120
  }
13031
13121
 
13032
13122
  // ../aft-bridge/dist/subc-transport.js
13123
+ class SubcTransportShuttingDownError extends SubcCallError {
13124
+ constructor() {
13125
+ super("terminal", "subc transport is shutting down", "transport_shutting_down");
13126
+ this.name = "SubcTransportShuttingDownError";
13127
+ }
13128
+ }
13033
13129
  var AFT_MODULE_ID = "aft";
13034
13130
  var MAX_CONSECUTIVE_TRANSPORT_FAILURES = 3;
13035
13131
  var BG_STABLE_MS = 5000;
@@ -13371,7 +13467,6 @@ class SubcTransport {
13371
13467
  pool;
13372
13468
  projectRoot;
13373
13469
  generation;
13374
- lastStatusBar;
13375
13470
  cachedStatus = null;
13376
13471
  constructor(pool, projectRoot, generation) {
13377
13472
  this.pool = pool;
@@ -13387,20 +13482,12 @@ class SubcTransport {
13387
13482
  getConcretePoolId() {
13388
13483
  return this.pool.getConcretePoolId();
13389
13484
  }
13390
- getStatusBar() {
13391
- return this.lastStatusBar;
13392
- }
13393
13485
  getCachedStatus() {
13394
13486
  return this.cachedStatus;
13395
13487
  }
13396
13488
  cacheStatusSnapshot(snapshot) {
13397
13489
  this.cachedStatus = snapshot;
13398
13490
  }
13399
- captureStatusBar(response) {
13400
- const parsed = parseStatusBarCounts(response.status_bar);
13401
- if (parsed)
13402
- this.lastStatusBar = parsed;
13403
- }
13404
13491
  identityFor(session) {
13405
13492
  return {
13406
13493
  project_root: this.projectRoot,
@@ -13421,9 +13508,7 @@ class SubcTransport {
13421
13508
  if (preview === true)
13422
13509
  body.preview = true;
13423
13510
  const reply = await this.pool.routeRequest(this.identityFor(sessionId), body, timeoutMs, onProgress, this.generation);
13424
- const result = reliftReply(reply);
13425
- this.captureStatusBar(result);
13426
- return result;
13511
+ return reliftReply(reply);
13427
13512
  }
13428
13513
  async send(command, params = {}, options) {
13429
13514
  this.assertCurrent();
@@ -13437,9 +13522,7 @@ class SubcTransport {
13437
13522
  if (editSlotSurvives !== undefined)
13438
13523
  body.edit_slot_survives = editSlotSurvives;
13439
13524
  const reply = await this.pool.routeRequest(this.identityFor(session), body, timeoutMs, onProgress, this.generation);
13440
- const response = reliftReply(reply);
13441
- this.captureStatusBar(response);
13442
- return response;
13525
+ return reliftReply(reply);
13443
13526
  }
13444
13527
  splitOptions(options) {
13445
13528
  if (!options)
@@ -13710,7 +13793,7 @@ class SubcTransportPool {
13710
13793
  getBridge(projectRoot) {
13711
13794
  const root = this.canonicalRoot(projectRoot);
13712
13795
  if (this.shuttingDown && this.lifecycleEnabled()) {
13713
- throw new SubcCallError("terminal", "subc transport is shutting down");
13796
+ throw new SubcTransportShuttingDownError;
13714
13797
  }
13715
13798
  if (!this.lifecycleEnabled())
13716
13799
  return this.makeFacade(root);
@@ -14010,7 +14093,7 @@ class SubcTransportPool {
14010
14093
  }
14011
14094
  async ensureClient() {
14012
14095
  if (this.shuttingDown)
14013
- throw new SubcCallError("terminal", "subc transport is shutting down");
14096
+ throw new SubcTransportShuttingDownError;
14014
14097
  if (this.client)
14015
14098
  return this.client;
14016
14099
  if (this.connecting)
@@ -14024,7 +14107,7 @@ class SubcTransportPool {
14024
14107
  try {
14025
14108
  client.close();
14026
14109
  } catch {}
14027
- throw new SubcCallError("terminal", "subc transport is shutting down");
14110
+ throw new SubcTransportShuttingDownError;
14028
14111
  }
14029
14112
  this.client = client;
14030
14113
  this.transportFailures = 0;
@@ -14289,8 +14372,24 @@ function isRouteGoodbyeError(error2) {
14289
14372
  }
14290
14373
  return error2.code === "route_closed" && error2.message.includes("route closed by subc");
14291
14374
  }
14375
+ function hasEngineResponse(error2) {
14376
+ const response = error2.response;
14377
+ if (response !== null && typeof response === "object")
14378
+ return true;
14379
+ const cause = error2.cause;
14380
+ if (cause === null || typeof cause !== "object")
14381
+ return false;
14382
+ const causeResponse = cause.response;
14383
+ return causeResponse !== null && typeof causeResponse === "object";
14384
+ }
14385
+ function isBashTransportDeadError(error2) {
14386
+ if (!(error2 instanceof Error) || hasEngineResponse(error2) || isRouteGoodbyeError(error2)) {
14387
+ return false;
14388
+ }
14389
+ return error2 instanceof BridgeTransportUnavailableError || error2 instanceof SubcTransportShuttingDownError || isConsumerReconnectTransient(error2) || error2 instanceof StaleRouteHandleError || error2 instanceof SubcRootGenerationExpiredError || error2 instanceof SubcRootReapedError;
14390
+ }
14292
14391
  function isTransportClassError(error2) {
14293
- return isBridgeTransportTimeout(error2) || isConsumerReconnectTransient(error2) || error2 instanceof StaleRouteHandleError || error2 instanceof SubcRootGenerationExpiredError || error2 instanceof SubcRootReapedError;
14392
+ return isBridgeTransportTimeout(error2) || isBashTransportDeadError(error2);
14294
14393
  }
14295
14394
  function adaptToolError(command, error2) {
14296
14395
  if (!(error2 instanceof Error))
@@ -16860,9 +16959,6 @@ class RevivableProjectTransport {
16860
16959
  getCwd() {
16861
16960
  return this.projectRoot;
16862
16961
  }
16863
- getStatusBar() {
16864
- return this.owner.currentBridge(this.projectRoot)?.getStatusBar();
16865
- }
16866
16962
  getCachedStatus() {
16867
16963
  return this.owner.currentBridge(this.projectRoot)?.getCachedStatus() ?? null;
16868
16964
  }
@@ -17881,7 +17977,7 @@ import {
17881
17977
  // package.json
17882
17978
  var package_default = {
17883
17979
  name: "@cortexkit/aft-pi",
17884
- version: "0.50.3",
17980
+ version: "0.51.1",
17885
17981
  type: "module",
17886
17982
  description: "Pi coding agent extension for Agent File Tools (AFT) — tree-sitter and LSP-powered code analysis",
17887
17983
  main: "dist/index.js",
@@ -17904,19 +18000,19 @@ var package_default = {
17904
18000
  "test:unit": "bun test src/__tests__ --path-ignore-patterns 'src/__tests__/e2e/**'"
17905
18001
  },
17906
18002
  dependencies: {
17907
- "@cortexkit/aft-bridge": "0.50.3",
18003
+ "@cortexkit/aft-bridge": "0.51.1",
17908
18004
  "comment-json": "^5.0.0",
17909
18005
  diff: "^8.0.4",
17910
18006
  typebox: "1.1.38",
17911
18007
  zod: "^4.4.3"
17912
18008
  },
17913
18009
  optionalDependencies: {
17914
- "@cortexkit/aft-darwin-arm64": "0.50.3",
17915
- "@cortexkit/aft-darwin-x64": "0.50.3",
17916
- "@cortexkit/aft-linux-arm64": "0.50.3",
17917
- "@cortexkit/aft-linux-x64": "0.50.3",
17918
- "@cortexkit/aft-win32-arm64": "0.50.3",
17919
- "@cortexkit/aft-win32-x64": "0.50.3"
18010
+ "@cortexkit/aft-darwin-arm64": "0.51.1",
18011
+ "@cortexkit/aft-darwin-x64": "0.51.1",
18012
+ "@cortexkit/aft-linux-arm64": "0.51.1",
18013
+ "@cortexkit/aft-linux-x64": "0.51.1",
18014
+ "@cortexkit/aft-win32-arm64": "0.51.1",
18015
+ "@cortexkit/aft-win32-x64": "0.51.1"
17920
18016
  },
17921
18017
  devDependencies: {
17922
18018
  "@earendil-works/pi-coding-agent": "^0.80.2",
@@ -17983,14 +18079,23 @@ function readStatusBar(value) {
17983
18079
  if (typeof value !== "object" || value === null)
17984
18080
  return;
17985
18081
  const bar = asRecord2(value);
18082
+ const errors = readOptionalNumber(bar.errors);
18083
+ const warnings = readOptionalNumber(bar.warnings);
18084
+ const deadCode = readOptionalNumber(bar.dead_code);
18085
+ const unusedExports = readOptionalNumber(bar.unused_exports);
18086
+ const duplicates = readOptionalNumber(bar.duplicates);
18087
+ const todos = readOptionalNumber(bar.todos);
18088
+ if (errors === null && warnings === null && deadCode === null && unusedExports === null && duplicates === null && todos === null) {
18089
+ return;
18090
+ }
17986
18091
  return {
17987
- errors: readNumber(bar.errors),
17988
- warnings: readNumber(bar.warnings),
17989
- dead_code: readNumber(bar.dead_code),
17990
- unused_exports: readNumber(bar.unused_exports),
17991
- duplicates: readNumber(bar.duplicates),
17992
- todos: readNumber(bar.todos),
17993
- tier2_stale: readBoolean(bar.tier2_stale)
18092
+ ...errors !== null ? { errors } : {},
18093
+ ...warnings !== null ? { warnings } : {},
18094
+ ...deadCode !== null ? { dead_code: deadCode } : {},
18095
+ ...unusedExports !== null ? { unused_exports: unusedExports } : {},
18096
+ ...duplicates !== null ? { duplicates } : {},
18097
+ ...todos !== null ? { todos } : {},
18098
+ ...bar.tier2_stale === true ? { tier2_stale: true } : {}
17994
18099
  };
17995
18100
  }
17996
18101
  function formatFlag(enabled) {
@@ -17999,6 +18104,15 @@ function formatFlag(enabled) {
17999
18104
  function formatCount(value) {
18000
18105
  return value == null ? "—" : value.toLocaleString("en-US");
18001
18106
  }
18107
+ function formatCacheRoleLabel(role) {
18108
+ if (role === "worktree") {
18109
+ return "worktree — shared repo index (built by the main checkout)";
18110
+ }
18111
+ if (role === "read_only") {
18112
+ return "read_only — sharing the repo index family (read-only borrow)";
18113
+ }
18114
+ return role;
18115
+ }
18002
18116
  function formatSemanticIndexStatus(status, stage) {
18003
18117
  if ((status === "loading" || status === "building") && stage === "fingerprint_change") {
18004
18118
  return "Rebuilding (model changed)";
@@ -18099,7 +18213,7 @@ function formatStatusDialogMessage(status) {
18099
18213
  `AFT version: ${status.version}`,
18100
18214
  `Project root: ${status.project_root ?? "(not configured)"}`,
18101
18215
  `Canonical root: ${status.canonical_root ?? "(not configured)"}`,
18102
- `Cache role: ${status.cache_role}`,
18216
+ `Cache role: ${formatCacheRoleLabel(status.cache_role)}`,
18103
18217
  "",
18104
18218
  "Enabled features",
18105
18219
  `- format_on_edit: ${formatFlag(status.features.format_on_edit)}`,
@@ -18131,7 +18245,19 @@ function formatStatusDialogMessage(status) {
18131
18245
  lines.push("", "Disk usage", `- trigram index: ${formatBytes(status.disk.trigram_disk_bytes)}`, `- semantic index: ${formatBytes(status.disk.semantic_disk_bytes)}`, "", "Runtime", `- LSP servers: ${formatCount(status.lsp_servers)}`, `- symbol cache: ${formatCount(status.symbol_cache.local_entries)} local / ${formatCount(status.symbol_cache.warm_entries)} warm`);
18132
18246
  if (status.status_bar) {
18133
18247
  const sb = status.status_bar;
18134
- lines.push("", `Code Health${sb.tier2_stale ? " (~ stale)" : ""}`, `- errors: ${formatCount(sb.errors)}`, `- warnings: ${formatCount(sb.warnings)}`, `- dead code: ${formatCount(sb.dead_code)}`, `- unused exports: ${formatCount(sb.unused_exports)}`, `- duplicates: ${formatCount(sb.duplicates)}`, `- todos: ${formatCount(sb.todos)}`);
18248
+ const rows = [
18249
+ ["errors", sb.errors],
18250
+ ["warnings", sb.warnings],
18251
+ ["dead code", sb.dead_code],
18252
+ ["unused exports", sb.unused_exports],
18253
+ ["duplicates", sb.duplicates],
18254
+ ["todos", sb.todos]
18255
+ ].filter((row) => typeof row[1] === "number");
18256
+ if (rows.length > 0) {
18257
+ lines.push("", `Code Health${sb.tier2_stale ? " (~ stale)" : ""}`);
18258
+ for (const [label, value] of rows)
18259
+ lines.push(`- ${label}: ${formatCount(value)}`);
18260
+ }
18135
18261
  }
18136
18262
  if (status.storage_dir ?? status.disk.storage_dir) {
18137
18263
  lines.push(`- storage dir: ${status.storage_dir ?? status.disk.storage_dir}`);
@@ -18210,6 +18336,8 @@ async function callBridge(bridge, command, params = {}, extCtx, options) {
18210
18336
  try {
18211
18337
  response = await bridge.send(command, merged, Object.keys(sendOptions).length > 0 ? sendOptions : undefined);
18212
18338
  } catch (error3) {
18339
+ if (command === "bash" && isBashTransportDeadError(error3))
18340
+ throw error3;
18213
18341
  throw adaptToolError(command, error3);
18214
18342
  }
18215
18343
  if (response.success === false) {
@@ -18345,8 +18473,8 @@ function renderInner(s, error3, theme, innerWidth) {
18345
18473
  }
18346
18474
  lines.push(rowFull("Project root", s.project_root ?? "(not configured)", theme, innerWidth));
18347
18475
  lines.push(rowFull("Canonical root", s.canonical_root ?? "(not configured)", theme, innerWidth));
18348
- const cacheTone = s.cache_role === "main" ? "accent" : s.cache_role === "worktree" ? "warning" : "muted";
18349
- lines.push(rowFull("Cache role", theme.fg(cacheTone, s.cache_role), theme, innerWidth));
18476
+ const cacheTone = s.cache_role === "main" ? "accent" : "muted";
18477
+ lines.push(rowFull("Cache role", theme.fg(cacheTone, formatCacheRoleLabel(s.cache_role)), theme, innerWidth));
18350
18478
  lines.push("");
18351
18479
  const colWidth = Math.floor((innerWidth - 2) / 2);
18352
18480
  const left = [];
@@ -32839,6 +32967,7 @@ function resolveBashConfig(config2) {
32839
32967
  rewrite: false,
32840
32968
  compress: false,
32841
32969
  background: false,
32970
+ host_fallback: false,
32842
32971
  long_running_reminder_enabled: reminderEnabled,
32843
32972
  long_running_reminder_interval_ms: reminderInterval,
32844
32973
  foreground_wait_window_ms: foregroundWaitWindowMs
@@ -32854,7 +32983,8 @@ function resolveBashConfig(config2) {
32854
32983
  enabled: true,
32855
32984
  rewrite: top.rewrite ?? true,
32856
32985
  compress: top.compress ?? true,
32857
- background: top.background ?? true
32986
+ background: top.background ?? true,
32987
+ host_fallback: top.host_fallback ?? false
32858
32988
  };
32859
32989
  }
32860
32990
  const hasLegacyFeatureFlag = legacy && (legacy.rewrite !== undefined || legacy.compress !== undefined || legacy.background !== undefined);
@@ -32941,6 +33071,7 @@ var BashFeaturesSchema = exports_external.object({
32941
33071
  rewrite: exports_external.boolean().optional(),
32942
33072
  compress: exports_external.boolean().optional(),
32943
33073
  background: exports_external.boolean().optional(),
33074
+ host_fallback: exports_external.boolean().optional(),
32944
33075
  long_running_reminder_enabled: exports_external.boolean().optional(),
32945
33076
  long_running_reminder_interval_ms: exports_external.number().int().positive().optional(),
32946
33077
  foreground_wait_window_ms: exports_external.number().int().positive().optional()
@@ -32969,6 +33100,9 @@ var InspectConfigSchema = exports_external.object({
32969
33100
  expected_mirrors: exports_external.array(exports_external.tuple([exports_external.string().trim().min(1), exports_external.string().trim().min(1)])).optional()
32970
33101
  }).optional()
32971
33102
  });
33103
+ var WorktreeConfigSchema = exports_external.object({
33104
+ ram_overlay: exports_external.boolean().optional()
33105
+ });
32972
33106
  var BackupConfigSchema = exports_external.object({
32973
33107
  enabled: exports_external.boolean().optional(),
32974
33108
  max_depth: exports_external.number().int().positive().optional(),
@@ -32993,6 +33127,7 @@ var AftConfigSchema = exports_external.preprocess((value) => stripHarnessSpecifi
32993
33127
  callgraph_chunk_size: exports_external.number().optional(),
32994
33128
  inspect: InspectConfigSchema.optional(),
32995
33129
  backup: BackupConfigSchema.optional(),
33130
+ worktree: WorktreeConfigSchema.optional(),
32996
33131
  sandbox: SandboxConfigSchema.optional(),
32997
33132
  bash: BashConfigSchema.optional(),
32998
33133
  experimental: ExperimentalConfigSchema.optional(),
@@ -33249,6 +33384,15 @@ function mergeLspConfig(base, override) {
33249
33384
  return;
33250
33385
  return Object.fromEntries(Object.entries(lsp).filter(([, v]) => v !== undefined));
33251
33386
  }
33387
+ function mergeWorktreeConfig(baseWorktree, overrideWorktree) {
33388
+ if (baseWorktree === undefined && overrideWorktree === undefined)
33389
+ return;
33390
+ if (overrideWorktree === undefined)
33391
+ return baseWorktree;
33392
+ if (baseWorktree === undefined)
33393
+ return overrideWorktree;
33394
+ return { ...baseWorktree, ...overrideWorktree };
33395
+ }
33252
33396
  function mergeInspectConfig(baseInspect, overrideInspect) {
33253
33397
  const inspect = {
33254
33398
  ...baseInspect,
@@ -33334,6 +33478,7 @@ var PROJECT_SAFE_TOP_LEVEL_FIELDS = new Set([
33334
33478
  "callgraph_store",
33335
33479
  "callgraph_chunk_size",
33336
33480
  "inspect",
33481
+ "worktree",
33337
33482
  "experimental",
33338
33483
  "bash"
33339
33484
  ]);
@@ -33378,11 +33523,13 @@ function mergeConfigs(base, override) {
33378
33523
  const experimental = mergeExperimentalConfig(base.experimental, override.experimental);
33379
33524
  const bash = mergeBashConfig(base.bash, override.bash);
33380
33525
  const inspect = mergeInspectConfig(base.inspect, override.inspect);
33526
+ const worktree = mergeWorktreeConfig(base.worktree, override.worktree);
33381
33527
  const sandbox = mergeSandboxConfig(base.sandbox, override.sandbox);
33382
33528
  const bridge = base.bridge;
33383
33529
  const safeOverride = pickProjectSafeFields(override);
33384
33530
  delete safeOverride.bash;
33385
33531
  delete safeOverride.inspect;
33532
+ delete safeOverride.worktree;
33386
33533
  return {
33387
33534
  ...base,
33388
33535
  ...safeOverride,
@@ -33391,6 +33538,7 @@ function mergeConfigs(base, override) {
33391
33538
  ...lsp ? { lsp } : {},
33392
33539
  ...bash !== undefined ? { bash } : {},
33393
33540
  ...inspect !== undefined ? { inspect } : {},
33541
+ ...worktree !== undefined ? { worktree } : {},
33394
33542
  ...sandbox !== undefined ? { sandbox } : {},
33395
33543
  experimental,
33396
33544
  semantic,
@@ -33472,7 +33620,7 @@ function loadAftConfig(projectDirectory) {
33472
33620
  }
33473
33621
 
33474
33622
  // src/lsp-auto-install.ts
33475
- import { spawn as spawn2 } from "node:child_process";
33623
+ import { spawn as spawn3 } from "node:child_process";
33476
33624
  import { createHash as createHash4 } from "node:crypto";
33477
33625
  import {
33478
33626
  createReadStream,
@@ -34072,7 +34220,7 @@ function runInstall(spec, version2, cwd, signal) {
34072
34220
  return;
34073
34221
  }
34074
34222
  ensureInstallAnchor(cwd);
34075
- const child = spawn2(npm.command, ["install", "--no-save", "--ignore-scripts", "--silent", target], {
34223
+ const child = spawn3(npm.command, ["install", "--no-save", "--ignore-scripts", "--silent", target], {
34076
34224
  stdio: ["ignore", "pipe", "pipe"],
34077
34225
  cwd,
34078
34226
  env: npmSpawnEnv(npm)
@@ -35270,26 +35418,6 @@ function sendFeatureAnnouncement(version2, features, footer, storageDir) {
35270
35418
  markAnnouncementSeen(storageDir, "pi", version2);
35271
35419
  }
35272
35420
 
35273
- // src/status-bar-inject.ts
35274
- var DEFAULT_SESSION = "__default__";
35275
- var emitStateBySession = new Map;
35276
- function statusBarBlockForSession(sessionID, counts, force = false) {
35277
- if (!counts)
35278
- return;
35279
- const key = sessionID ?? DEFAULT_SESSION;
35280
- let state = emitStateBySession.get(key);
35281
- if (!state) {
35282
- state = createStatusBarEmitState();
35283
- emitStateBySession.set(key, state);
35284
- }
35285
- if (force) {
35286
- state.last = counts;
35287
- state.callsSinceEmit = 0;
35288
- return formatStatusBar(counts);
35289
- }
35290
- return shouldEmitStatusBar(state, counts) ? formatStatusBar(counts) : undefined;
35291
- }
35292
-
35293
35421
  // src/bash-wait-detach.ts
35294
35422
  var BASH_TRANSPORT_TIMEOUT_MS = 30000;
35295
35423
  async function sendBashWaitDetach(bridge, sessionID) {
@@ -36730,7 +36858,7 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
36730
36858
  "Piped commands run verbatim and show the pipeline's output; run test/build tools without pipes when you need AFT's summary."
36731
36859
  ],
36732
36860
  parameters: bashParamsForConfig(bashCfg.background),
36733
- async execute(_toolCallId, params, _signal, onUpdate, extCtx) {
36861
+ async execute(_toolCallId, params, signal, onUpdate, extCtx) {
36734
36862
  const bridge = bridgeFor(ctx, extCtx.cwd);
36735
36863
  const bashCfg2 = resolveBashConfig(ctx.config);
36736
36864
  const foregroundWaitMs = resolveForegroundWaitMs(bashCfg2.foreground_wait_window_ms);
@@ -36765,30 +36893,62 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
36765
36893
  }
36766
36894
  const bridgeCommand = spawnContext.command;
36767
36895
  let streamed = "";
36768
- const response = await callBashWithPermissionLoop(bridge, {
36769
- command: bridgeCommand,
36770
- timeout: effectiveTimeout,
36771
- workdir: spawnContext.cwd ?? params.workdir,
36772
- env: spawnContext.env,
36773
- description: params.description,
36774
- background: effectiveBackground,
36775
- notify_on_completion: effectiveBackground,
36776
- compressed,
36777
- pty: requestedPty,
36778
- pty_rows: ptyRows,
36779
- pty_cols: ptyCols,
36780
- foreground_orchestrate: true,
36781
- block_to_completion: blockToCompletion,
36782
- wait: requestedWait,
36783
- sandbox: params.sandbox
36784
- }, extCtx, {
36785
- transportTimeoutMs: orchestratedTransportTimeoutMs(blockToCompletion, requestedWait, effectiveTimeout, foregroundWaitMs),
36786
- onProgress: ({ text }) => {
36787
- streamed += text;
36788
- const displayText = truncateToVisualLines(streamed, 100);
36789
- onUpdate?.(bashResult(displayText, { streaming: true }));
36790
- }
36791
- });
36896
+ let usedHostFallback = false;
36897
+ let response;
36898
+ try {
36899
+ response = await callBashWithPermissionLoop(bridge, {
36900
+ command: bridgeCommand,
36901
+ timeout: effectiveTimeout,
36902
+ workdir: spawnContext.cwd ?? params.workdir,
36903
+ env: spawnContext.env,
36904
+ description: params.description,
36905
+ background: effectiveBackground,
36906
+ notify_on_completion: effectiveBackground,
36907
+ compressed,
36908
+ pty: requestedPty,
36909
+ pty_rows: ptyRows,
36910
+ pty_cols: ptyCols,
36911
+ foreground_orchestrate: true,
36912
+ block_to_completion: blockToCompletion,
36913
+ wait: requestedWait,
36914
+ sandbox: params.sandbox
36915
+ }, extCtx, {
36916
+ transportTimeoutMs: orchestratedTransportTimeoutMs(blockToCompletion, requestedWait, effectiveTimeout, foregroundWaitMs),
36917
+ onProgress: ({ text }) => {
36918
+ streamed += text;
36919
+ const displayText = truncateToVisualLines(streamed, 100);
36920
+ onUpdate?.(bashResult(displayText, { streaming: true }));
36921
+ }
36922
+ });
36923
+ } catch (error53) {
36924
+ if (!bashCfg2.host_fallback || !isBashTransportDeadError(error53))
36925
+ throw error53;
36926
+ if (rawRequestedBackground) {
36927
+ throw new Error(`${BASH_HOST_FALLBACK_REFUSAL}; background:true is unsupported.`);
36928
+ }
36929
+ if (rawRequestedPty) {
36930
+ throw new Error(`${BASH_HOST_FALLBACK_REFUSAL}; pty:true is unsupported.`);
36931
+ }
36932
+ if (!extCtx.hasUI || typeof extCtx.ui?.confirm !== "function") {
36933
+ throw new BridgeError("Permission denied: host fallback execution requires an interactive UI.", "permission_denied");
36934
+ }
36935
+ const projectRoot = extCtx.cwd;
36936
+ const pattern = bashHostFallbackAskPattern(bridgeCommand, projectRoot);
36937
+ const approved = await extCtx.ui.confirm("AFT unavailable — run command on host?", pattern, {
36938
+ signal: signal ?? extCtx.signal
36939
+ });
36940
+ if (!approved) {
36941
+ throw new BridgeError("Permission denied: AFT host fallback execution was denied.", "permission_denied");
36942
+ }
36943
+ response = await runBashHostFallback({
36944
+ command: bridgeCommand,
36945
+ projectRoot,
36946
+ timeoutMs: timeout,
36947
+ signal: signal ?? extCtx.signal,
36948
+ env: spawnContext.env
36949
+ });
36950
+ usedHostFallback = true;
36951
+ }
36792
36952
  if (response.success === false) {
36793
36953
  throw new Error(response.message ?? "bash failed");
36794
36954
  }
@@ -36805,7 +36965,7 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
36805
36965
  task_id: taskId
36806
36966
  };
36807
36967
  const output = response.output ?? "";
36808
- return bashResult(withBashHints(output, bridgeCommand, aftSearchRegistered, extCtx.cwd), details);
36968
+ return bashResult(usedHostFallback ? output : withBashHints(output, bridgeCommand, aftSearchRegistered, extCtx.cwd), details);
36809
36969
  },
36810
36970
  renderCall(args, theme, context) {
36811
36971
  return renderBashCall(args?.command, args?.description, theme, context);
@@ -37647,10 +37807,10 @@ function registerImportTools(pi, ctx) {
37647
37807
  import { Type as Type8 } from "typebox";
37648
37808
  var InspectParams = Type8.Object({
37649
37809
  sections: Type8.Optional(Type8.Union([Type8.String(), Type8.Array(Type8.String())], {
37650
- description: "Categories to include in detailed drill-down (e.g. 'todos' or ['todos', 'dead_code', 'cycles']). Use 'all' for every active category. Omit for summary-only mode."
37810
+ description: "Categories to include in detailed drill-down (e.g. 'todos' or ['todos', 'dead_code', 'cycles']). Use 'all' for every active category. Omit for summary-only mode. `sections` changes detail, not the categories verified."
37651
37811
  })),
37652
37812
  scope: Type8.Optional(Type8.Union([Type8.String(), Type8.Array(Type8.String())], {
37653
- description: "Restrict scan/results to paths under this scope (file or directory, absolute or relative to project root). Tier 1 scopes the scan; Tier 2 scans project-wide and applies scope as a result filter."
37813
+ description: "Restrict returned results to paths under this scope (file or directory, absolute or relative to project root). `scope=` narrows results; it does not reduce the blocking-fresh verification work."
37654
37814
  })),
37655
37815
  topK: Type8.Optional(Type8.Integer({
37656
37816
  minimum: 1,
@@ -37659,11 +37819,6 @@ var InspectParams = Type8.Object({
37659
37819
  description: "Max drill-down items per category. Default 20, max 100."
37660
37820
  }))
37661
37821
  });
37662
- var TIER2_INSPECT_CATEGORIES = new Set(["dead_code", "unused_exports", "duplicates", "cycles"]);
37663
- var INSPECT_TIER2_RUN_TIMEOUT_MS = 5 * 60000;
37664
- var INSPECT_TIER2_MIN_TRIGGER_INTERVAL_MS = 4 * 60000;
37665
- var runningTier2Categories = new WeakMap;
37666
- var lastTier2TriggerAtByBridge = new WeakMap;
37667
37822
  function normalizeStringOrArray(value) {
37668
37823
  return isEmptyParam(value) ? undefined : value;
37669
37824
  }
@@ -37694,15 +37849,119 @@ function validateOptionalTopK(value) {
37694
37849
  }
37695
37850
  return value;
37696
37851
  }
37697
- function diagnosticsServerSummary(section) {
37698
- const pending = Array.isArray(section.servers_pending) ? section.servers_pending.filter((item) => typeof item === "string") : [];
37699
- const notInstalled = Array.isArray(section.servers_not_installed) ? section.servers_not_installed.filter((item) => typeof item === "string") : [];
37700
- const parts = [];
37701
- if (pending.length > 0)
37702
- parts.push(`pending: ${pending.join(", ")}`);
37703
- if (notInstalled.length > 0)
37704
- parts.push(`not installed: ${notInstalled.join(", ")}`);
37705
- return parts.length > 0 ? parts.join("; ") : "none reported";
37852
+ function terminalKind(response) {
37853
+ for (const value of [
37854
+ response.terminal,
37855
+ response.outcome,
37856
+ response.inspect_outcome,
37857
+ response.inspect_terminal,
37858
+ response.status
37859
+ ]) {
37860
+ if (typeof value !== "string")
37861
+ continue;
37862
+ const normalized = value.toUpperCase().replaceAll("_", "-");
37863
+ if (normalized === "FRESH" || normalized === "INTERRUPTED" || normalized === "PHASE-FAILED") {
37864
+ return normalized;
37865
+ }
37866
+ }
37867
+ return;
37868
+ }
37869
+ function firstRecord(...values) {
37870
+ for (const value of values) {
37871
+ const record2 = asRecord3(value);
37872
+ if (record2)
37873
+ return record2;
37874
+ }
37875
+ return;
37876
+ }
37877
+ function firstString(...values) {
37878
+ for (const value of values) {
37879
+ const text = asString2(value);
37880
+ if (text !== undefined)
37881
+ return text;
37882
+ }
37883
+ return;
37884
+ }
37885
+ function parseInspectPhaseEntries(value) {
37886
+ if (!Array.isArray(value))
37887
+ return [];
37888
+ return value.flatMap((candidate) => {
37889
+ const entry = asRecord3(candidate);
37890
+ const id = asString2(entry?.id);
37891
+ if (!id)
37892
+ return [];
37893
+ const alsoSatisfied = Array.isArray(entry?.also_satisfied) ? entry.also_satisfied.filter((category) => typeof category === "string") : [];
37894
+ return [
37895
+ {
37896
+ id,
37897
+ producer: asString2(entry?.producer),
37898
+ category: asString2(entry?.category),
37899
+ alsoSatisfied
37900
+ }
37901
+ ];
37902
+ });
37903
+ }
37904
+ function parseInspectTerminal(payload) {
37905
+ const response = asRecord3(payload);
37906
+ if (!response)
37907
+ return;
37908
+ const kind = terminalKind(response);
37909
+ if (!kind)
37910
+ return;
37911
+ const waitStamp = firstRecord(response.wait_stamp, response.waitStamp, response.blocking_wait_stamp);
37912
+ const phaseSource = kind === "FRESH" ? waitStamp ?? response : response;
37913
+ const phases = parseInspectPhaseEntries(phaseSource.phases ?? response.completed_phases ?? response.completedPhases);
37914
+ if (kind !== "PHASE-FAILED") {
37915
+ return {
37916
+ kind,
37917
+ phases,
37918
+ waitStampText: kind === "FRESH" ? firstString(waitStamp?.text, waitStamp?.human_text) : undefined
37919
+ };
37920
+ }
37921
+ const failedPhaseRecord = asRecord3(response.failed_phase);
37922
+ const failedPhaseId = firstString(failedPhaseRecord?.id, response.failed_phase);
37923
+ const failedPhase = failedPhaseId ? parseInspectPhaseEntries([
37924
+ {
37925
+ id: failedPhaseId,
37926
+ producer: firstString(failedPhaseRecord?.producer, response.failed_phase_producer, response.producer),
37927
+ category: firstString(failedPhaseRecord?.category, response.failed_phase_category, response.category)
37928
+ }
37929
+ ])[0] : undefined;
37930
+ return {
37931
+ kind,
37932
+ phases,
37933
+ failedPhase,
37934
+ failureReason: asString2(response.failure_reason),
37935
+ failureDetail: asString2(response.failure_detail)
37936
+ };
37937
+ }
37938
+ function formatPhase(entry) {
37939
+ const details = [
37940
+ entry.producer ? `producer: ${entry.producer}` : undefined,
37941
+ entry.category ? `category: ${entry.category}` : undefined,
37942
+ entry.alsoSatisfied.length > 0 ? `also satisfied: ${entry.alsoSatisfied.join(", ")}` : undefined
37943
+ ].filter((detail) => Boolean(detail));
37944
+ return details.length > 0 ? `${entry.id} (${details.join("; ")})` : entry.id;
37945
+ }
37946
+ function renderInspectTerminal(terminal, serverText) {
37947
+ const lines = [terminal.kind];
37948
+ if (terminal.kind === "FRESH") {
37949
+ lines.push(`wait-stamp: ${terminal.waitStampText ?? "not supplied"}`);
37950
+ }
37951
+ if (terminal.kind === "PHASE-FAILED") {
37952
+ if (terminal.failedPhase)
37953
+ lines.push(`failed phase: ${formatPhase(terminal.failedPhase)}`);
37954
+ lines.push(`failure reason: ${terminal.failureReason ?? "not supplied"}`);
37955
+ if (terminal.failureDetail)
37956
+ lines.push(`failure detail: ${terminal.failureDetail}`);
37957
+ }
37958
+ lines.push(terminal.phases.length > 0 ? `completed phases:
37959
+ ${terminal.phases.map((phase) => `- ${formatPhase(phase)}`).join(`
37960
+ `)}` : "completed phases: none");
37961
+ if (serverText?.trim())
37962
+ lines.push(serverText);
37963
+ return lines.join(`
37964
+ `);
37706
37965
  }
37707
37966
  function diagnosticsSummaryPart(summary) {
37708
37967
  const section = asRecord3(summary?.diagnostics);
@@ -37712,31 +37971,9 @@ function diagnosticsSummaryPart(summary) {
37712
37971
  const warnings = asNumber2(section.warnings);
37713
37972
  const info = asNumber2(section.info);
37714
37973
  const hints = asNumber2(section.hints);
37715
- const hasCounts = [errors3, warnings, info, hints].some((value) => value !== undefined);
37716
- const counts = `${errors3 ?? 0} errors/${warnings ?? 0} warnings/${info ?? 0} info/${hints ?? 0} hints`;
37717
- const status = asString2(section.status);
37718
- const provisionalCounts = asRecord3(section.provisional_counts);
37719
- const provisionalValues = provisionalCounts ? [
37720
- asNumber2(provisionalCounts.errors),
37721
- asNumber2(provisionalCounts.warnings),
37722
- asNumber2(provisionalCounts.info),
37723
- asNumber2(provisionalCounts.hints)
37724
- ] : [];
37725
- const provisionalText = provisionalCounts ? ` (${provisionalValues[0] ?? 0} errors/${provisionalValues[1] ?? 0} warnings/${provisionalValues[2] ?? 0} info/${provisionalValues[3] ?? 0} hints)` : "";
37726
- const provisionalFraming = `provisional — analyzer not ready; counts excluded from E/W${provisionalText}`;
37727
- if (status === "pending") {
37728
- return `diagnostics ${provisionalFraming} — still pending (servers: ${diagnosticsServerSummary(section)}); wait for the LSP update and use the next normal aft_inspect, not repeated polling`;
37729
- }
37730
- if (status === "incomplete") {
37731
- return `diagnostics ${provisionalFraming} (incomplete — servers: ${diagnosticsServerSummary(section)})`;
37732
- }
37733
- if (provisionalCounts) {
37734
- return `diagnostics ${provisionalFraming}`;
37735
- }
37736
- if (hasCounts) {
37737
- return `diagnostics ${counts}`;
37738
- }
37739
- return;
37974
+ if (![errors3, warnings, info, hints].some((value) => value !== undefined))
37975
+ return;
37976
+ return `diagnostics ${errors3 ?? 0} errors/${warnings ?? 0} warnings/${info ?? 0} info/${hints ?? 0} hints`;
37740
37977
  }
37741
37978
  function diagnosticLocation(diagnostic) {
37742
37979
  const file2 = asString2(diagnostic.file) ?? "(unknown file)";
@@ -37748,36 +37985,28 @@ function diagnosticLocation(diagnostic) {
37748
37985
  return `${file2}:${line}`;
37749
37986
  return `${file2}:${line}:${column}`;
37750
37987
  }
37751
- function diagnosticsDetailSection(details, summary) {
37988
+ function diagnosticsDetailSection(details) {
37752
37989
  const diagnostics = asRecords2(details?.diagnostics);
37753
37990
  if (diagnostics.length === 0)
37754
37991
  return;
37755
- const diagnosticsSummary = asRecord3(summary?.diagnostics);
37756
- const provisional = asString2(diagnosticsSummary?.status) === "pending" || asString2(diagnosticsSummary?.status) === "incomplete" || asRecord3(diagnosticsSummary?.provisional_counts) !== undefined;
37757
- const lines = [
37758
- provisional ? "diagnostics (provisional — analyzer not ready; counts excluded from E/W)" : "diagnostics"
37759
- ];
37760
- for (const diagnostic of diagnostics) {
37761
- const severity = asString2(diagnostic.severity) ?? "information";
37762
- const message = asString2(diagnostic.message) ?? "(no message)";
37763
- const source = asString2(diagnostic.source);
37764
- const suffix = source ? ` [${source}]` : "";
37765
- lines.push(`- ${diagnosticLocation(diagnostic)} ${severity} ${message}${suffix}`);
37766
- }
37767
- return lines.join(`
37992
+ return [
37993
+ "diagnostics",
37994
+ ...diagnostics.map((diagnostic) => {
37995
+ const severity = asString2(diagnostic.severity) ?? "information";
37996
+ const message = asString2(diagnostic.message) ?? "(no message)";
37997
+ const source = asString2(diagnostic.source);
37998
+ return `- ${diagnosticLocation(diagnostic)} ${severity} ${message}${source ? ` [${source}]` : ""}`;
37999
+ })
38000
+ ].join(`
37768
38001
  `);
37769
38002
  }
37770
38003
  function countFrom(summary, key) {
37771
- const section = asRecord3(summary?.[key]);
37772
- return asNumber2(section?.count);
38004
+ return asNumber2(asRecord3(summary?.[key])?.count);
37773
38005
  }
37774
38006
  function tier2SummaryPart(summary, key, label) {
37775
38007
  const section = asRecord3(summary?.[key]);
37776
38008
  const count = asNumber2(section?.count);
37777
- if (count !== undefined)
37778
- return `${label} ${count}`;
37779
- const status = asString2(section?.status);
37780
- return `${label} ${status ?? "unavailable"}`;
38009
+ return count !== undefined ? `${label} ${count}` : `${label} unavailable`;
37781
38010
  }
37782
38011
  function shortDupOccurrence(entry) {
37783
38012
  const [path2] = entry.split(":");
@@ -37785,95 +38014,41 @@ function shortDupOccurrence(entry) {
37785
38014
  }
37786
38015
  function tier2TopPreview(summary, theme) {
37787
38016
  const lines = [];
37788
- const dup = asRecord3(summary?.duplicates);
37789
- const dupTop = Array.isArray(dup?.top) ? dup.top : [];
38017
+ const dupTop = Array.isArray(asRecord3(summary?.duplicates)?.top) ? asRecord3(summary?.duplicates)?.top : [];
37790
38018
  for (const group of dupTop) {
37791
38019
  const record2 = asRecord3(group);
37792
38020
  const files = Array.isArray(record2?.files) ? record2.files : [];
37793
38021
  const cost = asNumber2(record2?.cost);
37794
38022
  if (files.length < 2)
37795
38023
  continue;
37796
- const a = shortDupOccurrence(String(files[0]));
37797
- const b = shortDupOccurrence(String(files[1]));
37798
- lines.push(` dup ${a} ↔ ${b}${cost !== undefined ? ` (${cost})` : ""}`);
38024
+ lines.push(` dup ${shortDupOccurrence(String(files[0]))} ↔ ${shortDupOccurrence(String(files[1]))}${cost !== undefined ? ` (${cost})` : ""}`);
37799
38025
  }
37800
38026
  for (const [key, label] of [
37801
38027
  ["dead_code", "dead"],
37802
38028
  ["unused_exports", "unused"]
37803
38029
  ]) {
37804
- const section = asRecord3(summary?.[key]);
37805
- const top = Array.isArray(section?.top) ? section.top : [];
38030
+ const top = Array.isArray(asRecord3(summary?.[key])?.top) ? asRecord3(summary?.[key])?.top : [];
37806
38031
  for (const item of top) {
37807
38032
  const record2 = asRecord3(item);
37808
38033
  const file2 = asString2(record2?.file);
37809
38034
  const symbol2 = asString2(record2?.symbol);
37810
- if (!file2 || !symbol2)
37811
- continue;
37812
- lines.push(` ${label} ${symbol2} (${file2.split("/").pop()})`);
38035
+ if (file2 && symbol2)
38036
+ lines.push(` ${label} ${symbol2} (${file2.split("/").pop()})`);
37813
38037
  }
37814
38038
  }
37815
- if (lines.length === 0)
37816
- return;
37817
- return `${theme.fg("muted", "top findings:")}
38039
+ return lines.length > 0 ? `${theme.fg("muted", "top findings:")}
37818
38040
  ${lines.join(`
37819
- `)}`;
37820
- }
37821
- function tier2RefreshCategories(response) {
37822
- const scannerState = asRecord3(response.scanner_state);
37823
- const categories = new Set;
37824
- for (const key of ["pending_categories", "stale_categories"]) {
37825
- const values = scannerState?.[key];
37826
- if (!Array.isArray(values))
37827
- continue;
37828
- for (const category of values) {
37829
- if (typeof category === "string" && TIER2_INSPECT_CATEGORIES.has(category)) {
37830
- categories.add(category);
37831
- }
37832
- }
37833
- }
37834
- return [...categories];
37835
- }
37836
- function runPendingTier2Categories(bridge, categories, extCtx) {
37837
- const now = Date.now();
37838
- const running = runningTier2Categories.get(bridge) ?? new Set;
37839
- const lastTriggerAt = lastTier2TriggerAtByBridge.get(bridge) ?? new Map;
37840
- const toRun = categories.filter((category) => {
37841
- if (running.has(category))
37842
- return false;
37843
- const previousTriggerAt = lastTriggerAt.get(category);
37844
- return previousTriggerAt === undefined || previousTriggerAt + INSPECT_TIER2_MIN_TRIGGER_INTERVAL_MS <= now;
37845
- });
37846
- if (toRun.length === 0)
37847
- return;
37848
- for (const category of toRun) {
37849
- running.add(category);
37850
- lastTriggerAt.set(category, now);
37851
- }
37852
- runningTier2Categories.set(bridge, running);
37853
- lastTier2TriggerAtByBridge.set(bridge, lastTriggerAt);
37854
- callBridge(bridge, "inspect_tier2_run", { categories: toRun }, extCtx, {
37855
- transportTimeoutMs: INSPECT_TIER2_RUN_TIMEOUT_MS
37856
- }).catch(() => {}).finally(() => {
37857
- const active = runningTier2Categories.get(bridge);
37858
- if (!active)
37859
- return;
37860
- for (const category of toRun) {
37861
- active.delete(category);
37862
- }
37863
- if (active.size === 0) {
37864
- runningTier2Categories.delete(bridge);
37865
- }
37866
- });
38041
+ `)}` : undefined;
37867
38042
  }
37868
38043
  function buildInspectSections(payload, theme) {
38044
+ const terminal = parseInspectTerminal(payload);
38045
+ if (terminal)
38046
+ return [renderInspectTerminal(terminal, asString2(asRecord3(payload)?.text))];
37869
38047
  const response = asRecord3(payload);
37870
38048
  if (!response)
37871
38049
  return [theme.fg("muted", "No inspect snapshot available.")];
37872
38050
  const summary = asRecord3(response.summary);
37873
38051
  const metrics = asRecord3(summary?.metrics);
37874
- const scannerState = asRecord3(response.scanner_state);
37875
- const stale = Array.isArray(scannerState?.stale_categories) ? scannerState.stale_categories.length : 0;
37876
- const pending = Array.isArray(scannerState?.pending_categories) ? scannerState.pending_categories.length : 0;
37877
38052
  const parts = [
37878
38053
  `todos ${countFrom(summary, "todos") ?? 0}`,
37879
38054
  diagnosticsSummaryPart(summary),
@@ -37884,9 +38059,6 @@ function buildInspectSections(payload, theme) {
37884
38059
  tier2SummaryPart(summary, "cycles", "cycles")
37885
38060
  ].filter((part) => Boolean(part));
37886
38061
  const sections = [theme.fg("accent", parts.join(" · "))];
37887
- if (stale > 0 || pending > 0) {
37888
- sections.push(theme.fg("warning", `scanner state: ${stale} stale · ${pending} pending. Treat stale_categories/pending_categories as stale or incomplete cache state. AFT schedules a Tier-2 refresh after its next idle or inspect-triggered background run; use one later normal aft_inspect after that refresh, not a polling loop`));
37889
- }
37890
38062
  const topPreview = tier2TopPreview(summary, theme);
37891
38063
  if (topPreview)
37892
38064
  sections.push(topPreview);
@@ -37894,7 +38066,7 @@ function buildInspectSections(payload, theme) {
37894
38066
  if (details) {
37895
38067
  const names = Object.keys(details);
37896
38068
  sections.push(names.length > 0 ? `details: ${names.join(", ")}` : theme.fg("muted", "No drill-down details returned."));
37897
- const diagnosticsDetails = diagnosticsDetailSection(details, summary);
38069
+ const diagnosticsDetails = diagnosticsDetailSection(details);
37898
38070
  if (diagnosticsDetails)
37899
38071
  sections.push(diagnosticsDetails);
37900
38072
  }
@@ -37910,15 +38082,19 @@ function renderInspectCall(args, theme, context) {
37910
38082
  return renderToolCall("inspect", summary ? theme.fg("toolOutput", summary) : undefined, theme, context);
37911
38083
  }
37912
38084
  function renderInspectResult(result, theme, context) {
38085
+ const payload = extractStructuredPayload(result);
38086
+ const terminal = parseInspectTerminal(payload);
38087
+ if (terminal)
38088
+ return renderSections([renderInspectTerminal(terminal, asString2(asRecord3(payload)?.text))], context);
37913
38089
  if (context.isError)
37914
38090
  return renderErrorResult(result, "inspect failed", theme, context);
37915
- return renderSections(buildInspectSections(extractStructuredPayload(result), theme), context);
38091
+ return renderSections(buildInspectSections(payload, theme), context);
37916
38092
  }
37917
38093
  function registerInspectTool(pi, ctx) {
37918
38094
  pi.registerTool({
37919
38095
  name: "aft_inspect",
37920
38096
  label: "inspect",
37921
- description: "Codebase health snapshot. One call returns summary stats for: TODOs, diagnostics, file/symbol metrics, dead code, unused exports, code duplicates, and TS/JS import cycles. Pass `sections` for per-category drill-down details.\n\n" + "Categories run in tiers — Tier 1 (todos, metrics) return synchronously from cache. Tier 2 (dead_code, unused_exports, duplicates, cycles) waits for a fresh reuse scan up to a short deadline; if a category is still scanning the response reports `complete: false` with `pending_categories: [...]` rather than a fabricated clean count. Pi may still trigger a deduped background warmup for categories that remain pending. Rust module cycles are out of scope for `cycles`.\n\n" + `Use when: starting work on unfamiliar code, after multi-edit batches to check diagnostics, before a refactor, before review, or to verify cleanup completeness.
38097
+ description: "Blocking-fresh codebase health inspection. Each call completes current analysis and produces exactly one terminal result: FRESH includes a wait-stamp and completed phases; INTERRUPTED and PHASE-FAILED retain completed phases, with PHASE-FAILED also reporting its phase attribution and failure reason. `sections` selects drill-down detail, not the categories verified.\n\n" + "Use `scope=` to narrow returned results. It does not reduce the fresh verification work. Passive health changes use the alert channel; do not infer inspect completion from that channel.\n\n" + `Use when: starting work on unfamiliar code, after multi-edit batches to check diagnostics, before a refactor, before review, or to verify cleanup completeness.
37922
38098
 
37923
38099
  ` + "Treat `dead_code` as a hint, not proof: reachability is call-based, so symbols reached only via method dispatch or referenced only in type position may be false positives — verify before deleting.",
37924
38100
  parameters: InspectParams,
@@ -37934,13 +38110,12 @@ function registerInspectTool(pi, ctx) {
37934
38110
  rawArgs.scope = scope;
37935
38111
  if (topK !== undefined)
37936
38112
  rawArgs.topK = topK;
37937
- const response = await callToolCall(bridge, "inspect", rawArgs, extCtx, {
37938
- keepBridgeOnTimeout: true
37939
- });
37940
- if (response.success === false) {
38113
+ const response = await callToolCall(bridge, "inspect", rawArgs, extCtx);
38114
+ const terminal = parseInspectTerminal(response);
38115
+ if (terminal)
38116
+ return textResult(renderInspectTerminal(terminal, response.text), response);
38117
+ if (response.success === false)
37941
38118
  throw new Error(response.text || response.message || "inspect failed");
37942
- }
37943
- runPendingTier2Categories(bridge, tier2RefreshCategories(response), extCtx);
37944
38119
  return textResult(response.text, response);
37945
38120
  },
37946
38121
  renderCall(args, theme, context) {
@@ -38729,7 +38904,7 @@ var SearchParams2 = Type13.Object({
38729
38904
  description: "Include test files (*.test.*, *_test.rs, __tests__/, …) plus test-support, fixture, mock, snapshot, and corpus files. Defaults to false."
38730
38905
  })),
38731
38906
  path: Type13.Optional(Type13.String({
38732
- description: "Search a different project root (absolute or ~ path). Requires that project to have been indexed by AFT."
38907
+ description: "Only set this to search a different Git project (absolute or ~ path). Omit it for the current configured workspace, including non-Git workspace roots; this is not a subdirectory filter. Unindexed external projects use a bounded lexical fallback."
38733
38908
  }))
38734
38909
  });
38735
38910
  function buildSemanticSections(args, payload, theme) {
@@ -39100,7 +39275,7 @@ var PLUGIN_VERSION = (() => {
39100
39275
  return "0.0.0";
39101
39276
  }
39102
39277
  })();
39103
- var ANNOUNCEMENT_VERSION = "0.50.3";
39278
+ var ANNOUNCEMENT_VERSION = "0.51.1";
39104
39279
  var ANNOUNCEMENT_FEATURES = [
39105
39280
  "Callgraph stays consistent under load: edits arriving mid-rebuild can no longer be silently lost from navigation results.",
39106
39281
  "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).",
@@ -39463,12 +39638,7 @@ ${lines}
39463
39638
  pi.on("tool_result", async (event, extCtx) => {
39464
39639
  const sessionID = resolveSessionId(extCtx);
39465
39640
  const bgContent = await appendToolResultBgCompletions({ ctx, directory: extCtx.cwd, sessionID }, event.content);
39466
- let content = bgContent ?? event.content;
39467
- const isInspect = typeof event.details === "object" && event.details !== null && "scanner_state" in event.details;
39468
- const activeBridge = pool.getActiveBridgeForRoot(extCtx.cwd);
39469
- const bar = statusBarBlockForSession(sessionID, activeBridge?.getStatusBar(), isInspect);
39470
- if (bar)
39471
- content = [...content, { type: "text", text: bar }];
39641
+ const content = bgContent ?? event.content;
39472
39642
  if (content === event.content)
39473
39643
  return;
39474
39644
  return { content, details: event.details, isError: event.isError };