From bfb0ccc9b4d25b220a6d63aafe6ef0de8eec184c Mon Sep 17 00:00:00 2001 From: jayzhu Date: Thu, 28 May 2026 23:09:03 +0800 Subject: [PATCH] fix: carry tool input in ApprovalRequired event so approval dialog always shows params The ApprovalRequest params_display() relied on pending_tool_uses, which gets drained by MessageComplete before ApprovalRequired arrives in the TUI event loop. When the model streaming response contained a text block, ContentBlockStop(Text) set pending_message_complete=true, triggering the drain and leaving the approval dialog with empty {}. Fix: add input: Value to Event::ApprovalRequired and populate it from tool_input in the engine tool execution loop. The TUI now reads params directly from the event instead of searching pending_tool_uses. --- crates/tui/src/core/engine/turn_loop.rs | 1 + crates/tui/src/core/events.rs | 3 +++ crates/tui/src/runtime_threads.rs | 4 ++++ crates/tui/src/tui/ui.rs | 8 ++------ 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/crates/tui/src/core/engine/turn_loop.rs b/crates/tui/src/core/engine/turn_loop.rs index f9df1795..9dc2a873 100644 --- a/crates/tui/src/core/engine/turn_loop.rs +++ b/crates/tui/src/core/engine/turn_loop.rs @@ -1696,6 +1696,7 @@ impl Engine { .send(Event::ApprovalRequired { id: tool_id.clone(), tool_name: tool_name.clone(), + input: tool_input.clone(), description: plan.approval_description.clone(), approval_key, approval_grouping_key, diff --git a/crates/tui/src/core/events.rs b/crates/tui/src/core/events.rs index b02ba2f9..f598b722 100644 --- a/crates/tui/src/core/events.rs +++ b/crates/tui/src/core/events.rs @@ -226,6 +226,9 @@ pub enum Event { id: String, tool_name: String, description: String, + /// Tool parameters for approval display. Carried on the event so the + /// TUI does not need to reconstruct them from `pending_tool_uses`. + input: Value, /// Exact-argument fingerprint, used to scope *denials* (#1617). approval_key: String, /// Lossy / arity-aware fingerprint, used to scope *approvals* so an diff --git a/crates/tui/src/runtime_threads.rs b/crates/tui/src/runtime_threads.rs index 25196f04..6dc9c1c1 100644 --- a/crates/tui/src/runtime_threads.rs +++ b/crates/tui/src/runtime_threads.rs @@ -4172,6 +4172,7 @@ mod tests { id: "tool_stale".to_string(), tool_name: "exec_command".to_string(), description: "stale approval".to_string(), + input: serde_json::json!({}), }) .await?; @@ -4245,6 +4246,7 @@ mod tests { id: "tool_external_allow".to_string(), tool_name: "exec_command".to_string(), description: "external allow".to_string(), + input: serde_json::json!({}), }) .await?; @@ -4322,6 +4324,7 @@ mod tests { id: "tool_external_deny".to_string(), tool_name: "exec_command".to_string(), description: "external deny".to_string(), + input: serde_json::json!({}), }) .await?; @@ -4508,6 +4511,7 @@ mod tests { id: "tool_remember".to_string(), tool_name: "exec_command".to_string(), description: "remember=true".to_string(), + input: serde_json::json!({}), }) .await?; diff --git a/crates/tui/src/tui/ui.rs b/crates/tui/src/tui/ui.rs index fb89de61..64e35f66 100644 --- a/crates/tui/src/tui/ui.rs +++ b/crates/tui/src/tui/ui.rs @@ -1893,6 +1893,7 @@ async fn run_event_loop( id, tool_name, description, + input, approval_key, approval_grouping_key, } => { @@ -1937,12 +1938,7 @@ async fn run_event_loop( app.status_message = Some(format!("Blocked tool '{tool_name}' (approval_mode=never)")); } else { - let tool_input = app - .pending_tool_uses - .iter() - .find(|(tool_id, _, _)| tool_id == &id) - .map(|(_, _, input)| input.clone()) - .unwrap_or_else(|| serde_json::json!({})); + let tool_input = input; if tool_name == "apply_patch" { maybe_add_patch_preview(app, &tool_input);