@modelprofile.com/flexharness 3.5.0 → 3.7.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.
@@ -10,11 +10,12 @@ import type {
10
10
  IFlexHarnessStores,
11
11
  IFlexPermissionSnapshot,
12
12
  IFlexPermissionStore,
13
- IFlexProjectionSnapshot,
13
+ IFlexProjectionSnapshotV2,
14
14
  IFlexProjectionStore,
15
15
  IFlexScopeSnapshot,
16
16
  IFlexScopeStore,
17
17
  IFlexToolJobStoreProvider,
18
+ TFlexProjectionSnapshot,
18
19
  } from './interfaces.js';
19
20
  import {
20
21
  assertFlexPermissionSnapshot,
@@ -24,7 +25,7 @@ import {
24
25
  cloneSerializable,
25
26
  } from './utils.json.js';
26
27
 
27
- type TSnapshot = IFlexScopeSnapshot | IFlexProjectionSnapshot | IFlexPermissionSnapshot;
28
+ type TSnapshot = IFlexScopeSnapshot | TFlexProjectionSnapshot | IFlexPermissionSnapshot;
28
29
  type TSnapshotValidator<TSnapshotValue extends TSnapshot> = (
29
30
  value: unknown,
30
31
  ) => asserts value is TSnapshotValue;
@@ -62,7 +63,7 @@ function providerKey(storageKey: string, sessionId: string): string {
62
63
  return JSON.stringify([storageKey, sessionId]);
63
64
  }
64
65
 
65
- function assertProjectionSession(snapshot: IFlexProjectionSnapshot, sessionId: string): void {
66
+ function assertProjectionSession(snapshot: TFlexProjectionSnapshot, sessionId: string): void {
66
67
  for (const message of snapshot.messages) {
67
68
  if (message.sessionId !== sessionId) {
68
69
  throw new FlexHarnessStoreFormatError(
@@ -350,12 +351,12 @@ class InMemoryScopeStore implements IFlexScopeStore {
350
351
  }
351
352
 
352
353
  class InMemoryProjectionStore implements IFlexProjectionStore {
353
- private readonly snapshots = new Map<string, IFlexProjectionSnapshot>();
354
+ private readonly snapshots = new Map<string, TFlexProjectionSnapshot>();
354
355
 
355
356
  public async load(
356
357
  storageKey: string,
357
358
  sessionId: string,
358
- ): Promise<IFlexProjectionSnapshot | undefined> {
359
+ ): Promise<TFlexProjectionSnapshot | undefined> {
359
360
  const key = providerKey(storageKey, sessionId);
360
361
  const snapshot = this.snapshots.get(key);
361
362
  return snapshot ? cloneSerializable(snapshot) : undefined;
@@ -364,7 +365,7 @@ class InMemoryProjectionStore implements IFlexProjectionStore {
364
365
  public async save(
365
366
  storageKey: string,
366
367
  sessionId: string,
367
- snapshot: IFlexProjectionSnapshot,
368
+ snapshot: IFlexProjectionSnapshotV2,
368
369
  expectedRevision: number,
369
370
  ): Promise<void> {
370
371
  const key = providerKey(storageKey, sessionId);
@@ -972,7 +973,7 @@ class JsonProjectionStore implements IFlexProjectionStore {
972
973
  public async load(
973
974
  storageKey: string,
974
975
  sessionId: string,
975
- ): Promise<IFlexProjectionSnapshot | undefined> {
976
+ ): Promise<TFlexProjectionSnapshot | undefined> {
976
977
  const filePath = this.root.sessionPath('projections', storageKey, sessionId);
977
978
  return JsonFileRoot.inQueue(filePath, async () => {
978
979
  const snapshot = await this.root.read(filePath, (value) => {
@@ -987,7 +988,7 @@ class JsonProjectionStore implements IFlexProjectionStore {
987
988
  public async save(
988
989
  storageKey: string,
989
990
  sessionId: string,
990
- snapshot: IFlexProjectionSnapshot,
991
+ snapshot: IFlexProjectionSnapshotV2,
991
992
  expectedRevision: number,
992
993
  ): Promise<void> {
993
994
  const validated = validateSnapshotSave(snapshot, expectedRevision, assertFlexProjectionSnapshot);
package/ts/errors.ts CHANGED
@@ -38,6 +38,31 @@ export class FlexHarnessSessionBusyError extends FlexHarnessError {
38
38
  }
39
39
  }
40
40
 
41
+ export class FlexHarnessSlashCommandUnavailableError extends FlexHarnessError {
42
+ public readonly commandName: string;
43
+ public readonly unavailableReason: string;
44
+
45
+ constructor(commandName: string, unavailableReason: string) {
46
+ super(
47
+ 'FLEX_SLASH_COMMAND_UNAVAILABLE',
48
+ `Slash command "/${commandName}" is unavailable: ${unavailableReason}`,
49
+ );
50
+ this.name = 'FlexHarnessSlashCommandUnavailableError';
51
+ this.commandName = commandName;
52
+ this.unavailableReason = unavailableReason;
53
+ }
54
+ }
55
+
56
+ export class FlexHarnessSlashCommandReentryError extends FlexHarnessError {
57
+ constructor(sessionId: string) {
58
+ super(
59
+ 'FLEX_SLASH_COMMAND_REENTRY',
60
+ `Session "${sessionId}" cannot reenter slash command execution.`,
61
+ );
62
+ this.name = 'FlexHarnessSlashCommandReentryError';
63
+ }
64
+ }
65
+
41
66
  export class FlexHarnessQueueFullError extends FlexHarnessError {
42
67
  constructor(message: string) {
43
68
  super('FLEX_QUEUE_FULL', message);
package/ts/index.ts CHANGED
@@ -3,3 +3,7 @@ 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
+ FLEX_SLASH_COMMAND_MAX_INPUT_BYTES,
8
+ parseSlashCommand,
9
+ } from './utils.slashcommands.js';
package/ts/interfaces.ts CHANGED
@@ -164,6 +164,109 @@ export interface IFlexPromptOptions {
164
164
  maxSteps?: number;
165
165
  }
166
166
 
167
+ export interface IFlexSlashCommandInvocation {
168
+ input: string;
169
+ name: string;
170
+ rawArguments: string;
171
+ arguments: string[];
172
+ }
173
+
174
+ export interface IFlexSlashCommandNotCommandResult {
175
+ type: 'not-command';
176
+ }
177
+
178
+ export interface IFlexSlashCommandMalformedResult {
179
+ type: 'malformed';
180
+ reason: string;
181
+ }
182
+
183
+ export interface IFlexParsedSlashCommand extends IFlexSlashCommandInvocation {
184
+ type: 'parsed';
185
+ }
186
+
187
+ export type TFlexSlashCommandParseResult =
188
+ | IFlexSlashCommandNotCommandResult
189
+ | IFlexSlashCommandMalformedResult
190
+ | IFlexParsedSlashCommand;
191
+
192
+ export type TFlexSlashCommandKind = 'builtin' | 'template' | 'handler';
193
+ export type TFlexSlashCommandWorkspaceReversion = 'supported' | 'unsupported' | 'not-applicable';
194
+
195
+ export interface IFlexSlashCommandDescriptor {
196
+ name: string;
197
+ description: string;
198
+ kind: TFlexSlashCommandKind;
199
+ hints: string[];
200
+ available: boolean;
201
+ unavailableReason?: string;
202
+ workspaceReversion: TFlexSlashCommandWorkspaceReversion;
203
+ }
204
+
205
+ export interface IFlexSlashCommandHandlerContext<TScope> {
206
+ readonly scopeId: string;
207
+ readonly scope: TScope;
208
+ readonly storageKey: string;
209
+ readonly sessionId: string;
210
+ readonly rawArguments: string;
211
+ readonly arguments: readonly string[];
212
+ readonly signal: AbortSignal;
213
+ }
214
+
215
+ export type TFlexSlashCommandHandler<TScope> = (
216
+ context: IFlexSlashCommandHandlerContext<TScope>,
217
+ ) => TJsonValue | void | Promise<TJsonValue | void>;
218
+
219
+ export interface IFlexSlashCommandTemplateRegistration {
220
+ name: string;
221
+ description?: string;
222
+ template: string;
223
+ handler?: never;
224
+ }
225
+
226
+ export interface IFlexSlashCommandHandlerRegistration<TScope> {
227
+ name: string;
228
+ description?: string;
229
+ handler: TFlexSlashCommandHandler<TScope>;
230
+ template?: never;
231
+ }
232
+
233
+ export type TFlexSlashCommandRegistration<TScope> =
234
+ | IFlexSlashCommandTemplateRegistration
235
+ | IFlexSlashCommandHandlerRegistration<TScope>;
236
+
237
+ export interface IFlexSlashCommandExecutionOptions extends IFlexPromptOptions {
238
+ signal?: AbortSignal;
239
+ }
240
+
241
+ export interface IFlexSlashCommandUnknownResult extends IFlexSlashCommandInvocation {
242
+ type: 'unknown';
243
+ }
244
+
245
+ export interface IFlexSlashCommandOperationResult {
246
+ type: 'operation';
247
+ name: string;
248
+ }
249
+
250
+ export interface IFlexSlashCommandHandlerResult {
251
+ type: 'handler-result';
252
+ name: string;
253
+ result: TJsonValue;
254
+ }
255
+
256
+ export interface IFlexSlashCommandPromptAdmissionResult {
257
+ type: 'prompt-admission';
258
+ name: string;
259
+ admission: IFlexPromptAdmission;
260
+ }
261
+
262
+ export type TFlexSlashCommandExecutionResult =
263
+ | IFlexSlashCommandNotCommandResult
264
+ | IFlexSlashCommandMalformedResult
265
+ | IFlexSlashCommandUnknownResult
266
+ | IFlexSlashCommandOperationResult
267
+ | IFlexSlashCommandHandlerResult
268
+ | IFlexSlashCommandPromptAdmissionResult;
269
+
167
270
  export type TFlexPromptQueueStatus =
168
271
  | 'queued'
169
272
  | 'starting'
@@ -300,6 +403,29 @@ export interface IFlexToolProvider<TScope> {
300
403
  ): Promise<IFlexToolHandle | undefined> | IFlexToolHandle | undefined;
301
404
  }
302
405
 
406
+ export interface IFlexResourceToolProviderResolverContext<TScope> {
407
+ scopeId: string;
408
+ scope: TScope;
409
+ sessionId: string;
410
+ runId: string;
411
+ readonly parentSessionId?: string;
412
+ readonly agent?: string;
413
+ signal: AbortSignal;
414
+ }
415
+
416
+ export interface IFlexResourceToolProviderDescriptor<TScope> {
417
+ resourceId: string;
418
+ attachmentRevision: number;
419
+ provider: IFlexToolProvider<TScope>;
420
+ }
421
+
422
+ export interface IFlexResourceToolProviderResolver<TScope> {
423
+ resolveResourceToolProviders(
424
+ context: IFlexResourceToolProviderResolverContext<TScope>,
425
+ ): Promise<readonly IFlexResourceToolProviderDescriptor<TScope>[]>
426
+ | readonly IFlexResourceToolProviderDescriptor<TScope>[];
427
+ }
428
+
303
429
  export interface IFlexSessionTombstone {
304
430
  sessionId: string;
305
431
  deletedAt: string;
@@ -326,13 +452,69 @@ export interface IFlexTerminalProjection {
326
452
  steps?: number;
327
453
  }
328
454
 
329
- export interface IFlexProjectionSnapshot {
455
+ export interface IFlexProjectionSnapshotV1 {
330
456
  schemaVersion: 1;
331
457
  revision: number;
332
458
  messages: IFlexMessage[];
333
459
  stagedTerminals: IFlexTerminalProjection[];
334
460
  }
335
461
 
462
+ export type TFlexReversionSegmentStatus = 'capturing' | 'completed' | 'failed' | 'cancelled';
463
+
464
+ export interface IFlexReversionSegment {
465
+ runId: string;
466
+ userMessageId: string;
467
+ status: TFlexReversionSegmentStatus;
468
+ contextAvailable: boolean;
469
+ eventIds: string[];
470
+ workspaceCaptured: boolean;
471
+ captureId?: string;
472
+ workspaceReference?: TJsonValue;
473
+ }
474
+
475
+ export interface IFlexPendingCaptureReversion {
476
+ kind: 'capture';
477
+ runId: string;
478
+ captureId: string;
479
+ state: 'preparing' | 'prepared' | 'finalizing';
480
+ }
481
+
482
+ export interface IFlexPendingApplyReversion {
483
+ kind: 'apply';
484
+ operationId: string;
485
+ direction: 'undo' | 'redo';
486
+ fromCursor: number;
487
+ toCursor: number;
488
+ segmentRunIds: string[];
489
+ appliedRunIds: string[];
490
+ }
491
+
492
+ export type TFlexPendingReversion =
493
+ | IFlexPendingCaptureReversion
494
+ | IFlexPendingApplyReversion;
495
+
496
+ export interface IFlexPendingReversionRelease {
497
+ runId: string;
498
+ captureId: string;
499
+ reference: TJsonValue;
500
+ }
501
+
502
+ export interface IFlexProjectionSnapshotV2 {
503
+ schemaVersion: 2;
504
+ revision: number;
505
+ messages: IFlexMessage[];
506
+ stagedTerminals: IFlexTerminalProjection[];
507
+ reversionSegments: IFlexReversionSegment[];
508
+ revertCursor: number;
509
+ excludedRunIds: string[];
510
+ pendingReversion?: TFlexPendingReversion;
511
+ pendingReversionReleases: IFlexPendingReversionRelease[];
512
+ }
513
+
514
+ export type IFlexProjectionSnapshot = IFlexProjectionSnapshotV1 | IFlexProjectionSnapshotV2;
515
+ export type TFlexProjectionSnapshot = IFlexProjectionSnapshot;
516
+ export type IFlexProjectionSnapshotCurrent = IFlexProjectionSnapshotV2;
517
+
336
518
  export interface IFlexPermissionSnapshot {
337
519
  schemaVersion: 1;
338
520
  revision: number;
@@ -349,11 +531,11 @@ export interface IFlexScopeStore {
349
531
  }
350
532
 
351
533
  export interface IFlexProjectionStore {
352
- load(storageKey: string, sessionId: string): Promise<IFlexProjectionSnapshot | undefined>;
534
+ load(storageKey: string, sessionId: string): Promise<TFlexProjectionSnapshot | undefined>;
353
535
  save(
354
536
  storageKey: string,
355
537
  sessionId: string,
356
- snapshot: IFlexProjectionSnapshot,
538
+ snapshot: IFlexProjectionSnapshotCurrent,
357
539
  expectedRevision: number,
358
540
  ): Promise<void>;
359
541
  deleteSession(storageKey: string, sessionId: string): Promise<void>;
@@ -415,6 +597,89 @@ export interface IFlexPromptQueueLimits {
415
597
  maxTerminalEntriesPerSession?: number;
416
598
  }
417
599
 
600
+ export interface IFlexReversionLimits {
601
+ maxCompletedTurns?: number;
602
+ maxSegments?: number;
603
+ maxExcludedRunIds?: number;
604
+ maxPendingReversionReleases?: number;
605
+ }
606
+
607
+ export const FLEX_REVERSION_DEFAULT_LIMITS = Object.freeze({
608
+ maxCompletedTurns: 100,
609
+ maxSegments: 300,
610
+ maxExcludedRunIds: 1000,
611
+ maxPendingReversionReleases: 1000,
612
+ });
613
+
614
+ export const FLEX_REVERSION_MAXIMUM_LIMITS = Object.freeze({
615
+ maxCompletedTurns: 1000,
616
+ maxSegments: 3000,
617
+ maxExcludedRunIds: 10_000,
618
+ maxPendingReversionReleases: 10_000,
619
+ });
620
+
621
+ export const FLEX_REVERSION_REFERENCE_MAX_BYTES = 256 * 1024;
622
+
623
+ export interface IFlexTurnReversionBaseContext<TScope> {
624
+ readonly scopeId: string;
625
+ readonly scope: TScope;
626
+ readonly storageKey: string;
627
+ readonly sessionId: string;
628
+ readonly runId: string;
629
+ readonly captureId: string;
630
+ readonly signal: AbortSignal;
631
+ }
632
+
633
+ export interface IFlexTurnReversionCaptureInspection {
634
+ status: 'missing' | 'prepared' | 'finalized' | 'unknown';
635
+ reference?: TJsonValue;
636
+ }
637
+
638
+ export interface IFlexTurnReversionApplyContext<TScope>
639
+ extends IFlexTurnReversionBaseContext<TScope> {
640
+ readonly reference: TJsonValue;
641
+ readonly operationId: string;
642
+ readonly direction: 'undo' | 'redo';
643
+ }
644
+
645
+ export interface IFlexTurnReversionApplyInspection {
646
+ status: 'not-applied' | 'applied' | 'unknown';
647
+ }
648
+
649
+ export interface IFlexTurnReversionReleaseContext<TScope>
650
+ extends IFlexTurnReversionBaseContext<TScope> {
651
+ readonly reference: TJsonValue;
652
+ }
653
+
654
+ export interface IFlexTurnReversionProvider<TScope> {
655
+ prepare(
656
+ context: IFlexTurnReversionBaseContext<TScope>,
657
+ ): Promise<void> | void;
658
+ inspectCapture(
659
+ context: IFlexTurnReversionBaseContext<TScope>,
660
+ ): Promise<IFlexTurnReversionCaptureInspection> | IFlexTurnReversionCaptureInspection;
661
+ finalize(
662
+ context: IFlexTurnReversionBaseContext<TScope>,
663
+ ): Promise<TJsonValue> | TJsonValue;
664
+ apply(
665
+ context: IFlexTurnReversionApplyContext<TScope>,
666
+ ): Promise<void> | void;
667
+ inspectApply(
668
+ context: IFlexTurnReversionApplyContext<TScope>,
669
+ ): Promise<IFlexTurnReversionApplyInspection> | IFlexTurnReversionApplyInspection;
670
+ release(
671
+ context: IFlexTurnReversionReleaseContext<TScope>,
672
+ ): Promise<void> | void;
673
+ }
674
+
675
+ export interface IFlexUndoSessionResult {
676
+ revertedRunId: string;
677
+ }
678
+
679
+ export interface IFlexRedoSessionResult {
680
+ restoredRunId: string;
681
+ }
682
+
418
683
  export type TFlexExternalErrorSource =
419
684
  | 'modelResolver'
420
685
  | 'toolProvider'
@@ -422,7 +687,9 @@ export type TFlexExternalErrorSource =
422
687
  | 'toolCallback'
423
688
  | 'toolCleanup'
424
689
  | 'agentSession'
425
- | 'persistence';
690
+ | 'persistence'
691
+ | 'slashCommand'
692
+ | 'turnReversion';
426
693
 
427
694
  export interface IFlexExternalErrorContext {
428
695
  source: TFlexExternalErrorSource;
@@ -494,14 +761,18 @@ export interface IFlexHarnessOptions<TScope> {
494
761
  scopeResolver: IFlexScopeResolver<TScope>;
495
762
  modelResolver: IFlexModelResolver<TScope>;
496
763
  toolProvider?: IFlexToolProvider<TScope>;
764
+ resourceToolProviderResolver?: IFlexResourceToolProviderResolver<TScope>;
497
765
  executionContextProvider?: IFlexExecutionContextProvider<TScope>;
498
766
  stores?: IFlexHarnessStores;
499
767
  agentSessionPolicy?: IFlexAgentSessionPolicy<TScope>;
500
768
  toolOutputLimits?: IFlexJsonLimits;
501
769
  callbackLimits?: IFlexCallbackLimits;
502
770
  promptQueueLimits?: IFlexPromptQueueLimits;
771
+ reversionLimits?: IFlexReversionLimits;
772
+ turnReversionProvider?: IFlexTurnReversionProvider<TScope>;
503
773
  externalErrorProjector?: TFlexExternalErrorProjector;
504
774
  subagents?: IFlexSubagentDefinition[];
775
+ slashCommands?: readonly TFlexSlashCommandRegistration<TScope>[];
505
776
  maxSubagentDepth?: number;
506
777
  maxSubagentCallsPerRun?: number;
507
778
  }
@@ -621,6 +892,12 @@ export interface IFlexPromptQueueEvent extends IFlexEventBase {
621
892
  readonly entry: IFlexPromptQueueEntry;
622
893
  }
623
894
 
895
+ export interface IFlexSessionHistoryChangedEvent extends IFlexEventBase {
896
+ readonly type: 'session.history.changed';
897
+ readonly direction: 'undo' | 'redo' | 'branch';
898
+ readonly runId?: string;
899
+ }
900
+
624
901
  export interface IFlexErrorInfo {
625
902
  name: string;
626
903
  message: string;
@@ -637,6 +914,7 @@ export type TFlexHarnessEvent =
637
914
  | IFlexPermissionRequestedEvent
638
915
  | IFlexPermissionResolvedEvent
639
916
  | IFlexRunFinishedEvent
640
- | IFlexPromptQueueEvent;
917
+ | IFlexPromptQueueEvent
918
+ | IFlexSessionHistoryChangedEvent;
641
919
 
642
920
  export type TFlexHarnessEventListener = (event: TFlexHarnessEvent) => void;
package/ts/plugins.ts CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  buildModelMessages,
14
14
  ToolJobStoreConflictError,
15
15
  createAgentEvent,
16
+ filterModelVisibleAgentEvents,
16
17
  getAgentGenerationTransactions,
17
18
  modelMessagesToAgentEvents,
18
19
  tool,
@@ -27,6 +28,7 @@ export {
27
28
  buildModelMessages,
28
29
  ToolJobStoreConflictError,
29
30
  createAgentEvent,
31
+ filterModelVisibleAgentEvents,
30
32
  getAgentGenerationTransactions,
31
33
  modelMessagesToAgentEvents,
32
34
  tool,