@@ -94,7 +94,7 @@ export class CodexAcpServer implements acp.Agent {
9494 private readonly pendingMcpStartupSessions : Map < string , PendingMcpStartupSession > ;
9595 private readonly pendingTurnStarts : Map < string , PendingTurnStart > ;
9696 private readonly activePrompts : Map < string , ActivePrompt > ;
97- private readonly closingSessions : Set < string > ;
97+ private readonly closingSessions : Map < string , number > ;
9898 private readonly sessionGenerations : Map < string , number > ;
9999 private readonly sessionOpenGenerations : Map < string , number > ;
100100
@@ -108,7 +108,7 @@ export class CodexAcpServer implements acp.Agent {
108108 this . pendingMcpStartupSessions = new Map ( ) ;
109109 this . pendingTurnStarts = new Map ( ) ;
110110 this . activePrompts = new Map ( ) ;
111- this . closingSessions = new Set ( ) ;
111+ this . closingSessions = new Map ( ) ;
112112 this . sessionGenerations = new Map ( ) ;
113113 this . sessionOpenGenerations = new Map ( ) ;
114114 this . connection = connection ;
@@ -209,34 +209,57 @@ export class CodexAcpServer implements acp.Agent {
209209
210210 private beginSessionOpen ( sessionId : string ) : number {
211211 const generation = this . getSessionGeneration ( sessionId ) ;
212- if ( this . closingSessions . has ( sessionId ) ) {
212+ if ( this . sessionIsClosing ( sessionId ) ) {
213213 throw RequestError . invalidRequest ( `Session ${ sessionId } is closing` ) ;
214214 }
215215 this . sessionOpenGenerations . set ( sessionId , generation ) ;
216216 return generation ;
217217 }
218218
219219 private sessionOpenCanInstall ( sessionId : string , generation : number ) : boolean {
220- return ! this . closingSessions . has ( sessionId ) && this . getSessionGeneration ( sessionId ) === generation ;
220+ return ! this . sessionIsClosing ( sessionId ) && this . getSessionGeneration ( sessionId ) === generation ;
221221 }
222222
223- private async closeStaleSessionOpen ( sessionId : string , generation : number ) : Promise < void > {
223+ private async cleanupStaleSessionOpen ( sessionId : string , generation : number ) : Promise < boolean > {
224224 if ( this . sessionOpenGenerations . get ( sessionId ) === generation ) {
225- const staleCloseGeneration = this . bumpSessionGeneration ( sessionId ) ;
226- this . closingSessions . add ( sessionId ) ;
225+ if ( ! this . sessionIsClosing ( sessionId ) ) {
226+ this . bumpSessionGeneration ( sessionId ) ;
227+ }
228+ this . beginSessionCloseFence ( sessionId ) ;
227229 try {
228230 await this . runWithProcessCheck ( ( ) => this . codexAcpClient . closeSession ( sessionId ) ) ;
229231 } catch ( err ) {
230232 logger . error ( `Failed to close stale session open for ${ sessionId } ` , err ) ;
231233 } finally {
232- if ( this . getSessionGeneration ( sessionId ) === staleCloseGeneration ) {
233- this . closingSessions . delete ( sessionId ) ;
234- }
234+ this . endSessionCloseFence ( sessionId ) ;
235235 }
236+ return true ;
236237 }
238+ return false ;
239+ }
240+
241+ private async closeStaleSessionOpen ( sessionId : string , generation : number ) : Promise < void > {
242+ await this . cleanupStaleSessionOpen ( sessionId , generation ) ;
237243 throw RequestError . invalidRequest ( `Session ${ sessionId } is closing` ) ;
238244 }
239245
246+ private sessionIsClosing ( sessionId : string ) : boolean {
247+ return ( this . closingSessions . get ( sessionId ) ?? 0 ) > 0 ;
248+ }
249+
250+ private beginSessionCloseFence ( sessionId : string ) : void {
251+ this . closingSessions . set ( sessionId , ( this . closingSessions . get ( sessionId ) ?? 0 ) + 1 ) ;
252+ }
253+
254+ private endSessionCloseFence ( sessionId : string ) : void {
255+ const count = this . closingSessions . get ( sessionId ) ?? 0 ;
256+ if ( count <= 1 ) {
257+ this . closingSessions . delete ( sessionId ) ;
258+ return ;
259+ }
260+ this . closingSessions . set ( sessionId , count - 1 ) ;
261+ }
262+
240263 private getSessionGeneration ( sessionId : string ) : number {
241264 return this . sessionGenerations . get ( sessionId ) ?? 0 ;
242265 }
@@ -258,18 +281,39 @@ export class CodexAcpServer implements acp.Agent {
258281 : null ;
259282
260283 let sessionMetadata : SessionMetadata ;
284+ let resumeSubscribed = false ;
261285 if ( "sessionId" in request ) {
262286 logger . log ( `Resume existing session: ${ request . sessionId } ...` )
263- sessionMetadata = await this . runWithProcessCheck ( ( ) => this . codexAcpClient . resumeSession ( request ) ) ;
287+ try {
288+ sessionMetadata = await this . runWithProcessCheck ( ( ) =>
289+ this . codexAcpClient . resumeSession ( request , ( ) => {
290+ resumeSubscribed = true ;
291+ } )
292+ ) ;
293+ } catch ( err ) {
294+ if ( resumeSubscribed && requestedSessionGeneration !== null ) {
295+ await this . cleanupStaleSessionOpen ( request . sessionId , requestedSessionGeneration ) ;
296+ }
297+ throw err ;
298+ }
264299 } else {
265300 logger . log ( `Create new session...` )
266301 sessionMetadata = await this . runWithProcessCheck ( ( ) => this . codexAcpClient . newSession ( request ) ) ;
267302 }
268303
269- const account = await this . getActiveAccount ( ) ;
270304 const { sessionId, currentModelId, models} = sessionMetadata ;
305+ let account : Account | null ;
306+ try {
307+ account = await this . getActiveAccount ( ) ;
308+ } catch ( err ) {
309+ if ( resumeSubscribed && requestedSessionGeneration !== null ) {
310+ await this . cleanupStaleSessionOpen ( sessionId , requestedSessionGeneration ) ;
311+ }
312+ throw err ;
313+ }
271314 const sessionGeneration = requestedSessionGeneration ?? this . beginSessionOpen ( sessionId ) ;
272315 if ( ! this . sessionOpenCanInstall ( sessionId , sessionGeneration ) ) {
316+ resumeSubscribed = false ;
273317 await this . closeStaleSessionOpen ( sessionId , sessionGeneration ) ;
274318 }
275319 const sessionMcpServers = this . resolveSessionMcpServers ( requestedMcpServers , "sessionId" in request ) ;
@@ -293,6 +337,7 @@ export class CodexAcpServer implements acp.Agent {
293337 sessionMcpServers : sessionMcpServers ,
294338 }
295339 this . sessions . set ( sessionId , sessionState ) ;
340+ resumeSubscribed = false ;
296341
297342 if ( requestedMcpServers . length > 0 && mcpServerStartupVersion !== null ) {
298343 this . pendingMcpStartupSessions . set ( sessionId , {
@@ -366,7 +411,7 @@ export class CodexAcpServer implements acp.Agent {
366411 logger . log ( "Closing session..." , { sessionId : params . sessionId } ) ;
367412 const closeGeneration = this . bumpSessionGeneration ( params . sessionId ) ;
368413 const sessionState = this . sessions . get ( params . sessionId ) ;
369- this . closingSessions . add ( params . sessionId ) ;
414+ this . beginSessionCloseFence ( params . sessionId ) ;
370415
371416 try {
372417 if ( sessionState ) {
@@ -389,8 +434,8 @@ export class CodexAcpServer implements acp.Agent {
389434 this . pendingMcpStartupSessions . delete ( params . sessionId ) ;
390435 this . pendingTurnStarts . delete ( params . sessionId ) ;
391436 this . activePrompts . delete ( params . sessionId ) ;
392- this . closingSessions . delete ( params . sessionId ) ;
393437 }
438+ this . endSessionCloseFence ( params . sessionId ) ;
394439 }
395440
396441 return { } ;
@@ -568,13 +613,33 @@ export class CodexAcpServer implements acp.Agent {
568613 : null ;
569614
570615 logger . log ( `Load existing session: ${ request . sessionId } ...` ) ;
571- const sessionMetadata : SessionMetadataWithThread = await this . runWithProcessCheck ( ( ) =>
572- this . codexAcpClient . loadSession ( request )
573- ) ;
616+ let subscribed = false ;
617+ let sessionMetadata : SessionMetadataWithThread ;
618+ try {
619+ sessionMetadata = await this . runWithProcessCheck ( ( ) =>
620+ this . codexAcpClient . loadSession ( request , ( ) => {
621+ subscribed = true ;
622+ } )
623+ ) ;
624+ } catch ( err ) {
625+ if ( subscribed ) {
626+ await this . cleanupStaleSessionOpen ( request . sessionId , requestedSessionGeneration ) ;
627+ }
628+ throw err ;
629+ }
574630
575- const account = await this . getActiveAccount ( ) ;
576631 const { sessionId, currentModelId, models, thread} = sessionMetadata ;
632+ let account : Account | null ;
633+ try {
634+ account = await this . getActiveAccount ( ) ;
635+ } catch ( err ) {
636+ if ( subscribed ) {
637+ await this . cleanupStaleSessionOpen ( request . sessionId , requestedSessionGeneration ) ;
638+ }
639+ throw err ;
640+ }
577641 if ( ! this . sessionOpenCanInstall ( sessionId , requestedSessionGeneration ) ) {
642+ subscribed = false ;
578643 await this . closeStaleSessionOpen ( sessionId , requestedSessionGeneration ) ;
579644 }
580645 const sessionMcpServers = this . resolveSessionMcpServers ( requestedMcpServers , true ) ;
@@ -598,6 +663,7 @@ export class CodexAcpServer implements acp.Agent {
598663 sessionMcpServers : sessionMcpServers ,
599664 } ;
600665 this . sessions . set ( sessionId , sessionState ) ;
666+ subscribed = false ;
601667
602668 if ( requestedMcpServers . length > 0 && mcpServerStartupVersion !== null ) {
603669 this . pendingMcpStartupSessions . set ( sessionId , {
@@ -887,7 +953,7 @@ export class CodexAcpServer implements acp.Agent {
887953 )
888954 ) ;
889955 if ( ! this . sessions . has ( sessionId )
890- || this . closingSessions . has ( sessionId )
956+ || this . sessionIsClosing ( sessionId )
891957 || this . pendingMcpStartupSessions . get ( sessionId ) !== pendingStartup ) {
892958 return ;
893959 }
@@ -969,6 +1035,10 @@ export class CodexAcpServer implements acp.Agent {
9691035 }
9701036
9711037 private interruptLateStartedTurn ( sessionId : string , turnId : string ) : void {
1038+ this . codexAcpClient . markTurnStale ( {
1039+ threadId : sessionId ,
1040+ turnId,
1041+ } ) ;
9721042 void this . runWithProcessCheck ( ( ) => this . codexAcpClient . turnInterrupt ( {
9731043 threadId : sessionId ,
9741044 turnId,
@@ -983,7 +1053,7 @@ export class CodexAcpServer implements acp.Agent {
9831053 }
9841054
9851055 private promptIsClosedOrStale ( sessionId : string , activePrompt : ActivePrompt ) : boolean {
986- return this . activePrompts . get ( sessionId ) !== activePrompt || this . closingSessions . has ( sessionId ) ;
1056+ return this . activePrompts . get ( sessionId ) !== activePrompt || this . sessionIsClosing ( sessionId ) ;
9871057 }
9881058
9891059 private async interruptSessionTurn (
@@ -1000,6 +1070,12 @@ export class CodexAcpServer implements acp.Agent {
10001070 sessionId : sessionState . sessionId ,
10011071 currentTurnId : turnId ,
10021072 } ) ;
1073+ if ( resolveInterruptedTurn ) {
1074+ this . codexAcpClient . markTurnStale ( {
1075+ threadId : sessionState . sessionId ,
1076+ turnId,
1077+ } ) ;
1078+ }
10031079 try {
10041080 await this . runWithProcessCheck ( ( ) => this . codexAcpClient . turnInterrupt ( {
10051081 threadId : sessionState . sessionId ,
@@ -1079,7 +1155,7 @@ export class CodexAcpServer implements acp.Agent {
10791155 } ;
10801156 }
10811157
1082- if ( this . closingSessions . has ( params . sessionId ) ) {
1158+ if ( this . sessionIsClosing ( params . sessionId ) ) {
10831159 return {
10841160 stopReason : "cancelled" ,
10851161 usage : this . buildPromptUsage ( sessionState . lastTokenUsage ) ,
@@ -1147,7 +1223,7 @@ export class CodexAcpServer implements acp.Agent {
11471223
11481224 // Check if turn was interrupted (cancelled)
11491225 if ( turnCompleted . turn . status === "interrupted" ) {
1150- if ( ! this . closingSessions . has ( params . sessionId ) && this . sessions . has ( params . sessionId ) ) {
1226+ if ( ! this . sessionIsClosing ( params . sessionId ) && this . sessions . has ( params . sessionId ) ) {
11511227 await this . connection . sessionUpdate ( {
11521228 sessionId : params . sessionId ,
11531229 update : {
0 commit comments