@modelprofile.com/flexharness 5.2.0 → 5.3.1

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/ts/interfaces.ts CHANGED
@@ -441,6 +441,7 @@ export interface IFlexSessionTombstone {
441
441
  rootSessionId?: string;
442
442
  depth?: number;
443
443
  parentSessionId?: string;
444
+ subagent?: IFlexSubagentProvenance;
444
445
  }
445
446
 
446
447
  export interface IFlexScopeSnapshot {
@@ -582,6 +583,16 @@ export interface IFlexSessionGeneration {
582
583
  sessionGenerationSequence: number;
583
584
  }
584
585
 
586
+ export interface IFlexSubagentProvenance {
587
+ readonly parentSessionId: string;
588
+ readonly parentSessionGenerationId: string;
589
+ readonly parentSessionGenerationSequence: number;
590
+ readonly originParentRunId: string;
591
+ readonly originParentToolCallId: string;
592
+ readonly agent: string;
593
+ readonly depth: number;
594
+ }
595
+
585
596
  export interface IFlexSessionGenerationCohortEntry extends IFlexSessionGeneration {
586
597
  sessionId: string;
587
598
  }
@@ -649,11 +660,15 @@ export interface IFlexProjectManagementWriteContext {
649
660
  toolCallId?: string;
650
661
  }
651
662
 
663
+ export interface IFlexProjectManagementSessionContext extends IFlexSessionGeneration {
664
+ readonly subagent?: Readonly<IFlexSubagentProvenance>;
665
+ }
666
+
652
667
  export interface IFlexProjectManagementStore {
653
668
  load(
654
669
  storageKey: string,
655
670
  sessionId: string,
656
- sessionGeneration?: Readonly<IFlexSessionGeneration>,
671
+ sessionContext?: Readonly<IFlexProjectManagementSessionContext>,
657
672
  ): Promise<TFlexProjectManagementRecord | undefined>;
658
673
  save(
659
674
  storageKey: string,
@@ -667,6 +682,7 @@ export interface IFlexProjectManagementStore {
667
682
  sessionId: string,
668
683
  tombstone: IFlexProjectManagementTombstone,
669
684
  expectedRevision: number,
685
+ sessionContext?: Readonly<IFlexProjectManagementSessionContext>,
670
686
  ): Promise<void>;
671
687
  purgeNamespace(storageKey: string): Promise<void>;
672
688
  }
@@ -944,6 +960,7 @@ export interface IFlexRedoSessionResult {
944
960
  export type TFlexExternalErrorSource =
945
961
  | 'modelResolver'
946
962
  | 'toolProvider'
963
+ | 'delegatedRunAdmissionProvider'
947
964
  | 'toolExecution'
948
965
  | 'toolCallback'
949
966
  | 'toolCleanup'
@@ -964,6 +981,39 @@ export type TFlexExternalErrorProjector = (
964
981
  context: IFlexExternalErrorContext,
965
982
  ) => IFlexErrorInfo;
966
983
 
984
+ export interface IFlexDelegatedRunAdmissionContext<TScope> extends IFlexSessionGeneration {
985
+ readonly scopeId: string;
986
+ readonly scope: TScope;
987
+ readonly storageKey: string;
988
+ readonly sessionId: string;
989
+ readonly sessionGenerationId: string;
990
+ readonly sessionGenerationSequence: number;
991
+ readonly queueId: string;
992
+ readonly runId: string;
993
+ readonly parentSessionId: string;
994
+ readonly parentSessionGenerationId: string;
995
+ readonly parentSessionGenerationSequence: number;
996
+ readonly parentQueueId: string;
997
+ readonly parentRunId: string;
998
+ readonly parentToolCallId: string;
999
+ readonly originParentRunId: string;
1000
+ readonly originParentToolCallId: string;
1001
+ readonly agent: string;
1002
+ readonly depth: number;
1003
+ readonly signal: AbortSignal;
1004
+ }
1005
+
1006
+ export interface IFlexDelegatedRunAdmissionLease {
1007
+ /** Must be idempotent and safe to retry after a rejected close attempt. */
1008
+ close(): Promise<void> | void;
1009
+ }
1010
+
1011
+ export interface IFlexDelegatedRunAdmissionProvider<TScope> {
1012
+ acquireDelegatedRunAdmission(
1013
+ context: IFlexDelegatedRunAdmissionContext<TScope>,
1014
+ ): Promise<IFlexDelegatedRunAdmissionLease> | IFlexDelegatedRunAdmissionLease;
1015
+ }
1016
+
967
1017
  export interface IFlexExecutionContextProviderContext<TScope> {
968
1018
  scopeId: string;
969
1019
  scope: TScope;
@@ -1035,6 +1085,7 @@ export interface IFlexHarnessOptions<TScope> {
1035
1085
  toolProvider?: IFlexToolProvider<TScope>;
1036
1086
  resourceToolProviderResolver?: IFlexResourceToolProviderResolver<TScope>;
1037
1087
  executionContextProvider?: IFlexExecutionContextProvider<TScope>;
1088
+ delegatedRunAdmissionProvider?: IFlexDelegatedRunAdmissionProvider<TScope>;
1038
1089
  stores?: IFlexHarnessStores;
1039
1090
  agentSessionPolicy?: IFlexAgentSessionPolicy<TScope>;
1040
1091
  toolOutputLimits?: IFlexJsonLimits;
package/ts/utils.json.ts CHANGED
@@ -15,6 +15,7 @@ import type {
15
15
  IFlexProjectionSnapshotV2,
16
16
  IFlexProjectionSnapshotV3,
17
17
  IFlexScopeSnapshot,
18
+ IFlexSubagentProvenance,
18
19
  TFlexAgentModelMessage,
19
20
  TJsonValue,
20
21
  } from './interfaces.js';
@@ -546,6 +547,68 @@ function validateSession(value: unknown, path: string): IValidatedSessionIdentit
546
547
  };
547
548
  }
548
549
 
550
+ function validateSubagentProvenance(
551
+ value: unknown,
552
+ path: string,
553
+ ): IFlexSubagentProvenance {
554
+ const provenance = requireRecord(value, path);
555
+ requireOnlyKeys(provenance, [
556
+ 'parentSessionId',
557
+ 'parentSessionGenerationId',
558
+ 'parentSessionGenerationSequence',
559
+ 'originParentRunId',
560
+ 'originParentToolCallId',
561
+ 'agent',
562
+ 'depth',
563
+ ], path);
564
+ const parentSessionId = requireString(
565
+ provenance.parentSessionId,
566
+ `${path}.parentSessionId`,
567
+ );
568
+ const parentSessionGenerationId = requireString(
569
+ provenance.parentSessionGenerationId,
570
+ `${path}.parentSessionGenerationId`,
571
+ );
572
+ if (
573
+ Buffer.byteLength(parentSessionGenerationId, 'utf8')
574
+ > FLEX_SESSION_GENERATION_ID_MAX_BYTES
575
+ ) {
576
+ throw new FlexHarnessStoreFormatError(
577
+ `${path}.parentSessionGenerationId exceeds ${FLEX_SESSION_GENERATION_ID_MAX_BYTES} UTF-8 bytes.`,
578
+ );
579
+ }
580
+ if (
581
+ !Number.isSafeInteger(provenance.parentSessionGenerationSequence)
582
+ || Number(provenance.parentSessionGenerationSequence) < 1
583
+ ) {
584
+ throw new FlexHarnessStoreFormatError(
585
+ `${path}.parentSessionGenerationSequence must be a positive integer.`,
586
+ );
587
+ }
588
+ const originParentRunId = requireString(
589
+ provenance.originParentRunId,
590
+ `${path}.originParentRunId`,
591
+ );
592
+ const originParentToolCallId = requireString(
593
+ provenance.originParentToolCallId,
594
+ `${path}.originParentToolCallId`,
595
+ );
596
+ const agent = requireString(provenance.agent, `${path}.agent`);
597
+ requireNonNegativeInteger(provenance.depth, `${path}.depth`);
598
+ if (provenance.depth === 0) {
599
+ throw new FlexHarnessStoreFormatError(`${path}.depth must be positive.`);
600
+ }
601
+ return {
602
+ parentSessionId,
603
+ parentSessionGenerationId,
604
+ parentSessionGenerationSequence: provenance.parentSessionGenerationSequence as number,
605
+ originParentRunId,
606
+ originParentToolCallId,
607
+ agent,
608
+ depth: provenance.depth as number,
609
+ };
610
+ }
611
+
549
612
  function validateModel(value: unknown, path: string): void {
550
613
  const model = requireRecord(value, path);
551
614
  requireOnlyKeys(model, ['provider', 'model', 'displayName', 'variant'], path);
@@ -811,9 +874,12 @@ export function assertFlexScopeSnapshot(value: unknown): asserts value is IFlexS
811
874
  const tombstoneIds = new Set<string>();
812
875
  const tombstones = new Map<string, {
813
876
  sessionId: string;
877
+ sessionGenerationId?: string;
878
+ sessionGenerationSequence?: number;
814
879
  rootSessionId?: string;
815
880
  depth?: number;
816
881
  parentSessionId?: string;
882
+ subagent?: IFlexSubagentProvenance;
817
883
  }>();
818
884
  for (let index = 0; index < snapshot.tombstones.length; index++) {
819
885
  const path = `$snapshot.tombstones[${index}]`;
@@ -828,11 +894,12 @@ export function assertFlexScopeSnapshot(value: unknown): asserts value is IFlexS
828
894
  'rootSessionId',
829
895
  'depth',
830
896
  'parentSessionId',
897
+ 'subagent',
831
898
  ],
832
899
  path,
833
900
  );
834
901
  const sessionId = requireString(tombstone.sessionId, `${path}.sessionId`);
835
- validateOptionalSessionGeneration(tombstone, path);
902
+ const generation = validateOptionalSessionGeneration(tombstone, path);
836
903
  requireString(tombstone.deletedAt, `${path}.deletedAt`);
837
904
  const parentSessionId = tombstone.parentSessionId === undefined
838
905
  ? undefined
@@ -840,6 +907,19 @@ export function assertFlexScopeSnapshot(value: unknown): asserts value is IFlexS
840
907
  if (parentSessionId === sessionId) {
841
908
  throw new FlexHarnessStoreFormatError(`${path} cannot be its own parent.`);
842
909
  }
910
+ const subagent = tombstone.subagent === undefined
911
+ ? undefined
912
+ : validateSubagentProvenance(tombstone.subagent, `${path}.subagent`);
913
+ if (subagent && generation.sessionGenerationId === undefined) {
914
+ throw new FlexHarnessStoreFormatError(
915
+ `${path}.subagent requires exact session generation fields.`,
916
+ );
917
+ }
918
+ if (subagent && subagent.parentSessionId !== parentSessionId) {
919
+ throw new FlexHarnessStoreFormatError(
920
+ `${path}.subagent does not match its parentSessionId.`,
921
+ );
922
+ }
843
923
  const hasRoot = tombstone.rootSessionId !== undefined;
844
924
  const hasDepth = tombstone.depth !== undefined;
845
925
  if (hasRoot !== hasDepth) {
@@ -864,11 +944,26 @@ export function assertFlexScopeSnapshot(value: unknown): asserts value is IFlexS
864
944
  );
865
945
  }
866
946
  tombstoneIds.add(sessionId);
947
+ if (subagent) {
948
+ const origin = JSON.stringify([
949
+ subagent.parentSessionId,
950
+ subagent.originParentRunId,
951
+ subagent.originParentToolCallId,
952
+ ]);
953
+ if (origins.has(origin)) {
954
+ throw new FlexHarnessStoreFormatError(
955
+ 'Snapshot contains duplicate subagent invocation origins.',
956
+ );
957
+ }
958
+ origins.add(origin);
959
+ }
867
960
  tombstones.set(sessionId, {
868
961
  sessionId,
962
+ ...generation,
869
963
  ...(hasRoot ? { rootSessionId: tombstone.rootSessionId as string } : {}),
870
964
  ...(hasDepth ? { depth: tombstone.depth as number } : {}),
871
965
  ...(parentSessionId === undefined ? {} : { parentSessionId }),
966
+ ...(subagent === undefined ? {} : { subagent }),
872
967
  });
873
968
  }
874
969
  for (const [index, tombstone] of snapshot.tombstones.entries()) {
@@ -894,6 +989,26 @@ export function assertFlexScopeSnapshot(value: unknown): asserts value is IFlexS
894
989
  `Snapshot tombstone "${tombstone.sessionId}" has no parent "${tombstone.parentSessionId}".`,
895
990
  );
896
991
  }
992
+ if (tombstone.subagent) {
993
+ const parent = parentSession ?? parentTombstone!;
994
+ if (
995
+ parent.sessionGenerationId !== tombstone.subagent.parentSessionGenerationId
996
+ || parent.sessionGenerationSequence
997
+ !== tombstone.subagent.parentSessionGenerationSequence
998
+ ) {
999
+ throw new FlexHarnessStoreFormatError(
1000
+ `Snapshot tombstone "${tombstone.sessionId}" has inconsistent parent generation provenance.`,
1001
+ );
1002
+ }
1003
+ const parentDepth = parent.parentSessionId === undefined
1004
+ ? 0
1005
+ : parentSession?.depth ?? parentTombstone?.subagent?.depth;
1006
+ if (parentDepth === undefined || tombstone.subagent.depth !== parentDepth + 1) {
1007
+ throw new FlexHarnessStoreFormatError(
1008
+ `Snapshot tombstone "${tombstone.sessionId}" has inconsistent session depth provenance.`,
1009
+ );
1010
+ }
1011
+ }
897
1012
  if (tombstone.depth !== undefined && tombstone.depth > 0) {
898
1013
  if (
899
1014
  !parentTombstone