@@ -222,6 +222,8 @@ export class CodexAppServerAgent extends BaseAcpAgent {
222222 private readonly usage = new UsageTracker ( ) ;
223223 /** Pause/clear can race a goal continuation already queued by app-server. */
224224 private cancelNextGoalTurn = false ;
225+ /** Native goal ticks start outside prompt(), so TurnController does not own them. */
226+ private nativeGoalTurnId ?: string ;
225227
226228 constructor (
227229 client : AgentSideConnection ,
@@ -427,6 +429,8 @@ export class CodexAppServerAgent extends BaseAcpAgent {
427429 additionalDirectories ?: string [ ] ;
428430 } ,
429431 ) : Promise < { threadId : string ; thread : AppServerThread | undefined } > {
432+ this . cancelNextGoalTurn = false ;
433+ this . nativeGoalTurnId = undefined ;
430434 this . jsonSchema = params . meta ?. jsonSchema ?? undefined ;
431435 this . taskRunId = params . meta ?. taskRunId ;
432436 this . environment = params . meta ?. environment ;
@@ -807,23 +811,36 @@ export class CodexAppServerAgent extends BaseAcpAgent {
807811 }
808812
809813 private async cancelRunningGoalTurn ( ) : Promise < void > {
810- if ( ! this . turns . isRunning ) return ;
811- this . cancelNextGoalTurn = false ;
812- await this . interrupt ( ) ;
814+ if ( this . turns . isRunning ) {
815+ this . cancelNextGoalTurn = false ;
816+ await this . interrupt ( ) ;
817+ return ;
818+ }
819+ if ( this . nativeGoalTurnId ) {
820+ await this . interruptNativeGoalTurn ( this . nativeGoalTurnId ) ;
821+ }
813822 }
814823
815824 private interruptQueuedGoalTurn ( turnId : string | undefined ) : void {
816825 if ( ! this . cancelNextGoalTurn || ! this . threadId || ! turnId ) return ;
817- void this . rpc
826+ void this . interruptNativeGoalTurn ( turnId ) ;
827+ }
828+
829+ private async interruptNativeGoalTurn ( turnId : string ) : Promise < void > {
830+ if ( ! this . threadId ) return ;
831+ await this . rpc
818832 . request ( APP_SERVER_METHODS . TURN_INTERRUPT , {
819833 threadId : this . threadId ,
820834 turnId,
821835 } )
822836 . then ( ( ) => {
823837 this . cancelNextGoalTurn = false ;
838+ if ( this . nativeGoalTurnId === turnId ) {
839+ this . nativeGoalTurnId = undefined ;
840+ }
824841 } )
825842 . catch ( ( error ) =>
826- this . logger . warn ( "Queued goal turn interrupt failed" , error ) ,
843+ this . logger . warn ( "Native goal turn interrupt failed" , error ) ,
827844 ) ;
828845 }
829846
@@ -1106,6 +1123,7 @@ export class CodexAppServerAgent extends BaseAcpAgent {
11061123
11071124 async closeSession ( ) : Promise < void > {
11081125 this . commandOutputs . clear ( ) ;
1126+ this . nativeGoalTurnId = undefined ;
11091127 this . session . abortController . abort ( ) ;
11101128 this . session . cancelled = true ;
11111129 this . planHandoffCancel ?.( ) ;
@@ -1156,6 +1174,9 @@ export class CodexAppServerAgent extends BaseAcpAgent {
11561174 if ( method === APP_SERVER_NOTIFICATIONS . TURN_STARTED ) {
11571175 // Capture the active turn id (steer precondition / interrupt target).
11581176 const turnId = ( params as { turn ?: { id ?: string } } ) ?. turn ?. id ;
1177+ if ( ! this . turns . isPending && turnId ) {
1178+ this . nativeGoalTurnId = turnId ;
1179+ }
11591180 this . turns . onStarted ( turnId ) ;
11601181 this . interruptQueuedGoalTurn ( turnId ) ;
11611182 }
@@ -1200,6 +1221,9 @@ export class CodexAppServerAgent extends BaseAcpAgent {
12001221 this . commandOutputs . clear ( ) ;
12011222 const turn = ( params as { turn ?: { id ?: string ; status ?: string } } )
12021223 ?. turn ;
1224+ if ( turn ?. id === this . nativeGoalTurnId ) {
1225+ this . nativeGoalTurnId = undefined ;
1226+ }
12031227 // Drop the late completion of an already-interrupted turn (else it cancels the follow-up).
12041228 if ( this . turns . shouldDropCompletion ( turn ?. id ) ) return ;
12051229 void this . finalizeTurn ( mapTurnStopReason ( turn ?. status ) ) ;
0 commit comments