mirror of
https://github.com/openai/codex.git
synced 2026-04-24 14:45:27 +00:00
refactor(core): resolve unified exec executor inputs in handler
This commit is contained in:
@@ -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| {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 re‑prompt 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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user