@@ -10,6 +10,7 @@ use crate::outgoing_message::OutgoingMessageSender;
1010
1111use codex_core:: Codex ;
1212use codex_core:: config:: Config as CodexConfig ;
13+ use codex_core:: protocol:: Submission ;
1314use mcp_types:: CallToolRequestParams ;
1415use mcp_types:: CallToolResult ;
1516use mcp_types:: ClientRequest ;
@@ -35,6 +36,7 @@ pub(crate) struct MessageProcessor {
3536 initialized : bool ,
3637 codex_linux_sandbox_exe : Option < PathBuf > ,
3738 session_map : Arc < Mutex < HashMap < Uuid , Arc < Codex > > > > ,
39+ running_requests_id_to_codex_uuid : Arc < Mutex < HashMap < RequestId , Uuid > > > ,
3840}
3941
4042impl MessageProcessor {
@@ -49,6 +51,7 @@ impl MessageProcessor {
4951 initialized : false ,
5052 codex_linux_sandbox_exe,
5153 session_map : Arc :: new ( Mutex :: new ( HashMap :: new ( ) ) ) ,
54+ running_requests_id_to_codex_uuid : Arc :: new ( Mutex :: new ( HashMap :: new ( ) ) ) ,
5255 }
5356 }
5457
@@ -116,7 +119,7 @@ impl MessageProcessor {
116119 }
117120
118121 /// Handle a fire-and-forget JSON-RPC notification.
119- pub ( crate ) fn process_notification ( & mut self , notification : JSONRPCNotification ) {
122+ pub ( crate ) async fn process_notification ( & mut self , notification : JSONRPCNotification ) {
120123 let server_notification = match ServerNotification :: try_from ( notification) {
121124 Ok ( n) => n,
122125 Err ( e) => {
@@ -129,7 +132,7 @@ impl MessageProcessor {
129132 // handler so additional logic can be implemented incrementally.
130133 match server_notification {
131134 ServerNotification :: CancelledNotification ( params) => {
132- self . handle_cancelled_notification ( params) ;
135+ self . handle_cancelled_notification ( params) . await ;
133136 }
134137 ServerNotification :: ProgressNotification ( params) => {
135138 self . handle_progress_notification ( params) ;
@@ -379,6 +382,7 @@ impl MessageProcessor {
379382 // Clone outgoing and session map to move into async task.
380383 let outgoing = self . outgoing . clone ( ) ;
381384 let session_map = self . session_map . clone ( ) ;
385+ let running_requests_id_to_codex_uuid = self . running_requests_id_to_codex_uuid . clone ( ) ;
382386
383387 // Spawn an async task to handle the Codex session so that we do not
384388 // block the synchronous message-processing loop.
@@ -390,6 +394,7 @@ impl MessageProcessor {
390394 config,
391395 outgoing,
392396 session_map,
397+ running_requests_id_to_codex_uuid,
393398 )
394399 . await ;
395400 } ) ;
@@ -464,13 +469,12 @@ impl MessageProcessor {
464469
465470 // Clone outgoing and session map to move into async task.
466471 let outgoing = self . outgoing . clone ( ) ;
472+ let running_requests_id_to_codex_uuid = self . running_requests_id_to_codex_uuid . clone ( ) ;
467473
468- // Spawn an async task to handle the Codex session so that we do not
469- // block the synchronous message-processing loop.
470- task:: spawn ( async move {
474+ let codex = {
471475 let session_map = session_map_mutex. lock ( ) . await ;
472- let codex = match session_map. get ( & session_id) {
473- Some ( codex ) => codex ,
476+ match session_map. get ( & session_id) . cloned ( ) {
477+ Some ( c ) => c ,
474478 None => {
475479 tracing:: warn!( "Session not found for session_id: {session_id}" ) ;
476480 let result = CallToolResult {
@@ -482,21 +486,32 @@ impl MessageProcessor {
482486 is_error : Some ( true ) ,
483487 structured_content : None ,
484488 } ;
485- // unwrap_or_default is fine here because we know the result is valid JSON
486489 outgoing
487490 . send_response ( request_id, serde_json:: to_value ( result) . unwrap_or_default ( ) )
488491 . await ;
489492 return ;
490493 }
491- } ;
494+ }
495+ } ;
492496
493- crate :: codex_tool_runner:: run_codex_tool_session_reply (
494- codex. clone ( ) ,
495- outgoing,
496- request_id,
497- prompt. clone ( ) ,
498- )
499- . await ;
497+ // Spawn the long-running reply handler.
498+ tokio:: spawn ( {
499+ let codex = codex. clone ( ) ;
500+ let outgoing = outgoing. clone ( ) ;
501+ let prompt = prompt. clone ( ) ;
502+ let running_requests_id_to_codex_uuid = running_requests_id_to_codex_uuid. clone ( ) ;
503+
504+ async move {
505+ crate :: codex_tool_runner:: run_codex_tool_session_reply (
506+ codex,
507+ outgoing,
508+ request_id,
509+ prompt,
510+ running_requests_id_to_codex_uuid,
511+ session_id,
512+ )
513+ . await ;
514+ }
500515 } ) ;
501516 }
502517
@@ -518,11 +533,58 @@ impl MessageProcessor {
518533 // Notification handlers
519534 // ---------------------------------------------------------------------
520535
521- fn handle_cancelled_notification (
536+ async fn handle_cancelled_notification (
522537 & self ,
523538 params : <mcp_types:: CancelledNotification as mcp_types:: ModelContextProtocolNotification >:: Params ,
524539 ) {
525- tracing:: info!( "notifications/cancelled -> params: {:?}" , params) ;
540+ let request_id = params. request_id ;
541+ // Create a stable string form early for logging and submission id.
542+ let request_id_string = match & request_id {
543+ RequestId :: String ( s) => s. clone ( ) ,
544+ RequestId :: Integer ( i) => i. to_string ( ) ,
545+ } ;
546+
547+ // Obtain the session_id while holding the first lock, then release.
548+ let session_id = {
549+ let map_guard = self . running_requests_id_to_codex_uuid . lock ( ) . await ;
550+ match map_guard. get ( & request_id) {
551+ Some ( id) => * id, // Uuid is Copy
552+ None => {
553+ tracing:: warn!( "Session not found for request_id: {}" , request_id_string) ;
554+ return ;
555+ }
556+ }
557+ } ;
558+ tracing:: info!( "session_id: {session_id}" ) ;
559+
560+ // Obtain the Codex Arc while holding the session_map lock, then release.
561+ let codex_arc = {
562+ let sessions_guard = self . session_map . lock ( ) . await ;
563+ match sessions_guard. get ( & session_id) {
564+ Some ( codex) => Arc :: clone ( codex) ,
565+ None => {
566+ tracing:: warn!( "Session not found for session_id: {session_id}" ) ;
567+ return ;
568+ }
569+ }
570+ } ;
571+
572+ // Submit interrupt to Codex.
573+ let err = codex_arc
574+ . submit_with_id ( Submission {
575+ id : request_id_string,
576+ op : codex_core:: protocol:: Op :: Interrupt ,
577+ } )
578+ . await ;
579+ if let Err ( e) = err {
580+ tracing:: error!( "Failed to submit interrupt to Codex: {e}" ) ;
581+ return ;
582+ }
583+ // unregister the id so we don't keep it in the map
584+ self . running_requests_id_to_codex_uuid
585+ . lock ( )
586+ . await
587+ . remove ( & request_id) ;
526588 }
527589
528590 fn handle_progress_notification (
0 commit comments