@@ -18,6 +18,7 @@ use crate::util::errors::{BitFunError, BitFunResult};
1818use log:: { debug, error, info, warn} ;
1919use std:: sync:: Arc ;
2020use std:: sync:: OnceLock ;
21+ use tokio:: sync:: mpsc;
2122use tokio_util:: sync:: CancellationToken ;
2223
2324/// Subagent execution result
@@ -65,13 +66,26 @@ impl Drop for CancelTokenGuard {
6566 }
6667}
6768
69+ /// Outcome of a completed dialog turn, used to notify DialogScheduler
70+ #[ derive( Debug , Clone ) ]
71+ pub enum TurnOutcome {
72+ /// Turn completed normally
73+ Completed ,
74+ /// Turn was cancelled by user
75+ Cancelled ,
76+ /// Turn failed with an error
77+ Failed ,
78+ }
79+
6880/// Conversation coordinator
6981pub struct ConversationCoordinator {
7082 session_manager : Arc < SessionManager > ,
7183 execution_engine : Arc < ExecutionEngine > ,
7284 tool_pipeline : Arc < ToolPipeline > ,
7385 event_queue : Arc < EventQueue > ,
7486 event_router : Arc < EventRouter > ,
87+ /// Notifies DialogScheduler of turn outcomes; injected after construction
88+ scheduler_notify_tx : OnceLock < mpsc:: Sender < ( String , TurnOutcome ) > > ,
7589}
7690
7791impl ConversationCoordinator {
@@ -88,17 +102,25 @@ impl ConversationCoordinator {
88102 tool_pipeline,
89103 event_queue,
90104 event_router,
105+ scheduler_notify_tx : OnceLock :: new ( ) ,
91106 }
92107 }
93108
109+ /// Inject the DialogScheduler notification channel after construction.
110+ /// Called once during app initialization after the scheduler is created.
111+ pub fn set_scheduler_notifier ( & self , tx : mpsc:: Sender < ( String , TurnOutcome ) > ) {
112+ let _ = self . scheduler_notify_tx . set ( tx) ;
113+ }
114+
94115 /// Create a new session
95116 pub async fn create_session (
96117 & self ,
97118 session_name : String ,
98119 agent_type : String ,
99120 config : SessionConfig ,
100121 ) -> BitFunResult < Session > {
101- self . create_session_with_workspace ( None , session_name, agent_type, config, None ) . await
122+ self . create_session_with_workspace ( None , session_name, agent_type, config, None )
123+ . await
102124 }
103125
104126 /// Create a new session with optional session ID
@@ -109,7 +131,8 @@ impl ConversationCoordinator {
109131 agent_type : String ,
110132 config : SessionConfig ,
111133 ) -> BitFunResult < Session > {
112- self . create_session_with_workspace ( session_id, session_name, agent_type, config, None ) . await
134+ self . create_session_with_workspace ( session_id, session_name, agent_type, config, None )
135+ . await
113136 }
114137
115138 /// Create a new session with optional session ID and workspace binding.
@@ -561,6 +584,7 @@ impl ConversationCoordinator {
561584 let turn_id_clone = turn_id. clone ( ) ;
562585 let session_workspace_path = session. config . workspace_path . clone ( ) ;
563586 let effective_agent_type_clone = effective_agent_type. clone ( ) ;
587+ let scheduler_notify_tx = self . scheduler_notify_tx . get ( ) . cloned ( ) ;
564588
565589 tokio:: spawn ( async move {
566590 // Note: Don't check cancellation here as cancel token hasn't been created yet
@@ -621,6 +645,10 @@ impl ConversationCoordinator {
621645 let _ = session_manager
622646 . update_session_state ( & session_id_clone, SessionState :: Idle )
623647 . await ;
648+
649+ if let Some ( tx) = & scheduler_notify_tx {
650+ let _ = tx. try_send ( ( session_id_clone. clone ( ) , TurnOutcome :: Completed ) ) ;
651+ }
624652 }
625653 Err ( e) => {
626654 let is_cancellation = matches ! ( & e, BitFunError :: Cancelled ( _) ) ;
@@ -632,6 +660,10 @@ impl ConversationCoordinator {
632660 let _ = session_manager
633661 . update_session_state ( & session_id_clone, SessionState :: Idle )
634662 . await ;
663+
664+ if let Some ( tx) = & scheduler_notify_tx {
665+ let _ = tx. try_send ( ( session_id_clone. clone ( ) , TurnOutcome :: Cancelled ) ) ;
666+ }
635667 } else {
636668 error ! ( "Dialog turn execution failed: {}" , e) ;
637669
@@ -659,6 +691,10 @@ impl ConversationCoordinator {
659691 } ,
660692 )
661693 . await ;
694+
695+ if let Some ( tx) = & scheduler_notify_tx {
696+ let _ = tx. try_send ( ( session_id_clone. clone ( ) , TurnOutcome :: Failed ) ) ;
697+ }
662698 }
663699 }
664700 }
@@ -765,7 +801,9 @@ impl ConversationCoordinator {
765801 limit : usize ,
766802 before_message_id : Option < & str > ,
767803 ) -> BitFunResult < ( Vec < Message > , bool ) > {
768- self . session_manager . get_messages_paginated ( session_id, limit, before_message_id) . await
804+ self . session_manager
805+ . get_messages_paginated ( session_id, limit, before_message_id)
806+ . await
769807 }
770808
771809 /// Subscribe to internal events
@@ -830,7 +868,9 @@ impl ConversationCoordinator {
830868 if let Some ( token) = cancel_token {
831869 if token. is_cancelled ( ) {
832870 debug ! ( "Subagent task cancelled before execution" ) ;
833- return Err ( BitFunError :: Cancelled ( "Subagent task has been cancelled" . to_string ( ) ) ) ;
871+ return Err ( BitFunError :: Cancelled (
872+ "Subagent task has been cancelled" . to_string ( ) ,
873+ ) ) ;
834874 }
835875 }
836876
@@ -851,7 +891,9 @@ impl ConversationCoordinator {
851891 if token. is_cancelled ( ) {
852892 debug ! ( "Subagent task cancelled before AI call, cleaning up resources" ) ;
853893 let _ = self . cleanup_subagent_resources ( & session. session_id ) . await ;
854- return Err ( BitFunError :: Cancelled ( "Subagent task has been cancelled" . to_string ( ) ) ) ;
894+ return Err ( BitFunError :: Cancelled (
895+ "Subagent task has been cancelled" . to_string ( ) ,
896+ ) ) ;
855897 }
856898 }
857899
0 commit comments