@modelprofile.com/flexharness 5.2.0 → 5.3.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.
- package/changelog.md +19 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.flexharness.d.ts +12 -0
- package/dist_ts/classes.flexharness.js +413 -32
- package/dist_ts/interfaces.d.ts +45 -3
- package/dist_ts/interfaces.js +1 -1
- package/dist_ts/utils.json.js +77 -2
- package/package.json +1 -1
- package/readme.hints.md +2 -2
- package/readme.md +50 -8
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.flexharness.ts +556 -32
- package/ts/interfaces.ts +52 -1
- package/ts/utils.json.ts +116 -1
|
@@ -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,
|
|
@@ -49,6 +51,7 @@ import type {
|
|
|
49
51
|
IFlexPermissionRequestInput,
|
|
50
52
|
IFlexPermissionSnapshot,
|
|
51
53
|
IFlexProjectGoalResult,
|
|
54
|
+
IFlexProjectManagementSessionContext,
|
|
52
55
|
IFlexProjectManagementSnapshot,
|
|
53
56
|
IFlexProjectManagementTombstone,
|
|
54
57
|
IFlexProjectManagementWriteContext,
|
|
@@ -85,6 +88,7 @@ import type {
|
|
|
85
88
|
IFlexSlashCommandExecutionOptions,
|
|
86
89
|
IFlexSlashCommandHandlerRegistration,
|
|
87
90
|
IFlexSubagentDefinition,
|
|
91
|
+
IFlexSubagentProvenance,
|
|
88
92
|
IFlexTerminalProjection,
|
|
89
93
|
IFlexToolHandle,
|
|
90
94
|
IFlexToolMessagePart,
|
|
@@ -297,6 +301,7 @@ interface IStorageState {
|
|
|
297
301
|
tombstones: Map<string, IFlexSessionTombstone>;
|
|
298
302
|
tombstoneCleanups: Map<string, Promise<void>>;
|
|
299
303
|
activeRuns: Map<string, IActiveRun>;
|
|
304
|
+
delegatedRunAdmissionOwners: Set<IDelegatedRunAdmissionOwner>;
|
|
300
305
|
pendingPermissions: Map<string, IPendingPermission>;
|
|
301
306
|
scopeQueue: Promise<void>;
|
|
302
307
|
lifecycle: 'active' | 'retiring' | 'fenced' | 'retired';
|
|
@@ -331,6 +336,7 @@ interface IActiveRun {
|
|
|
331
336
|
callbacksClosed: boolean;
|
|
332
337
|
reasoningPartIds: Map<string, string>;
|
|
333
338
|
toolPartIds: Map<string, string>;
|
|
339
|
+
trustedToolExecutionErrors: Map<string, Error>;
|
|
334
340
|
subagentCallCount: number;
|
|
335
341
|
subagentSessionIds: Set<string>;
|
|
336
342
|
pendingPermissionIds: Set<string>;
|
|
@@ -338,9 +344,56 @@ interface IActiveRun {
|
|
|
338
344
|
reservedUserMessage?: IFlexMessage;
|
|
339
345
|
reservedAssistantMessage?: IFlexMessage;
|
|
340
346
|
modelResolution?: IFlexResolvedModel;
|
|
347
|
+
delegatedRunAdmissionOwner?: IDelegatedRunAdmissionOwner;
|
|
348
|
+
delegatedRunAdmissionClose?: Promise<void>;
|
|
341
349
|
completion: Promise<IFlexPromptResult>;
|
|
342
350
|
}
|
|
343
351
|
|
|
352
|
+
interface IDelegatedRunAdmissionSeed {
|
|
353
|
+
readonly state: IStorageState;
|
|
354
|
+
readonly childStored: IStoredSessionState;
|
|
355
|
+
readonly parentRun: IActiveRun;
|
|
356
|
+
readonly parentStored: IStoredSessionState;
|
|
357
|
+
readonly scopeId: string;
|
|
358
|
+
readonly scope: unknown;
|
|
359
|
+
readonly storageKey: string;
|
|
360
|
+
readonly sessionId: string;
|
|
361
|
+
readonly sessionGenerationId: string;
|
|
362
|
+
readonly sessionGenerationSequence: number;
|
|
363
|
+
readonly originParentRunId: string;
|
|
364
|
+
readonly originParentToolCallId: string;
|
|
365
|
+
readonly parentSessionId: string;
|
|
366
|
+
readonly parentSessionGenerationId: string;
|
|
367
|
+
readonly parentSessionGenerationSequence: number;
|
|
368
|
+
readonly parentQueueId: string;
|
|
369
|
+
readonly parentRunId: string;
|
|
370
|
+
readonly parentToolCallId: string;
|
|
371
|
+
readonly agent: string;
|
|
372
|
+
readonly depth: number;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
type TDelegatedRunAdmissionOwnerStatus =
|
|
376
|
+
| 'acquiring'
|
|
377
|
+
| 'acquired'
|
|
378
|
+
| 'close-requested'
|
|
379
|
+
| 'retrying'
|
|
380
|
+
| 'closed';
|
|
381
|
+
|
|
382
|
+
interface IDelegatedRunAdmissionOwner {
|
|
383
|
+
readonly storageState: IStorageState;
|
|
384
|
+
readonly run: IActiveRun;
|
|
385
|
+
readonly context: Readonly<IFlexDelegatedRunAdmissionContext<unknown>>;
|
|
386
|
+
status: TDelegatedRunAdmissionOwnerStatus;
|
|
387
|
+
acquisitionSettled: boolean;
|
|
388
|
+
acquisitionDetached: boolean;
|
|
389
|
+
acquisitionCompletion?: Promise<void>;
|
|
390
|
+
acquisitionError?: Error;
|
|
391
|
+
lease?: IFlexDelegatedRunAdmissionLease;
|
|
392
|
+
closeAttempt?: Promise<void>;
|
|
393
|
+
closeAttemptSequence: number;
|
|
394
|
+
closeFailure?: { sequence: number; error: Error };
|
|
395
|
+
}
|
|
396
|
+
|
|
344
397
|
interface IQueuedPrompt {
|
|
345
398
|
state: IStorageState;
|
|
346
399
|
stored: IStoredSessionState;
|
|
@@ -357,6 +410,7 @@ interface IQueuedPrompt {
|
|
|
357
410
|
startedAt?: string;
|
|
358
411
|
prompt?: INormalizedFlexPrompt;
|
|
359
412
|
options?: IFlexPromptOptions;
|
|
413
|
+
delegationSeed?: IDelegatedRunAdmissionSeed;
|
|
360
414
|
byteSize: number;
|
|
361
415
|
completion: Promise<IFlexPromptResult>;
|
|
362
416
|
resolveCompletion: (result: IFlexPromptResult) => void;
|
|
@@ -493,6 +547,7 @@ const maxBackgroundExecutions = 100;
|
|
|
493
547
|
const maxSubagentTaskDescriptionBytes = 256;
|
|
494
548
|
const maxSubagentPromptBytes = 64 * 1024;
|
|
495
549
|
const maxSubagentTaskIdBytes = 512;
|
|
550
|
+
const maxSubagentPermissionMetadataBytes = 16 * 1024;
|
|
496
551
|
const maxSubagentResultTextBytes = 64 * 1024;
|
|
497
552
|
const defaultMaxSubagentDepth = 1;
|
|
498
553
|
const maximumMaxSubagentDepth = 8;
|
|
@@ -630,6 +685,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
630
685
|
private readonly toolProvider: IFlexHarnessOptions<TScope>['toolProvider'];
|
|
631
686
|
private readonly resourceToolProviderResolver: IFlexHarnessOptions<TScope>['resourceToolProviderResolver'];
|
|
632
687
|
private readonly executionContextProvider: IFlexHarnessOptions<TScope>['executionContextProvider'];
|
|
688
|
+
private readonly delegatedRunAdmissionProvider: IFlexHarnessOptions<TScope>['delegatedRunAdmissionProvider'];
|
|
633
689
|
private readonly stores: IFlexHarnessStores;
|
|
634
690
|
private readonly agentSessionPolicy: IFlexAgentSessionPolicy<TScope>;
|
|
635
691
|
private readonly toolOutputLimits: Required<NonNullable<IFlexHarnessOptions<TScope>['toolOutputLimits']>>;
|
|
@@ -693,6 +749,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
693
749
|
this.toolProvider = options.toolProvider;
|
|
694
750
|
this.resourceToolProviderResolver = options.resourceToolProviderResolver;
|
|
695
751
|
this.executionContextProvider = options.executionContextProvider;
|
|
752
|
+
this.delegatedRunAdmissionProvider = options.delegatedRunAdmissionProvider;
|
|
696
753
|
this.stores = requireHarnessStores(options.stores ?? new InMemoryFlexHarnessStores());
|
|
697
754
|
this.agentSessionPolicy = normalizeAgentSessionPolicy(options.agentSessionPolicy);
|
|
698
755
|
this.toolOutputLimits = resolveJsonLimits(options.toolOutputLimits);
|
|
@@ -1326,8 +1383,13 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1326
1383
|
const rootDepth = stored.session.depth ?? 0;
|
|
1327
1384
|
const deletedAt = new Date().toISOString();
|
|
1328
1385
|
const deletedSessions = subtree.map((entry) => cloneSerializable(entry.session));
|
|
1386
|
+
const provenanceBySessionId = new Map(subtree.map((entry) => [
|
|
1387
|
+
entry.session.sessionId,
|
|
1388
|
+
this.createSubagentProvenance(state, entry.session),
|
|
1389
|
+
]));
|
|
1329
1390
|
for (const entry of subtree) {
|
|
1330
1391
|
const entrySessionId = entry.session.sessionId;
|
|
1392
|
+
const subagent = provenanceBySessionId.get(entrySessionId);
|
|
1331
1393
|
state.sessions.delete(entrySessionId);
|
|
1332
1394
|
state.retainedSessionCleanups.set(entrySessionId, {
|
|
1333
1395
|
stored: entry,
|
|
@@ -1342,6 +1404,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1342
1404
|
...(entry.session.parentSessionId === undefined
|
|
1343
1405
|
? {}
|
|
1344
1406
|
: { parentSessionId: entry.session.parentSessionId }),
|
|
1407
|
+
...(subagent === undefined ? {} : { subagent: cloneSerializable(subagent) }),
|
|
1345
1408
|
});
|
|
1346
1409
|
}
|
|
1347
1410
|
return {
|
|
@@ -1779,7 +1842,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1779
1842
|
promptOptions,
|
|
1780
1843
|
undefined,
|
|
1781
1844
|
undefined,
|
|
1782
|
-
|
|
1845
|
+
undefined,
|
|
1783
1846
|
signal,
|
|
1784
1847
|
true,
|
|
1785
1848
|
);
|
|
@@ -3085,7 +3148,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3085
3148
|
options: IFlexPromptOptions,
|
|
3086
3149
|
scheduleKey?: string,
|
|
3087
3150
|
debounceMs?: number,
|
|
3088
|
-
|
|
3151
|
+
delegationSeed?: IDelegatedRunAdmissionSeed,
|
|
3089
3152
|
admissionSignal?: AbortSignal,
|
|
3090
3153
|
slashCommandAdmission = false,
|
|
3091
3154
|
): Promise<{
|
|
@@ -3145,11 +3208,14 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3145
3208
|
if (stored.pendingReversion) {
|
|
3146
3209
|
throw new FlexHarnessSessionBusyError(sessionId, 'has a pending reversion operation');
|
|
3147
3210
|
}
|
|
3148
|
-
if (stored.session.agent !== undefined &&
|
|
3211
|
+
if (stored.session.agent !== undefined && delegationSeed === undefined) {
|
|
3149
3212
|
throw new FlexHarnessValidationError(
|
|
3150
3213
|
'Subagent sessions can only be prompted through the foreground delegate tool.',
|
|
3151
3214
|
);
|
|
3152
3215
|
}
|
|
3216
|
+
if (delegationSeed !== undefined) {
|
|
3217
|
+
this.assertDelegatedRunAdmissionOwnership(delegationSeed, state, stored, scopeId);
|
|
3218
|
+
}
|
|
3153
3219
|
if (stored.outstandingPromptsById.size >= this.promptQueueLimits.maxOutstandingPromptsPerSession) {
|
|
3154
3220
|
throw new FlexHarnessQueueFullError(
|
|
3155
3221
|
`Session "${sessionId}" has reached its outstanding prompt limit.`,
|
|
@@ -3188,7 +3254,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3188
3254
|
state,
|
|
3189
3255
|
stored,
|
|
3190
3256
|
scopeId,
|
|
3191
|
-
scope: resolved.scope.scope,
|
|
3257
|
+
scope: delegationSeed === undefined ? resolved.scope.scope : delegationSeed.scope,
|
|
3192
3258
|
sessionId,
|
|
3193
3259
|
queueId: plugins.crypto.randomUUID(),
|
|
3194
3260
|
queueSequence: ++this.promptQueueSequence,
|
|
@@ -3197,6 +3263,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3197
3263
|
...(scheduleKey === undefined ? {} : { scheduleKey, debounceMs }),
|
|
3198
3264
|
prompt: normalizedPrompt,
|
|
3199
3265
|
options: normalizedOptions,
|
|
3266
|
+
...(delegationSeed === undefined ? {} : { delegationSeed }),
|
|
3200
3267
|
byteSize,
|
|
3201
3268
|
completion,
|
|
3202
3269
|
resolveCompletion,
|
|
@@ -3318,6 +3385,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3318
3385
|
callbacksClosed: false,
|
|
3319
3386
|
reasoningPartIds: new Map(),
|
|
3320
3387
|
toolPartIds: new Map(),
|
|
3388
|
+
trustedToolExecutionErrors: new Map(),
|
|
3321
3389
|
subagentCallCount: 0,
|
|
3322
3390
|
subagentSessionIds: new Set(),
|
|
3323
3391
|
pendingPermissionIds: new Set(),
|
|
@@ -3336,6 +3404,260 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3336
3404
|
|| run.stored.outstandingPromptsById.get(run.queueId) !== queued
|
|
3337
3405
|
|| run.state.activeRuns.get(run.sessionId) !== run
|
|
3338
3406
|
) throw this.trustInternalError(new FlexHarnessAbortError('The prompt was retired during admission.'));
|
|
3407
|
+
if (queued.delegationSeed) {
|
|
3408
|
+
this.assertDelegatedRunAdmissionOwnership(
|
|
3409
|
+
queued.delegationSeed,
|
|
3410
|
+
run.state,
|
|
3411
|
+
run.stored,
|
|
3412
|
+
run.scopeId,
|
|
3413
|
+
);
|
|
3414
|
+
if (
|
|
3415
|
+
run.queueId !== queued.queueId
|
|
3416
|
+
|| run.runId !== queued.runId
|
|
3417
|
+
|| run.sessionId !== queued.delegationSeed.sessionId
|
|
3418
|
+
) {
|
|
3419
|
+
throw this.trustInternalError(
|
|
3420
|
+
new FlexHarnessAbortError('The delegated prompt lost its exact child run ownership.'),
|
|
3421
|
+
);
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
|
|
3426
|
+
private assertDelegatedRunAdmissionOwnership(
|
|
3427
|
+
seed: IDelegatedRunAdmissionSeed,
|
|
3428
|
+
state: IStorageState,
|
|
3429
|
+
childStored: IStoredSessionState,
|
|
3430
|
+
scopeId: string,
|
|
3431
|
+
): void {
|
|
3432
|
+
const child = childStored.session;
|
|
3433
|
+
const parentRun = seed.parentRun;
|
|
3434
|
+
const parent = seed.parentStored.session;
|
|
3435
|
+
const parentPartId = parentRun.toolPartIds.get(seed.parentToolCallId);
|
|
3436
|
+
const parentPart = parentRun.callbackParts.find((part): part is IFlexToolMessagePart =>
|
|
3437
|
+
part.type === 'tool'
|
|
3438
|
+
&& part.partId === parentPartId
|
|
3439
|
+
&& part.toolCallId === seed.parentToolCallId);
|
|
3440
|
+
if (
|
|
3441
|
+
seed.state !== state
|
|
3442
|
+
|| seed.storageKey !== state.storageKey
|
|
3443
|
+
|| seed.scopeId !== scopeId
|
|
3444
|
+
|| seed.childStored !== childStored
|
|
3445
|
+
|| childStored.storageKey !== seed.storageKey
|
|
3446
|
+
|| state.sessions.get(seed.sessionId) !== childStored
|
|
3447
|
+
|| state.tombstones.has(seed.sessionId)
|
|
3448
|
+
|| state.initializingSessions.has(seed.sessionId)
|
|
3449
|
+
|| child.sessionId !== seed.sessionId
|
|
3450
|
+
|| child.sessionGenerationId !== seed.sessionGenerationId
|
|
3451
|
+
|| child.sessionGenerationSequence !== seed.sessionGenerationSequence
|
|
3452
|
+
|| child.parentSessionId !== seed.parentSessionId
|
|
3453
|
+
|| child.parentRunId !== seed.originParentRunId
|
|
3454
|
+
|| child.parentToolCallId !== seed.originParentToolCallId
|
|
3455
|
+
|| child.agent !== seed.agent
|
|
3456
|
+
|| child.depth !== seed.depth
|
|
3457
|
+
|| seed.parentStored !== parentRun.stored
|
|
3458
|
+
|| seed.parentStored.storageKey !== seed.storageKey
|
|
3459
|
+
|| parentRun.state !== state
|
|
3460
|
+
|| parentRun.scopeId !== seed.scopeId
|
|
3461
|
+
|| parentRun.scope !== seed.scope
|
|
3462
|
+
|| parentRun.sessionId !== seed.parentSessionId
|
|
3463
|
+
|| parentRun.queueId !== seed.parentQueueId
|
|
3464
|
+
|| parentRun.runId !== seed.parentRunId
|
|
3465
|
+
|| state.sessions.get(seed.parentSessionId) !== seed.parentStored
|
|
3466
|
+
|| state.tombstones.has(seed.parentSessionId)
|
|
3467
|
+
|| parent.sessionId !== seed.parentSessionId
|
|
3468
|
+
|| parent.sessionGenerationId !== seed.parentSessionGenerationId
|
|
3469
|
+
|| parent.sessionGenerationSequence !== seed.parentSessionGenerationSequence
|
|
3470
|
+
|| state.activeRuns.get(seed.parentSessionId) !== parentRun
|
|
3471
|
+
|| parentRun.callbacksClosed
|
|
3472
|
+
|| parentRun.controller.signal.aborted
|
|
3473
|
+
|| parentPart?.status !== 'running'
|
|
3474
|
+
) {
|
|
3475
|
+
throw this.trustInternalError(
|
|
3476
|
+
new FlexHarnessAbortError('The delegated prompt lost its exact parent or child ownership.'),
|
|
3477
|
+
);
|
|
3478
|
+
}
|
|
3479
|
+
}
|
|
3480
|
+
|
|
3481
|
+
private async acquireDelegatedRunAdmission(
|
|
3482
|
+
queued: IQueuedPrompt,
|
|
3483
|
+
run: IActiveRun,
|
|
3484
|
+
): Promise<void> {
|
|
3485
|
+
const seed = queued.delegationSeed;
|
|
3486
|
+
const provider = this.delegatedRunAdmissionProvider;
|
|
3487
|
+
if (!seed || !provider) return;
|
|
3488
|
+
const context = Object.freeze({
|
|
3489
|
+
scopeId: seed.scopeId,
|
|
3490
|
+
scope: seed.scope as TScope,
|
|
3491
|
+
storageKey: seed.storageKey,
|
|
3492
|
+
sessionId: seed.sessionId,
|
|
3493
|
+
sessionGenerationId: seed.sessionGenerationId,
|
|
3494
|
+
sessionGenerationSequence: seed.sessionGenerationSequence,
|
|
3495
|
+
queueId: queued.queueId,
|
|
3496
|
+
runId: run.runId,
|
|
3497
|
+
parentSessionId: seed.parentSessionId,
|
|
3498
|
+
parentSessionGenerationId: seed.parentSessionGenerationId,
|
|
3499
|
+
parentSessionGenerationSequence: seed.parentSessionGenerationSequence,
|
|
3500
|
+
parentQueueId: seed.parentQueueId,
|
|
3501
|
+
parentRunId: seed.parentRunId,
|
|
3502
|
+
parentToolCallId: seed.parentToolCallId,
|
|
3503
|
+
originParentRunId: seed.originParentRunId,
|
|
3504
|
+
originParentToolCallId: seed.originParentToolCallId,
|
|
3505
|
+
agent: seed.agent,
|
|
3506
|
+
depth: seed.depth,
|
|
3507
|
+
signal: run.controller.signal,
|
|
3508
|
+
});
|
|
3509
|
+
const owner: IDelegatedRunAdmissionOwner = {
|
|
3510
|
+
storageState: run.state,
|
|
3511
|
+
run,
|
|
3512
|
+
context,
|
|
3513
|
+
status: 'acquiring',
|
|
3514
|
+
acquisitionSettled: false,
|
|
3515
|
+
acquisitionDetached: false,
|
|
3516
|
+
closeAttemptSequence: 0,
|
|
3517
|
+
};
|
|
3518
|
+
run.state.delegatedRunAdmissionOwners.add(owner);
|
|
3519
|
+
run.delegatedRunAdmissionOwner = owner;
|
|
3520
|
+
const acquisition = Promise.resolve()
|
|
3521
|
+
.then(() => provider.acquireDelegatedRunAdmission(context))
|
|
3522
|
+
.then((lease) => this.normalizeDelegatedRunAdmissionLease(lease))
|
|
3523
|
+
.then(
|
|
3524
|
+
(lease) => {
|
|
3525
|
+
owner.lease = lease;
|
|
3526
|
+
owner.acquisitionSettled = true;
|
|
3527
|
+
if (owner.status === 'acquiring') owner.status = 'acquired';
|
|
3528
|
+
if (owner.status === 'close-requested') {
|
|
3529
|
+
void this.startDelegatedRunAdmissionCloseAttempt(owner).catch(() => undefined);
|
|
3530
|
+
}
|
|
3531
|
+
},
|
|
3532
|
+
(error: unknown) => {
|
|
3533
|
+
owner.acquisitionError = this.projectExternalError(
|
|
3534
|
+
run,
|
|
3535
|
+
error,
|
|
3536
|
+
'delegatedRunAdmissionProvider',
|
|
3537
|
+
);
|
|
3538
|
+
owner.acquisitionSettled = true;
|
|
3539
|
+
owner.status = 'closed';
|
|
3540
|
+
run.state.delegatedRunAdmissionOwners.delete(owner);
|
|
3541
|
+
},
|
|
3542
|
+
);
|
|
3543
|
+
owner.acquisitionCompletion = acquisition;
|
|
3544
|
+
|
|
3545
|
+
let resolveAbort!: () => void;
|
|
3546
|
+
const abort = new Promise<'aborted'>((resolve) => {
|
|
3547
|
+
resolveAbort = () => resolve('aborted');
|
|
3548
|
+
});
|
|
3549
|
+
run.controller.signal.addEventListener('abort', resolveAbort, { once: true });
|
|
3550
|
+
if (run.controller.signal.aborted) resolveAbort();
|
|
3551
|
+
try {
|
|
3552
|
+
const outcome = await Promise.race([
|
|
3553
|
+
acquisition.then(() => 'settled' as const),
|
|
3554
|
+
abort,
|
|
3555
|
+
]);
|
|
3556
|
+
if (outcome === 'aborted') {
|
|
3557
|
+
owner.acquisitionDetached = true;
|
|
3558
|
+
throw run.controller.signal.reason ?? this.trustInternalError(new FlexHarnessAbortError());
|
|
3559
|
+
}
|
|
3560
|
+
if (run.controller.signal.aborted) {
|
|
3561
|
+
throw run.controller.signal.reason ?? this.trustInternalError(new FlexHarnessAbortError());
|
|
3562
|
+
}
|
|
3563
|
+
if (owner.acquisitionError) throw owner.acquisitionError;
|
|
3564
|
+
if (owner.status !== 'acquired' || !owner.lease) {
|
|
3565
|
+
throw this.trustInternalError(
|
|
3566
|
+
new FlexHarnessAbortError('The delegated run admission was retired during acquisition.'),
|
|
3567
|
+
);
|
|
3568
|
+
}
|
|
3569
|
+
} finally {
|
|
3570
|
+
run.controller.signal.removeEventListener('abort', resolveAbort);
|
|
3571
|
+
}
|
|
3572
|
+
}
|
|
3573
|
+
|
|
3574
|
+
private normalizeDelegatedRunAdmissionLease(
|
|
3575
|
+
value: IFlexDelegatedRunAdmissionLease,
|
|
3576
|
+
): IFlexDelegatedRunAdmissionLease {
|
|
3577
|
+
if (!value || (typeof value !== 'object' && typeof value !== 'function')) {
|
|
3578
|
+
throw new FlexHarnessValidationError(
|
|
3579
|
+
'Delegated run admission provider returned an invalid lease.',
|
|
3580
|
+
);
|
|
3581
|
+
}
|
|
3582
|
+
const close = value.close;
|
|
3583
|
+
if (typeof close !== 'function') {
|
|
3584
|
+
throw new FlexHarnessValidationError(
|
|
3585
|
+
'Delegated run admission provider returned an invalid lease.',
|
|
3586
|
+
);
|
|
3587
|
+
}
|
|
3588
|
+
return Object.freeze({ close: () => close.call(value) });
|
|
3589
|
+
}
|
|
3590
|
+
|
|
3591
|
+
private requestDelegatedRunAdmissionClose(
|
|
3592
|
+
owner: IDelegatedRunAdmissionOwner,
|
|
3593
|
+
): Promise<void> {
|
|
3594
|
+
const priorAttemptSequence = owner.closeAttemptSequence;
|
|
3595
|
+
const joinedAttempt = owner.closeAttempt;
|
|
3596
|
+
if (owner.status === 'closed') return Promise.resolve();
|
|
3597
|
+
if (owner.status !== 'retrying') owner.status = 'close-requested';
|
|
3598
|
+
return (async () => {
|
|
3599
|
+
await owner.acquisitionCompletion;
|
|
3600
|
+
if (owner.status === 'closed') return;
|
|
3601
|
+
if (joinedAttempt) {
|
|
3602
|
+
await joinedAttempt;
|
|
3603
|
+
return;
|
|
3604
|
+
}
|
|
3605
|
+
if (owner.closeAttempt) {
|
|
3606
|
+
await owner.closeAttempt;
|
|
3607
|
+
return;
|
|
3608
|
+
}
|
|
3609
|
+
if (owner.closeFailure && owner.closeFailure.sequence > priorAttemptSequence) {
|
|
3610
|
+
throw owner.closeFailure.error;
|
|
3611
|
+
}
|
|
3612
|
+
await this.startDelegatedRunAdmissionCloseAttempt(owner);
|
|
3613
|
+
})();
|
|
3614
|
+
}
|
|
3615
|
+
|
|
3616
|
+
private startDelegatedRunAdmissionCloseAttempt(
|
|
3617
|
+
owner: IDelegatedRunAdmissionOwner,
|
|
3618
|
+
): Promise<void> {
|
|
3619
|
+
if (owner.closeAttempt) return owner.closeAttempt;
|
|
3620
|
+
if (!owner.lease) {
|
|
3621
|
+
throw new Error('Acquired delegated run admission has no lease.');
|
|
3622
|
+
}
|
|
3623
|
+
owner.status = 'retrying';
|
|
3624
|
+
owner.closeFailure = undefined;
|
|
3625
|
+
const sequence = ++owner.closeAttemptSequence;
|
|
3626
|
+
let attempt!: Promise<void>;
|
|
3627
|
+
attempt = Promise.resolve()
|
|
3628
|
+
.then(() => owner.lease!.close())
|
|
3629
|
+
.then(() => {
|
|
3630
|
+
owner.status = 'closed';
|
|
3631
|
+
delete owner.lease;
|
|
3632
|
+
owner.storageState.delegatedRunAdmissionOwners.delete(owner);
|
|
3633
|
+
}, (error: unknown) => {
|
|
3634
|
+
const projected = this.projectExternalError(
|
|
3635
|
+
owner.run,
|
|
3636
|
+
error,
|
|
3637
|
+
'delegatedRunAdmissionProvider',
|
|
3638
|
+
);
|
|
3639
|
+
owner.closeFailure = { sequence, error: projected };
|
|
3640
|
+
owner.status = 'close-requested';
|
|
3641
|
+
throw projected;
|
|
3642
|
+
})
|
|
3643
|
+
.finally(() => {
|
|
3644
|
+
if (owner.closeAttempt === attempt) owner.closeAttempt = undefined;
|
|
3645
|
+
});
|
|
3646
|
+
owner.closeAttempt = attempt;
|
|
3647
|
+
return attempt;
|
|
3648
|
+
}
|
|
3649
|
+
|
|
3650
|
+
private async closeRunDelegatedRunAdmission(
|
|
3651
|
+
run: IActiveRun,
|
|
3652
|
+
awaitSettlement = true,
|
|
3653
|
+
): Promise<void> {
|
|
3654
|
+
const owner = run.delegatedRunAdmissionOwner;
|
|
3655
|
+
if (!owner) return;
|
|
3656
|
+
if (!run.delegatedRunAdmissionClose) {
|
|
3657
|
+
run.delegatedRunAdmissionClose = this.requestDelegatedRunAdmissionClose(owner);
|
|
3658
|
+
void run.delegatedRunAdmissionClose.catch(() => undefined);
|
|
3659
|
+
}
|
|
3660
|
+
if (awaitSettlement) await run.delegatedRunAdmissionClose;
|
|
3339
3661
|
}
|
|
3340
3662
|
|
|
3341
3663
|
private async promoteQueuedPrompt(queued: IQueuedPrompt, run: IActiveRun): Promise<void> {
|
|
@@ -3343,13 +3665,19 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3343
3665
|
const options = queued.options!;
|
|
3344
3666
|
let projectionReserved = false;
|
|
3345
3667
|
try {
|
|
3668
|
+
this.assertPromptPromotion(queued, run);
|
|
3669
|
+
await this.acquireDelegatedRunAdmission(queued, run);
|
|
3670
|
+
this.assertPromptPromotion(queued, run);
|
|
3346
3671
|
await this.commitRevertedBranch(run.state, run.stored, run.scopeId, run.scope as TScope);
|
|
3672
|
+
this.assertPromptPromotion(queued, run);
|
|
3673
|
+
const generationPrompt = cloneSerializable(prompt.modelMessage.content) as Parameters<
|
|
3674
|
+
plugins.IAgentSession['beginGeneration']
|
|
3675
|
+
>[0];
|
|
3676
|
+
this.assertPromptPromotion(queued, run);
|
|
3347
3677
|
run.transaction = await this.withRunCompactorContext(
|
|
3348
3678
|
run,
|
|
3349
3679
|
() => run.stored.agentSession.beginGeneration(
|
|
3350
|
-
|
|
3351
|
-
plugins.IAgentSession['beginGeneration']
|
|
3352
|
-
>[0],
|
|
3680
|
+
generationPrompt,
|
|
3353
3681
|
{ generationId: run.runId },
|
|
3354
3682
|
),
|
|
3355
3683
|
);
|
|
@@ -3411,10 +3739,21 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3411
3739
|
let cleanupErrors: unknown[] = [];
|
|
3412
3740
|
let cleanupFailure: unknown;
|
|
3413
3741
|
try {
|
|
3414
|
-
|
|
3742
|
+
await this.closeRunDelegatedRunAdmission(
|
|
3743
|
+
run,
|
|
3744
|
+
!run.delegatedRunAdmissionOwner?.acquisitionDetached,
|
|
3745
|
+
);
|
|
3746
|
+
} catch (closeError) {
|
|
3747
|
+
cleanupErrors.push(closeError);
|
|
3748
|
+
}
|
|
3749
|
+
try {
|
|
3750
|
+
cleanupErrors.push(...await this.rollbackAdmission(run, projectionReserved, safeError));
|
|
3415
3751
|
} catch (rollbackError) {
|
|
3416
3752
|
cleanupFailure = rollbackError;
|
|
3417
3753
|
}
|
|
3754
|
+
if (cleanupFailure !== undefined && cleanupErrors.length > 0) {
|
|
3755
|
+
cleanupFailure = combineErrors([cleanupFailure, ...cleanupErrors]);
|
|
3756
|
+
}
|
|
3418
3757
|
if (run.state.lifecycle === 'fenced' && cleanupFailure === undefined) {
|
|
3419
3758
|
const combined = combineErrors([safeError, ...cleanupErrors]);
|
|
3420
3759
|
try {
|
|
@@ -3526,6 +3865,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3526
3865
|
stored.outstandingPromptBytes = Math.max(0, stored.outstandingPromptBytes - queued.byteSize);
|
|
3527
3866
|
delete queued.prompt;
|
|
3528
3867
|
delete queued.options;
|
|
3868
|
+
delete queued.delegationSeed;
|
|
3529
3869
|
const terminal: IFlexPromptQueueEntry = {
|
|
3530
3870
|
...this.projectPromptQueueEntry(queued),
|
|
3531
3871
|
status,
|
|
@@ -3587,6 +3927,11 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3587
3927
|
}
|
|
3588
3928
|
|
|
3589
3929
|
private purgeStoredPromptQueue(stored: IStoredSessionState): void {
|
|
3930
|
+
for (const queued of stored.outstandingPromptsById.values()) {
|
|
3931
|
+
delete queued.prompt;
|
|
3932
|
+
delete queued.options;
|
|
3933
|
+
delete queued.delegationSeed;
|
|
3934
|
+
}
|
|
3590
3935
|
stored.promptQueue.length = 0;
|
|
3591
3936
|
stored.outstandingPromptsById.clear();
|
|
3592
3937
|
stored.terminalPromptQueueEntries.clear();
|
|
@@ -3606,6 +3951,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3606
3951
|
const result = normalizeRunResult(rawResult);
|
|
3607
3952
|
if (!run.modelResolution) throw new FlexHarnessValidationError('Generation completed without a resolved model.');
|
|
3608
3953
|
await this.finalizeRunReversion(run, 'completed');
|
|
3954
|
+
await this.closeRunDelegatedRunAdmission(run);
|
|
3609
3955
|
const terminal = this.buildTerminal(run, 'completed', result);
|
|
3610
3956
|
try {
|
|
3611
3957
|
await this.mutateProjection(run.state, run.stored, () => {
|
|
@@ -3683,10 +4029,19 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3683
4029
|
? run.ownerCancellation!
|
|
3684
4030
|
: this.projectExternalError(run, error, generated ? 'persistence' : 'agentSession');
|
|
3685
4031
|
const errors: unknown[] = [safeError];
|
|
4032
|
+
let reversionFailed = false;
|
|
3686
4033
|
try {
|
|
3687
4034
|
await this.finalizeRunReversion(run, cancelled ? 'cancelled' : 'failed');
|
|
3688
4035
|
} catch (reversionError) {
|
|
3689
4036
|
errors.push(reversionError);
|
|
4037
|
+
reversionFailed = true;
|
|
4038
|
+
}
|
|
4039
|
+
try {
|
|
4040
|
+
await this.closeRunDelegatedRunAdmission(run);
|
|
4041
|
+
} catch (closeError) {
|
|
4042
|
+
if (!errors.includes(closeError)) errors.push(closeError);
|
|
4043
|
+
}
|
|
4044
|
+
if (reversionFailed) {
|
|
3690
4045
|
const combined = combineErrors(errors);
|
|
3691
4046
|
await this.fenceNamespace(run.state, run, combined);
|
|
3692
4047
|
throw combined;
|
|
@@ -3829,10 +4184,11 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3829
4184
|
): Promise<IFlexProjectManagementSnapshot> {
|
|
3830
4185
|
const store = this.stores.projectManagement;
|
|
3831
4186
|
const generation = requireSessionGeneration(stored.session);
|
|
4187
|
+
const sessionContext = this.createProjectManagementSessionContext(state, stored.session);
|
|
3832
4188
|
const record = await store.load(
|
|
3833
4189
|
state.storageKey,
|
|
3834
4190
|
stored.session.sessionId,
|
|
3835
|
-
|
|
4191
|
+
sessionContext,
|
|
3836
4192
|
);
|
|
3837
4193
|
if (record === undefined) {
|
|
3838
4194
|
return createEmptyFlexProjectManagementSnapshot(
|
|
@@ -3925,6 +4281,52 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3925
4281
|
return publicSnapshot({ revision: snapshot.revision, state: snapshot });
|
|
3926
4282
|
}
|
|
3927
4283
|
|
|
4284
|
+
private createSubagentProvenance(
|
|
4285
|
+
state: IStorageState,
|
|
4286
|
+
session: Readonly<IFlexSession>,
|
|
4287
|
+
): Readonly<IFlexSubagentProvenance> | undefined {
|
|
4288
|
+
if (session.parentSessionId === undefined) return undefined;
|
|
4289
|
+
if (
|
|
4290
|
+
session.parentRunId === undefined
|
|
4291
|
+
|| session.parentToolCallId === undefined
|
|
4292
|
+
|| session.agent === undefined
|
|
4293
|
+
|| session.depth === undefined
|
|
4294
|
+
) throw new FlexHarnessValidationError('Subagent session provenance is incomplete.');
|
|
4295
|
+
const parent = state.sessions.get(session.parentSessionId)?.session
|
|
4296
|
+
?? state.tombstones.get(session.parentSessionId);
|
|
4297
|
+
if (!parent) {
|
|
4298
|
+
throw new FlexHarnessValidationError(
|
|
4299
|
+
`Subagent session "${session.sessionId}" has no exact parent provenance.`,
|
|
4300
|
+
);
|
|
4301
|
+
}
|
|
4302
|
+
const parentGeneration = requireSessionGeneration(parent);
|
|
4303
|
+
return Object.freeze({
|
|
4304
|
+
parentSessionId: session.parentSessionId,
|
|
4305
|
+
parentSessionGenerationId: parentGeneration.sessionGenerationId,
|
|
4306
|
+
parentSessionGenerationSequence: parentGeneration.sessionGenerationSequence,
|
|
4307
|
+
originParentRunId: session.parentRunId,
|
|
4308
|
+
originParentToolCallId: session.parentToolCallId,
|
|
4309
|
+
agent: session.agent,
|
|
4310
|
+
depth: session.depth,
|
|
4311
|
+
});
|
|
4312
|
+
}
|
|
4313
|
+
|
|
4314
|
+
private createProjectManagementSessionContext(
|
|
4315
|
+
state: IStorageState,
|
|
4316
|
+
session: Readonly<IFlexSession | IFlexSessionTombstone>,
|
|
4317
|
+
): Readonly<IFlexProjectManagementSessionContext> {
|
|
4318
|
+
const source = 'deletedAt' in session
|
|
4319
|
+
? session.subagent
|
|
4320
|
+
: this.createSubagentProvenance(state, session);
|
|
4321
|
+
const subagent = source === undefined
|
|
4322
|
+
? undefined
|
|
4323
|
+
: Object.freeze({ ...cloneSerializable(source) });
|
|
4324
|
+
return Object.freeze({
|
|
4325
|
+
...requireSessionGeneration(session),
|
|
4326
|
+
...(subagent === undefined ? {} : { subagent }),
|
|
4327
|
+
});
|
|
4328
|
+
}
|
|
4329
|
+
|
|
3928
4330
|
private projectTaskResult(
|
|
3929
4331
|
snapshot: IFlexProjectManagementSnapshot,
|
|
3930
4332
|
task: IFlexProjectTask,
|
|
@@ -4613,15 +5015,27 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4613
5015
|
.map((definition) => `- ${definition.name}: ${definition.description}`)
|
|
4614
5016
|
.join('\n');
|
|
4615
5017
|
return plugins.tool({
|
|
4616
|
-
description: `Run one configured FlexHarness subagent in the foreground and return its final text.\nAvailable subagents:\n${available}`,
|
|
5018
|
+
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
5019
|
inputSchema: plugins.z.object({
|
|
4618
5020
|
description: plugins.z.string(),
|
|
4619
5021
|
prompt: plugins.z.string(),
|
|
4620
5022
|
subagentType: plugins.z.string(),
|
|
4621
|
-
taskId: plugins.z.string()
|
|
5023
|
+
taskId: plugins.z.string()
|
|
5024
|
+
.describe('Omit for a new child. For a later-run resume, use only the exact taskId returned by an earlier completed delegate call.')
|
|
5025
|
+
.optional(),
|
|
4622
5026
|
}).strict(),
|
|
4623
|
-
execute: (input: IFlexSubagentTaskInput, options?: { toolCallId?: string }) =>
|
|
4624
|
-
|
|
5027
|
+
execute: async (input: IFlexSubagentTaskInput, options?: { toolCallId?: string }) => {
|
|
5028
|
+
try {
|
|
5029
|
+
return await this.executeSubagentTask(run, input, options?.toolCallId);
|
|
5030
|
+
} catch (error) {
|
|
5031
|
+
if (
|
|
5032
|
+
options?.toolCallId
|
|
5033
|
+
&& error instanceof FlexHarnessValidationError
|
|
5034
|
+
&& this.trustedInternalErrors.has(error)
|
|
5035
|
+
) run.trustedToolExecutionErrors.set(options.toolCallId, error);
|
|
5036
|
+
throw error;
|
|
5037
|
+
}
|
|
5038
|
+
},
|
|
4625
5039
|
});
|
|
4626
5040
|
}
|
|
4627
5041
|
|
|
@@ -4690,16 +5104,20 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4690
5104
|
run.controller.signal.addEventListener('abort', abortChild, { once: true });
|
|
4691
5105
|
if (run.controller.signal.aborted) abortChild();
|
|
4692
5106
|
try {
|
|
5107
|
+
const permissionMetadata = {
|
|
5108
|
+
agent: definition.name,
|
|
5109
|
+
description: input.description,
|
|
5110
|
+
childSessionId: reservedSessionId,
|
|
5111
|
+
...(input.taskId === undefined ? {} : { taskId: input.taskId }),
|
|
5112
|
+
};
|
|
5113
|
+
if (jsonBytes(permissionMetadata) > maxSubagentPermissionMetadataBytes) {
|
|
5114
|
+
throw new FlexHarnessValidationError('Subagent permission metadata exceeds its byte limit.');
|
|
5115
|
+
}
|
|
4693
5116
|
await this.requestPermission(run.state, run, {
|
|
4694
5117
|
kind: 'subagent.start',
|
|
4695
5118
|
description: `Start foreground subagent "${definition.name}": ${input.description}`,
|
|
4696
5119
|
toolCallId,
|
|
4697
|
-
|
|
4698
|
-
agent: definition.name,
|
|
4699
|
-
description: input.description,
|
|
4700
|
-
...(input.taskId === undefined ? {} : { taskId: input.taskId }),
|
|
4701
|
-
},
|
|
4702
|
-
});
|
|
5120
|
+
}, Object.freeze(permissionMetadata));
|
|
4703
5121
|
const acquired = await this.acquireSubagentSession(
|
|
4704
5122
|
run,
|
|
4705
5123
|
definition,
|
|
@@ -4716,6 +5134,12 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4716
5134
|
...(definition.system === undefined ? {} : { system: definition.system }),
|
|
4717
5135
|
...(definition.maxSteps === undefined ? {} : { maxSteps: definition.maxSteps }),
|
|
4718
5136
|
};
|
|
5137
|
+
const delegationSeed = this.createDelegatedRunAdmissionSeed(
|
|
5138
|
+
run,
|
|
5139
|
+
child,
|
|
5140
|
+
definition,
|
|
5141
|
+
toolCallId,
|
|
5142
|
+
);
|
|
4719
5143
|
queued = await this.enqueuePromptInternal(
|
|
4720
5144
|
run.scopeId,
|
|
4721
5145
|
child.session.sessionId,
|
|
@@ -4723,7 +5147,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4723
5147
|
childOptions,
|
|
4724
5148
|
undefined,
|
|
4725
5149
|
undefined,
|
|
4726
|
-
|
|
5150
|
+
delegationSeed,
|
|
4727
5151
|
run.controller.signal,
|
|
4728
5152
|
);
|
|
4729
5153
|
admission = await queued.started;
|
|
@@ -4777,6 +5201,42 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4777
5201
|
}
|
|
4778
5202
|
}
|
|
4779
5203
|
|
|
5204
|
+
private createDelegatedRunAdmissionSeed(
|
|
5205
|
+
run: IActiveRun,
|
|
5206
|
+
child: IStoredSessionState,
|
|
5207
|
+
definition: Readonly<IFlexSubagentDefinition>,
|
|
5208
|
+
toolCallId: string,
|
|
5209
|
+
): IDelegatedRunAdmissionSeed {
|
|
5210
|
+
const childGeneration = requireSessionGeneration(child.session);
|
|
5211
|
+
const parentGeneration = requireSessionGeneration(run.stored.session);
|
|
5212
|
+
if (!child.session.parentRunId || !child.session.parentToolCallId) {
|
|
5213
|
+
throw new FlexHarnessValidationError('Subagent session origin metadata is missing.');
|
|
5214
|
+
}
|
|
5215
|
+
const seed = Object.freeze({
|
|
5216
|
+
state: run.state,
|
|
5217
|
+
childStored: child,
|
|
5218
|
+
parentRun: run,
|
|
5219
|
+
parentStored: run.stored,
|
|
5220
|
+
scopeId: run.scopeId,
|
|
5221
|
+
scope: run.scope,
|
|
5222
|
+
storageKey: run.state.storageKey,
|
|
5223
|
+
sessionId: child.session.sessionId,
|
|
5224
|
+
...childGeneration,
|
|
5225
|
+
originParentRunId: child.session.parentRunId,
|
|
5226
|
+
originParentToolCallId: child.session.parentToolCallId,
|
|
5227
|
+
parentSessionId: run.sessionId,
|
|
5228
|
+
parentSessionGenerationId: parentGeneration.sessionGenerationId,
|
|
5229
|
+
parentSessionGenerationSequence: parentGeneration.sessionGenerationSequence,
|
|
5230
|
+
parentQueueId: run.queueId,
|
|
5231
|
+
parentRunId: run.runId,
|
|
5232
|
+
parentToolCallId: toolCallId,
|
|
5233
|
+
agent: definition.name,
|
|
5234
|
+
depth: (run.stored.session.depth ?? 0) + 1,
|
|
5235
|
+
});
|
|
5236
|
+
this.assertDelegatedRunAdmissionOwnership(seed, run.state, child, run.scopeId);
|
|
5237
|
+
return seed;
|
|
5238
|
+
}
|
|
5239
|
+
|
|
4780
5240
|
private async acquireSubagentSession(
|
|
4781
5241
|
run: IActiveRun,
|
|
4782
5242
|
definition: Readonly<IFlexSubagentDefinition>,
|
|
@@ -4865,7 +5325,11 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4865
5325
|
return;
|
|
4866
5326
|
}
|
|
4867
5327
|
if (taskId !== undefined || state.tombstones.has(sessionId)) {
|
|
4868
|
-
throw
|
|
5328
|
+
throw this.trustInternalError(
|
|
5329
|
+
new FlexHarnessValidationError(
|
|
5330
|
+
'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.',
|
|
5331
|
+
),
|
|
5332
|
+
);
|
|
4869
5333
|
}
|
|
4870
5334
|
const timestamp = new Date().toISOString();
|
|
4871
5335
|
metadata = {
|
|
@@ -4941,6 +5405,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4941
5405
|
) throw projected;
|
|
4942
5406
|
if (state.tombstones.has(sessionId)) throw projected;
|
|
4943
5407
|
try {
|
|
5408
|
+
const subagent = this.createSubagentProvenance(state, metadata);
|
|
4944
5409
|
await this.mutateScope(state, () => {
|
|
4945
5410
|
if (state.sessions.get(sessionId) !== placeholder) return;
|
|
4946
5411
|
state.sessions.delete(sessionId);
|
|
@@ -4951,6 +5416,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4951
5416
|
rootSessionId: sessionId,
|
|
4952
5417
|
depth: 0,
|
|
4953
5418
|
parentSessionId: run.sessionId,
|
|
5419
|
+
...(subagent === undefined ? {} : { subagent: cloneSerializable(subagent) }),
|
|
4954
5420
|
});
|
|
4955
5421
|
}, true);
|
|
4956
5422
|
completeInitialization();
|
|
@@ -6430,10 +6896,12 @@ export class FlexHarness<TScope = unknown> {
|
|
|
6430
6896
|
const part = run.callbackParts.find((entry) => entry.partId === partId && entry.type === 'tool');
|
|
6431
6897
|
if (!part || part.type !== 'tool' || part.status !== 'running') return;
|
|
6432
6898
|
try {
|
|
6899
|
+
const trustedExecutionError = run.trustedToolExecutionErrors.get(event.toolCallId);
|
|
6900
|
+
run.trustedToolExecutionErrors.delete(event.toolCallId);
|
|
6433
6901
|
const output = event.success ? normalizeJsonValue(event.output, this.toolOutputLimits) : undefined;
|
|
6434
6902
|
const projectedError = event.success
|
|
6435
6903
|
? undefined
|
|
6436
|
-
: this.projectExternalError(run, event.error, 'toolCallback');
|
|
6904
|
+
: this.projectExternalError(run, trustedExecutionError ?? event.error, 'toolCallback');
|
|
6437
6905
|
const bytes = event.success ? jsonBytes(output) : Buffer.byteLength(projectedError!.message);
|
|
6438
6906
|
if (!this.reserveCallbackCapacity(run, 1, bytes, 0)) return;
|
|
6439
6907
|
if (event.success) {
|
|
@@ -6516,6 +6984,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
6516
6984
|
state: IStorageState,
|
|
6517
6985
|
run: IActiveRun,
|
|
6518
6986
|
input: IFlexPermissionRequestInput,
|
|
6987
|
+
exactMetadata?: TJsonValue,
|
|
6519
6988
|
): Promise<void> {
|
|
6520
6989
|
validateIdentifier(input.kind, 'permission kind');
|
|
6521
6990
|
validateIdentifier(input.description, 'permission description');
|
|
@@ -6538,9 +7007,11 @@ export class FlexHarness<TScope = unknown> {
|
|
|
6538
7007
|
description: truncateUtf8(input.description, maxTransferMetadataBytes),
|
|
6539
7008
|
...(input.toolCallId ? { toolCallId: input.toolCallId } : {}),
|
|
6540
7009
|
...(input.rememberKey ? { rememberKey: input.rememberKey } : {}),
|
|
6541
|
-
...(
|
|
6542
|
-
? {}
|
|
6543
|
-
:
|
|
7010
|
+
...(exactMetadata !== undefined
|
|
7011
|
+
? { metadata: cloneSerializable(exactMetadata) }
|
|
7012
|
+
: input.metadata === undefined
|
|
7013
|
+
? {}
|
|
7014
|
+
: { metadata: normalizeJsonValue(input.metadata, this.toolOutputLimits) }),
|
|
6544
7015
|
createdAt: new Date().toISOString(),
|
|
6545
7016
|
};
|
|
6546
7017
|
let resolvePermission!: () => void;
|
|
@@ -7166,6 +7637,39 @@ export class FlexHarness<TScope = unknown> {
|
|
|
7166
7637
|
.filter((error): error is Error => error !== undefined);
|
|
7167
7638
|
}
|
|
7168
7639
|
|
|
7640
|
+
private delegatedRunAdmissionOwnersForGeneration(
|
|
7641
|
+
state: IStorageState,
|
|
7642
|
+
session: Readonly<Pick<
|
|
7643
|
+
IFlexSession,
|
|
7644
|
+
'sessionId' | 'sessionGenerationId' | 'sessionGenerationSequence'
|
|
7645
|
+
>>,
|
|
7646
|
+
): IDelegatedRunAdmissionOwner[] {
|
|
7647
|
+
return [...state.delegatedRunAdmissionOwners].filter((owner) =>
|
|
7648
|
+
owner.storageState === state
|
|
7649
|
+
&& owner.context.sessionId === session.sessionId
|
|
7650
|
+
&& owner.context.sessionGenerationId === session.sessionGenerationId
|
|
7651
|
+
&& owner.context.sessionGenerationSequence === session.sessionGenerationSequence);
|
|
7652
|
+
}
|
|
7653
|
+
|
|
7654
|
+
private async settleDelegatedRunAdmissionOwners(
|
|
7655
|
+
owners: readonly IDelegatedRunAdmissionOwner[],
|
|
7656
|
+
): Promise<Error[]> {
|
|
7657
|
+
const results = await Promise.allSettled(
|
|
7658
|
+
owners.map((owner) => this.requestDelegatedRunAdmissionClose(owner)),
|
|
7659
|
+
);
|
|
7660
|
+
return results.flatMap((result, index) => {
|
|
7661
|
+
if (result.status === 'fulfilled') return [];
|
|
7662
|
+
const error = result.reason instanceof Error
|
|
7663
|
+
? result.reason
|
|
7664
|
+
: this.projectExternalError(
|
|
7665
|
+
owners[index].run,
|
|
7666
|
+
result.reason,
|
|
7667
|
+
'delegatedRunAdmissionProvider',
|
|
7668
|
+
);
|
|
7669
|
+
return [error];
|
|
7670
|
+
});
|
|
7671
|
+
}
|
|
7672
|
+
|
|
7169
7673
|
private projectExternalError(
|
|
7170
7674
|
run: IActiveRun,
|
|
7171
7675
|
error: unknown,
|
|
@@ -7288,6 +7792,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
7288
7792
|
tombstones: new Map(snapshot.tombstones.map((entry) => [entry.sessionId, entry])),
|
|
7289
7793
|
tombstoneCleanups: new Map(),
|
|
7290
7794
|
activeRuns: new Map(),
|
|
7795
|
+
delegatedRunAdmissionOwners: new Set(),
|
|
7291
7796
|
pendingPermissions: new Map(),
|
|
7292
7797
|
scopeQueue: Promise.resolve(),
|
|
7293
7798
|
lifecycle: 'active',
|
|
@@ -7347,7 +7852,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
7347
7852
|
scopeId,
|
|
7348
7853
|
scope,
|
|
7349
7854
|
);
|
|
7350
|
-
await this.cleanupSessionDomains(
|
|
7855
|
+
await this.cleanupSessionDomains(state, tombstone);
|
|
7351
7856
|
}
|
|
7352
7857
|
for (const tombstone of group) state.tombstones.delete(tombstone.sessionId);
|
|
7353
7858
|
scopeChanged = true;
|
|
@@ -8328,16 +8833,17 @@ export class FlexHarness<TScope = unknown> {
|
|
|
8328
8833
|
}
|
|
8329
8834
|
|
|
8330
8835
|
private async cleanupSessionDomains(
|
|
8331
|
-
|
|
8836
|
+
state: IStorageState,
|
|
8332
8837
|
tombstone: IFlexSessionTombstone,
|
|
8333
8838
|
): Promise<void> {
|
|
8839
|
+
const storageKey = state.storageKey;
|
|
8334
8840
|
const sessionId = tombstone.sessionId;
|
|
8335
8841
|
const projectManagementCleanup = async (): Promise<void> => {
|
|
8336
8842
|
await this.projectManagementQueues.get(
|
|
8337
8843
|
this.projectManagementKey(storageKey, sessionId),
|
|
8338
8844
|
);
|
|
8339
8845
|
await this.tombstoneProjectManagementSession(
|
|
8340
|
-
|
|
8846
|
+
state,
|
|
8341
8847
|
tombstone,
|
|
8342
8848
|
);
|
|
8343
8849
|
};
|
|
@@ -8355,18 +8861,20 @@ export class FlexHarness<TScope = unknown> {
|
|
|
8355
8861
|
}
|
|
8356
8862
|
|
|
8357
8863
|
private async tombstoneProjectManagementSession(
|
|
8358
|
-
|
|
8864
|
+
state: IStorageState,
|
|
8359
8865
|
sessionTombstone: IFlexSessionTombstone,
|
|
8360
8866
|
): Promise<void> {
|
|
8867
|
+
const storageKey = state.storageKey;
|
|
8361
8868
|
const store = this.stores.projectManagement;
|
|
8362
8869
|
const sessionId = sessionTombstone.sessionId;
|
|
8363
8870
|
const generation = requireSessionGeneration(sessionTombstone);
|
|
8871
|
+
const sessionContext = this.createProjectManagementSessionContext(state, sessionTombstone);
|
|
8364
8872
|
let lastConflict: FlexHarnessStoreConflictError | undefined;
|
|
8365
8873
|
for (let attempt = 0; attempt <= maxProjectManagementTombstoneConflicts; attempt++) {
|
|
8366
8874
|
const current = await store.load(
|
|
8367
8875
|
storageKey,
|
|
8368
8876
|
sessionId,
|
|
8369
|
-
|
|
8877
|
+
sessionContext,
|
|
8370
8878
|
);
|
|
8371
8879
|
if (current !== undefined) {
|
|
8372
8880
|
assertFlexProjectManagementRecord(current);
|
|
@@ -8407,6 +8915,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
8407
8915
|
sessionId,
|
|
8408
8916
|
tombstone,
|
|
8409
8917
|
expectedRevision,
|
|
8918
|
+
sessionContext,
|
|
8410
8919
|
);
|
|
8411
8920
|
} catch (error) {
|
|
8412
8921
|
if (!(error instanceof FlexHarnessStoreConflictError)) throw error;
|
|
@@ -8576,6 +9085,9 @@ export class FlexHarness<TScope = unknown> {
|
|
|
8576
9085
|
this.appendUnexpectedErrors(errors, settled[0].reason);
|
|
8577
9086
|
}
|
|
8578
9087
|
}
|
|
9088
|
+
errors.push(...await this.settleDelegatedRunAdmissionOwners(
|
|
9089
|
+
this.delegatedRunAdmissionOwnersForGeneration(state, tombstone),
|
|
9090
|
+
));
|
|
8579
9091
|
if (retained) {
|
|
8580
9092
|
if (retained.stored.promptQueueDrain) {
|
|
8581
9093
|
const settled = await Promise.allSettled([retained.stored.promptQueueDrain]);
|
|
@@ -8621,7 +9133,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
8621
9133
|
releaseContext.scope,
|
|
8622
9134
|
retained?.stored,
|
|
8623
9135
|
);
|
|
8624
|
-
await this.cleanupSessionDomains(state
|
|
9136
|
+
await this.cleanupSessionDomains(state, tombstone);
|
|
8625
9137
|
if (retained) retained.domainsCompleted = true;
|
|
8626
9138
|
}
|
|
8627
9139
|
}
|
|
@@ -8680,7 +9192,11 @@ export class FlexHarness<TScope = unknown> {
|
|
|
8680
9192
|
for (const result of runResults) {
|
|
8681
9193
|
if (result.status === 'rejected') this.appendUnexpectedErrors(cleanupErrors, result.reason);
|
|
8682
9194
|
}
|
|
8683
|
-
if (
|
|
9195
|
+
if (
|
|
9196
|
+
dependentRuns.length > 0
|
|
9197
|
+
|| (currentRun.delegatedRunAdmissionOwner !== undefined
|
|
9198
|
+
&& state.delegatedRunAdmissionOwners.has(currentRun.delegatedRunAdmissionOwner))
|
|
9199
|
+
) {
|
|
8684
9200
|
state.fenceAdditionalErrors.push(cause, ...cleanupErrors);
|
|
8685
9201
|
const stateLoad = this.stateLoads.get(state.storageKey);
|
|
8686
9202
|
if (!stateLoad) {
|
|
@@ -8697,6 +9213,9 @@ export class FlexHarness<TScope = unknown> {
|
|
|
8697
9213
|
void deferredDrain.catch(() => undefined);
|
|
8698
9214
|
return;
|
|
8699
9215
|
}
|
|
9216
|
+
cleanupErrors.push(...await this.settleDelegatedRunAdmissionOwners([
|
|
9217
|
+
...state.delegatedRunAdmissionOwners,
|
|
9218
|
+
]));
|
|
8700
9219
|
const detachedCleanupAttempts = this.beginDetachedCleanupSettlement(state);
|
|
8701
9220
|
await Promise.allSettled([...state.sessionInitializations.values()]);
|
|
8702
9221
|
cleanupErrors.push(...await this.closeOrphanedResources(state.storageKey));
|
|
@@ -8784,6 +9303,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
8784
9303
|
state.tombstoneCleanups.set(currentTombstoneRoot, currentTombstoneCleanup);
|
|
8785
9304
|
}
|
|
8786
9305
|
state.activeRuns.clear();
|
|
9306
|
+
state.delegatedRunAdmissionOwners.clear();
|
|
8787
9307
|
state.pendingPermissions.clear();
|
|
8788
9308
|
state.initializingSessions.clear();
|
|
8789
9309
|
state.sessionInitializations.clear();
|
|
@@ -9221,6 +9741,9 @@ export class FlexHarness<TScope = unknown> {
|
|
|
9221
9741
|
for (const result of runResults) {
|
|
9222
9742
|
if (result.status === 'rejected') this.appendUnexpectedErrors(errors, result.reason);
|
|
9223
9743
|
}
|
|
9744
|
+
errors.push(...await this.settleDelegatedRunAdmissionOwners([
|
|
9745
|
+
...state.delegatedRunAdmissionOwners,
|
|
9746
|
+
]));
|
|
9224
9747
|
for (const stored of state.sessions.values()) {
|
|
9225
9748
|
try {
|
|
9226
9749
|
await this.abortStoredSession(
|
|
@@ -9319,6 +9842,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
9319
9842
|
state.sessionDeletions.clear();
|
|
9320
9843
|
state.tombstoneCleanups.clear();
|
|
9321
9844
|
state.activeRuns.clear();
|
|
9845
|
+
state.delegatedRunAdmissionOwners.clear();
|
|
9322
9846
|
state.pendingPermissions.clear();
|
|
9323
9847
|
state.initializingSessions.clear();
|
|
9324
9848
|
state.sessionInitializations.clear();
|