@modelprofile.com/flexharness 10.23.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 (83) 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/dist_ts_compaction/plugins.d.ts +2 -2
  47. package/dist_ts_compaction/plugins.js +3 -3
  48. package/dist_ts_compaction/smartagent.compaction.js +6 -5
  49. package/dist_ts_stores_nosqldb/classes.nosqldbstores.d.ts +9 -0
  50. package/dist_ts_stores_nosqldb/classes.nosqldbstores.js +13 -1
  51. package/dist_ts_stores_nosqldb/classes.recordstore.d.ts +2 -2
  52. package/dist_ts_stores_nosqldb/classes.recordstore.js +3 -3
  53. package/package.json +3 -3
  54. package/readme.md +1 -1
  55. package/ts/00_commitinfo_data.ts +1 -1
  56. package/ts/classes.flexharness.admission.ts +331 -0
  57. package/ts/classes.flexharness.callbacks.ts +352 -0
  58. package/ts/classes.flexharness.compaction.ts +167 -0
  59. package/ts/classes.flexharness.configurations.ts +167 -0
  60. package/ts/classes.flexharness.core.ts +1830 -0
  61. package/ts/classes.flexharness.deletion.ts +616 -0
  62. package/ts/classes.flexharness.imports.ts +414 -0
  63. package/ts/classes.flexharness.lifecycle.ts +786 -0
  64. package/ts/classes.flexharness.permissions.ts +535 -0
  65. package/ts/classes.flexharness.projects.ts +1006 -0
  66. package/ts/classes.flexharness.prompts.ts +857 -0
  67. package/ts/classes.flexharness.providers.ts +322 -0
  68. package/ts/classes.flexharness.results.ts +98 -0
  69. package/ts/classes.flexharness.reversions.ts +1684 -0
  70. package/ts/classes.flexharness.runs.ts +1204 -0
  71. package/ts/classes.flexharness.sessions.ts +483 -0
  72. package/ts/classes.flexharness.slashcommands.ts +705 -0
  73. package/ts/classes.flexharness.subagents.ts +860 -0
  74. package/ts/classes.flexharness.tooldispatch.ts +475 -0
  75. package/ts/classes.flexharness.ts +147 -12567
  76. package/ts/interfaces.flexharness.ts +517 -0
  77. package/ts/readme.md +1 -1
  78. package/ts/utils.flexharness.ts +121 -0
  79. package/ts_compaction/plugins.ts +2 -2
  80. package/ts_compaction/smartagent.compaction.ts +5 -4
  81. package/ts_stores_nosqldb/classes.nosqldbstores.ts +13 -0
  82. package/ts_stores_nosqldb/classes.recordstore.ts +5 -5
  83. package/ts_stores_nosqldb/readme.md +7 -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
+ };
package/ts/readme.md CHANGED
@@ -1182,7 +1182,7 @@ Tool-handle close settles before a turn can be successful. After model generatio
1182
1182
 
1183
1183
  If `dispose()` overlaps a storage namespace already being retired, both calls await the same storage drain and cleanup runs once. A retirement call begun after disposal starts rejects with `FlexHarnessClosedError`.
1184
1184
 
1185
- Cancellation is cooperative: model resolvers, tool providers, AgentSession model execution, tools, execution contexts, and cleanup functions must observe the supplied `AbortSignal` and settle tracked work. After a sibling resolver fails, FlexHarness deliberately does not wait for an unresponsive model resolver; a detached tool provider remains tracked because any late handle must be closed. A process host that needs a hard shutdown deadline must enforce that deadline outside FlexHarness and terminate only the process it owns.
1185
+ Cancellation is cooperative: model resolvers, tool providers, AgentSession model execution, tools, execution contexts, and cleanup functions must observe the supplied `AbortSignal` and settle tracked work. A model call is the exception: one whose transport ignores the signal is cut off `MODEL_ABORT_GRACE_MS` after the abort. After a sibling resolver fails, FlexHarness deliberately does not wait for an unresponsive model resolver; a detached tool provider remains tracked because any late handle must be closed. A process host that needs a hard shutdown deadline must enforce that deadline outside FlexHarness and terminate only the process it owns.
1186
1186
 
1187
1187
  ## License and Legal Information
1188
1188
 
@@ -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
+ }
@@ -5,8 +5,8 @@ export { asSchema, streamText, wrapLanguageModel };
5
5
  export type { ModelMessage, ToolSet } from 'ai';
6
6
  export type { TSmartAiLanguageModel } from '@modelprofile.com/flexharness-models';
7
7
 
8
- import { AgentModelCallUsageRecorder } from '@modelprofile.com/flexharness-agent';
8
+ import { AgentModelCallUsageRecorder, endModelStreamAtAbort } from '@modelprofile.com/flexharness-agent';
9
9
 
10
- export { AgentModelCallUsageRecorder };
10
+ export { AgentModelCallUsageRecorder, endModelStreamAtAbort };
11
11
 
12
12
  export type { ProviderOptions, TAgentModelCallUsageReporter } from '@modelprofile.com/flexharness-agent';
@@ -117,16 +117,17 @@ export async function compactMessages(
117
117
  : undefined;
118
118
  // Each attempt, retries included, is one model call: it starts in the middleware, which also
119
119
  // settles an attempt that throws; a call whose response arrives ends in onLanguageModelCallEnd.
120
- const observedModel = modelCalls
120
+ // The middleware also ends a call at its abort where the provider would not.
121
+ const observedModel = modelCalls || options.abortSignal
121
122
  ? plugins.wrapLanguageModel({
122
123
  model,
123
124
  middleware: {
124
125
  wrapStream: async ({ doStream, params }) => {
125
- modelCalls.start();
126
+ modelCalls?.start();
126
127
  try {
127
- return await doStream();
128
+ return await plugins.endModelStreamAtAbort(doStream, params.abortSignal);
128
129
  } catch (error) {
129
- modelCalls.settle(params.abortSignal?.aborted ? 'aborted' : 'failed');
130
+ modelCalls?.settle(params.abortSignal?.aborted ? 'aborted' : 'failed');
130
131
  throw error;
131
132
  }
132
133
  },
@@ -528,4 +528,17 @@ export class NoSqlFlexHarnessStores implements IFlexHarnessStores {
528
528
  public prepare(): Promise<void> {
529
529
  return this.records.prepare();
530
530
  }
531
+
532
+ /**
533
+ * Physically removes every record and chunk of one storage namespace in every domain: the
534
+ * scope snapshot, each session's projection, permissions, Agent events and archives, jobs and
535
+ * results, and the project-management records and tombstones. This is the host's durable
536
+ * deletion of a scope. Call it only after stopping new admission for every alias of the scope
537
+ * and awaiting `retireScope()` on every harness that owns it; purging under a live harness
538
+ * removes the fences that keep its writes safe.
539
+ */
540
+ public purgeNamespace(storageKey: string): Promise<void> {
541
+ validateIdentifier(storageKey, 'storageKey');
542
+ return this.records.purgeNamespace(storageKey);
543
+ }
531
544
  }