diff --git a/codex-rs/core/src/tools/handlers/apply_patch.rs b/codex-rs/core/src/tools/handlers/apply_patch.rs index 3ef79f7aa0..ae93327b81 100644 --- a/codex-rs/core/src/tools/handlers/apply_patch.rs +++ b/codex-rs/core/src/tools/handlers/apply_patch.rs @@ -31,6 +31,7 @@ use async_trait::async_trait; use codex_apply_patch::ApplyPatchAction; use codex_apply_patch::ApplyPatchFileChange; use codex_utils_absolute_path::AbsolutePathBuf; +use std::sync::Arc; pub struct ApplyPatchHandler; @@ -139,8 +140,8 @@ impl ToolHandler for ApplyPatchHandler { let mut orchestrator = ToolOrchestrator::new(); let mut runtime = ApplyPatchRuntime::new(); let tool_ctx = ToolCtx { - session: session.as_ref(), - turn: turn.as_ref(), + session: session.clone(), + turn: turn.clone(), call_id: call_id.clone(), tool_name: tool_name.to_string(), }; @@ -149,7 +150,7 @@ impl ToolHandler for ApplyPatchHandler { &mut runtime, &req, &tool_ctx, - &turn, + turn.as_ref(), turn.approval_policy.value(), ) .await @@ -193,8 +194,8 @@ pub(crate) async fn intercept_apply_patch( command: &[String], cwd: &Path, timeout_ms: Option, - session: &Session, - turn: &TurnContext, + session: Arc, + turn: Arc, tracker: Option<&SharedTurnDiffTracker>, call_id: &str, tool_name: &str, @@ -203,11 +204,13 @@ pub(crate) async fn intercept_apply_patch( codex_apply_patch::MaybeApplyPatchVerified::Body(changes) => { session .record_model_warning( - format!("apply_patch was requested via {tool_name}. Use the apply_patch tool instead of exec_command."), - turn, + format!( + "apply_patch was requested via {tool_name}. Use the apply_patch tool instead of exec_command." + ), + turn.as_ref(), ) .await; - match apply_patch::apply_patch(turn, changes).await { + match apply_patch::apply_patch(turn.as_ref(), changes).await { InternalApplyPatchInvocation::Output(item) => { let content = item?; Ok(Some(ToolOutput::Function { @@ -219,8 +222,12 @@ pub(crate) async fn intercept_apply_patch( let changes = convert_apply_patch_to_protocol(&apply.action); let approval_keys = file_paths_for_action(&apply.action); let emitter = ToolEmitter::apply_patch(changes.clone(), apply.auto_approved); - let event_ctx = - ToolEventCtx::new(session, turn, call_id, tracker.as_ref().copied()); + let event_ctx = ToolEventCtx::new( + session.as_ref(), + turn.as_ref(), + call_id, + tracker.as_ref().copied(), + ); emitter.begin(event_ctx).await; let req = ApplyPatchRequest { @@ -235,8 +242,8 @@ pub(crate) async fn intercept_apply_patch( let mut orchestrator = ToolOrchestrator::new(); let mut runtime = ApplyPatchRuntime::new(); let tool_ctx = ToolCtx { - session, - turn, + session: session.clone(), + turn: turn.clone(), call_id: call_id.to_string(), tool_name: tool_name.to_string(), }; @@ -245,13 +252,17 @@ pub(crate) async fn intercept_apply_patch( &mut runtime, &req, &tool_ctx, - turn, + turn.as_ref(), turn.approval_policy.value(), ) .await .map(|result| result.output); - let event_ctx = - ToolEventCtx::new(session, turn, call_id, tracker.as_ref().copied()); + let event_ctx = ToolEventCtx::new( + session.as_ref(), + turn.as_ref(), + call_id, + tracker.as_ref().copied(), + ); let content = emitter.finish(event_ctx, out).await?; Ok(Some(ToolOutput::Function { body: FunctionCallOutputBody::Text(content), diff --git a/codex-rs/core/src/tools/handlers/shell.rs b/codex-rs/core/src/tools/handlers/shell.rs index 4cdbf454c4..46a90681d2 100644 --- a/codex-rs/core/src/tools/handlers/shell.rs +++ b/codex-rs/core/src/tools/handlers/shell.rs @@ -296,8 +296,8 @@ impl ShellHandler { &exec_params.command, &exec_params.cwd, exec_params.expiration.timeout_ms(), - session.as_ref(), - turn.as_ref(), + session.clone(), + turn.clone(), Some(&tracker), &call_id, tool_name.as_str(), @@ -343,8 +343,8 @@ impl ShellHandler { let mut orchestrator = ToolOrchestrator::new(); let mut runtime = ShellRuntime::new(); let tool_ctx = ToolCtx { - session: session.as_ref(), - turn: turn.as_ref(), + session: session.clone(), + turn: turn.clone(), call_id: call_id.clone(), tool_name, }; diff --git a/codex-rs/core/src/tools/handlers/unified_exec.rs b/codex-rs/core/src/tools/handlers/unified_exec.rs index c7d57731e3..76dbade19e 100644 --- a/codex-rs/core/src/tools/handlers/unified_exec.rs +++ b/codex-rs/core/src/tools/handlers/unified_exec.rs @@ -172,8 +172,8 @@ impl ToolHandler for UnifiedExecHandler { &command, &cwd, Some(yield_time_ms), - context.session.as_ref(), - context.turn.as_ref(), + context.session.clone(), + context.turn.clone(), Some(&tracker), &context.call_id, tool_name.as_str(), diff --git a/codex-rs/core/src/tools/orchestrator.rs b/codex-rs/core/src/tools/orchestrator.rs index afb24d4b3c..db28c93131 100644 --- a/codex-rs/core/src/tools/orchestrator.rs +++ b/codex-rs/core/src/tools/orchestrator.rs @@ -48,7 +48,7 @@ impl ToolOrchestrator { async fn run_attempt( tool: &mut T, req: &Rq, - tool_ctx: &ToolCtx<'_>, + tool_ctx: &ToolCtx, attempt: &SandboxAttempt<'_>, has_managed_network_requirements: bool, ) -> (Result, Option) @@ -56,7 +56,7 @@ impl ToolOrchestrator { T: ToolRuntime, { let network_approval = begin_network_approval( - tool_ctx.session, + &tool_ctx.session, &tool_ctx.turn.sub_id, &tool_ctx.call_id, has_managed_network_requirements, @@ -65,8 +65,8 @@ impl ToolOrchestrator { .await; let attempt_tool_ctx = ToolCtx { - session: tool_ctx.session, - turn: tool_ctx.turn, + session: tool_ctx.session.clone(), + turn: tool_ctx.turn.clone(), call_id: tool_ctx.call_id.clone(), tool_name: tool_ctx.tool_name.clone(), }; @@ -79,7 +79,7 @@ impl ToolOrchestrator { match network_approval.mode() { NetworkApprovalMode::Immediate => { let finalize_result = - finish_immediate_network_approval(tool_ctx.session, network_approval).await; + finish_immediate_network_approval(&tool_ctx.session, network_approval).await; if let Err(err) = finalize_result { return (Err(err), None); } @@ -88,7 +88,7 @@ impl ToolOrchestrator { NetworkApprovalMode::Deferred => { let deferred = network_approval.into_deferred(); if run_result.is_err() { - finish_deferred_network_approval(tool_ctx.session, deferred).await; + finish_deferred_network_approval(&tool_ctx.session, deferred).await; return (run_result, None); } (run_result, deferred) @@ -100,7 +100,7 @@ impl ToolOrchestrator { &mut self, tool: &mut T, req: &Rq, - tool_ctx: &ToolCtx<'_>, + tool_ctx: &ToolCtx, turn_ctx: &crate::codex::TurnContext, approval_policy: AskForApproval, ) -> Result, ToolError> @@ -128,7 +128,7 @@ impl ToolOrchestrator { } ExecApprovalRequirement::NeedsApproval { reason, .. } => { let approval_ctx = ApprovalCtx { - session: tool_ctx.session, + session: &tool_ctx.session, turn: turn_ctx, call_id: &tool_ctx.call_id, retry_reason: reason, @@ -256,7 +256,7 @@ impl ToolOrchestrator { && network_approval_context.is_none(); if !bypass_retry_approval { let approval_ctx = ApprovalCtx { - session: tool_ctx.session, + session: &tool_ctx.session, turn: turn_ctx, call_id: &tool_ctx.call_id, retry_reason: Some(retry_reason), diff --git a/codex-rs/core/src/tools/runtimes/apply_patch.rs b/codex-rs/core/src/tools/runtimes/apply_patch.rs index d0ae2f61d7..749e1abd2d 100644 --- a/codex-rs/core/src/tools/runtimes/apply_patch.rs +++ b/codex-rs/core/src/tools/runtimes/apply_patch.rs @@ -70,7 +70,7 @@ impl ApplyPatchRuntime { }) } - fn stdout_stream(ctx: &ToolCtx<'_>) -> Option { + fn stdout_stream(ctx: &ToolCtx) -> Option { Some(crate::exec::StdoutStream { sub_id: ctx.turn.sub_id.clone(), call_id: ctx.call_id.clone(), @@ -156,7 +156,7 @@ impl ToolRuntime for ApplyPatchRuntime { &mut self, req: &ApplyPatchRequest, attempt: &SandboxAttempt<'_>, - ctx: &ToolCtx<'_>, + ctx: &ToolCtx, ) -> Result { let spec = Self::build_command_spec(req)?; let env = attempt diff --git a/codex-rs/core/src/tools/runtimes/shell.rs b/codex-rs/core/src/tools/runtimes/shell.rs index bcbeafadfe..3daf5e2ed2 100644 --- a/codex-rs/core/src/tools/runtimes/shell.rs +++ b/codex-rs/core/src/tools/runtimes/shell.rs @@ -5,7 +5,11 @@ Executes shell requests under the orchestrator: asks for approval when needed, builds a CommandSpec, and runs it under the current SandboxAttempt. */ use crate::command_canonicalization::canonicalize_command_for_approval; +use crate::error::CodexErr; +use crate::error::SandboxErr; use crate::exec::ExecToolCallOutput; +use crate::exec::SandboxType; +use crate::exec::is_likely_sandbox_denied; use crate::features::Feature; use crate::powershell::prefix_powershell_script_with_utf8; use crate::sandboxing::SandboxPermissions; @@ -26,19 +30,56 @@ use crate::tools::sandboxing::ToolCtx; use crate::tools::sandboxing::ToolError; use crate::tools::sandboxing::ToolRuntime; use crate::tools::sandboxing::with_cached_approval; -use crate::zsh_exec_bridge::ZSH_EXEC_BRIDGE_WRAPPER_SOCKET_ENV_VAR; +use codex_execpolicy::Decision; +use codex_execpolicy::Policy; +use codex_execpolicy::RuleMatch; use codex_network_proxy::NetworkProxy; +use codex_protocol::config_types::WindowsSandboxLevel; +use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::ReviewDecision; +use codex_protocol::protocol::SandboxPolicy; +use codex_shell_command::bash::parse_shell_lc_plain_commands; +use codex_shell_command::bash::parse_shell_lc_single_command_prefix; +#[cfg(unix)] +use codex_shell_escalation::unix::core_shell_escalation::ShellActionProvider; +#[cfg(unix)] +use codex_shell_escalation::unix::core_shell_escalation::ShellPolicyFactory; +#[cfg(unix)] +use codex_shell_escalation::unix::escalate_protocol::EscalateAction; +#[cfg(unix)] +use codex_shell_escalation::unix::escalate_server::ExecParams; +#[cfg(unix)] +use codex_shell_escalation::unix::escalate_server::ExecResult; +#[cfg(unix)] +use codex_shell_escalation::unix::escalate_server::SandboxState; +#[cfg(unix)] +use codex_shell_escalation::unix::escalate_server::ShellCommandExecutor; +#[cfg(unix)] +use codex_shell_escalation::unix::escalate_server::run_escalate_server; +#[cfg(unix)] +use codex_shell_escalation::unix::stopwatch::Stopwatch; +#[cfg(unix)] +use codex_utils_absolute_path::AbsolutePathBuf; use futures::future::BoxFuture; +use shlex::try_join as shlex_try_join; +use std::collections::HashMap; +use std::path::Path; use std::path::PathBuf; +#[cfg(unix)] +use std::sync::Arc; +#[cfg(unix)] +use std::time::Duration; +#[cfg(unix)] +use tokio::sync::RwLock; +use tokio_util::sync::CancellationToken; #[derive(Clone, Debug)] pub struct ShellRequest { pub command: Vec, pub cwd: PathBuf, pub timeout_ms: Option, - pub env: std::collections::HashMap, - pub explicit_env_overrides: std::collections::HashMap, + pub env: HashMap, + pub explicit_env_overrides: HashMap, pub network: Option, pub sandbox_permissions: SandboxPermissions, pub justification: Option, @@ -60,7 +101,7 @@ impl ShellRuntime { Self } - fn stdout_stream(ctx: &ToolCtx<'_>) -> Option { + fn stdout_stream(ctx: &ToolCtx) -> Option { Some(crate::exec::StdoutStream { sub_id: ctx.turn.sub_id.clone(), call_id: ctx.call_id.clone(), @@ -73,6 +114,7 @@ impl Sandboxable for ShellRuntime { fn sandbox_preference(&self) -> SandboxablePreference { SandboxablePreference::Auto } + fn escalate_on_failure(&self) -> bool { true } @@ -146,11 +188,214 @@ impl Approvable for ShellRuntime { } } +#[cfg(unix)] +struct CoreShellActionProvider { + policy: Arc>, + session: std::sync::Arc, + turn: std::sync::Arc, + call_id: String, + approval_policy: AskForApproval, + sandbox_policy: SandboxPolicy, + sandbox_permissions: SandboxPermissions, +} + +#[cfg(unix)] +impl CoreShellActionProvider { + fn decision_driven_by_policy(matched_rules: &[RuleMatch], decision: Decision) -> bool { + matched_rules.iter().any(|rule_match| { + !matches!(rule_match, RuleMatch::HeuristicsRuleMatch { .. }) + && rule_match.decision() == decision + }) + } + + async fn prompt( + &self, + command: &[String], + workdir: &Path, + stopwatch: &Stopwatch, + ) -> anyhow::Result { + let command = command.to_vec(); + let workdir = workdir.to_path_buf(); + let session = self.session.clone(); + let turn = self.turn.clone(); + let call_id = self.call_id.clone(); + Ok(stopwatch + .pause_for(async move { + session + .request_command_approval( + &turn, call_id, None, command, workdir, None, None, None, + ) + .await + }) + .await) + } +} + +#[cfg(unix)] +#[async_trait::async_trait] +impl ShellActionProvider for CoreShellActionProvider { + async fn determine_action( + &self, + file: &Path, + argv: &[String], + workdir: &Path, + stopwatch: &Stopwatch, + ) -> anyhow::Result { + let command = std::iter::once(file.to_string_lossy().to_string()) + .chain(argv.iter().cloned()) + .collect::>(); + let (commands, used_complex_parsing) = + if let Some(commands) = parse_shell_lc_plain_commands(&command) { + (commands, false) + } else if let Some(single_command) = parse_shell_lc_single_command_prefix(&command) { + (vec![single_command], true) + } else { + (vec![command.clone()], false) + }; + + let policy = self.policy.read().await; + let fallback = |cmd: &[String]| { + crate::exec_policy::render_decision_for_unmatched_command( + self.approval_policy, + &self.sandbox_policy, + cmd, + self.sandbox_permissions, + used_complex_parsing, + ) + }; + let evaluation = policy.check_multiple(commands.iter(), &fallback); + let decision_driven_by_policy = + Self::decision_driven_by_policy(&evaluation.matched_rules, evaluation.decision); + let needs_escalation = + self.sandbox_permissions.requires_escalated_permissions() || decision_driven_by_policy; + + Ok(match evaluation.decision { + Decision::Forbidden => EscalateAction::Deny { + reason: Some("Execution forbidden by policy".to_string()), + }, + Decision::Prompt => { + if self.approval_policy == AskForApproval::Never { + EscalateAction::Deny { + reason: Some("Execution forbidden by policy".to_string()), + } + } else if decision_driven_by_policy { + EscalateAction::Escalate + } else { + match self.prompt(&command, workdir, stopwatch).await? { + ReviewDecision::Approved + | ReviewDecision::ApprovedExecpolicyAmendment { .. } + | ReviewDecision::ApprovedForSession => { + if needs_escalation { + EscalateAction::Escalate + } else { + EscalateAction::Run + } + } + ReviewDecision::Denied => EscalateAction::Deny { + reason: Some("User denied execution".to_string()), + }, + ReviewDecision::Abort => EscalateAction::Deny { + reason: Some("User cancelled execution".to_string()), + }, + } + } + } + Decision::Allow => EscalateAction::Run, + }) + } +} + +#[cfg(unix)] +struct CoreShellCommandExecutor; + +#[cfg(unix)] +#[async_trait::async_trait] +impl ShellCommandExecutor for CoreShellCommandExecutor { + async fn run( + &self, + command: Vec, + cwd: PathBuf, + env: HashMap, + cancel_rx: CancellationToken, + sandbox_state: &SandboxState, + ) -> anyhow::Result { + let result = crate::exec::process_exec_tool_call( + crate::exec::ExecParams { + command, + cwd, + expiration: crate::exec::ExecExpiration::Cancellation(cancel_rx), + env, + network: None, + sandbox_permissions: SandboxPermissions::UseDefault, + windows_sandbox_level: WindowsSandboxLevel::Disabled, + justification: None, + arg0: None, + }, + &sandbox_state.sandbox_policy, + &sandbox_state.sandbox_cwd, + &sandbox_state.codex_linux_sandbox_exe, + sandbox_state.use_linux_sandbox_bwrap, + None, + ) + .await?; + + Ok(ExecResult { + exit_code: result.exit_code, + output: result.aggregated_output.text, + duration: result.duration, + timed_out: result.timed_out, + }) + } +} + +#[cfg(unix)] +fn shell_execve_wrapper() -> anyhow::Result { + let exe = std::env::current_exe()?; + exe.parent() + .map(|parent| parent.join("codex-execve-wrapper")) + .ok_or_else(|| anyhow::anyhow!("failed to determine codex-execve-wrapper path")) +} + +#[cfg(unix)] +fn shell_exec_zsh_path(path: &AbsolutePathBuf) -> PathBuf { + path.to_path_buf() +} + +#[cfg(unix)] +fn map_exec_result( + sandbox: SandboxType, + result: ExecResult, +) -> Result { + let output = ExecToolCallOutput { + exit_code: result.exit_code, + stdout: crate::exec::StreamOutput::new(result.output.clone()), + stderr: crate::exec::StreamOutput::new(String::new()), + aggregated_output: crate::exec::StreamOutput::new(result.output.clone()), + duration: result.duration, + timed_out: result.timed_out, + }; + + if result.timed_out { + return Err(ToolError::Codex(CodexErr::Sandbox(SandboxErr::Timeout { + output: Box::new(output), + }))); + } + + if is_likely_sandbox_denied(sandbox, &output) { + return Err(ToolError::Codex(CodexErr::Sandbox(SandboxErr::Denied { + output: Box::new(output), + network_policy_decision: None, + }))); + } + + Ok(output) +} + impl ToolRuntime for ShellRuntime { fn network_approval_spec( &self, req: &ShellRequest, - _ctx: &ToolCtx<'_>, + _ctx: &ToolCtx, ) -> Option { req.network.as_ref()?; Some(NetworkApprovalSpec { @@ -163,17 +408,15 @@ impl ToolRuntime for ShellRuntime { &mut self, req: &ShellRequest, attempt: &SandboxAttempt<'_>, - ctx: &ToolCtx<'_>, + ctx: &ToolCtx, ) -> Result { - let base_command = &req.command; - let session_shell = ctx.session.user_shell(); let command = maybe_wrap_shell_lc_with_snapshot( - base_command, - session_shell.as_ref(), + &req.command, + ctx.session.user_shell().as_ref(), &req.cwd, &req.explicit_env_overrides, ); - let command = if matches!(session_shell.shell_type, ShellType::PowerShell) + let command = if matches!(ctx.session.user_shell().shell_type, ShellType::PowerShell) && ctx.session.features().enabled(Feature::PowershellUtf8) { prefix_powershell_script_with_utf8(&command) @@ -181,21 +424,15 @@ impl ToolRuntime for ShellRuntime { command }; - if ctx.session.features().enabled(Feature::ShellZshFork) { - let wrapper_socket_path = ctx - .session - .services - .zsh_exec_bridge - .next_wrapper_socket_path(); - let mut zsh_fork_env = req.env.clone(); - zsh_fork_env.insert( - ZSH_EXEC_BRIDGE_WRAPPER_SOCKET_ENV_VAR.to_string(), - wrapper_socket_path.to_string_lossy().to_string(), - ); + #[cfg(unix)] + if let Some(shell_zsh_path) = ctx.session.services.shell_zsh_path.as_ref() + && ctx.session.features().enabled(Feature::ShellZshFork) + && matches!(ctx.session.user_shell().shell_type, ShellType::Zsh) + { let spec = build_command_spec( &command, &req.cwd, - &zsh_fork_env, + &req.env, req.timeout_ms.into(), req.sandbox_permissions, req.justification.clone(), @@ -203,12 +440,52 @@ impl ToolRuntime for ShellRuntime { let env = attempt .env_for(spec, req.network.as_ref()) .map_err(|err| ToolError::Codex(err.into()))?; - return ctx - .session - .services - .zsh_exec_bridge - .execute_shell_request(&env, ctx.session, ctx.turn, &ctx.call_id) - .await; + let (_, args) = env + .command + .split_first() + .ok_or_else(|| ToolError::Rejected("command args are empty".to_string()))?; + let script = shlex_try_join(args.iter().map(String::as_str)) + .map_err(|err| ToolError::Rejected(format!("serialize shell script: {err}")))?; + let effective_timeout = Duration::from_millis( + req.timeout_ms + .unwrap_or(crate::exec::DEFAULT_EXEC_COMMAND_TIMEOUT_MS), + ); + let exec_policy = Arc::new(RwLock::new( + ctx.session.services.exec_policy.current().as_ref().clone(), + )); + let sandbox_state = SandboxState { + sandbox_policy: ctx.turn.sandbox_policy.get().clone(), + codex_linux_sandbox_exe: attempt.codex_linux_sandbox_exe.cloned(), + sandbox_cwd: req.cwd.clone(), + use_linux_sandbox_bwrap: attempt.use_linux_sandbox_bwrap, + }; + let exec_result = run_escalate_server( + ExecParams { + command: script, + workdir: req.cwd.to_string_lossy().to_string(), + timeout_ms: Some(effective_timeout.as_millis() as u64), + login: Some(false), + }, + &sandbox_state, + shell_exec_zsh_path(shell_zsh_path), + shell_execve_wrapper().map_err(|err| ToolError::Rejected(format!("{err}")))?, + exec_policy.clone(), + ShellPolicyFactory::new(CoreShellActionProvider { + policy: Arc::clone(&exec_policy), + session: Arc::clone(&ctx.session), + turn: Arc::clone(&ctx.turn), + call_id: ctx.call_id.clone(), + approval_policy: ctx.turn.approval_policy.value(), + sandbox_policy: attempt.policy.clone(), + sandbox_permissions: req.sandbox_permissions, + }), + effective_timeout, + &CoreShellCommandExecutor, + ) + .await + .map_err(|err| ToolError::Rejected(err.to_string()))?; + + return map_exec_result(attempt.sandbox, exec_result); } let spec = build_command_spec( diff --git a/codex-rs/core/src/tools/runtimes/unified_exec.rs b/codex-rs/core/src/tools/runtimes/unified_exec.rs index 201de7ad84..5e06ca3975 100644 --- a/codex-rs/core/src/tools/runtimes/unified_exec.rs +++ b/codex-rs/core/src/tools/runtimes/unified_exec.rs @@ -153,7 +153,7 @@ impl<'a> ToolRuntime for UnifiedExecRunt fn network_approval_spec( &self, req: &UnifiedExecRequest, - _ctx: &ToolCtx<'_>, + _ctx: &ToolCtx, ) -> Option { req.network.as_ref()?; Some(NetworkApprovalSpec { @@ -166,7 +166,7 @@ impl<'a> ToolRuntime for UnifiedExecRunt &mut self, req: &UnifiedExecRequest, attempt: &SandboxAttempt<'_>, - ctx: &ToolCtx<'_>, + ctx: &ToolCtx, ) -> Result { let base_command = &req.command; let session_shell = ctx.session.user_shell(); diff --git a/codex-rs/core/src/tools/sandboxing.rs b/codex-rs/core/src/tools/sandboxing.rs index 9721fed535..540eb16434 100644 --- a/codex-rs/core/src/tools/sandboxing.rs +++ b/codex-rs/core/src/tools/sandboxing.rs @@ -18,14 +18,14 @@ use codex_protocol::approvals::ExecPolicyAmendment; use codex_protocol::approvals::NetworkApprovalContext; use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::ReviewDecision; +use futures::Future; +use futures::future::BoxFuture; +use serde::Serialize; use std::collections::HashMap; use std::fmt::Debug; use std::hash::Hash; use std::path::Path; - -use futures::Future; -use futures::future::BoxFuture; -use serde::Serialize; +use std::sync::Arc; #[derive(Clone, Default, Debug)] pub(crate) struct ApprovalStore { @@ -267,9 +267,9 @@ pub(crate) trait Sandboxable { } } -pub(crate) struct ToolCtx<'a> { - pub session: &'a Session, - pub turn: &'a TurnContext, +pub(crate) struct ToolCtx { + pub session: Arc, + pub turn: Arc, pub call_id: String, pub tool_name: String, } @@ -281,7 +281,7 @@ pub(crate) enum ToolError { } pub(crate) trait ToolRuntime: Approvable + Sandboxable { - fn network_approval_spec(&self, _req: &Req, _ctx: &ToolCtx<'_>) -> Option { + fn network_approval_spec(&self, _req: &Req, _ctx: &ToolCtx) -> Option { None } diff --git a/codex-rs/core/src/unified_exec/process_manager.rs b/codex-rs/core/src/unified_exec/process_manager.rs index 68c4f95ceb..a3da4ba6ba 100644 --- a/codex-rs/core/src/unified_exec/process_manager.rs +++ b/codex-rs/core/src/unified_exec/process_manager.rs @@ -594,8 +594,8 @@ impl UnifiedExecProcessManager { exec_approval_requirement, }; let tool_ctx = ToolCtx { - session: context.session.as_ref(), - turn: context.turn.as_ref(), + session: context.session.clone(), + turn: context.turn.clone(), call_id: context.call_id.clone(), tool_name: "exec_command".to_string(), }; @@ -604,7 +604,7 @@ impl UnifiedExecProcessManager { &mut runtime, &req, &tool_ctx, - context.turn.as_ref(), + &context.turn, context.turn.approval_policy.value(), ) .await