@modelprofile.com/flexharness 5.1.0 → 5.2.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 +9 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.flexharness.d.ts +11 -1
- package/dist_ts/classes.flexharness.js +231 -128
- package/dist_ts/interfaces.d.ts +16 -3
- package/dist_ts/interfaces.js +2 -1
- package/dist_ts/utils.validation.d.ts +6 -1
- package/dist_ts/utils.validation.js +74 -2
- package/package.json +1 -1
- package/readme.md +14 -8
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.flexharness.ts +355 -126
- package/ts/interfaces.ts +20 -3
- package/ts/utils.validation.ts +125 -1
package/ts/interfaces.ts
CHANGED
|
@@ -155,6 +155,7 @@ export interface IFlexSession {
|
|
|
155
155
|
|
|
156
156
|
export interface IFlexCreateSessionOptions {
|
|
157
157
|
sessionId?: string;
|
|
158
|
+
sessionGenerationId?: string;
|
|
158
159
|
title?: string;
|
|
159
160
|
}
|
|
160
161
|
|
|
@@ -342,7 +343,7 @@ export interface IFlexScopeResolver<TScope> {
|
|
|
342
343
|
resolveScope(scopeId: string): Promise<IFlexResolvedScope<TScope>> | IFlexResolvedScope<TScope>;
|
|
343
344
|
}
|
|
344
345
|
|
|
345
|
-
export interface IFlexModelResolverContext<TScope> {
|
|
346
|
+
export interface IFlexModelResolverContext<TScope> extends IFlexSessionGeneration {
|
|
346
347
|
scopeId: string;
|
|
347
348
|
scope: TScope;
|
|
348
349
|
sessionId: string;
|
|
@@ -376,7 +377,8 @@ export interface IFlexPermissionRequestInput {
|
|
|
376
377
|
metadata?: TJsonValue;
|
|
377
378
|
}
|
|
378
379
|
|
|
379
|
-
export interface IFlexPermissionRequest
|
|
380
|
+
export interface IFlexPermissionRequest
|
|
381
|
+
extends IFlexPermissionRequestInput, IFlexSessionGeneration {
|
|
380
382
|
permissionId: string;
|
|
381
383
|
scopeId: string;
|
|
382
384
|
sessionId: string;
|
|
@@ -573,12 +575,27 @@ export interface IFlexPermissionSnapshot {
|
|
|
573
575
|
|
|
574
576
|
export const FLEX_PROJECT_MANAGEMENT_SCHEMA_VERSION = 1 as const;
|
|
575
577
|
export const FLEX_SESSION_GENERATION_ID_MAX_BYTES = 128;
|
|
578
|
+
export const FLEX_SESSION_GENERATION_COHORT_MAX_ENTRIES = 2048;
|
|
576
579
|
|
|
577
580
|
export interface IFlexSessionGeneration {
|
|
578
581
|
sessionGenerationId: string;
|
|
579
582
|
sessionGenerationSequence: number;
|
|
580
583
|
}
|
|
581
584
|
|
|
585
|
+
export interface IFlexSessionGenerationCohortEntry extends IFlexSessionGeneration {
|
|
586
|
+
sessionId: string;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
export interface IFlexDeleteSessionGenerationCohortInput {
|
|
590
|
+
root: IFlexSessionGenerationCohortEntry;
|
|
591
|
+
/** Exact authorized entries ordered by strictly ascending sessionId. */
|
|
592
|
+
authorizedCohort: readonly IFlexSessionGenerationCohortEntry[];
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
export interface IFlexDeleteSessionGenerationCohortResult {
|
|
596
|
+
readonly matched: boolean;
|
|
597
|
+
}
|
|
598
|
+
|
|
582
599
|
export const FLEX_PROJECT_MANAGEMENT_LIMITS = Object.freeze({
|
|
583
600
|
maxGoalBytes: 8 * 1024,
|
|
584
601
|
maxScratchpadBytes: 128 * 1024,
|
|
@@ -1070,7 +1087,7 @@ export interface IFlexEventArchiveMetadata {
|
|
|
1070
1087
|
eventCount: number;
|
|
1071
1088
|
}
|
|
1072
1089
|
|
|
1073
|
-
export interface IFlexEventBase {
|
|
1090
|
+
export interface IFlexEventBase extends IFlexSessionGeneration {
|
|
1074
1091
|
readonly eventId: string;
|
|
1075
1092
|
readonly sequence: number;
|
|
1076
1093
|
readonly timestamp: string;
|
package/ts/utils.validation.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { FlexHarnessValidationError } from './errors.js';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
FLEX_PROJECT_MANAGEMENT_LIMITS,
|
|
4
|
+
FLEX_SESSION_GENERATION_COHORT_MAX_ENTRIES,
|
|
5
|
+
FLEX_SESSION_GENERATION_ID_MAX_BYTES,
|
|
6
|
+
} from './interfaces.js';
|
|
3
7
|
import type {
|
|
4
8
|
IFlexCreateProjectTaskInput,
|
|
9
|
+
IFlexDeleteSessionGenerationCohortInput,
|
|
5
10
|
IFlexErrorInfo,
|
|
6
11
|
IFlexPromptOptions,
|
|
12
|
+
IFlexSessionGenerationCohortEntry,
|
|
7
13
|
IFlexSlashCommandExecutionOptions,
|
|
8
14
|
IFlexUpdateProjectTaskInput,
|
|
9
15
|
IFlexUpdateSessionOptions,
|
|
@@ -14,6 +20,7 @@ import type {
|
|
|
14
20
|
const maxProjectedErrorNameBytes = 128;
|
|
15
21
|
const maxProjectedErrorMessageBytes = 2048;
|
|
16
22
|
const maxProjectedErrorCodeBytes = 128;
|
|
23
|
+
const controlCharacterPattern = /[\u0000-\u001f\u007f]/u;
|
|
17
24
|
|
|
18
25
|
export function validateIdentifier(value: string, name: string): void {
|
|
19
26
|
if (typeof value !== 'string' || value.length === 0) {
|
|
@@ -38,6 +45,123 @@ export function validateUtf8String(
|
|
|
38
45
|
}
|
|
39
46
|
}
|
|
40
47
|
|
|
48
|
+
export function validateSessionGenerationId(
|
|
49
|
+
value: unknown,
|
|
50
|
+
name = 'sessionGenerationId',
|
|
51
|
+
): asserts value is string {
|
|
52
|
+
if (
|
|
53
|
+
typeof value !== 'string'
|
|
54
|
+
|| !value.trim()
|
|
55
|
+
|| Buffer.byteLength(value, 'utf8') > FLEX_SESSION_GENERATION_ID_MAX_BYTES
|
|
56
|
+
|| controlCharacterPattern.test(value)
|
|
57
|
+
) {
|
|
58
|
+
throw new FlexHarnessValidationError(
|
|
59
|
+
`${name} must be a non-empty string without control characters of at most ${FLEX_SESSION_GENERATION_ID_MAX_BYTES} UTF-8 bytes.`,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function validateSessionGenerationSequence(value: unknown, name: string): asserts value is number {
|
|
65
|
+
if (!Number.isSafeInteger(value) || Number(value) < 1) {
|
|
66
|
+
throw new FlexHarnessValidationError(`${name} must be a positive integer.`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function validateSessionGenerationCohortEntry(
|
|
71
|
+
value: unknown,
|
|
72
|
+
name: string,
|
|
73
|
+
): Readonly<IFlexSessionGenerationCohortEntry> {
|
|
74
|
+
if (
|
|
75
|
+
!value
|
|
76
|
+
|| typeof value !== 'object'
|
|
77
|
+
|| Array.isArray(value)
|
|
78
|
+
|| ![Object.prototype, null].includes(Object.getPrototypeOf(value))
|
|
79
|
+
) {
|
|
80
|
+
throw new FlexHarnessValidationError(`${name} must be a plain object.`);
|
|
81
|
+
}
|
|
82
|
+
const entry = value as Record<string, unknown>;
|
|
83
|
+
const keys = Reflect.ownKeys(entry);
|
|
84
|
+
if (
|
|
85
|
+
keys.length !== 3
|
|
86
|
+
|| keys.some((key) => typeof key !== 'string'
|
|
87
|
+
|| !['sessionId', 'sessionGenerationId', 'sessionGenerationSequence'].includes(key))
|
|
88
|
+
) {
|
|
89
|
+
throw new FlexHarnessValidationError(`${name} must use the exact session generation shape.`);
|
|
90
|
+
}
|
|
91
|
+
validateIdentifier(entry.sessionId as string, `${name}.sessionId`);
|
|
92
|
+
validateSessionGenerationId(entry.sessionGenerationId, `${name}.sessionGenerationId`);
|
|
93
|
+
validateSessionGenerationSequence(
|
|
94
|
+
entry.sessionGenerationSequence,
|
|
95
|
+
`${name}.sessionGenerationSequence`,
|
|
96
|
+
);
|
|
97
|
+
return Object.freeze({
|
|
98
|
+
sessionId: entry.sessionId as string,
|
|
99
|
+
sessionGenerationId: entry.sessionGenerationId,
|
|
100
|
+
sessionGenerationSequence: entry.sessionGenerationSequence,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function validateDeleteSessionGenerationCohortInput(
|
|
105
|
+
value: IFlexDeleteSessionGenerationCohortInput,
|
|
106
|
+
): {
|
|
107
|
+
root: Readonly<IFlexSessionGenerationCohortEntry>;
|
|
108
|
+
authorizedBySessionId: ReadonlyMap<string, Readonly<IFlexSessionGenerationCohortEntry>>;
|
|
109
|
+
} {
|
|
110
|
+
if (
|
|
111
|
+
!value
|
|
112
|
+
|| typeof value !== 'object'
|
|
113
|
+
|| Array.isArray(value)
|
|
114
|
+
|| ![Object.prototype, null].includes(Object.getPrototypeOf(value))
|
|
115
|
+
) {
|
|
116
|
+
throw new FlexHarnessValidationError(
|
|
117
|
+
'deleteSessionGenerationCohort input must be a plain object.',
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
const keys = Reflect.ownKeys(value);
|
|
121
|
+
if (
|
|
122
|
+
keys.length !== 2
|
|
123
|
+
|| keys.some((key) => typeof key !== 'string'
|
|
124
|
+
|| !['root', 'authorizedCohort'].includes(key))
|
|
125
|
+
) {
|
|
126
|
+
throw new FlexHarnessValidationError(
|
|
127
|
+
'deleteSessionGenerationCohort input must use the exact root and authorizedCohort shape.',
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
const root = validateSessionGenerationCohortEntry(value.root, 'root');
|
|
131
|
+
if (!Array.isArray(value.authorizedCohort)) {
|
|
132
|
+
throw new FlexHarnessValidationError('authorizedCohort must be an array.');
|
|
133
|
+
}
|
|
134
|
+
if (value.authorizedCohort.length > FLEX_SESSION_GENERATION_COHORT_MAX_ENTRIES) {
|
|
135
|
+
throw new FlexHarnessValidationError(
|
|
136
|
+
`authorizedCohort exceeds ${FLEX_SESSION_GENERATION_COHORT_MAX_ENTRIES} entries.`,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
const authorizedBySessionId = new Map<
|
|
140
|
+
string,
|
|
141
|
+
Readonly<IFlexSessionGenerationCohortEntry>
|
|
142
|
+
>();
|
|
143
|
+
let previousSessionId: string | undefined;
|
|
144
|
+
for (const [index, rawEntry] of value.authorizedCohort.entries()) {
|
|
145
|
+
const entry = validateSessionGenerationCohortEntry(
|
|
146
|
+
rawEntry,
|
|
147
|
+
`authorizedCohort[${index}]`,
|
|
148
|
+
);
|
|
149
|
+
if (authorizedBySessionId.has(entry.sessionId)) {
|
|
150
|
+
throw new FlexHarnessValidationError(
|
|
151
|
+
`authorizedCohort contains duplicate sessionId "${entry.sessionId}".`,
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
if (previousSessionId !== undefined && previousSessionId >= entry.sessionId) {
|
|
155
|
+
throw new FlexHarnessValidationError(
|
|
156
|
+
'authorizedCohort must be ordered by strictly ascending sessionId.',
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
authorizedBySessionId.set(entry.sessionId, entry);
|
|
160
|
+
previousSessionId = entry.sessionId;
|
|
161
|
+
}
|
|
162
|
+
return { root, authorizedBySessionId };
|
|
163
|
+
}
|
|
164
|
+
|
|
41
165
|
export function validateProjectedErrorInfo(value: unknown): IFlexErrorInfo {
|
|
42
166
|
if (
|
|
43
167
|
!value
|