@modelprofile.com/flexharness 3.3.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 +10 -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 +284 -108
- 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 +2 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.flexharness.ts +466 -98
- 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.
|
|
@@ -950,12 +979,12 @@ export class FlexHarness<TScope = unknown> {
|
|
|
950
979
|
}
|
|
951
980
|
|
|
952
981
|
public async deleteSession(scopeId: string, sessionId: string): Promise<void> {
|
|
953
|
-
const { state } = await this.resolveState(scopeId);
|
|
982
|
+
const { scope, state } = await this.resolveState(scopeId);
|
|
954
983
|
validateIdentifier(sessionId, 'sessionId');
|
|
955
984
|
const existingDeletion = state.sessionDeletions.get(sessionId);
|
|
956
985
|
if (existingDeletion) return existingDeletion;
|
|
957
986
|
let deletion!: Promise<void>;
|
|
958
|
-
deletion = this.deleteSessionInternal(state, scopeId, sessionId).finally(() => {
|
|
987
|
+
deletion = this.deleteSessionInternal(state, scopeId, scope.scope, sessionId).finally(() => {
|
|
959
988
|
if (state.sessionDeletions.get(sessionId) === deletion) {
|
|
960
989
|
state.sessionDeletions.delete(sessionId);
|
|
961
990
|
}
|
|
@@ -967,12 +996,13 @@ export class FlexHarness<TScope = unknown> {
|
|
|
967
996
|
private async deleteSessionInternal(
|
|
968
997
|
state: IStorageState,
|
|
969
998
|
scopeId: string,
|
|
999
|
+
scope: TScope,
|
|
970
1000
|
sessionId: string,
|
|
971
1001
|
): Promise<void> {
|
|
972
1002
|
const existingTombstone = state.tombstones.get(sessionId);
|
|
973
1003
|
if (existingTombstone) {
|
|
974
1004
|
try {
|
|
975
|
-
await this.finishTombstoneCleanup(state, sessionId, scopeId);
|
|
1005
|
+
await this.finishTombstoneCleanup(state, sessionId, scopeId, scope);
|
|
976
1006
|
} catch (error) {
|
|
977
1007
|
throw this.projectOperationError(error, 'persistence', scopeId, sessionId, 'session-delete');
|
|
978
1008
|
}
|
|
@@ -995,7 +1025,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
995
1025
|
});
|
|
996
1026
|
});
|
|
997
1027
|
try {
|
|
998
|
-
await this.finishTombstoneCleanup(state, sessionId, scopeId);
|
|
1028
|
+
await this.finishTombstoneCleanup(state, sessionId, scopeId, scope);
|
|
999
1029
|
} catch (error) {
|
|
1000
1030
|
throw this.projectOperationError(error, 'persistence', scopeId, sessionId, 'session-delete');
|
|
1001
1031
|
}
|
|
@@ -1177,7 +1207,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1177
1207
|
): Promise<boolean> {
|
|
1178
1208
|
validateIdentifier(queueId, 'queueId');
|
|
1179
1209
|
requireTransferIdentifier(queueId, 'queueId');
|
|
1180
|
-
const { state } = await this.resolveState(scopeId);
|
|
1210
|
+
const { scope, state } = await this.resolveState(scopeId);
|
|
1181
1211
|
const stored = this.requireSession(state, sessionId);
|
|
1182
1212
|
const queued = stored.outstandingPromptsById.get(queueId);
|
|
1183
1213
|
if (!queued) {
|
|
@@ -1187,6 +1217,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1187
1217
|
return this.cancelQueuedPrompt(
|
|
1188
1218
|
queued,
|
|
1189
1219
|
this.trustInternalError(new FlexHarnessAbortError('The queued prompt was cancelled.')),
|
|
1220
|
+
this.createCompactorContext(scopeId, scope.scope, state.storageKey, sessionId),
|
|
1190
1221
|
);
|
|
1191
1222
|
}
|
|
1192
1223
|
|
|
@@ -1196,20 +1227,28 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1196
1227
|
scheduleKey: string,
|
|
1197
1228
|
): Promise<boolean> {
|
|
1198
1229
|
validateIdentifier(scheduleKey, 'scheduleKey');
|
|
1199
|
-
const { state } = await this.resolveState(scopeId);
|
|
1230
|
+
const { scope, state } = await this.resolveState(scopeId);
|
|
1200
1231
|
const stored = this.requireSession(state, sessionId);
|
|
1201
1232
|
const queued = [...stored.outstandingPromptsById.values()]
|
|
1202
1233
|
.find((entry) => entry.scheduleKey === scheduleKey);
|
|
1203
1234
|
if (!queued) return false;
|
|
1204
1235
|
const cancellation = this.trustInternalError(new FlexHarnessAbortError('The scheduled run was cancelled.'));
|
|
1205
|
-
return this.cancelQueuedPrompt(
|
|
1236
|
+
return this.cancelQueuedPrompt(
|
|
1237
|
+
queued,
|
|
1238
|
+
cancellation,
|
|
1239
|
+
this.createCompactorContext(scopeId, scope.scope, state.storageKey, sessionId),
|
|
1240
|
+
);
|
|
1206
1241
|
}
|
|
1207
1242
|
|
|
1208
1243
|
public async abort(scopeId: string, sessionId: string): Promise<boolean> {
|
|
1209
|
-
const { state } = await this.resolveState(scopeId);
|
|
1244
|
+
const { scope, state } = await this.resolveState(scopeId);
|
|
1210
1245
|
const run = state.activeRuns.get(sessionId);
|
|
1211
1246
|
if (!run || run.phase === 'finalizing' || run.phase === 'promoting') return false;
|
|
1212
|
-
this.cancelRun(
|
|
1247
|
+
this.cancelRun(
|
|
1248
|
+
run,
|
|
1249
|
+
this.trustInternalError(new FlexHarnessAbortError()),
|
|
1250
|
+
this.createCompactorContext(scopeId, scope.scope, state.storageKey, sessionId),
|
|
1251
|
+
);
|
|
1213
1252
|
return true;
|
|
1214
1253
|
}
|
|
1215
1254
|
|
|
@@ -1233,12 +1272,16 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1233
1272
|
decision: TFlexPermissionDecision,
|
|
1234
1273
|
): Promise<void> {
|
|
1235
1274
|
validateIdentifier(permissionId, 'permissionId');
|
|
1236
|
-
const { state } = await this.resolveState(scopeId);
|
|
1275
|
+
const { scope, state } = await this.resolveState(scopeId);
|
|
1237
1276
|
const pending = state.pendingPermissions.get(permissionId);
|
|
1238
1277
|
if (!pending || pending.request.sessionId !== sessionId) {
|
|
1239
1278
|
throw new FlexHarnessNotFoundError('Permission', permissionId);
|
|
1240
1279
|
}
|
|
1241
|
-
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
|
+
));
|
|
1242
1285
|
pending.responseQueue = response.then(() => undefined, () => undefined);
|
|
1243
1286
|
return response;
|
|
1244
1287
|
}
|
|
@@ -1275,7 +1318,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1275
1318
|
if (!reconciliation || typeof reconciliation !== 'object' || Array.isArray(reconciliation)) {
|
|
1276
1319
|
throw new FlexHarnessValidationError('Tool execution reconciliation must be a plain object.');
|
|
1277
1320
|
}
|
|
1278
|
-
const { state } = await this.resolveState(scopeId);
|
|
1321
|
+
const { scope, state } = await this.resolveState(scopeId);
|
|
1279
1322
|
const stored = this.requireSession(state, sessionId);
|
|
1280
1323
|
this.assertStateAcceptingWork(state);
|
|
1281
1324
|
let canonical: plugins.TAgentToolExecutionReconciliationOptions;
|
|
@@ -1299,7 +1342,10 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1299
1342
|
throw new FlexHarnessValidationError('Unknown tool execution reconciliation.');
|
|
1300
1343
|
}
|
|
1301
1344
|
try {
|
|
1302
|
-
await
|
|
1345
|
+
await this.withCompactorContext(
|
|
1346
|
+
this.createCompactorContext(scopeId, scope.scope, state.storageKey, sessionId),
|
|
1347
|
+
() => stored.agentSession.reconcileToolExecution(intentId, canonical),
|
|
1348
|
+
);
|
|
1303
1349
|
} catch (error) {
|
|
1304
1350
|
throw this.projectOperationError(error, 'agentSession', scopeId, sessionId, 'tool-reconciliation');
|
|
1305
1351
|
}
|
|
@@ -1311,22 +1357,28 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1311
1357
|
event: IJsonObject & { type: string },
|
|
1312
1358
|
): Promise<void> {
|
|
1313
1359
|
this.validateRuntimeEvent(event);
|
|
1314
|
-
const { state } = await this.resolveState(scopeId);
|
|
1360
|
+
const { scope, state } = await this.resolveState(scopeId);
|
|
1315
1361
|
const stored = this.requireSession(state, sessionId);
|
|
1316
1362
|
this.assertStateAcceptingWork(state);
|
|
1317
1363
|
try {
|
|
1318
|
-
await
|
|
1364
|
+
await this.withCompactorContext(
|
|
1365
|
+
this.createCompactorContext(scopeId, scope.scope, state.storageKey, sessionId),
|
|
1366
|
+
() => stored.agentSession.pushRuntimeEvent(cloneSerializable(event)),
|
|
1367
|
+
);
|
|
1319
1368
|
} catch (error) {
|
|
1320
1369
|
throw this.projectOperationError(error, 'agentSession', scopeId, sessionId, 'runtime-event');
|
|
1321
1370
|
}
|
|
1322
1371
|
}
|
|
1323
1372
|
|
|
1324
1373
|
public async compactSession(scopeId: string, sessionId: string): Promise<void> {
|
|
1325
|
-
const { state } = await this.resolveState(scopeId);
|
|
1374
|
+
const { scope, state } = await this.resolveState(scopeId);
|
|
1326
1375
|
const stored = this.requireSession(state, sessionId);
|
|
1327
1376
|
this.assertStateAcceptingWork(state);
|
|
1328
1377
|
try {
|
|
1329
|
-
await
|
|
1378
|
+
await this.withCompactorContext(
|
|
1379
|
+
this.createCompactorContext(scopeId, scope.scope, state.storageKey, sessionId),
|
|
1380
|
+
() => stored.agentSession.compact(),
|
|
1381
|
+
);
|
|
1330
1382
|
} catch (error) {
|
|
1331
1383
|
throw this.projectOperationError(error, 'agentSession', scopeId, sessionId, 'compaction');
|
|
1332
1384
|
}
|
|
@@ -1403,11 +1455,14 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1403
1455
|
): Promise<void> {
|
|
1404
1456
|
validateIdentifier(executionId, 'executionId');
|
|
1405
1457
|
requireTransferIdentifier(executionId, 'executionId');
|
|
1406
|
-
const { state } = await this.resolveState(scopeId);
|
|
1458
|
+
const { scope, state } = await this.resolveState(scopeId);
|
|
1407
1459
|
const stored = this.requireSession(state, sessionId);
|
|
1408
1460
|
this.assertStateAcceptingWork(state);
|
|
1409
1461
|
try {
|
|
1410
|
-
await
|
|
1462
|
+
await this.withCompactorContext(
|
|
1463
|
+
this.createCompactorContext(scopeId, scope.scope, state.storageKey, sessionId),
|
|
1464
|
+
() => stored.agentSession.abortBackgroundExecution(executionId),
|
|
1465
|
+
);
|
|
1411
1466
|
} catch (error) {
|
|
1412
1467
|
throw this.projectOperationError(error, 'agentSession', scopeId, sessionId, 'background-abort');
|
|
1413
1468
|
}
|
|
@@ -1636,6 +1691,12 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1636
1691
|
return {
|
|
1637
1692
|
state: queued.state,
|
|
1638
1693
|
stored: queued.stored,
|
|
1694
|
+
originCompactorContext: this.createCompactorContext(
|
|
1695
|
+
queued.scopeId,
|
|
1696
|
+
queued.scope as TScope,
|
|
1697
|
+
queued.state.storageKey,
|
|
1698
|
+
queued.sessionId,
|
|
1699
|
+
),
|
|
1639
1700
|
scopeId: queued.scopeId,
|
|
1640
1701
|
scope: queued.scope,
|
|
1641
1702
|
sessionId: queued.sessionId,
|
|
@@ -1676,11 +1737,14 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1676
1737
|
const options = queued.options!;
|
|
1677
1738
|
let projectionReserved = false;
|
|
1678
1739
|
try {
|
|
1679
|
-
run.transaction = await
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
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
|
+
),
|
|
1684
1748
|
);
|
|
1685
1749
|
this.assertPromptPromotion(queued, run);
|
|
1686
1750
|
const reservation = this.createReservation(run, prompt);
|
|
@@ -1759,13 +1823,13 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1759
1823
|
prepare: (context: { generationId: string; abortSignal: AbortSignal }) =>
|
|
1760
1824
|
this.prepareGeneration(run, options, context.abortSignal),
|
|
1761
1825
|
};
|
|
1762
|
-
const generation = run.scheduleKey
|
|
1826
|
+
const generation = this.withRunCompactorContext(run, () => run.scheduleKey
|
|
1763
1827
|
? run.stored.agentSession.scheduleGenerate({
|
|
1764
1828
|
key: run.scheduleKey,
|
|
1765
1829
|
debounceMs: run.debounceMs,
|
|
1766
1830
|
...generateOptions,
|
|
1767
1831
|
})
|
|
1768
|
-
: run.stored.agentSession.generate(generateOptions);
|
|
1832
|
+
: run.stored.agentSession.generate(generateOptions));
|
|
1769
1833
|
const execution = this.executeRun(run, generation).then(
|
|
1770
1834
|
(result) => this.finishQueuedPrompt(queued, 'completed', undefined, result),
|
|
1771
1835
|
(error) => this.finishQueuedPrompt(
|
|
@@ -1853,7 +1917,11 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1853
1917
|
}
|
|
1854
1918
|
}
|
|
1855
1919
|
|
|
1856
|
-
private cancelQueuedPrompt(
|
|
1920
|
+
private cancelQueuedPrompt(
|
|
1921
|
+
queued: IQueuedPrompt,
|
|
1922
|
+
reason: FlexHarnessAbortError,
|
|
1923
|
+
context?: IFlexAgentContextInvocation<TScope>,
|
|
1924
|
+
): boolean {
|
|
1857
1925
|
if (queued.stored.outstandingPromptsById.get(queued.queueId) !== queued) return false;
|
|
1858
1926
|
if (queued.status === 'queued') {
|
|
1859
1927
|
this.finishQueuedPrompt(queued, 'cancelled', reason);
|
|
@@ -1863,7 +1931,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1863
1931
|
const run = queued.state.activeRuns.get(queued.sessionId);
|
|
1864
1932
|
if (!run || run.queueId !== queued.queueId) return false;
|
|
1865
1933
|
if (run.phase === 'finalizing' || run.phase === 'promoting') return false;
|
|
1866
|
-
this.cancelRun(run, reason);
|
|
1934
|
+
this.cancelRun(run, reason, context);
|
|
1867
1935
|
return true;
|
|
1868
1936
|
}
|
|
1869
1937
|
|
|
@@ -1871,10 +1939,15 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1871
1939
|
stored: IStoredSessionState,
|
|
1872
1940
|
reason: FlexHarnessAbortError,
|
|
1873
1941
|
excludedQueueId?: string,
|
|
1942
|
+
context?: IFlexAgentContextInvocation<unknown>,
|
|
1874
1943
|
): void {
|
|
1875
1944
|
for (const queued of [...stored.outstandingPromptsById.values()]) {
|
|
1876
1945
|
if (queued.queueId === excludedQueueId) continue;
|
|
1877
|
-
this.cancelQueuedPrompt(
|
|
1946
|
+
this.cancelQueuedPrompt(
|
|
1947
|
+
queued,
|
|
1948
|
+
reason,
|
|
1949
|
+
context as IFlexAgentContextInvocation<TScope> | undefined,
|
|
1950
|
+
);
|
|
1878
1951
|
}
|
|
1879
1952
|
}
|
|
1880
1953
|
|
|
@@ -1908,7 +1981,10 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1908
1981
|
if (run.controller.signal.aborted) throw run.controller.signal.reason;
|
|
1909
1982
|
run.phase = 'finalizing';
|
|
1910
1983
|
try {
|
|
1911
|
-
await
|
|
1984
|
+
await this.withRunCompactorContext(
|
|
1985
|
+
run,
|
|
1986
|
+
() => run.stored.agentSession.finalizeGeneration(run.transaction!, 'accepted'),
|
|
1987
|
+
);
|
|
1912
1988
|
} catch (error) {
|
|
1913
1989
|
const projected = this.projectExternalError(run, error, 'agentSession');
|
|
1914
1990
|
await this.fenceNamespace(run.state, run, projected);
|
|
@@ -1978,7 +2054,10 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1978
2054
|
run.phase = 'finalizing';
|
|
1979
2055
|
const desiredOutcome: TCanonicalOutcome = cancelled || !generated ? 'interrupted' : 'rejected';
|
|
1980
2056
|
try {
|
|
1981
|
-
await
|
|
2057
|
+
await this.withRunCompactorContext(
|
|
2058
|
+
run,
|
|
2059
|
+
() => run.stored.agentSession.finalizeGeneration(run.transaction!, desiredOutcome),
|
|
2060
|
+
);
|
|
1982
2061
|
} catch (finalizationError) {
|
|
1983
2062
|
errors.push(this.projectExternalError(run, finalizationError, 'agentSession'));
|
|
1984
2063
|
}
|
|
@@ -2352,11 +2431,14 @@ export class FlexHarness<TScope = unknown> {
|
|
|
2352
2431
|
private emitTerminalProjection(run: IActiveRun, terminal: IFlexTerminalProjection): void {
|
|
2353
2432
|
for (const part of terminal.assistantMessage.parts) {
|
|
2354
2433
|
const callbackPart = run.callbackParts.find((entry) => entry.partId === part.partId);
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
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) {
|
|
2360
2442
|
this.emitPartEvent(run, 'part.completed', part);
|
|
2361
2443
|
}
|
|
2362
2444
|
}
|
|
@@ -2617,6 +2699,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
2617
2699
|
state: IStorageState,
|
|
2618
2700
|
pending: IPendingPermission,
|
|
2619
2701
|
decision: TFlexPermissionDecision,
|
|
2702
|
+
context: IFlexAgentContextInvocation<TScope>,
|
|
2620
2703
|
): Promise<void> {
|
|
2621
2704
|
if (pending.settled) {
|
|
2622
2705
|
throw new FlexHarnessPermissionStateError(`Permission "${pending.request.permissionId}" has already been resolved.`);
|
|
@@ -2714,7 +2797,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
2714
2797
|
const rejection = this.trustInternalError(
|
|
2715
2798
|
new FlexHarnessPermissionRejectedError(pending.request.permissionId),
|
|
2716
2799
|
);
|
|
2717
|
-
this.abortRunInternally(pending.run, rejection);
|
|
2800
|
+
this.abortRunInternally(pending.run, rejection, context);
|
|
2718
2801
|
pending.reject(rejection);
|
|
2719
2802
|
} else {
|
|
2720
2803
|
pending.resolve();
|
|
@@ -2818,19 +2901,50 @@ export class FlexHarness<TScope = unknown> {
|
|
|
2818
2901
|
pending.reject(error);
|
|
2819
2902
|
}
|
|
2820
2903
|
|
|
2821
|
-
private cancelRun(
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
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
|
+
);
|
|
2828
2928
|
}
|
|
2829
2929
|
|
|
2830
|
-
private abortRunInternally(
|
|
2831
|
-
run
|
|
2832
|
-
|
|
2833
|
-
|
|
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
|
+
);
|
|
2834
2948
|
}
|
|
2835
2949
|
|
|
2836
2950
|
private async rollbackAdmission(
|
|
@@ -2841,7 +2955,10 @@ export class FlexHarness<TScope = unknown> {
|
|
|
2841
2955
|
const errors: unknown[] = [];
|
|
2842
2956
|
if (run.transaction) {
|
|
2843
2957
|
try {
|
|
2844
|
-
await
|
|
2958
|
+
await this.withRunCompactorContext(
|
|
2959
|
+
run,
|
|
2960
|
+
() => run.stored.agentSession.finalizeGeneration(run.transaction!, 'interrupted'),
|
|
2961
|
+
);
|
|
2845
2962
|
} catch (error) {
|
|
2846
2963
|
errors.push(this.projectExternalError(run, error, 'agentSession'));
|
|
2847
2964
|
}
|
|
@@ -3068,6 +3185,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3068
3185
|
storageKey: string,
|
|
3069
3186
|
scopeId: string,
|
|
3070
3187
|
scope: TScope,
|
|
3188
|
+
compactorLifecycleController: AbortController,
|
|
3071
3189
|
): Promise<IStorageState> {
|
|
3072
3190
|
const snapshot = cloneSerializable(await this.stores.scopes.load(storageKey) ?? {
|
|
3073
3191
|
schemaVersion: 1,
|
|
@@ -3077,6 +3195,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3077
3195
|
} satisfies IFlexScopeSnapshot);
|
|
3078
3196
|
const state: IStorageState = {
|
|
3079
3197
|
storageKey,
|
|
3198
|
+
compactorLifecycleController,
|
|
3080
3199
|
scopeIdHint: scopeId,
|
|
3081
3200
|
revision: snapshot.revision,
|
|
3082
3201
|
sessions: new Map(),
|
|
@@ -3164,6 +3283,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3164
3283
|
agentEventStoreReleased: true,
|
|
3165
3284
|
executionContextCloseCompleted: true,
|
|
3166
3285
|
jobStoreReleased: true,
|
|
3286
|
+
compactorLifecycleController: new AbortController(),
|
|
3167
3287
|
promptQueue: [],
|
|
3168
3288
|
outstandingPromptsById: new Map(),
|
|
3169
3289
|
terminalPromptQueueEntries: new Map(),
|
|
@@ -3178,6 +3298,12 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3178
3298
|
scope: TScope,
|
|
3179
3299
|
): Promise<IStoredSessionState> {
|
|
3180
3300
|
const sessionId = metadata.sessionId;
|
|
3301
|
+
const compactorContext = this.createCompactorContext(
|
|
3302
|
+
scopeId,
|
|
3303
|
+
scope,
|
|
3304
|
+
state.storageKey,
|
|
3305
|
+
sessionId,
|
|
3306
|
+
);
|
|
3181
3307
|
const [projectionResult, permissionResult, eventStoreResult, jobStoreResult] = await Promise.allSettled([
|
|
3182
3308
|
this.stores.projections.load(state.storageKey, sessionId),
|
|
3183
3309
|
this.stores.permissions.load(state.storageKey, sessionId),
|
|
@@ -3216,13 +3342,16 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3216
3342
|
let executionContextHandle: IFlexExecutionContextHandle | undefined;
|
|
3217
3343
|
try {
|
|
3218
3344
|
if (this.executionContextProvider) {
|
|
3219
|
-
executionContextHandle = await this.
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
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
|
+
);
|
|
3226
3355
|
if (
|
|
3227
3356
|
executionContextHandle
|
|
3228
3357
|
&& (!executionContextHandle.context
|
|
@@ -3233,6 +3362,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3233
3362
|
}
|
|
3234
3363
|
const stored: IStoredSessionState = {
|
|
3235
3364
|
storageKey: state.storageKey,
|
|
3365
|
+
compactorContext,
|
|
3236
3366
|
session: cloneSerializable(metadata),
|
|
3237
3367
|
messages: cloneSerializable(projection?.messages ?? []),
|
|
3238
3368
|
stagedTerminals: cloneSerializable(projection?.stagedTerminals ?? []),
|
|
@@ -3250,21 +3380,75 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3250
3380
|
executionContextCloseCompleted: executionContextHandle?.close === undefined,
|
|
3251
3381
|
jobStoreReleased: this.stores.jobs.releaseSession === undefined,
|
|
3252
3382
|
jobs: executionContextHandle?.context.jobs,
|
|
3383
|
+
compactorLifecycleController: new AbortController(),
|
|
3253
3384
|
promptQueue: [],
|
|
3254
3385
|
outstandingPromptsById: new Map(),
|
|
3255
3386
|
terminalPromptQueueEntries: new Map(),
|
|
3256
3387
|
outstandingPromptBytes: 0,
|
|
3257
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;
|
|
3258
3409
|
const agentSessionOptions: plugins.IAgentSessionOptions & {
|
|
3259
3410
|
transactionOutcomeErrorProjector: (error: unknown) => string;
|
|
3260
3411
|
} = {
|
|
3261
|
-
...
|
|
3412
|
+
...agentSessionPolicy,
|
|
3262
3413
|
sessionId,
|
|
3263
3414
|
eventStore,
|
|
3264
|
-
executionContext:
|
|
3415
|
+
executionContext: contextualExecutionContext,
|
|
3265
3416
|
contextBuilder: ({ events }) => hydrateAgentMessages(
|
|
3266
3417
|
(this.agentSessionPolicy.contextBuilder ?? ((options) => plugins.buildModelMessages(options.events)))({ events }),
|
|
3267
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
|
+
: {}),
|
|
3268
3452
|
transactionOutcomeErrorProjector: () => 'The model operation failed.',
|
|
3269
3453
|
onToken: (delta) => this.onTextDelta(state, sessionId, delta),
|
|
3270
3454
|
onReasoningStart: (id) => this.onReasoningStart(state, sessionId, id),
|
|
@@ -3273,7 +3457,10 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3273
3457
|
onToolCallStart: (event) => this.onToolStart(state, sessionId, event),
|
|
3274
3458
|
onToolCallFinish: (event) => this.onToolFinish(state, sessionId, event),
|
|
3275
3459
|
};
|
|
3276
|
-
stored.agentSession = await
|
|
3460
|
+
stored.agentSession = await this.withCompactorContext(
|
|
3461
|
+
compactorContext,
|
|
3462
|
+
() => plugins.AgentSession.create(agentSessionOptions),
|
|
3463
|
+
);
|
|
3277
3464
|
return stored;
|
|
3278
3465
|
} catch (error) {
|
|
3279
3466
|
const cleanupErrors: unknown[] = [];
|
|
@@ -3744,11 +3931,12 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3744
3931
|
state: IStorageState,
|
|
3745
3932
|
sessionId: string,
|
|
3746
3933
|
scopeId: string,
|
|
3934
|
+
scope?: TScope,
|
|
3747
3935
|
): Promise<void> {
|
|
3748
3936
|
const existing = state.tombstoneCleanups.get(sessionId);
|
|
3749
3937
|
if (existing) return existing;
|
|
3750
3938
|
let cleanup!: Promise<void>;
|
|
3751
|
-
cleanup = this.finishTombstoneCleanupInternal(state, sessionId, scopeId).finally(() => {
|
|
3939
|
+
cleanup = this.finishTombstoneCleanupInternal(state, sessionId, scopeId, scope).finally(() => {
|
|
3752
3940
|
if (state.tombstoneCleanups.get(sessionId) === cleanup) {
|
|
3753
3941
|
state.tombstoneCleanups.delete(sessionId);
|
|
3754
3942
|
}
|
|
@@ -3761,17 +3949,28 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3761
3949
|
state: IStorageState,
|
|
3762
3950
|
sessionId: string,
|
|
3763
3951
|
scopeId: string,
|
|
3952
|
+
scope?: TScope,
|
|
3764
3953
|
): Promise<void> {
|
|
3765
3954
|
const retained = state.retainedSessionCleanups.get(sessionId);
|
|
3766
3955
|
if (retained) {
|
|
3767
3956
|
const errors: unknown[] = [];
|
|
3768
3957
|
const reason = this.trustInternalError(new FlexHarnessAbortError('The session was deleted.'));
|
|
3769
|
-
|
|
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);
|
|
3770
3963
|
const run = state.activeRuns.get(sessionId);
|
|
3771
|
-
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
|
+
}
|
|
3772
3971
|
if (!retained.stored.agentSessionAbortCompleted) {
|
|
3773
3972
|
try {
|
|
3774
|
-
await this.abortStoredSession(retained.stored, reason);
|
|
3973
|
+
await this.abortStoredSession(retained.stored, reason, invocation);
|
|
3775
3974
|
} catch (error) {
|
|
3776
3975
|
errors.push(this.projectOperationError(
|
|
3777
3976
|
error,
|
|
@@ -3782,20 +3981,16 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3782
3981
|
));
|
|
3783
3982
|
}
|
|
3784
3983
|
}
|
|
3785
|
-
if (run) {
|
|
3786
|
-
const settled = await Promise.allSettled([run.completion]);
|
|
3787
|
-
if (settled[0].status === 'rejected' && !isAbortError(settled[0].reason)) {
|
|
3788
|
-
errors.push(settled[0].reason);
|
|
3789
|
-
}
|
|
3790
|
-
}
|
|
3791
3984
|
if (retained.stored.promptQueueDrain) {
|
|
3792
3985
|
const settled = await Promise.allSettled([retained.stored.promptQueueDrain]);
|
|
3793
|
-
if (settled[0].status === 'rejected')
|
|
3986
|
+
if (settled[0].status === 'rejected') {
|
|
3987
|
+
this.appendUnexpectedErrors(errors, settled[0].reason);
|
|
3988
|
+
}
|
|
3794
3989
|
}
|
|
3795
3990
|
try {
|
|
3796
|
-
await this.closeStoredSession(retained.stored);
|
|
3991
|
+
await this.closeStoredSession(retained.stored, invocation);
|
|
3797
3992
|
} catch (error) {
|
|
3798
|
-
|
|
3993
|
+
this.appendUnexpectedErrors(errors, this.projectOperationError(
|
|
3799
3994
|
error,
|
|
3800
3995
|
'toolCleanup',
|
|
3801
3996
|
scopeId,
|
|
@@ -3803,7 +3998,12 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3803
3998
|
'session-delete',
|
|
3804
3999
|
));
|
|
3805
4000
|
}
|
|
3806
|
-
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
|
+
}
|
|
3807
4007
|
this.purgeStoredPromptQueue(retained.stored);
|
|
3808
4008
|
}
|
|
3809
4009
|
if (!retained?.domainsCompleted) {
|
|
@@ -3829,17 +4029,33 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3829
4029
|
state.lifecycle = 'fenced';
|
|
3830
4030
|
const detachedCleanupAttempts = this.beginDetachedCleanupSettlement(state);
|
|
3831
4031
|
const reason = this.trustInternalError(new FlexHarnessAbortError('The session namespace was fenced.'));
|
|
4032
|
+
if (!state.compactorLifecycleController.signal.aborted) {
|
|
4033
|
+
state.compactorLifecycleController.abort(reason);
|
|
4034
|
+
}
|
|
3832
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
|
+
);
|
|
3833
4047
|
try {
|
|
3834
4048
|
for (const stored of state.sessions.values()) {
|
|
4049
|
+
this.abortCompactorLifecycle(stored, reason);
|
|
3835
4050
|
this.cancelStoredPromptQueue(
|
|
3836
4051
|
stored,
|
|
3837
4052
|
reason,
|
|
3838
4053
|
stored === currentRun.stored ? currentRun.queueId : undefined,
|
|
4054
|
+
contextFor(stored.session.sessionId),
|
|
3839
4055
|
);
|
|
3840
4056
|
}
|
|
3841
4057
|
const otherRuns = [...state.activeRuns.values()].filter((run) => run !== currentRun);
|
|
3842
|
-
for (const run of otherRuns) this.cancelRun(run, reason);
|
|
4058
|
+
for (const run of otherRuns) this.cancelRun(run, reason, contextFor(run.sessionId));
|
|
3843
4059
|
const runResults = await Promise.allSettled(otherRuns.map((run) => run.completion));
|
|
3844
4060
|
for (const result of runResults) {
|
|
3845
4061
|
if (result.status === 'rejected') this.appendUnexpectedErrors(cleanupErrors, result.reason);
|
|
@@ -3847,7 +4063,16 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3847
4063
|
await Promise.allSettled([...state.sessionInitializations.values()]);
|
|
3848
4064
|
cleanupErrors.push(...await this.closeOrphanedResources(state.storageKey));
|
|
3849
4065
|
await state.scopeQueue;
|
|
3850
|
-
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);
|
|
3851
4076
|
const tombstoneResults = await Promise.allSettled(
|
|
3852
4077
|
tombstoneAttempts.map(([, cleanup]) => cleanup),
|
|
3853
4078
|
);
|
|
@@ -3858,12 +4083,13 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3858
4083
|
const storedSessions = new Set([
|
|
3859
4084
|
...state.sessions.values(),
|
|
3860
4085
|
...[...state.retainedSessionCleanups]
|
|
3861
|
-
.filter(([sessionId]) =>
|
|
4086
|
+
.filter(([sessionId]) =>
|
|
4087
|
+
sessionId !== currentRun.sessionId && !attemptedTombstones.has(sessionId))
|
|
3862
4088
|
.map(([, retained]) => retained.stored),
|
|
3863
4089
|
]);
|
|
3864
4090
|
for (const stored of storedSessions) {
|
|
3865
4091
|
try {
|
|
3866
|
-
await this.abortStoredSession(stored, reason);
|
|
4092
|
+
await this.abortStoredSession(stored, reason, contextFor(stored.session.sessionId));
|
|
3867
4093
|
} catch (error) {
|
|
3868
4094
|
cleanupErrors.push(this.projectOperationError(
|
|
3869
4095
|
error,
|
|
@@ -3874,7 +4100,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3874
4100
|
));
|
|
3875
4101
|
}
|
|
3876
4102
|
try {
|
|
3877
|
-
await this.closeStoredSession(stored);
|
|
4103
|
+
await this.closeStoredSession(stored, contextFor(stored.session.sessionId));
|
|
3878
4104
|
} catch (error) {
|
|
3879
4105
|
if (!this.storedSessionCleanupCompleted(stored)) {
|
|
3880
4106
|
cleanupErrors.push(this.projectOperationError(
|
|
@@ -3893,24 +4119,41 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3893
4119
|
if (cleanupErrors.length > 0) throw combineErrors([cause, ...cleanupErrors]);
|
|
3894
4120
|
state.lifecycle = 'retired';
|
|
3895
4121
|
state.sessions.clear();
|
|
4122
|
+
const currentRetainedCleanup = state.retainedSessionCleanups.get(currentRun.sessionId);
|
|
3896
4123
|
state.retainedSessionCleanups.clear();
|
|
4124
|
+
if (currentRetainedCleanup) {
|
|
4125
|
+
state.retainedSessionCleanups.set(currentRun.sessionId, currentRetainedCleanup);
|
|
4126
|
+
}
|
|
3897
4127
|
state.sessionDeletions.clear();
|
|
3898
4128
|
state.tombstoneCleanups.clear();
|
|
4129
|
+
if (currentTombstoneCleanup) {
|
|
4130
|
+
state.tombstoneCleanups.set(currentRun.sessionId, currentTombstoneCleanup);
|
|
4131
|
+
}
|
|
3899
4132
|
state.activeRuns.clear();
|
|
3900
4133
|
state.pendingPermissions.clear();
|
|
3901
4134
|
state.initializingSessions.clear();
|
|
3902
4135
|
state.sessionInitializations.clear();
|
|
3903
4136
|
this.stateLoads.delete(state.storageKey);
|
|
4137
|
+
if (
|
|
4138
|
+
this.storageCompactorLifecycleControllers.get(state.storageKey)
|
|
4139
|
+
=== state.compactorLifecycleController
|
|
4140
|
+
) this.storageCompactorLifecycleControllers.delete(state.storageKey);
|
|
3904
4141
|
} finally {
|
|
3905
4142
|
state.fenceInProgress = false;
|
|
3906
4143
|
}
|
|
3907
4144
|
}
|
|
3908
4145
|
|
|
3909
|
-
private async closeStoredSession(
|
|
4146
|
+
private async closeStoredSession(
|
|
4147
|
+
stored: IStoredSessionState,
|
|
4148
|
+
context: IFlexAgentContextInvocation<unknown> = stored.compactorContext!,
|
|
4149
|
+
): Promise<void> {
|
|
3910
4150
|
const errors: unknown[] = [];
|
|
3911
4151
|
if (!stored.agentSessionCloseCompleted) {
|
|
3912
4152
|
try {
|
|
3913
|
-
await
|
|
4153
|
+
await this.withCompactorContext(
|
|
4154
|
+
context,
|
|
4155
|
+
() => stored.agentSession.close(),
|
|
4156
|
+
);
|
|
3914
4157
|
if (!stored.agentSession.closeCleanupCompleted) {
|
|
3915
4158
|
throw new Error('AgentSession.close() resolved before cleanup ownership was released.');
|
|
3916
4159
|
}
|
|
@@ -3960,9 +4203,13 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3960
4203
|
private async abortStoredSession(
|
|
3961
4204
|
stored: IStoredSessionState,
|
|
3962
4205
|
reason: FlexHarnessAbortError,
|
|
4206
|
+
context: IFlexAgentContextInvocation<unknown> = stored.compactorContext!,
|
|
3963
4207
|
): Promise<void> {
|
|
3964
4208
|
if (stored.agentSessionAbortCompleted) return;
|
|
3965
|
-
await
|
|
4209
|
+
await this.withCompactorContext(
|
|
4210
|
+
context,
|
|
4211
|
+
() => stored.agentSession.abortSession(reason, { abortBackgroundJobs: true }),
|
|
4212
|
+
);
|
|
3966
4213
|
stored.agentSessionAbortCompleted = true;
|
|
3967
4214
|
}
|
|
3968
4215
|
|
|
@@ -4018,6 +4265,29 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4018
4265
|
retained.storageKey === storageKey && retained.sessionId === sessionId);
|
|
4019
4266
|
}
|
|
4020
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
|
+
|
|
4021
4291
|
private closeOrphanedResources(storageKey?: string): Promise<unknown[]> {
|
|
4022
4292
|
const operation = this.orphanedResourceQueue.then(() =>
|
|
4023
4293
|
this.closeOrphanedResourcesInternal(storageKey));
|
|
@@ -4027,6 +4297,14 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4027
4297
|
|
|
4028
4298
|
private async closeOrphanedResourcesInternal(storageKey?: string): Promise<unknown[]> {
|
|
4029
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
|
+
}
|
|
4030
4308
|
for (const stored of [...this.orphanedStoredSessions]) {
|
|
4031
4309
|
if (storageKey !== undefined && stored.storageKey !== storageKey) continue;
|
|
4032
4310
|
try {
|
|
@@ -4067,6 +4345,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4067
4345
|
...[...this.orphanedStoredSessions].map((stored) => stored.storageKey),
|
|
4068
4346
|
...[...this.orphanedExecutionContexts.values()].map((owner) => owner.storageKey),
|
|
4069
4347
|
...[...this.orphanedProviderReleases.values()].map((retained) => retained.storageKey),
|
|
4348
|
+
...[...this.orphanedTombstoneCleanups.values()].map((retained) => retained.storageKey),
|
|
4070
4349
|
]);
|
|
4071
4350
|
}
|
|
4072
4351
|
|
|
@@ -4089,7 +4368,12 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4089
4368
|
}
|
|
4090
4369
|
} else {
|
|
4091
4370
|
try {
|
|
4092
|
-
await this.drainStorage(
|
|
4371
|
+
await this.drainStorage(
|
|
4372
|
+
scope.storageKey,
|
|
4373
|
+
stateLoad,
|
|
4374
|
+
this.createScopeRetirementError(),
|
|
4375
|
+
{ scopeId, scope: scope.scope },
|
|
4376
|
+
);
|
|
4093
4377
|
} catch (error) {
|
|
4094
4378
|
this.appendUnexpectedErrors(errors, error);
|
|
4095
4379
|
}
|
|
@@ -4101,15 +4385,25 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4101
4385
|
storageKey: string,
|
|
4102
4386
|
stateLoad: Promise<IStorageState>,
|
|
4103
4387
|
reason: FlexHarnessAbortError,
|
|
4388
|
+
invocation?: { scopeId: string; scope: TScope },
|
|
4104
4389
|
): Promise<void> {
|
|
4390
|
+
const compactorController = this.storageCompactorLifecycleControllers.get(storageKey);
|
|
4391
|
+
if (compactorController && !compactorController.signal.aborted) {
|
|
4392
|
+
compactorController.abort(reason);
|
|
4393
|
+
}
|
|
4105
4394
|
const existing = this.storageDrains.get(storageKey);
|
|
4106
4395
|
if (existing) return existing;
|
|
4107
4396
|
let completed = false;
|
|
4108
4397
|
let drain!: Promise<void>;
|
|
4109
|
-
drain = this.drainStorageInternal(storageKey, stateLoad, reason).then(() => {
|
|
4398
|
+
drain = this.drainStorageInternal(storageKey, stateLoad, reason, invocation).then(() => {
|
|
4110
4399
|
completed = true;
|
|
4111
4400
|
}).finally(() => {
|
|
4112
|
-
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
|
+
}
|
|
4113
4407
|
if (this.storageDrains.get(storageKey) === drain) this.storageDrains.delete(storageKey);
|
|
4114
4408
|
});
|
|
4115
4409
|
this.storageDrains.set(storageKey, drain);
|
|
@@ -4120,6 +4414,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4120
4414
|
storageKey: string,
|
|
4121
4415
|
stateLoad: Promise<IStorageState>,
|
|
4122
4416
|
reason: FlexHarnessAbortError,
|
|
4417
|
+
invocation?: { scopeId: string; scope: TScope },
|
|
4123
4418
|
): Promise<void> {
|
|
4124
4419
|
let state: IStorageState;
|
|
4125
4420
|
try {
|
|
@@ -4132,14 +4427,35 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4132
4427
|
if (state.lifecycle !== 'fenced') state.lifecycle = 'retiring';
|
|
4133
4428
|
const detachedCleanupAttempts = this.beginDetachedCleanupSettlement(state);
|
|
4134
4429
|
const errors: unknown[] = [];
|
|
4135
|
-
|
|
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
|
+
}
|
|
4136
4442
|
for (const run of state.activeRuns.values()) {
|
|
4137
|
-
if (run.phase !== 'finalizing' && run.phase !== 'promoting')
|
|
4443
|
+
if (run.phase !== 'finalizing' && run.phase !== 'promoting') {
|
|
4444
|
+
this.cancelRun(run, reason, contextFor(run.sessionId));
|
|
4445
|
+
}
|
|
4138
4446
|
}
|
|
4139
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
|
+
}
|
|
4140
4452
|
for (const stored of state.sessions.values()) {
|
|
4141
4453
|
try {
|
|
4142
|
-
await this.abortStoredSession(
|
|
4454
|
+
await this.abortStoredSession(
|
|
4455
|
+
stored,
|
|
4456
|
+
reason,
|
|
4457
|
+
contextFor(stored.session.sessionId),
|
|
4458
|
+
);
|
|
4143
4459
|
} catch (error) {
|
|
4144
4460
|
errors.push(this.projectOperationError(
|
|
4145
4461
|
error,
|
|
@@ -4150,10 +4466,6 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4150
4466
|
));
|
|
4151
4467
|
}
|
|
4152
4468
|
}
|
|
4153
|
-
const runResults = await Promise.allSettled([...state.activeRuns.values()].map((run) => run.completion));
|
|
4154
|
-
for (const result of runResults) {
|
|
4155
|
-
if (result.status === 'rejected') this.appendUnexpectedErrors(errors, result.reason);
|
|
4156
|
-
}
|
|
4157
4469
|
await Promise.allSettled([...state.sessionInitializations.values()]);
|
|
4158
4470
|
errors.push(...await this.closeOrphanedResources(state.storageKey));
|
|
4159
4471
|
await state.scopeQueue;
|
|
@@ -4173,7 +4485,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4173
4485
|
errors.push(...await this.settleDetachedCleanups(state, detachedCleanupAttempts));
|
|
4174
4486
|
for (const stored of state.sessions.values()) {
|
|
4175
4487
|
try {
|
|
4176
|
-
await this.closeStoredSession(stored);
|
|
4488
|
+
await this.closeStoredSession(stored, contextFor(stored.session.sessionId));
|
|
4177
4489
|
} catch (error) {
|
|
4178
4490
|
this.appendUnexpectedErrors(errors, this.projectOperationError(
|
|
4179
4491
|
error,
|
|
@@ -4192,7 +4504,8 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4192
4504
|
await this.finishTombstoneCleanup(
|
|
4193
4505
|
state,
|
|
4194
4506
|
sessionId,
|
|
4195
|
-
retained?.stored.session.scopeId ?? state.scopeIdHint,
|
|
4507
|
+
invocation?.scopeId ?? retained?.stored.session.scopeId ?? state.scopeIdHint,
|
|
4508
|
+
invocation?.scope,
|
|
4196
4509
|
);
|
|
4197
4510
|
} catch (error) {
|
|
4198
4511
|
this.appendUnexpectedErrors(errors, error);
|
|
@@ -4240,10 +4553,12 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4240
4553
|
}
|
|
4241
4554
|
this.listeners.clear();
|
|
4242
4555
|
if (errors.length > 0) throw combineErrors(errors);
|
|
4556
|
+
this.compactorInvocationContext.disable();
|
|
4243
4557
|
this.stateLoads.clear();
|
|
4244
4558
|
this.scopeAdmissions.clear();
|
|
4245
4559
|
this.scopeRetirements.clear();
|
|
4246
4560
|
this.storageDrains.clear();
|
|
4561
|
+
this.storageCompactorLifecycleControllers.clear();
|
|
4247
4562
|
}
|
|
4248
4563
|
|
|
4249
4564
|
private appendUnexpectedErrors(target: unknown[], error: unknown): void {
|
|
@@ -4252,6 +4567,12 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4252
4567
|
} else if (!isAbortError(error)) target.push(error);
|
|
4253
4568
|
}
|
|
4254
4569
|
|
|
4570
|
+
private abortCompactorLifecycle(stored: IStoredSessionState, reason: FlexHarnessAbortError): void {
|
|
4571
|
+
if (!stored.compactorLifecycleController.signal.aborted) {
|
|
4572
|
+
stored.compactorLifecycleController.abort(reason);
|
|
4573
|
+
}
|
|
4574
|
+
}
|
|
4575
|
+
|
|
4255
4576
|
private async resolveState(
|
|
4256
4577
|
scopeId: string,
|
|
4257
4578
|
): Promise<{ scope: IFlexResolvedScope<TScope>; state: IStorageState }> {
|
|
@@ -4279,10 +4600,26 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4279
4600
|
) throw this.createScopeRetirementError();
|
|
4280
4601
|
stateLoad = this.stateLoads.get(scope.storageKey);
|
|
4281
4602
|
if (!stateLoad) {
|
|
4282
|
-
|
|
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
|
+
);
|
|
4283
4614
|
this.stateLoads.set(scope.storageKey, stateLoad);
|
|
4284
4615
|
void stateLoad.catch(() => {
|
|
4285
|
-
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
|
+
}
|
|
4286
4623
|
});
|
|
4287
4624
|
}
|
|
4288
4625
|
}
|
|
@@ -4328,6 +4665,37 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4328
4665
|
}
|
|
4329
4666
|
}
|
|
4330
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
|
+
|
|
4331
4699
|
private requireSession(state: IStorageState, sessionId: string): IStoredSessionState {
|
|
4332
4700
|
validateIdentifier(sessionId, 'sessionId');
|
|
4333
4701
|
requireTransferIdentifier(sessionId, 'sessionId');
|