@@ -10,7 +10,7 @@ import {ModelId} from "./ModelId";
1010
1111export interface SessionState {
1212 sessionMetadata : SessionMetadata ;
13- pendingPrompt : AbortController | null ;
13+ currentTurnId : string | null ;
1414}
1515
1616export class CodexAcpServer implements acp . Agent {
@@ -62,7 +62,7 @@ export class CodexAcpServer implements acp.Agent {
6262 const { sessionId, currentModelId, models} = sessionMetadata ;
6363 this . sessions . set ( sessionId , {
6464 sessionMetadata : sessionMetadata ,
65- pendingPrompt : null
65+ currentTurnId : null
6666 } ) ;
6767
6868 const availableModels = this . buildAvailableModels ( models ) ;
@@ -153,25 +153,34 @@ export class CodexAcpServer implements acp.Agent {
153153 async prompt ( params : acp . PromptRequest ) : Promise < acp . PromptResponse > {
154154 const sessionState = this . getSessionState ( params . sessionId ) ;
155155
156- sessionState . pendingPrompt ?. abort ( ) ;
157- sessionState . pendingPrompt = new AbortController ( ) ;
156+ sessionState . currentTurnId = null ;
158157
159158 try {
160159 const messageHandler = new CodexEventHandler ( this . connection , sessionState ) ;
161- await this . runWithProcessCheck ( ( ) => this . codexAcpClient . sendPrompt ( params , ( event ) => messageHandler . handleNotification ( event ) ) ) ;
162- } catch ( err ) {
163- if ( sessionState . pendingPrompt . signal . aborted ) {
164- return { stopReason : "cancelled" } ;
160+ const turnCompleted = await this . runWithProcessCheck ( ( ) => this . codexAcpClient . sendPrompt (
161+ params ,
162+ ( event ) => messageHandler . handleNotification ( event ) ,
163+ ( turnId ) => {
164+ sessionState . currentTurnId = turnId ;
165+ }
166+ ) ) ;
167+
168+ // Check if turn was interrupted (cancelled)
169+ if ( turnCompleted . turn . status === "interrupted" ) {
170+ return {
171+ stopReason : "cancelled" ,
172+ } ;
165173 }
166174
175+ return {
176+ stopReason : "end_turn" ,
177+ } ;
178+ } catch ( err ) {
179+ console . error ( `[CodexAcpServer] prompt() failed:` , err ) ;
167180 throw err ;
181+ } finally {
182+ sessionState . currentTurnId = null ;
168183 }
169-
170- sessionState . pendingPrompt = null ;
171-
172- return {
173- stopReason : "end_turn" ,
174- } ;
175184 }
176185
177186 private async runWithProcessCheck < T > ( operation : ( ) => Promise < T > ) : Promise < T > {
@@ -191,7 +200,27 @@ export class CodexAcpServer implements acp.Agent {
191200 }
192201
193202 async cancel ( params : acp . CancelNotification ) : Promise < void > {
194- //TODO not supported yet
195- this . sessions . get ( params . sessionId ) ?. pendingPrompt ?. abort ( ) ;
203+ const sessionState = this . sessions . get ( params . sessionId ) ;
204+ if ( ! sessionState ) {
205+ console . info ( `Can not cancel: session ${ params . sessionId } not found` ) ;
206+ return ;
207+ }
208+
209+ if ( ! sessionState . currentTurnId ) {
210+ console . info ( `Can not cancel: session ${ params . sessionId } has no current turn` ) ;
211+ return ;
212+ }
213+
214+ console . info ( `Cancel session ${ params . sessionId } , currentTurnId: ${ sessionState . currentTurnId } ...` ) ;
215+ try {
216+ // After turnInterrupt(), Codex will send turn/completed event, which will naturally complete awaitTurnCompleted()
217+ await this . codexAcpClient . turnInterrupt ( {
218+ threadId : params . sessionId ,
219+ turnId : sessionState . currentTurnId
220+ } ) ;
221+ console . log ( `Cancel - turnInterrupt succeeded` ) ;
222+ } catch ( err ) {
223+ console . error ( `Cancel - turnInterrupt failed:` , err ) ;
224+ }
196225 }
197226}
0 commit comments