refactor(core): resolve unified exec executor inputs in handler

This commit is contained in:
Michael Bolin
2026-03-21 08:24:12 -07:00
parent 3412bcd33f
commit 21b80aff41
6 changed files with 223 additions and 166 deletions

View File

@@ -1,9 +1,12 @@
use crate::exec_env::create_env;
use crate::function_tool::FunctionCallError;
use crate::is_safe_command::is_known_safe_command;
use crate::powershell::prefix_powershell_script_with_utf8;
use crate::protocol::EventMsg;
use crate::protocol::TerminalInteractionEvent;
use crate::sandboxing::SandboxPermissions;
use crate::shell::Shell;
use crate::shell::ShellType;
use crate::shell::get_shell_by_model_provided_path;
use crate::skills::maybe_emit_implicit_skill_invocation;
use crate::tools::context::ExecCommandToolOutput;
@@ -18,13 +21,18 @@ use crate::tools::handlers::parse_arguments_with_base_path;
use crate::tools::handlers::resolve_workdir_base_path;
use crate::tools::registry::ToolHandler;
use crate::tools::registry::ToolKind;
use crate::tools::runtimes::maybe_wrap_shell_lc_with_snapshot;
use crate::tools::spec::UnifiedExecShellMode;
use crate::unified_exec::ExecCommandRequest;
use crate::unified_exec::UnifiedExecContext;
use crate::unified_exec::UnifiedExecProcessManager;
use crate::unified_exec::WriteStdinRequest;
use crate::unified_exec::apply_unified_exec_env;
use crate::unified_exec::into_wire_exec_approval_requirement;
use crate::unified_exec::resolve_max_tokens;
use async_trait::async_trait;
use codex_features::Feature;
use codex_protocol::executor::UnifiedExecExecCommandRequest;
use codex_protocol::executor::UnifiedExecWriteStdinRequest;
use codex_protocol::models::PermissionProfile;
use serde::Deserialize;
use std::path::PathBuf;
@@ -152,7 +160,6 @@ impl ToolHandler for UnifiedExecHandler {
args.workdir.as_deref(),
)
.await;
let process_id = manager.allocate_process_id().await;
let command = get_command(
&args,
session.user_shell(),
@@ -163,7 +170,7 @@ impl ToolHandler for UnifiedExecHandler {
let command_for_display = codex_shell_command::parse_command::shlex_join(&command);
let ExecCommandArgs {
workdir,
workdir: _,
tty,
yield_time_ms,
max_output_tokens,
@@ -199,16 +206,13 @@ impl ToolHandler for UnifiedExecHandler {
)
{
let approval_policy = context.turn.approval_policy.value();
manager.release_process_id(process_id).await;
return Err(FunctionCallError::RespondToModel(format!(
"approval policy is {approval_policy:?}; reject command — you cannot ask for escalated permissions if the approval policy is {approval_policy:?}"
)));
}
let workdir = workdir.filter(|value| !value.is_empty());
let workdir = workdir.map(|dir| context.turn.resolve_path(Some(dir)));
let cwd = workdir.clone().unwrap_or(cwd);
let explicit_env_overrides = context.turn.shell_environment_policy.r#set.clone();
let cwd = cwd.clone();
let normalized_additional_permissions = match implicit_granted_permissions(
sandbox_permissions,
requested_additional_permissions.as_ref(),
@@ -229,7 +233,6 @@ impl ToolHandler for UnifiedExecHandler {
) {
Ok(normalized) => normalized,
Err(err) => {
manager.release_process_id(process_id).await;
return Err(FunctionCallError::RespondToModel(err));
}
};
@@ -246,7 +249,6 @@ impl ToolHandler for UnifiedExecHandler {
)
.await?
{
manager.release_process_id(process_id).await;
return Ok(ExecCommandToolOutput {
event_call_id: String::new(),
chunk_id: String::new(),
@@ -260,23 +262,61 @@ impl ToolHandler for UnifiedExecHandler {
});
}
let exec_approval_requirement = context
.session
.services
.exec_policy
.create_exec_approval_requirement_for_command(
crate::exec_policy::ExecApprovalRequest {
command: &command,
approval_policy: context.turn.approval_policy.value(),
sandbox_policy: context.turn.sandbox_policy.get(),
file_system_sandbox_policy: &context.turn.file_system_sandbox_policy,
sandbox_permissions: if effective_additional_permissions
.permissions_preapproved
{
crate::sandboxing::SandboxPermissions::UseDefault
} else {
effective_additional_permissions.sandbox_permissions
},
prefix_rule: prefix_rule.clone(),
},
)
.await;
let mut command = maybe_wrap_shell_lc_with_snapshot(
&command,
session.user_shell().as_ref(),
cwd.as_path(),
&explicit_env_overrides,
);
if matches!(session.user_shell().shell_type, ShellType::PowerShell) {
command = prefix_powershell_script_with_utf8(&command);
}
let env = apply_unified_exec_env(create_env(
&context.turn.shell_environment_policy,
Some(context.session.conversation_id),
));
manager
.exec_command(
ExecCommandRequest {
command,
process_id,
yield_time_ms,
max_output_tokens,
workdir,
wire_request: UnifiedExecExecCommandRequest {
command,
cwd,
env,
tty,
yield_time_ms,
max_output_tokens: resolve_max_tokens(max_output_tokens),
sandbox_permissions: effective_additional_permissions
.sandbox_permissions,
additional_permissions: normalized_additional_permissions,
additional_permissions_preapproved:
effective_additional_permissions.permissions_preapproved,
justification,
exec_approval_requirement: into_wire_exec_approval_requirement(
exec_approval_requirement,
),
},
network: context.turn.network.clone(),
tty,
sandbox_permissions: effective_additional_permissions
.sandbox_permissions,
additional_permissions: normalized_additional_permissions,
additional_permissions_preapproved: effective_additional_permissions
.permissions_preapproved,
justification,
prefix_rule,
},
&context,
)
@@ -290,11 +330,11 @@ impl ToolHandler for UnifiedExecHandler {
"write_stdin" => {
let args: WriteStdinArgs = parse_arguments(&arguments)?;
let response = manager
.write_stdin(WriteStdinRequest {
.write_stdin(UnifiedExecWriteStdinRequest {
process_id: args.session_id,
input: &args.chars,
input: args.chars.clone(),
yield_time_ms: args.yield_time_ms,
max_output_tokens: args.max_output_tokens,
max_output_tokens: resolve_max_tokens(args.max_output_tokens),
})
.await
.map_err(|err| {

View File

@@ -11,13 +11,10 @@ use crate::exec::ExecExpiration;
use crate::guardian::GuardianApprovalRequest;
use crate::guardian::review_approval_request;
use crate::guardian::routes_approval_to_guardian;
use crate::powershell::prefix_powershell_script_with_utf8;
use crate::sandboxing::SandboxPermissions;
use crate::shell::ShellType;
use crate::tools::network_approval::NetworkApprovalMode;
use crate::tools::network_approval::NetworkApprovalSpec;
use crate::tools::runtimes::build_command_spec;
use crate::tools::runtimes::maybe_wrap_shell_lc_with_snapshot;
use crate::tools::runtimes::shell::zsh_fork_backend;
use crate::tools::sandboxing::Approvable;
use crate::tools::sandboxing::ApprovalCtx;
@@ -48,7 +45,6 @@ pub struct UnifiedExecRequest {
pub command: Vec<String>,
pub cwd: PathBuf,
pub env: HashMap<String, String>,
pub explicit_env_overrides: HashMap<String, String>,
pub network: Option<NetworkProxy>,
pub tty: bool,
pub sandbox_permissions: SandboxPermissions,
@@ -191,20 +187,7 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecProcess> for UnifiedExecRunt
attempt: &SandboxAttempt<'_>,
ctx: &ToolCtx,
) -> Result<UnifiedExecProcess, 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.cwd,
&req.explicit_env_overrides,
);
let command = if matches!(session_shell.shell_type, ShellType::PowerShell) {
prefix_powershell_script_with_utf8(&command)
} else {
command
};
let command = req.command.clone();
let mut env = req.env.clone();
if let Some(network) = req.network.as_ref() {
network.apply_to_env(&mut env);

View File

@@ -4,17 +4,21 @@
//! - Manages interactive processes (create, reuse, buffer output with caps).
//! - Uses the shared ToolOrchestrator to handle approval, sandbox selection, and
//! retry semantics in a single, descriptive flow.
//! - Consumes a handler-built executor request so shell/env/output defaults are
//! resolved before the process manager runs, while the manager itself owns
//! unified-exec session ids.
//! - Spawns the PTY from a sandbox-transformed `ExecRequest`; on sandbox denial,
//! retries without sandbox when policy allows (no reprompt thanks to caching).
//! - Uses the shared `is_likely_sandbox_denied` heuristic to keep denial messages
//! consistent with other exec paths.
//!
//! Flow at a glance (open process)
//! 1) Build a small request `{ command, cwd }`.
//! 2) Orchestrator: approval (bypass/cache/prompt) → select sandbox → run.
//! 3) Runtime: transform `CommandSpec` -> `ExecRequest` -> spawn PTY.
//! 4) If denial, orchestrator retries with `SandboxType::None`.
//! 5) Process handle is returned with streaming output + metadata.
//! 1) Build a resolved executor request `{ command, cwd, env, approval, ... }`.
//! 2) Manager allocates a unified-exec session id.
//! 3) Orchestrator: approval (bypass/cache/prompt) → select sandbox → run.
//! 4) Runtime: transform `CommandSpec` -> `ExecRequest` -> spawn PTY.
//! 5) If denial, orchestrator retries with `SandboxType::None`.
//! 6) Process handle is returned with streaming output + metadata.
//!
//! This keeps policy logic and user interaction centralized while the PTY/process
//! concerns remain isolated here. The implementation is split between:
@@ -23,19 +27,21 @@
use std::collections::HashMap;
use std::collections::HashSet;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::Weak;
use codex_network_proxy::NetworkProxy;
use codex_protocol::models::PermissionProfile;
use codex_protocol::approvals::ExecPolicyAmendment;
use codex_protocol::executor::UnifiedExecApprovalRequirement;
use codex_protocol::executor::UnifiedExecExecCommandRequest;
use codex_protocol::executor::UnifiedExecWriteStdinRequest;
use rand::Rng;
use rand::rng;
use tokio::sync::Mutex;
use crate::codex::Session;
use crate::codex::TurnContext;
use crate::sandboxing::SandboxPermissions;
use crate::tools::sandboxing::ExecApprovalRequirement;
mod async_watcher;
mod errors;
@@ -63,6 +69,18 @@ pub(crate) const DEFAULT_MAX_OUTPUT_TOKENS: usize = 10_000;
pub(crate) const UNIFIED_EXEC_OUTPUT_MAX_BYTES: usize = 1024 * 1024; // 1 MiB
pub(crate) const UNIFIED_EXEC_OUTPUT_MAX_TOKENS: usize = UNIFIED_EXEC_OUTPUT_MAX_BYTES / 4;
pub(crate) const MAX_UNIFIED_EXEC_PROCESSES: usize = 64;
const UNIFIED_EXEC_ENV: [(&str, &str); 10] = [
("NO_COLOR", "1"),
("TERM", "dumb"),
("LANG", "C.UTF-8"),
("LC_CTYPE", "C.UTF-8"),
("LC_ALL", "C.UTF-8"),
("COLORTERM", ""),
("PAGER", "cat"),
("GIT_PAGER", "cat"),
("GH_PAGER", "cat"),
("CODEX_CI", "1"),
];
// Send a warning message to the models when it reaches this number of processes.
pub(crate) const WARNING_UNIFIED_EXEC_PROCESSES: usize = 60;
@@ -85,27 +103,11 @@ impl UnifiedExecContext {
#[derive(Debug)]
pub(crate) struct ExecCommandRequest {
pub command: Vec<String>,
pub process_id: i32,
pub yield_time_ms: u64,
pub max_output_tokens: Option<usize>,
pub workdir: Option<PathBuf>,
pub wire_request: UnifiedExecExecCommandRequest,
pub network: Option<NetworkProxy>,
pub tty: bool,
pub sandbox_permissions: SandboxPermissions,
pub additional_permissions: Option<PermissionProfile>,
pub additional_permissions_preapproved: bool,
pub justification: Option<String>,
pub prefix_rule: Option<Vec<String>>,
}
#[derive(Debug)]
pub(crate) struct WriteStdinRequest<'a> {
pub process_id: i32,
pub input: &'a str,
pub yield_time_ms: u64,
pub max_output_tokens: Option<usize>,
}
pub(crate) type WriteStdinRequest = UnifiedExecWriteStdinRequest;
#[derive(Default)]
pub(crate) struct ProcessStore {
@@ -160,6 +162,73 @@ pub(crate) fn resolve_max_tokens(max_tokens: Option<usize>) -> usize {
max_tokens.unwrap_or(DEFAULT_MAX_OUTPUT_TOKENS)
}
pub(crate) fn apply_unified_exec_env(mut env: HashMap<String, String>) -> HashMap<String, String> {
for (key, value) in UNIFIED_EXEC_ENV {
env.insert(key.to_string(), value.to_string());
}
env
}
pub(crate) fn into_wire_exec_approval_requirement(
requirement: ExecApprovalRequirement,
) -> UnifiedExecApprovalRequirement {
match requirement {
ExecApprovalRequirement::Skip {
bypass_sandbox,
proposed_execpolicy_amendment,
} => UnifiedExecApprovalRequirement::Skip {
bypass_sandbox,
proposed_exec_policy_amendment: proposed_execpolicy_amendment,
},
ExecApprovalRequirement::NeedsApproval {
reason,
proposed_execpolicy_amendment,
} => UnifiedExecApprovalRequirement::NeedsApproval {
reason,
proposed_exec_policy_amendment: proposed_execpolicy_amendment,
},
ExecApprovalRequirement::Forbidden { reason } => {
UnifiedExecApprovalRequirement::Forbidden { reason }
}
}
}
pub(crate) fn from_wire_exec_approval_requirement(
requirement: &UnifiedExecApprovalRequirement,
) -> ExecApprovalRequirement {
match requirement {
UnifiedExecApprovalRequirement::Skip {
bypass_sandbox,
proposed_exec_policy_amendment,
} => ExecApprovalRequirement::Skip {
bypass_sandbox: *bypass_sandbox,
proposed_execpolicy_amendment: clone_exec_policy_amendment(
proposed_exec_policy_amendment,
),
},
UnifiedExecApprovalRequirement::NeedsApproval {
reason,
proposed_exec_policy_amendment,
} => ExecApprovalRequirement::NeedsApproval {
reason: reason.clone(),
proposed_execpolicy_amendment: clone_exec_policy_amendment(
proposed_exec_policy_amendment,
),
},
UnifiedExecApprovalRequirement::Forbidden { reason } => {
ExecApprovalRequirement::Forbidden {
reason: reason.clone(),
}
}
}
}
fn clone_exec_policy_amendment(
amendment: &Option<ExecPolicyAmendment>,
) -> Option<ExecPolicyAmendment> {
amendment.clone()
}
pub(crate) fn generate_chunk_id() -> String {
let mut rng = rng();
(0..6)

View File

@@ -3,11 +3,16 @@ use super::*;
use crate::codex::Session;
use crate::codex::TurnContext;
use crate::codex::make_session_and_context;
use crate::exec_env::create_env;
use crate::protocol::AskForApproval;
use crate::protocol::SandboxPolicy;
use crate::sandboxing::SandboxPermissions;
use crate::tools::context::ExecCommandToolOutput;
use crate::unified_exec::ExecCommandRequest;
use crate::unified_exec::WriteStdinRequest;
use crate::unified_exec::apply_unified_exec_env;
use codex_protocol::executor::UnifiedExecApprovalRequirement;
use codex_protocol::executor::UnifiedExecExecCommandRequest;
use core_test_support::skip_if_sandbox;
use std::sync::Arc;
use tokio::time::Duration;
@@ -35,29 +40,32 @@ async fn exec_command(
) -> Result<ExecCommandToolOutput, UnifiedExecError> {
let context =
UnifiedExecContext::new(Arc::clone(session), Arc::clone(turn), "call".to_string());
let process_id = session
.services
.unified_exec_manager
.allocate_process_id()
.await;
session
.services
.unified_exec_manager
.exec_command(
ExecCommandRequest {
command: vec!["bash".to_string(), "-lc".to_string(), cmd.to_string()],
process_id,
yield_time_ms,
max_output_tokens: None,
workdir: None,
wire_request: UnifiedExecExecCommandRequest {
command: vec!["bash".to_string(), "-lc".to_string(), cmd.to_string()],
cwd: turn.cwd.clone(),
env: apply_unified_exec_env(create_env(
&turn.shell_environment_policy,
Some(session.conversation_id),
)),
tty: true,
yield_time_ms,
max_output_tokens: DEFAULT_MAX_OUTPUT_TOKENS,
sandbox_permissions: SandboxPermissions::UseDefault,
additional_permissions: None,
additional_permissions_preapproved: false,
justification: None,
exec_approval_requirement: UnifiedExecApprovalRequirement::Skip {
bypass_sandbox: false,
proposed_exec_policy_amendment: None,
},
},
network: None,
tty: true,
sandbox_permissions: SandboxPermissions::UseDefault,
additional_permissions: None,
additional_permissions_preapproved: false,
justification: None,
prefix_rule: None,
},
&context,
)
@@ -75,9 +83,9 @@ async fn write_stdin(
.unified_exec_manager
.write_stdin(WriteStdinRequest {
process_id,
input,
input: input.to_string(),
yield_time_ms,
max_output_tokens: None,
max_output_tokens: DEFAULT_MAX_OUTPUT_TOKENS,
})
.await
}

View File

@@ -1,6 +1,5 @@
use rand::Rng;
use std::cmp::Reverse;
use std::collections::HashMap;
use std::collections::HashSet;
use std::path::PathBuf;
use std::sync::Arc;
@@ -13,8 +12,6 @@ use tokio::time::Duration;
use tokio::time::Instant;
use tokio_util::sync::CancellationToken;
use crate::exec_env::create_env;
use crate::exec_policy::ExecApprovalRequest;
use crate::protocol::ExecCommandSource;
use crate::sandboxing::ExecRequest;
use crate::tools::context::ExecCommandToolOutput;
@@ -44,6 +41,7 @@ use crate::unified_exec::async_watcher::emit_exec_end_for_unified_exec;
use crate::unified_exec::async_watcher::spawn_exit_watcher;
use crate::unified_exec::async_watcher::start_streaming_output;
use crate::unified_exec::clamp_yield_time;
use crate::unified_exec::from_wire_exec_approval_requirement;
use crate::unified_exec::generate_chunk_id;
use crate::unified_exec::head_tail_buffer::HeadTailBuffer;
use crate::unified_exec::process::OutputBuffer;
@@ -51,19 +49,6 @@ use crate::unified_exec::process::OutputHandles;
use crate::unified_exec::process::SpawnLifecycleHandle;
use crate::unified_exec::process::UnifiedExecProcess;
const UNIFIED_EXEC_ENV: [(&str, &str); 10] = [
("NO_COLOR", "1"),
("TERM", "dumb"),
("LANG", "C.UTF-8"),
("LC_CTYPE", "C.UTF-8"),
("LC_ALL", "C.UTF-8"),
("COLORTERM", ""),
("PAGER", "cat"),
("GIT_PAGER", "cat"),
("GH_PAGER", "cat"),
("CODEX_CI", "1"),
];
/// Test-only override for deterministic unified exec process IDs.
///
/// In production builds this value should remain at its default (`false`) and
@@ -82,13 +67,6 @@ fn should_use_deterministic_process_ids() -> bool {
cfg!(test) || deterministic_process_ids_forced_for_tests()
}
fn apply_unified_exec_env(mut env: HashMap<String, String>) -> HashMap<String, String> {
for (key, value) in UNIFIED_EXEC_ENV {
env.insert(key.to_string(), value.to_string());
}
env
}
struct PreparedProcessHandles {
writer_tx: mpsc::Sender<Vec<u8>>,
output_buffer: OutputBuffer,
@@ -103,7 +81,7 @@ struct PreparedProcessHandles {
}
impl UnifiedExecProcessManager {
pub(crate) async fn allocate_process_id(&self) -> i32 {
async fn allocate_process_id(&self) -> i32 {
loop {
let mut store = self.process_store.lock().await;
@@ -130,7 +108,7 @@ impl UnifiedExecProcessManager {
}
}
pub(crate) async fn release_process_id(&self, process_id: i32) {
async fn release_process_id(&self, process_id: i32) {
let removed = {
let mut store = self.process_store.lock().await;
store.remove(process_id)
@@ -157,20 +135,16 @@ impl UnifiedExecProcessManager {
request: ExecCommandRequest,
context: &UnifiedExecContext,
) -> Result<ExecCommandToolOutput, UnifiedExecError> {
let cwd = request
.workdir
.clone()
.unwrap_or_else(|| context.turn.cwd.clone());
let process = self
.open_session_with_sandbox(&request, cwd.clone(), context)
.await;
let process_id = self.allocate_process_id().await;
let cwd = request.wire_request.cwd.clone();
let process = self.open_session_with_sandbox(&request, context).await;
let (process, mut deferred_network_approval) = match process {
Ok((process, deferred_network_approval)) => {
(Arc::new(process), deferred_network_approval)
}
Err(err) => {
self.release_process_id(request.process_id).await;
self.release_process_id(process_id).await;
return Err(err);
}
};
@@ -183,10 +157,10 @@ impl UnifiedExecProcessManager {
/*turn_diff_tracker*/ None,
);
let emitter = ToolEmitter::unified_exec(
&request.command,
&request.wire_request.command,
cwd.clone(),
ExecCommandSource::UnifiedExecStartup,
Some(request.process_id.to_string()),
Some(process_id.to_string()),
);
emitter.emit(event_ctx, ToolEventStage::Begin).await;
@@ -202,18 +176,18 @@ impl UnifiedExecProcessManager {
self.store_process(
Arc::clone(&process),
context,
&request.command,
&request.wire_request.command,
cwd.clone(),
start,
request.process_id,
request.tty,
process_id,
request.wire_request.tty,
network_approval_id,
Arc::clone(&transcript),
)
.await;
}
let yield_time_ms = clamp_yield_time(request.yield_time_ms);
let yield_time_ms = clamp_yield_time(request.wire_request.yield_time_ms);
// For the initial exec_command call, we both stream output to events
// (via start_streaming_output above) and collect a snapshot here for
// the tool response body.
@@ -243,7 +217,6 @@ impl UnifiedExecProcessManager {
let text = String::from_utf8_lossy(&collected).to_string();
let chunk_id = generate_chunk_id();
let process_id = request.process_id;
let (response_process_id, exit_code) = if process_started_alive {
match self.refresh_process_state(process_id).await {
ProcessStatus::Alive {
@@ -269,7 +242,7 @@ impl UnifiedExecProcessManager {
Arc::clone(&context.session),
Arc::clone(&context.turn),
context.call_id.clone(),
request.command.clone(),
request.wire_request.command.clone(),
cwd.clone(),
Some(process_id.to_string()),
Arc::clone(&transcript),
@@ -279,7 +252,7 @@ impl UnifiedExecProcessManager {
)
.await;
self.release_process_id(request.process_id).await;
self.release_process_id(process_id).await;
finish_deferred_network_approval(
context.session.as_ref(),
deferred_network_approval.take(),
@@ -295,11 +268,11 @@ impl UnifiedExecProcessManager {
chunk_id,
wall_time,
raw_output: collected,
max_output_tokens: request.max_output_tokens,
max_output_tokens: Some(request.wire_request.max_output_tokens),
process_id: response_process_id,
exit_code,
original_token_count: Some(original_token_count),
session_command: Some(request.command.clone()),
session_command: Some(request.wire_request.command.clone()),
};
Ok(response)
@@ -307,7 +280,7 @@ impl UnifiedExecProcessManager {
pub(crate) async fn write_stdin(
&self,
request: WriteStdinRequest<'_>,
request: WriteStdinRequest,
) -> Result<ExecCommandToolOutput, UnifiedExecError> {
let process_id = request.process_id;
@@ -390,7 +363,7 @@ impl UnifiedExecProcessManager {
chunk_id,
wall_time,
raw_output: collected,
max_output_tokens: request.max_output_tokens,
max_output_tokens: Some(request.max_output_tokens),
process_id,
exit_code,
original_token_count: Some(original_token_count),
@@ -580,48 +553,29 @@ impl UnifiedExecProcessManager {
pub(super) async fn open_session_with_sandbox(
&self,
request: &ExecCommandRequest,
cwd: PathBuf,
context: &UnifiedExecContext,
) -> Result<(UnifiedExecProcess, Option<DeferredNetworkApproval>), UnifiedExecError> {
let env = apply_unified_exec_env(create_env(
&context.turn.shell_environment_policy,
Some(context.session.conversation_id),
));
let mut orchestrator = ToolOrchestrator::new();
let mut runtime = UnifiedExecRuntime::new(
self,
context.turn.tools_config.unified_exec_shell_mode.clone(),
);
let exec_approval_requirement = context
.session
.services
.exec_policy
.create_exec_approval_requirement_for_command(ExecApprovalRequest {
command: &request.command,
approval_policy: context.turn.approval_policy.value(),
sandbox_policy: context.turn.sandbox_policy.get(),
file_system_sandbox_policy: &context.turn.file_system_sandbox_policy,
sandbox_permissions: if request.additional_permissions_preapproved {
crate::sandboxing::SandboxPermissions::UseDefault
} else {
request.sandbox_permissions
},
prefix_rule: request.prefix_rule.clone(),
})
.await;
let req = UnifiedExecToolRequest {
command: request.command.clone(),
cwd,
env,
explicit_env_overrides: context.turn.shell_environment_policy.r#set.clone(),
command: request.wire_request.command.clone(),
cwd: request.wire_request.cwd.clone(),
env: request.wire_request.env.clone(),
network: request.network.clone(),
tty: request.tty,
sandbox_permissions: request.sandbox_permissions,
additional_permissions: request.additional_permissions.clone(),
tty: request.wire_request.tty,
sandbox_permissions: request.wire_request.sandbox_permissions,
additional_permissions: request.wire_request.additional_permissions.clone(),
#[cfg(unix)]
additional_permissions_preapproved: request.additional_permissions_preapproved,
justification: request.justification.clone(),
exec_approval_requirement,
additional_permissions_preapproved: request
.wire_request
.additional_permissions_preapproved,
justification: request.wire_request.justification.clone(),
exec_approval_requirement: from_wire_exec_approval_requirement(
&request.wire_request.exec_approval_requirement,
),
};
let tool_ctx = ToolCtx {
session: context.session.clone(),

View File

@@ -1,8 +1,11 @@
use super::*;
use pretty_assertions::assert_eq;
use std::collections::HashMap;
use tokio::time::Duration;
use tokio::time::Instant;
use crate::unified_exec::apply_unified_exec_env;
#[test]
fn unified_exec_env_injects_defaults() {
let env = apply_unified_exec_env(HashMap::new());