@cortexkit/aft-pi 0.49.4 → 0.50.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
@@ -8410,6 +8410,8 @@ var DEFAULT_BRIDGE_TIMEOUT_MS = 30000;
8410
8410
  var BRIDGE_HANG_TIMEOUT_THRESHOLD = 2;
8411
8411
  var MAX_STDOUT_BUFFER = 64 * 1024 * 1024;
8412
8412
  var STDOUT_BUFFER_COMPACT_THRESHOLD = 64 * 1024;
8413
+ var HASHLINE_REGISTRATION_LOG_INTERVAL_MS = 60000;
8414
+ var HASHLINE_REGISTRATION_LOG_STATE_LIMIT = 256;
8413
8415
  var TERMINAL_BASH_STATUSES = new Set([
8414
8416
  "completed",
8415
8417
  "failed",
@@ -8559,6 +8561,9 @@ class BinaryBridge {
8559
8561
  configured = false;
8560
8562
  _configurePromise = null;
8561
8563
  configOverrides;
8564
+ editSlotSurvives;
8565
+ editSlotSurvivesCaptured = false;
8566
+ hashlineRegistrationLogState = new Map;
8562
8567
  minVersion;
8563
8568
  onVersionMismatch;
8564
8569
  onConfigureWarnings;
@@ -8575,20 +8580,33 @@ class BinaryBridge {
8575
8580
  errorPrefix;
8576
8581
  logger;
8577
8582
  childEnv;
8578
- constructor(binaryPath, cwd, options, configOverrides) {
8583
+ constructor(binaryPath, cwd, options, configOverrides, editSlotSurvives) {
8579
8584
  this.binaryPath = binaryPath;
8580
8585
  this.cwd = cwd;
8581
8586
  this.timeoutMs = options?.timeoutMs ?? DEFAULT_BRIDGE_TIMEOUT_MS;
8582
8587
  this.hangThreshold = options?.hangThreshold ?? BRIDGE_HANG_TIMEOUT_THRESHOLD;
8583
8588
  this.maxRestarts = options?.maxRestarts ?? 3;
8584
- this.configOverrides = configOverrides ?? {};
8589
+ this.errorPrefix = options?.errorPrefix ?? "[aft-bridge]";
8590
+ this.configOverrides = { ...configOverrides ?? {} };
8591
+ const legacyEditSlotSurvives = this.configOverrides.edit_slot_survives;
8592
+ delete this.configOverrides.edit_slot_survives;
8593
+ if (legacyEditSlotSurvives !== undefined && typeof legacyEditSlotSurvives !== "boolean") {
8594
+ throw new Error(`${this.errorPrefix} edit_slot_survives must be a boolean`);
8595
+ }
8596
+ if (editSlotSurvives !== undefined && legacyEditSlotSurvives !== undefined && editSlotSurvives !== legacyEditSlotSurvives) {
8597
+ throw new Error(`${this.errorPrefix} conflicting edit_slot_survives construction values`);
8598
+ }
8599
+ const capturedEditSlotSurvives = editSlotSurvives ?? legacyEditSlotSurvives;
8600
+ if (typeof capturedEditSlotSurvives === "boolean") {
8601
+ this.editSlotSurvives = capturedEditSlotSurvives;
8602
+ this.editSlotSurvivesCaptured = true;
8603
+ }
8585
8604
  this.minVersion = options?.minVersion;
8586
8605
  this.onVersionMismatch = options?.onVersionMismatch;
8587
8606
  this.onConfigureWarnings = options?.onConfigureWarnings;
8588
8607
  this.onBashCompletion = options?.onBashCompletion;
8589
8608
  this.onBashLongRunning = options?.onBashLongRunning;
8590
8609
  this.onBashPatternMatch = options?.onBashPatternMatch;
8591
- this.errorPrefix = options?.errorPrefix ?? "[aft-bridge]";
8592
8610
  this.logger = options?.logger;
8593
8611
  this.childEnv = options?.childEnv;
8594
8612
  }
@@ -8699,13 +8717,54 @@ class BinaryBridge {
8699
8717
  cacheStatusSnapshot(snapshot) {
8700
8718
  this.cachedStatus = snapshot;
8701
8719
  }
8720
+ setEditSlotSurvives(value) {
8721
+ if (this.editSlotSurvivesCaptured) {
8722
+ throw new Error(`${this.errorPrefix} edit_slot_survives is write-once and was already captured`);
8723
+ }
8724
+ this.editSlotSurvives = value;
8725
+ this.editSlotSurvivesCaptured = true;
8726
+ }
8727
+ logHashlineRegistrationCarrier(phase, sessionId, editSlotSurvives) {
8728
+ const session = sessionId && sessionId.length > 0 ? sessionId : "__default__";
8729
+ const key = `${phase}\x00${session}\x00${String(editSlotSurvives)}`;
8730
+ const now = Date.now();
8731
+ const state = this.hashlineRegistrationLogState.get(key);
8732
+ if (state && now - state.lastEmittedAt < HASHLINE_REGISTRATION_LOG_INTERVAL_MS) {
8733
+ state.suppressed += 1;
8734
+ return;
8735
+ }
8736
+ const repeated = state && state.suppressed > 0 ? ` repeated=${state.suppressed + 1}` : "";
8737
+ if (!state && this.hashlineRegistrationLogState.size >= HASHLINE_REGISTRATION_LOG_STATE_LIMIT) {
8738
+ const oldest = this.hashlineRegistrationLogState.keys().next().value;
8739
+ if (oldest !== undefined)
8740
+ this.hashlineRegistrationLogState.delete(oldest);
8741
+ }
8742
+ this.hashlineRegistrationLogState.set(key, { lastEmittedAt: now, suppressed: 0 });
8743
+ this.sessionLogVia(sessionId, `hashline registration carrier transport=ndjson phase=${phase} edit_slot_survives=${editSlotSurvives}${repeated}`);
8744
+ }
8702
8745
  async send(command, params = {}, options) {
8703
- return this.sendWithVersionMismatchRetry(command, params, options, true);
8746
+ let dispatchParams = params;
8747
+ if (command === "configure") {
8748
+ dispatchParams = { ...params };
8749
+ delete dispatchParams.edit_slot_survives;
8750
+ const editSlotSurvives = this.editSlotSurvives;
8751
+ if (this.editSlotSurvivesCaptured && typeof editSlotSurvives === "boolean") {
8752
+ dispatchParams.edit_slot_survives = editSlotSurvives;
8753
+ const sessionId = typeof dispatchParams.session_id === "string" ? dispatchParams.session_id : undefined;
8754
+ this.logHashlineRegistrationCarrier("configure", sessionId, editSlotSurvives);
8755
+ }
8756
+ }
8757
+ return this.sendWithVersionMismatchRetry(command, dispatchParams, options, true);
8704
8758
  }
8705
8759
  async toolCall(sessionId, name, rawArgs = {}, options) {
8706
8760
  const params = { name, arguments: rawArgs };
8707
8761
  if (sessionId)
8708
8762
  params.session_id = sessionId;
8763
+ const editSlotSurvives = this.editSlotSurvives;
8764
+ if (this.editSlotSurvivesCaptured && typeof editSlotSurvives === "boolean") {
8765
+ params.edit_slot_survives = editSlotSurvives;
8766
+ this.logHashlineRegistrationCarrier("tool_call", sessionId, editSlotSurvives);
8767
+ }
8709
8768
  const { preview, ...sendOptions } = options ?? {};
8710
8769
  if (preview === true)
8711
8770
  params.preview = true;
@@ -9781,7 +9840,8 @@ function formatDroppedKeyWarnings(dropped) {
9781
9840
  // ../aft-bridge/dist/downloader.js
9782
9841
  import { spawnSync } from "node:child_process";
9783
9842
  import { createHash as createHash2, randomUUID } from "node:crypto";
9784
- import { chmodSync, closeSync, copyFileSync, createWriteStream, existsSync as existsSync2, mkdirSync, openSync, readFileSync as readFileSync3, renameSync, rmSync, statSync as statSync2, unlinkSync, writeSync } from "node:fs";
9843
+ import { chmodSync, closeSync, copyFileSync, createWriteStream, existsSync as existsSync2, mkdirSync, openSync, readdirSync, readFileSync as readFileSync3, renameSync, rmSync, statSync as statSync2, unlinkSync, writeSync } from "node:fs";
9844
+ import { hostname } from "node:os";
9785
9845
  import { join as join4 } from "node:path";
9786
9846
  import { Readable } from "node:stream";
9787
9847
  import { pipeline } from "node:stream/promises";
@@ -9806,8 +9866,8 @@ var REPO = "cortexkit/aft";
9806
9866
  var DOWNLOAD_TIMEOUT_MS = 300000;
9807
9867
  var LATEST_TAG_TIMEOUT_MS = 30000;
9808
9868
  var MAX_DOWNLOAD_BYTES = 200 * 1024 * 1024;
9809
- var DOWNLOAD_LOCK_TIMEOUT_MS = 120000;
9810
9869
  var DOWNLOAD_LOCK_STALE_MS = 10 * 60000;
9870
+ var DOWNLOAD_LOCK_TIMEOUT_MS = DOWNLOAD_LOCK_STALE_MS + 30000;
9811
9871
  function readBinaryVersion(binaryPath) {
9812
9872
  try {
9813
9873
  const result = spawnSync(binaryPath, ["--version"], {
@@ -9875,11 +9935,37 @@ async function downloadBinary(version) {
9875
9935
  let binaryTimeout = null;
9876
9936
  let checksumTimeout = null;
9877
9937
  const tmpPath = `${binaryPath}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`;
9938
+ const cleanUpPartialDownload = () => {
9939
+ try {
9940
+ if (existsSync2(tmpPath))
9941
+ unlinkSync(tmpPath);
9942
+ } catch {}
9943
+ };
9944
+ let interrupted = false;
9945
+ const cleanUpInterruptedDownload = () => {
9946
+ if (interrupted)
9947
+ return;
9948
+ interrupted = true;
9949
+ binaryController?.abort();
9950
+ checksumController?.abort();
9951
+ cleanUpPartialDownload();
9952
+ releaseLock?.();
9953
+ releaseLock = null;
9954
+ };
9955
+ const handleSigint = () => {
9956
+ cleanUpInterruptedDownload();
9957
+ process.off("SIGINT", handleSigint);
9958
+ process.kill(process.pid, "SIGINT");
9959
+ };
9960
+ const handleExit = () => cleanUpInterruptedDownload();
9961
+ process.once("SIGINT", handleSigint);
9962
+ process.once("exit", handleExit);
9878
9963
  try {
9879
9964
  if (!existsSync2(versionedCacheDir)) {
9880
9965
  mkdirSync(versionedCacheDir, { recursive: true });
9881
9966
  }
9882
9967
  releaseLock = await acquireDownloadLock(lockPath);
9968
+ sweepStaleDownloadTemps(versionedCacheDir, binaryName);
9883
9969
  if (existsSync2(binaryPath) && isExpectedCachedBinary(binaryPath, tag)) {
9884
9970
  return binaryPath;
9885
9971
  }
@@ -9955,13 +10041,11 @@ async function downloadBinary(version) {
9955
10041
  } catch (err) {
9956
10042
  const msg = err instanceof Error ? err.message : String(err);
9957
10043
  error(`Failed to download AFT binary: ${msg}`);
9958
- if (existsSync2(tmpPath)) {
9959
- try {
9960
- unlinkSync(tmpPath);
9961
- } catch {}
9962
- }
10044
+ cleanUpPartialDownload();
9963
10045
  return null;
9964
10046
  } finally {
10047
+ process.off("SIGINT", handleSigint);
10048
+ process.off("exit", handleExit);
9965
10049
  if (binaryTimeout) {
9966
10050
  binaryController?.abort();
9967
10051
  clearTimeout(binaryTimeout);
@@ -9987,17 +10071,85 @@ async function ensureBinary(version) {
9987
10071
  log("No cached binary found, downloading latest...");
9988
10072
  return downloadBinary();
9989
10073
  }
9990
- async function acquireDownloadLock(lockPath) {
10074
+ function createDownloadLockOwner() {
10075
+ return JSON.stringify({
10076
+ pid: process.pid,
10077
+ hostname: hostname(),
10078
+ createdAt: Date.now(),
10079
+ token: randomUUID()
10080
+ });
10081
+ }
10082
+ function parseDownloadLockOwner(raw) {
10083
+ try {
10084
+ const parsed = JSON.parse(raw);
10085
+ if (typeof parsed === "object" && parsed !== null) {
10086
+ const { pid, hostname: ownerHostname } = parsed;
10087
+ return {
10088
+ pid: typeof pid === "number" && Number.isSafeInteger(pid) && pid > 0 ? pid : null,
10089
+ hostname: typeof ownerHostname === "string" && ownerHostname ? ownerHostname : null
10090
+ };
10091
+ }
10092
+ } catch {}
10093
+ const legacyPid = Number(raw.split(":", 1)[0]);
10094
+ return {
10095
+ pid: Number.isSafeInteger(legacyPid) && legacyPid > 0 ? legacyPid : null,
10096
+ hostname: null
10097
+ };
10098
+ }
10099
+ function isProcessAlive(pid) {
10100
+ try {
10101
+ process.kill(pid, 0);
10102
+ return true;
10103
+ } catch (err) {
10104
+ return err.code !== "ESRCH";
10105
+ }
10106
+ }
10107
+ function isReclaimableDownloadLock(owner, ageMs, staleMs) {
10108
+ if (Math.abs(ageMs) > staleMs)
10109
+ return true;
10110
+ return (owner.hostname === null || owner.hostname === hostname()) && owner.pid !== null && !isProcessAlive(owner.pid);
10111
+ }
10112
+ function reclaimDownloadLock(lockPath, expectedOwner) {
10113
+ try {
10114
+ if (readFileSync3(lockPath, "utf-8") !== expectedOwner)
10115
+ return false;
10116
+ rmSync(lockPath, { force: true });
10117
+ return true;
10118
+ } catch (err) {
10119
+ if (err.code === "ENOENT")
10120
+ return true;
10121
+ throw err;
10122
+ }
10123
+ }
10124
+ function sweepStaleDownloadTemps(versionedCacheDir, binaryName) {
10125
+ const tempPrefix = `${binaryName}.`;
10126
+ try {
10127
+ for (const entry of readdirSync(versionedCacheDir, { withFileTypes: true })) {
10128
+ if (!entry.isFile() || !entry.name.startsWith(tempPrefix) || !entry.name.endsWith(".tmp")) {
10129
+ continue;
10130
+ }
10131
+ const tempPath = join4(versionedCacheDir, entry.name);
10132
+ const ageMs = Date.now() - statSync2(tempPath).mtimeMs;
10133
+ if (Math.abs(ageMs) > DOWNLOAD_LOCK_STALE_MS)
10134
+ unlinkSync(tempPath);
10135
+ }
10136
+ } catch {}
10137
+ }
10138
+ async function acquireDownloadLock(lockPath, timing = {}) {
10139
+ const timeoutMs = timing.timeoutMs ?? DOWNLOAD_LOCK_TIMEOUT_MS;
10140
+ const staleMs = timing.staleMs ?? DOWNLOAD_LOCK_STALE_MS;
10141
+ const pollIntervalMs = timing.pollIntervalMs ?? 100;
9991
10142
  const startedAt = Date.now();
9992
10143
  while (true) {
9993
10144
  try {
9994
- const owner = `${process.pid}:${Date.now()}:${randomUUID()}`;
10145
+ const owner = createDownloadLockOwner();
9995
10146
  const fd = openSync(lockPath, "wx");
9996
- writeSync(fd, owner);
10147
+ try {
10148
+ writeSync(fd, owner);
10149
+ } finally {
10150
+ closeSync(fd);
10151
+ }
9997
10152
  return () => {
9998
- try {
9999
- closeSync(fd);
10000
- } catch {}
10001
10153
  try {
10002
10154
  if (readFileSync3(lockPath, "utf-8") === owner) {
10003
10155
  rmSync(lockPath, { force: true });
@@ -10008,19 +10160,24 @@ async function acquireDownloadLock(lockPath) {
10008
10160
  const code = err.code;
10009
10161
  if (code !== "EEXIST")
10010
10162
  throw err;
10163
+ let existingOwner;
10164
+ let ageMs;
10011
10165
  try {
10012
- const ageMs = Date.now() - statSync2(lockPath).mtimeMs;
10013
- if (ageMs > DOWNLOAD_LOCK_STALE_MS) {
10014
- rmSync(lockPath, { force: true });
10166
+ existingOwner = readFileSync3(lockPath, "utf-8");
10167
+ ageMs = Date.now() - statSync2(lockPath).mtimeMs;
10168
+ } catch (readErr) {
10169
+ if (readErr.code === "ENOENT")
10170
+ continue;
10171
+ throw readErr;
10172
+ }
10173
+ if (isReclaimableDownloadLock(parseDownloadLockOwner(existingOwner), ageMs, staleMs)) {
10174
+ if (reclaimDownloadLock(lockPath, existingOwner))
10015
10175
  continue;
10016
- }
10017
- } catch {
10018
- continue;
10019
10176
  }
10020
- if (Date.now() - startedAt > DOWNLOAD_LOCK_TIMEOUT_MS) {
10177
+ if (Date.now() - startedAt > timeoutMs) {
10021
10178
  throw new Error(`Timed out waiting for download lock: ${lockPath}`);
10022
10179
  }
10023
- await new Promise((resolve3) => setTimeout(resolve3, 100));
10180
+ await new Promise((resolve3) => setTimeout(resolve3, pollIntervalMs));
10024
10181
  }
10025
10182
  }
10026
10183
  }
@@ -12873,6 +13030,8 @@ function normalizeWindowsRoot(p) {
12873
13030
  var AFT_MODULE_ID = "aft";
12874
13031
  var MAX_CONSECUTIVE_TRANSPORT_FAILURES = 3;
12875
13032
  var BG_STABLE_MS = 5000;
13033
+ var BG_LIFECYCLE_LOG_INTERVAL_MS = 60000;
13034
+ var BG_DISPATCH_PROBE_INTERVAL_MS = 60000;
12876
13035
  var DEFAULT_SESSION_ID = "__default__";
12877
13036
  var LOCALLY_SATISFIED_COMMANDS = new Set(["configure"]);
12878
13037
 
@@ -12935,12 +13094,15 @@ class BgSubscription {
12935
13094
  canAttach;
12936
13095
  onRootAttachFailure;
12937
13096
  onDormant;
13097
+ dispatchProbeIntervalMs;
12938
13098
  nudgeRef;
12939
13099
  isCurrent;
12940
13100
  stopped = false;
12941
13101
  current = null;
12942
13102
  loop;
12943
- constructor(identity, acquireClient, dropClient, consumerIdentity, onNudge, sleep2, canAttach, onRootAttachFailure, onDormant, nudgeRef, isCurrent = () => true) {
13103
+ lifecycleLogState = new Map;
13104
+ nudgeReceiptLogState = null;
13105
+ constructor(identity, acquireClient, dropClient, consumerIdentity, onNudge, sleep2, canAttach, onRootAttachFailure, onDormant, dispatchProbeIntervalMs, nudgeRef, isCurrent = () => true) {
12944
13106
  this.identity = identity;
12945
13107
  this.acquireClient = acquireClient;
12946
13108
  this.dropClient = dropClient;
@@ -12950,6 +13112,7 @@ class BgSubscription {
12950
13112
  this.canAttach = canAttach;
12951
13113
  this.onRootAttachFailure = onRootAttachFailure;
12952
13114
  this.onDormant = onDormant;
13115
+ this.dispatchProbeIntervalMs = dispatchProbeIntervalMs;
12953
13116
  this.nudgeRef = nudgeRef;
12954
13117
  this.isCurrent = isCurrent;
12955
13118
  this.loop = this.run();
@@ -12966,25 +13129,106 @@ class BgSubscription {
12966
13129
  return;
12967
13130
  });
12968
13131
  }
13132
+ info(kind, message) {
13133
+ const now = Date.now();
13134
+ const state = this.lifecycleLogState.get(kind);
13135
+ if (state && now - state.lastEmittedAt < BG_LIFECYCLE_LOG_INTERVAL_MS) {
13136
+ state.suppressed += 1;
13137
+ return;
13138
+ }
13139
+ const suppressed = state?.suppressed ?? 0;
13140
+ this.lifecycleLogState.set(kind, { lastEmittedAt: now, suppressed: 0 });
13141
+ const suffix = suppressed > 0 ? ` suppressed=${suppressed}` : "";
13142
+ log(`subc bg_events: ${message}${suffix}`, { sessionId: this.identity.session });
13143
+ }
13144
+ routeId(route) {
13145
+ return `${route.channel}@${route.epoch}`;
13146
+ }
13147
+ recordNudgeReceipt(routeId) {
13148
+ const now = Date.now();
13149
+ const state = this.nudgeReceiptLogState;
13150
+ if (state && now - state.lastEmittedAt < BG_LIFECYCLE_LOG_INTERVAL_MS) {
13151
+ state.count += 1;
13152
+ return;
13153
+ }
13154
+ const count = (state?.count ?? 0) + 1;
13155
+ this.nudgeReceiptLogState = { lastEmittedAt: now, count: 0 };
13156
+ log(`subc bg_events: nudge received channel=${routeId} count=${count}`, {
13157
+ sessionId: this.identity.session
13158
+ });
13159
+ }
13160
+ errorText(error2) {
13161
+ return error2 instanceof Error ? `${error2.name}: ${error2.message}` : String(error2);
13162
+ }
13163
+ startDispatchProbe(client, routeId) {
13164
+ const initial = client.droppedIngressFrames;
13165
+ if (typeof initial !== "number")
13166
+ return () => {
13167
+ return;
13168
+ };
13169
+ let previous = initial;
13170
+ const timer = setInterval(() => {
13171
+ const total = client.droppedIngressFrames;
13172
+ if (typeof total !== "number" || total <= previous)
13173
+ return;
13174
+ const delta = total - previous;
13175
+ previous = total;
13176
+ this.info("dispatch-epoch-drop", `client ingress epoch drops scope=client observed_while_channel=${routeId} delta=${delta} total=${total}`);
13177
+ }, this.dispatchProbeIntervalMs);
13178
+ timer.unref?.();
13179
+ return () => clearInterval(timer);
13180
+ }
12969
13181
  async run() {
12970
- let attempt = 0;
13182
+ let backoffAttempt = 0;
13183
+ let reconnectAttempt = 0;
13184
+ let reconnecting = false;
13185
+ const beginReconnect = () => {
13186
+ reconnecting = true;
13187
+ reconnectAttempt = reconnectAttempt === 0 ? 1 : reconnectAttempt + 1;
13188
+ };
13189
+ const giveUp = (reason) => {
13190
+ this.info("reconnect-gave-up", `reconnect gave-up attempt=${reconnectAttempt} reason=${reason}`);
13191
+ };
12971
13192
  while (!this.stopped) {
12972
- if (!this.isCurrent())
13193
+ if (!this.isCurrent()) {
13194
+ if (reconnecting)
13195
+ giveUp("stale-session");
12973
13196
  return;
13197
+ }
12974
13198
  if (!this.canAttach()) {
13199
+ if (reconnecting)
13200
+ giveUp("root-dormant");
12975
13201
  this.onDormant();
12976
13202
  return;
12977
13203
  }
13204
+ if (reconnecting) {
13205
+ this.info("reconnect-attempt", `reconnect attempt=${reconnectAttempt}`);
13206
+ }
12978
13207
  let client;
12979
13208
  try {
12980
13209
  client = await this.acquireClient();
12981
- } catch {
12982
- await this.backoff(attempt++);
13210
+ } catch (err) {
13211
+ if (!reconnecting)
13212
+ beginReconnect();
13213
+ this.info("reconnect-error", `reconnect error attempt=${reconnectAttempt} error=${this.errorText(err)}`);
13214
+ await this.backoff(backoffAttempt++);
13215
+ if (reconnecting)
13216
+ reconnectAttempt += 1;
12983
13217
  continue;
12984
13218
  }
12985
- if (this.stopped || !this.isCurrent())
13219
+ if (this.stopped) {
13220
+ if (reconnecting)
13221
+ giveUp("stopped");
12986
13222
  return;
13223
+ }
13224
+ if (!this.isCurrent()) {
13225
+ if (reconnecting)
13226
+ giveUp("stale-session");
13227
+ return;
13228
+ }
12987
13229
  if (!this.canAttach()) {
13230
+ if (reconnecting)
13231
+ giveUp("root-dormant");
12988
13232
  this.onDormant();
12989
13233
  return;
12990
13234
  }
@@ -12993,44 +13237,84 @@ class BgSubscription {
12993
13237
  route = await client.routeOpen({ kind: "tool_provider", module_id: AFT_MODULE_ID }, this.identity, { consumerIdentity: this.consumerIdentity });
12994
13238
  } catch (err) {
12995
13239
  if (this.isCurrent() && this.onRootAttachFailure(err)) {
13240
+ if (reconnecting)
13241
+ giveUp("root-dormant");
12996
13242
  this.onDormant();
12997
13243
  return;
12998
13244
  }
12999
13245
  if (isConsumerReconnectTransient(err))
13000
13246
  this.dropClient(client);
13001
- await this.backoff(attempt++);
13247
+ if (!reconnecting)
13248
+ beginReconnect();
13249
+ this.info("reconnect-error", `reconnect error attempt=${reconnectAttempt} error=${this.errorText(err)}`);
13250
+ await this.backoff(backoffAttempt++);
13251
+ if (reconnecting)
13252
+ reconnectAttempt += 1;
13002
13253
  continue;
13003
13254
  }
13004
13255
  if (this.stopped || !this.isCurrent()) {
13005
13256
  safeCloseRoute(client, route);
13257
+ if (reconnecting)
13258
+ giveUp(this.stopped ? "stopped" : "stale-session");
13006
13259
  return;
13007
13260
  }
13008
13261
  const subscribedAt = Date.now();
13262
+ const routeId = this.routeId(route);
13263
+ let stopDispatchProbe = () => {
13264
+ return;
13265
+ };
13009
13266
  try {
13010
13267
  const sub = client.subscribe(route, { op: "bg_events" }, () => {
13011
- if (!this.stopped && this.isCurrent())
13012
- this.onNudge();
13268
+ if (this.stopped) {
13269
+ this.info("nudge-drop-stopped", `nudge dropped cause=subscription-stopped channel=${routeId}`);
13270
+ return;
13271
+ }
13272
+ this.recordNudgeReceipt(routeId);
13273
+ if (!this.isCurrent()) {
13274
+ this.info("nudge-stale-carrier", `nudge carried by stale subscription; checking current session channel=${routeId}`);
13275
+ }
13276
+ this.onNudge();
13013
13277
  });
13014
13278
  this.current = sub;
13279
+ stopDispatchProbe = this.startDispatchProbe(client, routeId);
13280
+ this.info("subscription-open", `subscription open channel=${routeId}`);
13281
+ if (reconnecting) {
13282
+ this.info("reconnect-success", `reconnect success attempt=${reconnectAttempt} channel=${routeId}`);
13283
+ reconnecting = false;
13284
+ reconnectAttempt = 0;
13285
+ }
13015
13286
  if (this.stopped)
13016
13287
  sub.unsubscribe();
13017
13288
  if (!this.stopped && this.isCurrent())
13018
13289
  this.onNudge();
13019
13290
  await sub.closed;
13020
- return;
13291
+ this.info("stream-end", `stream ended channel=${routeId}`);
13292
+ if (this.stopped) {
13293
+ giveUp("stopped");
13294
+ return;
13295
+ }
13296
+ beginReconnect();
13021
13297
  } catch (err) {
13022
- if (this.stopped)
13298
+ const routeId2 = this.routeId(route);
13299
+ this.info("stream-error", `stream error channel=${routeId2} error=${this.errorText(err)}`);
13300
+ if (this.stopped) {
13301
+ giveUp("stopped");
13023
13302
  return;
13303
+ }
13024
13304
  if (isConsumerReconnectTransient(err))
13025
13305
  this.dropClient(client);
13026
13306
  if (Date.now() - subscribedAt >= BG_STABLE_MS)
13027
- attempt = 0;
13307
+ backoffAttempt = 0;
13308
+ beginReconnect();
13028
13309
  } finally {
13310
+ stopDispatchProbe();
13029
13311
  this.current = null;
13030
13312
  safeCloseRoute(client, route);
13031
13313
  }
13032
- await this.backoff(attempt++);
13314
+ await this.backoff(backoffAttempt++);
13033
13315
  }
13316
+ if (reconnecting)
13317
+ giveUp("stopped");
13034
13318
  }
13035
13319
  async backoff(attempt) {
13036
13320
  const ms = Math.min(100 * 2 ** Math.min(attempt, 6), 2000);
@@ -13128,6 +13412,9 @@ class SubcTransport {
13128
13412
  this.assertCurrent();
13129
13413
  const { preview, timeoutMs, onProgress } = this.splitOptions(options);
13130
13414
  const body = { name, arguments: rawArgs };
13415
+ const editSlotSurvives = this.pool.getEditSlotSurvives();
13416
+ if (editSlotSurvives !== undefined)
13417
+ body.edit_slot_survives = editSlotSurvives;
13131
13418
  if (preview === true)
13132
13419
  body.preview = true;
13133
13420
  const reply = await this.pool.routeRequest(this.identityFor(sessionId), body, timeoutMs, onProgress, this.generation);
@@ -13142,7 +13429,11 @@ class SubcTransport {
13142
13429
  }
13143
13430
  const { timeoutMs, onProgress } = this.splitOptions(options);
13144
13431
  const session = typeof params.session_id === "string" ? params.session_id : undefined;
13145
- const reply = await this.pool.routeRequest(this.identityFor(session), { name: command, arguments: params }, timeoutMs, onProgress, this.generation);
13432
+ const body = { name: command, arguments: params };
13433
+ const editSlotSurvives = this.pool.getEditSlotSurvives();
13434
+ if (editSlotSurvives !== undefined)
13435
+ body.edit_slot_survives = editSlotSurvives;
13436
+ const reply = await this.pool.routeRequest(this.identityFor(session), body, timeoutMs, onProgress, this.generation);
13146
13437
  const response = reliftReply(reply);
13147
13438
  this.captureStatusBar(response);
13148
13439
  return response;
@@ -13166,6 +13457,7 @@ class SubcTransportPool {
13166
13457
  onBgEventsNudge;
13167
13458
  onBgEventsNudgeRef;
13168
13459
  bgBackoffSleep;
13460
+ bgDispatchProbeIntervalMs;
13169
13461
  lifecycleDemandCheck;
13170
13462
  onLifecycleEvent;
13171
13463
  onBgNudgeRejected;
@@ -13181,8 +13473,11 @@ class SubcTransportPool {
13181
13473
  transportFailures = 0;
13182
13474
  transports = new Map;
13183
13475
  generationRejections = new Set;
13476
+ nudgeDeliveryLogState = new Map;
13184
13477
  pendingRootCleanups = new Set;
13185
13478
  shuttingDown = false;
13479
+ editSlotSurvives;
13480
+ editSlotSurvivesCaptured = false;
13186
13481
  constructor(options) {
13187
13482
  this.connectionFile = options.connectionFile;
13188
13483
  this.harness = options.harness;
@@ -13192,6 +13487,7 @@ class SubcTransportPool {
13192
13487
  this.onBgEventsNudge = options.onBgEventsNudge;
13193
13488
  this.onBgEventsNudgeRef = options.onBgEventsNudgeRef;
13194
13489
  this.bgBackoffSleep = options.bgBackoffSleep ?? ((ms) => new Promise((resolve4) => setTimeout(resolve4, ms)));
13490
+ this.bgDispatchProbeIntervalMs = options.bgDispatchProbeIntervalMs ?? BG_DISPATCH_PROBE_INTERVAL_MS;
13195
13491
  const lifecycle = options.lifecycle;
13196
13492
  const demandCheck = options.lifecycleDemandCheck ?? options.demandCheck ?? lifecycle?.demandCheck;
13197
13493
  this.lifecycleDemandCheck = demandCheck;
@@ -13508,6 +13804,35 @@ class SubcTransportPool {
13508
13804
  isCurrentSession(key, record) {
13509
13805
  return this.sessions.get(key) === record && !record.closed;
13510
13806
  }
13807
+ currentSessionForNudge(identity) {
13808
+ const current = this.sessions.get(identityKey(identity));
13809
+ return current && !current.closed ? current : null;
13810
+ }
13811
+ nudgeRefFor(record) {
13812
+ const poolId = this.currentPoolId();
13813
+ const generation = record.generation;
13814
+ if (poolId === undefined || generation === undefined)
13815
+ return;
13816
+ return {
13817
+ canonicalRoot: record.canonicalRoot,
13818
+ session: record.identity.session,
13819
+ concretePoolId: poolId,
13820
+ generation
13821
+ };
13822
+ }
13823
+ logNudgeDelivery(kind, record, message) {
13824
+ const key = `${kind}\x00${record.identityKey}`;
13825
+ const now = Date.now();
13826
+ const state = this.nudgeDeliveryLogState.get(key);
13827
+ if (state && now - state.lastEmittedAt < BG_LIFECYCLE_LOG_INTERVAL_MS) {
13828
+ state.suppressed += 1;
13829
+ return;
13830
+ }
13831
+ const suppressed = state?.suppressed ?? 0;
13832
+ this.nudgeDeliveryLogState.set(key, { lastEmittedAt: now, suppressed: 0 });
13833
+ const suffix = suppressed > 0 ? ` suppressed=${suppressed}` : "";
13834
+ log(`subc bg_events: ${message}${suffix}`, { sessionId: record.identity.session });
13835
+ }
13511
13836
  removeIndexMembership(record) {
13512
13837
  const keys = this.rootIndex.get(record.canonicalRoot);
13513
13838
  if (!keys)
@@ -13761,20 +14086,32 @@ class SubcTransportPool {
13761
14086
  return;
13762
14087
  if (record.bgSub)
13763
14088
  return;
13764
- const poolId = this.currentPoolId();
13765
- const generation = record.generation;
13766
- const nudgeRef = poolId !== undefined && generation !== undefined ? {
13767
- canonicalRoot: record.canonicalRoot,
13768
- session: identity.session,
13769
- concretePoolId: poolId,
13770
- generation
13771
- } : undefined;
14089
+ const nudgeRef = this.nudgeRefFor(record);
13772
14090
  const onNudge = () => {
13773
- if (!this.isCurrentSession(record.identityKey, record))
14091
+ const currentRecord = this.currentSessionForNudge(identity);
14092
+ if (!currentRecord) {
14093
+ this.logNudgeDelivery("drop-no-current-session", record, `nudge dropped cause=no-current-session root=${record.canonicalRoot}`);
13774
14094
  return;
13775
- this.onBgEventsNudge?.(identity.project_root, identity.session);
13776
- if (nudgeRef)
13777
- this.onBgEventsNudgeRef?.(nudgeRef);
14095
+ }
14096
+ if (currentRecord !== record) {
14097
+ this.logNudgeDelivery("forward-superseded-carrier", currentRecord, `nudge forwarding cause=superseded-carrying-record root=${currentRecord.canonicalRoot}`);
14098
+ }
14099
+ const currentRef = this.nudgeRefFor(currentRecord);
14100
+ let delivered = false;
14101
+ if (currentRef && this.onBgEventsNudgeRef) {
14102
+ this.onBgEventsNudgeRef(currentRef);
14103
+ delivered = true;
14104
+ }
14105
+ if (this.onBgEventsNudge) {
14106
+ if (!currentRef && this.onBgEventsNudgeRef) {
14107
+ this.logNudgeDelivery("fallback-missing-generation", currentRecord, `nudge dispatch fallback=root-session-handler cause=generation-provenance-unavailable root=${currentRecord.canonicalRoot}`);
14108
+ }
14109
+ this.onBgEventsNudge(currentRecord.identity.project_root, currentRecord.identity.session);
14110
+ delivered = true;
14111
+ }
14112
+ if (delivered)
14113
+ return;
14114
+ this.logNudgeDelivery("drop-no-compatible-handler", currentRecord, `nudge dropped cause=generation-provenance-unavailable-and-root-session-handler-unwired root=${currentRecord.canonicalRoot}`);
13778
14115
  };
13779
14116
  let sub = null;
13780
14117
  const clearDormantSubscription = () => {
@@ -13786,7 +14123,7 @@ class SubcTransportPool {
13786
14123
  return false;
13787
14124
  this.markRootDormant(record.canonicalRoot, error2);
13788
14125
  return true;
13789
- }, clearDormantSubscription, nudgeRef, () => this.isCurrentSession(record.identityKey, record) && this.isCurrentLiveGeneration(record.canonicalRoot, record.generation));
14126
+ }, clearDormantSubscription, this.bgDispatchProbeIntervalMs, nudgeRef, () => this.isCurrentSession(record.identityKey, record) && this.isCurrentLiveGeneration(record.canonicalRoot, record.generation));
13790
14127
  record.bgSub = sub;
13791
14128
  if (!this.rootCanAttach(record.canonicalRoot)) {
13792
14129
  record.bgSub = null;
@@ -13848,7 +14185,21 @@ class SubcTransportPool {
13848
14185
  return Promise.resolve();
13849
14186
  return registry.requestProjectRootClose(registration.concretePoolId, root, generation, cause);
13850
14187
  }
13851
- setConfigureOverride(_key, _value) {}
14188
+ setConfigureOverride(key, value) {
14189
+ if (key !== "edit_slot_survives")
14190
+ return;
14191
+ if (typeof value !== "boolean") {
14192
+ throw new Error("edit_slot_survives must be set once to a boolean");
14193
+ }
14194
+ if (this.editSlotSurvivesCaptured) {
14195
+ throw new Error("edit_slot_survives is write-once and was already captured");
14196
+ }
14197
+ this.editSlotSurvives = value;
14198
+ this.editSlotSurvivesCaptured = true;
14199
+ }
14200
+ getEditSlotSurvives() {
14201
+ return this.editSlotSurvives;
14202
+ }
13852
14203
  async reconfigure(_projectRoot, _overrides) {}
13853
14204
  async replaceBinary(path2) {
13854
14205
  return path2;
@@ -13926,14 +14277,29 @@ function toolErrorFromResponse(command, response) {
13926
14277
  return new AftToolError(message, code, response);
13927
14278
  }
13928
14279
  var BASH_TRANSPORT_DISPOSITION = "The transport to the AFT daemon was interrupted; no background task was created for this command and no task ID exists. Re-run the command. Do not poll bash_status for it.";
14280
+ var SUBC_MODULE_RESTART_DISPOSITION = "The AFT daemon module restarted while this call was in flight, so its outcome is UNKNOWN: it may or may not have executed. Verify actual state before re-running, and never blind-retry a mutation.";
14281
+ function isRouteGoodbyeError(error2) {
14282
+ if (!(error2 instanceof SubcError))
14283
+ return false;
14284
+ if (error2.code === undefined) {
14285
+ return error2.message.includes("route closed by subc");
14286
+ }
14287
+ return error2.code === "route_closed" && error2.message.includes("route closed by subc");
14288
+ }
13929
14289
  function isTransportClassError(error2) {
13930
14290
  return isBridgeTransportTimeout(error2) || isConsumerReconnectTransient(error2) || error2 instanceof StaleRouteHandleError || error2 instanceof SubcRootGenerationExpiredError || error2 instanceof SubcRootReapedError;
13931
14291
  }
13932
14292
  function adaptToolError(command, error2) {
13933
- if (command !== "bash" || !isTransportClassError(error2))
13934
- return error2;
13935
14293
  if (!(error2 instanceof Error))
13936
14294
  return error2;
14295
+ if (isRouteGoodbyeError(error2)) {
14296
+ if (error2.message.includes(SUBC_MODULE_RESTART_DISPOSITION))
14297
+ return error2;
14298
+ error2.message = error2.message ? `${error2.message} ${SUBC_MODULE_RESTART_DISPOSITION}` : SUBC_MODULE_RESTART_DISPOSITION;
14299
+ return error2;
14300
+ }
14301
+ if (command !== "bash" || !isTransportClassError(error2))
14302
+ return error2;
13937
14303
  if (error2.message.includes(BASH_TRANSPORT_DISPOSITION))
13938
14304
  return error2;
13939
14305
  error2.message = error2.message ? `${error2.message} ${BASH_TRANSPORT_DISPOSITION}` : BASH_TRANSPORT_DISPOSITION;
@@ -14740,7 +15106,7 @@ async function ensureStorageMigrated(opts) {
14740
15106
  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}` : ""));
14741
15107
  }
14742
15108
  // ../aft-bridge/dist/npm-resolver.js
14743
- import { readdirSync, statSync as statSync5 } from "node:fs";
15109
+ import { readdirSync as readdirSync2, statSync as statSync5 } from "node:fs";
14744
15110
  import { homedir as homedir9 } from "node:os";
14745
15111
  import { delimiter, dirname as dirname4, isAbsolute as isAbsolute3, join as join9 } from "node:path";
14746
15112
  function defaultDeps() {
@@ -14780,7 +15146,7 @@ function npmAdjacentToNode(deps) {
14780
15146
  function highestVersionedNodeBin(installsDir, name) {
14781
15147
  let entries;
14782
15148
  try {
14783
- entries = readdirSync(installsDir);
15149
+ entries = readdirSync2(installsDir);
14784
15150
  } catch {
14785
15151
  return null;
14786
15152
  }
@@ -14856,7 +15222,7 @@ function npmSpawnEnv(resolved, baseEnv = process.env) {
14856
15222
  // ../aft-bridge/dist/onnx-runtime.js
14857
15223
  import { execFileSync } from "node:child_process";
14858
15224
  import { createHash as createHash3 } from "node:crypto";
14859
- import { chmodSync as chmodSync3, closeSync as closeSync4, copyFileSync as copyFileSync3, createWriteStream as createWriteStream2, existsSync as existsSync7, lstatSync, mkdirSync as mkdirSync5, openSync as openSync4, readdirSync as readdirSync2, readFileSync as readFileSync6, readlinkSync, realpathSync as realpathSync2, rmSync as rmSync3, statSync as statSync6, symlinkSync, unlinkSync as unlinkSync4, writeFileSync as writeFileSync3 } from "node:fs";
15225
+ import { chmodSync as chmodSync3, closeSync as closeSync4, copyFileSync as copyFileSync3, createWriteStream as createWriteStream2, existsSync as existsSync7, lstatSync, mkdirSync as mkdirSync5, openSync as openSync4, readdirSync as readdirSync3, readFileSync as readFileSync6, readlinkSync, realpathSync as realpathSync2, rmSync as rmSync3, statSync as statSync6, symlinkSync, unlinkSync as unlinkSync4, writeFileSync as writeFileSync3 } from "node:fs";
14860
15226
  import { basename as basename2, dirname as dirname5, isAbsolute as isAbsolute4, join as join10, relative as relative2, resolve as resolve6, win32 } from "node:path";
14861
15227
  import { Readable as Readable2 } from "node:stream";
14862
15228
  import { pipeline as pipeline2 } from "node:stream/promises";
@@ -14970,7 +15336,7 @@ async function ensureOnnxRuntime(storageDir) {
14970
15336
  }
14971
15337
  function cleanupAbandonedStagingDirs(onnxBaseDir) {
14972
15338
  try {
14973
- const entries = readdirSync2(onnxBaseDir);
15339
+ const entries = readdirSync3(onnxBaseDir);
14974
15340
  for (const entry of entries) {
14975
15341
  if (!entry.startsWith(`${ORT_VERSION}.tmp.`))
14976
15342
  continue;
@@ -14981,7 +15347,7 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
14981
15347
  let abandoned = false;
14982
15348
  if (Number.isFinite(pid) && pid > 0) {
14983
15349
  if (process.platform === "win32") {
14984
- const ownerAlive = isProcessAlive(pid);
15350
+ const ownerAlive = isProcessAlive2(pid);
14985
15351
  if (!ownerAlive) {
14986
15352
  abandoned = true;
14987
15353
  } else {
@@ -14993,7 +15359,7 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
14993
15359
  }
14994
15360
  }
14995
15361
  } else {
14996
- abandoned = !isProcessAlive(pid);
15362
+ abandoned = !isProcessAlive2(pid);
14997
15363
  }
14998
15364
  } else {
14999
15365
  abandoned = true;
@@ -15044,7 +15410,7 @@ function isPathInsideRoot(root, candidate) {
15044
15410
  }
15045
15411
  function detectOnnxVersion(libDir, libName) {
15046
15412
  try {
15047
- const entries = readdirSync2(libDir);
15413
+ const entries = readdirSync3(libDir);
15048
15414
  const barePrefix = libName.replace(/\.(so|dylib|dll)$/, "");
15049
15415
  const expectedPrefix = process.platform === "win32" ? barePrefix.toLowerCase() : barePrefix;
15050
15416
  for (const entry of entries) {
@@ -15107,7 +15473,7 @@ function isWindowsSystem32Directory(dir) {
15107
15473
  }
15108
15474
  function directoryContainsLibrary(dir, libName) {
15109
15475
  try {
15110
- const entries = readdirSync2(dir);
15476
+ const entries = readdirSync3(dir);
15111
15477
  if (process.platform === "win32") {
15112
15478
  const expected = libName.toLowerCase();
15113
15479
  return entries.some((entry) => entry.toLowerCase() === expected);
@@ -15145,7 +15511,7 @@ function findSystemOnnxRuntime(libName) {
15145
15511
  if (!existsSync7(nugetPackageDir))
15146
15512
  return nugetPaths;
15147
15513
  try {
15148
- for (const entry of readdirSync2(nugetPackageDir, { withFileTypes: true })) {
15514
+ for (const entry of readdirSync3(nugetPackageDir, { withFileTypes: true })) {
15149
15515
  if (!entry.isDirectory())
15150
15516
  continue;
15151
15517
  if (entry.name === "__globalPackagesFolder" || entry.name.startsWith("."))
@@ -15240,7 +15606,7 @@ function validateExtractedTree(stagingRoot) {
15240
15606
  const realRoot = realpathSync2(stagingRoot);
15241
15607
  let totalBytes = 0;
15242
15608
  const walk = (dir) => {
15243
- const entries = readdirSync2(dir);
15609
+ const entries = readdirSync3(dir);
15244
15610
  for (const entry of entries) {
15245
15611
  const fullPath = join10(dir, entry);
15246
15612
  const lst = lstatSync(fullPath);
@@ -15298,7 +15664,7 @@ async function downloadOnnxRuntime(info, targetDir) {
15298
15664
  throw new Error(`Expected directory not found: ${extractedDir}`);
15299
15665
  }
15300
15666
  mkdirSync5(targetDir, { recursive: true });
15301
- const libFiles = readdirSync2(extractedDir).filter((f) => f.startsWith("libonnxruntime") || f.startsWith("onnxruntime"));
15667
+ const libFiles = readdirSync3(extractedDir).filter((f) => f.startsWith("libonnxruntime") || f.startsWith("onnxruntime"));
15302
15668
  const realFiles = [];
15303
15669
  const symlinks = [];
15304
15670
  for (const libFile of libFiles) {
@@ -15476,7 +15842,7 @@ ${new Date().toISOString()}
15476
15842
  }
15477
15843
  const age = Date.now() - lockMtimeMs;
15478
15844
  const ageWithinFresh = Math.abs(age) < STALE_LOCK_MS;
15479
- const ownerAlive = owningPid !== null && isProcessAlive(owningPid);
15845
+ const ownerAlive = owningPid !== null && isProcessAlive2(owningPid);
15480
15846
  if (ownerAlive && ageWithinFresh) {
15481
15847
  return false;
15482
15848
  }
@@ -15540,7 +15906,7 @@ function isWindowsProcessAlive(pid) {
15540
15906
  return false;
15541
15907
  }
15542
15908
  }
15543
- function isProcessAlive(pid) {
15909
+ function isProcessAlive2(pid) {
15544
15910
  if (process.platform === "win32")
15545
15911
  return isWindowsProcessAlive(pid);
15546
15912
  try {
@@ -15915,6 +16281,19 @@ function parseEditArray(value) {
15915
16281
  }
15916
16282
  return value;
15917
16283
  }
16284
+ function stripLineRangeSentinels(item) {
16285
+ const hasRangeField = ["startLine", "endLine", "content"].some((key) => hasOwn(item, key));
16286
+ if (!hasRangeField)
16287
+ return;
16288
+ if (item.oldString === "")
16289
+ delete item.oldString;
16290
+ if (item.newString === "")
16291
+ delete item.newString;
16292
+ if (item.replaceAll === false)
16293
+ delete item.replaceAll;
16294
+ if (item.occurrence === 1)
16295
+ delete item.occurrence;
16296
+ }
15918
16297
  function normalizeEditItem(value, index) {
15919
16298
  if (!value || typeof value !== "object" || Array.isArray(value)) {
15920
16299
  throw new InvalidRequestError(`edit: edits[${index}] must be an object`);
@@ -15923,6 +16302,7 @@ function normalizeEditItem(value, index) {
15923
16302
  const item = copyOwnProperties(source);
15924
16303
  normalizeItemAlias(item, "oldString", "oldText");
15925
16304
  normalizeItemAlias(item, "newString", "newText");
16305
+ stripLineRangeSentinels(item);
15926
16306
  const hasFindField = ["oldString", "newString", "replaceAll", "occurrence"].some((key) => hasOwn(item, key));
15927
16307
  const hasRangeField = ["startLine", "endLine", "content"].some((key) => hasOwn(item, key));
15928
16308
  if (hasFindField && hasRangeField) {
@@ -16065,6 +16445,8 @@ class BridgePool {
16065
16445
  idleTimeoutMs;
16066
16446
  bridgeOptions;
16067
16447
  configOverrides;
16448
+ editSlotSurvives;
16449
+ editSlotSurvivesCaptured = false;
16068
16450
  projectConfigLoader;
16069
16451
  logger;
16070
16452
  cleanupTimer = null;
@@ -16089,7 +16471,16 @@ class BridgePool {
16089
16471
  logger: options.logger,
16090
16472
  childEnv: options.childEnv
16091
16473
  };
16092
- this.configOverrides = configOverrides;
16474
+ this.configOverrides = { ...configOverrides };
16475
+ const initialEditSlotSurvives = this.configOverrides.edit_slot_survives;
16476
+ delete this.configOverrides.edit_slot_survives;
16477
+ if (initialEditSlotSurvives !== undefined) {
16478
+ if (typeof initialEditSlotSurvives !== "boolean") {
16479
+ throw new Error("edit_slot_survives must be a boolean");
16480
+ }
16481
+ this.editSlotSurvives = initialEditSlotSurvives;
16482
+ this.editSlotSurvivesCaptured = true;
16483
+ }
16093
16484
  this.startCleanupTimer();
16094
16485
  }
16095
16486
  getActiveBridgeForRoot(projectRoot) {
@@ -16128,7 +16519,8 @@ class BridgePool {
16128
16519
  }
16129
16520
  const projectOverrides = this.loadProjectOverrides(key);
16130
16521
  const mergedOverrides = { ...this.configOverrides, ...projectOverrides };
16131
- const bridge = new BinaryBridge(this.binaryPath, key, this.bridgeOptions, mergedOverrides);
16522
+ delete mergedOverrides.edit_slot_survives;
16523
+ const bridge = new BinaryBridge(this.binaryPath, key, this.bridgeOptions, mergedOverrides, this.editSlotSurvivesCaptured ? this.editSlotSurvives : undefined);
16132
16524
  this.bridges.set(key, { bridge, lastUsed: Date.now() });
16133
16525
  return bridge;
16134
16526
  }
@@ -16232,6 +16624,23 @@ class BridgePool {
16232
16624
  error(message, meta);
16233
16625
  }
16234
16626
  setConfigureOverride(key, value) {
16627
+ if (key === "edit_slot_survives") {
16628
+ if (typeof value !== "boolean") {
16629
+ throw new Error("edit_slot_survives must be set once to a boolean");
16630
+ }
16631
+ if (this.editSlotSurvivesCaptured) {
16632
+ throw new Error("edit_slot_survives is write-once and was already captured");
16633
+ }
16634
+ this.editSlotSurvives = value;
16635
+ this.editSlotSurvivesCaptured = true;
16636
+ for (const entry of this.bridges.values()) {
16637
+ entry.bridge.setEditSlotSurvives(value);
16638
+ }
16639
+ for (const bridge of this.staleBridges) {
16640
+ bridge.setEditSlotSurvives(value);
16641
+ }
16642
+ return;
16643
+ }
16235
16644
  if (value === undefined) {
16236
16645
  delete this.configOverrides[key];
16237
16646
  } else {
@@ -16289,6 +16698,7 @@ class RevivableTransportPool {
16289
16698
  revival = null;
16290
16699
  transports = new Map;
16291
16700
  configureOverrides = new Map;
16701
+ editSlotSurvivesCaptured = false;
16292
16702
  constructor(initialPool, createPool, onBinaryReplaced) {
16293
16703
  this.createPool = createPool;
16294
16704
  this.onBinaryReplaced = onBinaryReplaced;
@@ -16319,6 +16729,18 @@ class RevivableTransportPool {
16319
16729
  return this.getBridge(projectRoot).toolCall(runtime.sessionID, name, rawArgs, options);
16320
16730
  }
16321
16731
  setConfigureOverride(key, value) {
16732
+ if (key === "edit_slot_survives") {
16733
+ if (typeof value !== "boolean") {
16734
+ throw new Error("edit_slot_survives must be set once to a boolean");
16735
+ }
16736
+ if (this.editSlotSurvivesCaptured) {
16737
+ throw new Error("edit_slot_survives is write-once and was already captured");
16738
+ }
16739
+ this.activePool.setConfigureOverride(key, value);
16740
+ this.editSlotSurvivesCaptured = true;
16741
+ this.configureOverrides.set(key, value);
16742
+ return;
16743
+ }
16322
16744
  if (value === undefined)
16323
16745
  this.configureOverrides.delete(key);
16324
16746
  else
@@ -16326,14 +16748,20 @@ class RevivableTransportPool {
16326
16748
  this.activePool.setConfigureOverride(key, value);
16327
16749
  }
16328
16750
  async reconfigure(projectRoot, overrides) {
16751
+ const pool = await this.ensureActivePool();
16752
+ const runtimeOverrides = {};
16329
16753
  for (const [key, value] of Object.entries(overrides)) {
16754
+ if (key === "edit_slot_survives") {
16755
+ this.setConfigureOverride(key, value);
16756
+ continue;
16757
+ }
16330
16758
  if (value === undefined)
16331
16759
  this.configureOverrides.delete(key);
16332
16760
  else
16333
16761
  this.configureOverrides.set(key, value);
16762
+ runtimeOverrides[key] = value;
16334
16763
  }
16335
- const pool = await this.ensureActivePool();
16336
- await pool.reconfigure(projectRoot, overrides);
16764
+ await pool.reconfigure(projectRoot, runtimeOverrides);
16337
16765
  }
16338
16766
  async replaceBinary(path2) {
16339
16767
  const replaced = await this.activePool.replaceBinary(path2);
@@ -16739,6 +17167,12 @@ var UNKNOWN_COMPLETION_TTL_MS = 5000;
16739
17167
  var UNKNOWN_COMPLETION_CAP = 32;
16740
17168
  var DEFAULT_SESSION_ID2 = "__default__";
16741
17169
  var LOG_PREFIX = "[aft-pi] bg-notifications:";
17170
+ var SUBC_NUDGE_LOG_INTERVAL_MS = 60000;
17171
+ var subcNudgesInFlight = new Map;
17172
+ var subcNudgeLogState = new Map;
17173
+ function normalizeSessionID(sessionID) {
17174
+ return sessionID && sessionID.length > 0 ? sessionID : DEFAULT_SESSION_ID2;
17175
+ }
16742
17176
  function consumeBgCompletion(sessionID, taskId) {
16743
17177
  const state = stateFor(sessionID);
16744
17178
  state.pendingCompletions = state.pendingCompletions.filter((c) => c.task_id !== taskId);
@@ -16934,11 +17368,49 @@ async function handleTurnEndBgCompletions(drainContext) {
16934
17368
  stateFor(drainContext.sessionID).wakeDeferredTaskIds.clear();
16935
17369
  await triggerWakeIfPending(drainContext, false, true);
16936
17370
  }
16937
- async function handleSubcBgEventsNudge(drainContext) {
17371
+ function handleSubcBgEventsNudge(drainContext) {
17372
+ const resolvedDrainContext = {
17373
+ ...drainContext,
17374
+ sessionID: normalizeSessionID(drainContext.sessionID)
17375
+ };
17376
+ const key = `${resolvedDrainContext.directory}\x00${resolvedDrainContext.sessionID}`;
17377
+ const inFlight = subcNudgesInFlight.get(key);
17378
+ if (inFlight) {
17379
+ logSubcNudgeLifecycle(resolvedDrainContext, "coalesced-in-flight", "nudge coalesced cause=handler-already-in-flight");
17380
+ return inFlight;
17381
+ }
17382
+ logSubcNudgeLifecycle(resolvedDrainContext, "handler-entry", "nudge handler entered");
17383
+ const handling = handleSubcBgEventsNudgeOnce(resolvedDrainContext);
17384
+ subcNudgesInFlight.set(key, handling);
17385
+ const clear = () => {
17386
+ if (subcNudgesInFlight.get(key) === handling)
17387
+ subcNudgesInFlight.delete(key);
17388
+ };
17389
+ handling.then(clear, clear);
17390
+ return handling;
17391
+ }
17392
+ async function handleSubcBgEventsNudgeOnce(drainContext) {
16938
17393
  bridgeForDrain(drainContext);
16939
17394
  stateFor(drainContext.sessionID).wakeDeferredTaskIds.clear();
16940
17395
  await triggerWakeIfPending(drainContext, false, true, true);
16941
17396
  }
17397
+ function logSubcNudgeLifecycle(drainContext, kind, message) {
17398
+ const key = `${kind}\x00${drainContext.directory}\x00${drainContext.sessionID}`;
17399
+ const now = Date.now();
17400
+ const state = subcNudgeLogState.get(key);
17401
+ if (state && now - state.lastEmittedAt < SUBC_NUDGE_LOG_INTERVAL_MS) {
17402
+ state.suppressed += 1;
17403
+ return;
17404
+ }
17405
+ const suppressed = state?.suppressed ?? 0;
17406
+ subcNudgeLogState.set(key, { lastEmittedAt: now, suppressed: 0 });
17407
+ sessionLog(drainContext.sessionID, `${LOG_PREFIX} ${message}`, {
17408
+ event: "subc_bg_nudge_delivery",
17409
+ cause: kind,
17410
+ canonical_root: drainContext.directory,
17411
+ suppressed
17412
+ });
17413
+ }
16942
17414
  async function triggerWakeIfPending(drainContext, skipDrain, includeDeferredCompletions = true, forceDrain = false) {
16943
17415
  const state = stateFor(drainContext.sessionID);
16944
17416
  if (!skipDrain && (forceDrain || state.outstandingTaskIds.size > 0 || !state.forcedDrainCompleted)) {
@@ -17406,7 +17878,7 @@ import {
17406
17878
  // package.json
17407
17879
  var package_default = {
17408
17880
  name: "@cortexkit/aft-pi",
17409
- version: "0.49.4",
17881
+ version: "0.50.1",
17410
17882
  type: "module",
17411
17883
  description: "Pi coding agent extension for Agent File Tools (AFT) — tree-sitter and LSP-powered code analysis",
17412
17884
  main: "dist/index.js",
@@ -17429,19 +17901,19 @@ var package_default = {
17429
17901
  "test:unit": "bun test src/__tests__ --path-ignore-patterns 'src/__tests__/e2e/**'"
17430
17902
  },
17431
17903
  dependencies: {
17432
- "@cortexkit/aft-bridge": "0.49.4",
17904
+ "@cortexkit/aft-bridge": "0.50.1",
17433
17905
  "comment-json": "^5.0.0",
17434
17906
  diff: "^8.0.4",
17435
17907
  typebox: "1.1.38",
17436
17908
  zod: "^4.4.3"
17437
17909
  },
17438
17910
  optionalDependencies: {
17439
- "@cortexkit/aft-darwin-arm64": "0.49.4",
17440
- "@cortexkit/aft-darwin-x64": "0.49.4",
17441
- "@cortexkit/aft-linux-arm64": "0.49.4",
17442
- "@cortexkit/aft-linux-x64": "0.49.4",
17443
- "@cortexkit/aft-win32-arm64": "0.49.4",
17444
- "@cortexkit/aft-win32-x64": "0.49.4"
17911
+ "@cortexkit/aft-darwin-arm64": "0.50.1",
17912
+ "@cortexkit/aft-darwin-x64": "0.50.1",
17913
+ "@cortexkit/aft-linux-arm64": "0.50.1",
17914
+ "@cortexkit/aft-linux-x64": "0.50.1",
17915
+ "@cortexkit/aft-win32-arm64": "0.50.1",
17916
+ "@cortexkit/aft-win32-x64": "0.50.1"
17445
17917
  },
17446
17918
  devDependencies: {
17447
17919
  "@earendil-works/pi-coding-agent": "^0.80.2",
@@ -18181,7 +18653,7 @@ __export(exports_external, {
18181
18653
  instanceof: () => _instanceof,
18182
18654
  includes: () => _includes,
18183
18655
  httpUrl: () => httpUrl,
18184
- hostname: () => hostname2,
18656
+ hostname: () => hostname3,
18185
18657
  hex: () => hex2,
18186
18658
  hash: () => hash,
18187
18659
  guid: () => guid2,
@@ -19632,7 +20104,7 @@ __export(exports_regexes, {
19632
20104
  idnEmail: () => idnEmail,
19633
20105
  httpProtocol: () => httpProtocol,
19634
20106
  html5Email: () => html5Email,
19635
- hostname: () => hostname,
20107
+ hostname: () => hostname2,
19636
20108
  hex: () => hex,
19637
20109
  guid: () => guid,
19638
20110
  extendedDuration: () => extendedDuration,
@@ -19690,7 +20162,7 @@ var cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]
19690
20162
  var cidrv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/;
19691
20163
  var base64 = /^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/;
19692
20164
  var base64url = /^[A-Za-z0-9_-]*$/;
19693
- var hostname = /^(?=.{1,253}\.?$)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[-0-9a-zA-Z]{0,61}[0-9a-zA-Z])?)*\.?$/;
20165
+ var hostname2 = /^(?=.{1,253}\.?$)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[-0-9a-zA-Z]{0,61}[0-9a-zA-Z])?)*\.?$/;
19694
20166
  var domain = /^([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
19695
20167
  var httpProtocol = /^https?$/;
19696
20168
  var e164 = /^\+[1-9]\d{6,14}$/;
@@ -30314,7 +30786,7 @@ __export(exports_schemas2, {
30314
30786
  int: () => int,
30315
30787
  instanceof: () => _instanceof,
30316
30788
  httpUrl: () => httpUrl,
30317
- hostname: () => hostname2,
30789
+ hostname: () => hostname3,
30318
30790
  hex: () => hex2,
30319
30791
  hash: () => hash,
30320
30792
  guid: () => guid2,
@@ -30966,7 +31438,7 @@ var ZodCustomStringFormat = /* @__PURE__ */ $constructor("ZodCustomStringFormat"
30966
31438
  function stringFormat(format, fnOrRegex, _params = {}) {
30967
31439
  return _stringFormat(ZodCustomStringFormat, format, fnOrRegex, _params);
30968
31440
  }
30969
- function hostname2(_params) {
31441
+ function hostname3(_params) {
30970
31442
  return _stringFormat(ZodCustomStringFormat, "hostname", exports_regexes.hostname, _params);
30971
31443
  }
30972
31444
  function hex2(_params) {
@@ -32502,6 +32974,7 @@ var BackupConfigSchema = exports_external.object({
32502
32974
  var AftConfigSchema = exports_external.preprocess((value) => stripHarnessSpecificConfigKeys(value, OPENCODE_ONLY_KEYS), exports_external.object({
32503
32975
  $schema: exports_external.string().optional(),
32504
32976
  enabled: exports_external.boolean().optional(),
32977
+ edit_mode: exports_external.enum(["default", "hashline"]).optional(),
32505
32978
  format_on_edit: exports_external.boolean().optional(),
32506
32979
  formatter_timeout_secs: exports_external.number().int().min(1).max(600).optional(),
32507
32980
  validate_on_edit: exports_external.enum(["syntax", "full"]).optional(),
@@ -32715,10 +33188,15 @@ function loadConfigFromPath(configPath) {
32715
33188
  }
32716
33189
  }
32717
33190
  function parseConfigPartially(rawConfig) {
33191
+ let configForParsing = rawConfig;
33192
+ if (Object.hasOwn(rawConfig, "edit_mode") && rawConfig.edit_mode !== "default" && rawConfig.edit_mode !== "hashline") {
33193
+ warn2(`Unknown edit_mode value ${JSON.stringify(rawConfig.edit_mode)}; falling back to "default"`);
33194
+ configForParsing = { ...rawConfig, edit_mode: "default" };
33195
+ }
32718
33196
  const partialConfig = {};
32719
33197
  const invalidSections = [];
32720
- for (const key of Object.keys(rawConfig)) {
32721
- const sectionResult = AftConfigSchema.safeParse({ [key]: rawConfig[key] });
33198
+ for (const key of Object.keys(configForParsing)) {
33199
+ const sectionResult = AftConfigSchema.safeParse({ [key]: configForParsing[key] });
32722
33200
  if (sectionResult.success) {
32723
33201
  const parsed = sectionResult.data;
32724
33202
  if (parsed[key] !== undefined) {
@@ -32843,6 +33321,7 @@ function getProjectLspStrippedKeys(lsp) {
32843
33321
  }
32844
33322
  var PROJECT_SAFE_TOP_LEVEL_FIELDS = new Set([
32845
33323
  "enabled",
33324
+ "edit_mode",
32846
33325
  "tool_surface",
32847
33326
  "format_on_edit",
32848
33327
  "validate_on_edit",
@@ -33122,7 +33601,7 @@ ${new Date().toISOString()}
33122
33601
  const age = Date.now() - lockMtimeMs;
33123
33602
  const ageWithinFresh = Math.abs(age) < STALE_LOCK_MS2;
33124
33603
  const skipLiveness = process.platform === "win32";
33125
- const ownerAlive = !skipLiveness && owningPid !== null && isProcessAlive2(owningPid);
33604
+ const ownerAlive = !skipLiveness && owningPid !== null && isProcessAlive3(owningPid);
33126
33605
  if (skipLiveness ? ageWithinFresh : ownerAlive && ageWithinFresh) {
33127
33606
  return false;
33128
33607
  }
@@ -33132,7 +33611,7 @@ ${new Date().toISOString()}
33132
33611
  } catch {}
33133
33612
  return tryClaim();
33134
33613
  }
33135
- function isProcessAlive2(pid) {
33614
+ function isProcessAlive3(pid) {
33136
33615
  try {
33137
33616
  process.kill(pid, 0);
33138
33617
  return true;
@@ -33366,7 +33845,7 @@ var NPM_LSP_TABLE = [
33366
33845
  ];
33367
33846
 
33368
33847
  // src/lsp-project-relevance.ts
33369
- import { existsSync as existsSync11, readdirSync as readdirSync3, readFileSync as readFileSync9 } from "node:fs";
33848
+ import { existsSync as existsSync11, readdirSync as readdirSync4, readFileSync as readFileSync9 } from "node:fs";
33370
33849
  import { join as join13 } from "node:path";
33371
33850
  var MAX_WALK_DIRS = 200;
33372
33851
  var MAX_WALK_DEPTH = 4;
@@ -33431,7 +33910,7 @@ function relevantExtensionsInProject(projectRoot, extToServer) {
33431
33910
  visitedDirs += 1;
33432
33911
  let entries;
33433
33912
  try {
33434
- entries = readdirSync3(current.dir, { withFileTypes: true });
33913
+ entries = readdirSync4(current.dir, { withFileTypes: true });
33435
33914
  } catch {
33436
33915
  continue;
33437
33916
  }
@@ -33851,7 +34330,7 @@ import {
33851
34330
  existsSync as existsSync13,
33852
34331
  lstatSync as lstatSync2,
33853
34332
  mkdirSync as mkdirSync8,
33854
- readdirSync as readdirSync4,
34333
+ readdirSync as readdirSync5,
33855
34334
  readFileSync as readFileSync11,
33856
34335
  readlinkSync as readlinkSync2,
33857
34336
  realpathSync as realpathSync3,
@@ -34254,7 +34733,7 @@ function validateExtraction(stagingRoot) {
34254
34733
  const walk = (dir) => {
34255
34734
  let entries;
34256
34735
  try {
34257
- entries = readdirSync4(dir);
34736
+ entries = readdirSync5(dir);
34258
34737
  } catch (err) {
34259
34738
  throw new Error(`failed to read staging dir ${dir}: ${err}`);
34260
34739
  }
@@ -35190,6 +35669,15 @@ var EditParams = Type2.Object({
35190
35669
  description: "Batch edits — non-empty array of { oldString, newString }, { oldString, newString, replaceAll: true }, or { startLine, endLine, content } objects applied atomically."
35191
35670
  }))
35192
35671
  });
35672
+ var HashlineEditParams = Type2.Object({
35673
+ patch: Type2.String({
35674
+ minLength: 1,
35675
+ description: "Hashline patch text with one or more [path#TAG] sections and PUT/CUT/REM/MV operations"
35676
+ })
35677
+ }, { additionalProperties: false });
35678
+ function stringList(value) {
35679
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
35680
+ }
35193
35681
  var GrepParams = Type2.Object({
35194
35682
  pattern: Type2.String({ description: "Regex pattern to search for" }),
35195
35683
  path: Type2.Optional(Type2.String({
@@ -35354,7 +35842,42 @@ PDFs aren't supported on the Pi harness yet.`, response);
35354
35842
  }
35355
35843
  }));
35356
35844
  }
35357
- if (surface.hoistEdit) {
35845
+ if (surface.hoistEdit && ctx.hashlineEffective === true) {
35846
+ pi.registerTool({
35847
+ name: "edit",
35848
+ label: "edit",
35849
+ description: "Apply a hashline patch. Arguments are exactly `{patch}` where `patch` is a non-empty string of one or more `[path#TAG]` sections with PUT/CUT/REM/MV operations. Paths and tags come from section headers; obtain tags from tagged reads. Server-owned preview control is outside this schema.",
35850
+ promptSnippet: "Apply a tagged hashline patch through the edit tool",
35851
+ promptGuidelines: [
35852
+ "Read a file to obtain its current hashline tag before editing it.",
35853
+ "Pass exactly one patch string; paths and tags belong in patch section headers."
35854
+ ],
35855
+ parameters: HashlineEditParams,
35856
+ async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
35857
+ const bridge = bridgeFor(ctx, extCtx.cwd);
35858
+ const rawArgs = { patch: params.patch };
35859
+ const preflight = await callToolCall(bridge, "hashline_preflight", rawArgs, extCtx);
35860
+ if (preflight.success === false)
35861
+ throw toolErrorFromResponse("edit", preflight);
35862
+ for (const target of [
35863
+ ...stringList(preflight.affected_paths),
35864
+ ...stringList(preflight.mv_destinations)
35865
+ ]) {
35866
+ await assertExternalDirectoryPermission(extCtx, await resolvePathArg(extCtx.cwd, target), {
35867
+ restrictToProjectRoot: surface.restrictToProjectRoot
35868
+ });
35869
+ }
35870
+ const preview = await callToolCall(bridge, "edit", rawArgs, extCtx, { preview: true });
35871
+ if (preview.success === false)
35872
+ throw toolErrorFromResponse("edit", preview);
35873
+ const response = await callToolCall(bridge, "edit", rawArgs, extCtx);
35874
+ if (response.success === false)
35875
+ throw toolErrorFromResponse("edit", response);
35876
+ return buildMutationResult(response);
35877
+ }
35878
+ });
35879
+ }
35880
+ if (surface.hoistEdit && ctx.hashlineEffective !== true) {
35358
35881
  pi.registerTool(withPathAliasPreparation({
35359
35882
  name: "edit",
35360
35883
  label: "edit",
@@ -38574,7 +39097,7 @@ var PLUGIN_VERSION = (() => {
38574
39097
  return "0.0.0";
38575
39098
  }
38576
39099
  })();
38577
- var ANNOUNCEMENT_VERSION = "0.49.4";
39100
+ var ANNOUNCEMENT_VERSION = "0.50.1";
38578
39101
  var ANNOUNCEMENT_FEATURES = [
38579
39102
  "Callgraph stays consistent under load: edits arriving mid-rebuild can no longer be silently lost from navigation results.",
38580
39103
  "aft_zoom handles real-world .jsonc: files with comment banners above the opening brace now resolve, and not-found errors point at the segment that actually failed (thanks @iceteaSA).",
@@ -38850,6 +39373,16 @@ ${lines}
38850
39373
  poolOptions,
38851
39374
  configOverrides,
38852
39375
  subcConnectionFile: config2.subc?.connection_file,
39376
+ onBgEventsNudge: (directory, sessionID) => {
39377
+ handleSubcBgEventsNudge({
39378
+ ctx,
39379
+ directory,
39380
+ sessionID,
39381
+ runtime: pi
39382
+ }).catch((err) => {
39383
+ warn2(`[aft-pi] bg nudge rejected: ${err instanceof Error ? err.message : String(err)}`);
39384
+ });
39385
+ },
38853
39386
  onBgEventsNudgeRef: (ref) => {
38854
39387
  handleSubcBgEventsNudge({
38855
39388
  ctx,
@@ -38874,8 +39407,16 @@ ${lines}
38874
39407
  });
38875
39408
  }
38876
39409
  pool.setConfigureOverride("harness", "pi");
38877
- pool.setConfigureOverride("aft_search_registered", resolvePiToolSurface(config2).semantic);
38878
- const ctx = { pool, config: config2, storageDir };
39410
+ const surface = resolvePiToolSurface(config2);
39411
+ const hashlineEditRegistered = config2.edit_mode === "hashline" && surface.hoistEdit;
39412
+ pool.setConfigureOverride("edit_slot_survives", hashlineEditRegistered);
39413
+ pool.setConfigureOverride("aft_search_registered", surface.semantic);
39414
+ const ctx = {
39415
+ pool,
39416
+ config: config2,
39417
+ hashlineEffective: hashlineEditRegistered,
39418
+ storageDir
39419
+ };
38879
39420
  if (onnxRuntimePromise) {
38880
39421
  onnxRuntimePromise.then((ortDylibDir) => {
38881
39422
  if (ortDylibDir) {
@@ -38913,7 +39454,6 @@ ${lines}
38913
39454
  if (ANNOUNCEMENT_VERSION && ANNOUNCEMENT_FEATURES.length > 0) {
38914
39455
  sendFeatureAnnouncement(ANNOUNCEMENT_VERSION, ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER, storageDir);
38915
39456
  }
38916
- const surface = resolvePiToolSurface(config2);
38917
39457
  registerPiToolSurface(pi, ctx, surface);
38918
39458
  registerWorkflowHints(pi, config2, surface);
38919
39459
  registerStatusCommand(pi, ctx);