@modelprofile.com/flexharness 3.3.0 → 3.5.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 +20 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.flexharness.d.ts +30 -0
- package/dist_ts/classes.flexharness.js +998 -165
- package/dist_ts/interfaces.d.ts +37 -4
- package/dist_ts/plugins.d.ts +5 -4
- package/dist_ts/plugins.js +5 -4
- package/dist_ts/utils.json.js +176 -8
- package/package.json +1 -1
- package/readme.hints.md +12 -0
- package/readme.md +50 -3
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.flexharness.ts +1425 -159
- package/ts/interfaces.ts +45 -4
- package/ts/plugins.ts +7 -1
- 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
|
}
|
|
@@ -292,6 +303,9 @@ export interface IFlexToolProvider<TScope> {
|
|
|
292
303
|
export interface IFlexSessionTombstone {
|
|
293
304
|
sessionId: string;
|
|
294
305
|
deletedAt: string;
|
|
306
|
+
rootSessionId?: string;
|
|
307
|
+
depth?: number;
|
|
308
|
+
parentSessionId?: string;
|
|
295
309
|
}
|
|
296
310
|
|
|
297
311
|
export interface IFlexScopeSnapshot {
|
|
@@ -441,9 +455,25 @@ export interface IFlexExecutionContextProvider<TScope> {
|
|
|
441
455
|
): Promise<IFlexExecutionContextHandle | undefined> | IFlexExecutionContextHandle | undefined;
|
|
442
456
|
}
|
|
443
457
|
|
|
444
|
-
export interface
|
|
458
|
+
export interface IFlexAgentContextInvocation<TScope = unknown> {
|
|
459
|
+
readonly scopeId: string;
|
|
460
|
+
readonly scope: TScope;
|
|
461
|
+
readonly storageKey: string;
|
|
462
|
+
readonly sessionId: string;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
export interface IFlexAgentContextCompactionOptions<TScope = unknown>
|
|
466
|
+
extends plugins.IAgentContextCompactionOptions, IFlexAgentContextInvocation<TScope> {}
|
|
467
|
+
|
|
468
|
+
export type TFlexAgentContextCompactor<TScope = unknown> = (
|
|
469
|
+
messages: Parameters<plugins.TAgentContextCompactor>[0],
|
|
470
|
+
events: Parameters<plugins.TAgentContextCompactor>[1],
|
|
471
|
+
options: IFlexAgentContextCompactionOptions<TScope>,
|
|
472
|
+
) => ReturnType<plugins.TAgentContextCompactor>;
|
|
473
|
+
|
|
474
|
+
export interface IFlexAgentSessionPolicy<TScope = unknown> {
|
|
445
475
|
contextBuilder?: plugins.TAgentContextBuilder;
|
|
446
|
-
contextCompactor?:
|
|
476
|
+
contextCompactor?: TFlexAgentContextCompactor<TScope>;
|
|
447
477
|
eventRetention?: TFlexAgentSessionOptions['eventRetention'];
|
|
448
478
|
changeListenerTimeoutMs?: number;
|
|
449
479
|
maxPendingSessionChanges?: number;
|
|
@@ -452,17 +482,28 @@ export interface IFlexAgentSessionPolicy {
|
|
|
452
482
|
maxContextOverflowRetries?: number;
|
|
453
483
|
}
|
|
454
484
|
|
|
485
|
+
export interface IFlexSubagentDefinition {
|
|
486
|
+
name: string;
|
|
487
|
+
description: string;
|
|
488
|
+
modelHint?: string;
|
|
489
|
+
system?: string;
|
|
490
|
+
maxSteps?: number;
|
|
491
|
+
}
|
|
492
|
+
|
|
455
493
|
export interface IFlexHarnessOptions<TScope> {
|
|
456
494
|
scopeResolver: IFlexScopeResolver<TScope>;
|
|
457
495
|
modelResolver: IFlexModelResolver<TScope>;
|
|
458
496
|
toolProvider?: IFlexToolProvider<TScope>;
|
|
459
497
|
executionContextProvider?: IFlexExecutionContextProvider<TScope>;
|
|
460
498
|
stores?: IFlexHarnessStores;
|
|
461
|
-
agentSessionPolicy?: IFlexAgentSessionPolicy
|
|
499
|
+
agentSessionPolicy?: IFlexAgentSessionPolicy<TScope>;
|
|
462
500
|
toolOutputLimits?: IFlexJsonLimits;
|
|
463
501
|
callbackLimits?: IFlexCallbackLimits;
|
|
464
502
|
promptQueueLimits?: IFlexPromptQueueLimits;
|
|
465
503
|
externalErrorProjector?: TFlexExternalErrorProjector;
|
|
504
|
+
subagents?: IFlexSubagentDefinition[];
|
|
505
|
+
maxSubagentDepth?: number;
|
|
506
|
+
maxSubagentCallsPerRun?: number;
|
|
466
507
|
}
|
|
467
508
|
|
|
468
509
|
export interface IFlexUncertainToolExecution {
|
|
@@ -539,7 +580,7 @@ export interface IFlexMessageChangedEvent extends IFlexEventBase {
|
|
|
539
580
|
}
|
|
540
581
|
|
|
541
582
|
export interface IFlexPartChangedEvent extends IFlexEventBase {
|
|
542
|
-
readonly type: 'part.started' | 'part.delta' | 'part.completed';
|
|
583
|
+
readonly type: 'part.started' | 'part.delta' | 'part.updated' | 'part.completed';
|
|
543
584
|
readonly runId: string;
|
|
544
585
|
readonly messageId: string;
|
|
545
586
|
/** Zero-based position in the session's authoritative message sequence. */
|
package/ts/plugins.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// node native scope
|
|
2
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
3
|
import * as crypto from 'node:crypto';
|
|
3
4
|
import * as fs from 'node:fs/promises';
|
|
4
5
|
import * as path from 'node:path';
|
|
5
6
|
|
|
6
|
-
export { crypto, fs, path };
|
|
7
|
+
export { AsyncLocalStorage, crypto, fs, path };
|
|
7
8
|
|
|
8
9
|
// @push.rocks scope
|
|
9
10
|
import {
|
|
@@ -14,8 +15,10 @@ import {
|
|
|
14
15
|
createAgentEvent,
|
|
15
16
|
getAgentGenerationTransactions,
|
|
16
17
|
modelMessagesToAgentEvents,
|
|
18
|
+
tool,
|
|
17
19
|
validateAgentEventArchiveV2,
|
|
18
20
|
validateAgentEventSnapshotV2,
|
|
21
|
+
z,
|
|
19
22
|
} from '@push.rocks/smartagent';
|
|
20
23
|
|
|
21
24
|
export {
|
|
@@ -26,11 +29,14 @@ export {
|
|
|
26
29
|
createAgentEvent,
|
|
27
30
|
getAgentGenerationTransactions,
|
|
28
31
|
modelMessagesToAgentEvents,
|
|
32
|
+
tool,
|
|
29
33
|
validateAgentEventArchiveV2,
|
|
30
34
|
validateAgentEventSnapshotV2,
|
|
35
|
+
z,
|
|
31
36
|
};
|
|
32
37
|
export type {
|
|
33
38
|
IAgentEventArchiveV2,
|
|
39
|
+
IAgentContextCompactionOptions,
|
|
34
40
|
IAgentEventSnapshotV2,
|
|
35
41
|
IAgentEventStoreV2,
|
|
36
42
|
IAgentGenerateResult,
|
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
|
|