@modelprofile.com/flexharness 5.2.0 → 5.3.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.
- package/changelog.md +10 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.flexharness.d.ts +10 -0
- package/dist_ts/classes.flexharness.js +338 -14
- package/dist_ts/interfaces.d.ts +28 -1
- package/package.json +1 -1
- package/readme.md +38 -4
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.flexharness.ts +469 -16
- package/ts/interfaces.ts +33 -0
|
@@ -35,6 +35,8 @@ import type {
|
|
|
35
35
|
IFlexCreateSessionOptions,
|
|
36
36
|
IFlexDeleteSessionGenerationCohortInput,
|
|
37
37
|
IFlexDeleteSessionGenerationCohortResult,
|
|
38
|
+
IFlexDelegatedRunAdmissionContext,
|
|
39
|
+
IFlexDelegatedRunAdmissionLease,
|
|
38
40
|
IFlexErrorInfo,
|
|
39
41
|
IFlexEventArchiveMetadata,
|
|
40
42
|
IFlexExecutionContextHandle,
|
|
@@ -297,6 +299,7 @@ interface IStorageState {
|
|
|
297
299
|
tombstones: Map<string, IFlexSessionTombstone>;
|
|
298
300
|
tombstoneCleanups: Map<string, Promise<void>>;
|
|
299
301
|
activeRuns: Map<string, IActiveRun>;
|
|
302
|
+
delegatedRunAdmissionOwners: Set<IDelegatedRunAdmissionOwner>;
|
|
300
303
|
pendingPermissions: Map<string, IPendingPermission>;
|
|
301
304
|
scopeQueue: Promise<void>;
|
|
302
305
|
lifecycle: 'active' | 'retiring' | 'fenced' | 'retired';
|
|
@@ -331,6 +334,7 @@ interface IActiveRun {
|
|
|
331
334
|
callbacksClosed: boolean;
|
|
332
335
|
reasoningPartIds: Map<string, string>;
|
|
333
336
|
toolPartIds: Map<string, string>;
|
|
337
|
+
trustedToolExecutionErrors: Map<string, Error>;
|
|
334
338
|
subagentCallCount: number;
|
|
335
339
|
subagentSessionIds: Set<string>;
|
|
336
340
|
pendingPermissionIds: Set<string>;
|
|
@@ -338,9 +342,56 @@ interface IActiveRun {
|
|
|
338
342
|
reservedUserMessage?: IFlexMessage;
|
|
339
343
|
reservedAssistantMessage?: IFlexMessage;
|
|
340
344
|
modelResolution?: IFlexResolvedModel;
|
|
345
|
+
delegatedRunAdmissionOwner?: IDelegatedRunAdmissionOwner;
|
|
346
|
+
delegatedRunAdmissionClose?: Promise<void>;
|
|
341
347
|
completion: Promise<IFlexPromptResult>;
|
|
342
348
|
}
|
|
343
349
|
|
|
350
|
+
interface IDelegatedRunAdmissionSeed {
|
|
351
|
+
readonly state: IStorageState;
|
|
352
|
+
readonly childStored: IStoredSessionState;
|
|
353
|
+
readonly parentRun: IActiveRun;
|
|
354
|
+
readonly parentStored: IStoredSessionState;
|
|
355
|
+
readonly scopeId: string;
|
|
356
|
+
readonly scope: unknown;
|
|
357
|
+
readonly storageKey: string;
|
|
358
|
+
readonly sessionId: string;
|
|
359
|
+
readonly sessionGenerationId: string;
|
|
360
|
+
readonly sessionGenerationSequence: number;
|
|
361
|
+
readonly originParentRunId: string;
|
|
362
|
+
readonly originParentToolCallId: string;
|
|
363
|
+
readonly parentSessionId: string;
|
|
364
|
+
readonly parentSessionGenerationId: string;
|
|
365
|
+
readonly parentSessionGenerationSequence: number;
|
|
366
|
+
readonly parentQueueId: string;
|
|
367
|
+
readonly parentRunId: string;
|
|
368
|
+
readonly parentToolCallId: string;
|
|
369
|
+
readonly agent: string;
|
|
370
|
+
readonly depth: number;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
type TDelegatedRunAdmissionOwnerStatus =
|
|
374
|
+
| 'acquiring'
|
|
375
|
+
| 'acquired'
|
|
376
|
+
| 'close-requested'
|
|
377
|
+
| 'retrying'
|
|
378
|
+
| 'closed';
|
|
379
|
+
|
|
380
|
+
interface IDelegatedRunAdmissionOwner {
|
|
381
|
+
readonly storageState: IStorageState;
|
|
382
|
+
readonly run: IActiveRun;
|
|
383
|
+
readonly context: Readonly<IFlexDelegatedRunAdmissionContext<unknown>>;
|
|
384
|
+
status: TDelegatedRunAdmissionOwnerStatus;
|
|
385
|
+
acquisitionSettled: boolean;
|
|
386
|
+
acquisitionDetached: boolean;
|
|
387
|
+
acquisitionCompletion?: Promise<void>;
|
|
388
|
+
acquisitionError?: Error;
|
|
389
|
+
lease?: IFlexDelegatedRunAdmissionLease;
|
|
390
|
+
closeAttempt?: Promise<void>;
|
|
391
|
+
closeAttemptSequence: number;
|
|
392
|
+
closeFailure?: { sequence: number; error: Error };
|
|
393
|
+
}
|
|
394
|
+
|
|
344
395
|
interface IQueuedPrompt {
|
|
345
396
|
state: IStorageState;
|
|
346
397
|
stored: IStoredSessionState;
|
|
@@ -357,6 +408,7 @@ interface IQueuedPrompt {
|
|
|
357
408
|
startedAt?: string;
|
|
358
409
|
prompt?: INormalizedFlexPrompt;
|
|
359
410
|
options?: IFlexPromptOptions;
|
|
411
|
+
delegationSeed?: IDelegatedRunAdmissionSeed;
|
|
360
412
|
byteSize: number;
|
|
361
413
|
completion: Promise<IFlexPromptResult>;
|
|
362
414
|
resolveCompletion: (result: IFlexPromptResult) => void;
|
|
@@ -630,6 +682,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
630
682
|
private readonly toolProvider: IFlexHarnessOptions<TScope>['toolProvider'];
|
|
631
683
|
private readonly resourceToolProviderResolver: IFlexHarnessOptions<TScope>['resourceToolProviderResolver'];
|
|
632
684
|
private readonly executionContextProvider: IFlexHarnessOptions<TScope>['executionContextProvider'];
|
|
685
|
+
private readonly delegatedRunAdmissionProvider: IFlexHarnessOptions<TScope>['delegatedRunAdmissionProvider'];
|
|
633
686
|
private readonly stores: IFlexHarnessStores;
|
|
634
687
|
private readonly agentSessionPolicy: IFlexAgentSessionPolicy<TScope>;
|
|
635
688
|
private readonly toolOutputLimits: Required<NonNullable<IFlexHarnessOptions<TScope>['toolOutputLimits']>>;
|
|
@@ -693,6 +746,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
693
746
|
this.toolProvider = options.toolProvider;
|
|
694
747
|
this.resourceToolProviderResolver = options.resourceToolProviderResolver;
|
|
695
748
|
this.executionContextProvider = options.executionContextProvider;
|
|
749
|
+
this.delegatedRunAdmissionProvider = options.delegatedRunAdmissionProvider;
|
|
696
750
|
this.stores = requireHarnessStores(options.stores ?? new InMemoryFlexHarnessStores());
|
|
697
751
|
this.agentSessionPolicy = normalizeAgentSessionPolicy(options.agentSessionPolicy);
|
|
698
752
|
this.toolOutputLimits = resolveJsonLimits(options.toolOutputLimits);
|
|
@@ -1779,7 +1833,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1779
1833
|
promptOptions,
|
|
1780
1834
|
undefined,
|
|
1781
1835
|
undefined,
|
|
1782
|
-
|
|
1836
|
+
undefined,
|
|
1783
1837
|
signal,
|
|
1784
1838
|
true,
|
|
1785
1839
|
);
|
|
@@ -3085,7 +3139,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3085
3139
|
options: IFlexPromptOptions,
|
|
3086
3140
|
scheduleKey?: string,
|
|
3087
3141
|
debounceMs?: number,
|
|
3088
|
-
|
|
3142
|
+
delegationSeed?: IDelegatedRunAdmissionSeed,
|
|
3089
3143
|
admissionSignal?: AbortSignal,
|
|
3090
3144
|
slashCommandAdmission = false,
|
|
3091
3145
|
): Promise<{
|
|
@@ -3145,11 +3199,14 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3145
3199
|
if (stored.pendingReversion) {
|
|
3146
3200
|
throw new FlexHarnessSessionBusyError(sessionId, 'has a pending reversion operation');
|
|
3147
3201
|
}
|
|
3148
|
-
if (stored.session.agent !== undefined &&
|
|
3202
|
+
if (stored.session.agent !== undefined && delegationSeed === undefined) {
|
|
3149
3203
|
throw new FlexHarnessValidationError(
|
|
3150
3204
|
'Subagent sessions can only be prompted through the foreground delegate tool.',
|
|
3151
3205
|
);
|
|
3152
3206
|
}
|
|
3207
|
+
if (delegationSeed !== undefined) {
|
|
3208
|
+
this.assertDelegatedRunAdmissionOwnership(delegationSeed, state, stored, scopeId);
|
|
3209
|
+
}
|
|
3153
3210
|
if (stored.outstandingPromptsById.size >= this.promptQueueLimits.maxOutstandingPromptsPerSession) {
|
|
3154
3211
|
throw new FlexHarnessQueueFullError(
|
|
3155
3212
|
`Session "${sessionId}" has reached its outstanding prompt limit.`,
|
|
@@ -3188,7 +3245,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3188
3245
|
state,
|
|
3189
3246
|
stored,
|
|
3190
3247
|
scopeId,
|
|
3191
|
-
scope: resolved.scope.scope,
|
|
3248
|
+
scope: delegationSeed === undefined ? resolved.scope.scope : delegationSeed.scope,
|
|
3192
3249
|
sessionId,
|
|
3193
3250
|
queueId: plugins.crypto.randomUUID(),
|
|
3194
3251
|
queueSequence: ++this.promptQueueSequence,
|
|
@@ -3197,6 +3254,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3197
3254
|
...(scheduleKey === undefined ? {} : { scheduleKey, debounceMs }),
|
|
3198
3255
|
prompt: normalizedPrompt,
|
|
3199
3256
|
options: normalizedOptions,
|
|
3257
|
+
...(delegationSeed === undefined ? {} : { delegationSeed }),
|
|
3200
3258
|
byteSize,
|
|
3201
3259
|
completion,
|
|
3202
3260
|
resolveCompletion,
|
|
@@ -3318,6 +3376,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3318
3376
|
callbacksClosed: false,
|
|
3319
3377
|
reasoningPartIds: new Map(),
|
|
3320
3378
|
toolPartIds: new Map(),
|
|
3379
|
+
trustedToolExecutionErrors: new Map(),
|
|
3321
3380
|
subagentCallCount: 0,
|
|
3322
3381
|
subagentSessionIds: new Set(),
|
|
3323
3382
|
pendingPermissionIds: new Set(),
|
|
@@ -3336,6 +3395,258 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3336
3395
|
|| run.stored.outstandingPromptsById.get(run.queueId) !== queued
|
|
3337
3396
|
|| run.state.activeRuns.get(run.sessionId) !== run
|
|
3338
3397
|
) throw this.trustInternalError(new FlexHarnessAbortError('The prompt was retired during admission.'));
|
|
3398
|
+
if (queued.delegationSeed) {
|
|
3399
|
+
this.assertDelegatedRunAdmissionOwnership(
|
|
3400
|
+
queued.delegationSeed,
|
|
3401
|
+
run.state,
|
|
3402
|
+
run.stored,
|
|
3403
|
+
run.scopeId,
|
|
3404
|
+
);
|
|
3405
|
+
if (
|
|
3406
|
+
run.queueId !== queued.queueId
|
|
3407
|
+
|| run.runId !== queued.runId
|
|
3408
|
+
|| run.sessionId !== queued.delegationSeed.sessionId
|
|
3409
|
+
) {
|
|
3410
|
+
throw this.trustInternalError(
|
|
3411
|
+
new FlexHarnessAbortError('The delegated prompt lost its exact child run ownership.'),
|
|
3412
|
+
);
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3415
|
+
}
|
|
3416
|
+
|
|
3417
|
+
private assertDelegatedRunAdmissionOwnership(
|
|
3418
|
+
seed: IDelegatedRunAdmissionSeed,
|
|
3419
|
+
state: IStorageState,
|
|
3420
|
+
childStored: IStoredSessionState,
|
|
3421
|
+
scopeId: string,
|
|
3422
|
+
): void {
|
|
3423
|
+
const child = childStored.session;
|
|
3424
|
+
const parentRun = seed.parentRun;
|
|
3425
|
+
const parent = seed.parentStored.session;
|
|
3426
|
+
const parentPartId = parentRun.toolPartIds.get(seed.parentToolCallId);
|
|
3427
|
+
const parentPart = parentRun.callbackParts.find((part): part is IFlexToolMessagePart =>
|
|
3428
|
+
part.type === 'tool'
|
|
3429
|
+
&& part.partId === parentPartId
|
|
3430
|
+
&& part.toolCallId === seed.parentToolCallId);
|
|
3431
|
+
if (
|
|
3432
|
+
seed.state !== state
|
|
3433
|
+
|| seed.storageKey !== state.storageKey
|
|
3434
|
+
|| seed.scopeId !== scopeId
|
|
3435
|
+
|| seed.childStored !== childStored
|
|
3436
|
+
|| childStored.storageKey !== seed.storageKey
|
|
3437
|
+
|| state.sessions.get(seed.sessionId) !== childStored
|
|
3438
|
+
|| state.tombstones.has(seed.sessionId)
|
|
3439
|
+
|| state.initializingSessions.has(seed.sessionId)
|
|
3440
|
+
|| child.sessionId !== seed.sessionId
|
|
3441
|
+
|| child.sessionGenerationId !== seed.sessionGenerationId
|
|
3442
|
+
|| child.sessionGenerationSequence !== seed.sessionGenerationSequence
|
|
3443
|
+
|| child.parentSessionId !== seed.parentSessionId
|
|
3444
|
+
|| child.parentRunId !== seed.originParentRunId
|
|
3445
|
+
|| child.parentToolCallId !== seed.originParentToolCallId
|
|
3446
|
+
|| child.agent !== seed.agent
|
|
3447
|
+
|| child.depth !== seed.depth
|
|
3448
|
+
|| seed.parentStored !== parentRun.stored
|
|
3449
|
+
|| seed.parentStored.storageKey !== seed.storageKey
|
|
3450
|
+
|| parentRun.state !== state
|
|
3451
|
+
|| parentRun.scopeId !== seed.scopeId
|
|
3452
|
+
|| parentRun.scope !== seed.scope
|
|
3453
|
+
|| parentRun.sessionId !== seed.parentSessionId
|
|
3454
|
+
|| parentRun.queueId !== seed.parentQueueId
|
|
3455
|
+
|| parentRun.runId !== seed.parentRunId
|
|
3456
|
+
|| state.sessions.get(seed.parentSessionId) !== seed.parentStored
|
|
3457
|
+
|| state.tombstones.has(seed.parentSessionId)
|
|
3458
|
+
|| parent.sessionId !== seed.parentSessionId
|
|
3459
|
+
|| parent.sessionGenerationId !== seed.parentSessionGenerationId
|
|
3460
|
+
|| parent.sessionGenerationSequence !== seed.parentSessionGenerationSequence
|
|
3461
|
+
|| state.activeRuns.get(seed.parentSessionId) !== parentRun
|
|
3462
|
+
|| parentRun.callbacksClosed
|
|
3463
|
+
|| parentRun.controller.signal.aborted
|
|
3464
|
+
|| parentPart?.status !== 'running'
|
|
3465
|
+
) {
|
|
3466
|
+
throw this.trustInternalError(
|
|
3467
|
+
new FlexHarnessAbortError('The delegated prompt lost its exact parent or child ownership.'),
|
|
3468
|
+
);
|
|
3469
|
+
}
|
|
3470
|
+
}
|
|
3471
|
+
|
|
3472
|
+
private async acquireDelegatedRunAdmission(
|
|
3473
|
+
queued: IQueuedPrompt,
|
|
3474
|
+
run: IActiveRun,
|
|
3475
|
+
): Promise<void> {
|
|
3476
|
+
const seed = queued.delegationSeed;
|
|
3477
|
+
const provider = this.delegatedRunAdmissionProvider;
|
|
3478
|
+
if (!seed || !provider) return;
|
|
3479
|
+
const context = Object.freeze({
|
|
3480
|
+
scopeId: seed.scopeId,
|
|
3481
|
+
scope: seed.scope as TScope,
|
|
3482
|
+
storageKey: seed.storageKey,
|
|
3483
|
+
sessionId: seed.sessionId,
|
|
3484
|
+
sessionGenerationId: seed.sessionGenerationId,
|
|
3485
|
+
sessionGenerationSequence: seed.sessionGenerationSequence,
|
|
3486
|
+
queueId: queued.queueId,
|
|
3487
|
+
runId: run.runId,
|
|
3488
|
+
parentSessionId: seed.parentSessionId,
|
|
3489
|
+
parentSessionGenerationId: seed.parentSessionGenerationId,
|
|
3490
|
+
parentSessionGenerationSequence: seed.parentSessionGenerationSequence,
|
|
3491
|
+
parentQueueId: seed.parentQueueId,
|
|
3492
|
+
parentRunId: seed.parentRunId,
|
|
3493
|
+
parentToolCallId: seed.parentToolCallId,
|
|
3494
|
+
agent: seed.agent,
|
|
3495
|
+
depth: seed.depth,
|
|
3496
|
+
signal: run.controller.signal,
|
|
3497
|
+
});
|
|
3498
|
+
const owner: IDelegatedRunAdmissionOwner = {
|
|
3499
|
+
storageState: run.state,
|
|
3500
|
+
run,
|
|
3501
|
+
context,
|
|
3502
|
+
status: 'acquiring',
|
|
3503
|
+
acquisitionSettled: false,
|
|
3504
|
+
acquisitionDetached: false,
|
|
3505
|
+
closeAttemptSequence: 0,
|
|
3506
|
+
};
|
|
3507
|
+
run.state.delegatedRunAdmissionOwners.add(owner);
|
|
3508
|
+
run.delegatedRunAdmissionOwner = owner;
|
|
3509
|
+
const acquisition = Promise.resolve()
|
|
3510
|
+
.then(() => provider.acquireDelegatedRunAdmission(context))
|
|
3511
|
+
.then((lease) => this.normalizeDelegatedRunAdmissionLease(lease))
|
|
3512
|
+
.then(
|
|
3513
|
+
(lease) => {
|
|
3514
|
+
owner.lease = lease;
|
|
3515
|
+
owner.acquisitionSettled = true;
|
|
3516
|
+
if (owner.status === 'acquiring') owner.status = 'acquired';
|
|
3517
|
+
if (owner.status === 'close-requested') {
|
|
3518
|
+
void this.startDelegatedRunAdmissionCloseAttempt(owner).catch(() => undefined);
|
|
3519
|
+
}
|
|
3520
|
+
},
|
|
3521
|
+
(error: unknown) => {
|
|
3522
|
+
owner.acquisitionError = this.projectExternalError(
|
|
3523
|
+
run,
|
|
3524
|
+
error,
|
|
3525
|
+
'delegatedRunAdmissionProvider',
|
|
3526
|
+
);
|
|
3527
|
+
owner.acquisitionSettled = true;
|
|
3528
|
+
owner.status = 'closed';
|
|
3529
|
+
run.state.delegatedRunAdmissionOwners.delete(owner);
|
|
3530
|
+
},
|
|
3531
|
+
);
|
|
3532
|
+
owner.acquisitionCompletion = acquisition;
|
|
3533
|
+
|
|
3534
|
+
let resolveAbort!: () => void;
|
|
3535
|
+
const abort = new Promise<'aborted'>((resolve) => {
|
|
3536
|
+
resolveAbort = () => resolve('aborted');
|
|
3537
|
+
});
|
|
3538
|
+
run.controller.signal.addEventListener('abort', resolveAbort, { once: true });
|
|
3539
|
+
if (run.controller.signal.aborted) resolveAbort();
|
|
3540
|
+
try {
|
|
3541
|
+
const outcome = await Promise.race([
|
|
3542
|
+
acquisition.then(() => 'settled' as const),
|
|
3543
|
+
abort,
|
|
3544
|
+
]);
|
|
3545
|
+
if (outcome === 'aborted') {
|
|
3546
|
+
owner.acquisitionDetached = true;
|
|
3547
|
+
throw run.controller.signal.reason ?? this.trustInternalError(new FlexHarnessAbortError());
|
|
3548
|
+
}
|
|
3549
|
+
if (run.controller.signal.aborted) {
|
|
3550
|
+
throw run.controller.signal.reason ?? this.trustInternalError(new FlexHarnessAbortError());
|
|
3551
|
+
}
|
|
3552
|
+
if (owner.acquisitionError) throw owner.acquisitionError;
|
|
3553
|
+
if (owner.status !== 'acquired' || !owner.lease) {
|
|
3554
|
+
throw this.trustInternalError(
|
|
3555
|
+
new FlexHarnessAbortError('The delegated run admission was retired during acquisition.'),
|
|
3556
|
+
);
|
|
3557
|
+
}
|
|
3558
|
+
} finally {
|
|
3559
|
+
run.controller.signal.removeEventListener('abort', resolveAbort);
|
|
3560
|
+
}
|
|
3561
|
+
}
|
|
3562
|
+
|
|
3563
|
+
private normalizeDelegatedRunAdmissionLease(
|
|
3564
|
+
value: IFlexDelegatedRunAdmissionLease,
|
|
3565
|
+
): IFlexDelegatedRunAdmissionLease {
|
|
3566
|
+
if (!value || (typeof value !== 'object' && typeof value !== 'function')) {
|
|
3567
|
+
throw new FlexHarnessValidationError(
|
|
3568
|
+
'Delegated run admission provider returned an invalid lease.',
|
|
3569
|
+
);
|
|
3570
|
+
}
|
|
3571
|
+
const close = value.close;
|
|
3572
|
+
if (typeof close !== 'function') {
|
|
3573
|
+
throw new FlexHarnessValidationError(
|
|
3574
|
+
'Delegated run admission provider returned an invalid lease.',
|
|
3575
|
+
);
|
|
3576
|
+
}
|
|
3577
|
+
return Object.freeze({ close: () => close.call(value) });
|
|
3578
|
+
}
|
|
3579
|
+
|
|
3580
|
+
private requestDelegatedRunAdmissionClose(
|
|
3581
|
+
owner: IDelegatedRunAdmissionOwner,
|
|
3582
|
+
): Promise<void> {
|
|
3583
|
+
const priorAttemptSequence = owner.closeAttemptSequence;
|
|
3584
|
+
const joinedAttempt = owner.closeAttempt;
|
|
3585
|
+
if (owner.status === 'closed') return Promise.resolve();
|
|
3586
|
+
if (owner.status !== 'retrying') owner.status = 'close-requested';
|
|
3587
|
+
return (async () => {
|
|
3588
|
+
await owner.acquisitionCompletion;
|
|
3589
|
+
if (owner.status === 'closed') return;
|
|
3590
|
+
if (joinedAttempt) {
|
|
3591
|
+
await joinedAttempt;
|
|
3592
|
+
return;
|
|
3593
|
+
}
|
|
3594
|
+
if (owner.closeAttempt) {
|
|
3595
|
+
await owner.closeAttempt;
|
|
3596
|
+
return;
|
|
3597
|
+
}
|
|
3598
|
+
if (owner.closeFailure && owner.closeFailure.sequence > priorAttemptSequence) {
|
|
3599
|
+
throw owner.closeFailure.error;
|
|
3600
|
+
}
|
|
3601
|
+
await this.startDelegatedRunAdmissionCloseAttempt(owner);
|
|
3602
|
+
})();
|
|
3603
|
+
}
|
|
3604
|
+
|
|
3605
|
+
private startDelegatedRunAdmissionCloseAttempt(
|
|
3606
|
+
owner: IDelegatedRunAdmissionOwner,
|
|
3607
|
+
): Promise<void> {
|
|
3608
|
+
if (owner.closeAttempt) return owner.closeAttempt;
|
|
3609
|
+
if (!owner.lease) {
|
|
3610
|
+
throw new Error('Acquired delegated run admission has no lease.');
|
|
3611
|
+
}
|
|
3612
|
+
owner.status = 'retrying';
|
|
3613
|
+
owner.closeFailure = undefined;
|
|
3614
|
+
const sequence = ++owner.closeAttemptSequence;
|
|
3615
|
+
let attempt!: Promise<void>;
|
|
3616
|
+
attempt = Promise.resolve()
|
|
3617
|
+
.then(() => owner.lease!.close())
|
|
3618
|
+
.then(() => {
|
|
3619
|
+
owner.status = 'closed';
|
|
3620
|
+
delete owner.lease;
|
|
3621
|
+
owner.storageState.delegatedRunAdmissionOwners.delete(owner);
|
|
3622
|
+
}, (error: unknown) => {
|
|
3623
|
+
const projected = this.projectExternalError(
|
|
3624
|
+
owner.run,
|
|
3625
|
+
error,
|
|
3626
|
+
'delegatedRunAdmissionProvider',
|
|
3627
|
+
);
|
|
3628
|
+
owner.closeFailure = { sequence, error: projected };
|
|
3629
|
+
owner.status = 'close-requested';
|
|
3630
|
+
throw projected;
|
|
3631
|
+
})
|
|
3632
|
+
.finally(() => {
|
|
3633
|
+
if (owner.closeAttempt === attempt) owner.closeAttempt = undefined;
|
|
3634
|
+
});
|
|
3635
|
+
owner.closeAttempt = attempt;
|
|
3636
|
+
return attempt;
|
|
3637
|
+
}
|
|
3638
|
+
|
|
3639
|
+
private async closeRunDelegatedRunAdmission(
|
|
3640
|
+
run: IActiveRun,
|
|
3641
|
+
awaitSettlement = true,
|
|
3642
|
+
): Promise<void> {
|
|
3643
|
+
const owner = run.delegatedRunAdmissionOwner;
|
|
3644
|
+
if (!owner) return;
|
|
3645
|
+
if (!run.delegatedRunAdmissionClose) {
|
|
3646
|
+
run.delegatedRunAdmissionClose = this.requestDelegatedRunAdmissionClose(owner);
|
|
3647
|
+
void run.delegatedRunAdmissionClose.catch(() => undefined);
|
|
3648
|
+
}
|
|
3649
|
+
if (awaitSettlement) await run.delegatedRunAdmissionClose;
|
|
3339
3650
|
}
|
|
3340
3651
|
|
|
3341
3652
|
private async promoteQueuedPrompt(queued: IQueuedPrompt, run: IActiveRun): Promise<void> {
|
|
@@ -3343,13 +3654,19 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3343
3654
|
const options = queued.options!;
|
|
3344
3655
|
let projectionReserved = false;
|
|
3345
3656
|
try {
|
|
3657
|
+
this.assertPromptPromotion(queued, run);
|
|
3658
|
+
await this.acquireDelegatedRunAdmission(queued, run);
|
|
3659
|
+
this.assertPromptPromotion(queued, run);
|
|
3346
3660
|
await this.commitRevertedBranch(run.state, run.stored, run.scopeId, run.scope as TScope);
|
|
3661
|
+
this.assertPromptPromotion(queued, run);
|
|
3662
|
+
const generationPrompt = cloneSerializable(prompt.modelMessage.content) as Parameters<
|
|
3663
|
+
plugins.IAgentSession['beginGeneration']
|
|
3664
|
+
>[0];
|
|
3665
|
+
this.assertPromptPromotion(queued, run);
|
|
3347
3666
|
run.transaction = await this.withRunCompactorContext(
|
|
3348
3667
|
run,
|
|
3349
3668
|
() => run.stored.agentSession.beginGeneration(
|
|
3350
|
-
|
|
3351
|
-
plugins.IAgentSession['beginGeneration']
|
|
3352
|
-
>[0],
|
|
3669
|
+
generationPrompt,
|
|
3353
3670
|
{ generationId: run.runId },
|
|
3354
3671
|
),
|
|
3355
3672
|
);
|
|
@@ -3411,10 +3728,21 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3411
3728
|
let cleanupErrors: unknown[] = [];
|
|
3412
3729
|
let cleanupFailure: unknown;
|
|
3413
3730
|
try {
|
|
3414
|
-
|
|
3731
|
+
await this.closeRunDelegatedRunAdmission(
|
|
3732
|
+
run,
|
|
3733
|
+
!run.delegatedRunAdmissionOwner?.acquisitionDetached,
|
|
3734
|
+
);
|
|
3735
|
+
} catch (closeError) {
|
|
3736
|
+
cleanupErrors.push(closeError);
|
|
3737
|
+
}
|
|
3738
|
+
try {
|
|
3739
|
+
cleanupErrors.push(...await this.rollbackAdmission(run, projectionReserved, safeError));
|
|
3415
3740
|
} catch (rollbackError) {
|
|
3416
3741
|
cleanupFailure = rollbackError;
|
|
3417
3742
|
}
|
|
3743
|
+
if (cleanupFailure !== undefined && cleanupErrors.length > 0) {
|
|
3744
|
+
cleanupFailure = combineErrors([cleanupFailure, ...cleanupErrors]);
|
|
3745
|
+
}
|
|
3418
3746
|
if (run.state.lifecycle === 'fenced' && cleanupFailure === undefined) {
|
|
3419
3747
|
const combined = combineErrors([safeError, ...cleanupErrors]);
|
|
3420
3748
|
try {
|
|
@@ -3526,6 +3854,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3526
3854
|
stored.outstandingPromptBytes = Math.max(0, stored.outstandingPromptBytes - queued.byteSize);
|
|
3527
3855
|
delete queued.prompt;
|
|
3528
3856
|
delete queued.options;
|
|
3857
|
+
delete queued.delegationSeed;
|
|
3529
3858
|
const terminal: IFlexPromptQueueEntry = {
|
|
3530
3859
|
...this.projectPromptQueueEntry(queued),
|
|
3531
3860
|
status,
|
|
@@ -3587,6 +3916,11 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3587
3916
|
}
|
|
3588
3917
|
|
|
3589
3918
|
private purgeStoredPromptQueue(stored: IStoredSessionState): void {
|
|
3919
|
+
for (const queued of stored.outstandingPromptsById.values()) {
|
|
3920
|
+
delete queued.prompt;
|
|
3921
|
+
delete queued.options;
|
|
3922
|
+
delete queued.delegationSeed;
|
|
3923
|
+
}
|
|
3590
3924
|
stored.promptQueue.length = 0;
|
|
3591
3925
|
stored.outstandingPromptsById.clear();
|
|
3592
3926
|
stored.terminalPromptQueueEntries.clear();
|
|
@@ -3606,6 +3940,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3606
3940
|
const result = normalizeRunResult(rawResult);
|
|
3607
3941
|
if (!run.modelResolution) throw new FlexHarnessValidationError('Generation completed without a resolved model.');
|
|
3608
3942
|
await this.finalizeRunReversion(run, 'completed');
|
|
3943
|
+
await this.closeRunDelegatedRunAdmission(run);
|
|
3609
3944
|
const terminal = this.buildTerminal(run, 'completed', result);
|
|
3610
3945
|
try {
|
|
3611
3946
|
await this.mutateProjection(run.state, run.stored, () => {
|
|
@@ -3683,10 +4018,19 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3683
4018
|
? run.ownerCancellation!
|
|
3684
4019
|
: this.projectExternalError(run, error, generated ? 'persistence' : 'agentSession');
|
|
3685
4020
|
const errors: unknown[] = [safeError];
|
|
4021
|
+
let reversionFailed = false;
|
|
3686
4022
|
try {
|
|
3687
4023
|
await this.finalizeRunReversion(run, cancelled ? 'cancelled' : 'failed');
|
|
3688
4024
|
} catch (reversionError) {
|
|
3689
4025
|
errors.push(reversionError);
|
|
4026
|
+
reversionFailed = true;
|
|
4027
|
+
}
|
|
4028
|
+
try {
|
|
4029
|
+
await this.closeRunDelegatedRunAdmission(run);
|
|
4030
|
+
} catch (closeError) {
|
|
4031
|
+
if (!errors.includes(closeError)) errors.push(closeError);
|
|
4032
|
+
}
|
|
4033
|
+
if (reversionFailed) {
|
|
3690
4034
|
const combined = combineErrors(errors);
|
|
3691
4035
|
await this.fenceNamespace(run.state, run, combined);
|
|
3692
4036
|
throw combined;
|
|
@@ -4613,15 +4957,27 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4613
4957
|
.map((definition) => `- ${definition.name}: ${definition.description}`)
|
|
4614
4958
|
.join('\n');
|
|
4615
4959
|
return plugins.tool({
|
|
4616
|
-
description: `Run one configured FlexHarness subagent in the foreground and return its final text.\nAvailable subagents:\n${available}`,
|
|
4960
|
+
description: `Run one configured FlexHarness subagent in the foreground and return its final text. Omit taskId to create a new child. Supply taskId only to resume the exact child ID returned by an earlier completed delegate call in a later parent run.\nAvailable subagents:\n${available}`,
|
|
4617
4961
|
inputSchema: plugins.z.object({
|
|
4618
4962
|
description: plugins.z.string(),
|
|
4619
4963
|
prompt: plugins.z.string(),
|
|
4620
4964
|
subagentType: plugins.z.string(),
|
|
4621
|
-
taskId: plugins.z.string()
|
|
4965
|
+
taskId: plugins.z.string()
|
|
4966
|
+
.describe('Omit for a new child. For a later-run resume, use only the exact taskId returned by an earlier completed delegate call.')
|
|
4967
|
+
.optional(),
|
|
4622
4968
|
}).strict(),
|
|
4623
|
-
execute: (input: IFlexSubagentTaskInput, options?: { toolCallId?: string }) =>
|
|
4624
|
-
|
|
4969
|
+
execute: async (input: IFlexSubagentTaskInput, options?: { toolCallId?: string }) => {
|
|
4970
|
+
try {
|
|
4971
|
+
return await this.executeSubagentTask(run, input, options?.toolCallId);
|
|
4972
|
+
} catch (error) {
|
|
4973
|
+
if (
|
|
4974
|
+
options?.toolCallId
|
|
4975
|
+
&& error instanceof FlexHarnessValidationError
|
|
4976
|
+
&& this.trustedInternalErrors.has(error)
|
|
4977
|
+
) run.trustedToolExecutionErrors.set(options.toolCallId, error);
|
|
4978
|
+
throw error;
|
|
4979
|
+
}
|
|
4980
|
+
},
|
|
4625
4981
|
});
|
|
4626
4982
|
}
|
|
4627
4983
|
|
|
@@ -4716,6 +5072,12 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4716
5072
|
...(definition.system === undefined ? {} : { system: definition.system }),
|
|
4717
5073
|
...(definition.maxSteps === undefined ? {} : { maxSteps: definition.maxSteps }),
|
|
4718
5074
|
};
|
|
5075
|
+
const delegationSeed = this.createDelegatedRunAdmissionSeed(
|
|
5076
|
+
run,
|
|
5077
|
+
child,
|
|
5078
|
+
definition,
|
|
5079
|
+
toolCallId,
|
|
5080
|
+
);
|
|
4719
5081
|
queued = await this.enqueuePromptInternal(
|
|
4720
5082
|
run.scopeId,
|
|
4721
5083
|
child.session.sessionId,
|
|
@@ -4723,7 +5085,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4723
5085
|
childOptions,
|
|
4724
5086
|
undefined,
|
|
4725
5087
|
undefined,
|
|
4726
|
-
|
|
5088
|
+
delegationSeed,
|
|
4727
5089
|
run.controller.signal,
|
|
4728
5090
|
);
|
|
4729
5091
|
admission = await queued.started;
|
|
@@ -4777,6 +5139,42 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4777
5139
|
}
|
|
4778
5140
|
}
|
|
4779
5141
|
|
|
5142
|
+
private createDelegatedRunAdmissionSeed(
|
|
5143
|
+
run: IActiveRun,
|
|
5144
|
+
child: IStoredSessionState,
|
|
5145
|
+
definition: Readonly<IFlexSubagentDefinition>,
|
|
5146
|
+
toolCallId: string,
|
|
5147
|
+
): IDelegatedRunAdmissionSeed {
|
|
5148
|
+
const childGeneration = requireSessionGeneration(child.session);
|
|
5149
|
+
const parentGeneration = requireSessionGeneration(run.stored.session);
|
|
5150
|
+
if (!child.session.parentRunId || !child.session.parentToolCallId) {
|
|
5151
|
+
throw new FlexHarnessValidationError('Subagent session origin metadata is missing.');
|
|
5152
|
+
}
|
|
5153
|
+
const seed = Object.freeze({
|
|
5154
|
+
state: run.state,
|
|
5155
|
+
childStored: child,
|
|
5156
|
+
parentRun: run,
|
|
5157
|
+
parentStored: run.stored,
|
|
5158
|
+
scopeId: run.scopeId,
|
|
5159
|
+
scope: run.scope,
|
|
5160
|
+
storageKey: run.state.storageKey,
|
|
5161
|
+
sessionId: child.session.sessionId,
|
|
5162
|
+
...childGeneration,
|
|
5163
|
+
originParentRunId: child.session.parentRunId,
|
|
5164
|
+
originParentToolCallId: child.session.parentToolCallId,
|
|
5165
|
+
parentSessionId: run.sessionId,
|
|
5166
|
+
parentSessionGenerationId: parentGeneration.sessionGenerationId,
|
|
5167
|
+
parentSessionGenerationSequence: parentGeneration.sessionGenerationSequence,
|
|
5168
|
+
parentQueueId: run.queueId,
|
|
5169
|
+
parentRunId: run.runId,
|
|
5170
|
+
parentToolCallId: toolCallId,
|
|
5171
|
+
agent: definition.name,
|
|
5172
|
+
depth: (run.stored.session.depth ?? 0) + 1,
|
|
5173
|
+
});
|
|
5174
|
+
this.assertDelegatedRunAdmissionOwnership(seed, run.state, child, run.scopeId);
|
|
5175
|
+
return seed;
|
|
5176
|
+
}
|
|
5177
|
+
|
|
4780
5178
|
private async acquireSubagentSession(
|
|
4781
5179
|
run: IActiveRun,
|
|
4782
5180
|
definition: Readonly<IFlexSubagentDefinition>,
|
|
@@ -4865,7 +5263,11 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4865
5263
|
return;
|
|
4866
5264
|
}
|
|
4867
5265
|
if (taskId !== undefined || state.tombstones.has(sessionId)) {
|
|
4868
|
-
throw
|
|
5266
|
+
throw this.trustInternalError(
|
|
5267
|
+
new FlexHarnessValidationError(
|
|
5268
|
+
'The supplied taskId does not identify a resumable child. Omit taskId to create a new child, or use only an exact taskId returned by an earlier completed delegate call.',
|
|
5269
|
+
),
|
|
5270
|
+
);
|
|
4869
5271
|
}
|
|
4870
5272
|
const timestamp = new Date().toISOString();
|
|
4871
5273
|
metadata = {
|
|
@@ -6430,10 +6832,12 @@ export class FlexHarness<TScope = unknown> {
|
|
|
6430
6832
|
const part = run.callbackParts.find((entry) => entry.partId === partId && entry.type === 'tool');
|
|
6431
6833
|
if (!part || part.type !== 'tool' || part.status !== 'running') return;
|
|
6432
6834
|
try {
|
|
6835
|
+
const trustedExecutionError = run.trustedToolExecutionErrors.get(event.toolCallId);
|
|
6836
|
+
run.trustedToolExecutionErrors.delete(event.toolCallId);
|
|
6433
6837
|
const output = event.success ? normalizeJsonValue(event.output, this.toolOutputLimits) : undefined;
|
|
6434
6838
|
const projectedError = event.success
|
|
6435
6839
|
? undefined
|
|
6436
|
-
: this.projectExternalError(run, event.error, 'toolCallback');
|
|
6840
|
+
: this.projectExternalError(run, trustedExecutionError ?? event.error, 'toolCallback');
|
|
6437
6841
|
const bytes = event.success ? jsonBytes(output) : Buffer.byteLength(projectedError!.message);
|
|
6438
6842
|
if (!this.reserveCallbackCapacity(run, 1, bytes, 0)) return;
|
|
6439
6843
|
if (event.success) {
|
|
@@ -7166,6 +7570,39 @@ export class FlexHarness<TScope = unknown> {
|
|
|
7166
7570
|
.filter((error): error is Error => error !== undefined);
|
|
7167
7571
|
}
|
|
7168
7572
|
|
|
7573
|
+
private delegatedRunAdmissionOwnersForGeneration(
|
|
7574
|
+
state: IStorageState,
|
|
7575
|
+
session: Readonly<Pick<
|
|
7576
|
+
IFlexSession,
|
|
7577
|
+
'sessionId' | 'sessionGenerationId' | 'sessionGenerationSequence'
|
|
7578
|
+
>>,
|
|
7579
|
+
): IDelegatedRunAdmissionOwner[] {
|
|
7580
|
+
return [...state.delegatedRunAdmissionOwners].filter((owner) =>
|
|
7581
|
+
owner.storageState === state
|
|
7582
|
+
&& owner.context.sessionId === session.sessionId
|
|
7583
|
+
&& owner.context.sessionGenerationId === session.sessionGenerationId
|
|
7584
|
+
&& owner.context.sessionGenerationSequence === session.sessionGenerationSequence);
|
|
7585
|
+
}
|
|
7586
|
+
|
|
7587
|
+
private async settleDelegatedRunAdmissionOwners(
|
|
7588
|
+
owners: readonly IDelegatedRunAdmissionOwner[],
|
|
7589
|
+
): Promise<Error[]> {
|
|
7590
|
+
const results = await Promise.allSettled(
|
|
7591
|
+
owners.map((owner) => this.requestDelegatedRunAdmissionClose(owner)),
|
|
7592
|
+
);
|
|
7593
|
+
return results.flatMap((result, index) => {
|
|
7594
|
+
if (result.status === 'fulfilled') return [];
|
|
7595
|
+
const error = result.reason instanceof Error
|
|
7596
|
+
? result.reason
|
|
7597
|
+
: this.projectExternalError(
|
|
7598
|
+
owners[index].run,
|
|
7599
|
+
result.reason,
|
|
7600
|
+
'delegatedRunAdmissionProvider',
|
|
7601
|
+
);
|
|
7602
|
+
return [error];
|
|
7603
|
+
});
|
|
7604
|
+
}
|
|
7605
|
+
|
|
7169
7606
|
private projectExternalError(
|
|
7170
7607
|
run: IActiveRun,
|
|
7171
7608
|
error: unknown,
|
|
@@ -7288,6 +7725,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
7288
7725
|
tombstones: new Map(snapshot.tombstones.map((entry) => [entry.sessionId, entry])),
|
|
7289
7726
|
tombstoneCleanups: new Map(),
|
|
7290
7727
|
activeRuns: new Map(),
|
|
7728
|
+
delegatedRunAdmissionOwners: new Set(),
|
|
7291
7729
|
pendingPermissions: new Map(),
|
|
7292
7730
|
scopeQueue: Promise.resolve(),
|
|
7293
7731
|
lifecycle: 'active',
|
|
@@ -8576,6 +9014,9 @@ export class FlexHarness<TScope = unknown> {
|
|
|
8576
9014
|
this.appendUnexpectedErrors(errors, settled[0].reason);
|
|
8577
9015
|
}
|
|
8578
9016
|
}
|
|
9017
|
+
errors.push(...await this.settleDelegatedRunAdmissionOwners(
|
|
9018
|
+
this.delegatedRunAdmissionOwnersForGeneration(state, tombstone),
|
|
9019
|
+
));
|
|
8579
9020
|
if (retained) {
|
|
8580
9021
|
if (retained.stored.promptQueueDrain) {
|
|
8581
9022
|
const settled = await Promise.allSettled([retained.stored.promptQueueDrain]);
|
|
@@ -8680,7 +9121,11 @@ export class FlexHarness<TScope = unknown> {
|
|
|
8680
9121
|
for (const result of runResults) {
|
|
8681
9122
|
if (result.status === 'rejected') this.appendUnexpectedErrors(cleanupErrors, result.reason);
|
|
8682
9123
|
}
|
|
8683
|
-
if (
|
|
9124
|
+
if (
|
|
9125
|
+
dependentRuns.length > 0
|
|
9126
|
+
|| (currentRun.delegatedRunAdmissionOwner !== undefined
|
|
9127
|
+
&& state.delegatedRunAdmissionOwners.has(currentRun.delegatedRunAdmissionOwner))
|
|
9128
|
+
) {
|
|
8684
9129
|
state.fenceAdditionalErrors.push(cause, ...cleanupErrors);
|
|
8685
9130
|
const stateLoad = this.stateLoads.get(state.storageKey);
|
|
8686
9131
|
if (!stateLoad) {
|
|
@@ -8697,6 +9142,9 @@ export class FlexHarness<TScope = unknown> {
|
|
|
8697
9142
|
void deferredDrain.catch(() => undefined);
|
|
8698
9143
|
return;
|
|
8699
9144
|
}
|
|
9145
|
+
cleanupErrors.push(...await this.settleDelegatedRunAdmissionOwners([
|
|
9146
|
+
...state.delegatedRunAdmissionOwners,
|
|
9147
|
+
]));
|
|
8700
9148
|
const detachedCleanupAttempts = this.beginDetachedCleanupSettlement(state);
|
|
8701
9149
|
await Promise.allSettled([...state.sessionInitializations.values()]);
|
|
8702
9150
|
cleanupErrors.push(...await this.closeOrphanedResources(state.storageKey));
|
|
@@ -8784,6 +9232,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
8784
9232
|
state.tombstoneCleanups.set(currentTombstoneRoot, currentTombstoneCleanup);
|
|
8785
9233
|
}
|
|
8786
9234
|
state.activeRuns.clear();
|
|
9235
|
+
state.delegatedRunAdmissionOwners.clear();
|
|
8787
9236
|
state.pendingPermissions.clear();
|
|
8788
9237
|
state.initializingSessions.clear();
|
|
8789
9238
|
state.sessionInitializations.clear();
|
|
@@ -9221,6 +9670,9 @@ export class FlexHarness<TScope = unknown> {
|
|
|
9221
9670
|
for (const result of runResults) {
|
|
9222
9671
|
if (result.status === 'rejected') this.appendUnexpectedErrors(errors, result.reason);
|
|
9223
9672
|
}
|
|
9673
|
+
errors.push(...await this.settleDelegatedRunAdmissionOwners([
|
|
9674
|
+
...state.delegatedRunAdmissionOwners,
|
|
9675
|
+
]));
|
|
9224
9676
|
for (const stored of state.sessions.values()) {
|
|
9225
9677
|
try {
|
|
9226
9678
|
await this.abortStoredSession(
|
|
@@ -9319,6 +9771,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
9319
9771
|
state.sessionDeletions.clear();
|
|
9320
9772
|
state.tombstoneCleanups.clear();
|
|
9321
9773
|
state.activeRuns.clear();
|
|
9774
|
+
state.delegatedRunAdmissionOwners.clear();
|
|
9322
9775
|
state.pendingPermissions.clear();
|
|
9323
9776
|
state.initializingSessions.clear();
|
|
9324
9777
|
state.sessionInitializations.clear();
|