@modelprofile.com/flexharness 10.1.0 → 10.2.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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.flexharness.d.ts +13 -1
- package/dist_ts/classes.flexharness.js +312 -189
- package/dist_ts/interfaces.d.ts +18 -0
- package/dist_ts/interfaces.js +1 -1
- package/dist_ts_chat_harness/classes.transcript.d.ts +21 -6
- package/dist_ts_chat_harness/classes.transcript.js +37 -9
- package/package.json +3 -3
- package/readme.md +4 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.flexharness.ts +339 -211
- package/ts/interfaces.ts +19 -0
- package/ts/readme.md +4 -2
- package/ts_chat_harness/classes.transcript.ts +46 -12
- package/ts_chat_harness/readme.md +9 -6
|
@@ -90,6 +90,7 @@ import type {
|
|
|
90
90
|
IFlexSchedulePromptOptions,
|
|
91
91
|
IFlexScopeSnapshot,
|
|
92
92
|
IFlexSession,
|
|
93
|
+
IFlexSessionView,
|
|
93
94
|
IFlexSessionPermissionPolicyContext,
|
|
94
95
|
IFlexSessionActivity,
|
|
95
96
|
IFlexSessionGeneration,
|
|
@@ -360,6 +361,11 @@ interface IStorageState {
|
|
|
360
361
|
activeRuns: Map<string, IActiveRun>;
|
|
361
362
|
delegatedRunAdmissionOwners: Set<IDelegatedRunAdmissionOwner>;
|
|
362
363
|
pendingPermissions: Map<string, IPendingPermission>;
|
|
364
|
+
/**
|
|
365
|
+
* Per session, the open sections that change what a session view shows and emit the events
|
|
366
|
+
* describing it. A view waits until none is open, so it never shows a change without its event.
|
|
367
|
+
*/
|
|
368
|
+
publications: Map<string, Set<Promise<void>>>;
|
|
363
369
|
scopeQueue: Promise<void>;
|
|
364
370
|
lifecycle: 'active' | 'retiring' | 'fenced' | 'retired';
|
|
365
371
|
fenceInProgress: boolean;
|
|
@@ -652,6 +658,12 @@ type TEventDetails = Record<string, unknown> & {
|
|
|
652
658
|
|
|
653
659
|
const maxScheduleDebounceMs = 24 * 60 * 60 * 1000;
|
|
654
660
|
const maxBackgroundExecutions = 100;
|
|
661
|
+
/**
|
|
662
|
+
* Consecutive waits a session view makes for queued writes and open sections to settle. Each wait
|
|
663
|
+
* ends only when real work finishes, so the bound limits starvation by a session that never stops
|
|
664
|
+
* changing, not the time one slow save may take.
|
|
665
|
+
*/
|
|
666
|
+
const maxSessionViewPasses = 64;
|
|
655
667
|
const maxSubagentTaskDescriptionBytes = 256;
|
|
656
668
|
const maxSubagentPromptBytes = 64 * 1024;
|
|
657
669
|
const maxSubagentTaskIdBytes = 512;
|
|
@@ -1924,6 +1936,75 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1924
1936
|
}
|
|
1925
1937
|
}
|
|
1926
1938
|
|
|
1939
|
+
/**
|
|
1940
|
+
* Reads the session's metadata, visible messages and pending permissions in one step, together
|
|
1941
|
+
* with the instance's event sequence they include. It waits until no queued projection or scope
|
|
1942
|
+
* write and no change that emits events for the session is in progress.
|
|
1943
|
+
*/
|
|
1944
|
+
public async getSessionView(scopeId: string, sessionId: string): Promise<IFlexSessionView> {
|
|
1945
|
+
const { state } = await this.resolveState(scopeId);
|
|
1946
|
+
let stored = this.requireSession(state, sessionId);
|
|
1947
|
+
for (let pass = 0; ; pass++) {
|
|
1948
|
+
if (pass === maxSessionViewPasses) {
|
|
1949
|
+
throw new FlexHarnessSessionBusyError(
|
|
1950
|
+
sessionId,
|
|
1951
|
+
`kept changing through ${maxSessionViewPasses} consecutive waits for a settled view`,
|
|
1952
|
+
);
|
|
1953
|
+
}
|
|
1954
|
+
const scopeQueue = state.scopeQueue;
|
|
1955
|
+
const projectionQueue = stored.projectionQueue;
|
|
1956
|
+
const publications = [...(state.publications.get(sessionId) ?? [])];
|
|
1957
|
+
await Promise.all([scopeQueue, projectionQueue, ...publications]);
|
|
1958
|
+
this.assertStateAcceptingWork(state);
|
|
1959
|
+
stored = this.requireSession(state, sessionId);
|
|
1960
|
+
if (
|
|
1961
|
+
state.scopeQueue === scopeQueue
|
|
1962
|
+
&& stored.projectionQueue === projectionQueue
|
|
1963
|
+
&& !state.publications.get(sessionId)?.size
|
|
1964
|
+
) break;
|
|
1965
|
+
}
|
|
1966
|
+
const run = state.activeRuns.get(sessionId);
|
|
1967
|
+
const messages = this.visibleMessages(stored).map((message) => (
|
|
1968
|
+
run && run.stored === stored && message.messageId === run.assistantMessageId
|
|
1969
|
+
&& message.status === 'streaming'
|
|
1970
|
+
? { ...message, parts: run.callbackParts }
|
|
1971
|
+
: message
|
|
1972
|
+
));
|
|
1973
|
+
return publicSnapshot({
|
|
1974
|
+
session: stored.session,
|
|
1975
|
+
messages,
|
|
1976
|
+
pendingPermissions: [...state.pendingPermissions.values()]
|
|
1977
|
+
.map((pending) => pending.request)
|
|
1978
|
+
.filter((request) => request.sessionId === sessionId)
|
|
1979
|
+
.sort((left, right) => left.createdAt.localeCompare(right.createdAt)),
|
|
1980
|
+
sequence: this.sequence,
|
|
1981
|
+
});
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
/**
|
|
1985
|
+
* Opens a section that changes what a view of the session shows and emits the events that
|
|
1986
|
+
* describe the change. Closing is idempotent; close it once the events are emitted or the
|
|
1987
|
+
* change is rolled back.
|
|
1988
|
+
*/
|
|
1989
|
+
private openPublication(state: IStorageState, sessionId: string): () => void {
|
|
1990
|
+
let release!: () => void;
|
|
1991
|
+
const section = new Promise<void>((resolve) => {
|
|
1992
|
+
release = resolve;
|
|
1993
|
+
});
|
|
1994
|
+
let sections = state.publications.get(sessionId);
|
|
1995
|
+
if (!sections) {
|
|
1996
|
+
sections = new Set();
|
|
1997
|
+
state.publications.set(sessionId, sections);
|
|
1998
|
+
}
|
|
1999
|
+
sections.add(section);
|
|
2000
|
+
return () => {
|
|
2001
|
+
const current = state.publications.get(sessionId);
|
|
2002
|
+
if (!current?.delete(section)) return;
|
|
2003
|
+
if (current.size === 0) state.publications.delete(sessionId);
|
|
2004
|
+
release();
|
|
2005
|
+
};
|
|
2006
|
+
}
|
|
2007
|
+
|
|
1927
2008
|
public async getMessages(scopeId: string, sessionId: string): Promise<IFlexMessage[]> {
|
|
1928
2009
|
const { state } = await this.resolveState(scopeId);
|
|
1929
2010
|
const stored = this.requireSession(state, sessionId);
|
|
@@ -3397,12 +3478,17 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3397
3478
|
.map((runId) => stored.reversionSegments.find((segment) => segment.runId === runId))
|
|
3398
3479
|
.find((segment) => segment?.status === 'completed');
|
|
3399
3480
|
if (!target) throw new FlexHarnessValidationError('Pending reversion has no target candidate.');
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3481
|
+
const closePublication = this.openPublication(state, sessionId);
|
|
3482
|
+
try {
|
|
3483
|
+
await this.resumePendingApply(state, stored, scopeId, scope, operationSignal);
|
|
3484
|
+
this.emitEvent(scopeId, stored.session, {
|
|
3485
|
+
type: 'session.history.changed',
|
|
3486
|
+
direction,
|
|
3487
|
+
runId: target.runId,
|
|
3488
|
+
});
|
|
3489
|
+
} finally {
|
|
3490
|
+
closePublication();
|
|
3491
|
+
}
|
|
3406
3492
|
return target.runId;
|
|
3407
3493
|
}
|
|
3408
3494
|
const unit = this.reversionUnit(stored, direction);
|
|
@@ -3425,13 +3511,22 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3425
3511
|
'The turn has no available workspace reversion capture.',
|
|
3426
3512
|
);
|
|
3427
3513
|
}
|
|
3428
|
-
const operationId = this.reversionId(
|
|
3429
|
-
'apply',
|
|
3430
|
-
state.storageKey,
|
|
3431
|
-
sessionId,
|
|
3432
|
-
JSON.stringify([direction, stored.revertCursor, unit.toCursor, unit.target.runId]),
|
|
3433
|
-
);
|
|
3434
3514
|
await this.mutateProjection(state, stored, () => {
|
|
3515
|
+
// The write-ahead record commits at exactly projectionRevision + 1, so the
|
|
3516
|
+
// revision it replaces makes this apply's ID unique. Retries and recovery
|
|
3517
|
+
// read the persisted ID instead of deriving it again.
|
|
3518
|
+
const operationId = this.reversionId(
|
|
3519
|
+
'apply',
|
|
3520
|
+
state.storageKey,
|
|
3521
|
+
sessionId,
|
|
3522
|
+
JSON.stringify([
|
|
3523
|
+
direction,
|
|
3524
|
+
stored.revertCursor,
|
|
3525
|
+
unit.toCursor,
|
|
3526
|
+
unit.target.runId,
|
|
3527
|
+
stored.projectionRevision,
|
|
3528
|
+
]),
|
|
3529
|
+
);
|
|
3435
3530
|
stored.pendingReversion = {
|
|
3436
3531
|
kind: 'apply',
|
|
3437
3532
|
operationId,
|
|
@@ -3442,18 +3537,23 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3442
3537
|
appliedRunIds: [],
|
|
3443
3538
|
};
|
|
3444
3539
|
}, true);
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3540
|
+
const closePublication = this.openPublication(state, sessionId);
|
|
3541
|
+
try {
|
|
3542
|
+
await this.resumePendingApply(
|
|
3543
|
+
state,
|
|
3544
|
+
stored,
|
|
3545
|
+
scopeId,
|
|
3546
|
+
scope,
|
|
3547
|
+
operationSignal,
|
|
3548
|
+
);
|
|
3549
|
+
this.emitEvent(scopeId, stored.session, {
|
|
3550
|
+
type: 'session.history.changed',
|
|
3551
|
+
direction,
|
|
3552
|
+
runId: unit.target.runId,
|
|
3553
|
+
});
|
|
3554
|
+
} finally {
|
|
3555
|
+
closePublication();
|
|
3556
|
+
}
|
|
3457
3557
|
return unit.target.runId;
|
|
3458
3558
|
},
|
|
3459
3559
|
);
|
|
@@ -4405,123 +4505,130 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4405
4505
|
const prompt = queued.prompt!;
|
|
4406
4506
|
const options = queued.options!;
|
|
4407
4507
|
let projectionReserved = false;
|
|
4508
|
+
// Open from the reservation until its messages are announced or rolled back.
|
|
4509
|
+
let closeReservationPublication = (): void => undefined;
|
|
4408
4510
|
try {
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
run
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
status: 'capturing',
|
|
4439
|
-
contextAvailable: true,
|
|
4440
|
-
eventIds: [],
|
|
4441
|
-
workspaceCaptured: captureId !== undefined,
|
|
4442
|
-
protocolVersion,
|
|
4443
|
-
provenance: captureId === undefined ? 'transcript' : 'workspace',
|
|
4444
|
-
...(captureId === undefined ? {} : { disposition: 'pending' }),
|
|
4445
|
-
...(captureId === undefined ? {} : { captureId }),
|
|
4446
|
-
});
|
|
4447
|
-
if (captureId) {
|
|
4448
|
-
run.stored.pendingReversion = {
|
|
4449
|
-
kind: 'capture',
|
|
4511
|
+
try {
|
|
4512
|
+
this.assertPromptPromotion(queued, run);
|
|
4513
|
+
await this.acquireDelegatedRunAdmission(queued, run);
|
|
4514
|
+
this.assertPromptPromotion(queued, run);
|
|
4515
|
+
await this.commitRevertedBranch(run.state, run.stored, run.scopeId, run.scope as TScope);
|
|
4516
|
+
this.assertPromptPromotion(queued, run);
|
|
4517
|
+
const generationPrompt = cloneSerializable(prompt.modelMessage.content) as Parameters<
|
|
4518
|
+
plugins.IAgentSession['beginGeneration']
|
|
4519
|
+
>[0];
|
|
4520
|
+
this.assertPromptPromotion(queued, run);
|
|
4521
|
+
run.transaction = await this.withRunCompactorContext(
|
|
4522
|
+
run,
|
|
4523
|
+
() => run.stored.agentSession.beginGeneration(
|
|
4524
|
+
generationPrompt,
|
|
4525
|
+
{ generationId: run.runId },
|
|
4526
|
+
),
|
|
4527
|
+
);
|
|
4528
|
+
this.assertPromptPromotion(queued, run);
|
|
4529
|
+
const reservation = this.createReservation(run, prompt);
|
|
4530
|
+
closeReservationPublication = this.openPublication(run.state, run.sessionId);
|
|
4531
|
+
await this.mutateProjection(run.state, run.stored, () => {
|
|
4532
|
+
run.stored.messages.push(reservation.userMessage, reservation.assistantMessage);
|
|
4533
|
+
if (run.stored.session.agent === undefined) {
|
|
4534
|
+
this.pruneReversionState(run.stored, true);
|
|
4535
|
+
const captureId = this.turnReversionProvider
|
|
4536
|
+
? this.reversionId('capture', run.state.storageKey, run.sessionId, run.runId)
|
|
4537
|
+
: undefined;
|
|
4538
|
+
const protocolVersion = this.reversionProviderProtocolVersion() ?? 1;
|
|
4539
|
+
run.stored.reversionSegments.push({
|
|
4450
4540
|
runId: run.runId,
|
|
4451
|
-
|
|
4452
|
-
|
|
4541
|
+
userMessageId: reservation.userMessage.messageId,
|
|
4542
|
+
status: 'capturing',
|
|
4543
|
+
contextAvailable: true,
|
|
4544
|
+
eventIds: [],
|
|
4545
|
+
workspaceCaptured: captureId !== undefined,
|
|
4453
4546
|
protocolVersion,
|
|
4454
|
-
|
|
4547
|
+
provenance: captureId === undefined ? 'transcript' : 'workspace',
|
|
4548
|
+
...(captureId === undefined ? {} : { disposition: 'pending' }),
|
|
4549
|
+
...(captureId === undefined ? {} : { captureId }),
|
|
4550
|
+
});
|
|
4551
|
+
if (captureId) {
|
|
4552
|
+
run.stored.pendingReversion = {
|
|
4553
|
+
kind: 'capture',
|
|
4554
|
+
runId: run.runId,
|
|
4555
|
+
captureId,
|
|
4556
|
+
state: 'preparing',
|
|
4557
|
+
protocolVersion,
|
|
4558
|
+
};
|
|
4559
|
+
}
|
|
4455
4560
|
}
|
|
4561
|
+
});
|
|
4562
|
+
projectionReserved = true;
|
|
4563
|
+
run.reservedUserMessage = publicSnapshot(reservation.userMessage);
|
|
4564
|
+
run.reservedAssistantMessage = publicSnapshot(reservation.assistantMessage);
|
|
4565
|
+
this.assertPromptPromotion(queued, run);
|
|
4566
|
+
await this.mutateScope(run.state, () => {
|
|
4567
|
+
if (run.controller.signal.aborted) {
|
|
4568
|
+
throw run.controller.signal.reason ?? new FlexHarnessAbortError();
|
|
4569
|
+
}
|
|
4570
|
+
if (run.state.tombstones.has(run.sessionId) || run.state.sessions.get(run.sessionId) !== run.stored) {
|
|
4571
|
+
throw new FlexHarnessAbortError('The session was deleted during prompt admission.');
|
|
4572
|
+
}
|
|
4573
|
+
run.stored.session.status = run.scheduleKey ? 'scheduled' : 'running';
|
|
4574
|
+
run.stored.session.activity = {
|
|
4575
|
+
runId: run.runId,
|
|
4576
|
+
status: run.scheduleKey ? 'scheduled' : 'running',
|
|
4577
|
+
startedAt: reservation.userMessage.createdAt,
|
|
4578
|
+
};
|
|
4579
|
+
run.stored.session.updatedAt = reservation.userMessage.createdAt;
|
|
4580
|
+
});
|
|
4581
|
+
} catch (error) {
|
|
4582
|
+
if (containsStoreCommitUncertainty(error)) run.state.lifecycle = 'fenced';
|
|
4583
|
+
const safeError = this.projectExternalError(run, error, projectionReserved ? 'persistence' : 'agentSession');
|
|
4584
|
+
let cleanupErrors: unknown[] = [];
|
|
4585
|
+
let cleanupFailure: unknown;
|
|
4586
|
+
try {
|
|
4587
|
+
await this.closeRunDelegatedRunAdmission(
|
|
4588
|
+
run,
|
|
4589
|
+
!run.delegatedRunAdmissionOwner?.acquisitionDetached,
|
|
4590
|
+
);
|
|
4591
|
+
} catch (closeError) {
|
|
4592
|
+
cleanupErrors.push(closeError);
|
|
4456
4593
|
}
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
this.assertPromptPromotion(queued, run);
|
|
4462
|
-
await this.mutateScope(run.state, () => {
|
|
4463
|
-
if (run.controller.signal.aborted) {
|
|
4464
|
-
throw run.controller.signal.reason ?? new FlexHarnessAbortError();
|
|
4594
|
+
try {
|
|
4595
|
+
cleanupErrors.push(...await this.rollbackAdmission(run, projectionReserved, safeError));
|
|
4596
|
+
} catch (rollbackError) {
|
|
4597
|
+
cleanupFailure = rollbackError;
|
|
4465
4598
|
}
|
|
4466
|
-
if (
|
|
4467
|
-
|
|
4599
|
+
if (cleanupFailure !== undefined && cleanupErrors.length > 0) {
|
|
4600
|
+
cleanupFailure = combineErrors([cleanupFailure, ...cleanupErrors]);
|
|
4468
4601
|
}
|
|
4469
|
-
run.
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
});
|
|
4477
|
-
} catch (error) {
|
|
4478
|
-
if (containsStoreCommitUncertainty(error)) run.state.lifecycle = 'fenced';
|
|
4479
|
-
const safeError = this.projectExternalError(run, error, projectionReserved ? 'persistence' : 'agentSession');
|
|
4480
|
-
let cleanupErrors: unknown[] = [];
|
|
4481
|
-
let cleanupFailure: unknown;
|
|
4482
|
-
try {
|
|
4483
|
-
await this.closeRunDelegatedRunAdmission(
|
|
4484
|
-
run,
|
|
4485
|
-
!run.delegatedRunAdmissionOwner?.acquisitionDetached,
|
|
4486
|
-
);
|
|
4487
|
-
} catch (closeError) {
|
|
4488
|
-
cleanupErrors.push(closeError);
|
|
4489
|
-
}
|
|
4490
|
-
try {
|
|
4491
|
-
cleanupErrors.push(...await this.rollbackAdmission(run, projectionReserved, safeError));
|
|
4492
|
-
} catch (rollbackError) {
|
|
4493
|
-
cleanupFailure = rollbackError;
|
|
4494
|
-
}
|
|
4495
|
-
if (cleanupFailure !== undefined && cleanupErrors.length > 0) {
|
|
4496
|
-
cleanupFailure = combineErrors([cleanupFailure, ...cleanupErrors]);
|
|
4497
|
-
}
|
|
4498
|
-
if (run.state.lifecycle === 'fenced' && cleanupFailure === undefined) {
|
|
4499
|
-
const combined = combineErrors([safeError, ...cleanupErrors]);
|
|
4500
|
-
try {
|
|
4501
|
-
await this.fenceNamespace(run.state, run, combined);
|
|
4502
|
-
} catch (fenceError) {
|
|
4503
|
-
cleanupFailure = fenceError;
|
|
4602
|
+
if (run.state.lifecycle === 'fenced' && cleanupFailure === undefined) {
|
|
4603
|
+
const combined = combineErrors([safeError, ...cleanupErrors]);
|
|
4604
|
+
try {
|
|
4605
|
+
await this.fenceNamespace(run.state, run, combined);
|
|
4606
|
+
} catch (fenceError) {
|
|
4607
|
+
cleanupFailure = fenceError;
|
|
4608
|
+
}
|
|
4504
4609
|
}
|
|
4610
|
+
const rejection = cleanupFailure
|
|
4611
|
+
?? (cleanupErrors.length > 0 ? combineErrors([safeError, ...cleanupErrors]) : safeError);
|
|
4612
|
+
throw rejection;
|
|
4505
4613
|
}
|
|
4506
|
-
const rejection = cleanupFailure
|
|
4507
|
-
?? (cleanupErrors.length > 0 ? combineErrors([safeError, ...cleanupErrors]) : safeError);
|
|
4508
|
-
throw rejection;
|
|
4509
|
-
}
|
|
4510
4614
|
|
|
4511
|
-
|
|
4512
|
-
this.emitEvent(run.scopeId, run.stored.session, {
|
|
4513
|
-
type: runType,
|
|
4514
|
-
runId: run.runId,
|
|
4515
|
-
messageId: run.userMessageId,
|
|
4516
|
-
session: publicSnapshot(run.stored.session),
|
|
4517
|
-
});
|
|
4518
|
-
for (const message of [run.reservedUserMessage!, run.reservedAssistantMessage!]) {
|
|
4615
|
+
const runType = run.scheduleKey ? 'run.scheduled' : 'run.started';
|
|
4519
4616
|
this.emitEvent(run.scopeId, run.stored.session, {
|
|
4520
|
-
type:
|
|
4617
|
+
type: runType,
|
|
4521
4618
|
runId: run.runId,
|
|
4522
|
-
messageId:
|
|
4523
|
-
|
|
4619
|
+
messageId: run.userMessageId,
|
|
4620
|
+
session: publicSnapshot(run.stored.session),
|
|
4524
4621
|
});
|
|
4622
|
+
for (const message of [run.reservedUserMessage!, run.reservedAssistantMessage!]) {
|
|
4623
|
+
this.emitEvent(run.scopeId, run.stored.session, {
|
|
4624
|
+
type: 'message.created',
|
|
4625
|
+
runId: run.runId,
|
|
4626
|
+
messageId: message.messageId,
|
|
4627
|
+
message,
|
|
4628
|
+
});
|
|
4629
|
+
}
|
|
4630
|
+
} finally {
|
|
4631
|
+
closeReservationPublication();
|
|
4525
4632
|
}
|
|
4526
4633
|
|
|
4527
4634
|
queued.status = run.scheduleKey ? 'scheduled' : 'starting';
|
|
@@ -4729,15 +4836,20 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4729
4836
|
run.steering.keptInContext = true;
|
|
4730
4837
|
this.refreshAutomaticArchiveHorizon(run.stored);
|
|
4731
4838
|
run.phase = 'promoting';
|
|
4839
|
+
const closePublication = this.openPublication(run.state, run.sessionId);
|
|
4732
4840
|
try {
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4841
|
+
try {
|
|
4842
|
+
await this.promoteCompletedTerminal(run, terminal);
|
|
4843
|
+
} catch (error) {
|
|
4844
|
+
const projected = this.projectExternalError(run, error, 'persistence');
|
|
4845
|
+
await this.fenceNamespace(run.state, run, projected);
|
|
4846
|
+
throw projected;
|
|
4847
|
+
}
|
|
4848
|
+
this.emitTerminalProjection(run, terminal);
|
|
4849
|
+
this.emitFinalRunEvent(run, terminal.assistantMessage, undefined, false);
|
|
4850
|
+
} finally {
|
|
4851
|
+
closePublication();
|
|
4738
4852
|
}
|
|
4739
|
-
this.emitTerminalProjection(run, terminal);
|
|
4740
|
-
this.emitFinalRunEvent(run, terminal.assistantMessage, undefined, false);
|
|
4741
4853
|
await this.drainReversionReleases(
|
|
4742
4854
|
run.state,
|
|
4743
4855
|
run.stored,
|
|
@@ -4858,16 +4970,21 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4858
4970
|
: safeError;
|
|
4859
4971
|
const terminal = this.buildTerminal(run, publicStatus, undefined, terminalError);
|
|
4860
4972
|
if (!this.isTombstoned(run)) {
|
|
4973
|
+
const closePublication = this.openPublication(run.state, run.sessionId);
|
|
4861
4974
|
try {
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4975
|
+
try {
|
|
4976
|
+
await this.publishFailedTerminal(run, terminal);
|
|
4977
|
+
} catch (persistenceError) {
|
|
4978
|
+
errors.push(this.projectExternalError(run, persistenceError, 'persistence'));
|
|
4979
|
+
const combined = combineErrors(errors);
|
|
4980
|
+
await this.fenceNamespace(run.state, run, combined);
|
|
4981
|
+
throw combined;
|
|
4982
|
+
}
|
|
4983
|
+
this.emitTerminalProjection(run, terminal);
|
|
4984
|
+
this.emitFinalRunEvent(run, terminal.assistantMessage, terminalError, publicStatus === 'cancelled');
|
|
4985
|
+
} finally {
|
|
4986
|
+
closePublication();
|
|
4868
4987
|
}
|
|
4869
|
-
this.emitTerminalProjection(run, terminal);
|
|
4870
|
-
this.emitFinalRunEvent(run, terminal.assistantMessage, terminalError, publicStatus === 'cancelled');
|
|
4871
4988
|
}
|
|
4872
4989
|
await this.drainReversionReleases(
|
|
4873
4990
|
run.state,
|
|
@@ -6561,61 +6678,66 @@ export class FlexHarness<TScope = unknown> {
|
|
|
6561
6678
|
parts: [],
|
|
6562
6679
|
}
|
|
6563
6680
|
: undefined;
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6681
|
+
const closePublication = this.openPublication(run.state, run.sessionId);
|
|
6682
|
+
try {
|
|
6683
|
+
await this.mutateProjection(run.state, run.stored, () => {
|
|
6684
|
+
const index = run.stored.messages.findIndex((message) => message.messageId === answeredAssistantId);
|
|
6685
|
+
if (index < 0) throw new Error('The steered run has no projected assistant message.');
|
|
6686
|
+
if (!nextAssistant) {
|
|
6687
|
+
run.stored.messages.splice(index, 0, cloneSerializable(steerMessage));
|
|
6688
|
+
return;
|
|
6689
|
+
}
|
|
6690
|
+
completedAssistant = {
|
|
6691
|
+
...cloneSerializable(run.stored.messages[index]),
|
|
6692
|
+
status: 'completed',
|
|
6693
|
+
completedAt: timestamp,
|
|
6694
|
+
parts: cloneSerializable(run.callbackParts),
|
|
6695
|
+
...(run.modelResolution ? { model: cloneSerializable(run.modelResolution.identity) } : {}),
|
|
6696
|
+
};
|
|
6697
|
+
run.stored.messages.splice(
|
|
6698
|
+
index,
|
|
6699
|
+
1,
|
|
6700
|
+
cloneSerializable(completedAssistant),
|
|
6701
|
+
cloneSerializable(steerMessage),
|
|
6702
|
+
cloneSerializable(nextAssistant),
|
|
6703
|
+
);
|
|
6704
|
+
});
|
|
6705
|
+
if (completedAssistant && nextAssistant) {
|
|
6706
|
+
run.assistantMessageId = nextAssistant.messageId;
|
|
6707
|
+
run.reservedAssistantMessage = publicSnapshot(nextAssistant);
|
|
6708
|
+
run.callbackParts = [];
|
|
6709
|
+
run.reasoningPartIds.clear();
|
|
6710
|
+
this.emitEvent(run.scopeId, run.stored.session, {
|
|
6711
|
+
type: 'message.updated',
|
|
6712
|
+
runId: run.runId,
|
|
6713
|
+
messageId: completedAssistant.messageId,
|
|
6714
|
+
message: publicSnapshot(completedAssistant),
|
|
6715
|
+
});
|
|
6570
6716
|
}
|
|
6571
|
-
completedAssistant = {
|
|
6572
|
-
...cloneSerializable(run.stored.messages[index]),
|
|
6573
|
-
status: 'completed',
|
|
6574
|
-
completedAt: timestamp,
|
|
6575
|
-
parts: cloneSerializable(run.callbackParts),
|
|
6576
|
-
...(run.modelResolution ? { model: cloneSerializable(run.modelResolution.identity) } : {}),
|
|
6577
|
-
};
|
|
6578
|
-
run.stored.messages.splice(
|
|
6579
|
-
index,
|
|
6580
|
-
1,
|
|
6581
|
-
cloneSerializable(completedAssistant),
|
|
6582
|
-
cloneSerializable(steerMessage),
|
|
6583
|
-
cloneSerializable(nextAssistant),
|
|
6584
|
-
);
|
|
6585
|
-
});
|
|
6586
|
-
if (completedAssistant && nextAssistant) {
|
|
6587
|
-
run.assistantMessageId = nextAssistant.messageId;
|
|
6588
|
-
run.reservedAssistantMessage = publicSnapshot(nextAssistant);
|
|
6589
|
-
run.callbackParts = [];
|
|
6590
|
-
run.reasoningPartIds.clear();
|
|
6591
6717
|
this.emitEvent(run.scopeId, run.stored.session, {
|
|
6592
|
-
type: 'message.
|
|
6718
|
+
type: 'message.created',
|
|
6593
6719
|
runId: run.runId,
|
|
6594
|
-
messageId:
|
|
6595
|
-
message: publicSnapshot(
|
|
6720
|
+
messageId: steerMessage.messageId,
|
|
6721
|
+
message: publicSnapshot(steerMessage),
|
|
6596
6722
|
});
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
|
|
6604
|
-
|
|
6723
|
+
if (nextAssistant) {
|
|
6724
|
+
this.emitEvent(run.scopeId, run.stored.session, {
|
|
6725
|
+
type: 'message.created',
|
|
6726
|
+
runId: run.runId,
|
|
6727
|
+
messageId: nextAssistant.messageId,
|
|
6728
|
+
message: publicSnapshot(nextAssistant),
|
|
6729
|
+
});
|
|
6730
|
+
}
|
|
6605
6731
|
this.emitEvent(run.scopeId, run.stored.session, {
|
|
6606
|
-
type: '
|
|
6732
|
+
type: 'prompt.steer.applied',
|
|
6733
|
+
queueId: run.queueId,
|
|
6607
6734
|
runId: run.runId,
|
|
6608
|
-
|
|
6609
|
-
|
|
6735
|
+
steerId: applied.inputId,
|
|
6736
|
+
messageId: steerMessage.messageId,
|
|
6610
6737
|
});
|
|
6738
|
+
} finally {
|
|
6739
|
+
closePublication();
|
|
6611
6740
|
}
|
|
6612
|
-
this.emitEvent(run.scopeId, run.stored.session, {
|
|
6613
|
-
type: 'prompt.steer.applied',
|
|
6614
|
-
queueId: run.queueId,
|
|
6615
|
-
runId: run.runId,
|
|
6616
|
-
steerId: applied.inputId,
|
|
6617
|
-
messageId: steerMessage.messageId,
|
|
6618
|
-
});
|
|
6619
6741
|
}
|
|
6620
6742
|
|
|
6621
6743
|
private buildTerminal(
|
|
@@ -8255,28 +8377,33 @@ export class FlexHarness<TScope = unknown> {
|
|
|
8255
8377
|
responseQueue: Promise.resolve(),
|
|
8256
8378
|
abortListener,
|
|
8257
8379
|
};
|
|
8258
|
-
|
|
8259
|
-
run.pendingPermissionIds.add(request.permissionId);
|
|
8260
|
-
run.controller.signal.addEventListener('abort', abortListener, { once: true });
|
|
8380
|
+
const closePublication = this.openPublication(state, run.sessionId);
|
|
8261
8381
|
try {
|
|
8262
|
-
|
|
8263
|
-
|
|
8264
|
-
|
|
8265
|
-
|
|
8266
|
-
|
|
8267
|
-
|
|
8268
|
-
|
|
8269
|
-
|
|
8270
|
-
|
|
8271
|
-
|
|
8272
|
-
|
|
8273
|
-
|
|
8274
|
-
|
|
8275
|
-
|
|
8276
|
-
|
|
8277
|
-
|
|
8278
|
-
|
|
8279
|
-
|
|
8382
|
+
state.pendingPermissions.set(request.permissionId, pending);
|
|
8383
|
+
run.pendingPermissionIds.add(request.permissionId);
|
|
8384
|
+
run.controller.signal.addEventListener('abort', abortListener, { once: true });
|
|
8385
|
+
try {
|
|
8386
|
+
await this.mutateScope(state, () => {
|
|
8387
|
+
if (state.sessions.get(run.sessionId) !== run.stored) {
|
|
8388
|
+
throw new FlexHarnessAbortError('The session was deleted while permission was pending.');
|
|
8389
|
+
}
|
|
8390
|
+
run.stored.session.status = 'waiting_permission';
|
|
8391
|
+
run.stored.session.activity.status = 'waiting_permission';
|
|
8392
|
+
run.stored.session.updatedAt = new Date().toISOString();
|
|
8393
|
+
}, true);
|
|
8394
|
+
} catch (error) {
|
|
8395
|
+
this.forceRejectPending(state, pending, this.projectExternalError(run, error, 'persistence'));
|
|
8396
|
+
return promise;
|
|
8397
|
+
}
|
|
8398
|
+
if (!pending.settled) {
|
|
8399
|
+
this.emitEvent(run.scopeId, run.stored.session, {
|
|
8400
|
+
type: 'permission.requested',
|
|
8401
|
+
runId: run.runId,
|
|
8402
|
+
request: publicSnapshot(request),
|
|
8403
|
+
});
|
|
8404
|
+
}
|
|
8405
|
+
} finally {
|
|
8406
|
+
closePublication();
|
|
8280
8407
|
}
|
|
8281
8408
|
return promise;
|
|
8282
8409
|
}
|
|
@@ -9085,6 +9212,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
9085
9212
|
activeRuns: new Map(),
|
|
9086
9213
|
delegatedRunAdmissionOwners: new Set(),
|
|
9087
9214
|
pendingPermissions: new Map(),
|
|
9215
|
+
publications: new Map(),
|
|
9088
9216
|
scopeQueue: Promise.resolve(),
|
|
9089
9217
|
lifecycle: 'active',
|
|
9090
9218
|
fenceInProgress: false,
|