@modelprofile.com/flexharness 10.24.0 → 10.24.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.
Files changed (69) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/classes.flexharness.admission.d.ts +17 -0
  3. package/dist_ts/classes.flexharness.admission.js +263 -0
  4. package/dist_ts/classes.flexharness.callbacks.d.ts +38 -0
  5. package/dist_ts/classes.flexharness.callbacks.js +313 -0
  6. package/dist_ts/classes.flexharness.compaction.d.ts +25 -0
  7. package/dist_ts/classes.flexharness.compaction.js +87 -0
  8. package/dist_ts/classes.flexharness.configurations.d.ts +11 -0
  9. package/dist_ts/classes.flexharness.configurations.js +137 -0
  10. package/dist_ts/classes.flexharness.core.d.ts +143 -0
  11. package/dist_ts/classes.flexharness.core.js +1538 -0
  12. package/dist_ts/classes.flexharness.d.ts +2 -437
  13. package/dist_ts/classes.flexharness.deletion.d.ts +31 -0
  14. package/dist_ts/classes.flexharness.deletion.js +396 -0
  15. package/dist_ts/classes.flexharness.imports.d.ts +24 -0
  16. package/dist_ts/classes.flexharness.imports.js +345 -0
  17. package/dist_ts/classes.flexharness.js +119 -9776
  18. package/dist_ts/classes.flexharness.lifecycle.d.ts +47 -0
  19. package/dist_ts/classes.flexharness.lifecycle.js +625 -0
  20. package/dist_ts/classes.flexharness.permissions.d.ts +19 -0
  21. package/dist_ts/classes.flexharness.permissions.js +450 -0
  22. package/dist_ts/classes.flexharness.projects.d.ts +55 -0
  23. package/dist_ts/classes.flexharness.projects.js +575 -0
  24. package/dist_ts/classes.flexharness.prompts.d.ts +44 -0
  25. package/dist_ts/classes.flexharness.prompts.js +659 -0
  26. package/dist_ts/classes.flexharness.providers.d.ts +18 -0
  27. package/dist_ts/classes.flexharness.providers.js +267 -0
  28. package/dist_ts/classes.flexharness.results.d.ts +26 -0
  29. package/dist_ts/classes.flexharness.results.js +89 -0
  30. package/dist_ts/classes.flexharness.reversions.d.ts +52 -0
  31. package/dist_ts/classes.flexharness.reversions.js +1306 -0
  32. package/dist_ts/classes.flexharness.runs.d.ts +63 -0
  33. package/dist_ts/classes.flexharness.runs.js +1058 -0
  34. package/dist_ts/classes.flexharness.sessions.d.ts +20 -0
  35. package/dist_ts/classes.flexharness.sessions.js +381 -0
  36. package/dist_ts/classes.flexharness.slashcommands.d.ts +40 -0
  37. package/dist_ts/classes.flexharness.slashcommands.js +458 -0
  38. package/dist_ts/classes.flexharness.subagents.d.ts +31 -0
  39. package/dist_ts/classes.flexharness.subagents.js +690 -0
  40. package/dist_ts/classes.flexharness.tooldispatch.d.ts +53 -0
  41. package/dist_ts/classes.flexharness.tooldispatch.js +418 -0
  42. package/dist_ts/interfaces.flexharness.d.ts +455 -0
  43. package/dist_ts/interfaces.flexharness.js +2 -0
  44. package/dist_ts/utils.flexharness.d.ts +19 -0
  45. package/dist_ts/utils.flexharness.js +83 -0
  46. package/package.json +3 -3
  47. package/ts/00_commitinfo_data.ts +1 -1
  48. package/ts/classes.flexharness.admission.ts +331 -0
  49. package/ts/classes.flexharness.callbacks.ts +352 -0
  50. package/ts/classes.flexharness.compaction.ts +167 -0
  51. package/ts/classes.flexharness.configurations.ts +167 -0
  52. package/ts/classes.flexharness.core.ts +1830 -0
  53. package/ts/classes.flexharness.deletion.ts +616 -0
  54. package/ts/classes.flexharness.imports.ts +414 -0
  55. package/ts/classes.flexharness.lifecycle.ts +786 -0
  56. package/ts/classes.flexharness.permissions.ts +535 -0
  57. package/ts/classes.flexharness.projects.ts +1006 -0
  58. package/ts/classes.flexharness.prompts.ts +857 -0
  59. package/ts/classes.flexharness.providers.ts +322 -0
  60. package/ts/classes.flexharness.results.ts +98 -0
  61. package/ts/classes.flexharness.reversions.ts +1684 -0
  62. package/ts/classes.flexharness.runs.ts +1204 -0
  63. package/ts/classes.flexharness.sessions.ts +483 -0
  64. package/ts/classes.flexharness.slashcommands.ts +705 -0
  65. package/ts/classes.flexharness.subagents.ts +860 -0
  66. package/ts/classes.flexharness.tooldispatch.ts +475 -0
  67. package/ts/classes.flexharness.ts +147 -12567
  68. package/ts/interfaces.flexharness.ts +517 -0
  69. package/ts/utils.flexharness.ts +121 -0
