From 42bd73e150c887a3ac102409a38d124c2f26ba0a Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Tue, 5 Aug 2025 14:42:49 -0700 Subject: [PATCH 1/3] chore: remove unnecessary default_ prefix (#1854) This prefix is not inline with the other fields on the `ConfigOverrides` struct. --- codex-rs/core/src/config.rs | 12 ++++++------ codex-rs/exec/src/lib.rs | 4 ++-- codex-rs/mcp-server/src/codex_tool_config.rs | 4 ++-- .../src/tool_handlers/create_conversation.rs | 4 ++-- codex-rs/tui/src/lib.rs | 4 ++-- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/codex-rs/core/src/config.rs b/codex-rs/core/src/config.rs index e62fcc39e2..0b53df5ab7 100644 --- a/codex-rs/core/src/config.rs +++ b/codex-rs/core/src/config.rs @@ -385,8 +385,8 @@ pub struct ConfigOverrides { pub codex_linux_sandbox_exe: Option, pub base_instructions: Option, pub include_plan_tool: Option, - pub default_disable_response_storage: Option, - pub default_show_raw_agent_reasoning: Option, + pub disable_response_storage: Option, + pub show_raw_agent_reasoning: Option, } impl Config { @@ -410,8 +410,8 @@ impl Config { codex_linux_sandbox_exe, base_instructions, include_plan_tool, - default_disable_response_storage, - default_show_raw_agent_reasoning, + disable_response_storage, + show_raw_agent_reasoning, } = overrides; let config_profile = match config_profile_key.as_ref().or(cfg.profile.as_ref()) { @@ -529,7 +529,7 @@ impl Config { disable_response_storage: config_profile .disable_response_storage .or(cfg.disable_response_storage) - .or(default_disable_response_storage) + .or(disable_response_storage) .unwrap_or(false), notify: cfg.notify, user_instructions, @@ -546,7 +546,7 @@ impl Config { hide_agent_reasoning: cfg.hide_agent_reasoning.unwrap_or(false), show_raw_agent_reasoning: cfg .show_raw_agent_reasoning - .or(default_show_raw_agent_reasoning) + .or(show_raw_agent_reasoning) .unwrap_or(false), model_reasoning_effort: config_profile .model_reasoning_effort diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index a0360182b4..288b6177e5 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -147,8 +147,8 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option) -> any codex_linux_sandbox_exe, base_instructions: None, include_plan_tool: None, - default_disable_response_storage: oss.then_some(true), - default_show_raw_agent_reasoning: oss.then_some(true), + disable_response_storage: oss.then_some(true), + show_raw_agent_reasoning: oss.then_some(true), }; // Parse `-c` overrides. let cli_kv_overrides = match config_overrides.parse_overrides() { diff --git a/codex-rs/mcp-server/src/codex_tool_config.rs b/codex-rs/mcp-server/src/codex_tool_config.rs index f1a502bbb3..899451a50d 100644 --- a/codex-rs/mcp-server/src/codex_tool_config.rs +++ b/codex-rs/mcp-server/src/codex_tool_config.rs @@ -158,8 +158,8 @@ impl CodexToolCallParam { codex_linux_sandbox_exe, base_instructions, include_plan_tool, - default_disable_response_storage: None, - default_show_raw_agent_reasoning: None, + disable_response_storage: None, + show_raw_agent_reasoning: None, }; let cli_overrides = cli_overrides diff --git a/codex-rs/mcp-server/src/tool_handlers/create_conversation.rs b/codex-rs/mcp-server/src/tool_handlers/create_conversation.rs index c1f4035663..559bf72905 100644 --- a/codex-rs/mcp-server/src/tool_handlers/create_conversation.rs +++ b/codex-rs/mcp-server/src/tool_handlers/create_conversation.rs @@ -59,8 +59,8 @@ pub(crate) async fn handle_create_conversation( codex_linux_sandbox_exe: None, base_instructions, include_plan_tool: None, - default_disable_response_storage: None, - default_show_raw_agent_reasoning: None, + disable_response_storage: None, + show_raw_agent_reasoning: None, }; let cfg: CodexConfig = match CodexConfig::load_with_cli_overrides(cli_overrides, overrides) { diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index bab728e124..50535e5967 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -101,8 +101,8 @@ pub async fn run_main( codex_linux_sandbox_exe, base_instructions: None, include_plan_tool: Some(true), - default_disable_response_storage: cli.oss.then_some(true), - default_show_raw_agent_reasoning: cli.oss.then_some(true), + disable_response_storage: cli.oss.then_some(true), + show_raw_agent_reasoning: cli.oss.then_some(true), }; // Parse `-c` overrides from the CLI. let cli_kv_overrides = match cli.config_overrides.parse_overrides() { From f6c8d1117cfe8b17de3c5f5d077126279be44d8f Mon Sep 17 00:00:00 2001 From: ae Date: Tue, 5 Aug 2025 15:50:06 -0700 Subject: [PATCH 2/3] [feat] make approval key matching case insensitive (#1862) --- codex-rs/tui/src/user_approval_widget.rs | 75 +++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/codex-rs/tui/src/user_approval_widget.rs b/codex-rs/tui/src/user_approval_widget.rs index 91febde208..70b355d794 100644 --- a/codex-rs/tui/src/user_approval_widget.rs +++ b/codex-rs/tui/src/user_approval_widget.rs @@ -47,6 +47,8 @@ pub(crate) enum ApprovalRequest { } /// Options displayed in the *select* mode. +/// +/// The `key` is matched case-insensitively. struct SelectOption { label: Line<'static>, description: &'static str, @@ -187,6 +189,16 @@ impl UserApprovalWidget<'_> { } } + /// Normalize a key for comparison. + /// - For `KeyCode::Char`, converts to lowercase for case-insensitive matching. + /// - Other key codes are returned unchanged. + fn normalize_keycode(code: KeyCode) -> KeyCode { + match code { + KeyCode::Char(c) => KeyCode::Char(c.to_ascii_lowercase()), + other => other, + } + } + /// Handle Ctrl-C pressed by the user while the modal is visible. /// Behaves like pressing Escape: abort the request and close the modal. pub(crate) fn on_ctrl_c(&mut self) { @@ -210,7 +222,12 @@ impl UserApprovalWidget<'_> { self.send_decision(ReviewDecision::Abort); } other => { - if let Some(opt) = self.select_options.iter().find(|opt| opt.key == other) { + let normalized = Self::normalize_keycode(other); + if let Some(opt) = self + .select_options + .iter() + .find(|opt| Self::normalize_keycode(opt.key) == normalized) + { self.send_decision(opt.decision); } } @@ -330,3 +347,59 @@ impl WidgetRef for &UserApprovalWidget<'_> { ); } } + +#[cfg(test)] +mod tests { + use super::*; + use crossterm::event::KeyCode; + use crossterm::event::KeyEvent; + use crossterm::event::KeyModifiers; + use std::path::PathBuf; + use std::sync::mpsc::channel; + + #[test] + fn lowercase_shortcut_is_accepted() { + let (tx_raw, rx) = channel::(); + let tx = AppEventSender::new(tx_raw); + let req = ApprovalRequest::Exec { + id: "1".to_string(), + command: vec!["echo".to_string()], + cwd: PathBuf::new(), + reason: None, + }; + let mut widget = UserApprovalWidget::new(req, tx); + widget.handle_key_event(KeyEvent::new(KeyCode::Char('y'), KeyModifiers::NONE)); + assert!(widget.is_complete()); + let events: Vec = rx.try_iter().collect(); + assert!(events.iter().any(|e| matches!( + e, + AppEvent::CodexOp(Op::ExecApproval { + decision: ReviewDecision::Approved, + .. + }) + ))); + } + + #[test] + fn uppercase_shortcut_is_accepted() { + let (tx_raw, rx) = channel::(); + let tx = AppEventSender::new(tx_raw); + let req = ApprovalRequest::Exec { + id: "2".to_string(), + command: vec!["echo".to_string()], + cwd: PathBuf::new(), + reason: None, + }; + let mut widget = UserApprovalWidget::new(req, tx); + widget.handle_key_event(KeyEvent::new(KeyCode::Char('Y'), KeyModifiers::NONE)); + assert!(widget.is_complete()); + let events: Vec = rx.try_iter().collect(); + assert!(events.iter().any(|e| matches!( + e, + AppEvent::CodexOp(Op::ExecApproval { + decision: ReviewDecision::Approved, + .. + }) + ))); + } +} From de81606a39798f8712a794ad76cffabc3d40aecb Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Tue, 5 Aug 2025 18:43:59 -0700 Subject: [PATCH 3/3] fix: exit cleanly when ShutdownComplete is received --- codex-rs/exec/src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index 288b6177e5..06df2aebca 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -216,10 +216,16 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option) -> any res = codex.next_event() => match res { Ok(event) => { debug!("Received event: {event:?}"); + + let is_shutdown_complete = matches!(event.msg, EventMsg::ShutdownComplete); if let Err(e) = tx.send(event) { error!("Error sending event: {e:?}"); break; } + if is_shutdown_complete { + info!("Received shutdown event, exiting event loop."); + break; + } }, Err(e) => { error!("Error receiving event: {e:?}");