From c04e97426e3a01d84d96e6bfdddc1fe017522ac4 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Wed, 15 Oct 2025 12:21:25 -0700 Subject: [PATCH] feat: add Vec to ExecApprovalRequestEvent --- codex-rs/app-server-protocol/src/protocol.rs | 3 +++ codex-rs/app-server/src/codex_message_processor.rs | 2 ++ .../app-server/tests/suite/codex_message_processor_flow.rs | 1 + codex-rs/core/src/codex.rs | 2 ++ codex-rs/mcp-server/src/codex_tool_runner.rs | 2 ++ codex-rs/mcp-server/src/exec_approval.rs | 4 ++++ codex-rs/mcp-server/tests/suite/codex_tool.rs | 3 +++ codex-rs/protocol/src/protocol.rs | 1 + codex-rs/tui/src/chatwidget/tests.rs | 6 ++++++ 9 files changed, 24 insertions(+) diff --git a/codex-rs/app-server-protocol/src/protocol.rs b/codex-rs/app-server-protocol/src/protocol.rs index 845a2431f4..8d73e12ae7 100644 --- a/codex-rs/app-server-protocol/src/protocol.rs +++ b/codex-rs/app-server-protocol/src/protocol.rs @@ -9,6 +9,7 @@ use codex_protocol::config_types::ReasoningEffort; use codex_protocol::config_types::ReasoningSummary; use codex_protocol::config_types::SandboxMode; use codex_protocol::config_types::Verbosity; +use codex_protocol::parse_command::ParsedCommand; use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::FileChange; @@ -697,6 +698,7 @@ pub struct ExecCommandApprovalParams { pub cwd: PathBuf, #[serde(skip_serializing_if = "Option::is_none")] pub reason: Option, + pub parsed_cmd: Vec, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)] @@ -904,6 +906,7 @@ mod tests { command: vec!["echo".to_string(), "hello".to_string()], cwd: PathBuf::from("/tmp"), reason: Some("because tests".to_string()), + parsed_cmd: vec![], }; let request = ServerRequest::ExecCommandApproval { request_id: RequestId::Integer(7), diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index cb894a51ab..f455ad32e1 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -1284,6 +1284,7 @@ async fn apply_bespoke_event_handling( command, cwd, reason, + parsed_cmd, }) => { let params = ExecCommandApprovalParams { conversation_id, @@ -1291,6 +1292,7 @@ async fn apply_bespoke_event_handling( command, cwd, reason, + parsed_cmd, }; let rx = outgoing .send_request(ServerRequestPayload::ExecCommandApproval(params)) diff --git a/codex-rs/app-server/tests/suite/codex_message_processor_flow.rs b/codex-rs/app-server/tests/suite/codex_message_processor_flow.rs index 4dff2a1575..5ca8ef8471 100644 --- a/codex-rs/app-server/tests/suite/codex_message_processor_flow.rs +++ b/codex-rs/app-server/tests/suite/codex_message_processor_flow.rs @@ -311,6 +311,7 @@ async fn test_send_user_turn_changes_approval_policy_behavior() { ], cwd: working_directory.clone(), reason: None, + parsed_cmd: vec![], }, params ); diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 510395b073..ae7a803607 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -594,6 +594,7 @@ impl Session { warn!("Overwriting existing pending approval for sub_id: {event_id}"); } + let parsed_cmd = parse_command(&command); let event = Event { id: event_id, msg: EventMsg::ExecApprovalRequest(ExecApprovalRequestEvent { @@ -601,6 +602,7 @@ impl Session { command, cwd, reason, + parsed_cmd, }), }; self.send_event(event).await; diff --git a/codex-rs/mcp-server/src/codex_tool_runner.rs b/codex-rs/mcp-server/src/codex_tool_runner.rs index f09dc98cbc..370dde0e82 100644 --- a/codex-rs/mcp-server/src/codex_tool_runner.rs +++ b/codex-rs/mcp-server/src/codex_tool_runner.rs @@ -178,6 +178,7 @@ async fn run_codex_tool_session_inner( cwd, call_id, reason: _, + parsed_cmd, }) => { handle_exec_approval_request( command, @@ -188,6 +189,7 @@ async fn run_codex_tool_session_inner( request_id_str.clone(), event.id.clone(), call_id, + parsed_cmd, ) .await; continue; diff --git a/codex-rs/mcp-server/src/exec_approval.rs b/codex-rs/mcp-server/src/exec_approval.rs index 119481cda9..44607b754d 100644 --- a/codex-rs/mcp-server/src/exec_approval.rs +++ b/codex-rs/mcp-server/src/exec_approval.rs @@ -4,6 +4,7 @@ use std::sync::Arc; use codex_core::CodexConversation; use codex_core::protocol::Op; use codex_core::protocol::ReviewDecision; +use codex_protocol::parse_command::ParsedCommand; use mcp_types::ElicitRequest; use mcp_types::ElicitRequestParamsRequestedSchema; use mcp_types::JSONRPCErrorError; @@ -35,6 +36,7 @@ pub struct ExecApprovalElicitRequestParams { pub codex_call_id: String, pub codex_command: Vec, pub codex_cwd: PathBuf, + pub codex_parsed_cmd: Vec, } // TODO(mbolin): ExecApprovalResponse does not conform to ElicitResult. See: @@ -56,6 +58,7 @@ pub(crate) async fn handle_exec_approval_request( tool_call_id: String, event_id: String, call_id: String, + codex_parsed_cmd: Vec, ) { let escaped_command = shlex::try_join(command.iter().map(String::as_str)).unwrap_or_else(|_| command.join(" ")); @@ -77,6 +80,7 @@ pub(crate) async fn handle_exec_approval_request( codex_call_id: call_id, codex_command: command, codex_cwd: cwd, + codex_parsed_cmd, }; let params_json = match serde_json::to_value(¶ms) { Ok(value) => value, diff --git a/codex-rs/mcp-server/tests/suite/codex_tool.rs b/codex-rs/mcp-server/tests/suite/codex_tool.rs index 13ce3864e3..a26892e5d4 100644 --- a/codex-rs/mcp-server/tests/suite/codex_tool.rs +++ b/codex-rs/mcp-server/tests/suite/codex_tool.rs @@ -3,6 +3,7 @@ use std::env; use std::path::Path; use std::path::PathBuf; +use codex_core::parse_command; use codex_core::protocol::FileChange; use codex_core::protocol::ReviewDecision; use codex_core::spawn::CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR; @@ -176,6 +177,7 @@ fn create_expected_elicitation_request( shlex::try_join(command.iter().map(std::convert::AsRef::as_ref))?, workdir.to_string_lossy() ); + let codex_parsed_cmd = parse_command::parse_command(&command); Ok(JSONRPCRequest { jsonrpc: JSONRPC_VERSION.into(), id: elicitation_request_id, @@ -193,6 +195,7 @@ fn create_expected_elicitation_request( codex_command: command, codex_cwd: workdir.to_path_buf(), codex_call_id: "call1234".to_string(), + codex_parsed_cmd, })?), }) } diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index b73909473d..d18c5338c8 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -1178,6 +1178,7 @@ pub struct ExecApprovalRequestEvent { /// Optional human-readable reason for the approval (e.g. retry without sandbox). #[serde(skip_serializing_if = "Option::is_none")] pub reason: Option, + pub parsed_cmd: Vec, } #[derive(Debug, Clone, Deserialize, Serialize, TS)] diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index a92be43365..64130d55ae 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -392,6 +392,7 @@ fn exec_approval_emits_proposed_command_and_decision_history() { reason: Some( "this is a test reason such as one that would be produced by the model".into(), ), + parsed_cmd: vec![], }; chat.handle_codex_event(Event { id: "sub-short".into(), @@ -433,6 +434,7 @@ fn exec_approval_decision_truncates_multiline_and_long_commands() { reason: Some( "this is a test reason such as one that would be produced by the model".into(), ), + parsed_cmd: vec![], }; chat.handle_codex_event(Event { id: "sub-multi".into(), @@ -480,6 +482,7 @@ fn exec_approval_decision_truncates_multiline_and_long_commands() { command: vec!["bash".into(), "-lc".into(), long], cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")), reason: None, + parsed_cmd: vec![], }; chat.handle_codex_event(Event { id: "sub-long".into(), @@ -1317,6 +1320,7 @@ fn approval_modal_exec_snapshot() { reason: Some( "this is a test reason such as one that would be produced by the model".into(), ), + parsed_cmd: vec![], }; chat.handle_codex_event(Event { id: "sub-approve".into(), @@ -1360,6 +1364,7 @@ fn approval_modal_exec_without_reason_snapshot() { command: vec!["bash".into(), "-lc".into(), "echo hello world".into()], cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")), reason: None, + parsed_cmd: vec![], }; chat.handle_codex_event(Event { id: "sub-approve-noreason".into(), @@ -1569,6 +1574,7 @@ fn status_widget_and_approval_modal_snapshot() { reason: Some( "this is a test reason such as one that would be produced by the model".into(), ), + parsed_cmd: vec![], }; chat.handle_codex_event(Event { id: "sub-approve-exec".into(),