@cortexkit/aft 0.49.4 → 0.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +352 -41
  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;
@@ -5112,12 +5169,15 @@ class BgSubscription {
5112
5169
  canAttach;
5113
5170
  onRootAttachFailure;
5114
5171
  onDormant;
5172
+ dispatchProbeIntervalMs;
5115
5173
  nudgeRef;
5116
5174
  isCurrent;
5117
5175
  stopped = false;
5118
5176
  current = null;
5119
5177
  loop;
5120
- constructor(identity, acquireClient, dropClient, consumerIdentity, onNudge, sleep2, canAttach, onRootAttachFailure, onDormant, nudgeRef, isCurrent = () => true) {
5178
+ lifecycleLogState = new Map;
5179
+ nudgeReceiptLogState = null;
5180
+ constructor(identity, acquireClient, dropClient, consumerIdentity, onNudge, sleep2, canAttach, onRootAttachFailure, onDormant, dispatchProbeIntervalMs, nudgeRef, isCurrent = () => true) {
5121
5181
  this.identity = identity;
5122
5182
  this.acquireClient = acquireClient;
5123
5183
  this.dropClient = dropClient;
@@ -5127,6 +5187,7 @@ class BgSubscription {
5127
5187
  this.canAttach = canAttach;
5128
5188
  this.onRootAttachFailure = onRootAttachFailure;
5129
5189
  this.onDormant = onDormant;
5190
+ this.dispatchProbeIntervalMs = dispatchProbeIntervalMs;
5130
5191
  this.nudgeRef = nudgeRef;
5131
5192
  this.isCurrent = isCurrent;
5132
5193
  this.loop = this.run();
@@ -5143,25 +5204,106 @@ class BgSubscription {
5143
5204
  return;
5144
5205
  });
5145
5206
  }
5207
+ info(kind, message) {
5208
+ const now = Date.now();
5209
+ const state = this.lifecycleLogState.get(kind);
5210
+ if (state && now - state.lastEmittedAt < BG_LIFECYCLE_LOG_INTERVAL_MS) {
5211
+ state.suppressed += 1;
5212
+ return;
5213
+ }
5214
+ const suppressed = state?.suppressed ?? 0;
5215
+ this.lifecycleLogState.set(kind, { lastEmittedAt: now, suppressed: 0 });
5216
+ const suffix = suppressed > 0 ? ` suppressed=${suppressed}` : "";
5217
+ log(`subc bg_events: ${message}${suffix}`, { sessionId: this.identity.session });
5218
+ }
5219
+ routeId(route) {
5220
+ return `${route.channel}@${route.epoch}`;
5221
+ }
5222
+ recordNudgeReceipt(routeId) {
5223
+ const now = Date.now();
5224
+ const state = this.nudgeReceiptLogState;
5225
+ if (state && now - state.lastEmittedAt < BG_LIFECYCLE_LOG_INTERVAL_MS) {
5226
+ state.count += 1;
5227
+ return;
5228
+ }
5229
+ const count = (state?.count ?? 0) + 1;
5230
+ this.nudgeReceiptLogState = { lastEmittedAt: now, count: 0 };
5231
+ log(`subc bg_events: nudge received channel=${routeId} count=${count}`, {
5232
+ sessionId: this.identity.session
5233
+ });
5234
+ }
5235
+ errorText(error2) {
5236
+ return error2 instanceof Error ? `${error2.name}: ${error2.message}` : String(error2);
5237
+ }
5238
+ startDispatchProbe(client, routeId) {
5239
+ const initial = client.droppedIngressFrames;
5240
+ if (typeof initial !== "number")
5241
+ return () => {
5242
+ return;
5243
+ };
5244
+ let previous = initial;
5245
+ const timer = setInterval(() => {
5246
+ const total = client.droppedIngressFrames;
5247
+ if (typeof total !== "number" || total <= previous)
5248
+ return;
5249
+ const delta = total - previous;
5250
+ previous = total;
5251
+ this.info("dispatch-epoch-drop", `client ingress epoch drops scope=client observed_while_channel=${routeId} delta=${delta} total=${total}`);
5252
+ }, this.dispatchProbeIntervalMs);
5253
+ timer.unref?.();
5254
+ return () => clearInterval(timer);
5255
+ }
5146
5256
  async run() {
5147
- let attempt = 0;
5257
+ let backoffAttempt = 0;
5258
+ let reconnectAttempt = 0;
5259
+ let reconnecting = false;
5260
+ const beginReconnect = () => {
5261
+ reconnecting = true;
5262
+ reconnectAttempt = reconnectAttempt === 0 ? 1 : reconnectAttempt + 1;
5263
+ };
5264
+ const giveUp = (reason) => {
5265
+ this.info("reconnect-gave-up", `reconnect gave-up attempt=${reconnectAttempt} reason=${reason}`);
5266
+ };
5148
5267
  while (!this.stopped) {
5149
- if (!this.isCurrent())
5268
+ if (!this.isCurrent()) {
5269
+ if (reconnecting)
5270
+ giveUp("stale-session");
5150
5271
  return;
5272
+ }
5151
5273
  if (!this.canAttach()) {
5274
+ if (reconnecting)
5275
+ giveUp("root-dormant");
5152
5276
  this.onDormant();
5153
5277
  return;
5154
5278
  }
5279
+ if (reconnecting) {
5280
+ this.info("reconnect-attempt", `reconnect attempt=${reconnectAttempt}`);
5281
+ }
5155
5282
  let client;
5156
5283
  try {
5157
5284
  client = await this.acquireClient();
5158
- } catch {
5159
- await this.backoff(attempt++);
5285
+ } catch (err) {
5286
+ if (!reconnecting)
5287
+ beginReconnect();
5288
+ this.info("reconnect-error", `reconnect error attempt=${reconnectAttempt} error=${this.errorText(err)}`);
5289
+ await this.backoff(backoffAttempt++);
5290
+ if (reconnecting)
5291
+ reconnectAttempt += 1;
5160
5292
  continue;
5161
5293
  }
5162
- if (this.stopped || !this.isCurrent())
5294
+ if (this.stopped) {
5295
+ if (reconnecting)
5296
+ giveUp("stopped");
5297
+ return;
5298
+ }
5299
+ if (!this.isCurrent()) {
5300
+ if (reconnecting)
5301
+ giveUp("stale-session");
5163
5302
  return;
5303
+ }
5164
5304
  if (!this.canAttach()) {
5305
+ if (reconnecting)
5306
+ giveUp("root-dormant");
5165
5307
  this.onDormant();
5166
5308
  return;
5167
5309
  }
@@ -5170,44 +5312,84 @@ class BgSubscription {
5170
5312
  route = await client.routeOpen({ kind: "tool_provider", module_id: AFT_MODULE_ID }, this.identity, { consumerIdentity: this.consumerIdentity });
5171
5313
  } catch (err) {
5172
5314
  if (this.isCurrent() && this.onRootAttachFailure(err)) {
5315
+ if (reconnecting)
5316
+ giveUp("root-dormant");
5173
5317
  this.onDormant();
5174
5318
  return;
5175
5319
  }
5176
5320
  if (isConsumerReconnectTransient(err))
5177
5321
  this.dropClient(client);
5178
- await this.backoff(attempt++);
5322
+ if (!reconnecting)
5323
+ beginReconnect();
5324
+ this.info("reconnect-error", `reconnect error attempt=${reconnectAttempt} error=${this.errorText(err)}`);
5325
+ await this.backoff(backoffAttempt++);
5326
+ if (reconnecting)
5327
+ reconnectAttempt += 1;
5179
5328
  continue;
5180
5329
  }
5181
5330
  if (this.stopped || !this.isCurrent()) {
5182
5331
  safeCloseRoute(client, route);
5332
+ if (reconnecting)
5333
+ giveUp(this.stopped ? "stopped" : "stale-session");
5183
5334
  return;
5184
5335
  }
5185
5336
  const subscribedAt = Date.now();
5337
+ const routeId = this.routeId(route);
5338
+ let stopDispatchProbe = () => {
5339
+ return;
5340
+ };
5186
5341
  try {
5187
5342
  const sub = client.subscribe(route, { op: "bg_events" }, () => {
5188
- if (!this.stopped && this.isCurrent())
5189
- this.onNudge();
5343
+ if (this.stopped) {
5344
+ this.info("nudge-drop-stopped", `nudge dropped cause=subscription-stopped channel=${routeId}`);
5345
+ return;
5346
+ }
5347
+ this.recordNudgeReceipt(routeId);
5348
+ if (!this.isCurrent()) {
5349
+ this.info("nudge-stale-carrier", `nudge carried by stale subscription; checking current session channel=${routeId}`);
5350
+ }
5351
+ this.onNudge();
5190
5352
  });
5191
5353
  this.current = sub;
5354
+ stopDispatchProbe = this.startDispatchProbe(client, routeId);
5355
+ this.info("subscription-open", `subscription open channel=${routeId}`);
5356
+ if (reconnecting) {
5357
+ this.info("reconnect-success", `reconnect success attempt=${reconnectAttempt} channel=${routeId}`);
5358
+ reconnecting = false;
5359
+ reconnectAttempt = 0;
5360
+ }
5192
5361
  if (this.stopped)
5193
5362
  sub.unsubscribe();
5194
5363
  if (!this.stopped && this.isCurrent())
5195
5364
  this.onNudge();
5196
5365
  await sub.closed;
5197
- return;
5366
+ this.info("stream-end", `stream ended channel=${routeId}`);
5367
+ if (this.stopped) {
5368
+ giveUp("stopped");
5369
+ return;
5370
+ }
5371
+ beginReconnect();
5198
5372
  } catch (err) {
5199
- if (this.stopped)
5373
+ const routeId2 = this.routeId(route);
5374
+ this.info("stream-error", `stream error channel=${routeId2} error=${this.errorText(err)}`);
5375
+ if (this.stopped) {
5376
+ giveUp("stopped");
5200
5377
  return;
5378
+ }
5201
5379
  if (isConsumerReconnectTransient(err))
5202
5380
  this.dropClient(client);
5203
5381
  if (Date.now() - subscribedAt >= BG_STABLE_MS)
5204
- attempt = 0;
5382
+ backoffAttempt = 0;
5383
+ beginReconnect();
5205
5384
  } finally {
5385
+ stopDispatchProbe();
5206
5386
  this.current = null;
5207
5387
  safeCloseRoute(client, route);
5208
5388
  }
5209
- await this.backoff(attempt++);
5389
+ await this.backoff(backoffAttempt++);
5210
5390
  }
5391
+ if (reconnecting)
5392
+ giveUp("stopped");
5211
5393
  }
5212
5394
  async backoff(attempt) {
5213
5395
  const ms = Math.min(100 * 2 ** Math.min(attempt, 6), 2000);
@@ -5302,6 +5484,9 @@ class SubcTransport {
5302
5484
  this.assertCurrent();
5303
5485
  const { preview, timeoutMs, onProgress } = this.splitOptions(options);
5304
5486
  const body = { name, arguments: rawArgs };
5487
+ const editSlotSurvives = this.pool.getEditSlotSurvives();
5488
+ if (editSlotSurvives !== undefined)
5489
+ body.edit_slot_survives = editSlotSurvives;
5305
5490
  if (preview === true)
5306
5491
  body.preview = true;
5307
5492
  const reply = await this.pool.routeRequest(this.identityFor(sessionId), body, timeoutMs, onProgress, this.generation);
@@ -5316,7 +5501,11 @@ class SubcTransport {
5316
5501
  }
5317
5502
  const { timeoutMs, onProgress } = this.splitOptions(options);
5318
5503
  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);
5504
+ const body = { name: command, arguments: params };
5505
+ const editSlotSurvives = this.pool.getEditSlotSurvives();
5506
+ if (editSlotSurvives !== undefined)
5507
+ body.edit_slot_survives = editSlotSurvives;
5508
+ const reply = await this.pool.routeRequest(this.identityFor(session), body, timeoutMs, onProgress, this.generation);
5320
5509
  const response = reliftReply(reply);
5321
5510
  this.captureStatusBar(response);
5322
5511
  return response;
@@ -5340,6 +5529,7 @@ class SubcTransportPool {
5340
5529
  onBgEventsNudge;
5341
5530
  onBgEventsNudgeRef;
5342
5531
  bgBackoffSleep;
5532
+ bgDispatchProbeIntervalMs;
5343
5533
  lifecycleDemandCheck;
5344
5534
  onLifecycleEvent;
5345
5535
  onBgNudgeRejected;
@@ -5355,8 +5545,11 @@ class SubcTransportPool {
5355
5545
  transportFailures = 0;
5356
5546
  transports = new Map;
5357
5547
  generationRejections = new Set;
5548
+ nudgeDeliveryLogState = new Map;
5358
5549
  pendingRootCleanups = new Set;
5359
5550
  shuttingDown = false;
5551
+ editSlotSurvives;
5552
+ editSlotSurvivesCaptured = false;
5360
5553
  constructor(options) {
5361
5554
  this.connectionFile = options.connectionFile;
5362
5555
  this.harness = options.harness;
@@ -5366,6 +5559,7 @@ class SubcTransportPool {
5366
5559
  this.onBgEventsNudge = options.onBgEventsNudge;
5367
5560
  this.onBgEventsNudgeRef = options.onBgEventsNudgeRef;
5368
5561
  this.bgBackoffSleep = options.bgBackoffSleep ?? ((ms) => new Promise((resolve4) => setTimeout(resolve4, ms)));
5562
+ this.bgDispatchProbeIntervalMs = options.bgDispatchProbeIntervalMs ?? BG_DISPATCH_PROBE_INTERVAL_MS;
5369
5563
  const lifecycle = options.lifecycle;
5370
5564
  const demandCheck = options.lifecycleDemandCheck ?? options.demandCheck ?? lifecycle?.demandCheck;
5371
5565
  this.lifecycleDemandCheck = demandCheck;
@@ -5682,6 +5876,35 @@ class SubcTransportPool {
5682
5876
  isCurrentSession(key, record) {
5683
5877
  return this.sessions.get(key) === record && !record.closed;
5684
5878
  }
5879
+ currentSessionForNudge(identity) {
5880
+ const current = this.sessions.get(identityKey(identity));
5881
+ return current && !current.closed ? current : null;
5882
+ }
5883
+ nudgeRefFor(record) {
5884
+ const poolId = this.currentPoolId();
5885
+ const generation = record.generation;
5886
+ if (poolId === undefined || generation === undefined)
5887
+ return;
5888
+ return {
5889
+ canonicalRoot: record.canonicalRoot,
5890
+ session: record.identity.session,
5891
+ concretePoolId: poolId,
5892
+ generation
5893
+ };
5894
+ }
5895
+ logNudgeDelivery(kind, record, message) {
5896
+ const key = `${kind}\x00${record.identityKey}`;
5897
+ const now = Date.now();
5898
+ const state = this.nudgeDeliveryLogState.get(key);
5899
+ if (state && now - state.lastEmittedAt < BG_LIFECYCLE_LOG_INTERVAL_MS) {
5900
+ state.suppressed += 1;
5901
+ return;
5902
+ }
5903
+ const suppressed = state?.suppressed ?? 0;
5904
+ this.nudgeDeliveryLogState.set(key, { lastEmittedAt: now, suppressed: 0 });
5905
+ const suffix = suppressed > 0 ? ` suppressed=${suppressed}` : "";
5906
+ log(`subc bg_events: ${message}${suffix}`, { sessionId: record.identity.session });
5907
+ }
5685
5908
  removeIndexMembership(record) {
5686
5909
  const keys = this.rootIndex.get(record.canonicalRoot);
5687
5910
  if (!keys)
@@ -5935,20 +6158,32 @@ class SubcTransportPool {
5935
6158
  return;
5936
6159
  if (record.bgSub)
5937
6160
  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;
6161
+ const nudgeRef = this.nudgeRefFor(record);
5946
6162
  const onNudge = () => {
5947
- if (!this.isCurrentSession(record.identityKey, record))
6163
+ const currentRecord = this.currentSessionForNudge(identity);
6164
+ if (!currentRecord) {
6165
+ this.logNudgeDelivery("drop-no-current-session", record, `nudge dropped cause=no-current-session root=${record.canonicalRoot}`);
6166
+ return;
6167
+ }
6168
+ if (currentRecord !== record) {
6169
+ this.logNudgeDelivery("forward-superseded-carrier", currentRecord, `nudge forwarding cause=superseded-carrying-record root=${currentRecord.canonicalRoot}`);
6170
+ }
6171
+ const currentRef = this.nudgeRefFor(currentRecord);
6172
+ let delivered = false;
6173
+ if (currentRef && this.onBgEventsNudgeRef) {
6174
+ this.onBgEventsNudgeRef(currentRef);
6175
+ delivered = true;
6176
+ }
6177
+ if (this.onBgEventsNudge) {
6178
+ if (!currentRef && this.onBgEventsNudgeRef) {
6179
+ this.logNudgeDelivery("fallback-missing-generation", currentRecord, `nudge dispatch fallback=root-session-handler cause=generation-provenance-unavailable root=${currentRecord.canonicalRoot}`);
6180
+ }
6181
+ this.onBgEventsNudge(currentRecord.identity.project_root, currentRecord.identity.session);
6182
+ delivered = true;
6183
+ }
6184
+ if (delivered)
5948
6185
  return;
5949
- this.onBgEventsNudge?.(identity.project_root, identity.session);
5950
- if (nudgeRef)
5951
- this.onBgEventsNudgeRef?.(nudgeRef);
6186
+ this.logNudgeDelivery("drop-no-compatible-handler", currentRecord, `nudge dropped cause=generation-provenance-unavailable-and-root-session-handler-unwired root=${currentRecord.canonicalRoot}`);
5952
6187
  };
5953
6188
  let sub = null;
5954
6189
  const clearDormantSubscription = () => {
@@ -5960,7 +6195,7 @@ class SubcTransportPool {
5960
6195
  return false;
5961
6196
  this.markRootDormant(record.canonicalRoot, error2);
5962
6197
  return true;
5963
- }, clearDormantSubscription, nudgeRef, () => this.isCurrentSession(record.identityKey, record) && this.isCurrentLiveGeneration(record.canonicalRoot, record.generation));
6198
+ }, clearDormantSubscription, this.bgDispatchProbeIntervalMs, nudgeRef, () => this.isCurrentSession(record.identityKey, record) && this.isCurrentLiveGeneration(record.canonicalRoot, record.generation));
5964
6199
  record.bgSub = sub;
5965
6200
  if (!this.rootCanAttach(record.canonicalRoot)) {
5966
6201
  record.bgSub = null;
@@ -6022,7 +6257,21 @@ class SubcTransportPool {
6022
6257
  return Promise.resolve();
6023
6258
  return registry.requestProjectRootClose(registration.concretePoolId, root, generation, cause);
6024
6259
  }
6025
- setConfigureOverride(_key, _value) {}
6260
+ setConfigureOverride(key, value) {
6261
+ if (key !== "edit_slot_survives")
6262
+ return;
6263
+ if (typeof value !== "boolean") {
6264
+ throw new Error("edit_slot_survives must be set once to a boolean");
6265
+ }
6266
+ if (this.editSlotSurvivesCaptured) {
6267
+ throw new Error("edit_slot_survives is write-once and was already captured");
6268
+ }
6269
+ this.editSlotSurvives = value;
6270
+ this.editSlotSurvivesCaptured = true;
6271
+ }
6272
+ getEditSlotSurvives() {
6273
+ return this.editSlotSurvives;
6274
+ }
6026
6275
  async reconfigure(_projectRoot, _overrides) {}
6027
6276
  async replaceBinary(path2) {
6028
6277
  return path2;
@@ -6081,7 +6330,7 @@ function resolveBridgeForNudge(pool, ref) {
6081
6330
  currentConcretePoolId: candidate.getConcretePoolId?.()
6082
6331
  });
6083
6332
  }
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;
6333
+ 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
6334
  var init_subc_transport = __esm(() => {
6086
6335
  init_dist();
6087
6336
  init_active_logger();
@@ -6141,20 +6390,34 @@ function toolErrorFromResponse(command, response) {
6141
6390
  const message = typeof response.message === "string" && response.message.length > 0 ? response.message : `${command} failed`;
6142
6391
  return new AftToolError(message, code, response);
6143
6392
  }
6393
+ function isRouteGoodbyeError(error2) {
6394
+ if (!(error2 instanceof SubcError))
6395
+ return false;
6396
+ if (error2.code === undefined) {
6397
+ return error2.message.includes("route closed by subc");
6398
+ }
6399
+ return error2.code === "route_closed" && error2.message.includes("route closed by subc");
6400
+ }
6144
6401
  function isTransportClassError(error2) {
6145
6402
  return isBridgeTransportTimeout(error2) || isConsumerReconnectTransient(error2) || error2 instanceof StaleRouteHandleError || error2 instanceof SubcRootGenerationExpiredError || error2 instanceof SubcRootReapedError;
6146
6403
  }
6147
6404
  function adaptToolError(command, error2) {
6148
- if (command !== "bash" || !isTransportClassError(error2))
6149
- return error2;
6150
6405
  if (!(error2 instanceof Error))
6151
6406
  return error2;
6407
+ if (isRouteGoodbyeError(error2)) {
6408
+ if (error2.message.includes(SUBC_MODULE_RESTART_DISPOSITION))
6409
+ return error2;
6410
+ error2.message = error2.message ? `${error2.message} ${SUBC_MODULE_RESTART_DISPOSITION}` : SUBC_MODULE_RESTART_DISPOSITION;
6411
+ return error2;
6412
+ }
6413
+ if (command !== "bash" || !isTransportClassError(error2))
6414
+ return error2;
6152
6415
  if (error2.message.includes(BASH_TRANSPORT_DISPOSITION))
6153
6416
  return error2;
6154
6417
  error2.message = error2.message ? `${error2.message} ${BASH_TRANSPORT_DISPOSITION}` : BASH_TRANSPORT_DISPOSITION;
6155
6418
  return error2;
6156
6419
  }
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.";
6420
+ 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
6421
  var init_error_contract = __esm(() => {
6159
6422
  init_dist();
6160
6423
  init_bridge();
@@ -8404,6 +8667,8 @@ class BridgePool {
8404
8667
  idleTimeoutMs;
8405
8668
  bridgeOptions;
8406
8669
  configOverrides;
8670
+ editSlotSurvives;
8671
+ editSlotSurvivesCaptured = false;
8407
8672
  projectConfigLoader;
8408
8673
  logger;
8409
8674
  cleanupTimer = null;
@@ -8428,7 +8693,16 @@ class BridgePool {
8428
8693
  logger: options.logger,
8429
8694
  childEnv: options.childEnv
8430
8695
  };
8431
- this.configOverrides = configOverrides;
8696
+ this.configOverrides = { ...configOverrides };
8697
+ const initialEditSlotSurvives = this.configOverrides.edit_slot_survives;
8698
+ delete this.configOverrides.edit_slot_survives;
8699
+ if (initialEditSlotSurvives !== undefined) {
8700
+ if (typeof initialEditSlotSurvives !== "boolean") {
8701
+ throw new Error("edit_slot_survives must be a boolean");
8702
+ }
8703
+ this.editSlotSurvives = initialEditSlotSurvives;
8704
+ this.editSlotSurvivesCaptured = true;
8705
+ }
8432
8706
  this.startCleanupTimer();
8433
8707
  }
8434
8708
  getActiveBridgeForRoot(projectRoot) {
@@ -8467,7 +8741,8 @@ class BridgePool {
8467
8741
  }
8468
8742
  const projectOverrides = this.loadProjectOverrides(key);
8469
8743
  const mergedOverrides = { ...this.configOverrides, ...projectOverrides };
8470
- const bridge = new BinaryBridge(this.binaryPath, key, this.bridgeOptions, mergedOverrides);
8744
+ delete mergedOverrides.edit_slot_survives;
8745
+ const bridge = new BinaryBridge(this.binaryPath, key, this.bridgeOptions, mergedOverrides, this.editSlotSurvivesCaptured ? this.editSlotSurvives : undefined);
8471
8746
  this.bridges.set(key, { bridge, lastUsed: Date.now() });
8472
8747
  return bridge;
8473
8748
  }
@@ -8571,6 +8846,23 @@ class BridgePool {
8571
8846
  error(message, meta);
8572
8847
  }
8573
8848
  setConfigureOverride(key, value) {
8849
+ if (key === "edit_slot_survives") {
8850
+ if (typeof value !== "boolean") {
8851
+ throw new Error("edit_slot_survives must be set once to a boolean");
8852
+ }
8853
+ if (this.editSlotSurvivesCaptured) {
8854
+ throw new Error("edit_slot_survives is write-once and was already captured");
8855
+ }
8856
+ this.editSlotSurvives = value;
8857
+ this.editSlotSurvivesCaptured = true;
8858
+ for (const entry of this.bridges.values()) {
8859
+ entry.bridge.setEditSlotSurvives(value);
8860
+ }
8861
+ for (const bridge of this.staleBridges) {
8862
+ bridge.setEditSlotSurvives(value);
8863
+ }
8864
+ return;
8865
+ }
8574
8866
  if (value === undefined) {
8575
8867
  delete this.configOverrides[key];
8576
8868
  } else {
@@ -8645,6 +8937,7 @@ class RevivableTransportPool {
8645
8937
  revival = null;
8646
8938
  transports = new Map;
8647
8939
  configureOverrides = new Map;
8940
+ editSlotSurvivesCaptured = false;
8648
8941
  constructor(initialPool, createPool, onBinaryReplaced) {
8649
8942
  this.createPool = createPool;
8650
8943
  this.onBinaryReplaced = onBinaryReplaced;
@@ -8675,6 +8968,18 @@ class RevivableTransportPool {
8675
8968
  return this.getBridge(projectRoot).toolCall(runtime.sessionID, name, rawArgs, options);
8676
8969
  }
8677
8970
  setConfigureOverride(key, value) {
8971
+ if (key === "edit_slot_survives") {
8972
+ if (typeof value !== "boolean") {
8973
+ throw new Error("edit_slot_survives must be set once to a boolean");
8974
+ }
8975
+ if (this.editSlotSurvivesCaptured) {
8976
+ throw new Error("edit_slot_survives is write-once and was already captured");
8977
+ }
8978
+ this.activePool.setConfigureOverride(key, value);
8979
+ this.editSlotSurvivesCaptured = true;
8980
+ this.configureOverrides.set(key, value);
8981
+ return;
8982
+ }
8678
8983
  if (value === undefined)
8679
8984
  this.configureOverrides.delete(key);
8680
8985
  else
@@ -8682,14 +8987,20 @@ class RevivableTransportPool {
8682
8987
  this.activePool.setConfigureOverride(key, value);
8683
8988
  }
8684
8989
  async reconfigure(projectRoot, overrides) {
8990
+ const pool = await this.ensureActivePool();
8991
+ const runtimeOverrides = {};
8685
8992
  for (const [key, value] of Object.entries(overrides)) {
8993
+ if (key === "edit_slot_survives") {
8994
+ this.setConfigureOverride(key, value);
8995
+ continue;
8996
+ }
8686
8997
  if (value === undefined)
8687
8998
  this.configureOverrides.delete(key);
8688
8999
  else
8689
9000
  this.configureOverrides.set(key, value);
9001
+ runtimeOverrides[key] = value;
8690
9002
  }
8691
- const pool = await this.ensureActivePool();
8692
- await pool.reconfigure(projectRoot, overrides);
9003
+ await pool.reconfigure(projectRoot, runtimeOverrides);
8693
9004
  }
8694
9005
  async replaceBinary(path2) {
8695
9006
  const replaced = await this.activePool.replaceBinary(path2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/aft",
3
- "version": "0.49.4",
3
+ "version": "0.50.0",
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.0",
28
28
  "comment-json": "^4.6.2"
29
29
  },
30
30
  "devDependencies": {