steer_tui/tui/handlers/
approval.rs

1use crate::error::Error;
2use crate::error::Result;
3use crate::tui::Tui;
4use ratatui::crossterm::event::{KeyCode, KeyEvent};
5use steer_core::app::{AppCommand, command::ApprovalType};
6use steer_core::error::Error as CoreError;
7use steer_tools::tools::BASH_TOOL_NAME;
8use steer_tools::tools::bash::BashParams;
9use tracing::debug;
10
11impl Tui {
12    pub async fn handle_approval_mode(&mut self, key: KeyEvent) -> Result<bool> {
13        if let Some(tool_call) = self.current_tool_approval.take() {
14            match key.code {
15                KeyCode::Char('y') | KeyCode::Char('Y') => {
16                    // Approve once
17                    self.client
18                        .send_command(AppCommand::HandleToolResponse {
19                            id: tool_call.id,
20                            approval: ApprovalType::Once,
21                        })
22                        .await?;
23                    self.input_mode = self.default_input_mode();
24                }
25                KeyCode::Char('a') | KeyCode::Char('A') => {
26                    debug!(target: "handle_approval_mode", "Approving tool call with ID '{}' and name '{}'", tool_call.id, tool_call.name);
27                    if tool_call.name == BASH_TOOL_NAME {
28                        debug!(target: "handle_approval_mode", "(Always) Approving bash command with ID '{}'", tool_call.id);
29                        // For bash commands, 'A' approves this specific command pattern
30                        let bash_params: BashParams =
31                            serde_json::from_value(tool_call.parameters.clone()).map_err(|e| {
32                                Error::Core(CoreError::Tool(
33                                    steer_tools::ToolError::InvalidParams(
34                                        "bash".to_string(),
35                                        e.to_string(),
36                                    )
37                                    .into(),
38                                ))
39                            })?;
40                        // Approve with the bash pattern payload
41                        self.client
42                            .send_command(AppCommand::HandleToolResponse {
43                                id: tool_call.id,
44                                approval: ApprovalType::AlwaysBashPattern(
45                                    bash_params.command.clone(),
46                                ),
47                            })
48                            .await?;
49                    } else {
50                        // For non-bash tools, 'A' approves always
51                        self.client
52                            .send_command(AppCommand::HandleToolResponse {
53                                id: tool_call.id,
54                                approval: ApprovalType::AlwaysTool,
55                            })
56                            .await?;
57                    }
58                    self.input_mode = self.default_input_mode();
59                }
60                KeyCode::Char('l') | KeyCode::Char('L') => {
61                    if tool_call.name == BASH_TOOL_NAME {
62                        self.client
63                            .send_command(AppCommand::HandleToolResponse {
64                                id: tool_call.id,
65                                approval: ApprovalType::AlwaysTool,
66                            })
67                            .await?;
68                        self.input_mode = self.default_input_mode();
69                    } else {
70                        // Put it back if not a bash command
71                        self.current_tool_approval = Some(tool_call);
72                    }
73                }
74                KeyCode::Char('n') | KeyCode::Char('N') | KeyCode::Esc => {
75                    // Reject
76                    self.client
77                        .send_command(AppCommand::HandleToolResponse {
78                            id: tool_call.id,
79                            approval: ApprovalType::Denied,
80                        })
81                        .await?;
82                    self.input_mode = self.default_input_mode();
83                }
84                _ => {
85                    // Put it back if not handled
86                    self.current_tool_approval = Some(tool_call);
87                }
88            }
89        } else {
90            // No approval pending, return to normal
91            self.input_mode = self.default_input_mode();
92        }
93        Ok(false)
94    }
95}