@@ -0,0 +1,517 @@
1
+ import type * as plugins from './plugins.js';
2
+ import type { FlexHarnessAbortError } from './errors.js';
3
+ import type { FlexToolCatalog } from './classes.toolcatalog.js';
4
+ import type { FlexCodeSandbox } from './classes.codesandbox.js';
5
+ import type {
6
+ IFlexAgentContextInvocation,
7
+ IFlexAffectedWorkspace,
8
+ IFlexDelegatedRunAdmissionContext,
9
+ IFlexDelegatedRunAdmissionLease,
10
+ IFlexExecutionContextHandle,
11
+ IFlexImportedRun,
12
+ IFlexMessage,
13
+ IFlexPermissionRequest,
14
+ IFlexPromptAdmission,
15
+ IFlexPromptQueueEntry,
16
+ IFlexPromptOptions,
17
+ IFlexPromptResult,
18
+ IFlexReversionSegment,
19
+ IFlexPendingReversionRelease,
20
+ IFlexResolvedModel,
21
+ IFlexResolvedSessionConfiguration,
22
+ IFlexSession,
23
+ IFlexSessionMetricsState,
24
+ IFlexSessionTombstone,
25
+ IFlexSubagentDefinition,
26
+ IFlexTerminalProjection,
27
+ IFlexToolHandle,
28
+ IFlexToolProviderContext,
29
+ IFlexUsage,
30
+ TFlexHarnessEvent,
31
+ TFlexMessagePart,
32
+ TFlexProjectTaskPriority,
33
+ TFlexProjectTaskStatus,
34
+ TFlexPromptQueueStatus,
35
+ TFlexPendingReversion,
36
+ TFlexProjectionSnapshot,
37
+ TFlexSessionReversionGroupKind,
38
+ TFlexTurnReversionFinalizedOutcome,
39
+ } from './interfaces.js';
40
+ import type { INormalizedFlexPrompt } from './utils.prompt.js';
41
+ import type { TRegisteredSlashCommand } from './utils.harnessoptions.js';
42
+ import type { INormalizedSessionImportOptions } from './utils.sessionimport.js';
43
+ import type { ISessionMetricsOperation } from './utils.sessionmetrics.js';
44
+
45
+ export type TCanonicalOutcome = 'accepted' | 'rejected' | 'interrupted';
46
+ export type TRunPhase =
47
+ | 'admitting'
48
+ | 'scheduled'
49
+ | 'running'
50
+ | 'closing'
51
+ | 'finalizing'
52
+ | 'promoting';
53
+
54
+ /** The invocation a compaction runs under; a manual compaction also carries its requested model hint. */
55
+ export interface ICompactorInvocation<TScope> extends IFlexAgentContextInvocation<TScope> {
56
+ readonly modelHint?: string;
57
+ }
58
+
59
+ export interface IStoredSessionState {
60
+ storageKey: string;
61
+ compactorContext?: IFlexAgentContextInvocation<unknown>;
62
+ session: IFlexSession;
63
+ messages: IFlexMessage[];
64
+ stagedTerminals: IFlexTerminalProjection[];
65
+ reversionSegments: IFlexReversionSegment[];
66
+ revertCursor: number;
67
+ excludedRunIds: string[];
68
+ pendingReversion?: TFlexPendingReversion;
69
+ pendingReversionReleases: IFlexPendingReversionRelease[];
70
+ projectionSchemaVersion: 1 | 2 | 3;
71
+ projectionBaseline?: TFlexProjectionSnapshot;
72
+ projectionReconciliationRequired: boolean;
73
+ projectionRevision: number;
74
+ projectionQueue: Promise<void>;
75
+ metrics: IFlexSessionMetricsState;
76
+ metricsCompaction?: ISessionMetricsOperation;
77
+ /** Retained independently of the error-neutral projectionQueue. */
78
+ metricsFailure?: unknown;
79
+ rememberedPermissionKeys: Set<string>;
80
+ permissionRevision: number;
81
+ permissionQueue: Promise<void>;
82
+ eventStore: plugins.IAgentEventStoreV2;
83
+ agentSession: plugins.IAgentSession;
84
+ agentSessionAbortCompleted: boolean;
85
+ agentSessionCloseCompleted: boolean;
86
+ agentEventStoreReleased: boolean;
87
+ executionContextHandle?: IFlexExecutionContextHandle;
88
+ executionContextCloseCompleted: boolean;
89
+ jobStoreReleased: boolean;
90
+ jobs?: NonNullable<plugins.IToolExecutionContext['jobs']>;
91
+ compactorLifecycleController: AbortController;
92
+ promptQueue: IQueuedPrompt[];
93
+ outstandingPromptsById: Map<string, IQueuedPrompt>;
94
+ terminalPromptQueueEntries: Map<string, IFlexPromptQueueEntry>;
95
+ outstandingPromptBytes: number;
96
+ promptQueueDrain?: Promise<void>;
97
+ }
98
+
99
+ export interface IRetainedSessionCleanup {
100
+ stored: IStoredSessionState;
101
+ domainsCompleted: boolean;
102
+ }
103
+
104
+ export interface IReversionGroup {
105
+ target: IFlexReversionSegment;
106
+ segments: IFlexReversionSegment[];
107
+ kind: TFlexSessionReversionGroupKind;
108
+ affectedWorkspaces: IFlexAffectedWorkspace[];
109
+ affectedWorkspacesTruncated: boolean;
110
+ }
111
+
112
+ export type TNormalizedCaptureInspection =
113
+ | { status: 'missing' | 'prepared' | 'unknown' }
114
+ | { status: 'finalized'; outcome: TFlexTurnReversionFinalizedOutcome };
115
+
116
+ export interface IDetachedCleanup {
117
+ cleanup: () => Promise<void> | void;
118
+ projectError: (error: unknown) => Error;
119
+ completion?: Promise<void>;
120
+ error?: Error;
121
+ }
122
+
123
+ export interface IOrphanedProviderRelease {
124
+ providerName: 'agentEvents' | 'jobs';
125
+ storageKey: string;
126
+ sessionId: string;
127
+ release: () => Promise<void>;
128
+ }
129
+
130
+ export interface IOrphanedTombstoneCleanup {
131
+ storageKey: string;
132
+ sessionId: string;
133
+ completion: Promise<void>;
134
+ }
135
+
136
+ export interface IOrphanedTombstoneOwner<TScope> {
137
+ state: IStorageState;
138
+ rootSessionId: string;
139
+ scopeId: string;
140
+ scope: TScope;
141
+ }
142
+
143
+ export interface IOrphanedExecutionContextOwner {
144
+ storageKey: string;
145
+ sessionId: string;
146
+ }
147
+
148
+ /** A staged session import: its runs stay in memory until the import is committed. */
149
+ export interface IStagedSessionImport {
150
+ importId: string;
151
+ scopeId: string;
152
+ state: IStorageState;
153
+ scope: unknown;
154
+ sessionId: string;
155
+ options: INormalizedSessionImportOptions;
156
+ identity: string;
157
+ runs: IFlexImportedRun[];
158
+ runIds: Set<string>;
159
+ messageIds: Set<string>;
160
+ messageCount: number;
161
+ bytes: number;
162
+ phase: 'staging' | 'committing';
163
+ /** Discards the import when it stays idle; cleared once the import commits or is released. */
164
+ idleTimer?: ReturnType<typeof setTimeout>;
165
+ }
166
+
167
+ export interface IStorageState {
168
+ storageKey: string;
169
+ compactorLifecycleController: AbortController;
170
+ scopeIdHint: string;
171
+ scopeContext: { scopeId: string; scope: unknown };
172
+ revision: number;
173
+ sessions: Map<string, IStoredSessionState>;
174
+ retainedSessionCleanups: Map<string, IRetainedSessionCleanup>;
175
+ sessionDeletions: Map<string, Promise<void>>;
176
+ initializingSessions: Set<string>;
177
+ sessionInitializations: Map<string, Promise<void>>;
178
+ /** Staged session imports by session id; their ids are also in `initializingSessions`. */
179
+ sessionImports: Map<string, IStagedSessionImport>;
180
+ tombstones: Map<string, IFlexSessionTombstone>;
181
+ tombstoneCleanups: Map<string, Promise<void>>;
182
+ activeRuns: Map<string, IActiveRun>;
183
+ delegatedRunAdmissionOwners: Set<IDelegatedRunAdmissionOwner>;
184
+ pendingPermissions: Map<string, IPendingPermission>;
185
+ /**
186
+ * Per session, the open sections that change what a session view shows and emit the events
187
+ * describing it. A view waits until none is open, so it never shows a change without its event.
188
+ */
189
+ publications: Map<string, Set<Promise<void>>>;
190
+ scopeQueue: Promise<void>;
191
+ lifecycle: 'active' | 'retiring' | 'fenced' | 'retired';
192
+ fenceInProgress: boolean;
193
+ fenceAdditionalErrors: unknown[];
194
+ detachedCleanups: Set<IDetachedCleanup>;
195
+ }
196
+
197
+ export interface IActiveRun {
198
+ state: IStorageState;
199
+ stored: IStoredSessionState;
200
+ originCompactorContext: IFlexAgentContextInvocation<unknown>;
201
+ deferredCompactorContext?: IFlexAgentContextInvocation<unknown>;
202
+ scopeId: string;
203
+ scope: unknown;
204
+ sessionId: string;
205
+ queueId: string;
206
+ runId: string;
207
+ userMessageId: string;
208
+ assistantMessageId: string;
209
+ controller: AbortController;
210
+ transaction?: plugins.IAgentGenerationHandle;
211
+ scheduleKey?: string;
212
+ debounceMs?: number;
213
+ ownerCancellation?: FlexHarnessAbortError;
214
+ internalFailure?: unknown;
215
+ callbackError?: unknown;
216
+ callbackEventCount: number;
217
+ callbackOutputBytes: number;
218
+ callbackParts: TFlexMessagePart[];
219
+ partTextUtf8Bytes: Map<string, number>;
220
+ callbacksClosed: boolean;
221
+ /** Set once the run emitted its `run.finished` event. */
222
+ finishedEventEmitted: boolean;
223
+ /** The usage the provider reported for the run's model calls so far. */
224
+ usage: IFlexUsage;
225
+ metricsOperation?: ISessionMetricsOperation;
226
+ reasoningPartIds: Map<string, string>;
227
+ toolPartIds: Map<string, string>;
228
+ trustedToolExecutionErrors: Map<string, Error>;
229
+ /**
230
+ * The confirmations of read-only calls started while the model still streamed, by tool call ID:
231
+ * their permission requests wait for them.
232
+ */
233
+ unconfirmedToolCalls: Map<string, Promise<void>>;
234
+ /**
235
+ * The read-only calls of the turn by real name and canonical params, with the call that answers
236
+ * them. A call that is not read-only clears them when it runs.
237
+ */
238
+ readCalls: Map<string, IRunReadCall>;
239
+ /** Calls an identical earlier read answered: the call's ID to the earlier call's. */
240
+ deduplicatedToolCalls: Map<string, string>;
241
+ subagentCallCount: number;
242
+ subagentSessionIds: Set<string>;
243
+ backgroundSubagents: Set<Promise<void>>;
244
+ cacheDispatch?: plugins.ISmartAiCacheDispatch;
245
+ pendingPermissionIds: Set<string>;
246
+ phase: TRunPhase;
247
+ reservedUserMessage?: IFlexMessage;
248
+ reservedAssistantMessage?: IFlexMessage;
249
+ modelResolution?: IFlexResolvedModel;
250
+ /** The tool-provider context of the run, passed to catalog methods. */
251
+ toolProviderContext?: Readonly<IFlexToolProviderContext<unknown>>;
252
+ /** The catalog methods the run offers through search_tools, describe_tools and execute. */
253
+ toolCatalog?: FlexToolCatalog<unknown>;
254
+ /** The sandbox `run_code` programs run in, loaded when the catalog has code mode. */
255
+ codeSandbox?: FlexCodeSandbox;
256
+ /** The tool handle's classifier of read-only calls. */
257
+ isReadOnlyHostCall?: IFlexToolHandle['isReadOnlyToolCall'];
258
+ subagents: ReadonlyMap<string, Readonly<IFlexSubagentDefinition>>;
259
+ allowedTools?: readonly string[];
260
+ agentAllowedTools?: readonly string[];
261
+ permissionPolicy?: IFlexResolvedSessionConfiguration<unknown>['permissionPolicy'];
262
+ delegatedRunAdmissionOwner?: IDelegatedRunAdmissionOwner;
263
+ delegatedRunAdmissionClose?: Promise<void>;
264
+ completion: Promise<IFlexPromptResult>;
265
+ steering: IRunSteering;
266
+ /** Stops the run's generation for its owner, so what the model produced stays in context. */
267
+ stop: AbortController;
268
+ }
269
+
270
+ /** The validated params of a `results.get` call. */
271
+ export interface IResultRead {
272
+ handle: string;
273
+ page?: number;
274
+ path?: string;
275
+ }
276
+
277
+ /** A read-only call of a turn and its output, which identical reads after it share. */
278
+ export interface IRunReadCall {
279
+ readonly toolCallId: string;
280
+ /** The call's output; `notShared` when it streams its output, which only its own call reads. */
281
+ readonly output: Promise<unknown>;
282
+ }
283
+
284
+ /** The steers of a run: the input its generation takes in, and the prompts it accepted for it. */
285
+ export interface IRunSteering {
286
+ readonly input: plugins.AgentPendingInputQueue;
287
+ /** The session whose outstanding prompt bytes hold the bytes of the steers not taken in yet. */
288
+ readonly stored: IStoredSessionState;
289
+ /** Every steer the run accepted, in arrival order, by steer id. */
290
+ readonly accepted: Map<string, { prompt: INormalizedFlexPrompt; byteSize: number }>;
291
+ /** Accepted steers the generation recorded durably as user messages of the run. */
292
+ readonly applied: Set<string>;
293
+ /** Whether the run stays in the model context of later turns, once that is established. */
294
+ keptInContext?: boolean;
295
+ /** What became of the run's steers, fixed when the run ends. */
296
+ outcome?: IRunSteeringOutcome;
297
+ }
298
+
299
+ export interface IRunSteeringOutcome {
300
+ /** Whether the run's prompt, the steers it took in and what its model produced stay in context. */
301
+ readonly keptInContext: boolean;
302
+ /** Accepted steers the model context of later turns does not hold, in arrival order. */
303
+ readonly unappliedSteerIds: readonly string[];
304
+ }
305
+
306
+ export interface IDelegatedRunAdmissionSeed {
307
+ readonly state: IStorageState;
308
+ readonly childStored: IStoredSessionState;
309
+ readonly parentRun: IActiveRun;
310
+ readonly parentStored: IStoredSessionState;
311
+ readonly scopeId: string;
312
+ readonly scope: unknown;
313
+ readonly storageKey: string;
314
+ readonly sessionId: string;
315
+ readonly sessionGenerationId: string;
316
+ readonly sessionGenerationSequence: number;
317
+ readonly originParentRunId: string;
318
+ readonly originParentToolCallId: string;
319
+ readonly parentSessionId: string;
320
+ readonly parentSessionGenerationId: string;
321
+ readonly parentSessionGenerationSequence: number;
322
+ readonly parentQueueId: string;
323
+ readonly parentRunId: string;
324
+ readonly parentToolCallId: string;
325
+ readonly configurationRef?: string;
326
+ readonly agent: string;
327
+ readonly depth: number;
328
+ }
329
+
330
+ export type TDelegatedRunAdmissionOwnerStatus =
331
+ | 'acquiring'
332
+ | 'acquired'
333
+ | 'close-requested'
334
+ | 'retrying'
335
+ | 'closed';
336
+
337
+ export interface IDelegatedRunAdmissionOwner {
338
+ readonly storageState: IStorageState;
339
+ readonly run: IActiveRun;
340
+ readonly context: Readonly<IFlexDelegatedRunAdmissionContext<unknown>>;
341
+ status: TDelegatedRunAdmissionOwnerStatus;
342
+ acquisitionSettled: boolean;
343
+ acquisitionDetached: boolean;
344
+ acquisitionCompletion?: Promise<void>;
345
+ acquisitionError?: Error;
346
+ lease?: IFlexDelegatedRunAdmissionLease;
347
+ closeAttempt?: Promise<void>;
348
+ closeAttemptSequence: number;
349
+ closeFailure?: { sequence: number; error: Error };
350
+ }
351
+
352
+ export interface IQueuedPrompt {
353
+ state: IStorageState;
354
+ stored: IStoredSessionState;
355
+ scopeId: string;
356
+ scope: unknown;
357
+ sessionId: string;
358
+ queueId: string;
359
+ queueSequence: number;
360
+ status: Exclude<TFlexPromptQueueStatus, 'completed' | 'failed' | 'cancelled'>;
361
+ queuedAt: string;
362
+ runId?: string;
363
+ scheduleKey?: string;
364
+ debounceMs?: number;
365
+ startedAt?: string;
366
+ prompt?: INormalizedFlexPrompt;
367
+ options?: IFlexPromptOptions;
368
+ delegationSeed?: IDelegatedRunAdmissionSeed;
369
+ byteSize: number;
370
+ /** The steering of the run the prompt started, once it started one. */
371
+ steering?: IRunSteering;
372
+ completion: Promise<IFlexPromptResult>;
373
+ resolveCompletion: (result: IFlexPromptResult) => void;
374
+ rejectCompletion: (error: unknown) => void;
375
+ started: Promise<IFlexPromptAdmission>;
376
+ resolveStarted: (admission: IFlexPromptAdmission) => void;
377
+ rejectStarted: (error: unknown) => void;
378
+ }
379
+
380
+ export interface IPendingPromptAdmission {
381
+ controller: AbortController;
382
+ settled: Promise<void>;
383
+ resolveSettled: () => void;
384
+ }
385
+
386
+ export interface IPendingPermission {
387
+ request: IFlexPermissionRequest;
388
+ run: IActiveRun;
389
+ resolve: () => void;
390
+ reject: (error: unknown) => void;
391
+ settled: boolean;
392
+ responding: boolean;
393
+ abortReason?: unknown;
394
+ responseQueue: Promise<void>;
395
+ abortListener: () => void;
396
+ }
397
+
398
+ export interface IScopeAdmissionState {
399
+ generation: number;
400
+ inFlightResolvers: number;
401
+ retiring: boolean;
402
+ }
403
+
404
+ export interface IActiveSlashCommandExecution {
405
+ kind: 'handler' | 'operation' | 'prompt-admission';
406
+ storageKey: string;
407
+ sessionId: string;
408
+ controller: AbortController;
409
+ completion: Promise<unknown>;
410
+ }
411
+
412
+ export interface IActiveSlashCommandListing {
413
+ storageKey: string;
414
+ sessionId: string;
415
+ controller: AbortController;
416
+ completion: Promise<unknown>;
417
+ }
418
+
419
+ export interface IResolvedSessionConfiguration<TScope> {
420
+ subagents: ReadonlyMap<string, Readonly<IFlexSubagentDefinition>>;
421
+ slashCommands: ReadonlyMap<string, TRegisteredSlashCommand<TScope>>;
422
+ allowedTools?: readonly string[];
423
+ permissionPolicy?: IFlexResolvedSessionConfiguration<TScope>['permissionPolicy'];
424
+ }
425
+
426
+ export interface ISessionConfigurationOwner<TScope> {
427
+ storageKey: string;
428
+ sessionId: string;
429
+ controller: AbortController;
430
+ completion: Promise<IResolvedSessionConfiguration<TScope>>;
431
+ settled: boolean;
432
+ waiters: number;
433
+ }
434
+
435
+ export interface ISlashCommandInvocationOwner {
436
+ scopeId: string;
437
+ storageKey: string;
438
+ sessionId: string;
439
+ sessionKey: string;
440
+ }
441
+
442
+ export interface ISlashCommandActivityOwner {
443
+ scopeId: string;
444
+ storageKey: string;
445
+ sessionId: string;
446
+ }
447
+
448
+ export interface IResolverOutcome<TValue> {
449
+ source: 'model' | 'tools' | 'abort';
450
+ success: boolean;
451
+ value?: TValue;
452
+ error?: unknown;
453
+ }
454
+
455
+ export interface IFlexMessageCursor {
456
+ version: 1;
457
+ namespace: string;
458
+ sessionId: string;
459
+ anchorMessageId: string;
460
+ }
461
+
462
+ export interface IFlexSubagentTaskInput {
463
+ description: string;
464
+ prompt: string;
465
+ subagentType: string;
466
+ taskId?: string;
467
+ foreground?: boolean;
468
+ }
469
+
470
+ export interface IFlexSubagentAcquisition {
471
+ stored: IStoredSessionState;
472
+ created: boolean;
473
+ }
474
+
475
+ export interface ISessionDeletionPlan {
476
+ matched: boolean;
477
+ deletion?: Promise<void>;
478
+ }
479
+
480
+ export interface IReservedSessionDeletion {
481
+ deletedSessions: IFlexSession[];
482
+ descendantRoots: string[];
483
+ }
484
+
485
+ export type TProjectTaskToolInput =
486
+ | { action: 'list' }
487
+ | {
488
+ action: 'create';
489
+ id?: string;
490
+ content: string;
491
+ status?: TFlexProjectTaskStatus;
492
+ priority?: TFlexProjectTaskPriority;
493
+ }
494
+ | {
495
+ action: 'update';
496
+ id: string;
497
+ content?: string;
498
+ status?: TFlexProjectTaskStatus;
499
+ priority?: TFlexProjectTaskPriority;
500
+ }
501
+ | { action: 'delete'; id: string }
502
+ | { action: 'clear' };
503
+
504
+ export type TProjectGoalToolInput =
505
+ | { action: 'get' }
506
+ | { action: 'set'; goal: string }
507
+ | { action: 'clear' };
508
+
509
+ export type TProjectScratchpadToolInput =
510
+ | { action: 'get' }
511
+ | { action: 'set'; content: string }
512
+ | { action: 'append'; content: string }
513
+ | { action: 'clear' };
514
+
515
+ export type TEventDetails = Record<string, unknown> & {
516
+ type: TFlexHarnessEvent['type'];
517
+ };
@@ -0,0 +1,121 @@
1
+ import * as plugins from './plugins.js';
2
+ import {
3
+ FlexHarnessAbortError,
4
+ FlexHarnessRunError,
5
+ FlexHarnessValidationError,
6
+ } from './errors.js';
7
+ import type {
8
+ IFlexErrorInfo,
9
+ IFlexMessage,
10
+ IFlexSession,
11
+ IFlexSessionActivity,
12
+ IFlexSessionGeneration,
13
+ IFlexSessionTombstone,
14
+ } from './interfaces.js';
15
+ import { validateUtf8String } from './utils.validation.js';
16
+
17
+ /** Runs `operation` after the operations queued before it under `key`, whatever their outcome. */
18
+ export function queueOperation<TResult>(
19
+ queues: Map<string, Promise<void>>,
20
+ key: string,
21
+ operation: () => Promise<TResult>,
22
+ ): Promise<TResult> {
23
+ const previous = queues.get(key) ?? Promise.resolve();
24
+ const result = previous.then(operation);
25
+ const barrier = result.then(() => undefined, () => undefined);
26
+ queues.set(key, barrier);
27
+ void barrier.then(() => {
28
+ if (queues.get(key) === barrier) queues.delete(key);
29
+ });
30
+ return result;
31
+ }
32
+
33
+ export const maxResourceIdBytes = 512;
34
+ export const maxResourceToolNameBytes = 512;
35
+ export const resourceToolStemLength = 16;
36
+ export const scopeRetirementMessage = 'The scope is being retired.';
37
+ export const slashCommandSessionUnavailableReason = 'Session must be idle with no queued prompts.';
38
+
39
+ export const externalErrorFallback: IFlexErrorInfo = Object.freeze({
40
+ name: 'FlexHarnessExternalError',
41
+ message: 'The model operation failed.',
42
+ code: 'FLEX_EXTERNAL_ERROR',
43
+ });
44
+
45
+ export function createSessionGenerationId(): string {
46
+ return `generation_${plugins.crypto.randomUUID()}`;
47
+ }
48
+
49
+ export function requireSessionGeneration(
50
+ session: Pick<
51
+ IFlexSession | IFlexSessionTombstone,
52
+ 'sessionGenerationId' | 'sessionGenerationSequence'
53
+ >,
54
+ ): IFlexSessionGeneration {
55
+ if (
56
+ !session.sessionGenerationId
57
+ || !Number.isSafeInteger(session.sessionGenerationSequence)
58
+ || Number(session.sessionGenerationSequence) < 1
59
+ ) {
60
+ throw new FlexHarnessValidationError('Session generation metadata is missing.');
61
+ }
62
+ return {
63
+ sessionGenerationId: session.sessionGenerationId,
64
+ sessionGenerationSequence: session.sessionGenerationSequence as number,
65
+ };
66
+ }
67
+
68
+ export function sha256Hex(value: string): string {
69
+ return plugins.crypto.createHash('sha256').update(value, 'utf8').digest('hex');
70
+ }
71
+
72
+ export function createFlexResourceIdentityDigest(
73
+ resourceId: string,
74
+ attachmentRevision: number,
75
+ ): string {
76
+ validateUtf8String(resourceId, 'resourceId', maxResourceIdBytes, true);
77
+ if (!Number.isSafeInteger(attachmentRevision) || attachmentRevision < 0) {
78
+ throw new FlexHarnessValidationError('attachmentRevision must be a non-negative safe integer.');
79
+ }
80
+ return sha256Hex(JSON.stringify([resourceId, attachmentRevision]));
81
+ }
82
+
83
+ export function createFlexResourceToolNamespace(
84
+ resourceId: string,
85
+ attachmentRevision: number,
86
+ ): string {
87
+ const digest = createFlexResourceIdentityDigest(resourceId, attachmentRevision);
88
+ return `resource_${digest.slice(0, 16)}`;
89
+ }
90
+
91
+ export function createFlexResourceToolName(namespace: string, toolName: string): string {
92
+ if (!/^resource_[a-f0-9]{16}$/u.test(namespace)) {
93
+ throw new FlexHarnessValidationError('Resource tool namespace is invalid.');
94
+ }
95
+ validateUtf8String(toolName, 'resource tool name', maxResourceToolNameBytes, true);
96
+ const stem = toolName
97
+ .replace(/[^A-Za-z0-9_-]/gu, '_')
98
+ .slice(0, resourceToolStemLength) || 'tool';
99
+ return `${namespace}__${stem}__${sha256Hex(toolName).slice(0, 12)}`;
100
+ }
101
+
102
+ export function combineErrors(errors: unknown[]): unknown {
103
+ if (errors.length === 1) return errors[0];
104
+ const combined = new FlexHarnessRunError(errors);
105
+ Object.freeze(combined.errors);
106
+ return Object.freeze(combined);
107
+ }
108
+
109
+ /** The error and model limit a finished run leaves on its session activity. */
110
+ export function terminalActivityError(message: IFlexMessage): Pick<IFlexSessionActivity, 'error' | 'limit'> {
111
+ return {
112
+ ...(message.error ? { error: message.error } : {}),
113
+ ...(message.limit ? { limit: message.limit } : {}),
114
+ };
115
+ }
116
+
117
+ export function isAbortError(error: unknown): boolean {
118
+ return error instanceof FlexHarnessAbortError
119
+ || (error instanceof DOMException && error.name === 'AbortError')
120
+ || (error instanceof Error && error.name === 'AbortError');
121
+ }