@@ -1071,6 +1071,12 @@ export class AgentSession {
10711071 private readonly _promptLifecycles = new PromptLifecycleStore ( ) ;
10721072 private readonly _pendingCorrelatedPromptAdmissions = new Map < string , AbortController > ( ) ;
10731073 private readonly _promptEventContext = new AsyncLocalStorage < string > ( ) ;
1074+ private readonly _deferredPromptLifecycleSettlements = new Map < Promise < void > , string > ( ) ;
1075+ private readonly _promptEventQueueFailures = new Set < string > ( ) ;
1076+ private readonly _postCompactionPromptOwners = new WeakMap < PostCompactionContinuationSettlement , string > ( ) ;
1077+ private readonly _agentEventPromptCorrelations = new WeakMap < AgentEvent , string > ( ) ;
1078+ private readonly _postCompactionPromptScheduleRevisions = new Map < string , number > ( ) ;
1079+ private _activePostCompactionPromptCorrelationId : string | undefined ;
10741080 private _sessionReplacementFenced = false ;
10751081 private _sessionReplacementAdmissionGuard : ( ( ) => boolean ) | undefined ;
10761082 private _sessionInputPump : Promise < void > = Promise . resolve ( ) ;
@@ -1313,6 +1319,14 @@ export class AgentSession {
13131319 this . _goalAccountingStartedAt = Date . now ( ) ;
13141320 }
13151321
1322+ // A continuation settlement can be reused or replaced, so ownership follows
1323+ // the causal scheduling call rather than object identity.
1324+ const schedulePostCompactionContinue = this . _schedulePostCompactionContinue . bind ( this ) ;
1325+ this . _schedulePostCompactionContinue = ( ...args : Parameters < typeof this . _schedulePostCompactionContinue > ) => {
1326+ this . _recordPostCompactionPromptSchedule ( ) ;
1327+ return schedulePostCompactionContinue ( ...args ) ;
1328+ } ;
1329+
13161330 this . _unsubscribeAgent = this . agent . subscribe ( this . _handleAgentEvent ) ;
13171331 this . _installAgentToolHooks ( ) ;
13181332 this . _installAgentTurnHook ( ) ;
@@ -1537,20 +1551,94 @@ export class AgentSession {
15371551 phase : "completed" | "cancelled" | "failed" ,
15381552 ) : void {
15391553 if ( correlationId === undefined ) return ;
1554+ const eventQueueFailed = this . _promptEventQueueFailures . delete ( correlationId ) ;
15401555 const current = this . _promptLifecycles . get ( correlationId ) ;
15411556 if ( ! current || isPromptLifecycleTerminal ( current . phase ) ) return ;
1542- this . _transitionPromptLifecycle ( correlationId , phase ) ;
1557+ this . _postCompactionPromptScheduleRevisions . delete ( correlationId ) ;
1558+ // Later agent events intentionally recover the shared queue. A recovered tail must
1559+ // not turn an earlier correlated persistence failure into false completion.
1560+ this . _transitionPromptLifecycle ( correlationId , phase === "completed" && eventQueueFailed ? "failed" : phase ) ;
15431561 }
15441562
1545- private _emit ( event : AgentSessionEvent ) : void {
1563+ private _isDeliveredPromptLifecycleActive ( correlationId : string | undefined ) : correlationId is string {
1564+ if ( correlationId === undefined ) return false ;
1565+ const lifecycle = this . _promptLifecycles . get ( correlationId ) ;
1566+ return lifecycle ?. deliveryCrossed === true && ! isPromptLifecycleTerminal ( lifecycle . phase ) ;
1567+ }
1568+
1569+ private _currentPromptEventCorrelationId ( ) : string | undefined {
15461570 const contextualCorrelationId = this . _promptEventContext . getStore ( ) ;
1547- const contextualLifecycle =
1548- contextualCorrelationId === undefined ? undefined : this . _promptLifecycles . get ( contextualCorrelationId ) ;
1549- const promptCorrelationId =
1550- event . promptCorrelationId ??
1551- ( contextualLifecycle ?. deliveryCrossed === true && ! isPromptLifecycleTerminal ( contextualLifecycle . phase )
1552- ? contextualLifecycle . correlationId
1553- : null ) ;
1571+ return this . _isDeliveredPromptLifecycleActive ( contextualCorrelationId ) ? contextualCorrelationId : undefined ;
1572+ }
1573+
1574+ private _recordPostCompactionPromptSchedule ( ) : void {
1575+ const correlationId = this . _currentPromptEventCorrelationId ( ) ;
1576+ if ( correlationId !== undefined ) {
1577+ const revision = this . _postCompactionPromptScheduleRevisions . get ( correlationId ) ?? 0 ;
1578+ this . _postCompactionPromptScheduleRevisions . set ( correlationId , revision + 1 ) ;
1579+ }
1580+ }
1581+
1582+ private _transferDeferredPromptLifecycle (
1583+ correlationId : string ,
1584+ settlement : PostCompactionContinuationSettlement ,
1585+ ) : boolean {
1586+ const replacement = this . _postCompactionContinuationSettlement ;
1587+ if (
1588+ replacement === undefined ||
1589+ replacement === settlement ||
1590+ replacement . settled ||
1591+ this . _postCompactionPromptOwners . get ( settlement ) !== correlationId ||
1592+ ! this . _isDeliveredPromptLifecycleActive ( correlationId )
1593+ ) {
1594+ return false ;
1595+ }
1596+ this . _postCompactionPromptOwners . set ( replacement , correlationId ) ;
1597+ this . _deferPromptLifecycleSettlement ( correlationId , replacement ) ;
1598+ return true ;
1599+ }
1600+
1601+ private _deferPromptLifecycleSettlement (
1602+ correlationId : string ,
1603+ settlement : PostCompactionContinuationSettlement ,
1604+ ) : void {
1605+ const settle = async ( phase : "completed" | "failed" ) => {
1606+ let queueTailFailed = false ;
1607+ try {
1608+ await this . _agentEventQueue ;
1609+ } catch {
1610+ queueTailFailed = true ;
1611+ phase = "failed" ;
1612+ }
1613+ const recoveredEventFailure = this . _promptEventQueueFailures . has ( correlationId ) ;
1614+ this . _settlePromptLifecycle ( correlationId , phase ) ;
1615+ if ( queueTailFailed || recoveredEventFailure ) {
1616+ throw new Error ( "Correlated prompt event processing failed" ) ;
1617+ }
1618+ } ;
1619+ const operation = settlement . promise . then (
1620+ ( ) => {
1621+ if ( this . _transferDeferredPromptLifecycle ( correlationId , settlement ) ) return ;
1622+ return settle ( "completed" ) ;
1623+ } ,
1624+ ( ) => settle ( "failed" ) ,
1625+ ) ;
1626+ this . _deferredPromptLifecycleSettlements . set ( operation , correlationId ) ;
1627+ void operation
1628+ . finally ( ( ) => {
1629+ this . _deferredPromptLifecycleSettlements . delete ( operation ) ;
1630+ } )
1631+ . catch ( ( ) => undefined ) ;
1632+ }
1633+
1634+ private _failDeferredPromptLifecycles ( ) : void {
1635+ for ( const correlationId of new Set ( this . _deferredPromptLifecycleSettlements . values ( ) ) ) {
1636+ this . _settlePromptLifecycle ( correlationId , "failed" ) ;
1637+ }
1638+ }
1639+
1640+ private _emit ( event : AgentSessionEvent ) : void {
1641+ const promptCorrelationId = event . promptCorrelationId ?? this . _currentPromptEventCorrelationId ( ) ?? null ;
15541642 const correlatedEvent =
15551643 event . promptCorrelationId === undefined ? ( { ...event , promptCorrelationId } as AgentSessionEvent ) : event ;
15561644 for ( const l of this . _eventListeners ) {
@@ -3492,6 +3580,13 @@ export class AgentSession {
34923580 }
34933581
34943582 private _handleAgentEvent = ( event : AgentEvent ) : void => {
3583+ if ( event . type === "agent_start" && this . unfinishedActionCount === 0 ) {
3584+ const settlement = this . _postCompactionContinuationSettlement ;
3585+ const correlationId = settlement ? this . _postCompactionPromptOwners . get ( settlement ) : undefined ;
3586+ if ( this . _isDeliveredPromptLifecycleActive ( correlationId ) ) {
3587+ this . _activePostCompactionPromptCorrelationId = correlationId ;
3588+ }
3589+ }
34953590 this . _createRetryPromiseForAgentEnd ( event ) ;
34963591 if ( event . type === "message_start" || event . type === "message_end" ) {
34973592 for ( const action of this . _actionStore . ownedActions ( ) ) {
@@ -3551,10 +3646,37 @@ export class AgentSession {
35513646 }
35523647 }
35533648 }
3554- this . _agentEventQueue = this . _agentEventQueue . then (
3555- ( ) => this . _processAgentEvent ( event ) ,
3556- ( ) => this . _processAgentEvent ( event ) ,
3557- ) ;
3649+ const contextualCorrelationId = this . _currentPromptEventCorrelationId ( ) ;
3650+ const eventCorrelationId =
3651+ contextualCorrelationId ??
3652+ ( this . _isDeliveredPromptLifecycleActive ( this . _activePostCompactionPromptCorrelationId )
3653+ ? this . _activePostCompactionPromptCorrelationId
3654+ : undefined ) ;
3655+ if ( eventCorrelationId !== undefined ) this . _agentEventPromptCorrelations . set ( event , eventCorrelationId ) ;
3656+ if (
3657+ event . type === "agent_end" &&
3658+ eventCorrelationId !== undefined &&
3659+ this . _activePostCompactionPromptCorrelationId === eventCorrelationId
3660+ ) {
3661+ this . _activePostCompactionPromptCorrelationId = undefined ;
3662+ }
3663+ const processEvent = async ( ) => {
3664+ try {
3665+ if ( eventCorrelationId === undefined ) {
3666+ await this . _processAgentEvent ( event ) ;
3667+ } else {
3668+ await this . _promptEventContext . run ( eventCorrelationId , ( ) => this . _processAgentEvent ( event ) ) ;
3669+ }
3670+ } catch ( error ) {
3671+ if ( this . _isDeliveredPromptLifecycleActive ( eventCorrelationId ) ) {
3672+ this . _promptEventQueueFailures . add ( eventCorrelationId ) ;
3673+ }
3674+ throw error ;
3675+ } finally {
3676+ this . _agentEventPromptCorrelations . delete ( event ) ;
3677+ }
3678+ } ;
3679+ this . _agentEventQueue = this . _agentEventQueue . then ( processEvent , processEvent ) ;
35583680 this . _agentEventQueue . catch ( ( ) => { } ) ;
35593681 } ;
35603682
@@ -3610,6 +3732,7 @@ export class AgentSession {
36103732
36113733 private async _processAgentEvent ( event : AgentEvent ) : Promise < void > {
36123734 let clearedDispatchEnded = false ;
3735+ const eventCorrelationId = this . _agentEventPromptCorrelations . get ( event ) ;
36133736 if ( ( event . type === "message_start" || event . type === "message_end" ) && event . message . role === "toolResult" ) {
36143737 this . _applyLateIpythonSentAgentMessages ( event . message ) ;
36153738 }
@@ -3662,11 +3785,10 @@ export class AgentSession {
36623785
36633786 this . _addLoginGuidanceToAuthError ( event ) ;
36643787
3665- if ( event . type === "message_end" && event . message . role === "assistant" ) {
3666- const correlationId = this . _promptEventContext . getStore ( ) ;
3667- if ( correlationId !== undefined ) this . _promptLifecycles . addUsage ( correlationId , event . message . usage ) ;
3788+ if ( event . type === "message_end" && event . message . role === "assistant" && eventCorrelationId !== undefined ) {
3789+ this . _promptLifecycles . addUsage ( eventCorrelationId , event . message . usage ) ;
36683790 }
3669- this . _emit ( event ) ;
3791+ this . _emit ( { ... event , promptCorrelationId : eventCorrelationId ?? null } as AgentSessionEvent ) ;
36703792
36713793 if ( event . type === "message_end" ) {
36723794 if ( event . message . role === "custom" ) {
@@ -4251,6 +4373,7 @@ export class AgentSession {
42514373 return ;
42524374 }
42534375 this . _disposed = true ;
4376+ this . _failDeferredPromptLifecycles ( ) ;
42544377 for ( const run of this . _unsettledRlmChildRuns ) run . suppressTerminalNotice = true ;
42554378 for ( const controller of this . _rlmQuiescenceWaitAborts ) controller . abort ( ) ;
42564379 this . _sessionActionCommitDisposeAbortController . abort ( ) ;
@@ -6063,7 +6186,7 @@ export class AgentSession {
60636186 this . _emitQueueUpdate ( ) ;
60646187 try {
60656188 const startPreparedTurnActions = ( ) => this . _startPreparedTurnActions ( actions , epoch ) ;
6066- await ( first . promptCorrelationId
6189+ const postCompactionPromptSettlement = await ( first . promptCorrelationId
60676190 ? this . _promptEventContext . run ( first . promptCorrelationId , startPreparedTurnActions )
60686191 : startPreparedTurnActions ( ) ) ;
60696192 for ( const action of actions ) {
@@ -6079,7 +6202,14 @@ export class AgentSession {
60796202 }
60806203 if ( action . lifecycle . state === "running" ) {
60816204 transitionSessionAction ( action , { state : "completed" } ) ;
6082- this . _settlePromptLifecycle ( action . promptCorrelationId , "completed" ) ;
6205+ if ( action . promptCorrelationId !== undefined && postCompactionPromptSettlement ) {
6206+ this . _deferPromptLifecycleSettlement (
6207+ action . promptCorrelationId ,
6208+ postCompactionPromptSettlement ,
6209+ ) ;
6210+ } else {
6211+ this . _settlePromptLifecycle ( action . promptCorrelationId , "completed" ) ;
6212+ }
60836213 this . _actionStore . ticketFor ( action ) . settleCompleted ( ) ;
60846214 this . _settleAgentMessage ( action . agentMessageId , "completion" ) ;
60856215 }
@@ -6266,15 +6396,22 @@ export class AgentSession {
62666396 }
62676397 }
62686398
6269- private async _startPreparedTurnActions ( actions : QueuedSessionAction [ ] , epoch : number ) : Promise < void > {
6399+ private async _startPreparedTurnActions (
6400+ actions : QueuedSessionAction [ ] ,
6401+ epoch : number ,
6402+ ) : Promise < PostCompactionContinuationSettlement | undefined > {
62706403 let nextTurnMessages : CustomMessage [ ] = [ ] ;
62716404 const activeTurns = ( ) =>
62726405 actions . filter (
62736406 ( action ) : action is SessionAction < PreparedTurnPayload > =>
62746407 action . payload . kind === "turn" && action . lifecycle . state === "preparing" ,
62756408 ) ;
62766409 const firstTurn = activeTurns ( ) [ 0 ] ;
6277- if ( ! firstTurn ) return ;
6410+ if ( ! firstTurn ) return undefined ;
6411+ const postCompactionScheduleRevisionBeforeTurn =
6412+ firstTurn . promptCorrelationId === undefined
6413+ ? 0
6414+ : ( this . _postCompactionPromptScheduleRevisions . get ( firstTurn . promptCorrelationId ) ?? 0 ) ;
62786415 const executionPolicy = firstTurn . payload . executionPolicy ;
62796416 const restoreNextTurnContext = ( ) => {
62806417 this . _pendingNextTurnMessages . unshift ( ...nextTurnMessages ) ;
@@ -6375,6 +6512,12 @@ export class AgentSession {
63756512 await promptPromise ;
63766513 if ( executionPolicy . completionIncludesRetryChain ) await this . waitForRetry ( ) ;
63776514 if ( ! this . _hasCancelledDispatchCapture ( ) ) await this . _agentEventQueue ;
6515+ if (
6516+ firstTurn . promptCorrelationId !== undefined &&
6517+ this . _promptEventQueueFailures . has ( firstTurn . promptCorrelationId )
6518+ ) {
6519+ throw new Error ( "Correlated prompt event processing failed" ) ;
6520+ }
63786521 if (
63796522 turns . some (
63806523 ( action ) =>
@@ -6386,6 +6529,18 @@ export class AgentSession {
63866529 throw new Error ( "Session input dispatch settled without durable delivery" ) ;
63876530 }
63886531 this . _forgetConsumedPostCompactionContinuations ( turns . map ( ( action ) => primaryDeliveryRecord ( action ) . message ) ) ;
6532+ const postCompactionContinuation = this . _postCompactionContinuationSettlement ;
6533+ if (
6534+ firstTurn . promptCorrelationId !== undefined &&
6535+ postCompactionContinuation !== undefined &&
6536+ ! postCompactionContinuation . settled &&
6537+ ( this . _postCompactionPromptScheduleRevisions . get ( firstTurn . promptCorrelationId ) ?? 0 ) !==
6538+ postCompactionScheduleRevisionBeforeTurn
6539+ ) {
6540+ this . _postCompactionPromptOwners . set ( postCompactionContinuation , firstTurn . promptCorrelationId ) ;
6541+ return postCompactionContinuation ;
6542+ }
6543+ return undefined ;
63896544 } catch ( error ) {
63906545 const delivered = new Set ( this . agent . state . messages ) ;
63916546 this . _pendingNextTurnMessages . unshift ( ...nextTurnMessages . filter ( ( message ) => ! delivered . has ( message ) ) ) ;
@@ -6748,6 +6903,8 @@ export class AgentSession {
67486903 this . _emitQueueUpdate ( ) ;
67496904 return "applied" ;
67506905 }
6906+ // Correlated ownership binds the request fingerprint to the admitted payload.
6907+ if ( item . promptCorrelationId !== undefined ) return "rejected" ;
67516908 if (
67526909 item . payload . kind === "turn" &&
67536910 ( item . payload . acceptedAgentMessage ||
@@ -7182,13 +7339,19 @@ export class AgentSession {
71827339 }
71837340 }
71847341
7185- /** Waits out any owned post-compaction continuation and rejects when one cannot start; { @link waitForIdle} never rejects . */
7342+ /** Waits out owned post-compaction work and rejects when it cannot start or correlated event processing fails . */
71867343 async waitForHeadlessIdle ( ) : Promise < void > {
71877344 while ( true ) {
71887345 await this . waitForIdle ( ) ;
71897346 const postCompactionContinuation = this . _postCompactionContinuationSettlement ?. promise ;
7190- if ( ! postCompactionContinuation ) return ;
7191- await postCompactionContinuation ;
7347+ const promptLifecycleSettlements = [ ...this . _deferredPromptLifecycleSettlements . keys ( ) ] ;
7348+ if ( ! postCompactionContinuation && promptLifecycleSettlements . length === 0 ) return ;
7349+ const settlements = postCompactionContinuation
7350+ ? [ postCompactionContinuation , ...promptLifecycleSettlements ]
7351+ : promptLifecycleSettlements ;
7352+ const results = await Promise . allSettled ( settlements ) ;
7353+ const failure = results . find ( ( result ) : result is PromiseRejectedResult => result . status === "rejected" ) ;
7354+ if ( failure ) throw failure . reason ;
71927355 }
71937356 }
71947357
@@ -7239,6 +7402,7 @@ export class AgentSession {
72397402 }
72407403
72417404 requestAbort ( ) : void {
7405+ this . _failDeferredPromptLifecycles ( ) ;
72427406 for ( const run of [ ...this . _unsettledRlmChildRuns ] ) {
72437407 if ( run . status === "cancelled" ) this . _abandonRlmRunForQuiescence ( run ) ;
72447408 }
@@ -7286,6 +7450,7 @@ export class AgentSession {
72867450 }
72877451
72887452 abortForUpdateRestart ( ) : void {
7453+ this . _failDeferredPromptLifecycles ( ) ;
72897454 // Cancel scheduled pumps and suspend new ones: queued inputs must survive
72907455 // into the restart manifest instead of starting a turn during teardown.
72917456 this . _sessionInputPumpRequested = false ;
@@ -7890,6 +8055,7 @@ export class AgentSession {
78908055 }
78918056
78928057 private async _invalidatePendingAutoRefineForBranchChange ( ) : Promise < void > {
8058+ this . _failDeferredPromptLifecycles ( ) ;
78938059 this . _autoRefineReviewAbort ?. abort ( ) ;
78948060 this . _discardPendingAutoRefine ( { cancelPostCompactionContinue : true } ) ;
78958061 this . _assistantTurnsSinceAutoRefine = 0 ;
0 commit comments