@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.
- package/dist/index.js +352 -41
- 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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 (
|
|
5189
|
-
this.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
27
|
+
"@cortexkit/aft-bridge": "0.50.0",
|
|
28
28
|
"comment-json": "^4.6.2"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|