@modelprofile.com/flexharness 3.8.0 → 4.0.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/changelog.md +20 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.flexharness.d.ts +52 -1
- package/dist_ts/classes.flexharness.js +736 -25
- package/dist_ts/classes.stores.d.ts +10 -1
- package/dist_ts/classes.stores.js +164 -2
- package/dist_ts/index.d.ts +1 -0
- package/dist_ts/index.js +2 -1
- package/dist_ts/interfaces.d.ts +94 -0
- package/dist_ts/interfaces.js +12 -1
- package/dist_ts/utils.json.js +37 -3
- package/dist_ts/utils.projectmanagement.d.ts +5 -0
- package/dist_ts/utils.projectmanagement.js +141 -0
- package/package.json +1 -1
- package/readme.hints.md +4 -2
- package/readme.md +121 -15
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.flexharness.ts +1234 -24
- package/ts/classes.stores.ts +262 -3
- package/ts/index.ts +6 -0
- package/ts/interfaces.ts +129 -0
- package/ts/utils.json.ts +50 -1
- package/ts/utils.projectmanagement.ts +226 -0
package/ts/classes.stores.ts
CHANGED
|
@@ -10,12 +10,17 @@ import type {
|
|
|
10
10
|
IFlexHarnessStores,
|
|
11
11
|
IFlexPermissionSnapshot,
|
|
12
12
|
IFlexPermissionStore,
|
|
13
|
+
IFlexProjectManagementSnapshot,
|
|
14
|
+
IFlexProjectManagementStore,
|
|
15
|
+
IFlexProjectManagementTombstone,
|
|
16
|
+
IFlexProjectManagementWriteContext,
|
|
13
17
|
IFlexProjectionSnapshotV3,
|
|
14
18
|
IFlexProjectionStore,
|
|
15
19
|
IFlexScopeSnapshot,
|
|
16
20
|
IFlexScopeStore,
|
|
17
21
|
IFlexToolJobStoreProvider,
|
|
18
22
|
TFlexProjectionSnapshot,
|
|
23
|
+
TFlexProjectManagementRecord,
|
|
19
24
|
} from './interfaces.js';
|
|
20
25
|
import {
|
|
21
26
|
assertFlexPermissionSnapshot,
|
|
@@ -24,8 +29,17 @@ import {
|
|
|
24
29
|
assertJsonSerializable,
|
|
25
30
|
cloneSerializable,
|
|
26
31
|
} from './utils.json.js';
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
import {
|
|
33
|
+
assertFlexProjectManagementRecord,
|
|
34
|
+
assertFlexProjectManagementSnapshot,
|
|
35
|
+
assertFlexProjectManagementTombstone,
|
|
36
|
+
} from './utils.projectmanagement.js';
|
|
37
|
+
|
|
38
|
+
type TSnapshot =
|
|
39
|
+
| IFlexScopeSnapshot
|
|
40
|
+
| TFlexProjectionSnapshot
|
|
41
|
+
| IFlexPermissionSnapshot
|
|
42
|
+
| TFlexProjectManagementRecord;
|
|
29
43
|
type TSnapshotValidator<TSnapshotValue extends TSnapshot> = (
|
|
30
44
|
value: unknown,
|
|
31
45
|
) => asserts value is TSnapshotValue;
|
|
@@ -42,6 +56,20 @@ function validateExpectedRevision(expectedRevision: number): void {
|
|
|
42
56
|
}
|
|
43
57
|
}
|
|
44
58
|
|
|
59
|
+
function validateProjectManagementWriteContext(
|
|
60
|
+
writeContext: IFlexProjectManagementWriteContext,
|
|
61
|
+
): void {
|
|
62
|
+
assertJsonSerializable(writeContext, '$writeContext');
|
|
63
|
+
assertRecord(writeContext, 'writeContext');
|
|
64
|
+
assertExactKeys(writeContext, ['actor', 'runId', 'agent', 'toolCallId'], 'writeContext');
|
|
65
|
+
if (writeContext.actor !== 'agent' && writeContext.actor !== 'application') {
|
|
66
|
+
throw new FlexHarnessValidationError('writeContext.actor must be agent or application.');
|
|
67
|
+
}
|
|
68
|
+
for (const key of ['runId', 'agent', 'toolCallId'] as const) {
|
|
69
|
+
if (writeContext[key] !== undefined) validateIdentifier(writeContext[key], `writeContext.${key}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
45
73
|
function validateSnapshotSave<TSnapshotValue extends TSnapshot>(
|
|
46
74
|
snapshot: TSnapshotValue,
|
|
47
75
|
expectedRevision: number,
|
|
@@ -63,6 +91,25 @@ function providerKey(storageKey: string, sessionId: string): string {
|
|
|
63
91
|
return JSON.stringify([storageKey, sessionId]);
|
|
64
92
|
}
|
|
65
93
|
|
|
94
|
+
function sameProjectManagementGeneration(
|
|
95
|
+
left: TFlexProjectManagementRecord,
|
|
96
|
+
right: TFlexProjectManagementRecord,
|
|
97
|
+
): boolean {
|
|
98
|
+
return left.sessionGenerationId === right.sessionGenerationId
|
|
99
|
+
&& left.sessionGenerationSequence === right.sessionGenerationSequence;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function canInitializeProjectManagementGeneration(
|
|
103
|
+
current: TFlexProjectManagementRecord,
|
|
104
|
+
next: TFlexProjectManagementRecord,
|
|
105
|
+
expectedRevision: number,
|
|
106
|
+
): boolean {
|
|
107
|
+
return 'deletedAt' in current
|
|
108
|
+
&& expectedRevision === 0
|
|
109
|
+
&& next.sessionGenerationId !== current.sessionGenerationId
|
|
110
|
+
&& next.sessionGenerationSequence > current.sessionGenerationSequence;
|
|
111
|
+
}
|
|
112
|
+
|
|
66
113
|
function assertProjectionSession(snapshot: TFlexProjectionSnapshot, sessionId: string): void {
|
|
67
114
|
for (const message of snapshot.messages) {
|
|
68
115
|
if (message.sessionId !== sessionId) {
|
|
@@ -414,6 +461,81 @@ class InMemoryPermissionStore implements IFlexPermissionStore {
|
|
|
414
461
|
}
|
|
415
462
|
}
|
|
416
463
|
|
|
464
|
+
export class InMemoryFlexProjectManagementStore implements IFlexProjectManagementStore {
|
|
465
|
+
private readonly snapshots = new Map<string, TFlexProjectManagementRecord>();
|
|
466
|
+
|
|
467
|
+
public async load(
|
|
468
|
+
storageKey: string,
|
|
469
|
+
sessionId: string,
|
|
470
|
+
): Promise<TFlexProjectManagementRecord | undefined> {
|
|
471
|
+
const snapshot = this.snapshots.get(providerKey(storageKey, sessionId));
|
|
472
|
+
return snapshot ? cloneSerializable(snapshot) : undefined;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
public async save(
|
|
476
|
+
storageKey: string,
|
|
477
|
+
sessionId: string,
|
|
478
|
+
snapshot: IFlexProjectManagementSnapshot,
|
|
479
|
+
expectedRevision: number,
|
|
480
|
+
writeContext: IFlexProjectManagementWriteContext,
|
|
481
|
+
): Promise<void> {
|
|
482
|
+
const key = providerKey(storageKey, sessionId);
|
|
483
|
+
validateProjectManagementWriteContext(writeContext);
|
|
484
|
+
const validated = validateSnapshotSave(
|
|
485
|
+
snapshot,
|
|
486
|
+
expectedRevision,
|
|
487
|
+
assertFlexProjectManagementSnapshot,
|
|
488
|
+
);
|
|
489
|
+
const current = this.snapshots.get(key);
|
|
490
|
+
const actualRevision = current?.revision ?? 0;
|
|
491
|
+
if (current && !sameProjectManagementGeneration(current, validated)) {
|
|
492
|
+
if (!canInitializeProjectManagementGeneration(current, validated, expectedRevision)) {
|
|
493
|
+
throw new FlexHarnessStoreConflictError(key, expectedRevision, actualRevision);
|
|
494
|
+
}
|
|
495
|
+
} else if ((current && 'deletedAt' in current) || actualRevision !== expectedRevision) {
|
|
496
|
+
throw new FlexHarnessStoreConflictError(key, expectedRevision, actualRevision);
|
|
497
|
+
}
|
|
498
|
+
this.snapshots.set(key, validated);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
public async tombstoneSession(
|
|
502
|
+
storageKey: string,
|
|
503
|
+
sessionId: string,
|
|
504
|
+
tombstone: IFlexProjectManagementTombstone,
|
|
505
|
+
expectedRevision: number,
|
|
506
|
+
): Promise<void> {
|
|
507
|
+
const key = providerKey(storageKey, sessionId);
|
|
508
|
+
const validated = validateSnapshotSave(
|
|
509
|
+
tombstone,
|
|
510
|
+
expectedRevision,
|
|
511
|
+
assertFlexProjectManagementTombstone,
|
|
512
|
+
);
|
|
513
|
+
const current = this.snapshots.get(key);
|
|
514
|
+
const actualRevision = current?.revision ?? 0;
|
|
515
|
+
if (current && sameProjectManagementGeneration(current, validated)) {
|
|
516
|
+
if ('deletedAt' in current) return;
|
|
517
|
+
if (actualRevision !== expectedRevision) {
|
|
518
|
+
throw new FlexHarnessStoreConflictError(key, expectedRevision, actualRevision);
|
|
519
|
+
}
|
|
520
|
+
} else if (current) {
|
|
521
|
+
if (!canInitializeProjectManagementGeneration(current, validated, expectedRevision)) {
|
|
522
|
+
throw new FlexHarnessStoreConflictError(key, expectedRevision, actualRevision);
|
|
523
|
+
}
|
|
524
|
+
} else if (actualRevision !== expectedRevision) {
|
|
525
|
+
throw new FlexHarnessStoreConflictError(key, expectedRevision, actualRevision);
|
|
526
|
+
}
|
|
527
|
+
this.snapshots.set(key, validated);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
public async purgeNamespace(storageKey: string): Promise<void> {
|
|
531
|
+
validateIdentifier(storageKey, 'storageKey');
|
|
532
|
+
for (const key of this.snapshots.keys()) {
|
|
533
|
+
const [recordStorageKey] = JSON.parse(key) as [string, string];
|
|
534
|
+
if (recordStorageKey === storageKey) this.snapshots.delete(key);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
417
539
|
class InMemoryAgentEventStore implements plugins.IAgentEventStoreV2 {
|
|
418
540
|
public readonly eventSchemaVersion = 2 as const;
|
|
419
541
|
private snapshot: plugins.IAgentEventSnapshotV2 | undefined;
|
|
@@ -563,9 +685,18 @@ export class InMemoryFlexHarnessStores implements IFlexHarnessStores {
|
|
|
563
685
|
public readonly permissions: IFlexPermissionStore = new InMemoryPermissionStore();
|
|
564
686
|
public readonly agentEvents: IFlexAgentEventStoreProvider = new InMemoryAgentEventStoreProvider();
|
|
565
687
|
public readonly jobs: IFlexToolJobStoreProvider = new InMemoryToolJobStoreProvider();
|
|
688
|
+
public readonly projectManagement = new InMemoryFlexProjectManagementStore();
|
|
566
689
|
}
|
|
567
690
|
|
|
568
|
-
const jsonDomains = [
|
|
691
|
+
const jsonDomains = [
|
|
692
|
+
'scopes',
|
|
693
|
+
'projections',
|
|
694
|
+
'permissions',
|
|
695
|
+
'projectManagement',
|
|
696
|
+
'events',
|
|
697
|
+
'archives',
|
|
698
|
+
'jobs',
|
|
699
|
+
] as const;
|
|
569
700
|
type TJsonDomain = (typeof jsonDomains)[number];
|
|
570
701
|
|
|
571
702
|
interface IDirectorySyncAttempt {
|
|
@@ -621,6 +752,11 @@ class JsonFileRoot {
|
|
|
621
752
|
);
|
|
622
753
|
}
|
|
623
754
|
|
|
755
|
+
public sessionNamespacePath(domain: TJsonDomain, storageKey: string): string {
|
|
756
|
+
validateIdentifier(storageKey, 'storageKey');
|
|
757
|
+
return plugins.path.join(this.directory, domain, this.digest(storageKey));
|
|
758
|
+
}
|
|
759
|
+
|
|
624
760
|
public archivePath(storageKey: string, sessionId: string, archiveId: string): string {
|
|
625
761
|
validateIdentifier(archiveId, 'archiveId');
|
|
626
762
|
return plugins.path.join(
|
|
@@ -1058,6 +1194,127 @@ class JsonPermissionStore implements IFlexPermissionStore {
|
|
|
1058
1194
|
}
|
|
1059
1195
|
}
|
|
1060
1196
|
|
|
1197
|
+
class JsonProjectManagementStore implements IFlexProjectManagementStore {
|
|
1198
|
+
constructor(private readonly root: JsonFileRoot) {}
|
|
1199
|
+
|
|
1200
|
+
private inNamespaceQueue<T>(storageKey: string, operation: () => Promise<T>): Promise<T> {
|
|
1201
|
+
return JsonFileRoot.inQueue(
|
|
1202
|
+
`project-management:${this.root.sessionNamespacePath('projectManagement', storageKey)}`,
|
|
1203
|
+
operation,
|
|
1204
|
+
);
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
public async load(
|
|
1208
|
+
storageKey: string,
|
|
1209
|
+
sessionId: string,
|
|
1210
|
+
): Promise<TFlexProjectManagementRecord | undefined> {
|
|
1211
|
+
const filePath = this.root.sessionPath('projectManagement', storageKey, sessionId);
|
|
1212
|
+
return this.inNamespaceQueue(storageKey, () =>
|
|
1213
|
+
JsonFileRoot.inQueue(filePath, async () => {
|
|
1214
|
+
const snapshot = await this.root.read(filePath, (value) => {
|
|
1215
|
+
assertFlexProjectManagementRecord(value);
|
|
1216
|
+
return value;
|
|
1217
|
+
});
|
|
1218
|
+
return snapshot ? cloneSerializable(snapshot) : undefined;
|
|
1219
|
+
})
|
|
1220
|
+
);
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
public async save(
|
|
1224
|
+
storageKey: string,
|
|
1225
|
+
sessionId: string,
|
|
1226
|
+
snapshot: IFlexProjectManagementSnapshot,
|
|
1227
|
+
expectedRevision: number,
|
|
1228
|
+
writeContext: IFlexProjectManagementWriteContext,
|
|
1229
|
+
): Promise<void> {
|
|
1230
|
+
validateProjectManagementWriteContext(writeContext);
|
|
1231
|
+
const validated = validateSnapshotSave(
|
|
1232
|
+
snapshot,
|
|
1233
|
+
expectedRevision,
|
|
1234
|
+
assertFlexProjectManagementSnapshot,
|
|
1235
|
+
);
|
|
1236
|
+
const filePath = this.root.sessionPath('projectManagement', storageKey, sessionId);
|
|
1237
|
+
await this.inNamespaceQueue(storageKey, () =>
|
|
1238
|
+
JsonFileRoot.inQueue(filePath, async () => {
|
|
1239
|
+
const current = await this.root.read(filePath, (value) => {
|
|
1240
|
+
assertFlexProjectManagementRecord(value);
|
|
1241
|
+
return value;
|
|
1242
|
+
});
|
|
1243
|
+
const actualRevision = current?.revision ?? 0;
|
|
1244
|
+
if (current && !sameProjectManagementGeneration(current, validated)) {
|
|
1245
|
+
if (!canInitializeProjectManagementGeneration(current, validated, expectedRevision)) {
|
|
1246
|
+
throw new FlexHarnessStoreConflictError(
|
|
1247
|
+
providerKey(storageKey, sessionId),
|
|
1248
|
+
expectedRevision,
|
|
1249
|
+
actualRevision,
|
|
1250
|
+
);
|
|
1251
|
+
}
|
|
1252
|
+
} else if ((current && 'deletedAt' in current) || actualRevision !== expectedRevision) {
|
|
1253
|
+
throw new FlexHarnessStoreConflictError(
|
|
1254
|
+
providerKey(storageKey, sessionId),
|
|
1255
|
+
expectedRevision,
|
|
1256
|
+
actualRevision,
|
|
1257
|
+
);
|
|
1258
|
+
}
|
|
1259
|
+
await this.root.write(filePath, validated);
|
|
1260
|
+
}),
|
|
1261
|
+
);
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
public async tombstoneSession(
|
|
1265
|
+
storageKey: string,
|
|
1266
|
+
sessionId: string,
|
|
1267
|
+
tombstone: IFlexProjectManagementTombstone,
|
|
1268
|
+
expectedRevision: number,
|
|
1269
|
+
): Promise<void> {
|
|
1270
|
+
const validated = validateSnapshotSave(
|
|
1271
|
+
tombstone,
|
|
1272
|
+
expectedRevision,
|
|
1273
|
+
assertFlexProjectManagementTombstone,
|
|
1274
|
+
);
|
|
1275
|
+
const filePath = this.root.sessionPath('projectManagement', storageKey, sessionId);
|
|
1276
|
+
await this.inNamespaceQueue(storageKey, () =>
|
|
1277
|
+
JsonFileRoot.inQueue(filePath, async () => {
|
|
1278
|
+
const current = await this.root.read(filePath, (value) => {
|
|
1279
|
+
assertFlexProjectManagementRecord(value);
|
|
1280
|
+
return value;
|
|
1281
|
+
});
|
|
1282
|
+
const actualRevision = current?.revision ?? 0;
|
|
1283
|
+
if (current && sameProjectManagementGeneration(current, validated)) {
|
|
1284
|
+
if ('deletedAt' in current) return;
|
|
1285
|
+
if (actualRevision !== expectedRevision) {
|
|
1286
|
+
throw new FlexHarnessStoreConflictError(
|
|
1287
|
+
providerKey(storageKey, sessionId),
|
|
1288
|
+
expectedRevision,
|
|
1289
|
+
actualRevision,
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1292
|
+
} else if (current) {
|
|
1293
|
+
if (!canInitializeProjectManagementGeneration(current, validated, expectedRevision)) {
|
|
1294
|
+
throw new FlexHarnessStoreConflictError(
|
|
1295
|
+
providerKey(storageKey, sessionId),
|
|
1296
|
+
expectedRevision,
|
|
1297
|
+
actualRevision,
|
|
1298
|
+
);
|
|
1299
|
+
}
|
|
1300
|
+
} else if (actualRevision !== expectedRevision) {
|
|
1301
|
+
throw new FlexHarnessStoreConflictError(
|
|
1302
|
+
providerKey(storageKey, sessionId),
|
|
1303
|
+
expectedRevision,
|
|
1304
|
+
actualRevision,
|
|
1305
|
+
);
|
|
1306
|
+
}
|
|
1307
|
+
await this.root.write(filePath, validated);
|
|
1308
|
+
}),
|
|
1309
|
+
);
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
public async purgeNamespace(storageKey: string): Promise<void> {
|
|
1313
|
+
const directory = this.root.sessionNamespacePath('projectManagement', storageKey);
|
|
1314
|
+
await this.inNamespaceQueue(storageKey, () => this.root.deleteDirectory(directory));
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1061
1318
|
async function clearJsonAgentEventSession(
|
|
1062
1319
|
root: JsonFileRoot,
|
|
1063
1320
|
storageKey: string,
|
|
@@ -1305,6 +1562,7 @@ export class JsonFileFlexHarnessStores implements IFlexHarnessStores {
|
|
|
1305
1562
|
public readonly permissions: IFlexPermissionStore;
|
|
1306
1563
|
public readonly agentEvents: IFlexAgentEventStoreProvider;
|
|
1307
1564
|
public readonly jobs: IFlexToolJobStoreProvider;
|
|
1565
|
+
public readonly projectManagement: IFlexProjectManagementStore;
|
|
1308
1566
|
private readonly root: JsonFileRoot;
|
|
1309
1567
|
|
|
1310
1568
|
constructor(options: IJsonFileFlexHarnessStoresOptions) {
|
|
@@ -1315,6 +1573,7 @@ export class JsonFileFlexHarnessStores implements IFlexHarnessStores {
|
|
|
1315
1573
|
this.scopes = new JsonScopeStore(this.root);
|
|
1316
1574
|
this.projections = new JsonProjectionStore(this.root);
|
|
1317
1575
|
this.permissions = new JsonPermissionStore(this.root);
|
|
1576
|
+
this.projectManagement = new JsonProjectManagementStore(this.root);
|
|
1318
1577
|
this.agentEvents = new JsonAgentEventStoreProvider(this.root);
|
|
1319
1578
|
this.jobs = new JsonToolJobStoreProvider(this.root);
|
|
1320
1579
|
}
|
package/ts/index.ts
CHANGED
|
@@ -3,6 +3,12 @@ export * from './classes.stores.js';
|
|
|
3
3
|
export * from './errors.js';
|
|
4
4
|
export * from './interfaces.js';
|
|
5
5
|
export { normalizeJsonValue } from './utils.json.js';
|
|
6
|
+
export {
|
|
7
|
+
assertFlexProjectManagementSnapshot,
|
|
8
|
+
assertFlexProjectManagementRecord,
|
|
9
|
+
assertFlexProjectManagementTombstone,
|
|
10
|
+
createEmptyFlexProjectManagementSnapshot,
|
|
11
|
+
} from './utils.projectmanagement.js';
|
|
6
12
|
export {
|
|
7
13
|
FLEX_SLASH_COMMAND_MAX_INPUT_BYTES,
|
|
8
14
|
parseSlashCommand,
|
package/ts/interfaces.ts
CHANGED
|
@@ -135,6 +135,8 @@ export interface IFlexSessionActivity {
|
|
|
135
135
|
export interface IFlexSession {
|
|
136
136
|
scopeId: string;
|
|
137
137
|
sessionId: string;
|
|
138
|
+
sessionGenerationId?: string;
|
|
139
|
+
sessionGenerationSequence?: number;
|
|
138
140
|
title?: string;
|
|
139
141
|
createdAt: string;
|
|
140
142
|
updatedAt: string;
|
|
@@ -428,6 +430,8 @@ export interface IFlexResourceToolProviderResolver<TScope> {
|
|
|
428
430
|
|
|
429
431
|
export interface IFlexSessionTombstone {
|
|
430
432
|
sessionId: string;
|
|
433
|
+
sessionGenerationId?: string;
|
|
434
|
+
sessionGenerationSequence?: number;
|
|
431
435
|
deletedAt: string;
|
|
432
436
|
rootSessionId?: string;
|
|
433
437
|
depth?: number;
|
|
@@ -564,6 +568,118 @@ export interface IFlexPermissionSnapshot {
|
|
|
564
568
|
rememberedPermissionKeys: string[];
|
|
565
569
|
}
|
|
566
570
|
|
|
571
|
+
export const FLEX_PROJECT_MANAGEMENT_SCHEMA_VERSION = 1 as const;
|
|
572
|
+
export const FLEX_SESSION_GENERATION_ID_MAX_BYTES = 128;
|
|
573
|
+
|
|
574
|
+
export const FLEX_PROJECT_MANAGEMENT_LIMITS = Object.freeze({
|
|
575
|
+
maxGoalBytes: 8 * 1024,
|
|
576
|
+
maxScratchpadBytes: 128 * 1024,
|
|
577
|
+
maxTaskContentBytes: 8 * 1024,
|
|
578
|
+
maxTaskIdBytes: 512,
|
|
579
|
+
maxTasks: 512,
|
|
580
|
+
maxTitleBytes: 2048,
|
|
581
|
+
maxSnapshotBytes: 1024 * 1024,
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
export type TFlexProjectTaskStatus = 'pending' | 'in_progress' | 'completed' | 'cancelled';
|
|
585
|
+
export type TFlexProjectTaskPriority = 'high' | 'medium' | 'low';
|
|
586
|
+
|
|
587
|
+
export interface IFlexProjectTask {
|
|
588
|
+
id: string;
|
|
589
|
+
content: string;
|
|
590
|
+
status: TFlexProjectTaskStatus;
|
|
591
|
+
priority: TFlexProjectTaskPriority;
|
|
592
|
+
createdAt: string;
|
|
593
|
+
updatedAt: string;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
export interface IFlexProjectManagementSnapshot {
|
|
597
|
+
schemaVersion: 1;
|
|
598
|
+
revision: number;
|
|
599
|
+
sessionGenerationId: string;
|
|
600
|
+
sessionGenerationSequence: number;
|
|
601
|
+
goal?: string;
|
|
602
|
+
scratchpad: string;
|
|
603
|
+
tasks: IFlexProjectTask[];
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
export interface IFlexProjectManagementTombstone {
|
|
607
|
+
schemaVersion: 1;
|
|
608
|
+
revision: number;
|
|
609
|
+
sessionGenerationId: string;
|
|
610
|
+
sessionGenerationSequence: number;
|
|
611
|
+
deletedAt: string;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
export type TFlexProjectManagementRecord =
|
|
615
|
+
| IFlexProjectManagementSnapshot
|
|
616
|
+
| IFlexProjectManagementTombstone;
|
|
617
|
+
|
|
618
|
+
export type TFlexProjectManagementWriteActor = 'agent' | 'application';
|
|
619
|
+
|
|
620
|
+
export interface IFlexProjectManagementWriteContext {
|
|
621
|
+
actor: TFlexProjectManagementWriteActor;
|
|
622
|
+
runId?: string;
|
|
623
|
+
agent?: string;
|
|
624
|
+
toolCallId?: string;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
export interface IFlexProjectManagementStore {
|
|
628
|
+
load(
|
|
629
|
+
storageKey: string,
|
|
630
|
+
sessionId: string,
|
|
631
|
+
): Promise<TFlexProjectManagementRecord | undefined>;
|
|
632
|
+
save(
|
|
633
|
+
storageKey: string,
|
|
634
|
+
sessionId: string,
|
|
635
|
+
snapshot: IFlexProjectManagementSnapshot,
|
|
636
|
+
expectedRevision: number,
|
|
637
|
+
writeContext: IFlexProjectManagementWriteContext,
|
|
638
|
+
): Promise<void>;
|
|
639
|
+
tombstoneSession(
|
|
640
|
+
storageKey: string,
|
|
641
|
+
sessionId: string,
|
|
642
|
+
tombstone: IFlexProjectManagementTombstone,
|
|
643
|
+
expectedRevision: number,
|
|
644
|
+
): Promise<void>;
|
|
645
|
+
purgeNamespace(storageKey: string): Promise<void>;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
export interface IFlexCreateProjectTaskInput {
|
|
649
|
+
id: string;
|
|
650
|
+
content: string;
|
|
651
|
+
status?: TFlexProjectTaskStatus;
|
|
652
|
+
priority?: TFlexProjectTaskPriority;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
export interface IFlexUpdateProjectTaskInput {
|
|
656
|
+
id: string;
|
|
657
|
+
content?: string;
|
|
658
|
+
status?: TFlexProjectTaskStatus;
|
|
659
|
+
priority?: TFlexProjectTaskPriority;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
export interface IFlexProjectStateResult {
|
|
663
|
+
revision: number;
|
|
664
|
+
state: IFlexProjectManagementSnapshot;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
export interface IFlexProjectTaskResult extends IFlexProjectStateResult {
|
|
668
|
+
task: IFlexProjectTask;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
export interface IFlexProjectTasksResult extends IFlexProjectStateResult {
|
|
672
|
+
tasks: IFlexProjectTask[];
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
export interface IFlexProjectGoalResult extends IFlexProjectStateResult {
|
|
676
|
+
goal: string | null;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
export interface IFlexProjectScratchpadResult extends IFlexProjectStateResult {
|
|
680
|
+
scratchpad: string;
|
|
681
|
+
}
|
|
682
|
+
|
|
567
683
|
export interface IFlexScopeStore {
|
|
568
684
|
load(storageKey: string): Promise<IFlexScopeSnapshot | undefined>;
|
|
569
685
|
save(
|
|
@@ -617,6 +733,7 @@ export interface IFlexHarnessStores {
|
|
|
617
733
|
scopes: IFlexScopeStore;
|
|
618
734
|
projections: IFlexProjectionStore;
|
|
619
735
|
permissions: IFlexPermissionStore;
|
|
736
|
+
projectManagement: IFlexProjectManagementStore;
|
|
620
737
|
agentEvents: IFlexAgentEventStoreProvider;
|
|
621
738
|
jobs: IFlexToolJobStoreProvider;
|
|
622
739
|
}
|
|
@@ -875,6 +992,17 @@ export interface IFlexSubagentDefinition {
|
|
|
875
992
|
maxSteps?: number;
|
|
876
993
|
}
|
|
877
994
|
|
|
995
|
+
export interface IFlexProjectManagementBuiltInToolsOptions {
|
|
996
|
+
task?: boolean;
|
|
997
|
+
goal?: boolean;
|
|
998
|
+
scratchpad?: boolean;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
export interface IFlexBuiltInToolsOptions {
|
|
1002
|
+
renameSession?: boolean;
|
|
1003
|
+
projectManagement?: IFlexProjectManagementBuiltInToolsOptions;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
878
1006
|
export interface IFlexHarnessOptions<TScope> {
|
|
879
1007
|
scopeResolver: IFlexScopeResolver<TScope>;
|
|
880
1008
|
modelResolver: IFlexModelResolver<TScope>;
|
|
@@ -891,6 +1019,7 @@ export interface IFlexHarnessOptions<TScope> {
|
|
|
891
1019
|
reversionPolicy?: TFlexReversionPolicy;
|
|
892
1020
|
externalErrorProjector?: TFlexExternalErrorProjector;
|
|
893
1021
|
subagents?: IFlexSubagentDefinition[];
|
|
1022
|
+
builtInTools?: IFlexBuiltInToolsOptions;
|
|
894
1023
|
slashCommands?: readonly TFlexSlashCommandRegistration<TScope>[];
|
|
895
1024
|
maxSubagentDepth?: number;
|
|
896
1025
|
maxSubagentCallsPerRun?: number;
|
package/ts/utils.json.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
FLEX_REVERSION_REFERENCE_MAX_BYTES,
|
|
7
7
|
FLEX_REVERSION_WORKSPACE_ID_MAX_BYTES,
|
|
8
8
|
FLEX_REVERSION_WORKSPACE_LABEL_MAX_BYTES,
|
|
9
|
+
FLEX_SESSION_GENERATION_ID_MAX_BYTES,
|
|
9
10
|
} from './interfaces.js';
|
|
10
11
|
import type {
|
|
11
12
|
IFlexJsonLimits,
|
|
@@ -432,6 +433,39 @@ const activityStatuses = [
|
|
|
432
433
|
'cancelled',
|
|
433
434
|
] as const;
|
|
434
435
|
|
|
436
|
+
function validateOptionalSessionGeneration(
|
|
437
|
+
value: Record<string, unknown>,
|
|
438
|
+
path: string,
|
|
439
|
+
): { sessionGenerationId?: string; sessionGenerationSequence?: number } {
|
|
440
|
+
const hasId = value.sessionGenerationId !== undefined;
|
|
441
|
+
const hasSequence = value.sessionGenerationSequence !== undefined;
|
|
442
|
+
if (hasId !== hasSequence) {
|
|
443
|
+
throw new FlexHarnessStoreFormatError(`${path} has partial session generation fields.`);
|
|
444
|
+
}
|
|
445
|
+
if (!hasId) return {};
|
|
446
|
+
const sessionGenerationId = requireString(
|
|
447
|
+
value.sessionGenerationId,
|
|
448
|
+
`${path}.sessionGenerationId`,
|
|
449
|
+
);
|
|
450
|
+
if (Buffer.byteLength(sessionGenerationId, 'utf8') > FLEX_SESSION_GENERATION_ID_MAX_BYTES) {
|
|
451
|
+
throw new FlexHarnessStoreFormatError(
|
|
452
|
+
`${path}.sessionGenerationId exceeds ${FLEX_SESSION_GENERATION_ID_MAX_BYTES} UTF-8 bytes.`,
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
if (
|
|
456
|
+
!Number.isSafeInteger(value.sessionGenerationSequence)
|
|
457
|
+
|| Number(value.sessionGenerationSequence) < 1
|
|
458
|
+
) {
|
|
459
|
+
throw new FlexHarnessStoreFormatError(
|
|
460
|
+
`${path}.sessionGenerationSequence must be a positive integer.`,
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
return {
|
|
464
|
+
sessionGenerationId,
|
|
465
|
+
sessionGenerationSequence: value.sessionGenerationSequence as number,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
435
469
|
function validateSession(value: unknown, path: string): IValidatedSessionIdentity {
|
|
436
470
|
const session = requireRecord(value, path);
|
|
437
471
|
requireOnlyKeys(
|
|
@@ -439,6 +473,8 @@ function validateSession(value: unknown, path: string): IValidatedSessionIdentit
|
|
|
439
473
|
[
|
|
440
474
|
'scopeId',
|
|
441
475
|
'sessionId',
|
|
476
|
+
'sessionGenerationId',
|
|
477
|
+
'sessionGenerationSequence',
|
|
442
478
|
'title',
|
|
443
479
|
'createdAt',
|
|
444
480
|
'updatedAt',
|
|
@@ -455,6 +491,7 @@ function validateSession(value: unknown, path: string): IValidatedSessionIdentit
|
|
|
455
491
|
);
|
|
456
492
|
requireString(session.scopeId, `${path}.scopeId`);
|
|
457
493
|
const sessionId = requireString(session.sessionId, `${path}.sessionId`);
|
|
494
|
+
const generation = validateOptionalSessionGeneration(session, path);
|
|
458
495
|
requireOptionalString(session.title, `${path}.title`);
|
|
459
496
|
requireString(session.createdAt, `${path}.createdAt`);
|
|
460
497
|
requireString(session.updatedAt, `${path}.updatedAt`);
|
|
@@ -496,6 +533,7 @@ function validateSession(value: unknown, path: string): IValidatedSessionIdentit
|
|
|
496
533
|
}
|
|
497
534
|
return {
|
|
498
535
|
sessionId,
|
|
536
|
+
...generation,
|
|
499
537
|
...(relationshipCount === 0
|
|
500
538
|
? {}
|
|
501
539
|
: {
|
|
@@ -541,6 +579,8 @@ interface IValidatedMessageIdentity {
|
|
|
541
579
|
|
|
542
580
|
interface IValidatedSessionIdentity {
|
|
543
581
|
sessionId: string;
|
|
582
|
+
sessionGenerationId?: string;
|
|
583
|
+
sessionGenerationSequence?: number;
|
|
544
584
|
parentSessionId?: string;
|
|
545
585
|
parentRunId?: string;
|
|
546
586
|
parentToolCallId?: string;
|
|
@@ -780,10 +820,19 @@ export function assertFlexScopeSnapshot(value: unknown): asserts value is IFlexS
|
|
|
780
820
|
const tombstone = requireRecord(snapshot.tombstones[index], path);
|
|
781
821
|
requireOnlyKeys(
|
|
782
822
|
tombstone,
|
|
783
|
-
[
|
|
823
|
+
[
|
|
824
|
+
'sessionId',
|
|
825
|
+
'sessionGenerationId',
|
|
826
|
+
'sessionGenerationSequence',
|
|
827
|
+
'deletedAt',
|
|
828
|
+
'rootSessionId',
|
|
829
|
+
'depth',
|
|
830
|
+
'parentSessionId',
|
|
831
|
+
],
|
|
784
832
|
path,
|
|
785
833
|
);
|
|
786
834
|
const sessionId = requireString(tombstone.sessionId, `${path}.sessionId`);
|
|
835
|
+
validateOptionalSessionGeneration(tombstone, path);
|
|
787
836
|
requireString(tombstone.deletedAt, `${path}.deletedAt`);
|
|
788
837
|
const parentSessionId = tombstone.parentSessionId === undefined
|
|
789
838
|
? undefined
|