diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index f67470dcb7..4394417cb5 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -2724,6 +2724,7 @@ async fn apply_bespoke_event_handling( }, EventMsg::ExecApprovalRequest(ExecApprovalRequestEvent { call_id, + turn_id, command, cwd, reason, @@ -2757,8 +2758,7 @@ async fn apply_bespoke_event_handling( }; let params = CommandExecutionRequestApprovalParams { thread_id: conversation_id.to_string(), - // TODO: use the actual IDs once we have them - turn_id: "placeholder_turn_id".to_string(), + turn_id: turn_id.clone(), // TODO: Until we migrate the core to be aware of a first class CommandExecutionItem // and emit the corresponding EventMsg, we repurpose the call_id as the item_id. item_id: call_id.clone(), diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 273f7a4975..bc59c01353 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -890,6 +890,7 @@ impl Session { let parsed_cmd = parse_command(&command); let event = EventMsg::ExecApprovalRequest(ExecApprovalRequestEvent { call_id, + turn_id: turn_context.sub_id.clone(), command, cwd, reason, diff --git a/codex-rs/mcp-server/src/codex_tool_runner.rs b/codex-rs/mcp-server/src/codex_tool_runner.rs index 96e875153d..3f9cae0e14 100644 --- a/codex-rs/mcp-server/src/codex_tool_runner.rs +++ b/codex-rs/mcp-server/src/codex_tool_runner.rs @@ -174,6 +174,7 @@ async fn run_codex_tool_session_inner( match event.msg { EventMsg::ExecApprovalRequest(ExecApprovalRequestEvent { + turn_id: _, command, cwd, call_id, diff --git a/codex-rs/protocol/src/approvals.rs b/codex-rs/protocol/src/approvals.rs index 3227ddd1a3..f7c5fc6049 100644 --- a/codex-rs/protocol/src/approvals.rs +++ b/codex-rs/protocol/src/approvals.rs @@ -36,6 +36,10 @@ impl SandboxRiskLevel { pub struct ExecApprovalRequestEvent { /// Identifier for the associated exec call, if available. pub call_id: String, + /// Turn ID that this command belongs to. + /// Uses `#[serde(default)]` for backwards compatibility. + #[serde(default)] + pub turn_id: String, /// The command to be executed. pub command: Vec, /// The command's working directory. diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index e9ea5813fa..b245ea4eb4 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -509,6 +509,7 @@ fn exec_approval_emits_proposed_command_and_decision_history() { // Trigger an exec approval request with a short, single-line command let ev = ExecApprovalRequestEvent { call_id: "call-short".into(), + turn_id: "turn-short".into(), command: vec!["bash".into(), "-lc".into(), "echo hello world".into()], cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")), reason: Some( @@ -552,6 +553,7 @@ fn exec_approval_decision_truncates_multiline_and_long_commands() { // Multiline command: modal should show full command, history records decision only let ev_multi = ExecApprovalRequestEvent { call_id: "call-multi".into(), + turn_id: "turn-multi".into(), command: vec!["bash".into(), "-lc".into(), "echo line1\necho line2".into()], cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")), reason: Some( @@ -603,6 +605,7 @@ fn exec_approval_decision_truncates_multiline_and_long_commands() { let long = format!("echo {}", "a".repeat(200)); let ev_long = ExecApprovalRequestEvent { call_id: "call-long".into(), + turn_id: "turn-long".into(), command: vec!["bash".into(), "-lc".into(), long], cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")), reason: None, @@ -1828,6 +1831,7 @@ fn approval_modal_exec_snapshot() { // Inject an exec approval request to display the approval modal. let ev = ExecApprovalRequestEvent { call_id: "call-approve-cmd".into(), + turn_id: "turn-approve-cmd".into(), command: vec!["bash".into(), "-lc".into(), "echo hello world".into()], cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")), reason: Some( @@ -1875,6 +1879,7 @@ fn approval_modal_exec_without_reason_snapshot() { let ev = ExecApprovalRequestEvent { call_id: "call-approve-cmd-noreason".into(), + turn_id: "turn-approve-cmd-noreason".into(), command: vec!["bash".into(), "-lc".into(), "echo hello world".into()], cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")), reason: None, @@ -2084,6 +2089,7 @@ fn status_widget_and_approval_modal_snapshot() { // Now show an approval modal (e.g. exec approval). let ev = ExecApprovalRequestEvent { call_id: "call-approve-exec".into(), + turn_id: "turn-approve-exec".into(), command: vec!["echo".into(), "hello world".into()], cwd: PathBuf::from("/tmp"), reason: Some(