@modelprofile.com/flexharness 3.6.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'
@@ -349,13 +452,69 @@ export interface IFlexTerminalProjection {
349
452
  steps?: number;
350
453
  }
351
454
 
352
- export interface IFlexProjectionSnapshot {
455
+ export interface IFlexProjectionSnapshotV1 {
353
456
  schemaVersion: 1;
354
457
  revision: number;
355
458
  messages: IFlexMessage[];
356
459
  stagedTerminals: IFlexTerminalProjection[];
357
460
  }
358
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
+
359
518
  export interface IFlexPermissionSnapshot {
360
519
  schemaVersion: 1;
361
520
  revision: number;
@@ -372,11 +531,11 @@ export interface IFlexScopeStore {
372
531
  }
373
532
 
374
533
  export interface IFlexProjectionStore {
375
- load(storageKey: string, sessionId: string): Promise<IFlexProjectionSnapshot | undefined>;
534
+ load(storageKey: string, sessionId: string): Promise<TFlexProjectionSnapshot | undefined>;
376
535
  save(
377
536
  storageKey: string,
378
537
  sessionId: string,
379
- snapshot: IFlexProjectionSnapshot,
538
+ snapshot: IFlexProjectionSnapshotCurrent,
380
539
  expectedRevision: number,
381
540
  ): Promise<void>;
382
541
  deleteSession(storageKey: string, sessionId: string): Promise<void>;
@@ -438,6 +597,89 @@ export interface IFlexPromptQueueLimits {
438
597
  maxTerminalEntriesPerSession?: number;
439
598
  }
440
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
+
441
683
  export type TFlexExternalErrorSource =
442
684
  | 'modelResolver'
443
685
  | 'toolProvider'
@@ -445,7 +687,9 @@ export type TFlexExternalErrorSource =
445
687
  | 'toolCallback'
446
688
  | 'toolCleanup'
447
689
  | 'agentSession'
448
- | 'persistence';
690
+ | 'persistence'
691
+ | 'slashCommand'
692
+ | 'turnReversion';
449
693
 
450
694
  export interface IFlexExternalErrorContext {
451
695
  source: TFlexExternalErrorSource;
@@ -524,8 +768,11 @@ export interface IFlexHarnessOptions<TScope> {
524
768
  toolOutputLimits?: IFlexJsonLimits;
525
769
  callbackLimits?: IFlexCallbackLimits;
526
770
  promptQueueLimits?: IFlexPromptQueueLimits;
771
+ reversionLimits?: IFlexReversionLimits;
772
+ turnReversionProvider?: IFlexTurnReversionProvider<TScope>;
527
773
  externalErrorProjector?: TFlexExternalErrorProjector;
528
774
  subagents?: IFlexSubagentDefinition[];
775
+ slashCommands?: readonly TFlexSlashCommandRegistration<TScope>[];
529
776
  maxSubagentDepth?: number;
530
777
  maxSubagentCallsPerRun?: number;
531
778
  }
@@ -645,6 +892,12 @@ export interface IFlexPromptQueueEvent extends IFlexEventBase {
645
892
  readonly entry: IFlexPromptQueueEntry;
646
893
  }
647
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
+
648
901
  export interface IFlexErrorInfo {
649
902
  name: string;
650
903
  message: string;
@@ -661,6 +914,7 @@ export type TFlexHarnessEvent =
661
914
  | IFlexPermissionRequestedEvent
662
915
  | IFlexPermissionResolvedEvent
663
916
  | IFlexRunFinishedEvent
664
- | IFlexPromptQueueEvent;
917
+ | IFlexPromptQueueEvent
918
+ | IFlexSessionHistoryChangedEvent;
665
919
 
666
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,