@@ -44,6 +44,7 @@ export interface SessionState {
4444 supportedReasoningEfforts : Array < ReasoningEffortOption > ,
4545 supportedInputModalities : Array < InputModality > ,
4646 agentMode : AgentMode ,
47+ pendingTurnId : Promise < string > | null ;
4748 currentTurnId : string | null ;
4849 lastTokenUsage : TokenCount | null ;
4950 totalTokenUsage : TokenCount | null ;
@@ -68,6 +69,7 @@ export class CodexAcpServer implements acp.Agent {
6869
6970 private readonly sessions : Map < string , SessionState > ;
7071 private readonly pendingMcpStartupSessions : Map < string , PendingMcpStartupSession > ;
72+ private readonly closingSessions : Set < string > ;
7173
7274 constructor (
7375 connection : acp . AgentSideConnection ,
@@ -77,14 +79,17 @@ export class CodexAcpServer implements acp.Agent {
7779 ) {
7880 this . sessions = new Map ( ) ;
7981 this . pendingMcpStartupSessions = new Map ( ) ;
82+ this . closingSessions = new Set ( ) ;
8083 this . connection = connection ;
8184 this . codexAcpClient = codexAcpClient ;
8285 this . defaultAuthRequest = defaultAuthRequest ?? null ;
8386 this . getExitCode = getExitCode ?? ( ( ) => null ) ;
8487 this . availableCommands = new CodexCommands (
8588 connection ,
8689 codexAcpClient ,
87- ( operation ) => this . runWithProcessCheck ( operation )
90+ ( operation ) => this . runWithProcessCheck ( operation ) ,
91+ ( sessionId ) => this . sessions . has ( sessionId ) ,
92+ ( sessionId ) => this . closingSessions . has ( sessionId ) ,
8893 ) ;
8994 }
9095
@@ -170,6 +175,7 @@ export class CodexAcpServer implements acp.Agent {
170175 supportedReasoningEfforts : currentModel ?. supportedReasoningEfforts ?? [ ] ,
171176 supportedInputModalities : currentModel ?. inputModalities ?? [ "text" , "image" ] ,
172177 agentMode : AgentMode . getInitialAgentMode ( ) ,
178+ pendingTurnId : null ,
173179 currentTurnId : null ,
174180 lastTokenUsage : null ,
175181 totalTokenUsage : null ,
@@ -242,11 +248,18 @@ export class CodexAcpServer implements acp.Agent {
242248 async unstable_closeSession ( params : acp . CloseSessionRequest ) : Promise < acp . CloseSessionResponse > {
243249 logger . log ( "Closing session..." , { sessionId : params . sessionId } ) ;
244250 const sessionState = this . getSessionState ( params . sessionId ) ;
251+ this . closingSessions . add ( params . sessionId ) ;
245252
246- await this . runWithProcessCheck ( ( ) =>
247- this . codexAcpClient . closeSession ( params . sessionId , sessionState . currentTurnId )
248- ) ;
249- this . forgetSession ( params . sessionId ) ;
253+ try {
254+ const activeTurnId = await this . resolveActiveTurnId ( sessionState ) ;
255+ await this . runWithProcessCheck ( ( ) =>
256+ this . codexAcpClient . closeSession ( params . sessionId , activeTurnId )
257+ ) ;
258+ this . forgetSession ( params . sessionId ) ;
259+ } catch ( err ) {
260+ this . closingSessions . delete ( params . sessionId ) ;
261+ throw err ;
262+ }
250263
251264 logger . log ( "Session closed" , { sessionId : params . sessionId } ) ;
252265 return { } ;
@@ -394,6 +407,7 @@ export class CodexAcpServer implements acp.Agent {
394407 supportedReasoningEfforts : currentModel ?. supportedReasoningEfforts ?? [ ] ,
395408 supportedInputModalities : currentModel ?. inputModalities ?? [ "text" , "image" ] ,
396409 agentMode : AgentMode . getInitialAgentMode ( ) ,
410+ pendingTurnId : null ,
397411 currentTurnId : null ,
398412 lastTokenUsage : null ,
399413 totalTokenUsage : null ,
@@ -655,9 +669,26 @@ export class CodexAcpServer implements acp.Agent {
655669 return sessionState ;
656670 }
657671
672+ private async resolveActiveTurnId ( sessionState : SessionState ) : Promise < string | null > {
673+ if ( sessionState . currentTurnId ) {
674+ return sessionState . currentTurnId ;
675+ }
676+ if ( ! sessionState . pendingTurnId ) {
677+ return null ;
678+ }
679+
680+ try {
681+ return await sessionState . pendingTurnId ;
682+ } catch ( err ) {
683+ logger . error ( `Failed to resolve pending turn for session ${ sessionState . sessionId } ` , err ) ;
684+ return null ;
685+ }
686+ }
687+
658688 private forgetSession ( sessionId : string ) : void {
659689 this . sessions . delete ( sessionId ) ;
660690 this . pendingMcpStartupSessions . delete ( sessionId ) ;
691+ this . closingSessions . delete ( sessionId ) ;
661692 }
662693
663694 private resolveSessionMcpServers (
@@ -741,6 +772,10 @@ await this.publishMcpStartupStatus(sessionId, mcpStartup, pendingStartup.request
741772 prompt : params . prompt ,
742773 } ) ;
743774 const sessionState = this . getSessionState ( params . sessionId ) ;
775+ if ( this . closingSessions . has ( params . sessionId ) ) {
776+ throw RequestError . invalidRequest ( "Session is closing" ) ;
777+ }
778+ sessionState . pendingTurnId = null ;
744779 sessionState . currentTurnId = null ;
745780 sessionState . lastTokenUsage = null ;
746781
@@ -750,6 +785,9 @@ await this.publishMcpStartupStatus(sessionId, mcpStartup, pendingStartup.request
750785 const elicitationHandler = new CodexElicitationHandler ( this . connection , sessionState ) ;
751786 await this . codexAcpClient . subscribeToSessionEvents ( params . sessionId ,
752787 ( event ) => {
788+ if ( this . closingSessions . has ( params . sessionId ) ) {
789+ return ;
790+ }
753791 elicitationHandler . handleNotification ( event ) ;
754792 return eventHandler . handleNotification ( event ) ;
755793 } ,
@@ -781,21 +819,32 @@ await this.publishMcpStartupStatus(sessionId, mcpStartup, pendingStartup.request
781819 throw RequestError . invalidRequest ( "The current model does not support image input" ) ;
782820 }
783821 const agentMode = sessionState . agentMode ;
822+ const pendingTurnId = this . runWithProcessCheck (
823+ ( ) => this . codexAcpClient . startPrompt ( params , agentMode , modelId , disableSummary , sessionState . cwd )
824+ ) ;
825+ sessionState . pendingTurnId = pendingTurnId ;
826+ const turnId = await pendingTurnId ;
827+ sessionState . pendingTurnId = null ;
828+ sessionState . currentTurnId = turnId ;
829+
784830 const turnCompleted = await this . runWithProcessCheck (
785- ( ) => this . codexAcpClient . sendPrompt ( params , agentMode , modelId , disableSummary , sessionState . cwd ) ) ;
831+ ( ) => this . codexAcpClient . awaitTurnCompleted ( params . sessionId , turnId )
832+ ) ;
786833
787834 // Check if turn was interrupted (cancelled)
788835 if ( turnCompleted . turn . status === "interrupted" ) {
789- await this . connection . sessionUpdate ( {
790- sessionId : params . sessionId ,
791- update : {
792- sessionUpdate : "agent_message_chunk" ,
793- content : {
794- type : "text" ,
795- text : "*Conversation interrupted*"
836+ if ( ! this . closingSessions . has ( params . sessionId ) ) {
837+ await this . connection . sessionUpdate ( {
838+ sessionId : params . sessionId ,
839+ update : {
840+ sessionUpdate : "agent_message_chunk" ,
841+ content : {
842+ type : "text" ,
843+ text : "*Conversation interrupted*"
844+ }
796845 }
797- }
798- } ) ;
846+ } ) ;
847+ }
799848 return {
800849 stopReason : "cancelled" ,
801850 usage : this . buildPromptUsage ( sessionState . lastTokenUsage ) ,
@@ -819,6 +868,7 @@ await this.publishMcpStartupStatus(sessionId, mcpStartup, pendingStartup.request
819868 throw err ;
820869 } finally {
821870 logger . log ( "Prompt completed" , { sessionId : params . sessionId } ) ;
871+ sessionState . pendingTurnId = null ;
822872 sessionState . currentTurnId = null ;
823873 }
824874 }
@@ -872,24 +922,25 @@ await this.publishMcpStartupStatus(sessionId, mcpStartup, pendingStartup.request
872922 return ;
873923 }
874924
875- if ( ! sessionState . currentTurnId ) {
925+ const currentTurnId = await this . resolveActiveTurnId ( sessionState ) ;
926+ if ( ! currentTurnId ) {
876927 logger . log ( "Cancel request rejected: no current turn" , { sessionId : params . sessionId } ) ;
877928 return ;
878929 }
879930
880931 logger . log ( "Cancel session requested" , {
881932 sessionId : params . sessionId ,
882- currentTurnId : sessionState . currentTurnId
933+ currentTurnId,
883934 } ) ;
884935 try {
885936 // After turnInterrupt(), Codex will send turn/completed event, which will naturally complete awaitTurnCompleted()
886937 await this . codexAcpClient . turnInterrupt ( {
887938 threadId : params . sessionId ,
888- turnId : sessionState . currentTurnId
939+ turnId : currentTurnId
889940 } ) ;
890941 logger . log ( "Cancel - turnInterrupt succeeded" , {
891942 sessionId : params . sessionId ,
892- currentTurnId : sessionState . currentTurnId
943+ currentTurnId,
893944 } ) ;
894945 } catch ( err ) {
895946 logger . error ( `Cancel - turnInterrupt failed` , err ) ;
0 commit comments