@modelprofile.com/flexharness 3.4.0 → 3.6.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 +24 -0
- package/dist_ts/classes.flexharness.js +920 -97
- package/dist_ts/interfaces.d.ts +43 -1
- package/dist_ts/plugins.d.ts +2 -2
- package/dist_ts/plugins.js +3 -3
- package/dist_ts/utils.json.js +176 -8
- package/package.json +1 -1
- package/readme.hints.md +13 -0
- package/readme.md +84 -3
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.flexharness.ts +1203 -106
- package/ts/interfaces.ts +50 -1
- package/ts/plugins.ts +4 -0
- package/ts/utils.json.ts +202 -8
package/ts/interfaces.ts
CHANGED
|
@@ -90,6 +90,8 @@ export interface IFlexToolMessagePart {
|
|
|
90
90
|
input: TJsonValue;
|
|
91
91
|
output?: TJsonValue;
|
|
92
92
|
error?: string;
|
|
93
|
+
childSessionId?: string;
|
|
94
|
+
model?: IFlexModelIdentity;
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
export interface IFlexAttachmentMessagePart {
|
|
@@ -139,6 +141,11 @@ export interface IFlexSession {
|
|
|
139
141
|
archivedAt?: string;
|
|
140
142
|
status: TFlexActivityStatus;
|
|
141
143
|
activity: IFlexSessionActivity;
|
|
144
|
+
parentSessionId?: string;
|
|
145
|
+
parentRunId?: string;
|
|
146
|
+
parentToolCallId?: string;
|
|
147
|
+
agent?: string;
|
|
148
|
+
depth?: number;
|
|
142
149
|
}
|
|
143
150
|
|
|
144
151
|
export interface IFlexCreateSessionOptions {
|
|
@@ -233,6 +240,8 @@ export interface IFlexModelResolverContext<TScope> {
|
|
|
233
240
|
sessionId: string;
|
|
234
241
|
runId: string;
|
|
235
242
|
modelHint?: string;
|
|
243
|
+
readonly parentSessionId?: string;
|
|
244
|
+
readonly agent?: string;
|
|
236
245
|
signal: AbortSignal;
|
|
237
246
|
}
|
|
238
247
|
|
|
@@ -274,6 +283,8 @@ export interface IFlexToolProviderContext<TScope> {
|
|
|
274
283
|
scope: TScope;
|
|
275
284
|
sessionId: string;
|
|
276
285
|
runId: string;
|
|
286
|
+
readonly parentSessionId?: string;
|
|
287
|
+
readonly agent?: string;
|
|
277
288
|
signal: AbortSignal;
|
|
278
289
|
requestPermission(request: IFlexPermissionRequestInput): Promise<void>;
|
|
279
290
|
}
|
|
@@ -289,9 +300,35 @@ export interface IFlexToolProvider<TScope> {
|
|
|
289
300
|
): Promise<IFlexToolHandle | undefined> | IFlexToolHandle | undefined;
|
|
290
301
|
}
|
|
291
302
|
|
|
303
|
+
export interface IFlexResourceToolProviderResolverContext<TScope> {
|
|
304
|
+
scopeId: string;
|
|
305
|
+
scope: TScope;
|
|
306
|
+
sessionId: string;
|
|
307
|
+
runId: string;
|
|
308
|
+
readonly parentSessionId?: string;
|
|
309
|
+
readonly agent?: string;
|
|
310
|
+
signal: AbortSignal;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export interface IFlexResourceToolProviderDescriptor<TScope> {
|
|
314
|
+
resourceId: string;
|
|
315
|
+
attachmentRevision: number;
|
|
316
|
+
provider: IFlexToolProvider<TScope>;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export interface IFlexResourceToolProviderResolver<TScope> {
|
|
320
|
+
resolveResourceToolProviders(
|
|
321
|
+
context: IFlexResourceToolProviderResolverContext<TScope>,
|
|
322
|
+
): Promise<readonly IFlexResourceToolProviderDescriptor<TScope>[]>
|
|
323
|
+
| readonly IFlexResourceToolProviderDescriptor<TScope>[];
|
|
324
|
+
}
|
|
325
|
+
|
|
292
326
|
export interface IFlexSessionTombstone {
|
|
293
327
|
sessionId: string;
|
|
294
328
|
deletedAt: string;
|
|
329
|
+
rootSessionId?: string;
|
|
330
|
+
depth?: number;
|
|
331
|
+
parentSessionId?: string;
|
|
295
332
|
}
|
|
296
333
|
|
|
297
334
|
export interface IFlexScopeSnapshot {
|
|
@@ -468,10 +505,19 @@ export interface IFlexAgentSessionPolicy<TScope = unknown> {
|
|
|
468
505
|
maxContextOverflowRetries?: number;
|
|
469
506
|
}
|
|
470
507
|
|
|
508
|
+
export interface IFlexSubagentDefinition {
|
|
509
|
+
name: string;
|
|
510
|
+
description: string;
|
|
511
|
+
modelHint?: string;
|
|
512
|
+
system?: string;
|
|
513
|
+
maxSteps?: number;
|
|
514
|
+
}
|
|
515
|
+
|
|
471
516
|
export interface IFlexHarnessOptions<TScope> {
|
|
472
517
|
scopeResolver: IFlexScopeResolver<TScope>;
|
|
473
518
|
modelResolver: IFlexModelResolver<TScope>;
|
|
474
519
|
toolProvider?: IFlexToolProvider<TScope>;
|
|
520
|
+
resourceToolProviderResolver?: IFlexResourceToolProviderResolver<TScope>;
|
|
475
521
|
executionContextProvider?: IFlexExecutionContextProvider<TScope>;
|
|
476
522
|
stores?: IFlexHarnessStores;
|
|
477
523
|
agentSessionPolicy?: IFlexAgentSessionPolicy<TScope>;
|
|
@@ -479,6 +525,9 @@ export interface IFlexHarnessOptions<TScope> {
|
|
|
479
525
|
callbackLimits?: IFlexCallbackLimits;
|
|
480
526
|
promptQueueLimits?: IFlexPromptQueueLimits;
|
|
481
527
|
externalErrorProjector?: TFlexExternalErrorProjector;
|
|
528
|
+
subagents?: IFlexSubagentDefinition[];
|
|
529
|
+
maxSubagentDepth?: number;
|
|
530
|
+
maxSubagentCallsPerRun?: number;
|
|
482
531
|
}
|
|
483
532
|
|
|
484
533
|
export interface IFlexUncertainToolExecution {
|
|
@@ -555,7 +604,7 @@ export interface IFlexMessageChangedEvent extends IFlexEventBase {
|
|
|
555
604
|
}
|
|
556
605
|
|
|
557
606
|
export interface IFlexPartChangedEvent extends IFlexEventBase {
|
|
558
|
-
readonly type: 'part.started' | 'part.delta' | 'part.completed';
|
|
607
|
+
readonly type: 'part.started' | 'part.delta' | 'part.updated' | 'part.completed';
|
|
559
608
|
readonly runId: string;
|
|
560
609
|
readonly messageId: string;
|
|
561
610
|
/** Zero-based position in the session's authoritative message sequence. */
|
package/ts/plugins.ts
CHANGED
|
@@ -15,8 +15,10 @@ import {
|
|
|
15
15
|
createAgentEvent,
|
|
16
16
|
getAgentGenerationTransactions,
|
|
17
17
|
modelMessagesToAgentEvents,
|
|
18
|
+
tool,
|
|
18
19
|
validateAgentEventArchiveV2,
|
|
19
20
|
validateAgentEventSnapshotV2,
|
|
21
|
+
z,
|
|
20
22
|
} from '@push.rocks/smartagent';
|
|
21
23
|
|
|
22
24
|
export {
|
|
@@ -27,8 +29,10 @@ export {
|
|
|
27
29
|
createAgentEvent,
|
|
28
30
|
getAgentGenerationTransactions,
|
|
29
31
|
modelMessagesToAgentEvents,
|
|
32
|
+
tool,
|
|
30
33
|
validateAgentEventArchiveV2,
|
|
31
34
|
validateAgentEventSnapshotV2,
|
|
35
|
+
z,
|
|
32
36
|
};
|
|
33
37
|
export type {
|
|
34
38
|
IAgentEventArchiveV2,
|
package/ts/utils.json.ts
CHANGED
|
@@ -422,7 +422,7 @@ const activityStatuses = [
|
|
|
422
422
|
'cancelled',
|
|
423
423
|
] as const;
|
|
424
424
|
|
|
425
|
-
function validateSession(value: unknown, path: string):
|
|
425
|
+
function validateSession(value: unknown, path: string): IValidatedSessionIdentity {
|
|
426
426
|
const session = requireRecord(value, path);
|
|
427
427
|
requireOnlyKeys(
|
|
428
428
|
session,
|
|
@@ -435,6 +435,11 @@ function validateSession(value: unknown, path: string): string {
|
|
|
435
435
|
'archivedAt',
|
|
436
436
|
'status',
|
|
437
437
|
'activity',
|
|
438
|
+
'parentSessionId',
|
|
439
|
+
'parentRunId',
|
|
440
|
+
'parentToolCallId',
|
|
441
|
+
'agent',
|
|
442
|
+
'depth',
|
|
438
443
|
],
|
|
439
444
|
path,
|
|
440
445
|
);
|
|
@@ -456,7 +461,41 @@ function validateSession(value: unknown, path: string): string {
|
|
|
456
461
|
requireOptionalString(activity.startedAt, `${path}.activity.startedAt`);
|
|
457
462
|
requireOptionalString(activity.completedAt, `${path}.activity.completedAt`);
|
|
458
463
|
requireOptionalString(activity.error, `${path}.activity.error`);
|
|
459
|
-
|
|
464
|
+
const relationship = [
|
|
465
|
+
session.parentSessionId,
|
|
466
|
+
session.parentRunId,
|
|
467
|
+
session.parentToolCallId,
|
|
468
|
+
session.agent,
|
|
469
|
+
];
|
|
470
|
+
const relationshipCount = relationship.filter((entry) => entry !== undefined).length;
|
|
471
|
+
if (relationshipCount !== 0 && relationshipCount !== relationship.length) {
|
|
472
|
+
throw new FlexHarnessStoreFormatError(`${path} has partial subagent relationship fields.`);
|
|
473
|
+
}
|
|
474
|
+
for (const [key, entry] of [
|
|
475
|
+
['parentSessionId', session.parentSessionId],
|
|
476
|
+
['parentRunId', session.parentRunId],
|
|
477
|
+
['parentToolCallId', session.parentToolCallId],
|
|
478
|
+
['agent', session.agent],
|
|
479
|
+
] as const) requireOptionalString(entry, `${path}.${key}`);
|
|
480
|
+
if (session.depth !== undefined) requireNonNegativeInteger(session.depth, `${path}.depth`);
|
|
481
|
+
if (relationshipCount === 0 && session.depth !== undefined && session.depth !== 0) {
|
|
482
|
+
throw new FlexHarnessStoreFormatError(`${path}.depth must be 0 for a root session.`);
|
|
483
|
+
}
|
|
484
|
+
if (relationshipCount > 0 && (!Number.isSafeInteger(session.depth) || Number(session.depth) < 1)) {
|
|
485
|
+
throw new FlexHarnessStoreFormatError(`${path}.depth must be positive for a child session.`);
|
|
486
|
+
}
|
|
487
|
+
return {
|
|
488
|
+
sessionId,
|
|
489
|
+
...(relationshipCount === 0
|
|
490
|
+
? {}
|
|
491
|
+
: {
|
|
492
|
+
parentSessionId: session.parentSessionId as string,
|
|
493
|
+
parentRunId: session.parentRunId as string,
|
|
494
|
+
parentToolCallId: session.parentToolCallId as string,
|
|
495
|
+
agent: session.agent as string,
|
|
496
|
+
}),
|
|
497
|
+
...(session.depth === undefined ? {} : { depth: session.depth as number }),
|
|
498
|
+
};
|
|
460
499
|
}
|
|
461
500
|
|
|
462
501
|
function validateModel(value: unknown, path: string): void {
|
|
@@ -490,6 +529,15 @@ interface IValidatedMessageIdentity {
|
|
|
490
529
|
status: 'streaming' | 'completed' | 'failed' | 'cancelled';
|
|
491
530
|
}
|
|
492
531
|
|
|
532
|
+
interface IValidatedSessionIdentity {
|
|
533
|
+
sessionId: string;
|
|
534
|
+
parentSessionId?: string;
|
|
535
|
+
parentRunId?: string;
|
|
536
|
+
parentToolCallId?: string;
|
|
537
|
+
agent?: string;
|
|
538
|
+
depth?: number;
|
|
539
|
+
}
|
|
540
|
+
|
|
493
541
|
function validateMessage(value: unknown, path: string): IValidatedMessageIdentity {
|
|
494
542
|
const message = requireRecord(value, path);
|
|
495
543
|
requireOnlyKeys(
|
|
@@ -551,7 +599,18 @@ function validateMessage(value: unknown, path: string): IValidatedMessageIdentit
|
|
|
551
599
|
} else if (part.type === 'tool') {
|
|
552
600
|
requireOnlyKeys(
|
|
553
601
|
part,
|
|
554
|
-
[
|
|
602
|
+
[
|
|
603
|
+
'partId',
|
|
604
|
+
'type',
|
|
605
|
+
'toolCallId',
|
|
606
|
+
'toolName',
|
|
607
|
+
'status',
|
|
608
|
+
'input',
|
|
609
|
+
'output',
|
|
610
|
+
'error',
|
|
611
|
+
'childSessionId',
|
|
612
|
+
'model',
|
|
613
|
+
],
|
|
555
614
|
partPath,
|
|
556
615
|
);
|
|
557
616
|
requireString(part.toolCallId, `${partPath}.toolCallId`);
|
|
@@ -563,6 +622,8 @@ function validateMessage(value: unknown, path: string): IValidatedMessageIdentit
|
|
|
563
622
|
throw new FlexHarnessStoreFormatError(`${partPath}.input is required.`);
|
|
564
623
|
}
|
|
565
624
|
requireOptionalString(part.error, `${partPath}.error`);
|
|
625
|
+
requireOptionalString(part.childSessionId, `${partPath}.childSessionId`);
|
|
626
|
+
if (part.model !== undefined) validateModel(part.model, `${partPath}.model`);
|
|
566
627
|
} else if (part.type === 'attachment') {
|
|
567
628
|
requireOnlyKeys(
|
|
568
629
|
part,
|
|
@@ -650,23 +711,91 @@ export function assertFlexScopeSnapshot(value: unknown): asserts value is IFlexS
|
|
|
650
711
|
throw new FlexHarnessStoreFormatError('Snapshot sessions must be an array.');
|
|
651
712
|
}
|
|
652
713
|
const sessionIds = new Set<string>();
|
|
714
|
+
const sessions = new Map<string, IValidatedSessionIdentity>();
|
|
653
715
|
for (let index = 0; index < snapshot.sessions.length; index++) {
|
|
654
|
-
const
|
|
655
|
-
if (sessionIds.has(sessionId)) {
|
|
656
|
-
throw new FlexHarnessStoreFormatError(`Snapshot contains duplicate session "${sessionId}".`);
|
|
716
|
+
const session = validateSession(snapshot.sessions[index], `$snapshot.sessions[${index}]`);
|
|
717
|
+
if (sessionIds.has(session.sessionId)) {
|
|
718
|
+
throw new FlexHarnessStoreFormatError(`Snapshot contains duplicate session "${session.sessionId}".`);
|
|
719
|
+
}
|
|
720
|
+
sessionIds.add(session.sessionId);
|
|
721
|
+
sessions.set(session.sessionId, session);
|
|
722
|
+
}
|
|
723
|
+
const origins = new Set<string>();
|
|
724
|
+
for (const session of sessions.values()) {
|
|
725
|
+
if (!session.parentSessionId) continue;
|
|
726
|
+
const parent = sessions.get(session.parentSessionId);
|
|
727
|
+
if (!parent) {
|
|
728
|
+
throw new FlexHarnessStoreFormatError(
|
|
729
|
+
`Snapshot child session "${session.sessionId}" has no live parent "${session.parentSessionId}".`,
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
const expectedDepth = (parent.depth ?? 0) + 1;
|
|
733
|
+
if (session.depth !== expectedDepth) {
|
|
734
|
+
throw new FlexHarnessStoreFormatError(
|
|
735
|
+
`Snapshot child session "${session.sessionId}" has inconsistent depth.`,
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
const origin = JSON.stringify([
|
|
739
|
+
session.parentSessionId,
|
|
740
|
+
session.parentRunId,
|
|
741
|
+
session.parentToolCallId,
|
|
742
|
+
]);
|
|
743
|
+
if (origins.has(origin)) {
|
|
744
|
+
throw new FlexHarnessStoreFormatError('Snapshot contains duplicate subagent invocation origins.');
|
|
745
|
+
}
|
|
746
|
+
origins.add(origin);
|
|
747
|
+
const ancestors = new Set([session.sessionId]);
|
|
748
|
+
let current: IValidatedSessionIdentity | undefined = session;
|
|
749
|
+
while (current.parentSessionId) {
|
|
750
|
+
if (ancestors.has(current.parentSessionId)) {
|
|
751
|
+
throw new FlexHarnessStoreFormatError('Snapshot session relationships contain a cycle.');
|
|
752
|
+
}
|
|
753
|
+
ancestors.add(current.parentSessionId);
|
|
754
|
+
current = sessions.get(current.parentSessionId);
|
|
755
|
+
if (!current) break;
|
|
657
756
|
}
|
|
658
|
-
sessionIds.add(sessionId);
|
|
659
757
|
}
|
|
660
758
|
if (!Array.isArray(snapshot.tombstones)) {
|
|
661
759
|
throw new FlexHarnessStoreFormatError('Snapshot tombstones must be an array.');
|
|
662
760
|
}
|
|
663
761
|
const tombstoneIds = new Set<string>();
|
|
762
|
+
const tombstones = new Map<string, {
|
|
763
|
+
sessionId: string;
|
|
764
|
+
rootSessionId?: string;
|
|
765
|
+
depth?: number;
|
|
766
|
+
parentSessionId?: string;
|
|
767
|
+
}>();
|
|
664
768
|
for (let index = 0; index < snapshot.tombstones.length; index++) {
|
|
665
769
|
const path = `$snapshot.tombstones[${index}]`;
|
|
666
770
|
const tombstone = requireRecord(snapshot.tombstones[index], path);
|
|
667
|
-
requireOnlyKeys(
|
|
771
|
+
requireOnlyKeys(
|
|
772
|
+
tombstone,
|
|
773
|
+
['sessionId', 'deletedAt', 'rootSessionId', 'depth', 'parentSessionId'],
|
|
774
|
+
path,
|
|
775
|
+
);
|
|
668
776
|
const sessionId = requireString(tombstone.sessionId, `${path}.sessionId`);
|
|
669
777
|
requireString(tombstone.deletedAt, `${path}.deletedAt`);
|
|
778
|
+
const parentSessionId = tombstone.parentSessionId === undefined
|
|
779
|
+
? undefined
|
|
780
|
+
: requireString(tombstone.parentSessionId, `${path}.parentSessionId`);
|
|
781
|
+
if (parentSessionId === sessionId) {
|
|
782
|
+
throw new FlexHarnessStoreFormatError(`${path} cannot be its own parent.`);
|
|
783
|
+
}
|
|
784
|
+
const hasRoot = tombstone.rootSessionId !== undefined;
|
|
785
|
+
const hasDepth = tombstone.depth !== undefined;
|
|
786
|
+
if (hasRoot !== hasDepth) {
|
|
787
|
+
throw new FlexHarnessStoreFormatError(`${path} has partial tombstone group fields.`);
|
|
788
|
+
}
|
|
789
|
+
if (hasRoot) {
|
|
790
|
+
requireString(tombstone.rootSessionId, `${path}.rootSessionId`);
|
|
791
|
+
requireNonNegativeInteger(tombstone.depth, `${path}.depth`);
|
|
792
|
+
if (tombstone.depth === 0 && tombstone.rootSessionId !== sessionId) {
|
|
793
|
+
throw new FlexHarnessStoreFormatError(`${path} has an invalid tombstone group root.`);
|
|
794
|
+
}
|
|
795
|
+
if (tombstone.depth !== 0 && tombstone.rootSessionId === sessionId) {
|
|
796
|
+
throw new FlexHarnessStoreFormatError(`${path} has an invalid tombstone group depth.`);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
670
799
|
if (tombstoneIds.has(sessionId)) {
|
|
671
800
|
throw new FlexHarnessStoreFormatError(`Snapshot contains duplicate tombstone "${sessionId}".`);
|
|
672
801
|
}
|
|
@@ -676,6 +805,71 @@ export function assertFlexScopeSnapshot(value: unknown): asserts value is IFlexS
|
|
|
676
805
|
);
|
|
677
806
|
}
|
|
678
807
|
tombstoneIds.add(sessionId);
|
|
808
|
+
tombstones.set(sessionId, {
|
|
809
|
+
sessionId,
|
|
810
|
+
...(hasRoot ? { rootSessionId: tombstone.rootSessionId as string } : {}),
|
|
811
|
+
...(hasDepth ? { depth: tombstone.depth as number } : {}),
|
|
812
|
+
...(parentSessionId === undefined ? {} : { parentSessionId }),
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
for (const [index, tombstone] of snapshot.tombstones.entries()) {
|
|
816
|
+
if (tombstone.rootSessionId === undefined) continue;
|
|
817
|
+
const root = tombstones.get(tombstone.rootSessionId);
|
|
818
|
+
if (!root || root.rootSessionId !== root.sessionId || root.depth !== 0) {
|
|
819
|
+
throw new FlexHarnessStoreFormatError(
|
|
820
|
+
`$snapshot.tombstones[${index}] references an invalid cleanup group root.`,
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
for (const tombstone of tombstones.values()) {
|
|
825
|
+
if (tombstone.depth !== undefined && tombstone.depth > 0 && !tombstone.parentSessionId) {
|
|
826
|
+
throw new FlexHarnessStoreFormatError(
|
|
827
|
+
`Snapshot grouped tombstone "${tombstone.sessionId}" must retain its parent.`,
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
if (!tombstone.parentSessionId) continue;
|
|
831
|
+
const parentSession = sessions.get(tombstone.parentSessionId);
|
|
832
|
+
const parentTombstone = tombstones.get(tombstone.parentSessionId);
|
|
833
|
+
if (!parentSession && !parentTombstone) {
|
|
834
|
+
throw new FlexHarnessStoreFormatError(
|
|
835
|
+
`Snapshot tombstone "${tombstone.sessionId}" has no parent "${tombstone.parentSessionId}".`,
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
if (tombstone.depth !== undefined && tombstone.depth > 0) {
|
|
839
|
+
if (
|
|
840
|
+
!parentTombstone
|
|
841
|
+
|| parentTombstone.rootSessionId !== tombstone.rootSessionId
|
|
842
|
+
|| parentTombstone.depth !== tombstone.depth - 1
|
|
843
|
+
) {
|
|
844
|
+
throw new FlexHarnessStoreFormatError(
|
|
845
|
+
`Snapshot tombstone "${tombstone.sessionId}" has inconsistent group ancestry.`,
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
const ancestors = new Set([tombstone.sessionId]);
|
|
850
|
+
let currentId: string | undefined = tombstone.sessionId;
|
|
851
|
+
while (currentId) {
|
|
852
|
+
const currentParent: string | undefined = tombstones.get(currentId)?.parentSessionId
|
|
853
|
+
?? sessions.get(currentId)?.parentSessionId;
|
|
854
|
+
if (!currentParent) break;
|
|
855
|
+
if (ancestors.has(currentParent)) {
|
|
856
|
+
throw new FlexHarnessStoreFormatError('Snapshot session and tombstone relationships contain a cycle.');
|
|
857
|
+
}
|
|
858
|
+
ancestors.add(currentParent);
|
|
859
|
+
currentId = currentParent;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
const tombstoned = tombstoneIds;
|
|
863
|
+
for (const session of sessions.values()) {
|
|
864
|
+
let ancestorId = session.parentSessionId;
|
|
865
|
+
while (ancestorId) {
|
|
866
|
+
if (tombstoned.has(ancestorId)) {
|
|
867
|
+
throw new FlexHarnessStoreFormatError(
|
|
868
|
+
`Snapshot live session "${session.sessionId}" descends from a tombstoned ancestor.`,
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
ancestorId = sessions.get(ancestorId)?.parentSessionId;
|
|
872
|
+
}
|
|
679
873
|
}
|
|
680
874
|
}
|
|
681
875
|
|