@cortexkit/aft 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.
Files changed (2) hide show
  1. package/dist/index.js +515 -91
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -695,7 +695,7 @@ function coerceConfigureDroppedKeys(value) {
695
695
  function isBridgeTransportTimeout(err) {
696
696
  return err instanceof Error && err.code === "transport_timeout";
697
697
  }
698
- var DEFAULT_BRIDGE_TIMEOUT_MS = 30000, BRIDGE_HANG_TIMEOUT_THRESHOLD = 2, MAX_STDOUT_BUFFER, STDOUT_BUFFER_COMPACT_THRESHOLD, TERMINAL_BASH_STATUSES, binaryFingerprintReader, BENIGN_CPUINFO_PROC_CPUINFO_PARSE_FAILURE = "failed to parse processor information from /proc/cpuinfo", BridgeReplacedDuringVersionCheck, BridgeTransportTimeoutError, BinaryBridge;
698
+ var DEFAULT_BRIDGE_TIMEOUT_MS = 30000, BRIDGE_HANG_TIMEOUT_THRESHOLD = 2, MAX_STDOUT_BUFFER, STDOUT_BUFFER_COMPACT_THRESHOLD, HASHLINE_REGISTRATION_LOG_INTERVAL_MS = 60000, HASHLINE_REGISTRATION_LOG_STATE_LIMIT = 256, TERMINAL_BASH_STATUSES, binaryFingerprintReader, BENIGN_CPUINFO_PROC_CPUINFO_PARSE_FAILURE = "failed to parse processor information from /proc/cpuinfo", BridgeReplacedDuringVersionCheck, BridgeTransportTimeoutError, BinaryBridge;
699
699
  var init_bridge = __esm(() => {
700
700
  init_active_logger();
701
701
  init_command_timeouts();
@@ -753,6 +753,9 @@ var init_bridge = __esm(() => {
753
753
  configured = false;
754
754
  _configurePromise = null;
755
755
  configOverrides;
756
+ editSlotSurvives;
757
+ editSlotSurvivesCaptured = false;
758
+ hashlineRegistrationLogState = new Map;
756
759
  minVersion;
757
760
  onVersionMismatch;
758
761
  onConfigureWarnings;
@@ -769,20 +772,33 @@ var init_bridge = __esm(() => {
769
772
  errorPrefix;
770
773
  logger;
771
774
  childEnv;
772
- constructor(binaryPath, cwd, options, configOverrides) {
775
+ constructor(binaryPath, cwd, options, configOverrides, editSlotSurvives) {
773
776
  this.binaryPath = binaryPath;
774
777
  this.cwd = cwd;
775
778
  this.timeoutMs = options?.timeoutMs ?? DEFAULT_BRIDGE_TIMEOUT_MS;
776
779
  this.hangThreshold = options?.hangThreshold ?? BRIDGE_HANG_TIMEOUT_THRESHOLD;
777
780
  this.maxRestarts = options?.maxRestarts ?? 3;
778
- this.configOverrides = configOverrides ?? {};
781
+ this.errorPrefix = options?.errorPrefix ?? "[aft-bridge]";
782
+ this.configOverrides = { ...configOverrides ?? {} };
783
+ const legacyEditSlotSurvives = this.configOverrides.edit_slot_survives;
784
+ delete this.configOverrides.edit_slot_survives;
785
+ if (legacyEditSlotSurvives !== undefined && typeof legacyEditSlotSurvives !== "boolean") {
786
+ throw new Error(`${this.errorPrefix} edit_slot_survives must be a boolean`);
787
+ }
788
+ if (editSlotSurvives !== undefined && legacyEditSlotSurvives !== undefined && editSlotSurvives !== legacyEditSlotSurvives) {
789
+ throw new Error(`${this.errorPrefix} conflicting edit_slot_survives construction values`);
790
+ }
791
+ const capturedEditSlotSurvives = editSlotSurvives ?? legacyEditSlotSurvives;
792
+ if (typeof capturedEditSlotSurvives === "boolean") {
793
+ this.editSlotSurvives = capturedEditSlotSurvives;
794
+ this.editSlotSurvivesCaptured = true;
795
+ }
779
796
  this.minVersion = options?.minVersion;
780
797
  this.onVersionMismatch = options?.onVersionMismatch;
781
798
  this.onConfigureWarnings = options?.onConfigureWarnings;
782
799
  this.onBashCompletion = options?.onBashCompletion;
783
800
  this.onBashLongRunning = options?.onBashLongRunning;
784
801
  this.onBashPatternMatch = options?.onBashPatternMatch;
785
- this.errorPrefix = options?.errorPrefix ?? "[aft-bridge]";
786
802
  this.logger = options?.logger;
787
803
  this.childEnv = options?.childEnv;
788
804
  }
@@ -893,13 +909,54 @@ var init_bridge = __esm(() => {
893
909
  cacheStatusSnapshot(snapshot) {
894
910
  this.cachedStatus = snapshot;
895
911
  }
912
+ setEditSlotSurvives(value) {
913
+ if (this.editSlotSurvivesCaptured) {
914
+ throw new Error(`${this.errorPrefix} edit_slot_survives is write-once and was already captured`);
915
+ }
916
+ this.editSlotSurvives = value;
917
+ this.editSlotSurvivesCaptured = true;
918
+ }
919
+ logHashlineRegistrationCarrier(phase, sessionId, editSlotSurvives) {
920
+ const session = sessionId && sessionId.length > 0 ? sessionId : "__default__";
921
+ const key = `${phase}\x00${session}\x00${String(editSlotSurvives)}`;
922
+ const now = Date.now();
923
+ const state = this.hashlineRegistrationLogState.get(key);
924
+ if (state && now - state.lastEmittedAt < HASHLINE_REGISTRATION_LOG_INTERVAL_MS) {
925
+ state.suppressed += 1;
926
+ return;
927
+ }
928
+ const repeated = state && state.suppressed > 0 ? ` repeated=${state.suppressed + 1}` : "";
929
+ if (!state && this.hashlineRegistrationLogState.size >= HASHLINE_REGISTRATION_LOG_STATE_LIMIT) {
930
+ const oldest = this.hashlineRegistrationLogState.keys().next().value;
931
+ if (oldest !== undefined)
932
+ this.hashlineRegistrationLogState.delete(oldest);
933
+ }
934
+ this.hashlineRegistrationLogState.set(key, { lastEmittedAt: now, suppressed: 0 });
935
+ this.sessionLogVia(sessionId, `hashline registration carrier transport=ndjson phase=${phase} edit_slot_survives=${editSlotSurvives}${repeated}`);
936
+ }
896
937
  async send(command, params = {}, options) {
897
- return this.sendWithVersionMismatchRetry(command, params, options, true);
938
+ let dispatchParams = params;
939
+ if (command === "configure") {
940
+ dispatchParams = { ...params };
941
+ delete dispatchParams.edit_slot_survives;
942
+ const editSlotSurvives = this.editSlotSurvives;
943
+ if (this.editSlotSurvivesCaptured && typeof editSlotSurvives === "boolean") {
944
+ dispatchParams.edit_slot_survives = editSlotSurvives;
945
+ const sessionId = typeof dispatchParams.session_id === "string" ? dispatchParams.session_id : undefined;
946
+ this.logHashlineRegistrationCarrier("configure", sessionId, editSlotSurvives);
947
+ }
948
+ }
949
+ return this.sendWithVersionMismatchRetry(command, dispatchParams, options, true);
898
950
  }
899
951
  async toolCall(sessionId, name, rawArgs = {}, options) {
900
952
  const params = { name, arguments: rawArgs };
901
953
  if (sessionId)
902
954
  params.session_id = sessionId;
955
+ const editSlotSurvives = this.editSlotSurvives;
956
+ if (this.editSlotSurvivesCaptured && typeof editSlotSurvives === "boolean") {
957
+ params.edit_slot_survives = editSlotSurvives;
958
+ this.logHashlineRegistrationCarrier("tool_call", sessionId, editSlotSurvives);
959
+ }
903
960
  const { preview, ...sendOptions } = options ?? {};
904
961
  if (preview === true)
905
962
  params.preview = true;
@@ -2019,7 +2076,8 @@ var init_platform = __esm(() => {
2019
2076
  // ../aft-bridge/dist/downloader.js
2020
2077
  import { spawnSync } from "node:child_process";
2021
2078
  import { createHash as createHash2, randomUUID } from "node:crypto";
2022
- import { chmodSync, closeSync, copyFileSync, createWriteStream, existsSync as existsSync2, mkdirSync, openSync, readFileSync as readFileSync3, renameSync, rmSync, statSync as statSync2, unlinkSync, writeSync } from "node:fs";
2079
+ import { chmodSync, closeSync, copyFileSync, createWriteStream, existsSync as existsSync2, mkdirSync, openSync, readdirSync, readFileSync as readFileSync3, renameSync, rmSync, statSync as statSync2, unlinkSync, writeSync } from "node:fs";
2080
+ import { hostname } from "node:os";
2023
2081
  import { join as join4 } from "node:path";
2024
2082
  import { Readable } from "node:stream";
2025
2083
  import { pipeline } from "node:stream/promises";
@@ -2090,11 +2148,37 @@ async function downloadBinary(version) {
2090
2148
  let binaryTimeout = null;
2091
2149
  let checksumTimeout = null;
2092
2150
  const tmpPath = `${binaryPath}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`;
2151
+ const cleanUpPartialDownload = () => {
2152
+ try {
2153
+ if (existsSync2(tmpPath))
2154
+ unlinkSync(tmpPath);
2155
+ } catch {}
2156
+ };
2157
+ let interrupted = false;
2158
+ const cleanUpInterruptedDownload = () => {
2159
+ if (interrupted)
2160
+ return;
2161
+ interrupted = true;
2162
+ binaryController?.abort();
2163
+ checksumController?.abort();
2164
+ cleanUpPartialDownload();
2165
+ releaseLock?.();
2166
+ releaseLock = null;
2167
+ };
2168
+ const handleSigint = () => {
2169
+ cleanUpInterruptedDownload();
2170
+ process.off("SIGINT", handleSigint);
2171
+ process.kill(process.pid, "SIGINT");
2172
+ };
2173
+ const handleExit = () => cleanUpInterruptedDownload();
2174
+ process.once("SIGINT", handleSigint);
2175
+ process.once("exit", handleExit);
2093
2176
  try {
2094
2177
  if (!existsSync2(versionedCacheDir)) {
2095
2178
  mkdirSync(versionedCacheDir, { recursive: true });
2096
2179
  }
2097
2180
  releaseLock = await acquireDownloadLock(lockPath);
2181
+ sweepStaleDownloadTemps(versionedCacheDir, binaryName);
2098
2182
  if (existsSync2(binaryPath) && isExpectedCachedBinary(binaryPath, tag)) {
2099
2183
  return binaryPath;
2100
2184
  }
@@ -2170,13 +2254,11 @@ async function downloadBinary(version) {
2170
2254
  } catch (err) {
2171
2255
  const msg = err instanceof Error ? err.message : String(err);
2172
2256
  error(`Failed to download AFT binary: ${msg}`);
2173
- if (existsSync2(tmpPath)) {
2174
- try {
2175
- unlinkSync(tmpPath);
2176
- } catch {}
2177
- }
2257
+ cleanUpPartialDownload();
2178
2258
  return null;
2179
2259
  } finally {
2260
+ process.off("SIGINT", handleSigint);
2261
+ process.off("exit", handleExit);
2180
2262
  if (binaryTimeout) {
2181
2263
  binaryController?.abort();
2182
2264
  clearTimeout(binaryTimeout);
@@ -2202,17 +2284,85 @@ async function ensureBinary(version) {
2202
2284
  log("No cached binary found, downloading latest...");
2203
2285
  return downloadBinary();
2204
2286
  }
2205
- async function acquireDownloadLock(lockPath) {
2287
+ function createDownloadLockOwner() {
2288
+ return JSON.stringify({
2289
+ pid: process.pid,
2290
+ hostname: hostname(),
2291
+ createdAt: Date.now(),
2292
+ token: randomUUID()
2293
+ });
2294
+ }
2295
+ function parseDownloadLockOwner(raw) {
2296
+ try {
2297
+ const parsed = JSON.parse(raw);
2298
+ if (typeof parsed === "object" && parsed !== null) {
2299
+ const { pid, hostname: ownerHostname } = parsed;
2300
+ return {
2301
+ pid: typeof pid === "number" && Number.isSafeInteger(pid) && pid > 0 ? pid : null,
2302
+ hostname: typeof ownerHostname === "string" && ownerHostname ? ownerHostname : null
2303
+ };
2304
+ }
2305
+ } catch {}
2306
+ const legacyPid = Number(raw.split(":", 1)[0]);
2307
+ return {
2308
+ pid: Number.isSafeInteger(legacyPid) && legacyPid > 0 ? legacyPid : null,
2309
+ hostname: null
2310
+ };
2311
+ }
2312
+ function isProcessAlive(pid) {
2313
+ try {
2314
+ process.kill(pid, 0);
2315
+ return true;
2316
+ } catch (err) {
2317
+ return err.code !== "ESRCH";
2318
+ }
2319
+ }
2320
+ function isReclaimableDownloadLock(owner, ageMs, staleMs) {
2321
+ if (Math.abs(ageMs) > staleMs)
2322
+ return true;
2323
+ return (owner.hostname === null || owner.hostname === hostname()) && owner.pid !== null && !isProcessAlive(owner.pid);
2324
+ }
2325
+ function reclaimDownloadLock(lockPath, expectedOwner) {
2326
+ try {
2327
+ if (readFileSync3(lockPath, "utf-8") !== expectedOwner)
2328
+ return false;
2329
+ rmSync(lockPath, { force: true });
2330
+ return true;
2331
+ } catch (err) {
2332
+ if (err.code === "ENOENT")
2333
+ return true;
2334
+ throw err;
2335
+ }
2336
+ }
2337
+ function sweepStaleDownloadTemps(versionedCacheDir, binaryName) {
2338
+ const tempPrefix = `${binaryName}.`;
2339
+ try {
2340
+ for (const entry of readdirSync(versionedCacheDir, { withFileTypes: true })) {
2341
+ if (!entry.isFile() || !entry.name.startsWith(tempPrefix) || !entry.name.endsWith(".tmp")) {
2342
+ continue;
2343
+ }
2344
+ const tempPath = join4(versionedCacheDir, entry.name);
2345
+ const ageMs = Date.now() - statSync2(tempPath).mtimeMs;
2346
+ if (Math.abs(ageMs) > DOWNLOAD_LOCK_STALE_MS)
2347
+ unlinkSync(tempPath);
2348
+ }
2349
+ } catch {}
2350
+ }
2351
+ async function acquireDownloadLock(lockPath, timing = {}) {
2352
+ const timeoutMs = timing.timeoutMs ?? DOWNLOAD_LOCK_TIMEOUT_MS;
2353
+ const staleMs = timing.staleMs ?? DOWNLOAD_LOCK_STALE_MS;
2354
+ const pollIntervalMs = timing.pollIntervalMs ?? 100;
2206
2355
  const startedAt = Date.now();
2207
2356
  while (true) {
2208
2357
  try {
2209
- const owner = `${process.pid}:${Date.now()}:${randomUUID()}`;
2358
+ const owner = createDownloadLockOwner();
2210
2359
  const fd = openSync(lockPath, "wx");
2211
- writeSync(fd, owner);
2360
+ try {
2361
+ writeSync(fd, owner);
2362
+ } finally {
2363
+ closeSync(fd);
2364
+ }
2212
2365
  return () => {
2213
- try {
2214
- closeSync(fd);
2215
- } catch {}
2216
2366
  try {
2217
2367
  if (readFileSync3(lockPath, "utf-8") === owner) {
2218
2368
  rmSync(lockPath, { force: true });
@@ -2223,19 +2373,24 @@ async function acquireDownloadLock(lockPath) {
2223
2373
  const code = err.code;
2224
2374
  if (code !== "EEXIST")
2225
2375
  throw err;
2376
+ let existingOwner;
2377
+ let ageMs;
2226
2378
  try {
2227
- const ageMs = Date.now() - statSync2(lockPath).mtimeMs;
2228
- if (ageMs > DOWNLOAD_LOCK_STALE_MS) {
2229
- rmSync(lockPath, { force: true });
2379
+ existingOwner = readFileSync3(lockPath, "utf-8");
2380
+ ageMs = Date.now() - statSync2(lockPath).mtimeMs;
2381
+ } catch (readErr) {
2382
+ if (readErr.code === "ENOENT")
2383
+ continue;
2384
+ throw readErr;
2385
+ }
2386
+ if (isReclaimableDownloadLock(parseDownloadLockOwner(existingOwner), ageMs, staleMs)) {
2387
+ if (reclaimDownloadLock(lockPath, existingOwner))
2230
2388
  continue;
2231
- }
2232
- } catch {
2233
- continue;
2234
2389
  }
2235
- if (Date.now() - startedAt > DOWNLOAD_LOCK_TIMEOUT_MS) {
2390
+ if (Date.now() - startedAt > timeoutMs) {
2236
2391
  throw new Error(`Timed out waiting for download lock: ${lockPath}`);
2237
2392
  }
2238
- await new Promise((resolve3) => setTimeout(resolve3, 100));
2393
+ await new Promise((resolve3) => setTimeout(resolve3, pollIntervalMs));
2239
2394
  }
2240
2395
  }
2241
2396
  }
@@ -2270,7 +2425,7 @@ async function fetchLatestTag() {
2270
2425
  clearTimeout(timeout);
2271
2426
  }
2272
2427
  }
2273
- var REPO = "cortexkit/aft", DOWNLOAD_TIMEOUT_MS = 300000, LATEST_TAG_TIMEOUT_MS = 30000, MAX_DOWNLOAD_BYTES, DOWNLOAD_LOCK_TIMEOUT_MS = 120000, DOWNLOAD_LOCK_STALE_MS;
2428
+ var REPO = "cortexkit/aft", DOWNLOAD_TIMEOUT_MS = 300000, LATEST_TAG_TIMEOUT_MS = 30000, MAX_DOWNLOAD_BYTES, DOWNLOAD_LOCK_STALE_MS, DOWNLOAD_LOCK_TIMEOUT_MS;
2274
2429
  var init_downloader = __esm(() => {
2275
2430
  init_active_logger();
2276
2431
  init_cache_paths();
@@ -2278,6 +2433,7 @@ var init_downloader = __esm(() => {
2278
2433
  init_cache_paths();
2279
2434
  MAX_DOWNLOAD_BYTES = 200 * 1024 * 1024;
2280
2435
  DOWNLOAD_LOCK_STALE_MS = 10 * 60000;
2436
+ DOWNLOAD_LOCK_TIMEOUT_MS = DOWNLOAD_LOCK_STALE_MS + 30000;
2281
2437
  });
2282
2438
 
2283
2439
  // ../aft-bridge/dist/durable-log.js
@@ -5112,12 +5268,15 @@ class BgSubscription {
5112
5268
  canAttach;
5113
5269
  onRootAttachFailure;
5114
5270
  onDormant;
5271
+ dispatchProbeIntervalMs;
5115
5272
  nudgeRef;
5116
5273
  isCurrent;
5117
5274
  stopped = false;
5118
5275
  current = null;
5119
5276
  loop;
5120
- constructor(identity, acquireClient, dropClient, consumerIdentity, onNudge, sleep2, canAttach, onRootAttachFailure, onDormant, nudgeRef, isCurrent = () => true) {
5277
+ lifecycleLogState = new Map;
5278
+ nudgeReceiptLogState = null;
5279
+ constructor(identity, acquireClient, dropClient, consumerIdentity, onNudge, sleep2, canAttach, onRootAttachFailure, onDormant, dispatchProbeIntervalMs, nudgeRef, isCurrent = () => true) {
5121
5280
  this.identity = identity;
5122
5281
  this.acquireClient = acquireClient;
5123
5282
  this.dropClient = dropClient;
@@ -5127,6 +5286,7 @@ class BgSubscription {
5127
5286
  this.canAttach = canAttach;
5128
5287
  this.onRootAttachFailure = onRootAttachFailure;
5129
5288
  this.onDormant = onDormant;
5289
+ this.dispatchProbeIntervalMs = dispatchProbeIntervalMs;
5130
5290
  this.nudgeRef = nudgeRef;
5131
5291
  this.isCurrent = isCurrent;
5132
5292
  this.loop = this.run();
@@ -5143,25 +5303,106 @@ class BgSubscription {
5143
5303
  return;
5144
5304
  });
5145
5305
  }
5306
+ info(kind, message) {
5307
+ const now = Date.now();
5308
+ const state = this.lifecycleLogState.get(kind);
5309
+ if (state && now - state.lastEmittedAt < BG_LIFECYCLE_LOG_INTERVAL_MS) {
5310
+ state.suppressed += 1;
5311
+ return;
5312
+ }
5313
+ const suppressed = state?.suppressed ?? 0;
5314
+ this.lifecycleLogState.set(kind, { lastEmittedAt: now, suppressed: 0 });
5315
+ const suffix = suppressed > 0 ? ` suppressed=${suppressed}` : "";
5316
+ log(`subc bg_events: ${message}${suffix}`, { sessionId: this.identity.session });
5317
+ }
5318
+ routeId(route) {
5319
+ return `${route.channel}@${route.epoch}`;
5320
+ }
5321
+ recordNudgeReceipt(routeId) {
5322
+ const now = Date.now();
5323
+ const state = this.nudgeReceiptLogState;
5324
+ if (state && now - state.lastEmittedAt < BG_LIFECYCLE_LOG_INTERVAL_MS) {
5325
+ state.count += 1;
5326
+ return;
5327
+ }
5328
+ const count = (state?.count ?? 0) + 1;
5329
+ this.nudgeReceiptLogState = { lastEmittedAt: now, count: 0 };
5330
+ log(`subc bg_events: nudge received channel=${routeId} count=${count}`, {
5331
+ sessionId: this.identity.session
5332
+ });
5333
+ }
5334
+ errorText(error2) {
5335
+ return error2 instanceof Error ? `${error2.name}: ${error2.message}` : String(error2);
5336
+ }
5337
+ startDispatchProbe(client, routeId) {
5338
+ const initial = client.droppedIngressFrames;
5339
+ if (typeof initial !== "number")
5340
+ return () => {
5341
+ return;
5342
+ };
5343
+ let previous = initial;
5344
+ const timer = setInterval(() => {
5345
+ const total = client.droppedIngressFrames;
5346
+ if (typeof total !== "number" || total <= previous)
5347
+ return;
5348
+ const delta = total - previous;
5349
+ previous = total;
5350
+ this.info("dispatch-epoch-drop", `client ingress epoch drops scope=client observed_while_channel=${routeId} delta=${delta} total=${total}`);
5351
+ }, this.dispatchProbeIntervalMs);
5352
+ timer.unref?.();
5353
+ return () => clearInterval(timer);
5354
+ }
5146
5355
  async run() {
5147
- let attempt = 0;
5356
+ let backoffAttempt = 0;
5357
+ let reconnectAttempt = 0;
5358
+ let reconnecting = false;
5359
+ const beginReconnect = () => {
5360
+ reconnecting = true;
5361
+ reconnectAttempt = reconnectAttempt === 0 ? 1 : reconnectAttempt + 1;
5362
+ };
5363
+ const giveUp = (reason) => {
5364
+ this.info("reconnect-gave-up", `reconnect gave-up attempt=${reconnectAttempt} reason=${reason}`);
5365
+ };
5148
5366
  while (!this.stopped) {
5149
- if (!this.isCurrent())
5367
+ if (!this.isCurrent()) {
5368
+ if (reconnecting)
5369
+ giveUp("stale-session");
5150
5370
  return;
5371
+ }
5151
5372
  if (!this.canAttach()) {
5373
+ if (reconnecting)
5374
+ giveUp("root-dormant");
5152
5375
  this.onDormant();
5153
5376
  return;
5154
5377
  }
5378
+ if (reconnecting) {
5379
+ this.info("reconnect-attempt", `reconnect attempt=${reconnectAttempt}`);
5380
+ }
5155
5381
  let client;
5156
5382
  try {
5157
5383
  client = await this.acquireClient();
5158
- } catch {
5159
- await this.backoff(attempt++);
5384
+ } catch (err) {
5385
+ if (!reconnecting)
5386
+ beginReconnect();
5387
+ this.info("reconnect-error", `reconnect error attempt=${reconnectAttempt} error=${this.errorText(err)}`);
5388
+ await this.backoff(backoffAttempt++);
5389
+ if (reconnecting)
5390
+ reconnectAttempt += 1;
5160
5391
  continue;
5161
5392
  }
5162
- if (this.stopped || !this.isCurrent())
5393
+ if (this.stopped) {
5394
+ if (reconnecting)
5395
+ giveUp("stopped");
5163
5396
  return;
5397
+ }
5398
+ if (!this.isCurrent()) {
5399
+ if (reconnecting)
5400
+ giveUp("stale-session");
5401
+ return;
5402
+ }
5164
5403
  if (!this.canAttach()) {
5404
+ if (reconnecting)
5405
+ giveUp("root-dormant");
5165
5406
  this.onDormant();
5166
5407
  return;
5167
5408
  }
@@ -5170,44 +5411,84 @@ class BgSubscription {
5170
5411
  route = await client.routeOpen({ kind: "tool_provider", module_id: AFT_MODULE_ID }, this.identity, { consumerIdentity: this.consumerIdentity });
5171
5412
  } catch (err) {
5172
5413
  if (this.isCurrent() && this.onRootAttachFailure(err)) {
5414
+ if (reconnecting)
5415
+ giveUp("root-dormant");
5173
5416
  this.onDormant();
5174
5417
  return;
5175
5418
  }
5176
5419
  if (isConsumerReconnectTransient(err))
5177
5420
  this.dropClient(client);
5178
- await this.backoff(attempt++);
5421
+ if (!reconnecting)
5422
+ beginReconnect();
5423
+ this.info("reconnect-error", `reconnect error attempt=${reconnectAttempt} error=${this.errorText(err)}`);
5424
+ await this.backoff(backoffAttempt++);
5425
+ if (reconnecting)
5426
+ reconnectAttempt += 1;
5179
5427
  continue;
5180
5428
  }
5181
5429
  if (this.stopped || !this.isCurrent()) {
5182
5430
  safeCloseRoute(client, route);
5431
+ if (reconnecting)
5432
+ giveUp(this.stopped ? "stopped" : "stale-session");
5183
5433
  return;
5184
5434
  }
5185
5435
  const subscribedAt = Date.now();
5436
+ const routeId = this.routeId(route);
5437
+ let stopDispatchProbe = () => {
5438
+ return;
5439
+ };
5186
5440
  try {
5187
5441
  const sub = client.subscribe(route, { op: "bg_events" }, () => {
5188
- if (!this.stopped && this.isCurrent())
5189
- this.onNudge();
5442
+ if (this.stopped) {
5443
+ this.info("nudge-drop-stopped", `nudge dropped cause=subscription-stopped channel=${routeId}`);
5444
+ return;
5445
+ }
5446
+ this.recordNudgeReceipt(routeId);
5447
+ if (!this.isCurrent()) {
5448
+ this.info("nudge-stale-carrier", `nudge carried by stale subscription; checking current session channel=${routeId}`);
5449
+ }
5450
+ this.onNudge();
5190
5451
  });
5191
5452
  this.current = sub;
5453
+ stopDispatchProbe = this.startDispatchProbe(client, routeId);
5454
+ this.info("subscription-open", `subscription open channel=${routeId}`);
5455
+ if (reconnecting) {
5456
+ this.info("reconnect-success", `reconnect success attempt=${reconnectAttempt} channel=${routeId}`);
5457
+ reconnecting = false;
5458
+ reconnectAttempt = 0;
5459
+ }
5192
5460
  if (this.stopped)
5193
5461
  sub.unsubscribe();
5194
5462
  if (!this.stopped && this.isCurrent())
5195
5463
  this.onNudge();
5196
5464
  await sub.closed;
5197
- return;
5465
+ this.info("stream-end", `stream ended channel=${routeId}`);
5466
+ if (this.stopped) {
5467
+ giveUp("stopped");
5468
+ return;
5469
+ }
5470
+ beginReconnect();
5198
5471
  } catch (err) {
5199
- if (this.stopped)
5472
+ const routeId2 = this.routeId(route);
5473
+ this.info("stream-error", `stream error channel=${routeId2} error=${this.errorText(err)}`);
5474
+ if (this.stopped) {
5475
+ giveUp("stopped");
5200
5476
  return;
5477
+ }
5201
5478
  if (isConsumerReconnectTransient(err))
5202
5479
  this.dropClient(client);
5203
5480
  if (Date.now() - subscribedAt >= BG_STABLE_MS)
5204
- attempt = 0;
5481
+ backoffAttempt = 0;
5482
+ beginReconnect();
5205
5483
  } finally {
5484
+ stopDispatchProbe();
5206
5485
  this.current = null;
5207
5486
  safeCloseRoute(client, route);
5208
5487
  }
5209
- await this.backoff(attempt++);
5488
+ await this.backoff(backoffAttempt++);
5210
5489
  }
5490
+ if (reconnecting)
5491
+ giveUp("stopped");
5211
5492
  }
5212
5493
  async backoff(attempt) {
5213
5494
  const ms = Math.min(100 * 2 ** Math.min(attempt, 6), 2000);
@@ -5302,6 +5583,9 @@ class SubcTransport {
5302
5583
  this.assertCurrent();
5303
5584
  const { preview, timeoutMs, onProgress } = this.splitOptions(options);
5304
5585
  const body = { name, arguments: rawArgs };
5586
+ const editSlotSurvives = this.pool.getEditSlotSurvives();
5587
+ if (editSlotSurvives !== undefined)
5588
+ body.edit_slot_survives = editSlotSurvives;
5305
5589
  if (preview === true)
5306
5590
  body.preview = true;
5307
5591
  const reply = await this.pool.routeRequest(this.identityFor(sessionId), body, timeoutMs, onProgress, this.generation);
@@ -5316,7 +5600,11 @@ class SubcTransport {
5316
5600
  }
5317
5601
  const { timeoutMs, onProgress } = this.splitOptions(options);
5318
5602
  const session = typeof params.session_id === "string" ? params.session_id : undefined;
5319
- const reply = await this.pool.routeRequest(this.identityFor(session), { name: command, arguments: params }, timeoutMs, onProgress, this.generation);
5603
+ const body = { name: command, arguments: params };
5604
+ const editSlotSurvives = this.pool.getEditSlotSurvives();
5605
+ if (editSlotSurvives !== undefined)
5606
+ body.edit_slot_survives = editSlotSurvives;
5607
+ const reply = await this.pool.routeRequest(this.identityFor(session), body, timeoutMs, onProgress, this.generation);
5320
5608
  const response = reliftReply(reply);
5321
5609
  this.captureStatusBar(response);
5322
5610
  return response;
@@ -5340,6 +5628,7 @@ class SubcTransportPool {
5340
5628
  onBgEventsNudge;
5341
5629
  onBgEventsNudgeRef;
5342
5630
  bgBackoffSleep;
5631
+ bgDispatchProbeIntervalMs;
5343
5632
  lifecycleDemandCheck;
5344
5633
  onLifecycleEvent;
5345
5634
  onBgNudgeRejected;
@@ -5355,8 +5644,11 @@ class SubcTransportPool {
5355
5644
  transportFailures = 0;
5356
5645
  transports = new Map;
5357
5646
  generationRejections = new Set;
5647
+ nudgeDeliveryLogState = new Map;
5358
5648
  pendingRootCleanups = new Set;
5359
5649
  shuttingDown = false;
5650
+ editSlotSurvives;
5651
+ editSlotSurvivesCaptured = false;
5360
5652
  constructor(options) {
5361
5653
  this.connectionFile = options.connectionFile;
5362
5654
  this.harness = options.harness;
@@ -5366,6 +5658,7 @@ class SubcTransportPool {
5366
5658
  this.onBgEventsNudge = options.onBgEventsNudge;
5367
5659
  this.onBgEventsNudgeRef = options.onBgEventsNudgeRef;
5368
5660
  this.bgBackoffSleep = options.bgBackoffSleep ?? ((ms) => new Promise((resolve4) => setTimeout(resolve4, ms)));
5661
+ this.bgDispatchProbeIntervalMs = options.bgDispatchProbeIntervalMs ?? BG_DISPATCH_PROBE_INTERVAL_MS;
5369
5662
  const lifecycle = options.lifecycle;
5370
5663
  const demandCheck = options.lifecycleDemandCheck ?? options.demandCheck ?? lifecycle?.demandCheck;
5371
5664
  this.lifecycleDemandCheck = demandCheck;
@@ -5682,6 +5975,35 @@ class SubcTransportPool {
5682
5975
  isCurrentSession(key, record) {
5683
5976
  return this.sessions.get(key) === record && !record.closed;
5684
5977
  }
5978
+ currentSessionForNudge(identity) {
5979
+ const current = this.sessions.get(identityKey(identity));
5980
+ return current && !current.closed ? current : null;
5981
+ }
5982
+ nudgeRefFor(record) {
5983
+ const poolId = this.currentPoolId();
5984
+ const generation = record.generation;
5985
+ if (poolId === undefined || generation === undefined)
5986
+ return;
5987
+ return {
5988
+ canonicalRoot: record.canonicalRoot,
5989
+ session: record.identity.session,
5990
+ concretePoolId: poolId,
5991
+ generation
5992
+ };
5993
+ }
5994
+ logNudgeDelivery(kind, record, message) {
5995
+ const key = `${kind}\x00${record.identityKey}`;
5996
+ const now = Date.now();
5997
+ const state = this.nudgeDeliveryLogState.get(key);
5998
+ if (state && now - state.lastEmittedAt < BG_LIFECYCLE_LOG_INTERVAL_MS) {
5999
+ state.suppressed += 1;
6000
+ return;
6001
+ }
6002
+ const suppressed = state?.suppressed ?? 0;
6003
+ this.nudgeDeliveryLogState.set(key, { lastEmittedAt: now, suppressed: 0 });
6004
+ const suffix = suppressed > 0 ? ` suppressed=${suppressed}` : "";
6005
+ log(`subc bg_events: ${message}${suffix}`, { sessionId: record.identity.session });
6006
+ }
5685
6007
  removeIndexMembership(record) {
5686
6008
  const keys = this.rootIndex.get(record.canonicalRoot);
5687
6009
  if (!keys)
@@ -5935,20 +6257,32 @@ class SubcTransportPool {
5935
6257
  return;
5936
6258
  if (record.bgSub)
5937
6259
  return;
5938
- const poolId = this.currentPoolId();
5939
- const generation = record.generation;
5940
- const nudgeRef = poolId !== undefined && generation !== undefined ? {
5941
- canonicalRoot: record.canonicalRoot,
5942
- session: identity.session,
5943
- concretePoolId: poolId,
5944
- generation
5945
- } : undefined;
6260
+ const nudgeRef = this.nudgeRefFor(record);
5946
6261
  const onNudge = () => {
5947
- if (!this.isCurrentSession(record.identityKey, record))
6262
+ const currentRecord = this.currentSessionForNudge(identity);
6263
+ if (!currentRecord) {
6264
+ this.logNudgeDelivery("drop-no-current-session", record, `nudge dropped cause=no-current-session root=${record.canonicalRoot}`);
5948
6265
  return;
5949
- this.onBgEventsNudge?.(identity.project_root, identity.session);
5950
- if (nudgeRef)
5951
- this.onBgEventsNudgeRef?.(nudgeRef);
6266
+ }
6267
+ if (currentRecord !== record) {
6268
+ this.logNudgeDelivery("forward-superseded-carrier", currentRecord, `nudge forwarding cause=superseded-carrying-record root=${currentRecord.canonicalRoot}`);
6269
+ }
6270
+ const currentRef = this.nudgeRefFor(currentRecord);
6271
+ let delivered = false;
6272
+ if (currentRef && this.onBgEventsNudgeRef) {
6273
+ this.onBgEventsNudgeRef(currentRef);
6274
+ delivered = true;
6275
+ }
6276
+ if (this.onBgEventsNudge) {
6277
+ if (!currentRef && this.onBgEventsNudgeRef) {
6278
+ this.logNudgeDelivery("fallback-missing-generation", currentRecord, `nudge dispatch fallback=root-session-handler cause=generation-provenance-unavailable root=${currentRecord.canonicalRoot}`);
6279
+ }
6280
+ this.onBgEventsNudge(currentRecord.identity.project_root, currentRecord.identity.session);
6281
+ delivered = true;
6282
+ }
6283
+ if (delivered)
6284
+ return;
6285
+ this.logNudgeDelivery("drop-no-compatible-handler", currentRecord, `nudge dropped cause=generation-provenance-unavailable-and-root-session-handler-unwired root=${currentRecord.canonicalRoot}`);
5952
6286
  };
5953
6287
  let sub = null;
5954
6288
  const clearDormantSubscription = () => {
@@ -5960,7 +6294,7 @@ class SubcTransportPool {
5960
6294
  return false;
5961
6295
  this.markRootDormant(record.canonicalRoot, error2);
5962
6296
  return true;
5963
- }, clearDormantSubscription, nudgeRef, () => this.isCurrentSession(record.identityKey, record) && this.isCurrentLiveGeneration(record.canonicalRoot, record.generation));
6297
+ }, clearDormantSubscription, this.bgDispatchProbeIntervalMs, nudgeRef, () => this.isCurrentSession(record.identityKey, record) && this.isCurrentLiveGeneration(record.canonicalRoot, record.generation));
5964
6298
  record.bgSub = sub;
5965
6299
  if (!this.rootCanAttach(record.canonicalRoot)) {
5966
6300
  record.bgSub = null;
@@ -6022,7 +6356,21 @@ class SubcTransportPool {
6022
6356
  return Promise.resolve();
6023
6357
  return registry.requestProjectRootClose(registration.concretePoolId, root, generation, cause);
6024
6358
  }
6025
- setConfigureOverride(_key, _value) {}
6359
+ setConfigureOverride(key, value) {
6360
+ if (key !== "edit_slot_survives")
6361
+ return;
6362
+ if (typeof value !== "boolean") {
6363
+ throw new Error("edit_slot_survives must be set once to a boolean");
6364
+ }
6365
+ if (this.editSlotSurvivesCaptured) {
6366
+ throw new Error("edit_slot_survives is write-once and was already captured");
6367
+ }
6368
+ this.editSlotSurvives = value;
6369
+ this.editSlotSurvivesCaptured = true;
6370
+ }
6371
+ getEditSlotSurvives() {
6372
+ return this.editSlotSurvives;
6373
+ }
6026
6374
  async reconfigure(_projectRoot, _overrides) {}
6027
6375
  async replaceBinary(path2) {
6028
6376
  return path2;
@@ -6081,7 +6429,7 @@ function resolveBridgeForNudge(pool, ref) {
6081
6429
  currentConcretePoolId: candidate.getConcretePoolId?.()
6082
6430
  });
6083
6431
  }
6084
- var AFT_MODULE_ID = "aft", MAX_CONSECUTIVE_TRANSPORT_FAILURES = 3, BG_STABLE_MS = 5000, DEFAULT_SESSION_ID = "__default__", LOCALLY_SATISFIED_COMMANDS, SubcRootReapedError, SubcRootGenerationExpiredError, SubcRootDemandRequiredError, RouteTornDownError;
6432
+ var AFT_MODULE_ID = "aft", MAX_CONSECUTIVE_TRANSPORT_FAILURES = 3, BG_STABLE_MS = 5000, BG_LIFECYCLE_LOG_INTERVAL_MS = 60000, BG_DISPATCH_PROBE_INTERVAL_MS = 60000, DEFAULT_SESSION_ID = "__default__", LOCALLY_SATISFIED_COMMANDS, SubcRootReapedError, SubcRootGenerationExpiredError, SubcRootDemandRequiredError, RouteTornDownError;
6085
6433
  var init_subc_transport = __esm(() => {
6086
6434
  init_dist();
6087
6435
  init_active_logger();
@@ -6141,20 +6489,34 @@ function toolErrorFromResponse(command, response) {
6141
6489
  const message = typeof response.message === "string" && response.message.length > 0 ? response.message : `${command} failed`;
6142
6490
  return new AftToolError(message, code, response);
6143
6491
  }
6492
+ function isRouteGoodbyeError(error2) {
6493
+ if (!(error2 instanceof SubcError))
6494
+ return false;
6495
+ if (error2.code === undefined) {
6496
+ return error2.message.includes("route closed by subc");
6497
+ }
6498
+ return error2.code === "route_closed" && error2.message.includes("route closed by subc");
6499
+ }
6144
6500
  function isTransportClassError(error2) {
6145
6501
  return isBridgeTransportTimeout(error2) || isConsumerReconnectTransient(error2) || error2 instanceof StaleRouteHandleError || error2 instanceof SubcRootGenerationExpiredError || error2 instanceof SubcRootReapedError;
6146
6502
  }
6147
6503
  function adaptToolError(command, error2) {
6148
- if (command !== "bash" || !isTransportClassError(error2))
6149
- return error2;
6150
6504
  if (!(error2 instanceof Error))
6151
6505
  return error2;
6506
+ if (isRouteGoodbyeError(error2)) {
6507
+ if (error2.message.includes(SUBC_MODULE_RESTART_DISPOSITION))
6508
+ return error2;
6509
+ error2.message = error2.message ? `${error2.message} ${SUBC_MODULE_RESTART_DISPOSITION}` : SUBC_MODULE_RESTART_DISPOSITION;
6510
+ return error2;
6511
+ }
6512
+ if (command !== "bash" || !isTransportClassError(error2))
6513
+ return error2;
6152
6514
  if (error2.message.includes(BASH_TRANSPORT_DISPOSITION))
6153
6515
  return error2;
6154
6516
  error2.message = error2.message ? `${error2.message} ${BASH_TRANSPORT_DISPOSITION}` : BASH_TRANSPORT_DISPOSITION;
6155
6517
  return error2;
6156
6518
  }
6157
- var AftToolError, 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.";
6519
+ var AftToolError, 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.", 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.";
6158
6520
  var init_error_contract = __esm(() => {
6159
6521
  init_dist();
6160
6522
  init_bridge();
@@ -7028,7 +7390,7 @@ var init_migration = __esm(() => {
7028
7390
 
7029
7391
  // ../aft-bridge/dist/npm-resolver.js
7030
7392
  import { execFileSync } from "node:child_process";
7031
- import { readdirSync, statSync as statSync5 } from "node:fs";
7393
+ import { readdirSync as readdirSync2, statSync as statSync5 } from "node:fs";
7032
7394
  import { homedir as homedir9 } from "node:os";
7033
7395
  import { delimiter, dirname as dirname4, isAbsolute as isAbsolute3, join as join9 } from "node:path";
7034
7396
  function defaultDeps() {
@@ -7068,7 +7430,7 @@ function npmAdjacentToNode(deps) {
7068
7430
  function highestVersionedNodeBin(installsDir, name) {
7069
7431
  let entries;
7070
7432
  try {
7071
- entries = readdirSync(installsDir);
7433
+ entries = readdirSync2(installsDir);
7072
7434
  } catch {
7073
7435
  return null;
7074
7436
  }
@@ -7163,7 +7525,7 @@ var init_npm_resolver = () => {};
7163
7525
  // ../aft-bridge/dist/onnx-runtime.js
7164
7526
  import { execFileSync as execFileSync2 } from "node:child_process";
7165
7527
  import { createHash as createHash4 } from "node:crypto";
7166
- 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";
7528
+ 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";
7167
7529
  import { basename as basename2, dirname as dirname5, isAbsolute as isAbsolute4, join as join10, relative as relative2, resolve as resolve6, win32 } from "node:path";
7168
7530
  import { Readable as Readable2 } from "node:stream";
7169
7531
  import { pipeline as pipeline2 } from "node:stream/promises";
@@ -7240,7 +7602,7 @@ async function ensureOnnxRuntime(storageDir) {
7240
7602
  }
7241
7603
  function cleanupAbandonedStagingDirs(onnxBaseDir) {
7242
7604
  try {
7243
- const entries = readdirSync2(onnxBaseDir);
7605
+ const entries = readdirSync3(onnxBaseDir);
7244
7606
  for (const entry of entries) {
7245
7607
  if (!entry.startsWith(`${ORT_VERSION}.tmp.`))
7246
7608
  continue;
@@ -7251,7 +7613,7 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
7251
7613
  let abandoned = false;
7252
7614
  if (Number.isFinite(pid) && pid > 0) {
7253
7615
  if (process.platform === "win32") {
7254
- const ownerAlive = isProcessAlive(pid);
7616
+ const ownerAlive = isProcessAlive2(pid);
7255
7617
  if (!ownerAlive) {
7256
7618
  abandoned = true;
7257
7619
  } else {
@@ -7263,7 +7625,7 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
7263
7625
  }
7264
7626
  }
7265
7627
  } else {
7266
- abandoned = !isProcessAlive(pid);
7628
+ abandoned = !isProcessAlive2(pid);
7267
7629
  }
7268
7630
  } else {
7269
7631
  abandoned = true;
@@ -7315,7 +7677,7 @@ function isPathInsideRoot(root, candidate) {
7315
7677
  }
7316
7678
  function detectOnnxVersion(libDir, libName) {
7317
7679
  try {
7318
- const entries = readdirSync2(libDir);
7680
+ const entries = readdirSync3(libDir);
7319
7681
  const barePrefix = libName.replace(/\.(so|dylib|dll)$/, "");
7320
7682
  const expectedPrefix = process.platform === "win32" ? barePrefix.toLowerCase() : barePrefix;
7321
7683
  for (const entry of entries) {
@@ -7378,7 +7740,7 @@ function isWindowsSystem32Directory(dir) {
7378
7740
  }
7379
7741
  function directoryContainsLibrary(dir, libName) {
7380
7742
  try {
7381
- const entries = readdirSync2(dir);
7743
+ const entries = readdirSync3(dir);
7382
7744
  if (process.platform === "win32") {
7383
7745
  const expected = libName.toLowerCase();
7384
7746
  return entries.some((entry) => entry.toLowerCase() === expected);
@@ -7416,7 +7778,7 @@ function findSystemOnnxRuntime(libName) {
7416
7778
  if (!existsSync7(nugetPackageDir))
7417
7779
  return nugetPaths;
7418
7780
  try {
7419
- for (const entry of readdirSync2(nugetPackageDir, { withFileTypes: true })) {
7781
+ for (const entry of readdirSync3(nugetPackageDir, { withFileTypes: true })) {
7420
7782
  if (!entry.isDirectory())
7421
7783
  continue;
7422
7784
  if (entry.name === "__globalPackagesFolder" || entry.name.startsWith("."))
@@ -7511,7 +7873,7 @@ function validateExtractedTree(stagingRoot) {
7511
7873
  const realRoot = realpathSync2(stagingRoot);
7512
7874
  let totalBytes = 0;
7513
7875
  const walk = (dir) => {
7514
- const entries = readdirSync2(dir);
7876
+ const entries = readdirSync3(dir);
7515
7877
  for (const entry of entries) {
7516
7878
  const fullPath = join10(dir, entry);
7517
7879
  const lst = lstatSync(fullPath);
@@ -7569,7 +7931,7 @@ async function downloadOnnxRuntime(info, targetDir) {
7569
7931
  throw new Error(`Expected directory not found: ${extractedDir}`);
7570
7932
  }
7571
7933
  mkdirSync5(targetDir, { recursive: true });
7572
- const libFiles = readdirSync2(extractedDir).filter((f) => f.startsWith("libonnxruntime") || f.startsWith("onnxruntime"));
7934
+ const libFiles = readdirSync3(extractedDir).filter((f) => f.startsWith("libonnxruntime") || f.startsWith("onnxruntime"));
7573
7935
  const realFiles = [];
7574
7936
  const symlinks = [];
7575
7937
  for (const libFile of libFiles) {
@@ -7747,7 +8109,7 @@ ${new Date().toISOString()}
7747
8109
  }
7748
8110
  const age = Date.now() - lockMtimeMs;
7749
8111
  const ageWithinFresh = Math.abs(age) < STALE_LOCK_MS;
7750
- const ownerAlive = owningPid !== null && isProcessAlive(owningPid);
8112
+ const ownerAlive = owningPid !== null && isProcessAlive2(owningPid);
7751
8113
  if (ownerAlive && ageWithinFresh) {
7752
8114
  return false;
7753
8115
  }
@@ -7811,7 +8173,7 @@ function isWindowsProcessAlive(pid) {
7811
8173
  return false;
7812
8174
  }
7813
8175
  }
7814
- function isProcessAlive(pid) {
8176
+ function isProcessAlive2(pid) {
7815
8177
  if (process.platform === "win32")
7816
8178
  return isWindowsProcessAlive(pid);
7817
8179
  try {
@@ -8223,6 +8585,19 @@ function parseEditArray(value) {
8223
8585
  }
8224
8586
  return value;
8225
8587
  }
8588
+ function stripLineRangeSentinels(item) {
8589
+ const hasRangeField = ["startLine", "endLine", "content"].some((key) => hasOwn(item, key));
8590
+ if (!hasRangeField)
8591
+ return;
8592
+ if (item.oldString === "")
8593
+ delete item.oldString;
8594
+ if (item.newString === "")
8595
+ delete item.newString;
8596
+ if (item.replaceAll === false)
8597
+ delete item.replaceAll;
8598
+ if (item.occurrence === 1)
8599
+ delete item.occurrence;
8600
+ }
8226
8601
  function normalizeEditItem(value, index) {
8227
8602
  if (!value || typeof value !== "object" || Array.isArray(value)) {
8228
8603
  throw new InvalidRequestError(`edit: edits[${index}] must be an object`);
@@ -8231,6 +8606,7 @@ function normalizeEditItem(value, index) {
8231
8606
  const item = copyOwnProperties(source);
8232
8607
  normalizeItemAlias(item, "oldString", "oldText");
8233
8608
  normalizeItemAlias(item, "newString", "newText");
8609
+ stripLineRangeSentinels(item);
8234
8610
  const hasFindField = ["oldString", "newString", "replaceAll", "occurrence"].some((key) => hasOwn(item, key));
8235
8611
  const hasRangeField = ["startLine", "endLine", "content"].some((key) => hasOwn(item, key));
8236
8612
  if (hasFindField && hasRangeField) {
@@ -8404,6 +8780,8 @@ class BridgePool {
8404
8780
  idleTimeoutMs;
8405
8781
  bridgeOptions;
8406
8782
  configOverrides;
8783
+ editSlotSurvives;
8784
+ editSlotSurvivesCaptured = false;
8407
8785
  projectConfigLoader;
8408
8786
  logger;
8409
8787
  cleanupTimer = null;
@@ -8428,7 +8806,16 @@ class BridgePool {
8428
8806
  logger: options.logger,
8429
8807
  childEnv: options.childEnv
8430
8808
  };
8431
- this.configOverrides = configOverrides;
8809
+ this.configOverrides = { ...configOverrides };
8810
+ const initialEditSlotSurvives = this.configOverrides.edit_slot_survives;
8811
+ delete this.configOverrides.edit_slot_survives;
8812
+ if (initialEditSlotSurvives !== undefined) {
8813
+ if (typeof initialEditSlotSurvives !== "boolean") {
8814
+ throw new Error("edit_slot_survives must be a boolean");
8815
+ }
8816
+ this.editSlotSurvives = initialEditSlotSurvives;
8817
+ this.editSlotSurvivesCaptured = true;
8818
+ }
8432
8819
  this.startCleanupTimer();
8433
8820
  }
8434
8821
  getActiveBridgeForRoot(projectRoot) {
@@ -8467,7 +8854,8 @@ class BridgePool {
8467
8854
  }
8468
8855
  const projectOverrides = this.loadProjectOverrides(key);
8469
8856
  const mergedOverrides = { ...this.configOverrides, ...projectOverrides };
8470
- const bridge = new BinaryBridge(this.binaryPath, key, this.bridgeOptions, mergedOverrides);
8857
+ delete mergedOverrides.edit_slot_survives;
8858
+ const bridge = new BinaryBridge(this.binaryPath, key, this.bridgeOptions, mergedOverrides, this.editSlotSurvivesCaptured ? this.editSlotSurvives : undefined);
8471
8859
  this.bridges.set(key, { bridge, lastUsed: Date.now() });
8472
8860
  return bridge;
8473
8861
  }
@@ -8571,6 +8959,23 @@ class BridgePool {
8571
8959
  error(message, meta);
8572
8960
  }
8573
8961
  setConfigureOverride(key, value) {
8962
+ if (key === "edit_slot_survives") {
8963
+ if (typeof value !== "boolean") {
8964
+ throw new Error("edit_slot_survives must be set once to a boolean");
8965
+ }
8966
+ if (this.editSlotSurvivesCaptured) {
8967
+ throw new Error("edit_slot_survives is write-once and was already captured");
8968
+ }
8969
+ this.editSlotSurvives = value;
8970
+ this.editSlotSurvivesCaptured = true;
8971
+ for (const entry of this.bridges.values()) {
8972
+ entry.bridge.setEditSlotSurvives(value);
8973
+ }
8974
+ for (const bridge of this.staleBridges) {
8975
+ bridge.setEditSlotSurvives(value);
8976
+ }
8977
+ return;
8978
+ }
8574
8979
  if (value === undefined) {
8575
8980
  delete this.configOverrides[key];
8576
8981
  } else {
@@ -8645,6 +9050,7 @@ class RevivableTransportPool {
8645
9050
  revival = null;
8646
9051
  transports = new Map;
8647
9052
  configureOverrides = new Map;
9053
+ editSlotSurvivesCaptured = false;
8648
9054
  constructor(initialPool, createPool, onBinaryReplaced) {
8649
9055
  this.createPool = createPool;
8650
9056
  this.onBinaryReplaced = onBinaryReplaced;
@@ -8675,6 +9081,18 @@ class RevivableTransportPool {
8675
9081
  return this.getBridge(projectRoot).toolCall(runtime.sessionID, name, rawArgs, options);
8676
9082
  }
8677
9083
  setConfigureOverride(key, value) {
9084
+ if (key === "edit_slot_survives") {
9085
+ if (typeof value !== "boolean") {
9086
+ throw new Error("edit_slot_survives must be set once to a boolean");
9087
+ }
9088
+ if (this.editSlotSurvivesCaptured) {
9089
+ throw new Error("edit_slot_survives is write-once and was already captured");
9090
+ }
9091
+ this.activePool.setConfigureOverride(key, value);
9092
+ this.editSlotSurvivesCaptured = true;
9093
+ this.configureOverrides.set(key, value);
9094
+ return;
9095
+ }
8678
9096
  if (value === undefined)
8679
9097
  this.configureOverrides.delete(key);
8680
9098
  else
@@ -8682,14 +9100,20 @@ class RevivableTransportPool {
8682
9100
  this.activePool.setConfigureOverride(key, value);
8683
9101
  }
8684
9102
  async reconfigure(projectRoot, overrides) {
9103
+ const pool = await this.ensureActivePool();
9104
+ const runtimeOverrides = {};
8685
9105
  for (const [key, value] of Object.entries(overrides)) {
9106
+ if (key === "edit_slot_survives") {
9107
+ this.setConfigureOverride(key, value);
9108
+ continue;
9109
+ }
8686
9110
  if (value === undefined)
8687
9111
  this.configureOverrides.delete(key);
8688
9112
  else
8689
9113
  this.configureOverrides.set(key, value);
9114
+ runtimeOverrides[key] = value;
8690
9115
  }
8691
- const pool = await this.ensureActivePool();
8692
- await pool.reconfigure(projectRoot, overrides);
9116
+ await pool.reconfigure(projectRoot, runtimeOverrides);
8693
9117
  }
8694
9118
  async replaceBinary(path2) {
8695
9119
  const replaced = await this.activePool.replaceBinary(path2);
@@ -9453,7 +9877,7 @@ var init_binary_probe = __esm(async () => {
9453
9877
  });
9454
9878
 
9455
9879
  // src/lib/fs-util.ts
9456
- import { existsSync as existsSync10, readdirSync as readdirSync3, statSync as statSync7 } from "node:fs";
9880
+ import { existsSync as existsSync10, readdirSync as readdirSync4, statSync as statSync7 } from "node:fs";
9457
9881
  import { join as join14 } from "node:path";
9458
9882
  function dirSize(path2) {
9459
9883
  if (!existsSync10(path2)) {
@@ -9467,7 +9891,7 @@ function dirSize(path2) {
9467
9891
  return 0;
9468
9892
  }
9469
9893
  let total = 0;
9470
- for (const entry of readdirSync3(path2)) {
9894
+ for (const entry of readdirSync4(path2)) {
9471
9895
  total += dirSize(join14(path2, entry));
9472
9896
  }
9473
9897
  return total;
@@ -19894,7 +20318,7 @@ __export(exports_lsp, {
19894
20318
  printLspDoctorHelp: () => printLspDoctorHelp,
19895
20319
  findProjectRootForFile: () => findProjectRootForFile
19896
20320
  });
19897
- import { existsSync as existsSync14, readdirSync as readdirSync4, statSync as statSync9 } from "node:fs";
20321
+ import { existsSync as existsSync14, readdirSync as readdirSync5, statSync as statSync9 } from "node:fs";
19898
20322
  import { createRequire as createRequire4 } from "node:module";
19899
20323
  import { dirname as dirname8, join as join17, resolve as resolve8 } from "node:path";
19900
20324
  function findProjectRootForFile(filePath, fallbackCwd = process.cwd()) {
@@ -20066,7 +20490,7 @@ function childDirs(path2) {
20066
20490
  if (!existsSync14(path2))
20067
20491
  return [];
20068
20492
  try {
20069
- return readdirSync4(path2).map((entry) => join17(path2, entry)).filter((entry) => {
20493
+ return readdirSync5(path2).map((entry) => join17(path2, entry)).filter((entry) => {
20070
20494
  try {
20071
20495
  return statSync9(entry).isDirectory();
20072
20496
  } catch {
@@ -20397,7 +20821,7 @@ var init_doctor_filters = __esm(async () => {
20397
20821
  });
20398
20822
 
20399
20823
  // src/lib/binary-cache.ts
20400
- import { existsSync as existsSync16, readdirSync as readdirSync5, statSync as statSync10 } from "node:fs";
20824
+ import { existsSync as existsSync16, readdirSync as readdirSync6, statSync as statSync10 } from "node:fs";
20401
20825
  import { join as join18 } from "node:path";
20402
20826
  function getBinaryCacheInfo(activeVersion) {
20403
20827
  const path2 = getAftBinaryCacheDir();
@@ -20409,7 +20833,7 @@ function getBinaryCacheInfo(activeVersion) {
20409
20833
  path: path2
20410
20834
  };
20411
20835
  }
20412
- const versions = readdirSync5(path2).filter((entry) => {
20836
+ const versions = readdirSync6(path2).filter((entry) => {
20413
20837
  try {
20414
20838
  return statSync10(join18(path2, entry)).isDirectory();
20415
20839
  } catch {
@@ -20651,7 +21075,7 @@ var init_bridge_tool_failures = __esm(() => {
20651
21075
  });
20652
21076
 
20653
21077
  // src/lib/legacy-storage.ts
20654
- import { existsSync as existsSync18, readdirSync as readdirSync6, statSync as statSync12 } from "node:fs";
21078
+ import { existsSync as existsSync18, readdirSync as readdirSync7, statSync as statSync12 } from "node:fs";
20655
21079
  import { join as join19 } from "node:path";
20656
21080
  function summarizeLegacyPartitionDuplication(storageRoot) {
20657
21081
  if (!existsSync18(storageRoot)) {
@@ -20749,7 +21173,7 @@ function looksLikePartitionKey(value) {
20749
21173
  }
20750
21174
  function safeReadDir(path2) {
20751
21175
  try {
20752
- return readdirSync6(path2).sort((left, right) => left.localeCompare(right));
21176
+ return readdirSync7(path2).sort((left, right) => left.localeCompare(right));
20753
21177
  } catch {
20754
21178
  return [];
20755
21179
  }
@@ -20775,7 +21199,7 @@ var init_legacy_storage = __esm(() => {
20775
21199
  });
20776
21200
 
20777
21201
  // src/lib/lsp-cache.ts
20778
- import { existsSync as existsSync19, readdirSync as readdirSync7, rmSync as rmSync5, statSync as statSync13 } from "node:fs";
21202
+ import { existsSync as existsSync19, readdirSync as readdirSync8, rmSync as rmSync5, statSync as statSync13 } from "node:fs";
20779
21203
  import { join as join20 } from "node:path";
20780
21204
  function inspectDir(path2) {
20781
21205
  if (!existsSync19(path2)) {
@@ -20785,7 +21209,7 @@ function inspectDir(path2) {
20785
21209
  let totalSize = 0;
20786
21210
  let names;
20787
21211
  try {
20788
- names = readdirSync7(path2);
21212
+ names = readdirSync8(path2);
20789
21213
  } catch {
20790
21214
  return { entries: [], totalSize: 0 };
20791
21215
  }
@@ -20848,7 +21272,7 @@ var init_lsp_cache = __esm(() => {
20848
21272
  });
20849
21273
 
20850
21274
  // src/lib/onnx.ts
20851
- import { existsSync as existsSync20, readdirSync as readdirSync8, readlinkSync as readlinkSync2, realpathSync as realpathSync4 } from "node:fs";
21275
+ import { existsSync as existsSync20, readdirSync as readdirSync9, readlinkSync as readlinkSync2, realpathSync as realpathSync4 } from "node:fs";
20852
21276
  import { basename as basename3, isAbsolute as isAbsolute6, join as join21, resolve as resolve10, win32 as win322 } from "node:path";
20853
21277
  function getOnnxLibraryName() {
20854
21278
  if (process.platform === "darwin")
@@ -20902,7 +21326,7 @@ function isWindowsSystem32Directory2(dir) {
20902
21326
  }
20903
21327
  function directoryContainsLibrary2(dir, libName) {
20904
21328
  try {
20905
- const entries = readdirSync8(dir);
21329
+ const entries = readdirSync9(dir);
20906
21330
  if (process.platform === "win32") {
20907
21331
  const expected = libName.toLowerCase();
20908
21332
  return entries.some((entry) => entry.toLowerCase() === expected);
@@ -20950,7 +21374,7 @@ function findSystemOnnxRuntime2() {
20950
21374
  if (!existsSync20(nugetPackageDir))
20951
21375
  return nugetPaths;
20952
21376
  try {
20953
- for (const entry of readdirSync8(nugetPackageDir, { withFileTypes: true })) {
21377
+ for (const entry of readdirSync9(nugetPackageDir, { withFileTypes: true })) {
20954
21378
  if (!entry.isDirectory())
20955
21379
  continue;
20956
21380
  if (entry.name === "__globalPackagesFolder" || entry.name.startsWith("."))
@@ -21017,7 +21441,7 @@ function detectOrtVersion(libDir) {
21017
21441
  return null;
21018
21442
  const libName = getOnnxLibraryName();
21019
21443
  try {
21020
- const entries = readdirSync8(libDir);
21444
+ const entries = readdirSync9(libDir);
21021
21445
  const barePrefix = libName.replace(/\.(so|dylib|dll)$/, "");
21022
21446
  const expectedPrefix = process.platform === "win32" ? barePrefix.toLowerCase() : barePrefix;
21023
21447
  for (const entry of entries) {
@@ -21649,7 +22073,7 @@ var init_onnx_fix = __esm(() => {
21649
22073
  });
21650
22074
 
21651
22075
  // src/lib/sessions.ts
21652
- import { existsSync as existsSync23, readdirSync as readdirSync9, readFileSync as readFileSync10, statSync as statSync15 } from "node:fs";
22076
+ import { existsSync as existsSync23, readdirSync as readdirSync10, readFileSync as readFileSync10, statSync as statSync15 } from "node:fs";
21653
22077
  import { createRequire as createRequire5 } from "node:module";
21654
22078
  import { homedir as homedir18 } from "node:os";
21655
22079
  import { basename as basename4, join as join23 } from "node:path";
@@ -21745,7 +22169,7 @@ function collectJsonlFiles(root) {
21745
22169
  continue;
21746
22170
  let entries;
21747
22171
  try {
21748
- entries = readdirSync9(dir, { withFileTypes: true });
22172
+ entries = readdirSync10(dir, { withFileTypes: true });
21749
22173
  } catch {
21750
22174
  continue;
21751
22175
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/aft",
3
- "version": "0.49.4",
3
+ "version": "0.50.1",
4
4
  "type": "module",
5
5
  "description": "Unified CLI for Agent File Tools (AFT) — setup, doctor, and diagnostics across supported agent harnesses (OpenCode, Pi)",
6
6
  "license": "MIT",
@@ -24,7 +24,7 @@
24
24
  },
25
25
  "dependencies": {
26
26
  "@clack/prompts": "^1.6.0",
27
- "@cortexkit/aft-bridge": "0.49.4",
27
+ "@cortexkit/aft-bridge": "0.50.1",
28
28
  "comment-json": "^4.6.2"
29
29
  },
30
30
  "devDependencies": {