diff --git a/codex-rs/core/src/tools/handlers/apply_patch.rs b/codex-rs/core/src/tools/handlers/apply_patch.rs index b10758d2fc..bd5769835e 100644 --- a/codex-rs/core/src/tools/handlers/apply_patch.rs +++ b/codex-rs/core/src/tools/handlers/apply_patch.rs @@ -216,6 +216,7 @@ impl ToolHandler for ApplyPatchHandler { call_id: call_id.clone(), tool_name: tool_name.to_string(), command_override: None, + effective_command: None, }; let out = orchestrator .run( @@ -321,6 +322,7 @@ pub(crate) async fn intercept_apply_patch( call_id: call_id.to_string(), tool_name: tool_name.to_string(), command_override: None, + effective_command: None, }; let out = orchestrator .run( diff --git a/codex-rs/core/src/tools/handlers/shell.rs b/codex-rs/core/src/tools/handlers/shell.rs index 5fe2546de2..862d4062bb 100644 --- a/codex-rs/core/src/tools/handlers/shell.rs +++ b/codex-rs/core/src/tools/handlers/shell.rs @@ -411,14 +411,6 @@ impl ShellHandler { } let source = ExecCommandSource::Agent; - let emitter = ToolEmitter::shell( - exec_params.command.clone(), - exec_params.cwd.clone(), - source, - freeform, - ); - let event_ctx = ToolEventCtx::new(session.as_ref(), turn.as_ref(), &call_id, None); - emitter.begin(event_ctx).await; let exec_approval_requirement = session .services @@ -462,12 +454,15 @@ impl ShellHandler { } } }; + let effective_command = + std::sync::Arc::new(tokio::sync::Mutex::new(exec_params.command.clone())); let tool_ctx = ToolCtx { session: session.clone(), turn: turn.clone(), call_id: call_id.clone(), tool_name, command_override: None, + effective_command: Some(std::sync::Arc::clone(&effective_command)), }; let out = orchestrator .run( @@ -479,6 +474,12 @@ impl ShellHandler { ) .await .map(|result| result.output); + let emitter = ToolEmitter::shell( + effective_command.lock().await.clone(), + exec_params.cwd.clone(), + source, + freeform, + ); let event_ctx = ToolEventCtx::new(session.as_ref(), turn.as_ref(), &call_id, None); let content = emitter.finish(event_ctx, out).await?; Ok(FunctionToolOutput::from_text(content, Some(true))) diff --git a/codex-rs/core/src/tools/orchestrator.rs b/codex-rs/core/src/tools/orchestrator.rs index ecfdd61065..840eeb1991 100644 --- a/codex-rs/core/src/tools/orchestrator.rs +++ b/codex-rs/core/src/tools/orchestrator.rs @@ -52,7 +52,6 @@ impl ToolOrchestrator { req: &Rq, tool_ctx: &ToolCtx, attempt: &SandboxAttempt<'_>, - command_override: Option>, has_managed_network_requirements: bool, ) -> (Result, Option) where @@ -66,14 +65,7 @@ impl ToolOrchestrator { ) .await; - let attempt_tool_ctx = ToolCtx { - session: tool_ctx.session.clone(), - turn: tool_ctx.turn.clone(), - call_id: tool_ctx.call_id.clone(), - tool_name: tool_ctx.tool_name.clone(), - command_override, - }; - let run_result = tool.run(req, attempt, &attempt_tool_ctx).await; + let run_result = tool.run(req, attempt, tool_ctx).await; let Some(network_approval) = network_approval else { return (run_result, None); @@ -115,7 +107,14 @@ impl ToolOrchestrator { let otel_ci = &tool_ctx.call_id; let otel_user = ToolDecisionSource::User; let otel_cfg = ToolDecisionSource::Config; - let mut command_override: Option> = None; + let mut effective_tool_ctx = ToolCtx { + session: tool_ctx.session.clone(), + turn: tool_ctx.turn.clone(), + call_id: tool_ctx.call_id.clone(), + tool_name: tool_ctx.tool_name.clone(), + command_override: tool_ctx.command_override.clone(), + effective_command: tool_ctx.effective_command.clone(), + }; // 1) Approval let mut already_approved = false; @@ -132,18 +131,21 @@ impl ToolOrchestrator { } ExecApprovalRequirement::NeedsApproval { reason, .. } => { let approval_ctx = ApprovalCtx { - session: &tool_ctx.session, - turn: &tool_ctx.turn, - call_id: &tool_ctx.call_id, + session: &effective_tool_ctx.session, + turn: &effective_tool_ctx.turn, + call_id: &effective_tool_ctx.call_id, retry_reason: reason, network_approval_context: None, - command_override: None, + command_override: effective_tool_ctx.command_override.clone(), }; let decision = tool.start_approval_async(req, approval_ctx).await; otel.tool_decision(otel_tn, otel_ci, &decision, otel_user.clone()); if let Some(command) = decision.override_command() { - command_override = Some(command.to_vec()); + effective_tool_ctx.command_override = Some(command.to_vec()); + if let Some(effective_command) = &effective_tool_ctx.effective_command { + *effective_command.lock().await = command.to_vec(); + } } match decision { @@ -213,9 +215,8 @@ impl ToolOrchestrator { let (first_result, first_deferred_network_approval) = Self::run_attempt( tool, req, - tool_ctx, + &effective_tool_ctx, &initial_attempt, - command_override.clone(), has_managed_network_requirements, ) .await; @@ -287,18 +288,21 @@ impl ToolOrchestrator { && network_approval_context.is_none(); if !bypass_retry_approval { let approval_ctx = ApprovalCtx { - session: &tool_ctx.session, - turn: &tool_ctx.turn, - call_id: &tool_ctx.call_id, + session: &effective_tool_ctx.session, + turn: &effective_tool_ctx.turn, + call_id: &effective_tool_ctx.call_id, retry_reason: Some(retry_reason), network_approval_context: network_approval_context.clone(), - command_override: command_override.clone(), + command_override: effective_tool_ctx.command_override.clone(), }; let decision = tool.start_approval_async(req, approval_ctx).await; otel.tool_decision(otel_tn, otel_ci, &decision, otel_user); if let Some(command) = decision.override_command() { - command_override = Some(command.to_vec()); + effective_tool_ctx.command_override = Some(command.to_vec()); + if let Some(effective_command) = &effective_tool_ctx.effective_command { + *effective_command.lock().await = command.to_vec(); + } } match decision { @@ -346,9 +350,8 @@ impl ToolOrchestrator { let (retry_result, retry_deferred_network_approval) = Self::run_attempt( tool, req, - tool_ctx, + &effective_tool_ctx, &escalated_attempt, - command_override, has_managed_network_requirements, ) .await; diff --git a/codex-rs/core/src/tools/runtimes/shell.rs b/codex-rs/core/src/tools/runtimes/shell.rs index ba7ab83666..2193cb7ebf 100644 --- a/codex-rs/core/src/tools/runtimes/shell.rs +++ b/codex-rs/core/src/tools/runtimes/shell.rs @@ -225,6 +225,19 @@ impl ToolRuntime for ShellRuntime { ) -> Result { let session_shell = ctx.session.user_shell(); let base_command = ctx.command_override.as_ref().unwrap_or(&req.command); + crate::tools::events::ToolEmitter::shell( + base_command.to_vec(), + req.cwd.clone(), + ExecCommandSource::Agent, + false, + ) + .begin(crate::tools::events::ToolEventCtx::new( + ctx.session.as_ref(), + ctx.turn.as_ref(), + &ctx.call_id, + None, + )) + .await; let command = maybe_wrap_shell_lc_with_snapshot( base_command, session_shell.as_ref(), diff --git a/codex-rs/core/src/tools/sandboxing.rs b/codex-rs/core/src/tools/sandboxing.rs index cae41c4c53..fd173c0876 100644 --- a/codex-rs/core/src/tools/sandboxing.rs +++ b/codex-rs/core/src/tools/sandboxing.rs @@ -303,6 +303,7 @@ pub(crate) struct ToolCtx { pub call_id: String, pub tool_name: String, pub command_override: Option>, + pub effective_command: Option>>>, } #[derive(Debug)] diff --git a/codex-rs/core/src/unified_exec/process_manager.rs b/codex-rs/core/src/unified_exec/process_manager.rs index a40f5fd7bb..8d9e54394d 100644 --- a/codex-rs/core/src/unified_exec/process_manager.rs +++ b/codex-rs/core/src/unified_exec/process_manager.rs @@ -165,10 +165,12 @@ impl UnifiedExecProcessManager { .open_session_with_sandbox(&request, cwd.clone(), context) .await; - let (process, mut deferred_network_approval) = match process { - Ok((process, deferred_network_approval)) => { - (Arc::new(process), deferred_network_approval) - } + let (process, mut deferred_network_approval, effective_command) = match process { + Ok((process, deferred_network_approval, effective_command)) => ( + Arc::new(process), + deferred_network_approval, + effective_command, + ), Err(err) => { self.release_process_id(request.process_id).await; return Err(err); @@ -183,7 +185,7 @@ impl UnifiedExecProcessManager { None, ); let emitter = ToolEmitter::unified_exec( - &request.command, + &effective_command, cwd.clone(), ExecCommandSource::UnifiedExecStartup, Some(request.process_id.to_string()), @@ -236,7 +238,7 @@ impl UnifiedExecProcessManager { Arc::clone(&context.session), Arc::clone(&context.turn), context.call_id.clone(), - request.command.clone(), + effective_command.clone(), cwd.clone(), Some(process_id.to_string()), Arc::clone(&transcript), @@ -264,7 +266,7 @@ impl UnifiedExecProcessManager { self.store_process( Arc::clone(&process), context, - &request.command, + &effective_command, cwd.clone(), start, process_id, @@ -289,7 +291,7 @@ impl UnifiedExecProcessManager { }, exit_code, original_token_count: Some(original_token_count), - session_command: Some(request.command.clone()), + session_command: Some(effective_command.clone()), }; Ok(response) @@ -572,7 +574,14 @@ impl UnifiedExecProcessManager { request: &ExecCommandRequest, cwd: PathBuf, context: &UnifiedExecContext, - ) -> Result<(UnifiedExecProcess, Option), UnifiedExecError> { + ) -> Result< + ( + UnifiedExecProcess, + Option, + Vec, + ), + UnifiedExecError, + > { let env = apply_unified_exec_env(create_env( &context.turn.shell_environment_policy, Some(context.session.conversation_id), @@ -613,14 +622,17 @@ impl UnifiedExecProcessManager { justification: request.justification.clone(), exec_approval_requirement, }; + let effective_command = + std::sync::Arc::new(tokio::sync::Mutex::new(request.command.clone())); let tool_ctx = ToolCtx { session: context.session.clone(), turn: context.turn.clone(), call_id: context.call_id.clone(), tool_name: "exec_command".to_string(), command_override: None, + effective_command: Some(std::sync::Arc::clone(&effective_command)), }; - orchestrator + let result = orchestrator .run( &mut runtime, &req, @@ -629,8 +641,13 @@ impl UnifiedExecProcessManager { context.turn.approval_policy.value(), ) .await - .map(|result| (result.output, result.deferred_network_approval)) - .map_err(|e| UnifiedExecError::create_process(format!("{e:?}"))) + .map_err(|e| UnifiedExecError::create_process(format!("{e:?}")))?; + let effective_command = effective_command.lock().await.clone(); + Ok(( + result.output, + result.deferred_network_approval, + effective_command, + )) } pub(super) async fn collect_output_until_deadline(