@modelprofile.com/flexharness 3.2.0 → 3.4.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/changelog.md +19 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.flexharness.d.ts +11 -0
- package/dist_ts/classes.flexharness.js +287 -109
- package/dist_ts/interfaces.d.ts +12 -3
- package/dist_ts/plugins.d.ts +3 -2
- package/dist_ts/plugins.js +3 -2
- package/package.json +1 -1
- package/readme.hints.md +1 -0
- package/readme.md +3 -3
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.flexharness.ts +469 -99
- package/ts/interfaces.ts +19 -3
- package/ts/plugins.ts +3 -1
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
errorToInfo,
|
|
17
17
|
} from './errors.js';
|
|
18
18
|
import type {
|
|
19
|
+
IFlexAgentContextInvocation,
|
|
19
20
|
IFlexAgentSessionPolicy,
|
|
20
21
|
IFlexAttachmentMessagePart,
|
|
21
22
|
IFlexBackgroundExecution,
|
|
@@ -96,6 +97,7 @@ type TRunPhase =
|
|
|
96
97
|
|
|
97
98
|
interface IStoredSessionState {
|
|
98
99
|
storageKey: string;
|
|
100
|
+
compactorContext?: IFlexAgentContextInvocation<unknown>;
|
|
99
101
|
session: IFlexSession;
|
|
100
102
|
messages: IFlexMessage[];
|
|
101
103
|
stagedTerminals: IFlexTerminalProjection[];
|
|
@@ -113,6 +115,7 @@ interface IStoredSessionState {
|
|
|
113
115
|
executionContextCloseCompleted: boolean;
|
|
114
116
|
jobStoreReleased: boolean;
|
|
115
117
|
jobs?: NonNullable<plugins.IToolExecutionContext['jobs']>;
|
|
118
|
+
compactorLifecycleController: AbortController;
|
|
116
119
|
promptQueue: IQueuedPrompt[];
|
|
117
120
|
outstandingPromptsById: Map<string, IQueuedPrompt>;
|
|
118
121
|
terminalPromptQueueEntries: Map<string, IFlexPromptQueueEntry>;
|
|
@@ -139,6 +142,12 @@ interface IOrphanedProviderRelease {
|
|
|
139
142
|
release: () => Promise<void>;
|
|
140
143
|
}
|
|
141
144
|
|
|
145
|
+
interface IOrphanedTombstoneCleanup {
|
|
146
|
+
storageKey: string;
|
|
147
|
+
sessionId: string;
|
|
148
|
+
completion: Promise<void>;
|
|
149
|
+
}
|
|
150
|
+
|
|
142
151
|
interface IOrphanedExecutionContextOwner {
|
|
143
152
|
storageKey: string;
|
|
144
153
|
sessionId: string;
|
|
@@ -146,6 +155,7 @@ interface IOrphanedExecutionContextOwner {
|
|
|
146
155
|
|
|
147
156
|
interface IStorageState {
|
|
148
157
|
storageKey: string;
|
|
158
|
+
compactorLifecycleController: AbortController;
|
|
149
159
|
scopeIdHint: string;
|
|
150
160
|
revision: number;
|
|
151
161
|
sessions: Map<string, IStoredSessionState>;
|
|
@@ -167,6 +177,8 @@ interface IStorageState {
|
|
|
167
177
|
interface IActiveRun {
|
|
168
178
|
state: IStorageState;
|
|
169
179
|
stored: IStoredSessionState;
|
|
180
|
+
originCompactorContext: IFlexAgentContextInvocation<unknown>;
|
|
181
|
+
deferredCompactorContext?: IFlexAgentContextInvocation<unknown>;
|
|
170
182
|
scopeId: string;
|
|
171
183
|
scope: unknown;
|
|
172
184
|
sessionId: string;
|
|
@@ -694,7 +706,9 @@ function resolvePromptQueueLimits(
|
|
|
694
706
|
return resolved;
|
|
695
707
|
}
|
|
696
708
|
|
|
697
|
-
function normalizeAgentSessionPolicy(
|
|
709
|
+
function normalizeAgentSessionPolicy<TScope>(
|
|
710
|
+
policy: IFlexAgentSessionPolicy<TScope> = {},
|
|
711
|
+
): IFlexAgentSessionPolicy<TScope> {
|
|
698
712
|
return {
|
|
699
713
|
...(policy.contextBuilder === undefined ? {} : { contextBuilder: policy.contextBuilder }),
|
|
700
714
|
...(policy.contextCompactor === undefined ? {} : { contextCompactor: policy.contextCompactor }),
|
|
@@ -723,7 +737,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
723
737
|
private readonly toolProvider: IFlexHarnessOptions<TScope>['toolProvider'];
|
|
724
738
|
private readonly executionContextProvider: IFlexHarnessOptions<TScope>['executionContextProvider'];
|
|
725
739
|
private readonly stores: IFlexHarnessStores;
|
|
726
|
-
private readonly agentSessionPolicy: IFlexAgentSessionPolicy
|
|
740
|
+
private readonly agentSessionPolicy: IFlexAgentSessionPolicy<TScope>;
|
|
727
741
|
private readonly toolOutputLimits: Required<NonNullable<IFlexHarnessOptions<TScope>['toolOutputLimits']>>;
|
|
728
742
|
private readonly callbackLimits: Required<IFlexCallbackLimits>;
|
|
729
743
|
private readonly promptQueueLimits: Required<IFlexPromptQueueLimits>;
|
|
@@ -732,6 +746,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
732
746
|
private readonly scopeAdmissions = new Map<string, IScopeAdmissionState>();
|
|
733
747
|
private readonly scopeRetirements = new Map<string, Promise<void>>();
|
|
734
748
|
private readonly storageDrains = new Map<string, Promise<void>>();
|
|
749
|
+
private readonly storageCompactorLifecycleControllers = new Map<string, AbortController>();
|
|
735
750
|
private orphanedResourceQueue: Promise<void> = Promise.resolve();
|
|
736
751
|
private readonly listeners = new Set<TFlexHarnessEventListener>();
|
|
737
752
|
private readonly trustedInternalErrors = new WeakSet<object>();
|
|
@@ -741,7 +756,15 @@ export class FlexHarness<TScope = unknown> {
|
|
|
741
756
|
IOrphanedExecutionContextOwner
|
|
742
757
|
>();
|
|
743
758
|
private readonly orphanedProviderReleases = new Map<string, IOrphanedProviderRelease>();
|
|
759
|
+
private readonly orphanedTombstoneCleanups = new Map<string, IOrphanedTombstoneCleanup>();
|
|
744
760
|
private readonly pendingPromptAdmissionOwners = new Set<IPendingPromptAdmission>();
|
|
761
|
+
private readonly compactorInvocationContext = new plugins.AsyncLocalStorage<
|
|
762
|
+
IFlexAgentContextInvocation<TScope>
|
|
763
|
+
>();
|
|
764
|
+
private readonly deferredCompactorContexts = new WeakMap<
|
|
765
|
+
IFlexAgentContextInvocation<unknown>,
|
|
766
|
+
IFlexAgentContextInvocation<unknown>
|
|
767
|
+
>();
|
|
745
768
|
private sequence = 0;
|
|
746
769
|
private promptQueueSequence = 0;
|
|
747
770
|
private pendingPromptAdmissions = 0;
|
|
@@ -876,7 +899,12 @@ export class FlexHarness<TScope = unknown> {
|
|
|
876
899
|
resolved.state.tombstones.set(sessionId, tombstone);
|
|
877
900
|
}, true);
|
|
878
901
|
tombstoneCommitted = true;
|
|
879
|
-
await this.finishTombstoneCleanup(
|
|
902
|
+
await this.finishTombstoneCleanup(
|
|
903
|
+
resolved.state,
|
|
904
|
+
sessionId,
|
|
905
|
+
scopeId,
|
|
906
|
+
resolved.scope.scope,
|
|
907
|
+
);
|
|
880
908
|
domainCleanupCompleted = true;
|
|
881
909
|
} catch (cleanupError) {
|
|
882
910
|
const projectedCleanup = this.projectOperationError(
|
|
@@ -898,6 +926,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
898
926
|
this.trustInternalError(new FlexHarnessAbortError(
|
|
899
927
|
'The session namespace was fenced after uncertain creation cleanup.',
|
|
900
928
|
)),
|
|
929
|
+
{ scopeId, scope: resolved.scope.scope },
|
|
901
930
|
);
|
|
902
931
|
} catch {
|
|
903
932
|
// The projected creation and cleanup errors remain safe for the caller.
|
|
@@ -932,7 +961,9 @@ export class FlexHarness<TScope = unknown> {
|
|
|
932
961
|
const { state } = await this.resolveState(scopeId);
|
|
933
962
|
let result!: IFlexSession;
|
|
934
963
|
await this.mutateScope(state, () => {
|
|
935
|
-
const stored =
|
|
964
|
+
const stored = Object.prototype.hasOwnProperty.call(options, 'archived')
|
|
965
|
+
? this.requireMutableSession(state, sessionId)
|
|
966
|
+
: this.requireSession(state, sessionId);
|
|
936
967
|
const timestamp = new Date().toISOString();
|
|
937
968
|
if (Object.prototype.hasOwnProperty.call(options, 'title')) {
|
|
938
969
|
if (options.title === null) delete stored.session.title;
|
|
@@ -948,12 +979,12 @@ export class FlexHarness<TScope = unknown> {
|
|
|
948
979
|
}
|
|
949
980
|
|
|
950
981
|
public async deleteSession(scopeId: string, sessionId: string): Promise<void> {
|
|
951
|
-
const { state } = await this.resolveState(scopeId);
|
|
982
|
+
const { scope, state } = await this.resolveState(scopeId);
|
|
952
983
|
validateIdentifier(sessionId, 'sessionId');
|
|
953
984
|
const existingDeletion = state.sessionDeletions.get(sessionId);
|
|
954
985
|
if (existingDeletion) return existingDeletion;
|
|
955
986
|
let deletion!: Promise<void>;
|
|
956
|
-
deletion = this.deleteSessionInternal(state, scopeId, sessionId).finally(() => {
|
|
987
|
+
deletion = this.deleteSessionInternal(state, scopeId, scope.scope, sessionId).finally(() => {
|
|
957
988
|
if (state.sessionDeletions.get(sessionId) === deletion) {
|
|
958
989
|
state.sessionDeletions.delete(sessionId);
|
|
959
990
|
}
|
|
@@ -965,12 +996,13 @@ export class FlexHarness<TScope = unknown> {
|
|
|
965
996
|
private async deleteSessionInternal(
|
|
966
997
|
state: IStorageState,
|
|
967
998
|
scopeId: string,
|
|
999
|
+
scope: TScope,
|
|
968
1000
|
sessionId: string,
|
|
969
1001
|
): Promise<void> {
|
|
970
1002
|
const existingTombstone = state.tombstones.get(sessionId);
|
|
971
1003
|
if (existingTombstone) {
|
|
972
1004
|
try {
|
|
973
|
-
await this.finishTombstoneCleanup(state, sessionId, scopeId);
|
|
1005
|
+
await this.finishTombstoneCleanup(state, sessionId, scopeId, scope);
|
|
974
1006
|
} catch (error) {
|
|
975
1007
|
throw this.projectOperationError(error, 'persistence', scopeId, sessionId, 'session-delete');
|
|
976
1008
|
}
|
|
@@ -993,7 +1025,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
993
1025
|
});
|
|
994
1026
|
});
|
|
995
1027
|
try {
|
|
996
|
-
await this.finishTombstoneCleanup(state, sessionId, scopeId);
|
|
1028
|
+
await this.finishTombstoneCleanup(state, sessionId, scopeId, scope);
|
|
997
1029
|
} catch (error) {
|
|
998
1030
|
throw this.projectOperationError(error, 'persistence', scopeId, sessionId, 'session-delete');
|
|
999
1031
|
}
|
|
@@ -1175,7 +1207,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1175
1207
|
): Promise<boolean> {
|
|
1176
1208
|
validateIdentifier(queueId, 'queueId');
|
|
1177
1209
|
requireTransferIdentifier(queueId, 'queueId');
|
|
1178
|
-
const { state } = await this.resolveState(scopeId);
|
|
1210
|
+
const { scope, state } = await this.resolveState(scopeId);
|
|
1179
1211
|
const stored = this.requireSession(state, sessionId);
|
|
1180
1212
|
const queued = stored.outstandingPromptsById.get(queueId);
|
|
1181
1213
|
if (!queued) {
|
|
@@ -1185,6 +1217,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1185
1217
|
return this.cancelQueuedPrompt(
|
|
1186
1218
|
queued,
|
|
1187
1219
|
this.trustInternalError(new FlexHarnessAbortError('The queued prompt was cancelled.')),
|
|
1220
|
+
this.createCompactorContext(scopeId, scope.scope, state.storageKey, sessionId),
|
|
1188
1221
|
);
|
|
1189
1222
|
}
|
|
1190
1223
|
|
|
@@ -1194,20 +1227,28 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1194
1227
|
scheduleKey: string,
|
|
1195
1228
|
): Promise<boolean> {
|
|
1196
1229
|
validateIdentifier(scheduleKey, 'scheduleKey');
|
|
1197
|
-
const { state } = await this.resolveState(scopeId);
|
|
1230
|
+
const { scope, state } = await this.resolveState(scopeId);
|
|
1198
1231
|
const stored = this.requireSession(state, sessionId);
|
|
1199
1232
|
const queued = [...stored.outstandingPromptsById.values()]
|
|
1200
1233
|
.find((entry) => entry.scheduleKey === scheduleKey);
|
|
1201
1234
|
if (!queued) return false;
|
|
1202
1235
|
const cancellation = this.trustInternalError(new FlexHarnessAbortError('The scheduled run was cancelled.'));
|
|
1203
|
-
return this.cancelQueuedPrompt(
|
|
1236
|
+
return this.cancelQueuedPrompt(
|
|
1237
|
+
queued,
|
|
1238
|
+
cancellation,
|
|
1239
|
+
this.createCompactorContext(scopeId, scope.scope, state.storageKey, sessionId),
|
|
1240
|
+
);
|
|
1204
1241
|
}
|
|
1205
1242
|
|
|
1206
1243
|
public async abort(scopeId: string, sessionId: string): Promise<boolean> {
|
|
1207
|
-
const { state } = await this.resolveState(scopeId);
|
|
1244
|
+
const { scope, state } = await this.resolveState(scopeId);
|
|
1208
1245
|
const run = state.activeRuns.get(sessionId);
|
|
1209
1246
|
if (!run || run.phase === 'finalizing' || run.phase === 'promoting') return false;
|
|
1210
|
-
this.cancelRun(
|
|
1247
|
+
this.cancelRun(
|
|
1248
|
+
run,
|
|
1249
|
+
this.trustInternalError(new FlexHarnessAbortError()),
|
|
1250
|
+
this.createCompactorContext(scopeId, scope.scope, state.storageKey, sessionId),
|
|
1251
|
+
);
|
|
1211
1252
|
return true;
|
|
1212
1253
|
}
|
|
1213
1254
|
|
|
@@ -1231,12 +1272,16 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1231
1272
|
decision: TFlexPermissionDecision,
|
|
1232
1273
|
): Promise<void> {
|
|
1233
1274
|
validateIdentifier(permissionId, 'permissionId');
|
|
1234
|
-
const { state } = await this.resolveState(scopeId);
|
|
1275
|
+
const { scope, state } = await this.resolveState(scopeId);
|
|
1235
1276
|
const pending = state.pendingPermissions.get(permissionId);
|
|
1236
1277
|
if (!pending || pending.request.sessionId !== sessionId) {
|
|
1237
1278
|
throw new FlexHarnessNotFoundError('Permission', permissionId);
|
|
1238
1279
|
}
|
|
1239
|
-
const
|
|
1280
|
+
const context = this.createCompactorContext(scopeId, scope.scope, state.storageKey, sessionId);
|
|
1281
|
+
const response = pending.responseQueue.then(() => this.withCompactorContext(
|
|
1282
|
+
context,
|
|
1283
|
+
() => this.applyPermissionResponse(state, pending, decision, context),
|
|
1284
|
+
));
|
|
1240
1285
|
pending.responseQueue = response.then(() => undefined, () => undefined);
|
|
1241
1286
|
return response;
|
|
1242
1287
|
}
|
|
@@ -1273,7 +1318,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1273
1318
|
if (!reconciliation || typeof reconciliation !== 'object' || Array.isArray(reconciliation)) {
|
|
1274
1319
|
throw new FlexHarnessValidationError('Tool execution reconciliation must be a plain object.');
|
|
1275
1320
|
}
|
|
1276
|
-
const { state } = await this.resolveState(scopeId);
|
|
1321
|
+
const { scope, state } = await this.resolveState(scopeId);
|
|
1277
1322
|
const stored = this.requireSession(state, sessionId);
|
|
1278
1323
|
this.assertStateAcceptingWork(state);
|
|
1279
1324
|
let canonical: plugins.TAgentToolExecutionReconciliationOptions;
|
|
@@ -1297,7 +1342,10 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1297
1342
|
throw new FlexHarnessValidationError('Unknown tool execution reconciliation.');
|
|
1298
1343
|
}
|
|
1299
1344
|
try {
|
|
1300
|
-
await
|
|
1345
|
+
await this.withCompactorContext(
|
|
1346
|
+
this.createCompactorContext(scopeId, scope.scope, state.storageKey, sessionId),
|
|
1347
|
+
() => stored.agentSession.reconcileToolExecution(intentId, canonical),
|
|
1348
|
+
);
|
|
1301
1349
|
} catch (error) {
|
|
1302
1350
|
throw this.projectOperationError(error, 'agentSession', scopeId, sessionId, 'tool-reconciliation');
|
|
1303
1351
|
}
|
|
@@ -1309,22 +1357,28 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1309
1357
|
event: IJsonObject & { type: string },
|
|
1310
1358
|
): Promise<void> {
|
|
1311
1359
|
this.validateRuntimeEvent(event);
|
|
1312
|
-
const { state } = await this.resolveState(scopeId);
|
|
1360
|
+
const { scope, state } = await this.resolveState(scopeId);
|
|
1313
1361
|
const stored = this.requireSession(state, sessionId);
|
|
1314
1362
|
this.assertStateAcceptingWork(state);
|
|
1315
1363
|
try {
|
|
1316
|
-
await
|
|
1364
|
+
await this.withCompactorContext(
|
|
1365
|
+
this.createCompactorContext(scopeId, scope.scope, state.storageKey, sessionId),
|
|
1366
|
+
() => stored.agentSession.pushRuntimeEvent(cloneSerializable(event)),
|
|
1367
|
+
);
|
|
1317
1368
|
} catch (error) {
|
|
1318
1369
|
throw this.projectOperationError(error, 'agentSession', scopeId, sessionId, 'runtime-event');
|
|
1319
1370
|
}
|
|
1320
1371
|
}
|
|
1321
1372
|
|
|
1322
1373
|
public async compactSession(scopeId: string, sessionId: string): Promise<void> {
|
|
1323
|
-
const { state } = await this.resolveState(scopeId);
|
|
1374
|
+
const { scope, state } = await this.resolveState(scopeId);
|
|
1324
1375
|
const stored = this.requireSession(state, sessionId);
|
|
1325
1376
|
this.assertStateAcceptingWork(state);
|
|
1326
1377
|
try {
|
|
1327
|
-
await
|
|
1378
|
+
await this.withCompactorContext(
|
|
1379
|
+
this.createCompactorContext(scopeId, scope.scope, state.storageKey, sessionId),
|
|
1380
|
+
() => stored.agentSession.compact(),
|
|
1381
|
+
);
|
|
1328
1382
|
} catch (error) {
|
|
1329
1383
|
throw this.projectOperationError(error, 'agentSession', scopeId, sessionId, 'compaction');
|
|
1330
1384
|
}
|
|
@@ -1401,11 +1455,14 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1401
1455
|
): Promise<void> {
|
|
1402
1456
|
validateIdentifier(executionId, 'executionId');
|
|
1403
1457
|
requireTransferIdentifier(executionId, 'executionId');
|
|
1404
|
-
const { state } = await this.resolveState(scopeId);
|
|
1458
|
+
const { scope, state } = await this.resolveState(scopeId);
|
|
1405
1459
|
const stored = this.requireSession(state, sessionId);
|
|
1406
1460
|
this.assertStateAcceptingWork(state);
|
|
1407
1461
|
try {
|
|
1408
|
-
await
|
|
1462
|
+
await this.withCompactorContext(
|
|
1463
|
+
this.createCompactorContext(scopeId, scope.scope, state.storageKey, sessionId),
|
|
1464
|
+
() => stored.agentSession.abortBackgroundExecution(executionId),
|
|
1465
|
+
);
|
|
1409
1466
|
} catch (error) {
|
|
1410
1467
|
throw this.projectOperationError(error, 'agentSession', scopeId, sessionId, 'background-abort');
|
|
1411
1468
|
}
|
|
@@ -1634,6 +1691,12 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1634
1691
|
return {
|
|
1635
1692
|
state: queued.state,
|
|
1636
1693
|
stored: queued.stored,
|
|
1694
|
+
originCompactorContext: this.createCompactorContext(
|
|
1695
|
+
queued.scopeId,
|
|
1696
|
+
queued.scope as TScope,
|
|
1697
|
+
queued.state.storageKey,
|
|
1698
|
+
queued.sessionId,
|
|
1699
|
+
),
|
|
1637
1700
|
scopeId: queued.scopeId,
|
|
1638
1701
|
scope: queued.scope,
|
|
1639
1702
|
sessionId: queued.sessionId,
|
|
@@ -1674,11 +1737,14 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1674
1737
|
const options = queued.options!;
|
|
1675
1738
|
let projectionReserved = false;
|
|
1676
1739
|
try {
|
|
1677
|
-
run.transaction = await
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1740
|
+
run.transaction = await this.withRunCompactorContext(
|
|
1741
|
+
run,
|
|
1742
|
+
() => run.stored.agentSession.beginGeneration(
|
|
1743
|
+
cloneSerializable(prompt.modelMessage.content) as Parameters<
|
|
1744
|
+
plugins.IAgentSession['beginGeneration']
|
|
1745
|
+
>[0],
|
|
1746
|
+
{ generationId: run.runId },
|
|
1747
|
+
),
|
|
1682
1748
|
);
|
|
1683
1749
|
this.assertPromptPromotion(queued, run);
|
|
1684
1750
|
const reservation = this.createReservation(run, prompt);
|
|
@@ -1757,13 +1823,13 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1757
1823
|
prepare: (context: { generationId: string; abortSignal: AbortSignal }) =>
|
|
1758
1824
|
this.prepareGeneration(run, options, context.abortSignal),
|
|
1759
1825
|
};
|
|
1760
|
-
const generation = run.scheduleKey
|
|
1826
|
+
const generation = this.withRunCompactorContext(run, () => run.scheduleKey
|
|
1761
1827
|
? run.stored.agentSession.scheduleGenerate({
|
|
1762
1828
|
key: run.scheduleKey,
|
|
1763
1829
|
debounceMs: run.debounceMs,
|
|
1764
1830
|
...generateOptions,
|
|
1765
1831
|
})
|
|
1766
|
-
: run.stored.agentSession.generate(generateOptions);
|
|
1832
|
+
: run.stored.agentSession.generate(generateOptions));
|
|
1767
1833
|
const execution = this.executeRun(run, generation).then(
|
|
1768
1834
|
(result) => this.finishQueuedPrompt(queued, 'completed', undefined, result),
|
|
1769
1835
|
(error) => this.finishQueuedPrompt(
|
|
@@ -1851,7 +1917,11 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1851
1917
|
}
|
|
1852
1918
|
}
|
|
1853
1919
|
|
|
1854
|
-
private cancelQueuedPrompt(
|
|
1920
|
+
private cancelQueuedPrompt(
|
|
1921
|
+
queued: IQueuedPrompt,
|
|
1922
|
+
reason: FlexHarnessAbortError,
|
|
1923
|
+
context?: IFlexAgentContextInvocation<TScope>,
|
|
1924
|
+
): boolean {
|
|
1855
1925
|
if (queued.stored.outstandingPromptsById.get(queued.queueId) !== queued) return false;
|
|
1856
1926
|
if (queued.status === 'queued') {
|
|
1857
1927
|
this.finishQueuedPrompt(queued, 'cancelled', reason);
|
|
@@ -1861,7 +1931,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1861
1931
|
const run = queued.state.activeRuns.get(queued.sessionId);
|
|
1862
1932
|
if (!run || run.queueId !== queued.queueId) return false;
|
|
1863
1933
|
if (run.phase === 'finalizing' || run.phase === 'promoting') return false;
|
|
1864
|
-
this.cancelRun(run, reason);
|
|
1934
|
+
this.cancelRun(run, reason, context);
|
|
1865
1935
|
return true;
|
|
1866
1936
|
}
|
|
1867
1937
|
|
|
@@ -1869,10 +1939,15 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1869
1939
|
stored: IStoredSessionState,
|
|
1870
1940
|
reason: FlexHarnessAbortError,
|
|
1871
1941
|
excludedQueueId?: string,
|
|
1942
|
+
context?: IFlexAgentContextInvocation<unknown>,
|
|
1872
1943
|
): void {
|
|
1873
1944
|
for (const queued of [...stored.outstandingPromptsById.values()]) {
|
|
1874
1945
|
if (queued.queueId === excludedQueueId) continue;
|
|
1875
|
-
this.cancelQueuedPrompt(
|
|
1946
|
+
this.cancelQueuedPrompt(
|
|
1947
|
+
queued,
|
|
1948
|
+
reason,
|
|
1949
|
+
context as IFlexAgentContextInvocation<TScope> | undefined,
|
|
1950
|
+
);
|
|
1876
1951
|
}
|
|
1877
1952
|
}
|
|
1878
1953
|
|
|
@@ -1906,7 +1981,10 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1906
1981
|
if (run.controller.signal.aborted) throw run.controller.signal.reason;
|
|
1907
1982
|
run.phase = 'finalizing';
|
|
1908
1983
|
try {
|
|
1909
|
-
await
|
|
1984
|
+
await this.withRunCompactorContext(
|
|
1985
|
+
run,
|
|
1986
|
+
() => run.stored.agentSession.finalizeGeneration(run.transaction!, 'accepted'),
|
|
1987
|
+
);
|
|
1910
1988
|
} catch (error) {
|
|
1911
1989
|
const projected = this.projectExternalError(run, error, 'agentSession');
|
|
1912
1990
|
await this.fenceNamespace(run.state, run, projected);
|
|
@@ -1976,7 +2054,10 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1976
2054
|
run.phase = 'finalizing';
|
|
1977
2055
|
const desiredOutcome: TCanonicalOutcome = cancelled || !generated ? 'interrupted' : 'rejected';
|
|
1978
2056
|
try {
|
|
1979
|
-
await
|
|
2057
|
+
await this.withRunCompactorContext(
|
|
2058
|
+
run,
|
|
2059
|
+
() => run.stored.agentSession.finalizeGeneration(run.transaction!, desiredOutcome),
|
|
2060
|
+
);
|
|
1980
2061
|
} catch (finalizationError) {
|
|
1981
2062
|
errors.push(this.projectExternalError(run, finalizationError, 'agentSession'));
|
|
1982
2063
|
}
|
|
@@ -2350,11 +2431,14 @@ export class FlexHarness<TScope = unknown> {
|
|
|
2350
2431
|
private emitTerminalProjection(run: IActiveRun, terminal: IFlexTerminalProjection): void {
|
|
2351
2432
|
for (const part of terminal.assistantMessage.parts) {
|
|
2352
2433
|
const callbackPart = run.callbackParts.find((entry) => entry.partId === part.partId);
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2434
|
+
const shouldComplete = part.type === 'text'
|
|
2435
|
+
? callbackPart?.type === 'text'
|
|
2436
|
+
: part.type === 'reasoning'
|
|
2437
|
+
? callbackPart?.type === 'reasoning' && callbackPart.status === 'running'
|
|
2438
|
+
: part.type === 'tool'
|
|
2439
|
+
? callbackPart?.type === 'tool' && callbackPart.status === 'running'
|
|
2440
|
+
: false;
|
|
2441
|
+
if (shouldComplete) {
|
|
2358
2442
|
this.emitPartEvent(run, 'part.completed', part);
|
|
2359
2443
|
}
|
|
2360
2444
|
}
|
|
@@ -2615,6 +2699,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
2615
2699
|
state: IStorageState,
|
|
2616
2700
|
pending: IPendingPermission,
|
|
2617
2701
|
decision: TFlexPermissionDecision,
|
|
2702
|
+
context: IFlexAgentContextInvocation<TScope>,
|
|
2618
2703
|
): Promise<void> {
|
|
2619
2704
|
if (pending.settled) {
|
|
2620
2705
|
throw new FlexHarnessPermissionStateError(`Permission "${pending.request.permissionId}" has already been resolved.`);
|
|
@@ -2712,7 +2797,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
2712
2797
|
const rejection = this.trustInternalError(
|
|
2713
2798
|
new FlexHarnessPermissionRejectedError(pending.request.permissionId),
|
|
2714
2799
|
);
|
|
2715
|
-
this.abortRunInternally(pending.run, rejection);
|
|
2800
|
+
this.abortRunInternally(pending.run, rejection, context);
|
|
2716
2801
|
pending.reject(rejection);
|
|
2717
2802
|
} else {
|
|
2718
2803
|
pending.resolve();
|
|
@@ -2816,19 +2901,50 @@ export class FlexHarness<TScope = unknown> {
|
|
|
2816
2901
|
pending.reject(error);
|
|
2817
2902
|
}
|
|
2818
2903
|
|
|
2819
|
-
private cancelRun(
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2904
|
+
private cancelRun(
|
|
2905
|
+
run: IActiveRun,
|
|
2906
|
+
reason: FlexHarnessAbortError,
|
|
2907
|
+
context?: IFlexAgentContextInvocation<TScope>,
|
|
2908
|
+
): void {
|
|
2909
|
+
const invocationContext = context ?? this.createRunCompactorContext(run);
|
|
2910
|
+
if (run.internalFailure === undefined && run.ownerCancellation === undefined) {
|
|
2911
|
+
run.ownerCancellation = reason;
|
|
2912
|
+
run.deferredCompactorContext = invocationContext;
|
|
2913
|
+
this.deferredCompactorContexts.set(run.originCompactorContext, invocationContext);
|
|
2914
|
+
}
|
|
2915
|
+
this.withCompactorContext(
|
|
2916
|
+
(run.deferredCompactorContext ?? invocationContext) as IFlexAgentContextInvocation<TScope>,
|
|
2917
|
+
() => {
|
|
2918
|
+
this.rejectRunPermissions(run.state, run, reason);
|
|
2919
|
+
if (!run.controller.signal.aborted) run.controller.abort(reason);
|
|
2920
|
+
if (run.phase === 'admitting') return;
|
|
2921
|
+
if (run.scheduleKey) {
|
|
2922
|
+
run.stored.agentSession.cancelScheduledGeneration(run.scheduleKey, reason);
|
|
2923
|
+
} else {
|
|
2924
|
+
run.stored.agentSession.abortCurrentGeneration(reason);
|
|
2925
|
+
}
|
|
2926
|
+
},
|
|
2927
|
+
);
|
|
2826
2928
|
}
|
|
2827
2929
|
|
|
2828
|
-
private abortRunInternally(
|
|
2829
|
-
run
|
|
2830
|
-
|
|
2831
|
-
|
|
2930
|
+
private abortRunInternally(
|
|
2931
|
+
run: IActiveRun,
|
|
2932
|
+
error: unknown,
|
|
2933
|
+
context?: IFlexAgentContextInvocation<TScope>,
|
|
2934
|
+
): void {
|
|
2935
|
+
const invocationContext = context ?? this.createRunCompactorContext(run);
|
|
2936
|
+
if (run.internalFailure === undefined) {
|
|
2937
|
+
run.internalFailure = error ?? new Error('Internal FlexHarness run failure.');
|
|
2938
|
+
run.deferredCompactorContext = invocationContext;
|
|
2939
|
+
this.deferredCompactorContexts.set(run.originCompactorContext, invocationContext);
|
|
2940
|
+
}
|
|
2941
|
+
this.withCompactorContext(
|
|
2942
|
+
(run.deferredCompactorContext ?? invocationContext) as IFlexAgentContextInvocation<TScope>,
|
|
2943
|
+
() => {
|
|
2944
|
+
if (!run.controller.signal.aborted) run.controller.abort(run.internalFailure);
|
|
2945
|
+
run.stored.agentSession.abortCurrentGeneration(run.internalFailure);
|
|
2946
|
+
},
|
|
2947
|
+
);
|
|
2832
2948
|
}
|
|
2833
2949
|
|
|
2834
2950
|
private async rollbackAdmission(
|
|
@@ -2839,7 +2955,10 @@ export class FlexHarness<TScope = unknown> {
|
|
|
2839
2955
|
const errors: unknown[] = [];
|
|
2840
2956
|
if (run.transaction) {
|
|
2841
2957
|
try {
|
|
2842
|
-
await
|
|
2958
|
+
await this.withRunCompactorContext(
|
|
2959
|
+
run,
|
|
2960
|
+
() => run.stored.agentSession.finalizeGeneration(run.transaction!, 'interrupted'),
|
|
2961
|
+
);
|
|
2843
2962
|
} catch (error) {
|
|
2844
2963
|
errors.push(this.projectExternalError(run, error, 'agentSession'));
|
|
2845
2964
|
}
|
|
@@ -3066,6 +3185,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3066
3185
|
storageKey: string,
|
|
3067
3186
|
scopeId: string,
|
|
3068
3187
|
scope: TScope,
|
|
3188
|
+
compactorLifecycleController: AbortController,
|
|
3069
3189
|
): Promise<IStorageState> {
|
|
3070
3190
|
const snapshot = cloneSerializable(await this.stores.scopes.load(storageKey) ?? {
|
|
3071
3191
|
schemaVersion: 1,
|
|
@@ -3075,6 +3195,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3075
3195
|
} satisfies IFlexScopeSnapshot);
|
|
3076
3196
|
const state: IStorageState = {
|
|
3077
3197
|
storageKey,
|
|
3198
|
+
compactorLifecycleController,
|
|
3078
3199
|
scopeIdHint: scopeId,
|
|
3079
3200
|
revision: snapshot.revision,
|
|
3080
3201
|
sessions: new Map(),
|
|
@@ -3162,6 +3283,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3162
3283
|
agentEventStoreReleased: true,
|
|
3163
3284
|
executionContextCloseCompleted: true,
|
|
3164
3285
|
jobStoreReleased: true,
|
|
3286
|
+
compactorLifecycleController: new AbortController(),
|
|
3165
3287
|
promptQueue: [],
|
|
3166
3288
|
outstandingPromptsById: new Map(),
|
|
3167
3289
|
terminalPromptQueueEntries: new Map(),
|
|
@@ -3176,6 +3298,12 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3176
3298
|
scope: TScope,
|
|
3177
3299
|
): Promise<IStoredSessionState> {
|
|
3178
3300
|
const sessionId = metadata.sessionId;
|
|
3301
|
+
const compactorContext = this.createCompactorContext(
|
|
3302
|
+
scopeId,
|
|
3303
|
+
scope,
|
|
3304
|
+
state.storageKey,
|
|
3305
|
+
sessionId,
|
|
3306
|
+
);
|
|
3179
3307
|
const [projectionResult, permissionResult, eventStoreResult, jobStoreResult] = await Promise.allSettled([
|
|
3180
3308
|
this.stores.projections.load(state.storageKey, sessionId),
|
|
3181
3309
|
this.stores.permissions.load(state.storageKey, sessionId),
|
|
@@ -3214,13 +3342,16 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3214
3342
|
let executionContextHandle: IFlexExecutionContextHandle | undefined;
|
|
3215
3343
|
try {
|
|
3216
3344
|
if (this.executionContextProvider) {
|
|
3217
|
-
executionContextHandle = await this.
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3345
|
+
executionContextHandle = await this.withCompactorContext(
|
|
3346
|
+
compactorContext,
|
|
3347
|
+
() => this.executionContextProvider!.provideExecutionContext({
|
|
3348
|
+
scopeId,
|
|
3349
|
+
scope,
|
|
3350
|
+
storageKey: state.storageKey,
|
|
3351
|
+
sessionId,
|
|
3352
|
+
jobStore,
|
|
3353
|
+
}),
|
|
3354
|
+
);
|
|
3224
3355
|
if (
|
|
3225
3356
|
executionContextHandle
|
|
3226
3357
|
&& (!executionContextHandle.context
|
|
@@ -3231,6 +3362,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3231
3362
|
}
|
|
3232
3363
|
const stored: IStoredSessionState = {
|
|
3233
3364
|
storageKey: state.storageKey,
|
|
3365
|
+
compactorContext,
|
|
3234
3366
|
session: cloneSerializable(metadata),
|
|
3235
3367
|
messages: cloneSerializable(projection?.messages ?? []),
|
|
3236
3368
|
stagedTerminals: cloneSerializable(projection?.stagedTerminals ?? []),
|
|
@@ -3248,21 +3380,75 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3248
3380
|
executionContextCloseCompleted: executionContextHandle?.close === undefined,
|
|
3249
3381
|
jobStoreReleased: this.stores.jobs.releaseSession === undefined,
|
|
3250
3382
|
jobs: executionContextHandle?.context.jobs,
|
|
3383
|
+
compactorLifecycleController: new AbortController(),
|
|
3251
3384
|
promptQueue: [],
|
|
3252
3385
|
outstandingPromptsById: new Map(),
|
|
3253
3386
|
terminalPromptQueueEntries: new Map(),
|
|
3254
3387
|
outstandingPromptBytes: 0,
|
|
3255
3388
|
};
|
|
3389
|
+
const {
|
|
3390
|
+
contextCompactor,
|
|
3391
|
+
...agentSessionPolicy
|
|
3392
|
+
} = this.agentSessionPolicy;
|
|
3393
|
+
const executionContext = executionContextHandle?.context;
|
|
3394
|
+
const jobContext = executionContext?.jobs;
|
|
3395
|
+
const contextualExecutionContext = executionContext && jobContext?.subscribe
|
|
3396
|
+
? {
|
|
3397
|
+
...executionContext,
|
|
3398
|
+
jobs: {
|
|
3399
|
+
start: jobContext.start.bind(jobContext),
|
|
3400
|
+
get: jobContext.get.bind(jobContext),
|
|
3401
|
+
list: jobContext.list.bind(jobContext),
|
|
3402
|
+
...(jobContext.abort ? { abort: jobContext.abort.bind(jobContext) } : {}),
|
|
3403
|
+
subscribe: (listener: Parameters<NonNullable<typeof jobContext.subscribe>>[0]) =>
|
|
3404
|
+
jobContext.subscribe!((event) =>
|
|
3405
|
+
this.withCompactorContext(compactorContext, () => listener(event))),
|
|
3406
|
+
},
|
|
3407
|
+
}
|
|
3408
|
+
: executionContext;
|
|
3256
3409
|
const agentSessionOptions: plugins.IAgentSessionOptions & {
|
|
3257
3410
|
transactionOutcomeErrorProjector: (error: unknown) => string;
|
|
3258
3411
|
} = {
|
|
3259
|
-
...
|
|
3412
|
+
...agentSessionPolicy,
|
|
3260
3413
|
sessionId,
|
|
3261
3414
|
eventStore,
|
|
3262
|
-
executionContext:
|
|
3415
|
+
executionContext: contextualExecutionContext,
|
|
3263
3416
|
contextBuilder: ({ events }) => hydrateAgentMessages(
|
|
3264
3417
|
(this.agentSessionPolicy.contextBuilder ?? ((options) => plugins.buildModelMessages(options.events)))({ events }),
|
|
3265
3418
|
),
|
|
3419
|
+
...(contextCompactor
|
|
3420
|
+
? {
|
|
3421
|
+
contextCompactor: async (messages, events, options) => {
|
|
3422
|
+
const ambientContext = this.compactorInvocationContext.getStore();
|
|
3423
|
+
const invocationContext = ambientContext
|
|
3424
|
+
? (this.deferredCompactorContexts.get(ambientContext) ?? ambientContext) as
|
|
3425
|
+
IFlexAgentContextInvocation<TScope>
|
|
3426
|
+
: undefined;
|
|
3427
|
+
if (
|
|
3428
|
+
!invocationContext
|
|
3429
|
+
|| invocationContext.storageKey !== state.storageKey
|
|
3430
|
+
|| invocationContext.sessionId !== sessionId
|
|
3431
|
+
) {
|
|
3432
|
+
throw new Error('Agent context compaction is missing its exact FlexHarness invocation context.');
|
|
3433
|
+
}
|
|
3434
|
+
return contextCompactor(messages, events, {
|
|
3435
|
+
...options,
|
|
3436
|
+
...invocationContext,
|
|
3437
|
+
scope: invocationContext.scope as TScope,
|
|
3438
|
+
abortSignal: options.abortSignal
|
|
3439
|
+
? AbortSignal.any([
|
|
3440
|
+
options.abortSignal,
|
|
3441
|
+
state.compactorLifecycleController.signal,
|
|
3442
|
+
stored.compactorLifecycleController.signal,
|
|
3443
|
+
])
|
|
3444
|
+
: AbortSignal.any([
|
|
3445
|
+
state.compactorLifecycleController.signal,
|
|
3446
|
+
stored.compactorLifecycleController.signal,
|
|
3447
|
+
]),
|
|
3448
|
+
});
|
|
3449
|
+
},
|
|
3450
|
+
}
|
|
3451
|
+
: {}),
|
|
3266
3452
|
transactionOutcomeErrorProjector: () => 'The model operation failed.',
|
|
3267
3453
|
onToken: (delta) => this.onTextDelta(state, sessionId, delta),
|
|
3268
3454
|
onReasoningStart: (id) => this.onReasoningStart(state, sessionId, id),
|
|
@@ -3271,7 +3457,10 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3271
3457
|
onToolCallStart: (event) => this.onToolStart(state, sessionId, event),
|
|
3272
3458
|
onToolCallFinish: (event) => this.onToolFinish(state, sessionId, event),
|
|
3273
3459
|
};
|
|
3274
|
-
stored.agentSession = await
|
|
3460
|
+
stored.agentSession = await this.withCompactorContext(
|
|
3461
|
+
compactorContext,
|
|
3462
|
+
() => plugins.AgentSession.create(agentSessionOptions),
|
|
3463
|
+
);
|
|
3275
3464
|
return stored;
|
|
3276
3465
|
} catch (error) {
|
|
3277
3466
|
const cleanupErrors: unknown[] = [];
|
|
@@ -3742,11 +3931,12 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3742
3931
|
state: IStorageState,
|
|
3743
3932
|
sessionId: string,
|
|
3744
3933
|
scopeId: string,
|
|
3934
|
+
scope?: TScope,
|
|
3745
3935
|
): Promise<void> {
|
|
3746
3936
|
const existing = state.tombstoneCleanups.get(sessionId);
|
|
3747
3937
|
if (existing) return existing;
|
|
3748
3938
|
let cleanup!: Promise<void>;
|
|
3749
|
-
cleanup = this.finishTombstoneCleanupInternal(state, sessionId, scopeId).finally(() => {
|
|
3939
|
+
cleanup = this.finishTombstoneCleanupInternal(state, sessionId, scopeId, scope).finally(() => {
|
|
3750
3940
|
if (state.tombstoneCleanups.get(sessionId) === cleanup) {
|
|
3751
3941
|
state.tombstoneCleanups.delete(sessionId);
|
|
3752
3942
|
}
|
|
@@ -3759,17 +3949,28 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3759
3949
|
state: IStorageState,
|
|
3760
3950
|
sessionId: string,
|
|
3761
3951
|
scopeId: string,
|
|
3952
|
+
scope?: TScope,
|
|
3762
3953
|
): Promise<void> {
|
|
3763
3954
|
const retained = state.retainedSessionCleanups.get(sessionId);
|
|
3764
3955
|
if (retained) {
|
|
3765
3956
|
const errors: unknown[] = [];
|
|
3766
3957
|
const reason = this.trustInternalError(new FlexHarnessAbortError('The session was deleted.'));
|
|
3767
|
-
|
|
3958
|
+
const invocation = scope === undefined
|
|
3959
|
+
? retained.stored.compactorContext
|
|
3960
|
+
: this.createCompactorContext(scopeId, scope, state.storageKey, sessionId);
|
|
3961
|
+
this.abortCompactorLifecycle(retained.stored, reason);
|
|
3962
|
+
this.cancelStoredPromptQueue(retained.stored, reason, undefined, invocation);
|
|
3768
3963
|
const run = state.activeRuns.get(sessionId);
|
|
3769
|
-
if (run) this.cancelRun(run, reason);
|
|
3964
|
+
if (run) this.cancelRun(run, reason, invocation as IFlexAgentContextInvocation<TScope>);
|
|
3965
|
+
if (run) {
|
|
3966
|
+
const settled = await Promise.allSettled([run.completion]);
|
|
3967
|
+
if (settled[0].status === 'rejected') {
|
|
3968
|
+
this.appendUnexpectedErrors(errors, settled[0].reason);
|
|
3969
|
+
}
|
|
3970
|
+
}
|
|
3770
3971
|
if (!retained.stored.agentSessionAbortCompleted) {
|
|
3771
3972
|
try {
|
|
3772
|
-
await this.abortStoredSession(retained.stored, reason);
|
|
3973
|
+
await this.abortStoredSession(retained.stored, reason, invocation);
|
|
3773
3974
|
} catch (error) {
|
|
3774
3975
|
errors.push(this.projectOperationError(
|
|
3775
3976
|
error,
|
|
@@ -3780,20 +3981,16 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3780
3981
|
));
|
|
3781
3982
|
}
|
|
3782
3983
|
}
|
|
3783
|
-
if (run) {
|
|
3784
|
-
const settled = await Promise.allSettled([run.completion]);
|
|
3785
|
-
if (settled[0].status === 'rejected' && !isAbortError(settled[0].reason)) {
|
|
3786
|
-
errors.push(settled[0].reason);
|
|
3787
|
-
}
|
|
3788
|
-
}
|
|
3789
3984
|
if (retained.stored.promptQueueDrain) {
|
|
3790
3985
|
const settled = await Promise.allSettled([retained.stored.promptQueueDrain]);
|
|
3791
|
-
if (settled[0].status === 'rejected')
|
|
3986
|
+
if (settled[0].status === 'rejected') {
|
|
3987
|
+
this.appendUnexpectedErrors(errors, settled[0].reason);
|
|
3988
|
+
}
|
|
3792
3989
|
}
|
|
3793
3990
|
try {
|
|
3794
|
-
await this.closeStoredSession(retained.stored);
|
|
3991
|
+
await this.closeStoredSession(retained.stored, invocation);
|
|
3795
3992
|
} catch (error) {
|
|
3796
|
-
|
|
3993
|
+
this.appendUnexpectedErrors(errors, this.projectOperationError(
|
|
3797
3994
|
error,
|
|
3798
3995
|
'toolCleanup',
|
|
3799
3996
|
scopeId,
|
|
@@ -3801,7 +3998,12 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3801
3998
|
'session-delete',
|
|
3802
3999
|
));
|
|
3803
4000
|
}
|
|
3804
|
-
if (errors.length > 0)
|
|
4001
|
+
if (errors.length > 0) {
|
|
4002
|
+
if (state.lifecycle === 'retired' && !this.storedSessionCleanupCompleted(retained.stored)) {
|
|
4003
|
+
this.orphanedStoredSessions.add(retained.stored);
|
|
4004
|
+
}
|
|
4005
|
+
throw combineErrors(errors);
|
|
4006
|
+
}
|
|
3805
4007
|
this.purgeStoredPromptQueue(retained.stored);
|
|
3806
4008
|
}
|
|
3807
4009
|
if (!retained?.domainsCompleted) {
|
|
@@ -3827,17 +4029,33 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3827
4029
|
state.lifecycle = 'fenced';
|
|
3828
4030
|
const detachedCleanupAttempts = this.beginDetachedCleanupSettlement(state);
|
|
3829
4031
|
const reason = this.trustInternalError(new FlexHarnessAbortError('The session namespace was fenced.'));
|
|
4032
|
+
if (!state.compactorLifecycleController.signal.aborted) {
|
|
4033
|
+
state.compactorLifecycleController.abort(reason);
|
|
4034
|
+
}
|
|
3830
4035
|
const cleanupErrors: unknown[] = [];
|
|
4036
|
+
const ambientContext = this.compactorInvocationContext.getStore();
|
|
4037
|
+
const invocation = ambientContext?.storageKey === state.storageKey
|
|
4038
|
+
&& ambientContext.sessionId === currentRun.sessionId
|
|
4039
|
+
? ambientContext
|
|
4040
|
+
: this.effectiveRunCompactorContext(currentRun);
|
|
4041
|
+
const contextFor = (sessionId: string) => this.createCompactorContext(
|
|
4042
|
+
invocation.scopeId,
|
|
4043
|
+
invocation.scope,
|
|
4044
|
+
state.storageKey,
|
|
4045
|
+
sessionId,
|
|
4046
|
+
);
|
|
3831
4047
|
try {
|
|
3832
4048
|
for (const stored of state.sessions.values()) {
|
|
4049
|
+
this.abortCompactorLifecycle(stored, reason);
|
|
3833
4050
|
this.cancelStoredPromptQueue(
|
|
3834
4051
|
stored,
|
|
3835
4052
|
reason,
|
|
3836
4053
|
stored === currentRun.stored ? currentRun.queueId : undefined,
|
|
4054
|
+
contextFor(stored.session.sessionId),
|
|
3837
4055
|
);
|
|
3838
4056
|
}
|
|
3839
4057
|
const otherRuns = [...state.activeRuns.values()].filter((run) => run !== currentRun);
|
|
3840
|
-
for (const run of otherRuns) this.cancelRun(run, reason);
|
|
4058
|
+
for (const run of otherRuns) this.cancelRun(run, reason, contextFor(run.sessionId));
|
|
3841
4059
|
const runResults = await Promise.allSettled(otherRuns.map((run) => run.completion));
|
|
3842
4060
|
for (const result of runResults) {
|
|
3843
4061
|
if (result.status === 'rejected') this.appendUnexpectedErrors(cleanupErrors, result.reason);
|
|
@@ -3845,7 +4063,16 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3845
4063
|
await Promise.allSettled([...state.sessionInitializations.values()]);
|
|
3846
4064
|
cleanupErrors.push(...await this.closeOrphanedResources(state.storageKey));
|
|
3847
4065
|
await state.scopeQueue;
|
|
3848
|
-
const
|
|
4066
|
+
const currentTombstoneCleanup = state.tombstoneCleanups.get(currentRun.sessionId);
|
|
4067
|
+
if (currentTombstoneCleanup) {
|
|
4068
|
+
this.retainOrphanedTombstoneCleanup(
|
|
4069
|
+
state.storageKey,
|
|
4070
|
+
currentRun.sessionId,
|
|
4071
|
+
currentTombstoneCleanup,
|
|
4072
|
+
);
|
|
4073
|
+
}
|
|
4074
|
+
const tombstoneAttempts = [...state.tombstoneCleanups.entries()]
|
|
4075
|
+
.filter(([sessionId]) => sessionId !== currentRun.sessionId);
|
|
3849
4076
|
const tombstoneResults = await Promise.allSettled(
|
|
3850
4077
|
tombstoneAttempts.map(([, cleanup]) => cleanup),
|
|
3851
4078
|
);
|
|
@@ -3856,12 +4083,13 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3856
4083
|
const storedSessions = new Set([
|
|
3857
4084
|
...state.sessions.values(),
|
|
3858
4085
|
...[...state.retainedSessionCleanups]
|
|
3859
|
-
.filter(([sessionId]) =>
|
|
4086
|
+
.filter(([sessionId]) =>
|
|
4087
|
+
sessionId !== currentRun.sessionId && !attemptedTombstones.has(sessionId))
|
|
3860
4088
|
.map(([, retained]) => retained.stored),
|
|
3861
4089
|
]);
|
|
3862
4090
|
for (const stored of storedSessions) {
|
|
3863
4091
|
try {
|
|
3864
|
-
await this.abortStoredSession(stored, reason);
|
|
4092
|
+
await this.abortStoredSession(stored, reason, contextFor(stored.session.sessionId));
|
|
3865
4093
|
} catch (error) {
|
|
3866
4094
|
cleanupErrors.push(this.projectOperationError(
|
|
3867
4095
|
error,
|
|
@@ -3872,7 +4100,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3872
4100
|
));
|
|
3873
4101
|
}
|
|
3874
4102
|
try {
|
|
3875
|
-
await this.closeStoredSession(stored);
|
|
4103
|
+
await this.closeStoredSession(stored, contextFor(stored.session.sessionId));
|
|
3876
4104
|
} catch (error) {
|
|
3877
4105
|
if (!this.storedSessionCleanupCompleted(stored)) {
|
|
3878
4106
|
cleanupErrors.push(this.projectOperationError(
|
|
@@ -3891,24 +4119,41 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3891
4119
|
if (cleanupErrors.length > 0) throw combineErrors([cause, ...cleanupErrors]);
|
|
3892
4120
|
state.lifecycle = 'retired';
|
|
3893
4121
|
state.sessions.clear();
|
|
4122
|
+
const currentRetainedCleanup = state.retainedSessionCleanups.get(currentRun.sessionId);
|
|
3894
4123
|
state.retainedSessionCleanups.clear();
|
|
4124
|
+
if (currentRetainedCleanup) {
|
|
4125
|
+
state.retainedSessionCleanups.set(currentRun.sessionId, currentRetainedCleanup);
|
|
4126
|
+
}
|
|
3895
4127
|
state.sessionDeletions.clear();
|
|
3896
4128
|
state.tombstoneCleanups.clear();
|
|
4129
|
+
if (currentTombstoneCleanup) {
|
|
4130
|
+
state.tombstoneCleanups.set(currentRun.sessionId, currentTombstoneCleanup);
|
|
4131
|
+
}
|
|
3897
4132
|
state.activeRuns.clear();
|
|
3898
4133
|
state.pendingPermissions.clear();
|
|
3899
4134
|
state.initializingSessions.clear();
|
|
3900
4135
|
state.sessionInitializations.clear();
|
|
3901
4136
|
this.stateLoads.delete(state.storageKey);
|
|
4137
|
+
if (
|
|
4138
|
+
this.storageCompactorLifecycleControllers.get(state.storageKey)
|
|
4139
|
+
=== state.compactorLifecycleController
|
|
4140
|
+
) this.storageCompactorLifecycleControllers.delete(state.storageKey);
|
|
3902
4141
|
} finally {
|
|
3903
4142
|
state.fenceInProgress = false;
|
|
3904
4143
|
}
|
|
3905
4144
|
}
|
|
3906
4145
|
|
|
3907
|
-
private async closeStoredSession(
|
|
4146
|
+
private async closeStoredSession(
|
|
4147
|
+
stored: IStoredSessionState,
|
|
4148
|
+
context: IFlexAgentContextInvocation<unknown> = stored.compactorContext!,
|
|
4149
|
+
): Promise<void> {
|
|
3908
4150
|
const errors: unknown[] = [];
|
|
3909
4151
|
if (!stored.agentSessionCloseCompleted) {
|
|
3910
4152
|
try {
|
|
3911
|
-
await
|
|
4153
|
+
await this.withCompactorContext(
|
|
4154
|
+
context,
|
|
4155
|
+
() => stored.agentSession.close(),
|
|
4156
|
+
);
|
|
3912
4157
|
if (!stored.agentSession.closeCleanupCompleted) {
|
|
3913
4158
|
throw new Error('AgentSession.close() resolved before cleanup ownership was released.');
|
|
3914
4159
|
}
|
|
@@ -3958,9 +4203,13 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3958
4203
|
private async abortStoredSession(
|
|
3959
4204
|
stored: IStoredSessionState,
|
|
3960
4205
|
reason: FlexHarnessAbortError,
|
|
4206
|
+
context: IFlexAgentContextInvocation<unknown> = stored.compactorContext!,
|
|
3961
4207
|
): Promise<void> {
|
|
3962
4208
|
if (stored.agentSessionAbortCompleted) return;
|
|
3963
|
-
await
|
|
4209
|
+
await this.withCompactorContext(
|
|
4210
|
+
context,
|
|
4211
|
+
() => stored.agentSession.abortSession(reason, { abortBackgroundJobs: true }),
|
|
4212
|
+
);
|
|
3964
4213
|
stored.agentSessionAbortCompleted = true;
|
|
3965
4214
|
}
|
|
3966
4215
|
|
|
@@ -4016,6 +4265,29 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4016
4265
|
retained.storageKey === storageKey && retained.sessionId === sessionId);
|
|
4017
4266
|
}
|
|
4018
4267
|
|
|
4268
|
+
private retainOrphanedTombstoneCleanup(
|
|
4269
|
+
storageKey: string,
|
|
4270
|
+
sessionId: string,
|
|
4271
|
+
completion: Promise<void>,
|
|
4272
|
+
): void {
|
|
4273
|
+
const key = JSON.stringify([storageKey, sessionId]);
|
|
4274
|
+
if (this.orphanedTombstoneCleanups.has(key)) return;
|
|
4275
|
+
const retained: IOrphanedTombstoneCleanup = { storageKey, sessionId, completion };
|
|
4276
|
+
this.orphanedTombstoneCleanups.set(key, retained);
|
|
4277
|
+
void completion.then(
|
|
4278
|
+
() => {
|
|
4279
|
+
if (this.orphanedTombstoneCleanups.get(key) === retained) {
|
|
4280
|
+
this.orphanedTombstoneCleanups.delete(key);
|
|
4281
|
+
}
|
|
4282
|
+
},
|
|
4283
|
+
() => {
|
|
4284
|
+
if (this.orphanedTombstoneCleanups.get(key) === retained) {
|
|
4285
|
+
this.orphanedTombstoneCleanups.delete(key);
|
|
4286
|
+
}
|
|
4287
|
+
},
|
|
4288
|
+
);
|
|
4289
|
+
}
|
|
4290
|
+
|
|
4019
4291
|
private closeOrphanedResources(storageKey?: string): Promise<unknown[]> {
|
|
4020
4292
|
const operation = this.orphanedResourceQueue.then(() =>
|
|
4021
4293
|
this.closeOrphanedResourcesInternal(storageKey));
|
|
@@ -4025,6 +4297,14 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4025
4297
|
|
|
4026
4298
|
private async closeOrphanedResourcesInternal(storageKey?: string): Promise<unknown[]> {
|
|
4027
4299
|
const errors: unknown[] = [];
|
|
4300
|
+
const tombstoneCleanups = [...this.orphanedTombstoneCleanups.values()]
|
|
4301
|
+
.filter((retained) => storageKey === undefined || retained.storageKey === storageKey);
|
|
4302
|
+
const tombstoneResults = await Promise.allSettled(
|
|
4303
|
+
tombstoneCleanups.map((retained) => retained.completion),
|
|
4304
|
+
);
|
|
4305
|
+
for (const result of tombstoneResults) {
|
|
4306
|
+
if (result.status === 'rejected') this.appendUnexpectedErrors(errors, result.reason);
|
|
4307
|
+
}
|
|
4028
4308
|
for (const stored of [...this.orphanedStoredSessions]) {
|
|
4029
4309
|
if (storageKey !== undefined && stored.storageKey !== storageKey) continue;
|
|
4030
4310
|
try {
|
|
@@ -4065,6 +4345,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4065
4345
|
...[...this.orphanedStoredSessions].map((stored) => stored.storageKey),
|
|
4066
4346
|
...[...this.orphanedExecutionContexts.values()].map((owner) => owner.storageKey),
|
|
4067
4347
|
...[...this.orphanedProviderReleases.values()].map((retained) => retained.storageKey),
|
|
4348
|
+
...[...this.orphanedTombstoneCleanups.values()].map((retained) => retained.storageKey),
|
|
4068
4349
|
]);
|
|
4069
4350
|
}
|
|
4070
4351
|
|
|
@@ -4087,7 +4368,12 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4087
4368
|
}
|
|
4088
4369
|
} else {
|
|
4089
4370
|
try {
|
|
4090
|
-
await this.drainStorage(
|
|
4371
|
+
await this.drainStorage(
|
|
4372
|
+
scope.storageKey,
|
|
4373
|
+
stateLoad,
|
|
4374
|
+
this.createScopeRetirementError(),
|
|
4375
|
+
{ scopeId, scope: scope.scope },
|
|
4376
|
+
);
|
|
4091
4377
|
} catch (error) {
|
|
4092
4378
|
this.appendUnexpectedErrors(errors, error);
|
|
4093
4379
|
}
|
|
@@ -4099,15 +4385,25 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4099
4385
|
storageKey: string,
|
|
4100
4386
|
stateLoad: Promise<IStorageState>,
|
|
4101
4387
|
reason: FlexHarnessAbortError,
|
|
4388
|
+
invocation?: { scopeId: string; scope: TScope },
|
|
4102
4389
|
): Promise<void> {
|
|
4390
|
+
const compactorController = this.storageCompactorLifecycleControllers.get(storageKey);
|
|
4391
|
+
if (compactorController && !compactorController.signal.aborted) {
|
|
4392
|
+
compactorController.abort(reason);
|
|
4393
|
+
}
|
|
4103
4394
|
const existing = this.storageDrains.get(storageKey);
|
|
4104
4395
|
if (existing) return existing;
|
|
4105
4396
|
let completed = false;
|
|
4106
4397
|
let drain!: Promise<void>;
|
|
4107
|
-
drain = this.drainStorageInternal(storageKey, stateLoad, reason).then(() => {
|
|
4398
|
+
drain = this.drainStorageInternal(storageKey, stateLoad, reason, invocation).then(() => {
|
|
4108
4399
|
completed = true;
|
|
4109
4400
|
}).finally(() => {
|
|
4110
|
-
if (completed && this.stateLoads.get(storageKey) === stateLoad)
|
|
4401
|
+
if (completed && this.stateLoads.get(storageKey) === stateLoad) {
|
|
4402
|
+
this.stateLoads.delete(storageKey);
|
|
4403
|
+
if (this.storageCompactorLifecycleControllers.get(storageKey) === compactorController) {
|
|
4404
|
+
this.storageCompactorLifecycleControllers.delete(storageKey);
|
|
4405
|
+
}
|
|
4406
|
+
}
|
|
4111
4407
|
if (this.storageDrains.get(storageKey) === drain) this.storageDrains.delete(storageKey);
|
|
4112
4408
|
});
|
|
4113
4409
|
this.storageDrains.set(storageKey, drain);
|
|
@@ -4118,6 +4414,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4118
4414
|
storageKey: string,
|
|
4119
4415
|
stateLoad: Promise<IStorageState>,
|
|
4120
4416
|
reason: FlexHarnessAbortError,
|
|
4417
|
+
invocation?: { scopeId: string; scope: TScope },
|
|
4121
4418
|
): Promise<void> {
|
|
4122
4419
|
let state: IStorageState;
|
|
4123
4420
|
try {
|
|
@@ -4130,14 +4427,35 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4130
4427
|
if (state.lifecycle !== 'fenced') state.lifecycle = 'retiring';
|
|
4131
4428
|
const detachedCleanupAttempts = this.beginDetachedCleanupSettlement(state);
|
|
4132
4429
|
const errors: unknown[] = [];
|
|
4133
|
-
|
|
4430
|
+
const contextFor = (sessionId: string) => invocation
|
|
4431
|
+
? this.createCompactorContext(invocation.scopeId, invocation.scope, storageKey, sessionId)
|
|
4432
|
+
: undefined;
|
|
4433
|
+
for (const stored of state.sessions.values()) {
|
|
4434
|
+
this.abortCompactorLifecycle(stored, reason);
|
|
4435
|
+
this.cancelStoredPromptQueue(
|
|
4436
|
+
stored,
|
|
4437
|
+
reason,
|
|
4438
|
+
undefined,
|
|
4439
|
+
contextFor(stored.session.sessionId),
|
|
4440
|
+
);
|
|
4441
|
+
}
|
|
4134
4442
|
for (const run of state.activeRuns.values()) {
|
|
4135
|
-
if (run.phase !== 'finalizing' && run.phase !== 'promoting')
|
|
4443
|
+
if (run.phase !== 'finalizing' && run.phase !== 'promoting') {
|
|
4444
|
+
this.cancelRun(run, reason, contextFor(run.sessionId));
|
|
4445
|
+
}
|
|
4136
4446
|
}
|
|
4137
4447
|
for (const pending of [...state.pendingPermissions.values()]) this.rejectPending(state, pending, reason);
|
|
4448
|
+
const runResults = await Promise.allSettled([...state.activeRuns.values()].map((run) => run.completion));
|
|
4449
|
+
for (const result of runResults) {
|
|
4450
|
+
if (result.status === 'rejected') this.appendUnexpectedErrors(errors, result.reason);
|
|
4451
|
+
}
|
|
4138
4452
|
for (const stored of state.sessions.values()) {
|
|
4139
4453
|
try {
|
|
4140
|
-
await this.abortStoredSession(
|
|
4454
|
+
await this.abortStoredSession(
|
|
4455
|
+
stored,
|
|
4456
|
+
reason,
|
|
4457
|
+
contextFor(stored.session.sessionId),
|
|
4458
|
+
);
|
|
4141
4459
|
} catch (error) {
|
|
4142
4460
|
errors.push(this.projectOperationError(
|
|
4143
4461
|
error,
|
|
@@ -4148,10 +4466,6 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4148
4466
|
));
|
|
4149
4467
|
}
|
|
4150
4468
|
}
|
|
4151
|
-
const runResults = await Promise.allSettled([...state.activeRuns.values()].map((run) => run.completion));
|
|
4152
|
-
for (const result of runResults) {
|
|
4153
|
-
if (result.status === 'rejected') this.appendUnexpectedErrors(errors, result.reason);
|
|
4154
|
-
}
|
|
4155
4469
|
await Promise.allSettled([...state.sessionInitializations.values()]);
|
|
4156
4470
|
errors.push(...await this.closeOrphanedResources(state.storageKey));
|
|
4157
4471
|
await state.scopeQueue;
|
|
@@ -4171,7 +4485,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4171
4485
|
errors.push(...await this.settleDetachedCleanups(state, detachedCleanupAttempts));
|
|
4172
4486
|
for (const stored of state.sessions.values()) {
|
|
4173
4487
|
try {
|
|
4174
|
-
await this.closeStoredSession(stored);
|
|
4488
|
+
await this.closeStoredSession(stored, contextFor(stored.session.sessionId));
|
|
4175
4489
|
} catch (error) {
|
|
4176
4490
|
this.appendUnexpectedErrors(errors, this.projectOperationError(
|
|
4177
4491
|
error,
|
|
@@ -4190,7 +4504,8 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4190
4504
|
await this.finishTombstoneCleanup(
|
|
4191
4505
|
state,
|
|
4192
4506
|
sessionId,
|
|
4193
|
-
retained?.stored.session.scopeId ?? state.scopeIdHint,
|
|
4507
|
+
invocation?.scopeId ?? retained?.stored.session.scopeId ?? state.scopeIdHint,
|
|
4508
|
+
invocation?.scope,
|
|
4194
4509
|
);
|
|
4195
4510
|
} catch (error) {
|
|
4196
4511
|
this.appendUnexpectedErrors(errors, error);
|
|
@@ -4238,10 +4553,12 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4238
4553
|
}
|
|
4239
4554
|
this.listeners.clear();
|
|
4240
4555
|
if (errors.length > 0) throw combineErrors(errors);
|
|
4556
|
+
this.compactorInvocationContext.disable();
|
|
4241
4557
|
this.stateLoads.clear();
|
|
4242
4558
|
this.scopeAdmissions.clear();
|
|
4243
4559
|
this.scopeRetirements.clear();
|
|
4244
4560
|
this.storageDrains.clear();
|
|
4561
|
+
this.storageCompactorLifecycleControllers.clear();
|
|
4245
4562
|
}
|
|
4246
4563
|
|
|
4247
4564
|
private appendUnexpectedErrors(target: unknown[], error: unknown): void {
|
|
@@ -4250,6 +4567,12 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4250
4567
|
} else if (!isAbortError(error)) target.push(error);
|
|
4251
4568
|
}
|
|
4252
4569
|
|
|
4570
|
+
private abortCompactorLifecycle(stored: IStoredSessionState, reason: FlexHarnessAbortError): void {
|
|
4571
|
+
if (!stored.compactorLifecycleController.signal.aborted) {
|
|
4572
|
+
stored.compactorLifecycleController.abort(reason);
|
|
4573
|
+
}
|
|
4574
|
+
}
|
|
4575
|
+
|
|
4253
4576
|
private async resolveState(
|
|
4254
4577
|
scopeId: string,
|
|
4255
4578
|
): Promise<{ scope: IFlexResolvedScope<TScope>; state: IStorageState }> {
|
|
@@ -4277,10 +4600,26 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4277
4600
|
) throw this.createScopeRetirementError();
|
|
4278
4601
|
stateLoad = this.stateLoads.get(scope.storageKey);
|
|
4279
4602
|
if (!stateLoad) {
|
|
4280
|
-
|
|
4603
|
+
const compactorLifecycleController = new AbortController();
|
|
4604
|
+
this.storageCompactorLifecycleControllers.set(
|
|
4605
|
+
scope.storageKey,
|
|
4606
|
+
compactorLifecycleController,
|
|
4607
|
+
);
|
|
4608
|
+
stateLoad = this.loadState(
|
|
4609
|
+
scope.storageKey,
|
|
4610
|
+
scopeId,
|
|
4611
|
+
scope.scope,
|
|
4612
|
+
compactorLifecycleController,
|
|
4613
|
+
);
|
|
4281
4614
|
this.stateLoads.set(scope.storageKey, stateLoad);
|
|
4282
4615
|
void stateLoad.catch(() => {
|
|
4283
|
-
if (this.stateLoads.get(scope.storageKey) === stateLoad)
|
|
4616
|
+
if (this.stateLoads.get(scope.storageKey) === stateLoad) {
|
|
4617
|
+
this.stateLoads.delete(scope.storageKey);
|
|
4618
|
+
if (
|
|
4619
|
+
this.storageCompactorLifecycleControllers.get(scope.storageKey)
|
|
4620
|
+
=== compactorLifecycleController
|
|
4621
|
+
) this.storageCompactorLifecycleControllers.delete(scope.storageKey);
|
|
4622
|
+
}
|
|
4284
4623
|
});
|
|
4285
4624
|
}
|
|
4286
4625
|
}
|
|
@@ -4326,6 +4665,37 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4326
4665
|
}
|
|
4327
4666
|
}
|
|
4328
4667
|
|
|
4668
|
+
private createCompactorContext(
|
|
4669
|
+
scopeId: string,
|
|
4670
|
+
scope: TScope,
|
|
4671
|
+
storageKey: string,
|
|
4672
|
+
sessionId: string,
|
|
4673
|
+
): IFlexAgentContextInvocation<TScope> {
|
|
4674
|
+
return Object.freeze({ scopeId, scope, storageKey, sessionId });
|
|
4675
|
+
}
|
|
4676
|
+
|
|
4677
|
+
private withCompactorContext<TResult>(
|
|
4678
|
+
context: IFlexAgentContextInvocation<unknown>,
|
|
4679
|
+
operation: () => TResult,
|
|
4680
|
+
): TResult {
|
|
4681
|
+
return this.compactorInvocationContext.run(
|
|
4682
|
+
context as IFlexAgentContextInvocation<TScope>,
|
|
4683
|
+
operation,
|
|
4684
|
+
);
|
|
4685
|
+
}
|
|
4686
|
+
|
|
4687
|
+
private createRunCompactorContext(run: IActiveRun): IFlexAgentContextInvocation<TScope> {
|
|
4688
|
+
return run.originCompactorContext as IFlexAgentContextInvocation<TScope>;
|
|
4689
|
+
}
|
|
4690
|
+
|
|
4691
|
+
private effectiveRunCompactorContext(run: IActiveRun): IFlexAgentContextInvocation<TScope> {
|
|
4692
|
+
return (run.deferredCompactorContext ?? run.originCompactorContext) as IFlexAgentContextInvocation<TScope>;
|
|
4693
|
+
}
|
|
4694
|
+
|
|
4695
|
+
private withRunCompactorContext<TResult>(run: IActiveRun, operation: () => TResult): TResult {
|
|
4696
|
+
return this.withCompactorContext(this.effectiveRunCompactorContext(run), operation);
|
|
4697
|
+
}
|
|
4698
|
+
|
|
4329
4699
|
private requireSession(state: IStorageState, sessionId: string): IStoredSessionState {
|
|
4330
4700
|
validateIdentifier(sessionId, 'sessionId');
|
|
4331
4701
|
requireTransferIdentifier(sessionId, 'sessionId');
|