approval ui (#1933)
Asking for approval: <img width="269" height="41" alt="image" src="https://github.com/user-attachments/assets/b9ced569-3297-4dae-9ce7-0b015c9e14ea" /> Allow: <img width="400" height="31" alt="image" src="https://github.com/user-attachments/assets/92056b22-efda-4d49-854d-e2943d5fcf17" /> Reject: <img width="372" height="30" alt="image" src="https://github.com/user-attachments/assets/be9530a9-7d41-4800-bb42-abb9a24fc3ea" /> Always Approve: <img width="410" height="36" alt="image" src="https://github.com/user-attachments/assets/acf871ba-4c26-4501-b303-7956d0151754" />
This commit is contained in:
@@ -45,7 +45,6 @@ use crate::bottom_pane::BottomPane;
|
|||||||
use crate::bottom_pane::BottomPaneParams;
|
use crate::bottom_pane::BottomPaneParams;
|
||||||
use crate::bottom_pane::CancellationEvent;
|
use crate::bottom_pane::CancellationEvent;
|
||||||
use crate::bottom_pane::InputResult;
|
use crate::bottom_pane::InputResult;
|
||||||
use crate::exec_command::strip_bash_lc_and_escape;
|
|
||||||
use crate::history_cell::CommandOutput;
|
use crate::history_cell::CommandOutput;
|
||||||
use crate::history_cell::HistoryCell;
|
use crate::history_cell::HistoryCell;
|
||||||
use crate::history_cell::PatchEventType;
|
use crate::history_cell::PatchEventType;
|
||||||
@@ -393,17 +392,6 @@ impl ChatWidget<'_> {
|
|||||||
reason,
|
reason,
|
||||||
}) => {
|
}) => {
|
||||||
self.finalize_active_stream();
|
self.finalize_active_stream();
|
||||||
// Log a background summary immediately so the history is chronological.
|
|
||||||
let cmdline = strip_bash_lc_and_escape(&command);
|
|
||||||
let text = format!(
|
|
||||||
"command requires approval:\n$ {cmdline}{reason}",
|
|
||||||
reason = reason
|
|
||||||
.as_ref()
|
|
||||||
.map(|r| format!("\n{r}"))
|
|
||||||
.unwrap_or_default()
|
|
||||||
);
|
|
||||||
self.add_to_history(HistoryCell::new_background_event(text));
|
|
||||||
|
|
||||||
let request = ApprovalRequest::Exec {
|
let request = ApprovalRequest::Exec {
|
||||||
id,
|
id,
|
||||||
command,
|
command,
|
||||||
|
|||||||
@@ -435,7 +435,8 @@ impl HistoryCell {
|
|||||||
view: TextBlock::new(lines),
|
view: TextBlock::new(lines),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// allow dead code for now. maybe we'll use it again.
|
||||||
|
#[allow(dead_code)]
|
||||||
pub(crate) fn new_background_event(message: String) -> Self {
|
pub(crate) fn new_background_event(message: String) -> Self {
|
||||||
let mut lines: Vec<Line<'static>> = Vec::new();
|
let mut lines: Vec<Line<'static>> = Vec::new();
|
||||||
lines.push(Line::from("event".dim()));
|
lines.push(Line::from("event".dim()));
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ use ratatui::widgets::Wrap;
|
|||||||
|
|
||||||
use crate::app_event::AppEvent;
|
use crate::app_event::AppEvent;
|
||||||
use crate::app_event_sender::AppEventSender;
|
use crate::app_event_sender::AppEventSender;
|
||||||
use crate::exec_command::relativize_to_home;
|
|
||||||
use crate::exec_command::strip_bash_lc_and_escape;
|
use crate::exec_command::strip_bash_lc_and_escape;
|
||||||
|
|
||||||
/// Request coming from the agent that needs user approval.
|
/// Request coming from the agent that needs user approval.
|
||||||
@@ -36,6 +35,7 @@ pub(crate) enum ApprovalRequest {
|
|||||||
Exec {
|
Exec {
|
||||||
id: String,
|
id: String,
|
||||||
command: Vec<String>,
|
command: Vec<String>,
|
||||||
|
#[allow(dead_code)]
|
||||||
cwd: PathBuf,
|
cwd: PathBuf,
|
||||||
reason: Option<String>,
|
reason: Option<String>,
|
||||||
},
|
},
|
||||||
@@ -115,21 +115,18 @@ impl UserApprovalWidget<'_> {
|
|||||||
pub(crate) fn new(approval_request: ApprovalRequest, app_event_tx: AppEventSender) -> Self {
|
pub(crate) fn new(approval_request: ApprovalRequest, app_event_tx: AppEventSender) -> Self {
|
||||||
let confirmation_prompt = match &approval_request {
|
let confirmation_prompt = match &approval_request {
|
||||||
ApprovalRequest::Exec {
|
ApprovalRequest::Exec {
|
||||||
command,
|
command, reason, ..
|
||||||
cwd,
|
|
||||||
reason,
|
|
||||||
..
|
|
||||||
} => {
|
} => {
|
||||||
let cmd = strip_bash_lc_and_escape(command);
|
let cmd = strip_bash_lc_and_escape(command);
|
||||||
// Maybe try to relativize to the cwd of this process first?
|
// Present a single-line summary without cwd: "codex wants to run: <cmd>"
|
||||||
// Will make cwd_str shorter in the common case.
|
let mut cmd_span: Span = cmd.clone().into();
|
||||||
let cwd_str = match relativize_to_home(cwd) {
|
cmd_span.style = cmd_span.style.add_modifier(Modifier::DIM);
|
||||||
Some(rel) => format!("~/{}", rel.display()),
|
|
||||||
None => cwd.display().to_string(),
|
|
||||||
};
|
|
||||||
let mut contents: Vec<Line> = vec![
|
let mut contents: Vec<Line> = vec![
|
||||||
Line::from(vec!["codex".bold().magenta(), " wants to run:".into()]),
|
Line::from(vec![
|
||||||
Line::from(vec![cwd_str.dim(), "$".into(), format!(" {cmd}").into()]),
|
"? ".fg(Color::Blue),
|
||||||
|
"Codex wants to run ".bold(),
|
||||||
|
cmd_span,
|
||||||
|
]),
|
||||||
Line::from(""),
|
Line::from(""),
|
||||||
];
|
];
|
||||||
if let Some(reason) = reason {
|
if let Some(reason) = reason {
|
||||||
@@ -243,9 +240,52 @@ impl UserApprovalWidget<'_> {
|
|||||||
match &self.approval_request {
|
match &self.approval_request {
|
||||||
ApprovalRequest::Exec { command, .. } => {
|
ApprovalRequest::Exec { command, .. } => {
|
||||||
let cmd = strip_bash_lc_and_escape(command);
|
let cmd = strip_bash_lc_and_escape(command);
|
||||||
lines.push(Line::from("approval decision"));
|
let mut cmd_span: Span = cmd.clone().into();
|
||||||
lines.push(Line::from(format!("$ {cmd}")));
|
cmd_span.style = cmd_span.style.add_modifier(Modifier::DIM);
|
||||||
lines.push(Line::from(format!("decision: {decision:?}")));
|
|
||||||
|
// Result line based on decision.
|
||||||
|
match decision {
|
||||||
|
ReviewDecision::Approved => {
|
||||||
|
lines.push(Line::from(vec![
|
||||||
|
"✓ ".fg(Color::Green),
|
||||||
|
"You ".into(),
|
||||||
|
"approved".bold(),
|
||||||
|
" codex to run ".into(),
|
||||||
|
cmd_span,
|
||||||
|
" ".into(),
|
||||||
|
"this time".bold(),
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
ReviewDecision::ApprovedForSession => {
|
||||||
|
lines.push(Line::from(vec![
|
||||||
|
"✓ ".fg(Color::Green),
|
||||||
|
"You ".into(),
|
||||||
|
"approved".bold(),
|
||||||
|
" codex to run ".into(),
|
||||||
|
cmd_span,
|
||||||
|
" ".into(),
|
||||||
|
"every time this session".bold(),
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
ReviewDecision::Denied => {
|
||||||
|
lines.push(Line::from(vec![
|
||||||
|
"✗ ".fg(Color::Red),
|
||||||
|
"You ".into(),
|
||||||
|
"did not approve".bold(),
|
||||||
|
" codex to run ".into(),
|
||||||
|
cmd_span,
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
ReviewDecision::Abort => {
|
||||||
|
lines.push(Line::from(vec![
|
||||||
|
"✗ ".fg(Color::Red),
|
||||||
|
"You ".into(),
|
||||||
|
"canceled".bold(),
|
||||||
|
" the request to run ".into(),
|
||||||
|
cmd_span,
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ApprovalRequest::ApplyPatch { .. } => {
|
ApprovalRequest::ApplyPatch { .. } => {
|
||||||
lines.push(Line::from(format!("patch approval decision: {decision:?}")));
|
lines.push(Line::from(format!("patch approval decision: {decision:?}")));
|
||||||
|
|||||||
Reference in New Issue
Block a user