Files
codex/prs/bolinfest/PR-1933.md
2025-09-02 15:17:45 -07:00

7.9 KiB

PR #1933: approval ui

Description

Asking for approval:

image

Allow:

image

Reject:

image

Always Approve:

image

Full Diff

diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs
index 9936c0eef9..d4872d377e 100644
--- a/codex-rs/tui/src/chatwidget.rs
+++ b/codex-rs/tui/src/chatwidget.rs
@@ -45,7 +45,6 @@ use crate::bottom_pane::BottomPane;
 use crate::bottom_pane::BottomPaneParams;
 use crate::bottom_pane::CancellationEvent;
 use crate::bottom_pane::InputResult;
-use crate::exec_command::strip_bash_lc_and_escape;
 use crate::history_cell::CommandOutput;
 use crate::history_cell::HistoryCell;
 use crate::history_cell::PatchEventType;
@@ -393,17 +392,6 @@ impl ChatWidget<'_> {
                 reason,
             }) => {
                 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 {
                     id,
                     command,
diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs
index be96a9e9ef..c5c02581fa 100644
--- a/codex-rs/tui/src/history_cell.rs
+++ b/codex-rs/tui/src/history_cell.rs
@@ -433,7 +433,8 @@ impl HistoryCell {
             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 {
         let mut lines: Vec<Line<'static>> = Vec::new();
         lines.push(Line::from("event".dim()));
diff --git a/codex-rs/tui/src/user_approval_widget.rs b/codex-rs/tui/src/user_approval_widget.rs
index 70b355d794..966b8d68f9 100644
--- a/codex-rs/tui/src/user_approval_widget.rs
+++ b/codex-rs/tui/src/user_approval_widget.rs
@@ -28,7 +28,6 @@ use ratatui::widgets::Wrap;
 
 use crate::app_event::AppEvent;
 use crate::app_event_sender::AppEventSender;
-use crate::exec_command::relativize_to_home;
 use crate::exec_command::strip_bash_lc_and_escape;
 
 /// Request coming from the agent that needs user approval.
@@ -36,6 +35,7 @@ pub(crate) enum ApprovalRequest {
     Exec {
         id: String,
         command: Vec<String>,
+        #[allow(dead_code)]
         cwd: PathBuf,
         reason: Option<String>,
     },
@@ -115,21 +115,18 @@ impl UserApprovalWidget<'_> {
     pub(crate) fn new(approval_request: ApprovalRequest, app_event_tx: AppEventSender) -> Self {
         let confirmation_prompt = match &approval_request {
             ApprovalRequest::Exec {
-                command,
-                cwd,
-                reason,
-                ..
+                command, reason, ..
             } => {
                 let cmd = strip_bash_lc_and_escape(command);
-                // Maybe try to relativize to the cwd of this process first?
-                // Will make cwd_str shorter in the common case.
-                let cwd_str = match relativize_to_home(cwd) {
-                    Some(rel) => format!("~/{}", rel.display()),
-                    None => cwd.display().to_string(),
-                };
+                // Present a single-line summary without cwd: "codex wants to run: <cmd>"
+                let mut cmd_span: Span = cmd.clone().into();
+                cmd_span.style = cmd_span.style.add_modifier(Modifier::DIM);
                 let mut contents: Vec<Line> = vec![
-                    Line::from(vec!["codex".bold().magenta(), " wants to run:".into()]),
-                    Line::from(vec![cwd_str.dim(), "$".into(), format!(" {cmd}").into()]),
+                    Line::from(vec![
+                        "? ".fg(Color::Blue),
+                        "Codex wants to run ".bold(),
+                        cmd_span,
+                    ]),
                     Line::from(""),
                 ];
                 if let Some(reason) = reason {
@@ -243,9 +240,52 @@ impl UserApprovalWidget<'_> {
         match &self.approval_request {
             ApprovalRequest::Exec { command, .. } => {
                 let cmd = strip_bash_lc_and_escape(command);
-                lines.push(Line::from("approval decision"));
-                lines.push(Line::from(format!("$ {cmd}")));
-                lines.push(Line::from(format!("decision: {decision:?}")));
+                let mut cmd_span: Span = cmd.clone().into();
+                cmd_span.style = cmd_span.style.add_modifier(Modifier::DIM);
+
+                // 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 { .. } => {
                 lines.push(Line::from(format!("patch approval decision: {decision:?}")));

Review Comments

codex-rs/tui/src/history_cell.rs

@@ -433,7 +433,8 @@ impl HistoryCell {
             view: TextBlock::new(lines),
         }
     }
-
+    // allow dead code for now. maybe we'll use it again.
+    #[allow(dead_code)]

I would just rip it all out in a follow-up PR.

As a colleague once told me, "It will live on in Git history."