Compare commits

...

1 Commits

Author SHA1 Message Date
Michael Bolin
86bfa68e42 Use Arc-based ToolCtx in tool runtimes 2026-02-23 09:52:41 -08:00
9 changed files with 362 additions and 74 deletions

View File

@@ -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<u64>,
session: &Session,
turn: &TurnContext,
session: Arc<Session>,
turn: Arc<TurnContext>,
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),

View File

@@ -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,
};

View File

@@ -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(),

View File

@@ -48,7 +48,7 @@ impl ToolOrchestrator {
async fn run_attempt<Rq, Out, T>(
tool: &mut T,
req: &Rq,
tool_ctx: &ToolCtx<'_>,
tool_ctx: &ToolCtx,
attempt: &SandboxAttempt<'_>,
has_managed_network_requirements: bool,
) -> (Result<Out, ToolError>, Option<DeferredNetworkApproval>)
@@ -56,7 +56,7 @@ impl ToolOrchestrator {
T: ToolRuntime<Rq, Out>,
{
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<OrchestratorRunResult<Out>, 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),

View File

@@ -70,7 +70,7 @@ impl ApplyPatchRuntime {
})
}
fn stdout_stream(ctx: &ToolCtx<'_>) -> Option<crate::exec::StdoutStream> {
fn stdout_stream(ctx: &ToolCtx) -> Option<crate::exec::StdoutStream> {
Some(crate::exec::StdoutStream {
sub_id: ctx.turn.sub_id.clone(),
call_id: ctx.call_id.clone(),
@@ -156,7 +156,7 @@ impl ToolRuntime<ApplyPatchRequest, ExecToolCallOutput> for ApplyPatchRuntime {
&mut self,
req: &ApplyPatchRequest,
attempt: &SandboxAttempt<'_>,
ctx: &ToolCtx<'_>,
ctx: &ToolCtx,
) -> Result<ExecToolCallOutput, ToolError> {
let spec = Self::build_command_spec(req)?;
let env = attempt

View File

@@ -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<String>,
pub cwd: PathBuf,
pub timeout_ms: Option<u64>,
pub env: std::collections::HashMap<String, String>,
pub explicit_env_overrides: std::collections::HashMap<String, String>,
pub env: HashMap<String, String>,
pub explicit_env_overrides: HashMap<String, String>,
pub network: Option<NetworkProxy>,
pub sandbox_permissions: SandboxPermissions,
pub justification: Option<String>,
@@ -60,7 +101,7 @@ impl ShellRuntime {
Self
}
fn stdout_stream(ctx: &ToolCtx<'_>) -> Option<crate::exec::StdoutStream> {
fn stdout_stream(ctx: &ToolCtx) -> Option<crate::exec::StdoutStream> {
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<ShellRequest> for ShellRuntime {
}
}
#[cfg(unix)]
struct CoreShellActionProvider {
policy: Arc<RwLock<Policy>>,
session: std::sync::Arc<crate::codex::Session>,
turn: std::sync::Arc<crate::codex::TurnContext>,
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<ReviewDecision> {
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<EscalateAction> {
let command = std::iter::once(file.to_string_lossy().to_string())
.chain(argv.iter().cloned())
.collect::<Vec<_>>();
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<String>,
cwd: PathBuf,
env: HashMap<String, String>,
cancel_rx: CancellationToken,
sandbox_state: &SandboxState,
) -> anyhow::Result<ExecResult> {
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<PathBuf> {
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<ExecToolCallOutput, ToolError> {
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<ShellRequest, ExecToolCallOutput> for ShellRuntime {
fn network_approval_spec(
&self,
req: &ShellRequest,
_ctx: &ToolCtx<'_>,
_ctx: &ToolCtx,
) -> Option<NetworkApprovalSpec> {
req.network.as_ref()?;
Some(NetworkApprovalSpec {
@@ -163,17 +408,15 @@ impl ToolRuntime<ShellRequest, ExecToolCallOutput> for ShellRuntime {
&mut self,
req: &ShellRequest,
attempt: &SandboxAttempt<'_>,
ctx: &ToolCtx<'_>,
ctx: &ToolCtx,
) -> Result<ExecToolCallOutput, ToolError> {
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<ShellRequest, ExecToolCallOutput> 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<ShellRequest, ExecToolCallOutput> 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(

View File

@@ -153,7 +153,7 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecProcess> for UnifiedExecRunt
fn network_approval_spec(
&self,
req: &UnifiedExecRequest,
_ctx: &ToolCtx<'_>,
_ctx: &ToolCtx,
) -> Option<NetworkApprovalSpec> {
req.network.as_ref()?;
Some(NetworkApprovalSpec {
@@ -166,7 +166,7 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecProcess> for UnifiedExecRunt
&mut self,
req: &UnifiedExecRequest,
attempt: &SandboxAttempt<'_>,
ctx: &ToolCtx<'_>,
ctx: &ToolCtx,
) -> Result<UnifiedExecProcess, ToolError> {
let base_command = &req.command;
let session_shell = ctx.session.user_shell();

View File

@@ -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<Session>,
pub turn: Arc<TurnContext>,
pub call_id: String,
pub tool_name: String,
}
@@ -281,7 +281,7 @@ pub(crate) enum ToolError {
}
pub(crate) trait ToolRuntime<Req, Out>: Approvable<Req> + Sandboxable {
fn network_approval_spec(&self, _req: &Req, _ctx: &ToolCtx<'_>) -> Option<NetworkApprovalSpec> {
fn network_approval_spec(&self, _req: &Req, _ctx: &ToolCtx) -> Option<NetworkApprovalSpec> {
None
}

View File

@@ -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