@cortexkit/aft-pi 0.58.0 → 0.58.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
@@ -7938,6 +7938,9 @@ function isTerminalStatus(status) {
7938
7938
  function sleep(ms) {
7939
7939
  return new Promise((resolve) => setTimeout(resolve, ms));
7940
7940
  }
7941
+ function monotonicNowMs() {
7942
+ return performance.now();
7943
+ }
7941
7944
  // ../aft-bridge/dist/bash-hints.js
7942
7945
  import * as fs from "node:fs";
7943
7946
  import * as os from "node:os";
@@ -7948,6 +7951,10 @@ function resolveWatchTimeoutMs(requestedMs, role, capMs) {
7948
7951
  const fallback = role === "worker" ? capMs : DEFAULT_PRIMARY_WATCH_TIMEOUT_MS;
7949
7952
  return Math.min(requestedMs ?? fallback, capMs);
7950
7953
  }
7954
+ function formatWatchWaited(elapsedMs, limitMs, capMs) {
7955
+ const limit = limitMs >= capMs ? `limit ${limitMs}ms, the bash.watch_sync_max_ms cap` : `limit ${limitMs}ms`;
7956
+ return `Waited ${Math.round(elapsedMs)}ms (${limit})`;
7957
+ }
7951
7958
  function watchTimeoutSteer(role, capMs, timeoutParam = "timeoutMs") {
7952
7959
  if (role === "worker") {
7953
7960
  return `The command is still running; this is not a failure. Watch again (${timeoutParam} up to ${capMs}) and don't report a result until it finishes.`;
@@ -8334,7 +8341,7 @@ import { existsSync } from "node:fs";
8334
8341
  import { join as join3 } from "node:path";
8335
8342
 
8336
8343
  // ../aft-bridge/dist/child-process.js
8337
- import { execFileSync as nodeExecFileSync, execSync as nodeExecSync, spawn as nodeSpawn, spawnSync as nodeSpawnSync } from "node:child_process";
8344
+ import { execFile as nodeExecFile, execFileSync as nodeExecFileSync, execSync as nodeExecSync, spawn as nodeSpawn, spawnSync as nodeSpawnSync } from "node:child_process";
8338
8345
  var HIDDEN = { windowsHide: true };
8339
8346
  function withWindowsHidden(args, takesArgv) {
8340
8347
  const out = [...args];
@@ -8356,6 +8363,7 @@ function hidden(resolve, takesArgv) {
8356
8363
  }
8357
8364
  var spawn = hidden(() => nodeSpawn, true);
8358
8365
  var spawnSync = hidden(() => nodeSpawnSync, true);
8366
+ var execFile = hidden(() => nodeExecFile, true);
8359
8367
  var execFileSync = hidden(() => nodeExecFileSync, true);
8360
8368
  var execSync = hidden(() => nodeExecSync, false);
8361
8369
 
@@ -9682,17 +9690,30 @@ class BinaryBridge {
9682
9690
  }
9683
9691
  }
9684
9692
  onStderrData(data) {
9685
- this.stderrBuffer += data;
9686
- let newlineIdx;
9687
- while ((newlineIdx = this.stderrBuffer.indexOf(`
9688
- `)) !== -1) {
9689
- const line = this.stderrBuffer.slice(0, newlineIdx).replace(/\r$/, "");
9690
- this.stderrBuffer = this.stderrBuffer.slice(newlineIdx + 1);
9691
- if (!line || !shouldSurfaceStderrLine(line))
9692
- continue;
9693
- const tagged = tagStderrLine(line);
9694
- this.logVia(tagged);
9695
- this.pushStderrLine(tagged);
9693
+ const partialLimit = 64 * 1024;
9694
+ let offset = 0;
9695
+ while (offset < data.length) {
9696
+ const newline = data.indexOf(`
9697
+ `, offset);
9698
+ const end = newline < 0 ? data.length : newline;
9699
+ while (offset < end) {
9700
+ const count = Math.min(partialLimit - this.stderrBuffer.length, end - offset);
9701
+ this.stderrBuffer += data.slice(offset, offset + count);
9702
+ offset += count;
9703
+ if (this.stderrBuffer.length === partialLimit) {
9704
+ const last = this.stderrBuffer.charCodeAt(this.stderrBuffer.length - 1);
9705
+ const carry = last >= 55296 && last <= 56319 ? this.stderrBuffer.slice(-1) : "";
9706
+ const fragment = carry ? this.stderrBuffer.slice(0, -1) : this.stderrBuffer;
9707
+ const tagged = tagStderrLine(`${fragment} [stderr record truncated into bounded fragments; ${Buffer.byteLength(fragment)} bytes shown, continuation follows]`);
9708
+ this.stderrBuffer = carry;
9709
+ this.logVia(tagged);
9710
+ this.pushStderrLine(tagged);
9711
+ }
9712
+ }
9713
+ if (newline < 0)
9714
+ break;
9715
+ this.flushStderrBuffer();
9716
+ offset = newline + 1;
9696
9717
  }
9697
9718
  }
9698
9719
  flushStderrBuffer() {
@@ -10438,9 +10459,366 @@ function formatDroppedKeyWarnings(dropped) {
10438
10459
  }
10439
10460
  return dropped.map((item) => `Ignoring ${item.key} from ${item.tier} config (${item.reason})`);
10440
10461
  }
10462
+ // ../aft-bridge/dist/config-watch.js
10463
+ import { readFileSync as readFileSync3, statSync as statSync3, watch } from "node:fs";
10464
+ import { basename, dirname } from "node:path";
10465
+ var CONFIG_WATCH_DEBOUNCE_MS = 150;
10466
+ var CONFIG_LIVE_KEEP_NOTE = "AFT keeps using the last valid configuration until the file is fixed.";
10467
+ function pathKey(name) {
10468
+ const segments = name.split(".");
10469
+ return {
10470
+ name,
10471
+ read(config) {
10472
+ let value = config;
10473
+ for (const segment of segments) {
10474
+ if (value === null || typeof value !== "object")
10475
+ return;
10476
+ value = value[segment];
10477
+ }
10478
+ return value;
10479
+ },
10480
+ write(config, value) {
10481
+ const set = (target, index) => {
10482
+ const copy = target !== null && typeof target === "object" && !Array.isArray(target) ? { ...target } : {};
10483
+ const segment = segments[index];
10484
+ if (index === segments.length - 1) {
10485
+ if (value === undefined)
10486
+ delete copy[segment];
10487
+ else
10488
+ copy[segment] = value;
10489
+ } else {
10490
+ copy[segment] = set(copy[segment], index + 1);
10491
+ }
10492
+ return copy;
10493
+ };
10494
+ return set(config, 0);
10495
+ }
10496
+ };
10497
+ }
10498
+ function bashKey(name, resolveBash) {
10499
+ return {
10500
+ name: `bash.${name}`,
10501
+ read: (config) => resolveBash(config)[name],
10502
+ write(config, value) {
10503
+ const resolved = { ...resolveBash(config), [name]: value };
10504
+ for (const key of Object.keys(resolved)) {
10505
+ if (resolved[key] === undefined)
10506
+ delete resolved[key];
10507
+ }
10508
+ return { ...config, bash: resolved };
10509
+ }
10510
+ };
10511
+ }
10512
+ function aftLiveConfigKeys(resolveBash) {
10513
+ return [
10514
+ pathKey("configure_warnings_delivery"),
10515
+ pathKey("restrict_to_project_root"),
10516
+ pathKey("inspect.diagnostics_timeout_ms"),
10517
+ pathKey("inspect.tier2_idle_minutes"),
10518
+ bashKey("foreground_wait_window_ms", resolveBash),
10519
+ bashKey("host_fallback", resolveBash),
10520
+ bashKey("subagent_background", resolveBash),
10521
+ bashKey("watch_sync_max_ms", resolveBash)
10522
+ ];
10523
+ }
10524
+ function sameValue(a, b) {
10525
+ return JSON.stringify(a) === JSON.stringify(b);
10526
+ }
10527
+ function flattenLeaves(value, prefix, out) {
10528
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
10529
+ const entries = Object.entries(value);
10530
+ if (entries.length === 0 && prefix)
10531
+ out.set(prefix, "{}");
10532
+ for (const [key, child] of entries) {
10533
+ flattenLeaves(child, prefix ? `${prefix}.${key}` : key, out);
10534
+ }
10535
+ return;
10536
+ }
10537
+ if (prefix)
10538
+ out.set(prefix, JSON.stringify(value));
10539
+ }
10540
+ function applyLiveConfigKeys(current, next, keys, baseline = current) {
10541
+ let config = current;
10542
+ const applied = [];
10543
+ for (const key of keys) {
10544
+ const value = key.read(next);
10545
+ if (sameValue(key.read(current), value))
10546
+ continue;
10547
+ config = key.write(config, value);
10548
+ applied.push(key.name);
10549
+ }
10550
+ const before = new Map;
10551
+ const after = new Map;
10552
+ flattenLeaves(baseline, "", before);
10553
+ flattenLeaves(next, "", after);
10554
+ const liveNames = keys.map((key) => key.name);
10555
+ const isLive = (leaf) => liveNames.some((name) => leaf === name || leaf.startsWith(`${name}.`));
10556
+ const deferred = new Set;
10557
+ for (const leaf of new Set([...before.keys(), ...after.keys()])) {
10558
+ if (before.get(leaf) === after.get(leaf) || isLive(leaf))
10559
+ continue;
10560
+ deferred.add(leaf);
10561
+ }
10562
+ return { config, applied, deferred: [...deferred].sort() };
10563
+ }
10564
+ function readTextOrNull(path) {
10565
+ try {
10566
+ return readFileSync3(path, "utf8");
10567
+ } catch {
10568
+ return null;
10569
+ }
10570
+ }
10571
+ var CONFIG_WATCH_REVALIDATE_MS = 2000;
10572
+ var CONFIG_WATCH_REJECTED_RETRIES = 3;
10573
+ var CONFIG_WATCH_MAX_DELAY_MS = 1000;
10574
+ function identityOf(dir) {
10575
+ try {
10576
+ const stat = statSync3(dir, { bigint: true });
10577
+ const base = `${stat.dev}:${stat.ino}`;
10578
+ return process.platform === "win32" ? base : `${base}:${stat.birthtimeNs}`;
10579
+ } catch {
10580
+ return null;
10581
+ }
10582
+ }
10583
+ function watchDirFor(file) {
10584
+ let dir = dirname(file);
10585
+ while (dir !== dirname(dir) && identityOf(dir) === null)
10586
+ dir = dirname(dir);
10587
+ return dir;
10588
+ }
10589
+ function watchAftConfigFiles(options) {
10590
+ const debounceMs = options.debounceMs ?? CONFIG_WATCH_DEBOUNCE_MS;
10591
+ const lastSeen = new Map;
10592
+ for (const path of options.paths) {
10593
+ if (options.initialTexts)
10594
+ lastSeen.set(path, options.initialTexts[path] ?? null);
10595
+ else
10596
+ lastSeen.set(path, options.checkAtStart ? undefined : readTextOrNull(path));
10597
+ }
10598
+ let timer = null;
10599
+ let firstPendingAt = null;
10600
+ let stopped = false;
10601
+ const watchers = new Map;
10602
+ const watchImpl = options.watchImpl ?? watch;
10603
+ let rejectedRetries = 0;
10604
+ const check = () => {
10605
+ timer = null;
10606
+ firstPendingAt = null;
10607
+ if (stopped)
10608
+ return;
10609
+ revalidate();
10610
+ const seen = new Map;
10611
+ for (const path of options.paths) {
10612
+ const text = readTextOrNull(path);
10613
+ if (text !== lastSeen.get(path))
10614
+ seen.set(path, text);
10615
+ }
10616
+ if (seen.size === 0)
10617
+ return;
10618
+ const outcome = options.onChange();
10619
+ if (outcome === false) {
10620
+ if (rejectedRetries < CONFIG_WATCH_REJECTED_RETRIES) {
10621
+ rejectedRetries += 1;
10622
+ retryTimer = setTimeout(check, CONFIG_WATCH_MAX_DELAY_MS);
10623
+ retryTimer.unref?.();
10624
+ }
10625
+ return;
10626
+ }
10627
+ rejectedRetries = 0;
10628
+ if (outcome instanceof Map) {
10629
+ for (const path of options.paths)
10630
+ lastSeen.set(path, outcome.get(path) ?? null);
10631
+ } else {
10632
+ for (const [path, text] of seen)
10633
+ lastSeen.set(path, text);
10634
+ }
10635
+ };
10636
+ let retryTimer = null;
10637
+ const schedule = () => {
10638
+ if (stopped)
10639
+ return;
10640
+ const now = Date.now();
10641
+ firstPendingAt ??= now;
10642
+ if (timer)
10643
+ clearTimeout(timer);
10644
+ const delay = Math.max(0, Math.min(debounceMs, firstPendingAt + CONFIG_WATCH_MAX_DELAY_MS - now));
10645
+ timer = setTimeout(check, delay);
10646
+ timer.unref?.();
10647
+ };
10648
+ function revalidate() {
10649
+ const wanted = new Set(options.paths.map(watchDirFor));
10650
+ let moved = false;
10651
+ for (const [dir, entry] of watchers) {
10652
+ if (!wanted.has(dir) || entry.stale || identityOf(dir) !== entry.identity) {
10653
+ entry.watcher.close();
10654
+ watchers.delete(dir);
10655
+ moved = true;
10656
+ }
10657
+ }
10658
+ for (const dir of wanted) {
10659
+ if (watchers.has(dir))
10660
+ continue;
10661
+ try {
10662
+ const identity = identityOf(dir);
10663
+ options.beforeWatchForTest?.(dir);
10664
+ const name = basename(dir);
10665
+ const watcher = watchImpl(dir, { persistent: false }, (event, filename) => {
10666
+ if (event === "rename" && filename === name) {
10667
+ const current = watchers.get(dir);
10668
+ if (current && current.watcher === watcher)
10669
+ current.stale = true;
10670
+ }
10671
+ schedule();
10672
+ });
10673
+ if (identityOf(dir) !== identity) {
10674
+ watcher.close();
10675
+ moved = true;
10676
+ continue;
10677
+ }
10678
+ const entry = { watcher, identity, stale: false };
10679
+ entry.watcher.on("error", () => {
10680
+ entry.watcher.close();
10681
+ if (watchers.get(dir) === entry)
10682
+ watchers.delete(dir);
10683
+ schedule();
10684
+ });
10685
+ watchers.set(dir, entry);
10686
+ moved = true;
10687
+ } catch {}
10688
+ }
10689
+ return moved;
10690
+ }
10691
+ revalidate();
10692
+ if (options.checkAtStart)
10693
+ check();
10694
+ const revalidateTimer = setInterval(() => {
10695
+ if (!stopped && revalidate())
10696
+ schedule();
10697
+ }, CONFIG_WATCH_REVALIDATE_MS);
10698
+ revalidateTimer.unref?.();
10699
+ return () => {
10700
+ stopped = true;
10701
+ if (timer)
10702
+ clearTimeout(timer);
10703
+ if (retryTimer)
10704
+ clearTimeout(retryTimer);
10705
+ clearInterval(revalidateTimer);
10706
+ for (const entry of watchers.values())
10707
+ entry.watcher.close();
10708
+ watchers.clear();
10709
+ };
10710
+ }
10711
+ function aftLiveSecurityKeys(keys) {
10712
+ const byName = new Map(keys.map((key) => [key.name, key]));
10713
+ const pick = (name) => {
10714
+ const key = byName.get(name);
10715
+ if (!key)
10716
+ throw new Error(`unknown live config key ${name}`);
10717
+ return key;
10718
+ };
10719
+ return [
10720
+ {
10721
+ key: pick("restrict_to_project_root"),
10722
+ loosens: (current, next) => current === true && next !== true
10723
+ },
10724
+ {
10725
+ key: pick("bash.host_fallback"),
10726
+ loosens: (current, next) => current !== true && next === true
10727
+ }
10728
+ ];
10729
+ }
10730
+ function liveConfigReloadLogLine(applied, deferred) {
10731
+ let line = "config reload";
10732
+ if (applied.length > 0)
10733
+ line += ` applied=[${applied.join(",")}]`;
10734
+ if (deferred.length > 0) {
10735
+ line += ` deferred=[${deferred.join(",")}] (deferred keys apply on next connect/restart)`;
10736
+ }
10737
+ return line;
10738
+ }
10739
+ function startLiveConfigReload(options) {
10740
+ const baseline = options.getConfig();
10741
+ let lastError = null;
10742
+ let acceptedSources = new Set(options.initialSources);
10743
+ let acceptedProjectText = options.initialProjectText ?? null;
10744
+ let acceptedTexts;
10745
+ const reload = () => {
10746
+ let loaded = options.load();
10747
+ if (loaded.ok) {
10748
+ const read = new Set(loaded.sources);
10749
+ const deleted = [...acceptedSources].find((path) => !read.has(path));
10750
+ if (deleted !== undefined) {
10751
+ loaded = { ok: false, message: `AFT config at ${deleted} was deleted` };
10752
+ } else {
10753
+ acceptedSources = read;
10754
+ }
10755
+ }
10756
+ if (!loaded.ok) {
10757
+ const message = `${loaded.message.trim().replace(/[.!?]?$/, ".")} ${CONFIG_LIVE_KEEP_NOTE}`;
10758
+ if (message !== lastError) {
10759
+ lastError = message;
10760
+ options.reportError(message);
10761
+ }
10762
+ return null;
10763
+ }
10764
+ lastError = null;
10765
+ acceptedTexts = loaded.texts;
10766
+ const current = options.getConfig();
10767
+ let next = loaded.config;
10768
+ const held = [];
10769
+ const projectText = loaded.projectText ?? null;
10770
+ if (loaded.projectText !== undefined && projectText !== acceptedProjectText) {
10771
+ for (const security of options.securityKeys ?? []) {
10772
+ const now = security.key.read(current);
10773
+ if (security.loosens(now, security.key.read(next))) {
10774
+ next = security.key.write(next, now);
10775
+ held.push(security.key.name);
10776
+ }
10777
+ }
10778
+ if (held.length === 0)
10779
+ acceptedProjectText = projectText;
10780
+ }
10781
+ const result = applyLiveConfigKeys(current, next, options.keys, baseline);
10782
+ if (result.applied.length > 0)
10783
+ options.setConfig(result.config);
10784
+ if (result.applied.length > 0 || result.deferred.length > 0 || held.length > 0) {
10785
+ let line = liveConfigReloadLogLine(result.applied, result.deferred);
10786
+ if (held.length > 0) {
10787
+ line += ` held=[${held.join(",")}] (not loosened while the project file differs from the one the published values came from; the next restart applies them)`;
10788
+ }
10789
+ options.log(line);
10790
+ }
10791
+ return { ...result, held };
10792
+ };
10793
+ const safeReload = () => {
10794
+ try {
10795
+ acceptedTexts = undefined;
10796
+ if (reload() === null)
10797
+ return false;
10798
+ const texts = acceptedTexts;
10799
+ if (!texts)
10800
+ return true;
10801
+ return new Map(options.paths.map((path) => [path, texts[path] ?? null]));
10802
+ } catch (err) {
10803
+ options.reportError(`AFT config reload failed: ${err instanceof Error ? err.message : String(err)}`);
10804
+ return false;
10805
+ }
10806
+ };
10807
+ if (options.watch === false) {
10808
+ return { reload, stop: () => {} };
10809
+ }
10810
+ const stop = watchAftConfigFiles({
10811
+ paths: options.paths,
10812
+ debounceMs: options.debounceMs,
10813
+ onChange: safeReload,
10814
+ initialTexts: options.initialTexts,
10815
+ checkAtStart: true
10816
+ });
10817
+ return { reload, stop };
10818
+ }
10441
10819
  // ../aft-bridge/dist/downloader.js
10442
10820
  import { createHash as createHash2, randomUUID } from "node:crypto";
10443
- import { chmodSync, closeSync, copyFileSync, createWriteStream, existsSync as existsSync3, mkdirSync, openSync, readdirSync, readFileSync as readFileSync3, renameSync as renameSync2, rmSync, statSync as statSync3, unlinkSync as unlinkSync2, writeSync } from "node:fs";
10821
+ import { chmodSync, closeSync, copyFileSync, createWriteStream, existsSync as existsSync3, mkdirSync, openSync, readdirSync, readFileSync as readFileSync4, renameSync as renameSync2, rmSync, statSync as statSync4, unlinkSync as unlinkSync2, writeSync } from "node:fs";
10444
10822
  import { hostname } from "node:os";
10445
10823
  import { join as join7 } from "node:path";
10446
10824
  import { Readable } from "node:stream";
@@ -10792,7 +11170,7 @@ function isReclaimableDownloadLock(owner, ageMs, staleMs) {
10792
11170
  }
10793
11171
  function reclaimDownloadLock(lockPath, expectedOwner) {
10794
11172
  try {
10795
- if (readFileSync3(lockPath, "utf-8") !== expectedOwner)
11173
+ if (readFileSync4(lockPath, "utf-8") !== expectedOwner)
10796
11174
  return false;
10797
11175
  rmSync(lockPath, { force: true });
10798
11176
  return true;
@@ -10810,7 +11188,7 @@ function sweepStaleDownloadTemps(versionedCacheDir, binaryName) {
10810
11188
  continue;
10811
11189
  }
10812
11190
  const tempPath = join7(versionedCacheDir, entry.name);
10813
- const ageMs = Date.now() - statSync3(tempPath).mtimeMs;
11191
+ const ageMs = Date.now() - statSync4(tempPath).mtimeMs;
10814
11192
  if (Math.abs(ageMs) > DOWNLOAD_LOCK_STALE_MS)
10815
11193
  unlinkSync2(tempPath);
10816
11194
  }
@@ -10832,7 +11210,7 @@ async function acquireDownloadLock(lockPath, timing = {}) {
10832
11210
  }
10833
11211
  return () => {
10834
11212
  try {
10835
- if (readFileSync3(lockPath, "utf-8") === owner) {
11213
+ if (readFileSync4(lockPath, "utf-8") === owner) {
10836
11214
  rmSync(lockPath, { force: true });
10837
11215
  }
10838
11216
  } catch {}
@@ -10844,8 +11222,8 @@ async function acquireDownloadLock(lockPath, timing = {}) {
10844
11222
  let existingOwner;
10845
11223
  let ageMs;
10846
11224
  try {
10847
- existingOwner = readFileSync3(lockPath, "utf-8");
10848
- ageMs = Date.now() - statSync3(lockPath).mtimeMs;
11225
+ existingOwner = readFileSync4(lockPath, "utf-8");
11226
+ ageMs = Date.now() - statSync4(lockPath).mtimeMs;
10849
11227
  } catch (readErr) {
10850
11228
  if (readErr.code === "ENOENT")
10851
11229
  continue;
@@ -10895,7 +11273,7 @@ async function fetchLatestTag() {
10895
11273
  }
10896
11274
  // ../aft-bridge/dist/durable-log.js
10897
11275
  import { appendFile, chmod, mkdir, rename, rm, stat } from "node:fs/promises";
10898
- import { dirname } from "node:path";
11276
+ import { dirname as dirname2 } from "node:path";
10899
11277
  var DEFAULT_LOG_BYTES = 32 * 1024 * 1024;
10900
11278
  var DEFAULT_LOG_GENERATIONS = 1;
10901
11279
  var LOG_DIR_MODE = 448;
@@ -10908,6 +11286,10 @@ class RotatingLogSink {
10908
11286
  queue = Promise.resolve();
10909
11287
  disabled = false;
10910
11288
  failureReported = false;
11289
+ queuedBytes = 0;
11290
+ queuedRecords = 0;
11291
+ droppedBytes = 0;
11292
+ overflowQueued = false;
10911
11293
  constructor(path, options = {}) {
10912
11294
  this.path = path;
10913
11295
  this.maxBytes = options.maxBytes ?? DEFAULT_LOG_BYTES;
@@ -10916,7 +11298,34 @@ class RotatingLogSink {
10916
11298
  append(data) {
10917
11299
  if (this.disabled || data.length === 0)
10918
11300
  return;
10919
- this.queue = this.queue.then(() => this.write(data)).catch((error) => {
11301
+ const bytes = Buffer.byteLength(data);
11302
+ const overflow = this.queuedBytes + bytes > 1024 * 1024 || this.queuedRecords >= 4096;
11303
+ if (overflow) {
11304
+ this.droppedBytes += bytes;
11305
+ if (this.overflowQueued)
11306
+ return;
11307
+ this.overflowQueued = true;
11308
+ data = "";
11309
+ } else {
11310
+ this.queuedBytes += bytes;
11311
+ this.queuedRecords += 1;
11312
+ }
11313
+ this.queue = this.queue.then(async () => {
11314
+ if (overflow) {
11315
+ const dropped = this.droppedBytes;
11316
+ this.droppedBytes = 0;
11317
+ this.overflowQueued = false;
11318
+ await this.write(`[aft-plugin] durable log queue overflow: dropped ${dropped} bytes (1MiB/4096-record pending limit); reduce diagnostic volume.
11319
+ `);
11320
+ } else {
11321
+ try {
11322
+ await this.write(data);
11323
+ } finally {
11324
+ this.queuedBytes -= bytes;
11325
+ this.queuedRecords -= 1;
11326
+ }
11327
+ }
11328
+ }).catch((error) => {
10920
11329
  this.disabled = true;
10921
11330
  if (!this.failureReported) {
10922
11331
  this.failureReported = true;
@@ -10931,8 +11340,8 @@ class RotatingLogSink {
10931
11340
  await this.queue;
10932
11341
  }
10933
11342
  async write(data) {
10934
- const dir = dirname(this.path);
10935
- await mkdir(dirname(dir), { recursive: true });
11343
+ const dir = dirname2(this.path);
11344
+ await mkdir(dirname2(dir), { recursive: true });
10936
11345
  try {
10937
11346
  await mkdir(dir, { mode: LOG_DIR_MODE });
10938
11347
  } catch (error) {
@@ -11038,6 +11447,15 @@ function formatEditSummary(data) {
11038
11447
  let s = `Edited (${detail}).`;
11039
11448
  if (data.formatted)
11040
11449
  s += formatAutoFormattedSuffix(data);
11450
+ const fuzzy = data.fuzzy_matches ?? (data.fuzzy_match ? [data.fuzzy_match] : []);
11451
+ for (const match of fuzzy) {
11452
+ s += `
11453
+ Fuzzy match (pass ${match.pass}).`;
11454
+ for (const [index, text] of match.replaced_text.entries()) {
11455
+ s += `
11456
+ Replaced text ${index + 1}: ${text}`;
11457
+ }
11458
+ }
11041
11459
  return s;
11042
11460
  }
11043
11461
  function formatAutoFormattedSuffix(data) {
@@ -12766,7 +13184,7 @@ function reverseRequestMethod(body) {
12766
13184
  }
12767
13185
  }
12768
13186
  // ../aft-bridge/dist/subc-transport.js
12769
- import { existsSync as existsSync4, statSync as statSync4 } from "node:fs";
13187
+ import { existsSync as existsSync4, statSync as statSync5 } from "node:fs";
12770
13188
 
12771
13189
  // ../aft-bridge/dist/lifecycle-registry.js
12772
13190
  import { lstat } from "node:fs/promises";
@@ -13289,6 +13707,7 @@ var RECONNECT_RETRY_FLOOR_MS = 100;
13289
13707
  var RECONNECT_RETRY_CAP_MS = 2000;
13290
13708
  var SUBC_DEFAULT_REQUEST_TIMEOUT_MS = 30000;
13291
13709
  var ROUTE_OPEN_RELOAD_WAIT_CEILING_MS = 45000;
13710
+ var CALL_DEADLINE_EXPIRED = Symbol("call deadline expired");
13292
13711
  function reloadWaitExhaustedSuffix(callDeadlineMs) {
13293
13712
  if (callDeadlineMs > ROUTE_OPEN_RELOAD_WAIT_CEILING_MS) {
13294
13713
  const ceilingSeconds = ROUTE_OPEN_RELOAD_WAIT_CEILING_MS / 1000;
@@ -13641,7 +14060,16 @@ function absentRootError(root) {
13641
14060
  return new SubcError(`invalid route project root: project root does not exist: ${root}`, "config_divergence");
13642
14061
  }
13643
14062
  function reloadWindowExhaustedError(error, callDeadlineMs) {
13644
- const suffix = reloadWaitExhaustedSuffix(callDeadlineMs);
14063
+ return withDeadlineSuffix(error, reloadWaitExhaustedSuffix(callDeadlineMs));
14064
+ }
14065
+ function callDeadlinePassedError(lastError, callDeadlineMs) {
14066
+ const seconds = Math.round(callDeadlineMs / 100) / 10;
14067
+ const suffix = ` The AFT daemon module did not return within this call's ${seconds}s deadline.`;
14068
+ if (lastError !== undefined)
14069
+ return withDeadlineSuffix(lastError, suffix);
14070
+ return new SubcError(`route to module '${AFT_MODULE_ID}' was not ready before the call ran out of time.${suffix}`, REQUEST_DEADLINE_CODE);
14071
+ }
14072
+ function withDeadlineSuffix(error, suffix) {
13645
14073
  if (error instanceof Error) {
13646
14074
  try {
13647
14075
  error.message += suffix;
@@ -13880,7 +14308,7 @@ class SubcTransportPool {
13880
14308
  rootCanAttach(root) {
13881
14309
  let directoryExists = false;
13882
14310
  try {
13883
- directoryExists = statSync4(root).isDirectory();
14311
+ directoryExists = statSync5(root).isDirectory();
13884
14312
  } catch {}
13885
14313
  if (directoryExists) {
13886
14314
  this.dormantRoots.delete(root);
@@ -14220,6 +14648,7 @@ class SubcTransportPool {
14220
14648
  return this.rootReapedError(record);
14221
14649
  }
14222
14650
  async routeRequest(identity, body, timeoutMs, onProgress, expectedGeneration, abortSignal) {
14651
+ const callStartedAt = performance.now();
14223
14652
  const root = asCanonicalRootPath(identity.project_root);
14224
14653
  let generation = expectedGeneration;
14225
14654
  if (this.lifecycleEnabled()) {
@@ -14272,19 +14701,41 @@ class SubcTransportPool {
14272
14701
  };
14273
14702
  const callDeadlineMs = timeoutMs ?? SUBC_DEFAULT_REQUEST_TIMEOUT_MS;
14274
14703
  const reloadWaitBudgetMs = Math.min(callDeadlineMs, ROUTE_OPEN_RELOAD_WAIT_CEILING_MS);
14275
- let reloadWaitedMs = 0;
14704
+ let scheduledDelayMs = 0;
14705
+ let lastRefusal;
14706
+ const spentMs = () => Math.max(performance.now() - callStartedAt, scheduledDelayMs);
14707
+ const remainingMs = () => callDeadlineMs - spentMs();
14708
+ const openRouteWithinDeadline = async () => {
14709
+ const remaining = remainingMs();
14710
+ if (remaining <= 0)
14711
+ throw callDeadlinePassedError(lastRefusal, callDeadlineMs);
14712
+ let timer;
14713
+ const expired = new Promise((resolve) => {
14714
+ timer = setTimeout(() => resolve(CALL_DEADLINE_EXPIRED), remaining);
14715
+ });
14716
+ try {
14717
+ const opened = await Promise.race([openRoute(), expired]);
14718
+ if (opened === CALL_DEADLINE_EXPIRED) {
14719
+ throw callDeadlinePassedError(lastRefusal, callDeadlineMs);
14720
+ }
14721
+ return opened;
14722
+ } finally {
14723
+ clearTimeout(timer);
14724
+ }
14725
+ };
14276
14726
  const openRouteAfterReloadWindow = async () => {
14277
14727
  while (true) {
14278
14728
  try {
14279
- return await openRoute();
14729
+ return await openRouteWithinDeadline();
14280
14730
  } catch (error) {
14281
14731
  if (!isRouteOpenReloadWindowError(error))
14282
14732
  throw error;
14283
- if (reloadWaitedMs + this.nextRouteReopenDelayMs() >= reloadWaitBudgetMs) {
14733
+ lastRefusal = error;
14734
+ if (spentMs() + this.nextRouteReopenDelayMs() >= reloadWaitBudgetMs) {
14284
14735
  throw reloadWindowExhaustedError(error, callDeadlineMs);
14285
14736
  }
14286
14737
  const { delayMs, wait } = this.waitForRouteReopenBackoff();
14287
- reloadWaitedMs += delayMs;
14738
+ scheduledDelayMs = spentMs() + delayMs;
14288
14739
  await wait;
14289
14740
  }
14290
14741
  }
@@ -14320,7 +14771,7 @@ class SubcTransportPool {
14320
14771
  route: routeLabel(entry.handle)
14321
14772
  });
14322
14773
  };
14323
- const requestOnRoute = async (route, entry) => {
14774
+ const requestOnRoute = async (route, entry, requestTimeoutMs) => {
14324
14775
  this.assertRecordLive(record);
14325
14776
  if (abortSignal?.aborted) {
14326
14777
  clearRouteEntry(entry);
@@ -14336,8 +14787,10 @@ class SubcTransportPool {
14336
14787
  };
14337
14788
  abortSignal?.addEventListener("abort", onAbort, { once: true });
14338
14789
  try {
14339
- const requestTimeoutMs = reloadWaitedMs > 0 ? callDeadlineMs - reloadWaitedMs : timeoutMs;
14340
- const request = client.request(route, body, { timeoutMs: requestTimeoutMs, onProgress });
14790
+ const request = client.request(route, body, {
14791
+ timeoutMs: Math.ceil(requestTimeoutMs),
14792
+ onProgress
14793
+ });
14341
14794
  const reply = abortSignal ? await Promise.race([request, aborted]) : await request;
14342
14795
  if (!this.isCurrentSession(key, record)) {
14343
14796
  if (this.isReapInduced(record))
@@ -14358,8 +14811,11 @@ class SubcTransportPool {
14358
14811
  let reopened = false;
14359
14812
  let retriedAbsentRoute = false;
14360
14813
  while (true) {
14814
+ const requestTimeoutMs = remainingMs();
14815
+ if (requestTimeoutMs <= 0)
14816
+ throw callDeadlinePassedError(lastRefusal, callDeadlineMs);
14361
14817
  try {
14362
- const reply = await requestOnRoute(routeAndEntry.route, routeAndEntry.entry);
14818
+ const reply = await requestOnRoute(routeAndEntry.route, routeAndEntry.entry, requestTimeoutMs);
14363
14819
  if (reopened)
14364
14820
  this.resetRouteReopenBackoff();
14365
14821
  return reply;
@@ -14369,11 +14825,12 @@ class SubcTransportPool {
14369
14825
  const ownsRoute = this.isCurrentSession(key, record) && this.client === client;
14370
14826
  if (ownsRoute && isRouteRequestReloadRefusal(error)) {
14371
14827
  clearRouteEntry(routeAndEntry.entry);
14372
- if (reloadWaitedMs + this.nextRouteReopenDelayMs() >= reloadWaitBudgetMs) {
14828
+ lastRefusal = error;
14829
+ if (spentMs() + this.nextRouteReopenDelayMs() >= reloadWaitBudgetMs) {
14373
14830
  throw reloadWindowExhaustedError(error, callDeadlineMs);
14374
14831
  }
14375
14832
  const { delayMs, wait } = this.waitForRouteReopenBackoff();
14376
- reloadWaitedMs += delayMs;
14833
+ scheduledDelayMs = spentMs() + delayMs;
14377
14834
  await wait;
14378
14835
  routeAndEntry = await openRouteAfterReloadWindow();
14379
14836
  reopened = true;
@@ -14382,7 +14839,12 @@ class SubcTransportPool {
14382
14839
  if (ownsRoute && !retriedAbsentRoute && isRouteProvenAbsentError(error)) {
14383
14840
  retriedAbsentRoute = true;
14384
14841
  clearRouteEntry(routeAndEntry.entry);
14385
- await this.waitForRouteReopenBackoff().wait;
14842
+ if (spentMs() + this.nextRouteReopenDelayMs() >= callDeadlineMs) {
14843
+ throw callDeadlinePassedError(error, callDeadlineMs);
14844
+ }
14845
+ const { delayMs, wait } = this.waitForRouteReopenBackoff();
14846
+ scheduledDelayMs = spentMs() + delayMs;
14847
+ await wait;
14386
14848
  routeAndEntry = await openRouteAfterReloadWindow();
14387
14849
  reopened = true;
14388
14850
  continue;
@@ -16060,14 +16522,14 @@ function shouldInterruptWaitsForMessage(detachOnUserMessage, operatorText) {
16060
16522
  return detachOnUserMessage || containsStandaloneDetachKeyword(operatorText);
16061
16523
  }
16062
16524
  // ../aft-bridge/dist/migration.js
16063
- import { closeSync as closeSync3, existsSync as existsSync8, mkdirSync as mkdirSync3, openSync as openSync3, readFileSync as readFileSync6, renameSync as renameSync4, rmSync as rmSync2, statSync as statSync5, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "node:fs";
16525
+ import { closeSync as closeSync3, existsSync as existsSync8, mkdirSync as mkdirSync3, openSync as openSync3, readFileSync as readFileSync7, renameSync as renameSync4, rmSync as rmSync2, statSync as statSync6, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "node:fs";
16064
16526
  import { homedir as homedir10, tmpdir as tmpdir2 } from "node:os";
16065
- import { basename, dirname as dirname4, join as join10, resolve as resolve6 } from "node:path";
16527
+ import { basename as basename2, dirname as dirname5, join as join10, resolve as resolve6 } from "node:path";
16066
16528
 
16067
16529
  // ../aft-bridge/dist/paths.js
16068
- import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync4, renameSync as renameSync3, writeFileSync as writeFileSync2 } from "node:fs";
16530
+ import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync5, renameSync as renameSync3, writeFileSync as writeFileSync2 } from "node:fs";
16069
16531
  import { homedir as homedir8 } from "node:os";
16070
- import { dirname as dirname2, isAbsolute as isAbsolute3, join as join8, resolve as resolve5 } from "node:path";
16532
+ import { dirname as dirname3, isAbsolute as isAbsolute3, join as join8, resolve as resolve5 } from "node:path";
16071
16533
  function homeDir3() {
16072
16534
  if (process.platform === "win32")
16073
16535
  return process.env.USERPROFILE || process.env.HOME || homedir8();
@@ -16127,7 +16589,7 @@ function repairRootScopedStorageFile(storageRoot, harness, fileName) {
16127
16589
  if (existsSync6(harnessPath) || !existsSync6(rootPath))
16128
16590
  return harnessPath;
16129
16591
  try {
16130
- mkdirSync2(dirname2(harnessPath), { recursive: true });
16592
+ mkdirSync2(dirname3(harnessPath), { recursive: true });
16131
16593
  renameSync3(rootPath, harnessPath);
16132
16594
  } catch {}
16133
16595
  return harnessPath;
@@ -16139,7 +16601,7 @@ function shouldShowAnnouncement(storageRoot, harness, currentVersion) {
16139
16601
  let lastVersion = "";
16140
16602
  try {
16141
16603
  if (existsSync6(versionFile)) {
16142
- lastVersion = readFileSync4(versionFile, "utf-8").trim();
16604
+ lastVersion = readFileSync5(versionFile, "utf-8").trim();
16143
16605
  }
16144
16606
  } catch {
16145
16607
  return false;
@@ -16148,7 +16610,7 @@ function shouldShowAnnouncement(storageRoot, harness, currentVersion) {
16148
16610
  return false;
16149
16611
  if (!lastVersion) {
16150
16612
  try {
16151
- mkdirSync2(dirname2(versionFile), { recursive: true });
16613
+ mkdirSync2(dirname3(versionFile), { recursive: true });
16152
16614
  writeFileSync2(versionFile, currentVersion);
16153
16615
  } catch {}
16154
16616
  return false;
@@ -16160,7 +16622,7 @@ function markAnnouncementSeen(storageRoot, harness, currentVersion) {
16160
16622
  return;
16161
16623
  const versionFile = repairRootScopedStorageFile(storageRoot, harness, "last_announced_version");
16162
16624
  try {
16163
- mkdirSync2(dirname2(versionFile), { recursive: true });
16625
+ mkdirSync2(dirname3(versionFile), { recursive: true });
16164
16626
  writeFileSync2(versionFile, currentVersion);
16165
16627
  } catch {}
16166
16628
  }
@@ -16209,10 +16671,10 @@ function decodeFileUrl(target) {
16209
16671
  }
16210
16672
 
16211
16673
  // ../aft-bridge/dist/resolver.js
16212
- import { closeSync as closeSync2, existsSync as existsSync7, promises as fsPromises, openSync as openSync2, readFileSync as readFileSync5, readSync } from "node:fs";
16674
+ import { closeSync as closeSync2, existsSync as existsSync7, promises as fsPromises, openSync as openSync2, readFileSync as readFileSync6, readSync } from "node:fs";
16213
16675
  import { createRequire } from "node:module";
16214
16676
  import { homedir as homedir9 } from "node:os";
16215
- import { delimiter, dirname as dirname3, isAbsolute as isAbsolute4, join as join9 } from "node:path";
16677
+ import { delimiter, dirname as dirname4, isAbsolute as isAbsolute4, join as join9 } from "node:path";
16216
16678
  var ensureBinaryForResolver = ensureBinary;
16217
16679
  var npmPlatformPackageReader = null;
16218
16680
  var cacheCopiesInFlight = new Map;
@@ -16365,8 +16827,8 @@ function npmPlatformPackage(ext) {
16365
16827
  try {
16366
16828
  const req = createRequire(import.meta.url);
16367
16829
  const manifestPath = req.resolve(`@cortexkit/aft-${platformKey()}/package.json`);
16368
- const manifest = JSON.parse(readFileSync5(manifestPath, "utf8"));
16369
- const binaryPath = join9(dirname3(manifestPath), "bin", `aft${ext}`);
16830
+ const manifest = JSON.parse(readFileSync6(manifestPath, "utf8"));
16831
+ const binaryPath = join9(dirname4(manifestPath), "bin", `aft${ext}`);
16370
16832
  if (typeof manifest.version !== "string" || !existsSync7(binaryPath))
16371
16833
  return null;
16372
16834
  return { binaryPath, version: normalizeBareVersion(manifest.version) };
@@ -16620,7 +17082,7 @@ function acquireConfigMigrationLock(lockDir) {
16620
17082
  if (code !== "EEXIST")
16621
17083
  throw err;
16622
17084
  try {
16623
- const ageMs = Date.now() - statSync5(lockDir).mtimeMs;
17085
+ const ageMs = Date.now() - statSync6(lockDir).mtimeMs;
16624
17086
  if (ageMs > 60000) {
16625
17087
  rmSync2(lockDir, { recursive: true, force: true });
16626
17088
  continue;
@@ -16634,12 +17096,12 @@ function acquireConfigMigrationLock(lockDir) {
16634
17096
  }
16635
17097
  }
16636
17098
  function atomicCopyConfigFile(sourcePath, targetPath) {
16637
- mkdirSync3(dirname4(targetPath), { recursive: true });
16638
- const tmpPath = join10(dirname4(targetPath), `.${basename(targetPath)}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
17099
+ mkdirSync3(dirname5(targetPath), { recursive: true });
17100
+ const tmpPath = join10(dirname5(targetPath), `.${basename2(targetPath)}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
16639
17101
  let fd = null;
16640
17102
  try {
16641
17103
  fd = openSync3(tmpPath, "wx", 384);
16642
- writeFileSync3(fd, readFileSync6(sourcePath));
17104
+ writeFileSync3(fd, readFileSync7(sourcePath));
16643
17105
  closeSync3(fd);
16644
17106
  fd = null;
16645
17107
  renameSync4(tmpPath, targetPath);
@@ -16656,8 +17118,8 @@ function atomicCopyConfigFile(sourcePath, targetPath) {
16656
17118
  }
16657
17119
  }
16658
17120
  function atomicWriteConfigFile(targetPath, content) {
16659
- mkdirSync3(dirname4(targetPath), { recursive: true });
16660
- const tmpPath = join10(dirname4(targetPath), `.${basename(targetPath)}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
17121
+ mkdirSync3(dirname5(targetPath), { recursive: true });
17122
+ const tmpPath = join10(dirname5(targetPath), `.${basename2(targetPath)}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
16661
17123
  let fd = null;
16662
17124
  try {
16663
17125
  fd = openSync3(tmpPath, "wx", 384);
@@ -16718,11 +17180,11 @@ function markLegacySourcesMovedAside(sources, targetPath, logger) {
16718
17180
  const desiredMarkerPath = `${source.path}${MOVED_MARKER_SUFFIX}`;
16719
17181
  const markerPath = nonClobberingSidecarPath(desiredMarkerPath);
16720
17182
  try {
16721
- const original = readFileSync6(source.path, "utf-8");
17183
+ const original = readFileSync7(source.path, "utf-8");
16722
17184
  if (markerPath !== desiredMarkerPath) {
16723
17185
  logger?.warn?.(`Preserving existing legacy AFT marker ${desiredMarkerPath}; writing new marker to ${markerPath}`);
16724
17186
  }
16725
- atomicWriteConfigFile(markerPath, movedMarkerContent(targetPath, basename(source.path), original));
17187
+ atomicWriteConfigFile(markerPath, movedMarkerContent(targetPath, basename2(source.path), original));
16726
17188
  unlinkSync3(source.path);
16727
17189
  info?.(`Moved legacy AFT config ${source.path} aside to ${markerPath}; AFT now reads ${targetPath}`);
16728
17190
  } catch (err) {
@@ -16782,15 +17244,15 @@ function migrateAftConfigFile(opts) {
16782
17244
  if (existingSources.length === 0) {
16783
17245
  return { migrated: false, conflict: false, targetPath: opts.targetPath, warnings };
16784
17246
  }
16785
- mkdirSync3(dirname4(opts.targetPath), { recursive: true });
17247
+ mkdirSync3(dirname5(opts.targetPath), { recursive: true });
16786
17248
  const release = acquireConfigMigrationLock(`${opts.targetPath}.lock`);
16787
17249
  try {
16788
17250
  const sources = existingSources.map((source) => ({
16789
17251
  ...source,
16790
- content: readFileSync6(source.path, "utf-8")
17252
+ content: readFileSync7(source.path, "utf-8")
16791
17253
  }));
16792
17254
  if (existsSync8(opts.targetPath)) {
16793
- const targetContent = readFileSync6(opts.targetPath, "utf-8");
17255
+ const targetContent = readFileSync7(opts.targetPath, "utf-8");
16794
17256
  for (const source of sources) {
16795
17257
  if (fileSemanticsMatch(source.content, targetContent))
16796
17258
  continue;
@@ -16841,11 +17303,11 @@ function spawnErrorLabel(error) {
16841
17303
  function migrationLogPath(newRoot, harness, logger) {
16842
17304
  const desired = join10(newRoot, "logs", "migration", `${harness}-${Date.now()}.jsonl`);
16843
17305
  try {
16844
- mkdirSync3(dirname4(desired), { recursive: true });
17306
+ mkdirSync3(dirname5(desired), { recursive: true });
16845
17307
  return desired;
16846
17308
  } catch (err) {
16847
17309
  const fallback = join10(tmpdir2(), `aft-migration-${harness}-${Date.now()}.jsonl`);
16848
- logger?.warn?.(`Failed to create AFT migration log directory ${dirname4(desired)}: ${err instanceof Error ? err.message : String(err)}. ` + `Using fallback log path ${fallback}.`);
17310
+ logger?.warn?.(`Failed to create AFT migration log directory ${dirname5(desired)}: ${err instanceof Error ? err.message : String(err)}. ` + `Using fallback log path ${fallback}.`);
16849
17311
  return fallback;
16850
17312
  }
16851
17313
  }
@@ -16906,9 +17368,9 @@ async function ensureStorageMigrated(opts) {
16906
17368
  throw new Error(`AFT storage migration failed (${detail}). ` + `Harness: ${opts.harness}. Legacy: ${legacyRoot}. Target: ${newRoot}. ` + `See log: ${logPath}. ` + `Plugin load aborted to prevent legacy/new state divergence.` + (stderrTail ? ` Stderr tail: ${stderrTail}` : "") + (stdoutTail ? ` Stdout tail: ${stdoutTail}` : ""));
16907
17369
  }
16908
17370
  // ../aft-bridge/dist/migration-notices.js
16909
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync7, renameSync as renameSync5, rmdirSync, statSync as statSync6, writeFileSync as writeFileSync4 } from "node:fs";
17371
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync8, renameSync as renameSync5, rmdirSync, statSync as statSync7, writeFileSync as writeFileSync4 } from "node:fs";
16910
17372
  import { homedir as homedir11 } from "node:os";
16911
- import { dirname as dirname5, join as join11, resolve as resolve7 } from "node:path";
17373
+ import { dirname as dirname6, join as join11, resolve as resolve7 } from "node:path";
16912
17374
  function migrationNoticeStorePath(env = process.env) {
16913
17375
  const stateHome = env.XDG_STATE_HOME && env.XDG_STATE_HOME.length > 0 ? env.XDG_STATE_HOME : join11(homedir11(), ".local", "state");
16914
17376
  return join11(stateHome, "cortexkit", "aft", "migration-notices.json");
@@ -16918,7 +17380,7 @@ function noticeKey(configPath, digest) {
16918
17380
  }
16919
17381
  function readRecords(storePath) {
16920
17382
  try {
16921
- const parsed = JSON.parse(readFileSync7(storePath, "utf8"));
17383
+ const parsed = JSON.parse(readFileSync8(storePath, "utf8"));
16922
17384
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
16923
17385
  } catch {
16924
17386
  return {};
@@ -16927,7 +17389,7 @@ function readRecords(storePath) {
16927
17389
  var LOCK_STALE_MS = 1e4;
16928
17390
  function withStoreLock(storePath, action) {
16929
17391
  const lockPath = `${storePath}.lock`;
16930
- mkdirSync4(dirname5(storePath), { recursive: true });
17392
+ mkdirSync4(dirname6(storePath), { recursive: true });
16931
17393
  const deadline = Date.now() + 2000;
16932
17394
  for (;; ) {
16933
17395
  try {
@@ -16937,7 +17399,7 @@ function withStoreLock(storePath, action) {
16937
17399
  if (error.code !== "EEXIST")
16938
17400
  throw error;
16939
17401
  try {
16940
- if (Date.now() - statSync6(lockPath).mtimeMs > LOCK_STALE_MS)
17402
+ if (Date.now() - statSync7(lockPath).mtimeMs > LOCK_STALE_MS)
16941
17403
  rmdirSync(lockPath);
16942
17404
  } catch {}
16943
17405
  if (Date.now() > deadline)
@@ -16978,9 +17440,9 @@ function deliverMigrationNoticeOnce(options) {
16978
17440
  }
16979
17441
  }
16980
17442
  // ../aft-bridge/dist/npm-resolver.js
16981
- import { readdirSync as readdirSync2, statSync as statSync7 } from "node:fs";
17443
+ import { readdirSync as readdirSync2, statSync as statSync8 } from "node:fs";
16982
17444
  import { homedir as homedir12 } from "node:os";
16983
- import { dirname as dirname6, isAbsolute as isAbsolute5, join as join12 } from "node:path";
17445
+ import { dirname as dirname7, isAbsolute as isAbsolute5, join as join12 } from "node:path";
16984
17446
  function defaultDeps() {
16985
17447
  return {
16986
17448
  platform: process.platform,
@@ -16994,7 +17456,7 @@ function npmBinaryName(platform) {
16994
17456
  }
16995
17457
  function isFile(p) {
16996
17458
  try {
16997
- return statSync7(p).isFile();
17459
+ return statSync8(p).isFile();
16998
17460
  } catch {
16999
17461
  return false;
17000
17462
  }
@@ -17015,7 +17477,7 @@ function npmFromPath(deps) {
17015
17477
  return null;
17016
17478
  }
17017
17479
  function npmAdjacentToNode(deps) {
17018
- const dir = dirname6(deps.execPath);
17480
+ const dir = dirname7(deps.execPath);
17019
17481
  return isFile(join12(dir, npmBinaryName(deps.platform))) ? dir : null;
17020
17482
  }
17021
17483
  function highestVersionedNodeBin(installsDir, name) {
@@ -17400,8 +17862,8 @@ function probeOnnxRuntimeLoadable(libPath, arch = process.arch) {
17400
17862
  }
17401
17863
  // ../aft-bridge/dist/onnx-runtime.js
17402
17864
  import { createHash as createHash4, randomBytes as randomBytes2 } from "node:crypto";
17403
- import { chmodSync as chmodSync2, closeSync as closeSync5, copyFileSync as copyFileSync2, createWriteStream as createWriteStream2, existsSync as existsSync11, lstatSync, mkdirSync as mkdirSync5, openSync as openSync5, readdirSync as readdirSync3, readFileSync as readFileSync8, readlinkSync, realpathSync as realpathSync3, renameSync as renameSync6, rmSync as rmSync3, statSync as statSync8, symlinkSync, unlinkSync as unlinkSync4, writeFileSync as writeFileSync5 } from "node:fs";
17404
- import { basename as basename2, dirname as dirname7, isAbsolute as isAbsolute6, join as join14, relative as relative2, resolve as resolve8, win32 } from "node:path";
17865
+ import { chmodSync as chmodSync2, closeSync as closeSync5, copyFileSync as copyFileSync2, createWriteStream as createWriteStream2, existsSync as existsSync11, lstatSync, mkdirSync as mkdirSync5, openSync as openSync5, readdirSync as readdirSync3, readFileSync as readFileSync9, readlinkSync, realpathSync as realpathSync3, renameSync as renameSync6, rmSync as rmSync3, statSync as statSync9, symlinkSync, unlinkSync as unlinkSync4, writeFileSync as writeFileSync5 } from "node:fs";
17866
+ import { basename as basename3, dirname as dirname8, isAbsolute as isAbsolute6, join as join14, relative as relative2, resolve as resolve8, win32 } from "node:path";
17405
17867
  import { Readable as Readable2 } from "node:stream";
17406
17868
  import { pipeline as pipeline2 } from "node:stream/promises";
17407
17869
 
@@ -17605,7 +18067,7 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
17605
18067
  abandoned = true;
17606
18068
  } else {
17607
18069
  try {
17608
- const ageMs = Date.now() - statSync8(stagingDir).mtimeMs;
18070
+ const ageMs = Date.now() - statSync9(stagingDir).mtimeMs;
17609
18071
  abandoned = ageMs > STALE_LOCK_MS;
17610
18072
  } catch {
17611
18073
  abandoned = true;
@@ -17642,7 +18104,7 @@ var REQUIRED_ORT_MAJOR = 1;
17642
18104
  var REQUIRED_ORT_MIN_MINOR = 20;
17643
18105
  var INVALID_ORT_VERSION = "<invalid>";
17644
18106
  function parseOnnxVersionFromPath(value) {
17645
- const name = basename2(value);
18107
+ const name = basename3(value);
17646
18108
  const semverish = name.match(/(?:^|[._-])(\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?)(?:\.(?:dylib|dll))?$/);
17647
18109
  if (semverish)
17648
18110
  return semverish[1].split(/[-+]/, 1)[0];
@@ -17847,7 +18309,7 @@ async function downloadFileWithCap(url, destPath) {
17847
18309
  if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES2) {
17848
18310
  throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES2}`);
17849
18311
  }
17850
- mkdirSync5(dirname7(destPath), { recursive: true });
18312
+ mkdirSync5(dirname8(destPath), { recursive: true });
17851
18313
  let bytesWritten = 0;
17852
18314
  const guard = new TransformStream({
17853
18315
  transform(chunk, transformController) {
@@ -17881,7 +18343,7 @@ function validateExtractedTree(stagingRoot) {
17881
18343
  const lst = lstatSync(fullPath);
17882
18344
  if (lst.isSymbolicLink()) {
17883
18345
  const linkTarget = readlinkSync(fullPath);
17884
- const resolvedTarget = resolve8(dirname7(fullPath), linkTarget);
18346
+ const resolvedTarget = resolve8(dirname8(fullPath), linkTarget);
17885
18347
  const rel = relative2(realRoot, resolvedTarget);
17886
18348
  if (rel.startsWith("..") || isAbsolute6(rel) || win32.isAbsolute(rel)) {
17887
18349
  throw new Error(`extracted symlink ${fullPath} points outside staging root: ${linkTarget}`);
@@ -18017,7 +18479,7 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
18017
18479
  unlinkSync4(dst);
18018
18480
  } catch {}
18019
18481
  const dstForContainment = join14(targetRoot, link.name);
18020
- const resolvedTarget = resolve8(dirname7(dstForContainment), link.target);
18482
+ const resolvedTarget = resolve8(dirname8(dstForContainment), link.target);
18021
18483
  if (!isPathInsideRoot(targetRoot, resolvedTarget)) {
18022
18484
  const message = `ONNX Runtime symlink ${link.name} points outside install dir: ${link.target}`;
18023
18485
  if (requiredLibs.has(link.name)) {
@@ -18069,9 +18531,9 @@ function writeOnnxInstalledMeta(installDir, version, sha256, archiveSha256) {
18069
18531
  function readOnnxInstalledMeta(installDir) {
18070
18532
  const path = join14(installDir, ONNX_INSTALLED_META_FILE);
18071
18533
  try {
18072
- if (!statSync8(path).isFile())
18534
+ if (!statSync9(path).isFile())
18073
18535
  return null;
18074
- const raw = readFileSync8(path, "utf8");
18536
+ const raw = readFileSync9(path, "utf8");
18075
18537
  const parsed = JSON.parse(raw);
18076
18538
  if (typeof parsed.version !== "string" || parsed.version.length === 0)
18077
18539
  return null;
@@ -18087,7 +18549,7 @@ function readOnnxInstalledMeta(installDir) {
18087
18549
  }
18088
18550
  function sha256File2(path) {
18089
18551
  const hash = createHash4("sha256");
18090
- hash.update(readFileSync8(path));
18552
+ hash.update(readFileSync9(path));
18091
18553
  return hash.digest("hex");
18092
18554
  }
18093
18555
  function acquireLock(lockPath) {
@@ -18115,12 +18577,12 @@ ${new Date().toISOString()}
18115
18577
  let owningPid = null;
18116
18578
  let lockMtimeMs = 0;
18117
18579
  try {
18118
- const raw = readFileSync8(lockPath, "utf8");
18580
+ const raw = readFileSync9(lockPath, "utf8");
18119
18581
  const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
18120
18582
  const parsed = Number.parseInt(firstLine, 10);
18121
18583
  if (Number.isFinite(parsed) && parsed > 0)
18122
18584
  owningPid = parsed;
18123
- lockMtimeMs = statSync8(lockPath).mtimeMs;
18585
+ lockMtimeMs = statSync9(lockPath).mtimeMs;
18124
18586
  } catch {
18125
18587
  return tryClaim();
18126
18588
  }
@@ -18140,7 +18602,7 @@ function releaseLock(lockPath) {
18140
18602
  try {
18141
18603
  let owningPid = null;
18142
18604
  try {
18143
- const raw = readFileSync8(lockPath, "utf8");
18605
+ const raw = readFileSync9(lockPath, "utf8");
18144
18606
  const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
18145
18607
  const parsed = Number.parseInt(firstLine, 10);
18146
18608
  if (Number.isFinite(parsed) && parsed > 0)
@@ -19880,7 +20342,7 @@ import {
19880
20342
  // package.json
19881
20343
  var package_default = {
19882
20344
  name: "@cortexkit/aft-pi",
19883
- version: "0.58.0",
20345
+ version: "0.58.1",
19884
20346
  type: "module",
19885
20347
  description: "Pi coding agent extension for Agent File Tools (AFT) — tree-sitter and LSP-powered code analysis",
19886
20348
  main: "dist/index.js",
@@ -19903,30 +20365,32 @@ var package_default = {
19903
20365
  "test:unit": "bun test src/__tests__ --path-ignore-patterns 'src/__tests__/e2e/**'"
19904
20366
  },
19905
20367
  dependencies: {
19906
- "@cortexkit/aft-bridge": "0.58.0",
20368
+ "@cortexkit/aft-bridge": "0.58.1",
19907
20369
  "comment-json": "^5.0.0",
19908
20370
  diff: "^8.0.4",
19909
- typebox: "1.1.38",
19910
20371
  zod: "^4.4.3"
19911
20372
  },
19912
20373
  optionalDependencies: {
19913
- "@cortexkit/aft-darwin-arm64": "0.58.0",
19914
- "@cortexkit/aft-darwin-x64": "0.58.0",
19915
- "@cortexkit/aft-linux-arm64": "0.58.0",
19916
- "@cortexkit/aft-linux-x64": "0.58.0",
19917
- "@cortexkit/aft-win32-arm64": "0.58.0",
19918
- "@cortexkit/aft-win32-x64": "0.58.0"
20374
+ "@cortexkit/aft-darwin-arm64": "0.58.1",
20375
+ "@cortexkit/aft-darwin-x64": "0.58.1",
20376
+ "@cortexkit/aft-linux-arm64": "0.58.1",
20377
+ "@cortexkit/aft-linux-x64": "0.58.1",
20378
+ "@cortexkit/aft-win32-arm64": "0.58.1",
20379
+ "@cortexkit/aft-win32-x64": "0.58.1"
19919
20380
  },
19920
20381
  devDependencies: {
19921
20382
  "@earendil-works/pi-coding-agent": "^0.80.2",
19922
20383
  "@earendil-works/pi-ai": "^0.80.2",
19923
20384
  "@earendil-works/pi-tui": "^0.80.2",
19924
20385
  "@types/node": "^22.0.0",
20386
+ typebox: "1.1.38",
19925
20387
  typescript: "^5.8.0"
19926
20388
  },
19927
20389
  peerDependencies: {
20390
+ "@earendil-works/pi-ai": "*",
19928
20391
  "@earendil-works/pi-coding-agent": "*",
19929
- "@earendil-works/pi-tui": "*"
20392
+ "@earendil-works/pi-tui": "*",
20393
+ typebox: "*"
19930
20394
  },
19931
20395
  exports: {
19932
20396
  ".": {
@@ -20623,7 +21087,7 @@ function registerStatusCommand(pi, ctx) {
20623
21087
  }
20624
21088
 
20625
21089
  // src/config.ts
20626
- import { existsSync as existsSync13, readFileSync as readFileSync9, renameSync as renameSync7, unlinkSync as unlinkSync5, writeFileSync as writeFileSync6 } from "node:fs";
21090
+ import { existsSync as existsSync13, readFileSync as readFileSync10, renameSync as renameSync7, unlinkSync as unlinkSync5, writeFileSync as writeFileSync6 } from "node:fs";
20627
21091
  import { createRequire as createRequire2 } from "node:module";
20628
21092
  import { homedir as homedir13 } from "node:os";
20629
21093
  import { isAbsolute as isAbsolute7 } from "node:path";
@@ -25436,7 +25900,7 @@ var IndexConfigSchema = object({
25436
25900
  roots: array(IndexRootSchema).optional()
25437
25901
  });
25438
25902
  var SemanticConfigSchema = object({
25439
- backend: _enum(["fastembed", "openai_compatible", "ollama"]).optional(),
25903
+ backend: _enum(["fastembed", "openai_compatible", "ollama", "synapse"]).optional(),
25440
25904
  model: string2().trim().min(1).optional(),
25441
25905
  base_url: string2().trim().min(1).optional(),
25442
25906
  api_key_env: string2().trim().min(1).optional(),
@@ -25447,6 +25911,17 @@ var SemanticConfigSchema = object({
25447
25911
  max_input_tokens: number2().int().positive().optional(),
25448
25912
  max_files: number2().int().positive().optional()
25449
25913
  });
25914
+ var RerankConfigSchema = object({
25915
+ backend: _enum(["off", "onnx", "remote", "synapse"]).optional(),
25916
+ model: string2().trim().min(1).optional(),
25917
+ endpoint: string2().trim().min(1).optional(),
25918
+ api_key_env: string2().trim().min(1).optional(),
25919
+ top_n: number2().int().positive().optional(),
25920
+ timeout_ms: number2().int().positive().optional()
25921
+ });
25922
+ var SearchConfigSchema = object({
25923
+ rerank: RerankConfigSchema.optional()
25924
+ });
25450
25925
  var LspExtensionSchema = string2().trim().min(1).refine((value) => value.replace(/^\.+/, "").length > 0, {
25451
25926
  message: "Extension must include characters other than leading dots"
25452
25927
  });
@@ -25582,6 +26057,7 @@ var AftConfigFieldsSchema = object({
25582
26057
  lsp: LspConfigSchema.optional(),
25583
26058
  url_fetch_allow_private: boolean2().optional(),
25584
26059
  semantic: SemanticConfigSchema.optional(),
26060
+ search: SearchConfigSchema.optional(),
25585
26061
  bridge: BridgeConfigSchema.optional(),
25586
26062
  subc: SubcConfigSchema.optional(),
25587
26063
  github: GithubConfigSchema.optional(),
@@ -25729,7 +26205,7 @@ function migrateAftConfigFile2(configPath, logger = { log: log2, warn: warn2 })
25729
26205
  let tmpPath = null;
25730
26206
  let oldKeys = [];
25731
26207
  try {
25732
- const content = readFileSync9(configPath, "utf-8");
26208
+ const content = readFileSync10(configPath, "utf-8");
25733
26209
  const rawConfig = import_comment_json.parse(content);
25734
26210
  if (!rawConfig || typeof rawConfig !== "object" || Array.isArray(rawConfig)) {
25735
26211
  return { migrated: false, oldKeys: [] };
@@ -25764,6 +26240,18 @@ ${serialized}` : serialized;
25764
26240
  }
25765
26241
  }
25766
26242
  var configLoadErrors = [];
26243
+ var configValidationErrors = [];
26244
+ var configLoadSources = [];
26245
+ var configLoadTexts = new Map;
26246
+ function getConfigLoadSources() {
26247
+ return configLoadSources;
26248
+ }
26249
+ function getConfigLoadTexts() {
26250
+ return configLoadTexts;
26251
+ }
26252
+ function getConfigValidationErrors() {
26253
+ return configValidationErrors;
26254
+ }
25767
26255
  function getConfigLoadErrors() {
25768
26256
  return configLoadErrors;
25769
26257
  }
@@ -25808,7 +26296,9 @@ function loadConfigFromPath(configPath, tier) {
25808
26296
  try {
25809
26297
  if (!existsSync13(configPath))
25810
26298
  return null;
25811
- const content = readFileSync9(configPath, "utf-8");
26299
+ const content = readFileSync10(configPath, "utf-8");
26300
+ configLoadSources.push(configPath);
26301
+ configLoadTexts.set(configPath, content);
25812
26302
  const rawConfig = import_comment_json.parse(content);
25813
26303
  if (!rawConfig || typeof rawConfig !== "object" || Array.isArray(rawConfig)) {
25814
26304
  recordConfigParseFailure(configPath, "root must be an object");
@@ -25858,6 +26348,7 @@ function loadConfigFromPath(configPath, tier) {
25858
26348
  } else {
25859
26349
  const errorMsg = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ");
25860
26350
  warn2(`Config validation error in ${configPath}: ${errorMsg}`);
26351
+ configValidationErrors.push({ path: configPath, message: errorMsg });
25861
26352
  parsed = parseConfigPartially(cleanConfig);
25862
26353
  }
25863
26354
  if (tier === "user" && parsed.disabled_tools === undefined) {
@@ -26068,14 +26559,33 @@ function getStrippedTopLevelKeys(override) {
26068
26559
  for (const tool of partitionProjectDisables(override.disabled_tools).ignored) {
26069
26560
  stripped.push(`disabled_tools.${tool}`);
26070
26561
  }
26562
+ stripped.push(...projectRerankStrippedKeys(override.search?.rerank));
26071
26563
  return stripped;
26072
26564
  }
26565
+ function projectRerankStrippedKeys(rerank) {
26566
+ if (rerank === undefined)
26567
+ return [];
26568
+ const stripped = [];
26569
+ if (rerank.backend !== undefined && rerank.backend !== "off")
26570
+ stripped.push("search.rerank.backend");
26571
+ for (const key of ["model", "endpoint", "api_key_env", "top_n", "timeout_ms"]) {
26572
+ if (rerank[key] !== undefined)
26573
+ stripped.push(`search.rerank.${key}`);
26574
+ }
26575
+ return stripped;
26576
+ }
26577
+ function mergeProjectSearchConfig(base, project) {
26578
+ if (project?.rerank?.backend !== "off")
26579
+ return base;
26580
+ return { ...base, rerank: { ...base?.rerank, backend: "off" } };
26581
+ }
26073
26582
  function mergeConfigs(base, override) {
26074
26583
  const disabledTools = unionDisabledTools(base.disabled_tools, partitionProjectDisables(override.disabled_tools).accepted);
26075
26584
  const indexes = mergeIndexes(base.indexes, override.indexes, true);
26076
26585
  const formatter = { ...base.formatter, ...override.formatter };
26077
26586
  const checker = { ...base.checker, ...override.checker };
26078
26587
  const semantic = mergeSemanticConfig(base.semantic, override.semantic);
26588
+ const search = mergeProjectSearchConfig(base.search, override.search);
26079
26589
  const lsp = mergeLspConfig(base.lsp, override.lsp);
26080
26590
  const experimental = mergeExperimentalConfig(base.experimental, override.experimental);
26081
26591
  const bash = mergeBashConfig(base.bash, override.bash);
@@ -26105,6 +26615,7 @@ function mergeConfigs(base, override) {
26105
26615
  ...pi !== undefined ? { pi } : {},
26106
26616
  experimental,
26107
26617
  semantic,
26618
+ search,
26108
26619
  ...bridge !== undefined ? { bridge } : {},
26109
26620
  ...indexes !== undefined ? { indexes } : {},
26110
26621
  ...disabledTools !== undefined ? { disabled_tools: disabledTools } : {}
@@ -26163,6 +26674,9 @@ function buildConfigTierConfigureParams(projectDirectory, processState = {}) {
26163
26674
  }
26164
26675
  function loadAftConfig(projectDirectory) {
26165
26676
  configLoadErrors = [];
26677
+ configValidationErrors = [];
26678
+ configLoadSources = [];
26679
+ configLoadTexts = new Map;
26166
26680
  configLoadNotices = [];
26167
26681
  semanticInputSupplied = false;
26168
26682
  const { userConfigPath, projectConfigPath } = resolveAftConfigPaths(projectDirectory);
@@ -26213,10 +26727,10 @@ import {
26213
26727
  createReadStream as createReadStream2,
26214
26728
  existsSync as existsSync15,
26215
26729
  mkdirSync as mkdirSync7,
26216
- readFileSync as readFileSync12,
26730
+ readFileSync as readFileSync13,
26217
26731
  renameSync as renameSync9,
26218
26732
  rmSync as rmSync5,
26219
- statSync as statSync10,
26733
+ statSync as statSync11,
26220
26734
  writeFileSync as writeFileSync8
26221
26735
  } from "node:fs";
26222
26736
  import { join as join17 } from "node:path";
@@ -26227,10 +26741,10 @@ import {
26227
26741
  closeSync as closeSync6,
26228
26742
  mkdirSync as mkdirSync6,
26229
26743
  openSync as openSync6,
26230
- readFileSync as readFileSync10,
26744
+ readFileSync as readFileSync11,
26231
26745
  renameSync as renameSync8,
26232
26746
  rmSync as rmSync4,
26233
- statSync as statSync9,
26747
+ statSync as statSync10,
26234
26748
  unlinkSync as unlinkSync6,
26235
26749
  writeFileSync as writeFileSync7
26236
26750
  } from "node:fs";
@@ -26253,7 +26767,7 @@ function lspBinDir(npmPackage) {
26253
26767
  function isInstalled(npmPackage, binary) {
26254
26768
  for (const candidate of lspBinaryCandidates(binary)) {
26255
26769
  try {
26256
- if (statSync9(join15(lspBinDir(npmPackage), candidate)).isFile())
26770
+ if (statSync10(join15(lspBinDir(npmPackage), candidate)).isFile())
26257
26771
  return true;
26258
26772
  } catch {}
26259
26773
  }
@@ -26281,9 +26795,9 @@ function writeInstalledMetaIn(installDir, version, sha256) {
26281
26795
  function readInstalledMetaIn(installDir) {
26282
26796
  const path = join15(installDir, INSTALLED_META_FILE);
26283
26797
  try {
26284
- if (!statSync9(path).isFile())
26798
+ if (!statSync10(path).isFile())
26285
26799
  return null;
26286
- const raw = readFileSync10(path, "utf8");
26800
+ const raw = readFileSync11(path, "utf8");
26287
26801
  const parsed = JSON.parse(raw);
26288
26802
  if (typeof parsed.version !== "string" || parsed.version.length === 0)
26289
26803
  return null;
@@ -26352,12 +26866,12 @@ ${token}
26352
26866
  let owningPid = null;
26353
26867
  let lockMtimeMs = 0;
26354
26868
  try {
26355
- const raw = readFileSync10(lock, "utf8");
26869
+ const raw = readFileSync11(lock, "utf8");
26356
26870
  const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
26357
26871
  const parsed = Number.parseInt(firstLine, 10);
26358
26872
  if (Number.isFinite(parsed) && parsed > 0)
26359
26873
  owningPid = parsed;
26360
- lockMtimeMs = statSync9(lock).mtimeMs;
26874
+ lockMtimeMs = statSync10(lock).mtimeMs;
26361
26875
  } catch {
26362
26876
  return tryClaim();
26363
26877
  }
@@ -26391,7 +26905,7 @@ function releaseInstallLock(lockKey, acquisitionToken) {
26391
26905
  let owningPid = null;
26392
26906
  let owningToken = "";
26393
26907
  try {
26394
- const raw = readFileSync10(lock, "utf8");
26908
+ const raw = readFileSync11(lock, "utf8");
26395
26909
  const lines = raw.split(/\r?\n/);
26396
26910
  const parsed = Number.parseInt(lines[0]?.trim() ?? "", 10);
26397
26911
  if (Number.isFinite(parsed) && parsed > 0)
@@ -26438,7 +26952,7 @@ function autoInstallStampPath() {
26438
26952
  }
26439
26953
  function hasRecentAutoInstallStamp(path) {
26440
26954
  try {
26441
- const parsed = JSON.parse(readFileSync10(path, "utf8"));
26955
+ const parsed = JSON.parse(readFileSync11(path, "utf8"));
26442
26956
  return typeof parsed.lastRunMs === "number" && Number.isFinite(parsed.lastRunMs) && Math.abs(Date.now() - parsed.lastRunMs) < AUTO_INSTALL_WINDOW_MS;
26443
26957
  } catch {
26444
26958
  return false;
@@ -26446,7 +26960,7 @@ function hasRecentAutoInstallStamp(path) {
26446
26960
  }
26447
26961
  function readAutoInstallLockOwner(path) {
26448
26962
  try {
26449
- const parsed = JSON.parse(readFileSync10(path, "utf8"));
26963
+ const parsed = JSON.parse(readFileSync11(path, "utf8"));
26450
26964
  if (typeof parsed.pid !== "number" || typeof parsed.startedMs !== "number" || typeof parsed.token !== "string") {
26451
26965
  return null;
26452
26966
  }
@@ -26459,7 +26973,7 @@ function autoInstallLockIsStale(path) {
26459
26973
  const owner = readAutoInstallLockOwner(path);
26460
26974
  let age = Number.POSITIVE_INFINITY;
26461
26975
  try {
26462
- age = Math.abs(Date.now() - statSync9(path).mtimeMs);
26976
+ age = Math.abs(Date.now() - statSync10(path).mtimeMs);
26463
26977
  } catch {
26464
26978
  return true;
26465
26979
  }
@@ -26537,7 +27051,7 @@ var VERSION_CHECK_FILE = ".aft-version-check";
26537
27051
  function readVersionCheck(npmPackage) {
26538
27052
  const file = join15(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
26539
27053
  try {
26540
- const raw = readFileSync10(file, "utf8");
27054
+ const raw = readFileSync11(file, "utf8");
26541
27055
  const parsed = JSON.parse(raw);
26542
27056
  if (typeof parsed.last_checked === "string") {
26543
27057
  return {
@@ -26640,6 +27154,13 @@ var NPM_LSP_TABLE = [
26640
27154
  extensions: ["ts", "tsx", "js", "jsx", "mjs", "cjs", "mts", "cts"],
26641
27155
  rootMarkers: ["tsconfig.json", "jsconfig.json", "package.json"]
26642
27156
  },
27157
+ {
27158
+ id: "typescript-sdk",
27159
+ npm: "typescript",
27160
+ binary: "tsserver",
27161
+ extensions: ["ts", "tsx", "js", "jsx", "mjs", "cjs", "mts", "cts"],
27162
+ rootMarkers: ["tsconfig.json", "jsconfig.json", "package.json"]
27163
+ },
26643
27164
  {
26644
27165
  id: "python",
26645
27166
  npm: "pyright",
@@ -26714,7 +27235,7 @@ var NPM_LSP_TABLE = [
26714
27235
  ];
26715
27236
 
26716
27237
  // src/lsp-project-relevance.ts
26717
- import { existsSync as existsSync14, readdirSync as readdirSync4, readFileSync as readFileSync11 } from "node:fs";
27238
+ import { existsSync as existsSync14, readdirSync as readdirSync4, readFileSync as readFileSync12 } from "node:fs";
26718
27239
  import { join as join16 } from "node:path";
26719
27240
  var MAX_WALK_DIRS = 200;
26720
27241
  var MAX_WALK_DEPTH = 4;
@@ -26756,7 +27277,7 @@ function hasPackageJsonDep(projectRoot, depNames) {
26756
27277
  }
26757
27278
  function readPackageJson(projectRoot) {
26758
27279
  try {
26759
- const raw = readFileSync11(join16(projectRoot, "package.json"), "utf8");
27280
+ const raw = readFileSync12(join16(projectRoot, "package.json"), "utf8");
26760
27281
  const parsed = JSON.parse(raw);
26761
27282
  if (typeof parsed !== "object" || parsed === null)
26762
27283
  return null;
@@ -27104,7 +27625,7 @@ function hashInstalledBinary(spec) {
27104
27625
  let pathToHash = null;
27105
27626
  for (const p of candidates) {
27106
27627
  try {
27107
- if (statSync10(p).isFile()) {
27628
+ if (statSync11(p).isFile()) {
27108
27629
  pathToHash = p;
27109
27630
  break;
27110
27631
  }
@@ -27130,14 +27651,14 @@ function installedBinaryPath(spec) {
27130
27651
  ] : [lspBinaryPath(spec.npm, spec.binary)];
27131
27652
  for (const candidate of candidates) {
27132
27653
  try {
27133
- if (statSync10(candidate).isFile())
27654
+ if (statSync11(candidate).isFile())
27134
27655
  return candidate;
27135
27656
  } catch {}
27136
27657
  }
27137
27658
  return null;
27138
27659
  }
27139
27660
  function sha256OfFileSync(path) {
27140
- return createHash5("sha256").update(readFileSync12(path)).digest("hex");
27661
+ return createHash5("sha256").update(readFileSync13(path)).digest("hex");
27141
27662
  }
27142
27663
  function quarantineCachedNpmInstall(spec, reason) {
27143
27664
  const packageDir = lspPackageDir(spec.npm);
@@ -27180,7 +27701,7 @@ function runAutoInstall(projectRoot, config, fetchImpl = fetch) {
27180
27701
  if (isInstalled(spec.npm, spec.binary) && validateCachedNpmInstall(spec)) {
27181
27702
  cachedBinDirs.push(lspBinDir(spec.npm));
27182
27703
  }
27183
- if (config.disabled.has(spec.id)) {
27704
+ if (config.disabled.has(spec.id) || spec.id === "typescript-sdk" && config.disabled.has("typescript")) {
27184
27705
  skipped.push({ id: spec.id, reason: "disabled by config" });
27185
27706
  continue;
27186
27707
  }
@@ -27235,16 +27756,16 @@ import {
27235
27756
  lstatSync as lstatSync2,
27236
27757
  mkdirSync as mkdirSync8,
27237
27758
  readdirSync as readdirSync5,
27238
- readFileSync as readFileSync13,
27759
+ readFileSync as readFileSync14,
27239
27760
  readlinkSync as readlinkSync2,
27240
27761
  realpathSync as realpathSync4,
27241
27762
  renameSync as renameSync10,
27242
27763
  rmSync as rmSync6,
27243
- statSync as statSync11,
27764
+ statSync as statSync12,
27244
27765
  unlinkSync as unlinkSync7,
27245
27766
  writeFileSync as writeFileSync9
27246
27767
  } from "node:fs";
27247
- import { dirname as dirname8, join as join18, relative as relative3, resolve as resolve9 } from "node:path";
27768
+ import { dirname as dirname9, join as join18, relative as relative3, resolve as resolve9 } from "node:path";
27248
27769
  import { Readable as Readable3 } from "node:stream";
27249
27770
  import { pipeline as pipeline3 } from "node:stream/promises";
27250
27771
 
@@ -27353,7 +27874,7 @@ function installFailureMarkerPath(spec) {
27353
27874
  }
27354
27875
  function readInstallFailureMarker(spec) {
27355
27876
  try {
27356
- const marker = JSON.parse(readFileSync13(installFailureMarkerPath(spec), "utf8"));
27877
+ const marker = JSON.parse(readFileSync14(installFailureMarkerPath(spec), "utf8"));
27357
27878
  if (typeof marker.release !== "string" || typeof marker.sha256 !== "string" || typeof marker.error !== "string" || typeof marker.at !== "string" || !Number.isFinite(Date.parse(marker.at))) {
27358
27879
  return null;
27359
27880
  }
@@ -27367,7 +27888,7 @@ function writeInstallFailureMarker(spec, release, sha256, installError) {
27367
27888
  const markerError = installError.replace(/\s+/g, " ").trim();
27368
27889
  const tempPath = `${path}.tmp-${process.pid}-${randomBytes3(6).toString("hex")}`;
27369
27890
  try {
27370
- mkdirSync8(dirname8(path), { recursive: true });
27891
+ mkdirSync8(dirname9(path), { recursive: true });
27371
27892
  writeFileSync9(tempPath, `${JSON.stringify({ release, sha256, error: markerError, at: new Date().toISOString() })}
27372
27893
  `, { encoding: "utf8", mode: 384 });
27373
27894
  renameSync10(tempPath, path);
@@ -27396,7 +27917,7 @@ function ghBinaryPath(spec, platform) {
27396
27917
  function isGithubInstalled(spec, platform) {
27397
27918
  for (const candidate of ghBinaryCandidates(spec, platform)) {
27398
27919
  try {
27399
- if (statSync11(join18(ghBinDir(spec), candidate)).isFile())
27920
+ if (statSync12(join18(ghBinDir(spec), candidate)).isFile())
27400
27921
  return true;
27401
27922
  } catch {}
27402
27923
  }
@@ -27410,9 +27931,9 @@ function ghBinaryCandidates(spec, platform) {
27410
27931
  function readGithubInstalledMetaIn(installDir) {
27411
27932
  try {
27412
27933
  const path = join18(installDir, INSTALLED_META_FILE2);
27413
- if (!statSync11(path).isFile())
27934
+ if (!statSync12(path).isFile())
27414
27935
  return null;
27415
- const parsed = JSON.parse(readFileSync13(path, "utf8"));
27936
+ const parsed = JSON.parse(readFileSync14(path, "utf8"));
27416
27937
  if (typeof parsed.version !== "string" || parsed.version.length === 0)
27417
27938
  return null;
27418
27939
  return {
@@ -27453,7 +27974,7 @@ function sha256OfFile(path) {
27453
27974
  });
27454
27975
  }
27455
27976
  function sha256OfFileSync2(path) {
27456
- return createHash6("sha256").update(readFileSync13(path)).digest("hex");
27977
+ return createHash6("sha256").update(readFileSync14(path)).digest("hex");
27457
27978
  }
27458
27979
  async function fetchReleaseByTag(githubRepo, tag, fetchImpl, signal) {
27459
27980
  const candidates = [];
@@ -27629,7 +28150,7 @@ async function downloadFile(url, destPath, fetchImpl, assetSize, signal) {
27629
28150
  if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES3) {
27630
28151
  throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES3}`);
27631
28152
  }
27632
- mkdirSync8(dirname8(destPath), { recursive: true });
28153
+ mkdirSync8(dirname9(destPath), { recursive: true });
27633
28154
  let bytesWritten = 0;
27634
28155
  const guard = new TransformStream({
27635
28156
  transform(chunk, controller) {
@@ -27799,7 +28320,7 @@ function quarantineCachedGithubInstall(spec, reason) {
27799
28320
  const dest = join18(ghCacheRoot(), ".quarantine", encodeURIComponent(spec.id), `${Date.now()}`);
27800
28321
  warn2(`[lsp] tofu_mismatch ${spec.id}: ${reason}; quarantining ${packageDir} -> ${dest}`);
27801
28322
  try {
27802
- mkdirSync8(dirname8(dest), { recursive: true });
28323
+ mkdirSync8(dirname9(dest), { recursive: true });
27803
28324
  rmSync6(dest, { recursive: true, force: true });
27804
28325
  renameSync10(packageDir, dest);
27805
28326
  } catch (err) {
@@ -27811,7 +28332,7 @@ function validateCachedGithubInstall(spec, platform) {
27811
28332
  const meta = readGithubInstalledMetaIn(packageDir);
27812
28333
  const binaryPath = ghBinaryCandidates(spec, platform).map((candidate) => join18(ghBinDir(spec), candidate)).find((candidate) => {
27813
28334
  try {
27814
- return statSync11(candidate).isFile();
28335
+ return statSync12(candidate).isFile();
27815
28336
  } catch {
27816
28337
  return false;
27817
28338
  }
@@ -27929,7 +28450,7 @@ async function downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl,
27929
28450
  return { ok: false, sha256: archiveSha256, error: message };
27930
28451
  }
27931
28452
  const targetBinary = ghBinaryPath(spec, platform);
27932
- mkdirSync8(dirname8(targetBinary), { recursive: true });
28453
+ mkdirSync8(dirname9(targetBinary), { recursive: true });
27933
28454
  try {
27934
28455
  copyFileSync3(innerBinaryPath, targetBinary);
27935
28456
  if (platform !== "win32") {
@@ -28694,7 +29215,7 @@ var ISSUE_AND_PR_READ_DESCRIPTION = "GitHub issues and pull requests can be read
28694
29215
  function whenGhReadEnabled(enabled, description) {
28695
29216
  return enabled ? description : "";
28696
29217
  }
28697
- var READ_DESCRIPTION = "Read file contents with line numbers. Backed by AFT's indexed Rust reader — faster than the built-in `read` on large repos. Images are returned as attachments on vision-capable models; PDFs and non-vision models are not yet supported.";
29218
+ var READ_DESCRIPTION = "Read file contents with line numbers or sorted directory entries. Use startLine/endLine or 1-based offset/limit for either. Directory limit defaults to and is capped at 1000; enumeration stops at 10,000 entries and partial listings carry a shown/total trailer. 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.";
28698
29219
  function readDescription(ghReadEnabled) {
28699
29220
  const githubDescription = whenGhReadEnabled(ghReadEnabled, ISSUE_AND_PR_READ_DESCRIPTION);
28700
29221
  return githubDescription ? `${READ_DESCRIPTION} ${githubDescription}` : READ_DESCRIPTION;
@@ -28790,12 +29311,12 @@ async function assertExternalDirectoryPermission(extCtx, target, options = {}) {
28790
29311
  }
28791
29312
  var ReadParams = Type2.Object({
28792
29313
  path: Type2.String({
28793
- description: "Path to the file to read (absolute or relative to project root)"
29314
+ description: "Path to the file or directory to read (absolute or relative to project root)"
28794
29315
  }),
28795
- startLine: optionalInt(1, Number.MAX_SAFE_INTEGER, "1-based line to start reading from"),
28796
- endLine: optionalInt(1, Number.MAX_SAFE_INTEGER, "1-based line to stop reading at (inclusive)"),
28797
- limit: optionalInt(1, Number.MAX_SAFE_INTEGER, "Maximum number of lines to return"),
28798
- offset: optionalInt(1, Number.MAX_SAFE_INTEGER, "1-based line number to start reading from (use with limit)")
29316
+ startLine: optionalInt(1, Number.MAX_SAFE_INTEGER, "1-based line or directory entry to start reading from"),
29317
+ endLine: optionalInt(1, Number.MAX_SAFE_INTEGER, "1-based line or directory entry to stop reading at (inclusive)"),
29318
+ limit: optionalInt(1, Number.MAX_SAFE_INTEGER, "Max lines (default: 2000) or directory entries (default and cap: 1000) to return"),
29319
+ offset: optionalInt(1, Number.MAX_SAFE_INTEGER, "1-based line or directory entry to start reading from (use with limit). Ignored if startLine is provided")
28799
29320
  });
28800
29321
  var WriteParams = Type2.Object({
28801
29322
  path: Type2.String({
@@ -28930,7 +29451,7 @@ function registerHoistedTools(pi, ctx, surface) {
28930
29451
  name: readName,
28931
29452
  label: readName,
28932
29453
  description: readDescription(resolveGithubConfig(ctx.config).read),
28933
- promptSnippet: "Read file contents (supports offset/limit for large files)",
29454
+ promptSnippet: "Read files or directories (supports offset/limit for either)",
28934
29455
  promptGuidelines: [`Use ${readName} to examine files instead of cat or sed.`],
28935
29456
  parameters: ReadParams,
28936
29457
  async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
@@ -30105,7 +30626,7 @@ A notification will fire when the pattern matches or the task exits.`, watchDeta
30105
30626
  const effectiveWaitMs = workerForcedSync ? syncWaitCap : resolveWatchTimeoutMs(coerceConfiguredWatchTimeout(params.timeout_ms, syncWaitCap), role, syncWaitCap);
30106
30627
  const data = await waitForBashStatus(ctx, bridge, extCtx, params.task_id, undefined, waitFor, true, effectiveWaitMs);
30107
30628
  if (data.waited?.reason === "user_message") {
30108
- const convertedText = await convertToAsyncWatchOnAbort(bridge, extCtx, params.task_id, waitFor, coerceBoolean(params.once, true));
30629
+ const convertedText = await convertToAsyncWatchOnAbort(bridge, extCtx, params.task_id, waitFor, coerceBoolean(params.once, true), data.waited.elapsed_ms);
30109
30630
  return textResult(convertedText, { waited: data.waited });
30110
30631
  }
30111
30632
  const text = await formatBashStatus(extCtx, params.task_id, data, undefined, { role, capMs: syncWaitCap });
@@ -30113,9 +30634,10 @@ A notification will fire when the pattern matches or the task exits.`, watchDeta
30113
30634
  }
30114
30635
  };
30115
30636
  }
30116
- async function convertToAsyncWatchOnAbort(bridge, extCtx, taskId, waitFor, once) {
30637
+ async function convertToAsyncWatchOnAbort(bridge, extCtx, taskId, waitFor, once, elapsedMs) {
30638
+ const interrupted = `Sync watch for task ${taskId} was interrupted because you sent a message after ${elapsedMs}ms of waiting. `;
30117
30639
  if (!waitFor) {
30118
- return `Sync watch for task ${taskId} was interrupted because you sent a message. ` + `The task is still running in the background. A completion reminder will be ` + `delivered automatically when the task exits; don't poll bash_status.`;
30640
+ return interrupted + `The task is still running in the background. A completion reminder will be ` + `delivered automatically when the task exits; don't poll bash_status.`;
30119
30641
  }
30120
30642
  const notifyParams = {
30121
30643
  task_id: taskId,
@@ -30131,12 +30653,12 @@ async function convertToAsyncWatchOnAbort(bridge, extCtx, taskId, waitFor, once)
30131
30653
  const registered = await callBashBridge(bridge, "bash_notify", notifyParams, extCtx);
30132
30654
  if (registered.success === false) {
30133
30655
  unmarkExplicitControl(sessionId, taskId);
30134
- return `Sync watch for task ${taskId} was interrupted because you sent a message. ` + `Auto-registering an async watch failed (${String(registered.message ?? "unknown error")}). ` + `The task is still running in the background. A completion reminder will be ` + `delivered automatically when the task exits.`;
30656
+ return interrupted + `Auto-registering an async watch failed (${String(registered.message ?? "unknown error")}). ` + `The task is still running in the background. A completion reminder will be ` + `delivered automatically when the task exits.`;
30135
30657
  }
30136
- return `Sync watch for task ${taskId} was interrupted because you sent a message. ` + `The wait has been converted to an async watch (${registered.watch_id}). ` + `A notification will fire when the pattern matches or the task exits.`;
30658
+ return interrupted + `The wait has been converted to an async watch (${registered.watch_id}). ` + `A notification will fire when the pattern matches or the task exits.`;
30137
30659
  } catch (err) {
30138
30660
  unmarkExplicitControl(sessionId, taskId);
30139
- return `Sync watch for task ${taskId} was interrupted because you sent a message. ` + `Auto-registering an async watch failed (${err instanceof Error ? err.message : String(err)}). ` + `The task is still running in the background. A completion reminder will be ` + `delivered automatically when the task exits.`;
30661
+ return interrupted + `Auto-registering an async watch failed (${err instanceof Error ? err.message : String(err)}). ` + `The task is still running in the background. A completion reminder will be ` + `delivered automatically when the task exits.`;
30140
30662
  }
30141
30663
  }
30142
30664
  function createBashWriteTool(ctx) {
@@ -30208,8 +30730,9 @@ async function bashStatusSnapshot(bridge, extCtx, taskId, outputMode, options, c
30208
30730
  }, extCtx, options);
30209
30731
  }
30210
30732
  async function waitForBashStatus(ctx, bridge, extCtx, taskId, outputMode, waitFor, waitForExit, effectiveWaitMs) {
30211
- const startedAt = Date.now();
30733
+ const startedAt = monotonicNowMs();
30212
30734
  const deadline = startedAt + effectiveWaitMs;
30735
+ const elapsedMs = () => Math.round(monotonicNowMs() - startedAt);
30213
30736
  let spillCursor = { output: 0, stderr: 0 };
30214
30737
  const scanState = {
30215
30738
  output: { text: "", baseOffset: 0 },
@@ -30239,16 +30762,18 @@ async function waitForBashStatus(ctx, bridge, extCtx, taskId, outputMode, waitFo
30239
30762
  if (isSyncWatchAborted(sessionId)) {
30240
30763
  return withWaited(lastData ?? unavailableSnapshot(), {
30241
30764
  reason: "user_message",
30242
- elapsed_ms: Date.now() - startedAt
30765
+ elapsed_ms: elapsedMs(),
30766
+ limit_ms: effectiveWaitMs
30243
30767
  });
30244
30768
  }
30245
- if (Date.now() >= deadline) {
30769
+ if (monotonicNowMs() >= deadline) {
30246
30770
  return withWaited(lastData ?? unavailableSnapshot(), {
30247
30771
  reason: "unavailable",
30248
- elapsed_ms: Date.now() - startedAt
30772
+ elapsed_ms: elapsedMs(),
30773
+ limit_ms: effectiveWaitMs
30249
30774
  });
30250
30775
  }
30251
- await sleep(Math.min(BASH_WAIT_POLL_INTERVAL_MS, Math.max(0, deadline - Date.now())));
30776
+ await sleep(Math.min(BASH_WAIT_POLL_INTERVAL_MS, Math.max(0, deadline - monotonicNowMs())));
30252
30777
  continue;
30253
30778
  }
30254
30779
  lastData = data;
@@ -30277,7 +30802,8 @@ async function waitForBashStatus(ctx, bridge, extCtx, taskId, outputMode, waitFo
30277
30802
  const matchStream = data.mode === "pty" ? undefined : chunk.stream === "output" ? "stdout" : "stderr";
30278
30803
  return withWaited(data, {
30279
30804
  reason: "matched",
30280
- elapsed_ms: Date.now() - startedAt,
30805
+ elapsed_ms: elapsedMs(),
30806
+ limit_ms: effectiveWaitMs,
30281
30807
  match: match.text,
30282
30808
  match_offset: state.baseOffset + match.byteOffset,
30283
30809
  match_stream: matchStream
@@ -30297,15 +30823,27 @@ async function waitForBashStatus(ctx, bridge, extCtx, taskId, outputMode, waitFo
30297
30823
  consumeBgCompletion(sessionId, taskId);
30298
30824
  await markBgCompletionDelivered({ ctx, directory: extCtx.cwd, sessionID: sessionId }, taskId);
30299
30825
  }
30300
- return withWaited(data, { reason: "exited", elapsed_ms: Date.now() - startedAt });
30826
+ return withWaited(data, {
30827
+ reason: "exited",
30828
+ elapsed_ms: elapsedMs(),
30829
+ limit_ms: effectiveWaitMs
30830
+ });
30301
30831
  }
30302
30832
  if (isSyncWatchAborted(sessionId)) {
30303
- return withWaited(data, { reason: "user_message", elapsed_ms: Date.now() - startedAt });
30833
+ return withWaited(data, {
30834
+ reason: "user_message",
30835
+ elapsed_ms: elapsedMs(),
30836
+ limit_ms: effectiveWaitMs
30837
+ });
30304
30838
  }
30305
- if (Date.now() >= deadline) {
30306
- return withWaited(data, { reason: "timeout", elapsed_ms: Date.now() - startedAt });
30839
+ if (monotonicNowMs() >= deadline) {
30840
+ return withWaited(data, {
30841
+ reason: "timeout",
30842
+ elapsed_ms: elapsedMs(),
30843
+ limit_ms: effectiveWaitMs
30844
+ });
30307
30845
  }
30308
- await sleep(Math.min(BASH_WAIT_POLL_INTERVAL_MS, Math.max(0, deadline - Date.now())));
30846
+ await sleep(Math.min(BASH_WAIT_POLL_INTERVAL_MS, Math.max(0, deadline - monotonicNowMs())));
30309
30847
  }
30310
30848
  } finally {
30311
30849
  if (waitForExit && !sawTerminal)
@@ -30412,18 +30950,19 @@ function withWaited(data, waited) {
30412
30950
  return { ...data, waited };
30413
30951
  }
30414
30952
  function formatWaitSummary(waited, details, watchRole) {
30953
+ const waitedText = formatWatchWaited(waited.elapsed_ms, waited.limit_ms, watchRole.capMs);
30415
30954
  if (waited.reason === "matched") {
30416
30955
  const stream = waited.match_stream ? ` in ${waited.match_stream}` : "";
30417
- return `Waited ${waited.elapsed_ms}ms; matched ${JSON.stringify(waited.match ?? "")}${stream} at offset ${waited.match_offset ?? 0}.`;
30956
+ return `${waitedText}; matched ${JSON.stringify(waited.match ?? "")}${stream} at offset ${waited.match_offset ?? 0}.`;
30418
30957
  }
30419
30958
  if (waited.reason === "timeout") {
30420
- return `Waited ${waited.elapsed_ms}ms; timeout reached without match. ${watchTimeoutSteer(watchRole.role, watchRole.capMs, "timeout_ms")}`;
30959
+ return `${waitedText}; timeout reached without match. ${watchTimeoutSteer(watchRole.role, watchRole.capMs, "timeout_ms")}`;
30421
30960
  }
30422
30961
  if (waited.reason === "unavailable") {
30423
- return `Waited ${waited.elapsed_ms}ms; the bridge was busy, so task state is unknown. Do not poll; let the task's completion notification wake the session, or use one bash_status snapshot on the next normal tool call.`;
30962
+ return `${waitedText}; the bridge was busy, so task state is unknown. Do not poll; let the task's completion notification wake the session, or use one bash_status snapshot on the next normal tool call.`;
30424
30963
  }
30425
30964
  const exit = typeof details.exit_code === "number" ? `, exit ${details.exit_code}` : "";
30426
- return `Waited ${waited.elapsed_ms}ms; task exited (${details.status}${exit}).`;
30965
+ return `${waitedText}; task exited (${details.status}${exit}).`;
30427
30966
  }
30428
30967
  async function formatBashStatus(extCtx, taskId, details, requestedOutputMode, watchRole) {
30429
30968
  const exit = typeof details.exit_code === "number" ? ` (exit ${details.exit_code})` : "";
@@ -30690,7 +31229,7 @@ function registerFsTools(pi, ctx, surface) {
30690
31229
  pi.registerTool(withPathAliasPreparation({
30691
31230
  name: "aft_delete",
30692
31231
  label: "delete",
30693
- description: "Delete one or more files (or directories). " + (backupsDisabled ? "Backup capture is disabled by user config, so this tool does not create undo snapshots. " : "Each file is backed up before deletion — use `aft_safety undo` to recover any of them. For directories, every file inside is individually backed up before removal. The deleted file's contents stay in the undo store until its retention expires. A recursive delete whose backup would copy more than 2,000 files or 100 MiB in one call is refused before anything is deleted; delete such a tree in smaller pieces, or use bash `rm -rf` when no undo is needed. ") + "Directory deletion requires recursive: true. " + "Returns { success, complete, deleted, skipped_files }: partial success is allowed; files that fail are reported in skipped_files.",
31232
+ description: "Delete one or more files (or directories). " + (backupsDisabled ? "Backup capture is disabled by user config, so this tool does not create undo snapshots. A directory tree containing a mount point of another filesystem is still refused. " : "Each file is backed up before deletion — use `aft_safety undo` to recover any of them. A recursive delete backs up the whole tree: directories (empty ones too, with their permissions), file contents, hard links (relinked by undo) and symlinks (the link itself, never its target; restored exactly, even when dangling). Sockets are deleted but undo does not restore them, and a file hard-linked to paths outside the tree comes back as an independent copy; both are reported as warnings. Refused before anything is deleted: mount points of another filesystem, named pipes, device nodes, and symlinks undo cannot recreate exactly (non-UTF-8 target, or on Windows). Paths under the system temp directory are never backed up, so only mount points are refused there. The deleted file's contents stay in the undo store until its retention expires. A recursive delete whose backup would record more than 2,000 entries (files, directories and links) or copy 100 MiB in one call is refused before anything is deleted; delete such a tree in smaller pieces, or use bash `rm -rf` when no undo is needed. ") + "Directory deletion requires recursive: true. " + "Returns { success, complete, deleted, skipped_files }: partial success is allowed; files that fail are reported in skipped_files.",
30694
31233
  parameters: DeleteParams,
30695
31234
  async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
30696
31235
  const inputs = coerceStringArray(params.files);
@@ -30698,14 +31237,13 @@ function registerFsTools(pi, ctx, surface) {
30698
31237
  throw new Error("delete: `files` must be a non-empty array of paths");
30699
31238
  }
30700
31239
  const files = await Promise.all(inputs.map((file) => resolvePathArg(extCtx.cwd, file)));
31240
+ const restrictToProjectRoot = ctx.config.restrict_to_project_root ?? false;
30701
31241
  const checked = new Set;
30702
31242
  for (const file of files) {
30703
31243
  if (checked.has(file))
30704
31244
  continue;
30705
31245
  checked.add(file);
30706
- await assertExternalDirectoryPermission(extCtx, file, {
30707
- restrictToProjectRoot: ctx.config.restrict_to_project_root ?? false
30708
- });
31246
+ await assertExternalDirectoryPermission(extCtx, file, { restrictToProjectRoot });
30709
31247
  }
30710
31248
  const bridge = bridgeFor(ctx, extCtx.cwd);
30711
31249
  const response = await callToolCall(bridge, "delete", {
@@ -30743,10 +31281,9 @@ function registerFsTools(pi, ctx, surface) {
30743
31281
  const filePath = await resolvePathArg(extCtx.cwd, params.path);
30744
31282
  const destination = await resolvePathArg(extCtx.cwd, params.destination);
30745
31283
  const checked = new Set([filePath, destination]);
31284
+ const restrictToProjectRoot = ctx.config.restrict_to_project_root ?? false;
30746
31285
  for (const file of checked) {
30747
- await assertExternalDirectoryPermission(extCtx, file, {
30748
- restrictToProjectRoot: ctx.config.restrict_to_project_root ?? false
30749
- });
31286
+ await assertExternalDirectoryPermission(extCtx, file, { restrictToProjectRoot });
30750
31287
  }
30751
31288
  const bridge = bridgeFor(ctx, extCtx.cwd);
30752
31289
  const response = await callToolCall(bridge, "move", {
@@ -32176,6 +32713,12 @@ function piHashlineDowngrade(config, surface) {
32176
32713
  message: 'edit_mode "hashline" is not in effect because "read" is in disabled_tools (hashline edits need tagged reads); registered tools keep their ordinary behavior.'
32177
32714
  };
32178
32715
  }
32716
+ function withLiveRestriction(surface, ctx) {
32717
+ return Object.defineProperty({ ...surface }, "restrictToProjectRoot", {
32718
+ enumerable: true,
32719
+ get: () => ctx.config.restrict_to_project_root ?? false
32720
+ });
32721
+ }
32179
32722
  function resolvePiToolSurface(config, pi) {
32180
32723
  const disabled = new Set(resolvedDisabledTools(config));
32181
32724
  const ok = (name) => !disabled.has(name);
@@ -32228,7 +32771,8 @@ function bindToolRegistrationFunnel(pi, ctx, harness) {
32228
32771
  }
32229
32772
  });
32230
32773
  }
32231
- function registerPiToolSurface(pi, ctx, surface, harness) {
32774
+ function registerPiToolSurface(pi, ctx, loadedSurface, harness) {
32775
+ const surface = withLiveRestriction(loadedSurface, ctx);
32232
32776
  const boundPi = bindToolRegistrationFunnel(pi, ctx, harness);
32233
32777
  if (surface.hoistBash)
32234
32778
  registerBashTool(boundPi, ctx, surface.semantic, "bash", false);
@@ -32262,6 +32806,8 @@ var CONFIG_ERROR_STATUS_KEY = "aft";
32262
32806
  var defaultDependencies = {
32263
32807
  loadConfig: loadAftConfig,
32264
32808
  configLoadErrors: getConfigLoadErrors,
32809
+ configLoadSources: getConfigLoadSources,
32810
+ configLoadTexts: getConfigLoadTexts,
32265
32811
  migrateConfigLocations: (directory) => migrateAftConfigLocations(directory, bridgeLogger).flatMap((result) => result.warnings),
32266
32812
  subcConnectionFileError
32267
32813
  };
@@ -32280,6 +32826,8 @@ function parseFailure(dependencies) {
32280
32826
  }
32281
32827
  async function resolvePiBootstrapConfig(directory, notify, dependencies = defaultDependencies) {
32282
32828
  let config;
32829
+ let sources = [];
32830
+ let sourceTexts = {};
32283
32831
  try {
32284
32832
  dependencies.loadConfig(directory);
32285
32833
  const firstFailure = parseFailure(dependencies);
@@ -32291,11 +32839,13 @@ async function resolvePiBootstrapConfig(directory, notify, dependencies = defaul
32291
32839
  const failure = parseFailure(dependencies);
32292
32840
  if (failure)
32293
32841
  return configErrorState(failure, notify);
32842
+ sources = [...dependencies.configLoadSources?.() ?? []];
32843
+ sourceTexts = Object.fromEntries(dependencies.configLoadTexts?.() ?? []);
32294
32844
  } catch (err) {
32295
32845
  return configErrorState(err instanceof Error ? err.message : String(err), notify);
32296
32846
  }
32297
32847
  const missing = await dependencies.subcConnectionFileError(config.subc?.connection_file);
32298
- return missing === null ? { ok: true, config } : configErrorState(missing, notify, config);
32848
+ return missing === null ? { ok: true, config, sources, sourceTexts } : configErrorState(missing, notify, config);
32299
32849
  }
32300
32850
  function failingRegistrations(pi, message) {
32301
32851
  const registerTool = (tool) => {
@@ -32331,6 +32881,58 @@ function registerPiConfigErrorState(pi, config, message) {
32331
32881
  log2("AFT is in the config error state; every tool call will fail");
32332
32882
  }
32333
32883
 
32884
+ // src/config-live-reload.ts
32885
+ var PI_LIVE_CONFIG_KEYS = aftLiveConfigKeys((config) => resolveBashConfig(config));
32886
+ function projectTextOf(directory) {
32887
+ const { projectConfigPath } = resolveCortexKitConfigPaths(directory);
32888
+ return getConfigLoadTexts().get(projectConfigPath) ?? null;
32889
+ }
32890
+ function loadPiConfigForLiveReload(directory) {
32891
+ try {
32892
+ const config = loadAftConfig(directory);
32893
+ const [failure] = getConfigLoadErrors();
32894
+ if (failure) {
32895
+ return { ok: false, message: formatConfigParseErrorMessage(failure.path, failure.message) };
32896
+ }
32897
+ const [invalid] = getConfigValidationErrors();
32898
+ if (invalid) {
32899
+ return {
32900
+ ok: false,
32901
+ message: `AFT config at ${invalid.path} has an invalid setting: ${invalid.message}`
32902
+ };
32903
+ }
32904
+ return {
32905
+ ok: true,
32906
+ config,
32907
+ sources: [...getConfigLoadSources()],
32908
+ projectText: projectTextOf(directory),
32909
+ texts: Object.fromEntries(getConfigLoadTexts())
32910
+ };
32911
+ } catch (err) {
32912
+ return { ok: false, message: err instanceof Error ? err.message : String(err) };
32913
+ }
32914
+ }
32915
+ function startPiLiveConfigReload(options) {
32916
+ const { userConfigPath, projectConfigPath } = resolveCortexKitConfigPaths(options.directory);
32917
+ return startLiveConfigReload({
32918
+ paths: [userConfigPath, projectConfigPath],
32919
+ load: () => loadPiConfigForLiveReload(options.directory),
32920
+ initialSources: options.initialSources,
32921
+ initialProjectText: options.initialSourceTexts?.[projectConfigPath] ?? null,
32922
+ initialTexts: options.initialSourceTexts,
32923
+ securityKeys: aftLiveSecurityKeys(PI_LIVE_CONFIG_KEYS),
32924
+ keys: PI_LIVE_CONFIG_KEYS,
32925
+ getConfig: options.getConfig,
32926
+ setConfig: options.setConfig,
32927
+ log: (message) => log2(`${message} (${options.directory})`),
32928
+ reportError: (message) => {
32929
+ error2(message);
32930
+ options.notify(message);
32931
+ },
32932
+ watch: options.watch
32933
+ });
32934
+ }
32935
+
32334
32936
  // src/shutdown-hooks.ts
32335
32937
  var GLOBAL_KEY = "__aftPiShutdownHooks__";
32336
32938
  function getState() {
@@ -32503,6 +33105,8 @@ function buildHintsFromConfig(config, absentTools, hashlineEffective = false) {
32503
33105
  });
32504
33106
  }
32505
33107
  function registerWorkflowHints(pi, config, surface) {
33108
+ if (skipsEagerStartup())
33109
+ return;
32506
33110
  const absent = new Set;
32507
33111
  if (!surface.outline)
32508
33112
  absent.add("aft_outline");
@@ -32956,6 +33560,16 @@ ${lines}
32956
33560
  hashlineEffective: hashlineEditRegistered,
32957
33561
  storageDir
32958
33562
  };
33563
+ const liveConfigReload = startPiLiveConfigReload({
33564
+ directory: projectRoot,
33565
+ initialSources: bootstrap.sources ?? [],
33566
+ initialSourceTexts: bootstrap.sourceTexts,
33567
+ getConfig: () => ctx.config,
33568
+ setConfig: (next) => {
33569
+ ctx.config = next;
33570
+ },
33571
+ notify: (message) => deliverConfigMigrationWarnings([message])
33572
+ });
32959
33573
  if (onnxRuntimePromise) {
32960
33574
  onnxRuntimePromise.then((ortDylibDir) => {
32961
33575
  if (ortDylibDir) {
@@ -33062,6 +33676,7 @@ Retry with: npx @cortexkit/aft doctor --fix`
33062
33676
  });
33063
33677
  const unregisterShutdownCleanup = registerShutdownCleanup(async () => {
33064
33678
  hostShutdownStarted = true;
33679
+ liveConfigReload.stop();
33065
33680
  try {
33066
33681
  await Promise.allSettled([abortInFlightAutoInstalls(), abortInFlightGithubInstalls()]);
33067
33682
  await pool.shutdown();
@@ -33072,6 +33687,7 @@ Retry with: npx @cortexkit/aft doctor --fix`
33072
33687
  });
33073
33688
  pi.on("session_shutdown", async () => {
33074
33689
  hostShutdownStarted = true;
33690
+ liveConfigReload.stop();
33075
33691
  try {
33076
33692
  await Promise.allSettled([abortInFlightAutoInstalls(), abortInFlightGithubInstalls()]);
33077
33693
  await pool.shutdown();