@@ -487,6 +487,13 @@ fn should_bridge_agent_event(event: &AgentEvent) -> bool {
487487 AgentEvent :: SubagentStart { .. }
488488 | AgentEvent :: SubagentProgress { .. }
489489 | AgentEvent :: SubagentEnd { .. }
490+ // A delegated child that inherits the parent confirmation provider
491+ // waits for the parent UI to answer these events. Filtering them
492+ // here leaves the child blocked even though its confirmation is
493+ // registered on the shared provider.
494+ | AgentEvent :: ConfirmationRequired { .. }
495+ | AgentEvent :: ConfirmationReceived { .. }
496+ | AgentEvent :: ConfirmationTimeout { .. }
490497 )
491498}
492499
@@ -761,6 +768,72 @@ mod tests {
761768 ) ) ) ;
762769 }
763770
771+ #[ tokio:: test]
772+ async fn forwarder_exposes_delegated_confirmation_lifecycle ( ) {
773+ let run_store = Arc :: new ( crate :: run:: InMemoryRunStore :: new ( ) ) ;
774+ let run = run_store. create_run ( "session-1" , "prompt" ) . await ;
775+ let sink = RuntimeEventSink :: new ( RuntimeEventSinkConfig {
776+ run_store : Arc :: clone ( & run_store) ,
777+ run_id : run. id . clone ( ) ,
778+ session_id : "session-1" . to_string ( ) ,
779+ hook_executor : None ,
780+ security_provider : None ,
781+ persistence_state : persistence_state ( ) ,
782+ active_tools : active_tools ( ) ,
783+ subagent_tasks : Arc :: new (
784+ crate :: subagent_task_tracker:: InMemorySubagentTaskTracker :: new ( ) ,
785+ ) ,
786+ } ) ;
787+ let ( runtime_tx, runtime_rx) = mpsc:: channel ( 4 ) ;
788+ let ( stream_tx, mut stream_rx) = mpsc:: channel ( 4 ) ;
789+ let ( agent_tx, barrier, agent_rx) = run_agent_event_channel ( 8 ) ;
790+ let forwarder = sink. spawn_forwarder ( runtime_rx, stream_tx, Some ( agent_rx) ) ;
791+
792+ let expected = vec ! [
793+ AgentEvent :: ConfirmationRequired {
794+ tool_id: "child-tool-1" . to_string( ) ,
795+ tool_name: "install" . to_string( ) ,
796+ args: serde_json:: json!( { "component" : "browser" } ) ,
797+ timeout_ms: 30_000 ,
798+ } ,
799+ AgentEvent :: ConfirmationReceived {
800+ tool_id: "child-tool-1" . to_string( ) ,
801+ approved: true ,
802+ reason: Some ( "approved by parent" . to_string( ) ) ,
803+ } ,
804+ AgentEvent :: ConfirmationTimeout {
805+ tool_id: "child-tool-2" . to_string( ) ,
806+ action_taken: "rejected" . to_string( ) ,
807+ } ,
808+ ] ;
809+ for event in & expected {
810+ agent_tx. send ( event. clone ( ) ) . unwrap ( ) ;
811+ }
812+ barrier. flush ( ) . await ;
813+ drop ( agent_tx) ;
814+ drop ( runtime_tx) ;
815+ forwarder. await . unwrap ( ) ;
816+
817+ let mut streamed = Vec :: new ( ) ;
818+ while let Some ( event) = stream_rx. recv ( ) . await {
819+ streamed. push ( event) ;
820+ }
821+ assert_eq ! (
822+ serde_json:: to_value( & streamed) . unwrap( ) ,
823+ serde_json:: to_value( & expected) . unwrap( )
824+ ) ;
825+ let persisted = run_store
826+ . events ( & run. id )
827+ . await
828+ . into_iter ( )
829+ . map ( |record| record. event )
830+ . collect :: < Vec < _ > > ( ) ;
831+ assert_eq ! (
832+ serde_json:: to_value( & persisted) . unwrap( ) ,
833+ serde_json:: to_value( & expected) . unwrap( )
834+ ) ;
835+ }
836+
764837 #[ tokio:: test]
765838 async fn forwarder_exposes_and_persists_only_sanitized_events ( ) {
766839 let run_store = Arc :: new ( crate :: run:: InMemoryRunStore :: new ( ) ) ;
0 commit comments