Skip to content

Commit d68155a

Browse files
committed
feat(code): forward confirmation events to streaming channel
1 parent 384be4c commit d68155a

1 file changed

Lines changed: 51 additions & 2 deletions

File tree

core/src/agent.rs

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)