@@ -2024,12 +2024,36 @@ impl AgentLoop {
20242024 )
20252025 . await ;
20262026
2027+ // Forward ConfirmationRequired to the streaming event channel
2028+ // so external consumers (e.g. SafeClaw engine) can relay it
2029+ // to the browser UI.
2030+ if let Some ( tx) = & event_tx {
2031+ tx. send ( AgentEvent :: ConfirmationRequired {
2032+ tool_id : tool_call. id . clone ( ) ,
2033+ tool_name : tool_call. name . clone ( ) ,
2034+ args : tool_call. args . clone ( ) ,
2035+ timeout_ms,
2036+ } )
2037+ . await
2038+ . ok ( ) ;
2039+ }
2040+
20272041 // Wait for confirmation with timeout
20282042 let confirmation_result =
20292043 tokio:: time:: timeout ( Duration :: from_millis ( timeout_ms) , rx) . await ;
20302044
20312045 match confirmation_result {
20322046 Ok ( Ok ( response) ) => {
2047+ // Forward ConfirmationReceived
2048+ if let Some ( tx) = & event_tx {
2049+ tx. send ( AgentEvent :: ConfirmationReceived {
2050+ tool_id : tool_call. id . clone ( ) ,
2051+ approved : response. approved ,
2052+ reason : response. reason . clone ( ) ,
2053+ } )
2054+ . await
2055+ . ok ( ) ;
2056+ }
20332057 if response. approved {
20342058 let stream_ctx = self . streaming_tool_context (
20352059 & event_tx,
@@ -2047,14 +2071,24 @@ impl AgentLoop {
20472071 Self :: tool_result_to_tuple ( result)
20482072 } else {
20492073 let rejection_msg = format ! (
2050- "Tool '{}' execution was rejected by user. Reason: {}" ,
2074+ "Tool '{}' execution was REJECTED by the user. Reason: {}. \
2075+ DO NOT retry this tool call unless the user explicitly asks you to.",
20512076 tool_call. name,
20522077 response. reason. unwrap_or_else( || "No reason provided" . to_string( ) )
20532078 ) ;
20542079 ( rejection_msg, 1 , true , None , Vec :: new ( ) )
20552080 }
20562081 }
20572082 Ok ( Err ( _) ) => {
2083+ // Forward ConfirmationTimeout (channel closed = effectively timed out)
2084+ if let Some ( tx) = & event_tx {
2085+ tx. send ( AgentEvent :: ConfirmationTimeout {
2086+ tool_id : tool_call. id . clone ( ) ,
2087+ action_taken : "rejected" . to_string ( ) ,
2088+ } )
2089+ . await
2090+ . ok ( ) ;
2091+ }
20582092 let msg = format ! (
20592093 "Tool '{}' confirmation failed: confirmation channel closed" ,
20602094 tool_call. name
@@ -2064,10 +2098,25 @@ impl AgentLoop {
20642098 Err ( _) => {
20652099 cm. check_timeouts ( ) . await ;
20662100
2101+ // Forward ConfirmationTimeout
2102+ if let Some ( tx) = & event_tx {
2103+ tx. send ( AgentEvent :: ConfirmationTimeout {
2104+ tool_id : tool_call. id . clone ( ) ,
2105+ action_taken : match timeout_action {
2106+ crate :: hitl:: TimeoutAction :: Reject => "rejected" . to_string ( ) ,
2107+ crate :: hitl:: TimeoutAction :: AutoApprove => "auto_approved" . to_string ( ) ,
2108+ } ,
2109+ } )
2110+ . await
2111+ . ok ( ) ;
2112+ }
2113+
20672114 match timeout_action {
20682115 crate :: hitl:: TimeoutAction :: Reject => {
20692116 let msg = format ! (
2070- "Tool '{}' execution timed out waiting for confirmation ({}ms). Execution rejected." ,
2117+ "Tool '{}' execution was REJECTED: user confirmation timed out after {}ms. \
2118+ DO NOT retry this tool call — the user did not approve it. \
2119+ Inform the user that the operation requires their approval and ask them to try again.",
20712120 tool_call. name, timeout_ms
20722121 ) ;
20732122 ( msg, 1 , true , None , Vec :: new ( ) )
0 commit comments