@@ -77,6 +77,8 @@ pub(crate) struct CodexMessageProcessor {
7777 codex_linux_sandbox_exe : Option < PathBuf > ,
7878 conversation_listeners : HashMap < Uuid , oneshot:: Sender < ( ) > > ,
7979 active_login : Arc < Mutex < Option < ActiveLogin > > > ,
80+ // Queue of pending interrupt requests per conversation. We reply when TurnAborted arrives.
81+ pending_interrupts : Arc < Mutex < HashMap < Uuid , Vec < RequestId > > > > ,
8082}
8183
8284impl CodexMessageProcessor {
@@ -91,6 +93,7 @@ impl CodexMessageProcessor {
9193 codex_linux_sandbox_exe,
9294 conversation_listeners : HashMap :: new ( ) ,
9395 active_login : Arc :: new ( Mutex :: new ( None ) ) ,
96+ pending_interrupts : Arc :: new ( Mutex :: new ( HashMap :: new ( ) ) ) ,
9497 }
9598 }
9699
@@ -399,13 +402,14 @@ impl CodexMessageProcessor {
399402 return ;
400403 } ;
401404
402- let _ = conversation. submit ( Op :: Interrupt ) . await ;
405+ // Record the pending interrupt so we can reply when TurnAborted arrives.
406+ {
407+ let mut map = self . pending_interrupts . lock ( ) . await ;
408+ map. entry ( conversation_id. 0 ) . or_default ( ) . push ( request_id) ;
409+ }
403410
404- // Apparently CodexConversation does not send an ack for Op::Interrupt,
405- // so we can reply to the request right away.
406- self . outgoing
407- . send_response ( request_id, InterruptConversationResponse { } )
408- . await ;
411+ // Submit the interrupt; we'll respond upon TurnAborted.
412+ let _ = conversation. submit ( Op :: Interrupt ) . await ;
409413 }
410414
411415 async fn add_conversation_listener (
@@ -433,6 +437,7 @@ impl CodexMessageProcessor {
433437 self . conversation_listeners
434438 . insert ( subscription_id, cancel_tx) ;
435439 let outgoing_for_task = self . outgoing . clone ( ) ;
440+ let pending_interrupts = self . pending_interrupts . clone ( ) ;
436441 tokio:: spawn ( async move {
437442 loop {
438443 tokio:: select! {
@@ -473,7 +478,7 @@ impl CodexMessageProcessor {
473478 } )
474479 . await ;
475480
476- apply_bespoke_event_handling( event, conversation_id, conversation. clone( ) , outgoing_for_task. clone( ) ) . await ;
481+ apply_bespoke_event_handling( event. clone ( ) , conversation_id, conversation. clone( ) , outgoing_for_task. clone ( ) , pending_interrupts . clone( ) ) . await ;
477482 }
478483 }
479484 }
@@ -512,6 +517,7 @@ async fn apply_bespoke_event_handling(
512517 conversation_id : ConversationId ,
513518 conversation : Arc < CodexConversation > ,
514519 outgoing : Arc < OutgoingMessageSender > ,
520+ pending_interrupts : Arc < Mutex < HashMap < Uuid , Vec < RequestId > > > > ,
515521) {
516522 let Event { id : event_id, msg } = event;
517523 match msg {
@@ -560,6 +566,22 @@ async fn apply_bespoke_event_handling(
560566 on_exec_approval_response ( event_id, rx, conversation) . await ;
561567 } ) ;
562568 }
569+ // If this is a TurnAborted, reply to any pending interrupt requests.
570+ EventMsg :: TurnAborted ( turn_aborted_event) => {
571+ let pending = {
572+ let mut map = pending_interrupts. lock ( ) . await ;
573+ map. remove ( & conversation_id. 0 ) . unwrap_or_default ( )
574+ } ;
575+ if !pending. is_empty ( ) {
576+ let response = InterruptConversationResponse {
577+ abort_reason : turn_aborted_event. reason ,
578+ } ;
579+ for rid in pending {
580+ outgoing. send_response ( rid, response. clone ( ) ) . await ;
581+ }
582+ }
583+ }
584+
563585 _ => { }
564586 }
565587}
0 commit comments