mirror of
https://github.com/openai/codex.git
synced 2026-10-28 18:58:46 +00:00
Carry prepared commands through unified execution (#57438)
## What changed Pass `PreparedExecCommand` through the process manager and runtime instead of copying command, shell, environment, and permission fields into separate request structs. Move approval requirement evaluation and approval action construction onto the prepared command, and use it for shell snapshots and launch failure events. GitOrigin-RevId: 3e9f87e0974218fea6fffc0f7740616d696b9800
This commit is contained in:
@@ -284,6 +284,7 @@ pub(crate) fn normalize_and_validate_additional_permissions(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct EffectiveAdditionalPermissions {
|
||||
pub sandbox_permissions: SandboxPermissions,
|
||||
pub additional_permissions: Option<AdditionalPermissionProfile>,
|
||||
|
||||
@@ -175,16 +175,13 @@ impl ExecCommandHandler {
|
||||
)
|
||||
.await;
|
||||
let prepared = parsed.prepare(self.lifetime)?;
|
||||
let command = prepared.resolved.command;
|
||||
let cwd = prepared.cwd;
|
||||
let hook_command = prepared.hook_command;
|
||||
let max_output_tokens = prepared.max_output_tokens;
|
||||
let fs = turn_environment.environment.get_filesystem();
|
||||
let fs = prepared.turn_environment.environment.get_filesystem();
|
||||
let intercepted_patch = intercept_apply_patch(
|
||||
&command,
|
||||
&cwd,
|
||||
&prepared.resolved.command,
|
||||
&prepared.cwd,
|
||||
fs.as_ref(),
|
||||
turn_environment.clone(),
|
||||
prepared.turn_environment.clone(),
|
||||
context.session.clone(),
|
||||
Arc::clone(&context.step_context),
|
||||
context.cancellation_token.clone(),
|
||||
@@ -217,32 +214,22 @@ impl ExecCommandHandler {
|
||||
context.session.as_ref(),
|
||||
context.step_context.turn.as_ref(),
|
||||
&context.call_id,
|
||||
&command,
|
||||
&cwd,
|
||||
&prepared.resolved.command,
|
||||
&prepared.cwd,
|
||||
fs.as_ref(),
|
||||
)
|
||||
.await;
|
||||
// Preparation can be cancelled. Reserve a process only once it is done.
|
||||
let process_id = manager.allocate_process_id().await;
|
||||
let hook_command = prepared.hook_command.clone();
|
||||
let tool_cwd =
|
||||
(!prepared.turn_environment.environment.is_remote()).then(|| prepared.cwd.clone());
|
||||
let request = ExecCommandRequest {
|
||||
command,
|
||||
shell: prepared.resolved.shell,
|
||||
hook_command: hook_command.clone(),
|
||||
prepared,
|
||||
process_id,
|
||||
yield_time_ms: prepared.yield_time_ms,
|
||||
max_output_tokens,
|
||||
cwd: cwd.clone(),
|
||||
sandbox_cwd: turn_environment.cwd().clone(),
|
||||
turn_environment: turn_environment.clone(),
|
||||
network: context.step_context.turn.network.clone(),
|
||||
tty: prepared.tty,
|
||||
sandbox_permissions: prepared.permissions.sandbox_permissions,
|
||||
additional_permissions: prepared.permissions.additional_permissions,
|
||||
additional_permissions_preapproved: prepared.permissions.permissions_preapproved,
|
||||
justification: prepared.justification,
|
||||
prefix_rule: prepared.prefix_rule,
|
||||
};
|
||||
let result = match prepared.completion_timeout {
|
||||
let result = match request.prepared.completion_timeout {
|
||||
Some(timeout) => {
|
||||
UnifiedExecProcessManager::exec_command_to_completion(request, &context, timeout)
|
||||
.await
|
||||
@@ -276,7 +263,7 @@ impl ExecCommandHandler {
|
||||
exit_code: Some(output.exit_code),
|
||||
original_token_count: Some(original_token_count),
|
||||
hook_command: Some(hook_command),
|
||||
tool_cwd: (!turn_environment.environment.is_remote()).then_some(cwd),
|
||||
tool_cwd,
|
||||
}))
|
||||
}
|
||||
Err(err) => {
|
||||
|
||||
@@ -6,6 +6,7 @@ use super::ExecCommandWorkdirArgs;
|
||||
use super::ResolvedCommand;
|
||||
use super::get_command;
|
||||
use crate::exec::DEFAULT_EXEC_COMMAND_TIMEOUT_MS;
|
||||
use crate::exec_policy::ExecApprovalRequest;
|
||||
use crate::exec_policy::prompt_is_rejected_by_policy;
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::sandboxing::SandboxPermissions;
|
||||
@@ -20,10 +21,14 @@ use crate::tools::handlers::normalize_and_validate_additional_permissions;
|
||||
use crate::tools::handlers::parse_arguments;
|
||||
use crate::tools::handlers::parse_arguments_with_base_path;
|
||||
use crate::tools::handlers::resolve_sandbox_permissions;
|
||||
use crate::tools::sandboxing::ApprovalAction;
|
||||
use crate::tools::sandboxing::ExecApprovalRequirement;
|
||||
use crate::windows_sandbox::windows_sandbox_level_for_legacy_checks;
|
||||
use codex_features::Feature;
|
||||
use codex_sandboxing::SandboxManager;
|
||||
use codex_sandboxing::SandboxType;
|
||||
use codex_sandboxing::SandboxablePreference;
|
||||
use codex_shell_command::is_dangerous_command::DangerousCommandPlatform;
|
||||
use codex_shell_command::shell_detect::detect_shell_type;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_path_uri::PathConvention;
|
||||
@@ -39,7 +44,9 @@ pub(crate) enum ExecCommandLifetime {
|
||||
}
|
||||
|
||||
/// Command details available before the live handler reserves a process.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct PreparedExecCommand {
|
||||
pub(crate) turn_environment: TurnEnvironment,
|
||||
pub(crate) resolved: ResolvedCommand,
|
||||
pub(crate) hook_command: String,
|
||||
pub(crate) cwd: PathUri,
|
||||
@@ -52,6 +59,69 @@ pub(crate) struct PreparedExecCommand {
|
||||
pub(crate) completion_timeout: Option<Duration>,
|
||||
}
|
||||
|
||||
impl PreparedExecCommand {
|
||||
pub(crate) async fn approval_requirement(
|
||||
&self,
|
||||
session: &Session,
|
||||
step: &StepContext,
|
||||
) -> ExecApprovalRequirement {
|
||||
let environment = &self.turn_environment;
|
||||
let session_shell = session.user_shell();
|
||||
let configured_shell = environment.shell.as_ref().unwrap_or(session_shell.as_ref());
|
||||
// Legacy executors omit the platform. Unknown reported platforms use the
|
||||
// more conservative Windows rules rather than silently weakening checks.
|
||||
let command_platform = match environment.executor_platform_os.as_deref() {
|
||||
Some("linux" | "macos") => DangerousCommandPlatform::Posix,
|
||||
Some("windows") | Some(_) => DangerousCommandPlatform::Windows,
|
||||
None => DangerousCommandPlatform::host(),
|
||||
};
|
||||
session
|
||||
.services
|
||||
.exec_policy
|
||||
.create_exec_approval_requirement_for_shell(
|
||||
ExecApprovalRequest {
|
||||
command: &self.resolved.command,
|
||||
approval_policy: step.settings.approval_policy(),
|
||||
permission_profile: environment.permission_profile().clone(),
|
||||
environment_policy: environment.config().exec_policy.as_ref(),
|
||||
windows_sandbox_level: windows_sandbox_level_for_legacy_checks(
|
||||
environment.config().windows_sandbox_type,
|
||||
environment.config().windows_sandbox_level,
|
||||
),
|
||||
sandbox_permissions: if self.permissions.permissions_preapproved {
|
||||
SandboxPermissions::UseDefault
|
||||
} else {
|
||||
self.permissions.sandbox_permissions
|
||||
},
|
||||
prefix_rule: self.prefix_rule.clone(),
|
||||
allow_prefix_rules: step.turn.allow_prefix_rules(),
|
||||
},
|
||||
configured_shell,
|
||||
command_platform,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) fn approval_action(
|
||||
&self,
|
||||
call_id: &str,
|
||||
requirement: &ExecApprovalRequirement,
|
||||
) -> ApprovalAction {
|
||||
ApprovalAction::ExecCommand {
|
||||
id: call_id.to_owned(),
|
||||
environment_id: self.turn_environment.selection.environment_id.clone(),
|
||||
command: self.resolved.command.clone(),
|
||||
hook_command: self.hook_command.clone(),
|
||||
cwd: self.cwd.clone(),
|
||||
sandbox_permissions: self.permissions.sandbox_permissions,
|
||||
additional_permissions: self.permissions.additional_permissions.clone(),
|
||||
justification: self.justification.clone(),
|
||||
tty: self.tty,
|
||||
proposed_execpolicy_amendment: requirement.proposed_execpolicy_amendment().cloned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ParsedExecCommand<'a> {
|
||||
session: &'a Session,
|
||||
step: &'a StepContext,
|
||||
@@ -232,6 +302,7 @@ impl<'a> ParsedExecCommand<'a> {
|
||||
.map_err(FunctionCallError::RespondToModel)?;
|
||||
|
||||
Ok(PreparedExecCommand {
|
||||
turn_environment: turn_environment.clone(),
|
||||
resolved: resolved_command,
|
||||
hook_command: args.cmd,
|
||||
yield_time_ms: args.yield_time_ms,
|
||||
|
||||
@@ -14,11 +14,11 @@ use crate::sandboxing::ExecOptions;
|
||||
use crate::sandboxing::ExecServerEnvConfig;
|
||||
use crate::sandboxing::SandboxPermissions;
|
||||
use crate::session::turn_context::TurnEnvironment;
|
||||
use crate::shell::ShellInvocation;
|
||||
use crate::shell::ShellType;
|
||||
use crate::shell_snapshot::ShellSnapshotSandbox;
|
||||
use crate::shell_snapshot::snapshot_read_permissions;
|
||||
use crate::tools::flat_tool_name;
|
||||
use crate::tools::handlers::unified_exec::preparation::PreparedExecCommand;
|
||||
use crate::tools::network_approval::NetworkApprovalSpec;
|
||||
use crate::tools::network_approval::remote_network_proxy_launch;
|
||||
use crate::tools::runtimes::RuntimePathPrepends;
|
||||
@@ -72,23 +72,14 @@ const REMOTE_NETWORK_POLICY_DECISION_MARGIN: Duration = Duration::from_secs(10);
|
||||
/// Request payload used by the unified-exec runtime after approvals and
|
||||
/// sandbox preferences have been resolved for the current turn.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct UnifiedExecRequest {
|
||||
pub command: Vec<String>,
|
||||
pub(crate) shell: ShellInvocation,
|
||||
pub hook_command: String,
|
||||
pub struct UnifiedExecRequest<'a> {
|
||||
pub(crate) prepared: &'a PreparedExecCommand,
|
||||
pub process_id: i32,
|
||||
pub cwd: PathUri,
|
||||
pub sandbox_cwd: PathUri,
|
||||
pub turn_environment: TurnEnvironment,
|
||||
pub env: HashMap<String, String>,
|
||||
pub exec_server_env_config: Option<ExecServerEnvConfig>,
|
||||
pub shell_snapshot: Option<codex_exec_server::ShellSnapshotRequest>,
|
||||
pub explicit_env_overrides: HashMap<String, String>,
|
||||
pub network: Option<NetworkProxy>,
|
||||
pub tty: bool,
|
||||
pub sandbox_permissions: SandboxPermissions,
|
||||
pub additional_permissions: Option<AdditionalPermissionProfile>,
|
||||
pub justification: Option<String>,
|
||||
pub exec_approval_requirement: ExecApprovalRequirement,
|
||||
}
|
||||
|
||||
@@ -167,65 +158,54 @@ impl Sandboxable for UnifiedExecRuntime<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Approvable<UnifiedExecRequest> for UnifiedExecRuntime<'_> {
|
||||
impl Approvable<UnifiedExecRequest<'_>> for UnifiedExecRuntime<'_> {
|
||||
fn approval_action(
|
||||
&self,
|
||||
req: &UnifiedExecRequest,
|
||||
req: &UnifiedExecRequest<'_>,
|
||||
call_id: &str,
|
||||
) -> std::io::Result<ApprovalAction> {
|
||||
Ok(ApprovalAction::ExecCommand {
|
||||
id: call_id.to_string(),
|
||||
environment_id: req.turn_environment.selection.environment_id.clone(),
|
||||
command: req.command.clone(),
|
||||
hook_command: req.hook_command.clone(),
|
||||
cwd: req.cwd.clone(),
|
||||
sandbox_permissions: req.sandbox_permissions,
|
||||
additional_permissions: req.additional_permissions.clone(),
|
||||
justification: req.justification.clone(),
|
||||
tty: req.tty,
|
||||
proposed_execpolicy_amendment: req
|
||||
.exec_approval_requirement
|
||||
.proposed_execpolicy_amendment()
|
||||
.cloned(),
|
||||
})
|
||||
Ok(req
|
||||
.prepared
|
||||
.approval_action(call_id, &req.exec_approval_requirement))
|
||||
}
|
||||
|
||||
fn exec_approval_requirement(
|
||||
&self,
|
||||
req: &UnifiedExecRequest,
|
||||
req: &UnifiedExecRequest<'_>,
|
||||
) -> Option<ExecApprovalRequirement> {
|
||||
Some(req.exec_approval_requirement.clone())
|
||||
}
|
||||
|
||||
fn sandbox_permissions(&self, req: &UnifiedExecRequest) -> SandboxPermissions {
|
||||
req.sandbox_permissions
|
||||
fn sandbox_permissions(&self, req: &UnifiedExecRequest<'_>) -> SandboxPermissions {
|
||||
req.prepared.permissions.sandbox_permissions
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecAttempt> for UnifiedExecRuntime<'a> {
|
||||
fn turn_environment<'b>(&self, req: &'b UnifiedExecRequest) -> &'b TurnEnvironment {
|
||||
&req.turn_environment
|
||||
impl<'a> ToolRuntime<UnifiedExecRequest<'_>, UnifiedExecAttempt> for UnifiedExecRuntime<'a> {
|
||||
fn turn_environment<'b>(&self, req: &'b UnifiedExecRequest<'_>) -> &'b TurnEnvironment {
|
||||
&req.prepared.turn_environment
|
||||
}
|
||||
|
||||
fn uses_executor_managed_process_sandbox(&self, req: &UnifiedExecRequest) -> bool {
|
||||
req.turn_environment.environment.is_remote() || req.shell_snapshot.is_some()
|
||||
fn uses_executor_managed_process_sandbox(&self, req: &UnifiedExecRequest<'_>) -> bool {
|
||||
req.prepared.turn_environment.environment.is_remote() || req.shell_snapshot.is_some()
|
||||
}
|
||||
|
||||
fn sandbox_cwd<'b>(&self, req: &'b UnifiedExecRequest) -> Option<&'b PathUri> {
|
||||
Some(&req.sandbox_cwd)
|
||||
fn sandbox_cwd<'b>(&self, req: &'b UnifiedExecRequest<'_>) -> Option<&'b PathUri> {
|
||||
Some(req.prepared.turn_environment.cwd())
|
||||
}
|
||||
|
||||
fn network_approval_spec(
|
||||
&self,
|
||||
req: &UnifiedExecRequest,
|
||||
req: &UnifiedExecRequest<'_>,
|
||||
ctx: &ToolCtx,
|
||||
) -> Option<NetworkApprovalSpec> {
|
||||
let file_system_sandbox_policy = req
|
||||
let prepared = req.prepared;
|
||||
let file_system_sandbox_policy = prepared
|
||||
.turn_environment
|
||||
.permission_profile()
|
||||
.file_system_sandbox_policy();
|
||||
let sandbox_permissions = sandbox_permissions_preserving_denied_reads(
|
||||
req.sandbox_permissions,
|
||||
prepared.permissions.sandbox_permissions,
|
||||
&file_system_sandbox_policy,
|
||||
);
|
||||
// Explicit full escalation bypasses controller and attachment-owned network proxies.
|
||||
@@ -235,7 +215,7 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecAttempt> for UnifiedExecRunt
|
||||
}
|
||||
let network = req.network.clone();
|
||||
// No-proxy fast path; owners still need a spec for execution-only proxies.
|
||||
if network.is_none() && req.turn_environment.config().network_policy.is_none() {
|
||||
if network.is_none() && prepared.turn_environment.config().network_policy.is_none() {
|
||||
return None;
|
||||
}
|
||||
Some(NetworkApprovalSpec {
|
||||
@@ -244,45 +224,46 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecAttempt> for UnifiedExecRunt
|
||||
trigger: GuardianNetworkAccessTrigger {
|
||||
call_id: ctx.call_id.clone(),
|
||||
tool_name: flat_tool_name(&ctx.tool_name).into_owned(),
|
||||
command: req.command.clone(),
|
||||
cwd: req.cwd.clone(),
|
||||
sandbox_permissions: req.sandbox_permissions,
|
||||
additional_permissions: req.additional_permissions.clone(),
|
||||
justification: req.justification.clone(),
|
||||
tty: Some(req.tty),
|
||||
command: prepared.resolved.command.clone(),
|
||||
cwd: prepared.cwd.clone(),
|
||||
sandbox_permissions: prepared.permissions.sandbox_permissions,
|
||||
additional_permissions: prepared.permissions.additional_permissions.clone(),
|
||||
justification: prepared.justification.clone(),
|
||||
tty: Some(prepared.tty),
|
||||
},
|
||||
command: req.hook_command.clone(),
|
||||
environment_id: req.turn_environment.selection.environment_id.clone(),
|
||||
permission_profile: req.turn_environment.permission_profile().clone(),
|
||||
network_policy: req.turn_environment.config().network_policy.clone(),
|
||||
command: prepared.hook_command.clone(),
|
||||
environment_id: prepared.turn_environment.selection.environment_id.clone(),
|
||||
permission_profile: prepared.turn_environment.permission_profile().clone(),
|
||||
network_policy: prepared.turn_environment.config().network_policy.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&mut self,
|
||||
req: &UnifiedExecRequest,
|
||||
req: &UnifiedExecRequest<'_>,
|
||||
attempt: &SandboxAttempt<'_>,
|
||||
ctx: &ToolCtx,
|
||||
) -> Result<UnifiedExecAttempt, ToolError> {
|
||||
let base_command = &req.command;
|
||||
let requested_shell = &req.shell.shell;
|
||||
let prepared = req.prepared;
|
||||
let base_command = &prepared.resolved.command;
|
||||
let requested_shell = &prepared.resolved.shell.shell;
|
||||
let windows_sandbox_proxy_settings_mode = ctx.session.windows_sandbox_proxy_settings_mode;
|
||||
let session_shell = ctx.session.user_shell();
|
||||
let environment_shell = req
|
||||
let environment_shell = prepared
|
||||
.turn_environment
|
||||
.shell
|
||||
.as_ref()
|
||||
.unwrap_or(session_shell.as_ref());
|
||||
let (file_system_sandbox_policy, _) = attempt.permissions.to_runtime_permissions();
|
||||
let launch_sandbox_permissions = sandbox_permissions_preserving_denied_reads(
|
||||
req.sandbox_permissions,
|
||||
prepared.permissions.sandbox_permissions,
|
||||
&file_system_sandbox_policy,
|
||||
);
|
||||
let managed_network = attempt.network_proxy(managed_network_for_sandbox_permissions(
|
||||
req.network.as_ref(),
|
||||
launch_sandbox_permissions,
|
||||
));
|
||||
let environment_is_remote = req.turn_environment.environment.is_remote();
|
||||
let environment_is_remote = prepared.turn_environment.environment.is_remote();
|
||||
let credential_broker_available = !environment_is_remote
|
||||
&& req.network.is_some()
|
||||
&& ctx
|
||||
@@ -307,11 +288,12 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecAttempt> for UnifiedExecRunt
|
||||
None
|
||||
} else {
|
||||
// TODO(anp): Make shell snapshot lookup accept PathUri.
|
||||
let native_cwd = req
|
||||
let native_cwd = prepared
|
||||
.cwd
|
||||
.to_abs_path()
|
||||
.map_err(|err| ToolError::Rejected(err.to_string()))?;
|
||||
req.turn_environment
|
||||
prepared
|
||||
.turn_environment
|
||||
.shell_snapshot(
|
||||
&native_cwd,
|
||||
base_command,
|
||||
@@ -320,8 +302,8 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecAttempt> for UnifiedExecRunt
|
||||
Some(ShellSnapshotSandbox::new(
|
||||
attempt,
|
||||
managed_network,
|
||||
&req.turn_environment.selection.environment_id,
|
||||
req.additional_permissions.as_ref(),
|
||||
&prepared.turn_environment.selection.environment_id,
|
||||
prepared.permissions.additional_permissions.as_ref(),
|
||||
)),
|
||||
)
|
||||
.await
|
||||
@@ -332,19 +314,19 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecAttempt> for UnifiedExecRunt
|
||||
let shell_snapshot_location = shell_snapshot.as_ref().map(|snapshot| snapshot.path());
|
||||
let mut env = exec_env_for_sandbox_permissions(&req.env, launch_sandbox_permissions);
|
||||
let snapshot_credential_context = if let Some(snapshot) = shell_snapshot.as_ref()
|
||||
&& (managed_network.is_some() || req.shell.is_posix_login())
|
||||
&& (managed_network.is_some() || prepared.resolved.shell.is_posix_login())
|
||||
{
|
||||
Some(
|
||||
snapshot
|
||||
.restore_credentials(&mut env, req.turn_environment.shell_environment_policy()),
|
||||
)
|
||||
Some(snapshot.restore_credentials(
|
||||
&mut env,
|
||||
prepared.turn_environment.shell_environment_policy(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let (mut env, managed_network_context, network_proxy_launch) = match managed_network {
|
||||
Some(network) if environment_is_remote => {
|
||||
let (mut launch, credential_masking) = remote_network_proxy_launch(
|
||||
&req.turn_environment,
|
||||
&prepared.turn_environment,
|
||||
network,
|
||||
&ctx.step_context.turn,
|
||||
)
|
||||
@@ -380,27 +362,31 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecAttempt> for UnifiedExecRunt
|
||||
(env, None, launch.proxy.enabled.then_some(launch))
|
||||
}
|
||||
Some(network) => {
|
||||
let prepared = snapshot_credential_context
|
||||
let child_environment = snapshot_credential_context
|
||||
.unwrap_or_default()
|
||||
.prepare_child_environment(
|
||||
network,
|
||||
env,
|
||||
Some(&req.turn_environment.selection.environment_id),
|
||||
Some(&prepared.turn_environment.selection.environment_id),
|
||||
)
|
||||
.map_err(|err| {
|
||||
ToolError::Codex(CodexErr::Io(io::Error::other(format!(
|
||||
"failed to prepare network proxy for environment `{}`: {err}",
|
||||
req.turn_environment.selection.environment_id
|
||||
prepared.turn_environment.selection.environment_id
|
||||
))))
|
||||
})?;
|
||||
(prepared.env, Some(prepared.sandbox_context), None)
|
||||
(
|
||||
child_environment.env,
|
||||
Some(child_environment.sandbox_context),
|
||||
None,
|
||||
)
|
||||
}
|
||||
None => (env, None, None),
|
||||
};
|
||||
if let Some(snapshot) = shell_snapshot.as_ref() {
|
||||
snapshot.restore_fail_open_aliases(
|
||||
&mut env,
|
||||
Some(&req.turn_environment.selection.environment_id),
|
||||
Some(&prepared.turn_environment.selection.environment_id),
|
||||
);
|
||||
}
|
||||
if managed_network.is_some()
|
||||
@@ -458,9 +444,9 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecAttempt> for UnifiedExecRunt
|
||||
}
|
||||
let metrics_sidecar = sidecar_for_command(
|
||||
ctx,
|
||||
&req.command,
|
||||
&req.cwd,
|
||||
req.turn_environment.environment.as_ref(),
|
||||
&prepared.resolved.command,
|
||||
&prepared.cwd,
|
||||
prepared.turn_environment.environment.as_ref(),
|
||||
)
|
||||
.await;
|
||||
if let Some(sidecar) = metrics_sidecar.as_ref() {
|
||||
@@ -485,12 +471,14 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecAttempt> for UnifiedExecRunt
|
||||
// `hook_command` is the raw script the helper needs to prepend setup inside that same shell.
|
||||
let mut command = base_command.to_vec();
|
||||
if ctx.session.enabled(Feature::LoginShellPackagePath)
|
||||
&& req.shell.is_posix_login()
|
||||
&& prepared.resolved.shell.is_posix_login()
|
||||
&& !explicit_env_overrides.contains_key("PATH")
|
||||
&& let Ok(info) = req.turn_environment.environment.info().await
|
||||
&& let Some(command_with_path_prepends) = req
|
||||
.shell
|
||||
.derive_exec_args_with_path_prepends(&req.hook_command, &info.prepend_path_dirs)
|
||||
&& let Ok(info) = prepared.turn_environment.environment.info().await
|
||||
&& let Some(command_with_path_prepends) =
|
||||
prepared.resolved.shell.derive_exec_args_with_path_prepends(
|
||||
&prepared.hook_command,
|
||||
&info.prepend_path_dirs,
|
||||
)
|
||||
{
|
||||
command = command_with_path_prepends;
|
||||
}
|
||||
@@ -585,7 +573,8 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecAttempt> for UnifiedExecRunt
|
||||
.and_then(|path| {
|
||||
snapshot_read_permissions(
|
||||
path,
|
||||
&req.turn_environment
|
||||
&prepared
|
||||
.turn_environment
|
||||
.permission_profile_with_workspace_roots(),
|
||||
attempt.sandbox_cwd,
|
||||
)
|
||||
@@ -593,11 +582,11 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecAttempt> for UnifiedExecRunt
|
||||
let internal_permissions =
|
||||
merge_permission_profiles(sidecar_permissions.as_ref(), snapshot_permissions.as_ref());
|
||||
let additional_permissions = merge_permission_profiles(
|
||||
req.additional_permissions.as_ref(),
|
||||
prepared.permissions.additional_permissions.as_ref(),
|
||||
internal_permissions.as_ref(),
|
||||
);
|
||||
let permissions = TerminalPermissions::for_launch(
|
||||
&req.turn_environment,
|
||||
&prepared.turn_environment,
|
||||
&ctx.step_context.turn,
|
||||
if self.uses_executor_managed_process_sandbox(req) {
|
||||
TerminalSandboxSource::Executor
|
||||
@@ -609,10 +598,10 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecAttempt> for UnifiedExecRunt
|
||||
} else {
|
||||
SandboxPermissions::UseDefault
|
||||
},
|
||||
req.additional_permissions.as_ref(),
|
||||
prepared.permissions.additional_permissions.as_ref(),
|
||||
internal_permissions.as_ref(),
|
||||
);
|
||||
let baseline_file_system = req
|
||||
let baseline_file_system = prepared
|
||||
.turn_environment
|
||||
.permission_profile()
|
||||
.file_system_sandbox_policy();
|
||||
@@ -626,7 +615,7 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecAttempt> for UnifiedExecRunt
|
||||
|
||||
let command = build_unified_exec_sandbox_command(
|
||||
&command,
|
||||
&req.cwd,
|
||||
&prepared.cwd,
|
||||
&env,
|
||||
managed_network_context,
|
||||
additional_permissions,
|
||||
@@ -648,15 +637,15 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecAttempt> for UnifiedExecRunt
|
||||
attempt,
|
||||
managed_network,
|
||||
network_proxy_launch,
|
||||
/*environment_id*/ Some(&req.turn_environment.selection.environment_id),
|
||||
/*environment_id*/ Some(&prepared.turn_environment.selection.environment_id),
|
||||
req.exec_server_env_config.clone(),
|
||||
req.shell_snapshot.clone(),
|
||||
windows_sandbox_proxy_settings_mode,
|
||||
req.tty,
|
||||
req.turn_environment.environment.as_ref(),
|
||||
prepared.tty,
|
||||
prepared.turn_environment.environment.as_ref(),
|
||||
)
|
||||
.await;
|
||||
let mut process = with_launch_failure_events(process, req, ctx).await?;
|
||||
let mut process = with_launch_failure_events(process, prepared, ctx).await?;
|
||||
process._shell_snapshot = shell_snapshot;
|
||||
Ok(UnifiedExecAttempt {
|
||||
process,
|
||||
@@ -668,7 +657,7 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecAttempt> for UnifiedExecRunt
|
||||
|
||||
async fn warn_if_remote_credential_masking_unavailable(
|
||||
ctx: &ToolCtx,
|
||||
req: &UnifiedExecRequest,
|
||||
req: &UnifiedExecRequest<'_>,
|
||||
credential_masking: RemoteCredentialMasking,
|
||||
proxy_config: &RemoteNetworkProxyConfig,
|
||||
) {
|
||||
@@ -682,7 +671,8 @@ async fn warn_if_remote_credential_masking_unavailable(
|
||||
return;
|
||||
}
|
||||
|
||||
let environment_id = &req.turn_environment.selection.environment_id;
|
||||
let prepared = req.prepared;
|
||||
let environment_id = &prepared.turn_environment.selection.environment_id;
|
||||
let message = if credential_masking == RemoteCredentialMasking::Disabled {
|
||||
format!(
|
||||
"Credential masking is unavailable on environment `{environment_id}` because its exec-server does not support it. This command will run with unmasked credentials."
|
||||
@@ -691,14 +681,17 @@ async fn warn_if_remote_credential_masking_unavailable(
|
||||
format!(
|
||||
"Credential masking is unavailable for this network proxy configuration on environment `{environment_id}`. This command will run with unmasked credentials."
|
||||
)
|
||||
} else if !matches!(req.shell.shell.shell_type, ShellType::Bash | ShellType::Zsh) {
|
||||
} else if !matches!(
|
||||
prepared.resolved.shell.shell.shell_type,
|
||||
ShellType::Bash | ShellType::Zsh
|
||||
) {
|
||||
format!(
|
||||
"Credential masking is unavailable for shell `{}` on environment `{environment_id}`. This command will run with unmasked credentials.",
|
||||
req.shell.shell.name(),
|
||||
prepared.resolved.shell.shell.name(),
|
||||
)
|
||||
} else if match req.shell.shell.shell_type {
|
||||
ShellType::Bash => req.shell.use_login_shell || req.shell_snapshot.is_some(),
|
||||
ShellType::Zsh => !req.shell.use_login_shell || req.shell_snapshot.is_none(),
|
||||
} else if match prepared.resolved.shell.shell.shell_type {
|
||||
ShellType::Bash => prepared.resolved.shell.use_login_shell || req.shell_snapshot.is_some(),
|
||||
ShellType::Zsh => !prepared.resolved.shell.use_login_shell || req.shell_snapshot.is_none(),
|
||||
ShellType::Sh | ShellType::PowerShell | ShellType::Cmd => true,
|
||||
} {
|
||||
format!(
|
||||
@@ -723,6 +716,9 @@ mod tests {
|
||||
use crate::environment_selection::EnvironmentConfigOrigin;
|
||||
use crate::exec::DEFAULT_EXEC_COMMAND_TIMEOUT_MS;
|
||||
use crate::shell::Shell;
|
||||
use crate::shell::ShellInvocation;
|
||||
use crate::tools::handlers::EffectiveAdditionalPermissions;
|
||||
use crate::tools::handlers::unified_exec::ResolvedCommand;
|
||||
use crate::tools::sandboxing::ToolRuntime;
|
||||
use codex_exec_server::Environment;
|
||||
use codex_exec_server::LOCAL_ENVIRONMENT_ID;
|
||||
@@ -794,23 +790,17 @@ mod tests {
|
||||
async fn approval_key_includes_environment_id() {
|
||||
let manager = UnifiedExecProcessManager::default();
|
||||
let runtime = UnifiedExecRuntime::new(&manager);
|
||||
let mut request = test_request(
|
||||
SandboxPermissions::UseDefault,
|
||||
ExecApprovalRequirement::Skip {
|
||||
bypass_sandbox: false,
|
||||
proposed_execpolicy_amendment: None,
|
||||
},
|
||||
);
|
||||
request.turn_environment.selection.environment_id = "remote".to_string();
|
||||
let original_key = runtime
|
||||
.approval_action(&request, "call-1")
|
||||
.expect("build approval action")
|
||||
.cache_keys();
|
||||
request.turn_environment.selection.environment_id = "other".to_string();
|
||||
let other_key = runtime
|
||||
.approval_action(&request, "call-1")
|
||||
.expect("build approval action")
|
||||
.cache_keys();
|
||||
let mut prepared = test_prepared_command();
|
||||
let approval_key = |prepared: &PreparedExecCommand| {
|
||||
runtime
|
||||
.approval_action(&test_request(prepared), "call-1")
|
||||
.expect("build approval action")
|
||||
.cache_keys()
|
||||
};
|
||||
prepared.turn_environment.selection.environment_id = "remote".to_string();
|
||||
let original_key = approval_key(&prepared);
|
||||
prepared.turn_environment.selection.environment_id = "other".to_string();
|
||||
let other_key = approval_key(&prepared);
|
||||
|
||||
assert_ne!(original_key, other_key);
|
||||
}
|
||||
@@ -825,34 +815,10 @@ mod tests {
|
||||
.expect("absolute sandbox temp dir");
|
||||
let manager = UnifiedExecProcessManager::default();
|
||||
let runtime = UnifiedExecRuntime::new(&manager);
|
||||
let request = UnifiedExecRequest {
|
||||
command: vec!["pwd".to_string()],
|
||||
shell: ShellInvocation {
|
||||
shell: Shell {
|
||||
shell_type: ShellType::Sh,
|
||||
shell_path: "sh".into(),
|
||||
},
|
||||
use_login_shell: false,
|
||||
},
|
||||
hook_command: "pwd".to_string(),
|
||||
process_id: 1000,
|
||||
cwd: cwd.into(),
|
||||
sandbox_cwd: sandbox_cwd.clone().into(),
|
||||
turn_environment: test_turn_environment(sandbox_cwd.clone().into()),
|
||||
env: HashMap::new(),
|
||||
exec_server_env_config: None,
|
||||
shell_snapshot: None,
|
||||
explicit_env_overrides: HashMap::new(),
|
||||
network: None,
|
||||
tty: false,
|
||||
sandbox_permissions: SandboxPermissions::UseDefault,
|
||||
additional_permissions: None,
|
||||
justification: None,
|
||||
exec_approval_requirement: ExecApprovalRequirement::Skip {
|
||||
bypass_sandbox: false,
|
||||
proposed_execpolicy_amendment: None,
|
||||
},
|
||||
};
|
||||
let mut prepared = test_prepared_command();
|
||||
prepared.cwd = cwd.into();
|
||||
prepared.turn_environment = test_turn_environment(sandbox_cwd.clone().into());
|
||||
let request = test_request(&prepared);
|
||||
|
||||
assert_eq!(
|
||||
runtime.sandbox_cwd(&request),
|
||||
@@ -860,36 +826,50 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
fn test_request(
|
||||
sandbox_permissions: SandboxPermissions,
|
||||
exec_approval_requirement: ExecApprovalRequirement,
|
||||
) -> UnifiedExecRequest {
|
||||
fn test_prepared_command() -> PreparedExecCommand {
|
||||
let cwd = AbsolutePathBuf::try_from(std::env::current_dir().unwrap())
|
||||
.expect("current dir is absolute");
|
||||
UnifiedExecRequest {
|
||||
command: vec!["zsh".to_string(), "-c".to_string(), "echo hi".to_string()],
|
||||
shell: ShellInvocation {
|
||||
shell: Shell {
|
||||
shell_type: ShellType::Zsh,
|
||||
shell_path: "zsh".into(),
|
||||
PreparedExecCommand {
|
||||
resolved: ResolvedCommand {
|
||||
command: vec!["zsh".to_string(), "-c".to_string(), "echo hi".to_string()],
|
||||
shell: ShellInvocation {
|
||||
shell: Shell {
|
||||
shell_type: ShellType::Zsh,
|
||||
shell_path: "zsh".into(),
|
||||
},
|
||||
use_login_shell: false,
|
||||
},
|
||||
use_login_shell: false,
|
||||
},
|
||||
hook_command: "echo hi".to_string(),
|
||||
turn_environment: test_turn_environment(cwd.clone().into()),
|
||||
cwd: cwd.into(),
|
||||
permissions: EffectiveAdditionalPermissions {
|
||||
sandbox_permissions: SandboxPermissions::UseDefault,
|
||||
additional_permissions: None,
|
||||
permissions_preapproved: false,
|
||||
},
|
||||
justification: None,
|
||||
prefix_rule: None,
|
||||
tty: false,
|
||||
yield_time_ms: 10_000,
|
||||
max_output_tokens: None,
|
||||
completion_timeout: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn test_request(prepared: &PreparedExecCommand) -> UnifiedExecRequest<'_> {
|
||||
UnifiedExecRequest {
|
||||
prepared,
|
||||
process_id: 1000,
|
||||
cwd: cwd.clone().into(),
|
||||
sandbox_cwd: cwd.clone().into(),
|
||||
turn_environment: test_turn_environment(cwd.into()),
|
||||
env: HashMap::new(),
|
||||
exec_server_env_config: None,
|
||||
shell_snapshot: None,
|
||||
explicit_env_overrides: HashMap::new(),
|
||||
network: None,
|
||||
tty: false,
|
||||
sandbox_permissions,
|
||||
additional_permissions: None,
|
||||
justification: None,
|
||||
exec_approval_requirement,
|
||||
exec_approval_requirement: ExecApprovalRequirement::Skip {
|
||||
bypass_sandbox: false,
|
||||
proposed_execpolicy_amendment: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
//! Approval rejections retain their own lifecycle, and sandbox denials remain retryable.
|
||||
//! Once started, failure event publication survives caller cancellation.
|
||||
|
||||
use super::UnifiedExecRequest;
|
||||
use crate::tools::events::ToolEmitter;
|
||||
use crate::tools::events::ToolEventCtx;
|
||||
use crate::tools::events::ToolEventFailure;
|
||||
use crate::tools::events::ToolEventStage;
|
||||
use crate::tools::handlers::unified_exec::preparation::PreparedExecCommand;
|
||||
use crate::tools::sandboxing::ToolCtx;
|
||||
use crate::tools::sandboxing::ToolError;
|
||||
use crate::unified_exec::UnifiedExecProcess;
|
||||
@@ -16,7 +16,7 @@ use tracing::Instrument;
|
||||
|
||||
pub(super) async fn with_launch_failure_events(
|
||||
result: Result<UnifiedExecProcess, ToolError>,
|
||||
req: &UnifiedExecRequest,
|
||||
req: &PreparedExecCommand,
|
||||
ctx: &ToolCtx,
|
||||
) -> Result<UnifiedExecProcess, ToolError> {
|
||||
let Err(ToolError::Rejected(message)) = &result else {
|
||||
@@ -29,17 +29,21 @@ pub(super) async fn with_launch_failure_events(
|
||||
.get_filesystem_without_reconnect();
|
||||
ctx.step_context
|
||||
.turn
|
||||
.plugin_attribution_for_executor_command(&req.command, &req.cwd, file_system.as_ref())
|
||||
.plugin_attribution_for_executor_command(
|
||||
&req.resolved.command,
|
||||
&req.cwd,
|
||||
file_system.as_ref(),
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
req.cwd.to_abs_path().ok().and_then(|cwd| {
|
||||
ctx.step_context
|
||||
.turn
|
||||
.plugin_attribution_for_command(&req.command, &cwd)
|
||||
.plugin_attribution_for_command(&req.resolved.command, &cwd)
|
||||
})
|
||||
};
|
||||
let emitter = ToolEmitter::unified_exec(
|
||||
&req.command,
|
||||
&req.resolved.command,
|
||||
req.cwd.clone(),
|
||||
ExecCommandSource::UnifiedExecStartup,
|
||||
/*process_id*/ None,
|
||||
|
||||
@@ -16,7 +16,8 @@ pub(super) struct SnapshotMetrics {
|
||||
}
|
||||
|
||||
impl SnapshotMetrics {
|
||||
pub(super) fn start(req: &UnifiedExecRequest, config: &Config) -> Option<Self> {
|
||||
pub(super) fn start(req: &UnifiedExecRequest<'_>, config: &Config) -> Option<Self> {
|
||||
let prepared = req.prepared;
|
||||
let brokered = config
|
||||
.permissions
|
||||
.network
|
||||
@@ -26,10 +27,10 @@ impl SnapshotMetrics {
|
||||
// remote legacy, and non-login invocations are outside this denominator.
|
||||
if !cfg!(unix)
|
||||
|| req.shell_snapshot.is_some()
|
||||
|| req.turn_environment.environment.is_remote()
|
||||
|| prepared.turn_environment.environment.is_remote()
|
||||
|| !config.features.enabled(Feature::ShellSnapshot)
|
||||
|| !req.shell.is_posix_login()
|
||||
|| (req.cwd != req.turn_environment.selection.cwd && !brokered)
|
||||
|| !prepared.resolved.shell.is_posix_login()
|
||||
|| (prepared.cwd != prepared.turn_environment.selection.cwd && !brokered)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
@@ -37,11 +38,11 @@ impl SnapshotMetrics {
|
||||
// Protected snapshots use a separate lazy cache, not the prewarm task.
|
||||
"protected"
|
||||
} else {
|
||||
match req.turn_environment.shell_snapshot.peek() {
|
||||
match prepared.turn_environment.shell_snapshot.peek() {
|
||||
None => "prewarm_pending",
|
||||
Some(Some(snapshot))
|
||||
if &snapshot.shell_environment_policy
|
||||
== req.turn_environment.shell_environment_policy() =>
|
||||
== prepared.turn_environment.shell_environment_policy() =>
|
||||
{
|
||||
"prewarm_ready"
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ use std::sync::Arc;
|
||||
use std::sync::Weak;
|
||||
|
||||
use codex_network_proxy::NetworkProxy;
|
||||
use codex_protocol::models::AdditionalPermissionProfile;
|
||||
use codex_utils_output_truncation::TruncationPolicy;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
use rand::Rng;
|
||||
@@ -37,12 +36,10 @@ use rand::rng;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::sandboxing::SandboxPermissions;
|
||||
use crate::session::session::Session;
|
||||
use crate::session::step_context::StepContext;
|
||||
use crate::session::turn_context::TurnContext;
|
||||
use crate::session::turn_context::TurnEnvironment;
|
||||
use crate::shell::ShellInvocation;
|
||||
use crate::tools::handlers::unified_exec::preparation::PreparedExecCommand;
|
||||
use crate::tools::network_approval::DeferredNetworkApproval;
|
||||
use codex_core_plugins::PluginMetricsSidecar;
|
||||
|
||||
@@ -108,22 +105,9 @@ impl UnifiedExecContext {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ExecCommandRequest {
|
||||
pub command: Vec<String>,
|
||||
pub shell: ShellInvocation,
|
||||
pub hook_command: String,
|
||||
pub prepared: PreparedExecCommand,
|
||||
pub process_id: i32,
|
||||
pub yield_time_ms: u64,
|
||||
pub max_output_tokens: Option<usize>,
|
||||
pub cwd: PathUri,
|
||||
pub sandbox_cwd: PathUri,
|
||||
pub turn_environment: TurnEnvironment,
|
||||
pub network: Option<NetworkProxy>,
|
||||
pub tty: bool,
|
||||
pub sandbox_permissions: SandboxPermissions,
|
||||
pub additional_permissions: Option<AdditionalPermissionProfile>,
|
||||
pub additional_permissions_preapproved: bool,
|
||||
pub justification: Option<String>,
|
||||
pub prefix_rule: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
/// The process and environment behind a terminal session ID at the time of lookup.
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::codex_thread::BackgroundTerminalInfo;
|
||||
use crate::exec::ExecCapturePolicy;
|
||||
use crate::exec::ExecExpiration;
|
||||
use crate::sandboxing::ExecRequest;
|
||||
use crate::sandboxing::SandboxPermissions;
|
||||
use crate::session::session::Session;
|
||||
use crate::session::tests::make_session_and_context;
|
||||
use crate::session::turn_context::TurnContext;
|
||||
|
||||
@@ -25,7 +25,6 @@ use crate::exec_env::inject_apply_patch_env;
|
||||
use crate::exec_env::inject_permission_profile_env;
|
||||
use crate::exec_env::inject_session_env;
|
||||
use crate::exec_env::set_tool_call_id_env_var;
|
||||
use crate::exec_policy::ExecApprovalRequest;
|
||||
use crate::guardian::GuardianReviewContext;
|
||||
use crate::plugins::metrics::finish_and_track_measurements;
|
||||
use crate::sandboxing::ExecOptions;
|
||||
@@ -71,7 +70,6 @@ use crate::unified_exec::process::UnifiedExecProcess;
|
||||
use crate::unified_exec::shell_snapshot::shell_snapshot_request;
|
||||
use crate::unified_exec::take_plugin_metrics_sidecar;
|
||||
use crate::unified_exec::trace_id;
|
||||
use crate::windows_sandbox::windows_sandbox_level_for_legacy_checks;
|
||||
use codex_core_plugins::PLUGIN_METRICS_OUTPUT_ENV_VAR;
|
||||
use codex_core_plugins::PluginCommandAttribution;
|
||||
use codex_core_plugins::PluginMetricsSidecar;
|
||||
@@ -87,7 +85,6 @@ use codex_protocol::protocol::ExecCommandSource;
|
||||
use codex_protocol::protocol::TerminalInteractionEvent;
|
||||
use codex_protocol::shell_environment::is_non_inheritable_env_var;
|
||||
use codex_sandboxing::SandboxCommand;
|
||||
use codex_shell_command::is_dangerous_command::DangerousCommandPlatform;
|
||||
use codex_tools::ToolName;
|
||||
use codex_utils_head_tail_buffer::HeadTailBuffer;
|
||||
use codex_utils_output_truncation::approx_tokens_from_byte_count;
|
||||
@@ -416,7 +413,7 @@ async fn emit_failed_initial_exec_end_if_unstored(
|
||||
Arc::clone(&context.step_context.turn),
|
||||
Arc::clone(&context.step_context.settings.model_info),
|
||||
context.call_id.clone(),
|
||||
request.command.clone(),
|
||||
request.prepared.resolved.command.clone(),
|
||||
cwd,
|
||||
Some(request.process_id.to_string()),
|
||||
plugin_attribution,
|
||||
@@ -543,10 +540,10 @@ impl UnifiedExecProcessManager {
|
||||
context: &UnifiedExecContext,
|
||||
mut completion: Option<&mut Completion<'_>>,
|
||||
) -> Result<ExecCommandToolOutput, UnifiedExecError> {
|
||||
let cwd = request.cwd.clone();
|
||||
let process = self
|
||||
.open_session_with_sandbox(&request, cwd.clone(), context)
|
||||
.await;
|
||||
let prepared = &request.prepared;
|
||||
let command = &prepared.resolved.command;
|
||||
let cwd = prepared.cwd.clone();
|
||||
let process = self.open_session_with_sandbox(&request, context).await;
|
||||
|
||||
let (attempt, mut deferred_network_approval) = match process {
|
||||
Ok((attempt, deferred_network_approval)) => (attempt, deferred_network_approval),
|
||||
@@ -582,27 +579,23 @@ impl UnifiedExecProcessManager {
|
||||
/*turn_diff_tracker*/ None,
|
||||
);
|
||||
event_ctx.model_context = Some(&model_context);
|
||||
let plugin_attribution = if request.turn_environment.environment.is_remote() {
|
||||
let file_system = request.turn_environment.environment.get_filesystem();
|
||||
let plugin_attribution = if prepared.turn_environment.environment.is_remote() {
|
||||
let file_system = prepared.turn_environment.environment.get_filesystem();
|
||||
context
|
||||
.step_context
|
||||
.turn
|
||||
.plugin_attribution_for_executor_command(
|
||||
&request.command,
|
||||
&cwd,
|
||||
file_system.as_ref(),
|
||||
)
|
||||
.plugin_attribution_for_executor_command(command, &cwd, file_system.as_ref())
|
||||
.await
|
||||
} else {
|
||||
cwd.to_abs_path().ok().and_then(|cwd| {
|
||||
context
|
||||
.step_context
|
||||
.turn
|
||||
.plugin_attribution_for_command(&request.command, &cwd)
|
||||
.plugin_attribution_for_command(command, &cwd)
|
||||
})
|
||||
};
|
||||
let emitter = ToolEmitter::unified_exec(
|
||||
&request.command,
|
||||
command,
|
||||
cwd.clone(),
|
||||
ExecCommandSource::UnifiedExecStartup,
|
||||
Some(request.process_id.to_string()),
|
||||
@@ -637,15 +630,15 @@ impl UnifiedExecProcessManager {
|
||||
self.store_process(
|
||||
Arc::clone(&process),
|
||||
context,
|
||||
&request.command,
|
||||
request.hook_command.clone(),
|
||||
command,
|
||||
prepared.hook_command.clone(),
|
||||
cwd.clone(),
|
||||
request.turn_environment.selection.environment_id.clone(),
|
||||
prepared.turn_environment.selection.environment_id.clone(),
|
||||
permissions,
|
||||
plugin_attribution.clone(),
|
||||
start,
|
||||
request.process_id,
|
||||
request.tty,
|
||||
prepared.tty,
|
||||
deferred_network_approval.clone(),
|
||||
network_denial_monitor,
|
||||
initial_exec_command_guard.metrics_sidecar.take(),
|
||||
@@ -655,7 +648,7 @@ impl UnifiedExecProcessManager {
|
||||
.await;
|
||||
}
|
||||
|
||||
let yield_time_ms = clamp_yield_time(request.yield_time_ms);
|
||||
let yield_time_ms = clamp_yield_time(prepared.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.
|
||||
@@ -837,7 +830,7 @@ impl UnifiedExecProcessManager {
|
||||
Arc::clone(&context.step_context.turn),
|
||||
Arc::clone(&context.step_context.settings.model_info),
|
||||
context.call_id.clone(),
|
||||
request.command.clone(),
|
||||
command.clone(),
|
||||
cwd.clone(),
|
||||
Some(process_id.to_string()),
|
||||
plugin_attribution.clone(),
|
||||
@@ -870,13 +863,13 @@ impl UnifiedExecProcessManager {
|
||||
.model_info
|
||||
.truncation_policy
|
||||
.into(),
|
||||
max_output_tokens: request.max_output_tokens,
|
||||
max_output_tokens: prepared.max_output_tokens,
|
||||
process_id: response_process_id,
|
||||
exit_code,
|
||||
original_token_count: Some(original_token_count),
|
||||
hook_command: Some(request.hook_command.clone()),
|
||||
tool_cwd: (!request.turn_environment.environment.is_remote())
|
||||
.then(|| request.cwd.clone()),
|
||||
hook_command: Some(prepared.hook_command.clone()),
|
||||
tool_cwd: (!prepared.turn_environment.environment.is_remote())
|
||||
.then(|| prepared.cwd.clone()),
|
||||
};
|
||||
|
||||
Ok(response)
|
||||
@@ -1489,10 +1482,10 @@ impl UnifiedExecProcessManager {
|
||||
pub(super) async fn open_session_with_sandbox(
|
||||
&self,
|
||||
request: &ExecCommandRequest,
|
||||
cwd: PathUri,
|
||||
context: &UnifiedExecContext,
|
||||
) -> Result<(UnifiedExecAttempt, Option<DeferredNetworkApproval>), UnifiedExecError> {
|
||||
let shell_environment_policy = request.turn_environment.shell_environment_policy();
|
||||
let prepared = &request.prepared;
|
||||
let shell_environment_policy = prepared.turn_environment.shell_environment_policy();
|
||||
let local_policy_env = create_env(shell_environment_policy, /*thread_id*/ None);
|
||||
let mut env = local_policy_env.clone();
|
||||
#[cfg(windows)]
|
||||
@@ -1504,7 +1497,7 @@ impl UnifiedExecProcessManager {
|
||||
set_tool_call_id_env_var(&mut env, Some(&context.call_id));
|
||||
inject_session_env(&mut env, context.session.session_id());
|
||||
inject_apply_patch_env(&mut env);
|
||||
let active_permission_profile = request.turn_environment.active_permission_profile();
|
||||
let active_permission_profile = prepared.turn_environment.active_permission_profile();
|
||||
inject_permission_profile_env(&mut env, active_permission_profile.as_ref());
|
||||
let mut env = apply_unified_exec_env(env);
|
||||
strip_output_env(&mut env);
|
||||
@@ -1514,65 +1507,20 @@ impl UnifiedExecProcessManager {
|
||||
policy: exec_env_policy_from_shell_policy(shell_environment_policy),
|
||||
local_policy_env,
|
||||
};
|
||||
let shell_snapshot = shell_snapshot_request(request, &cwd, context);
|
||||
let shell_snapshot = shell_snapshot_request(prepared, context);
|
||||
let mut orchestrator = ToolOrchestrator::new();
|
||||
let mut runtime = UnifiedExecRuntime::new(self);
|
||||
let session_shell = context.session.user_shell();
|
||||
let configured_shell = request
|
||||
.turn_environment
|
||||
.shell
|
||||
.as_ref()
|
||||
.unwrap_or(session_shell.as_ref());
|
||||
// Legacy executors omit the platform. Unknown reported platforms use the
|
||||
// more conservative Windows rules rather than silently weakening checks.
|
||||
let command_platform = match request.turn_environment.executor_platform_os.as_deref() {
|
||||
Some("linux" | "macos") => DangerousCommandPlatform::Posix,
|
||||
Some("windows") | Some(_) => DangerousCommandPlatform::Windows,
|
||||
None => DangerousCommandPlatform::host(),
|
||||
};
|
||||
let exec_approval_requirement = context
|
||||
.session
|
||||
.services
|
||||
.exec_policy
|
||||
.create_exec_approval_requirement_for_shell(
|
||||
ExecApprovalRequest {
|
||||
command: &request.command,
|
||||
approval_policy: context.step_context.settings.approval_policy(),
|
||||
permission_profile: request.turn_environment.permission_profile().clone(),
|
||||
environment_policy: request.turn_environment.config().exec_policy.as_ref(),
|
||||
windows_sandbox_level: windows_sandbox_level_for_legacy_checks(
|
||||
request.turn_environment.config().windows_sandbox_type,
|
||||
request.turn_environment.config().windows_sandbox_level,
|
||||
),
|
||||
sandbox_permissions: if request.additional_permissions_preapproved {
|
||||
crate::sandboxing::SandboxPermissions::UseDefault
|
||||
} else {
|
||||
request.sandbox_permissions
|
||||
},
|
||||
prefix_rule: request.prefix_rule.clone(),
|
||||
allow_prefix_rules: context.step_context.turn.allow_prefix_rules(),
|
||||
},
|
||||
configured_shell,
|
||||
command_platform,
|
||||
)
|
||||
let exec_approval_requirement = prepared
|
||||
.approval_requirement(&context.session, &context.step_context)
|
||||
.await;
|
||||
let req = UnifiedExecToolRequest {
|
||||
command: request.command.clone(),
|
||||
shell: request.shell.clone(),
|
||||
hook_command: request.hook_command.clone(),
|
||||
prepared,
|
||||
process_id: request.process_id,
|
||||
cwd,
|
||||
sandbox_cwd: request.sandbox_cwd.clone(),
|
||||
turn_environment: request.turn_environment.clone(),
|
||||
env,
|
||||
exec_server_env_config: Some(exec_server_env_config),
|
||||
shell_snapshot,
|
||||
explicit_env_overrides,
|
||||
network: request.network.clone(),
|
||||
tty: request.tty,
|
||||
sandbox_permissions: request.sandbox_permissions,
|
||||
additional_permissions: request.additional_permissions.clone(),
|
||||
justification: request.justification.clone(),
|
||||
exec_approval_requirement,
|
||||
};
|
||||
let tool_ctx = ToolCtx {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use super::*;
|
||||
use crate::shell::Shell;
|
||||
use crate::shell::ShellInvocation;
|
||||
use crate::shell::ShellType;
|
||||
use crate::tools::handlers::unified_exec::preparation::ExecCommandLifetime;
|
||||
use crate::tools::handlers::unified_exec::preparation::ParsedExecCommand;
|
||||
use crate::unified_exec::clamp_yield_time;
|
||||
use codex_exec_server::ExecOutputStream;
|
||||
use codex_network_proxy::ManagedNetworkSandboxContext;
|
||||
@@ -464,39 +463,23 @@ async fn failed_initial_end_for_unstored_process_uses_fallback_output() {
|
||||
tokio_util::sync::CancellationToken::new(),
|
||||
"call-unified-denied".to_string(),
|
||||
);
|
||||
let environment = turn
|
||||
.initial_environments
|
||||
.primary()
|
||||
.expect("primary environment");
|
||||
let prepared = ParsedExecCommand::parse(
|
||||
r#"{"cmd":"echo before"}"#,
|
||||
&session,
|
||||
&context.step_context,
|
||||
environment,
|
||||
)
|
||||
.expect("parse command")
|
||||
.prepare(ExecCommandLifetime::Interactive)
|
||||
.expect("prepare command");
|
||||
let request = ExecCommandRequest {
|
||||
command: vec![
|
||||
"sh".to_string(),
|
||||
"-lc".to_string(),
|
||||
"echo before".to_string(),
|
||||
],
|
||||
shell: ShellInvocation {
|
||||
shell: Shell {
|
||||
shell_type: ShellType::Sh,
|
||||
shell_path: "sh".into(),
|
||||
},
|
||||
use_login_shell: true,
|
||||
},
|
||||
hook_command: "echo before".to_string(),
|
||||
prepared,
|
||||
process_id: 123,
|
||||
yield_time_ms: 1000,
|
||||
max_output_tokens: None,
|
||||
#[allow(deprecated)]
|
||||
cwd: turn.cwd.clone().into(),
|
||||
#[allow(deprecated)]
|
||||
sandbox_cwd: turn.cwd.clone().into(),
|
||||
turn_environment: turn
|
||||
.initial_environments
|
||||
.primary()
|
||||
.cloned()
|
||||
.expect("primary environment"),
|
||||
network: None,
|
||||
tty: true,
|
||||
sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,
|
||||
additional_permissions: None,
|
||||
additional_permissions_preapproved: false,
|
||||
justification: None,
|
||||
prefix_rule: None,
|
||||
};
|
||||
|
||||
let output_buffer = Arc::new(tokio::sync::Mutex::new(OutputBuffers::default()));
|
||||
@@ -510,8 +493,7 @@ async fn failed_initial_end_for_unstored_process_uses_fallback_output() {
|
||||
Some(codex_protocol::sandbox::SandboxType::WindowsMxc),
|
||||
&context,
|
||||
&request,
|
||||
#[allow(deprecated)]
|
||||
turn.cwd.clone().into(),
|
||||
request.prepared.cwd.clone(),
|
||||
/*plugin_attribution*/ None,
|
||||
output_buffer,
|
||||
"PRE_DENIAL_MARKER".to_string(),
|
||||
|
||||
@@ -11,11 +11,9 @@ use codex_protocol::sandbox::SandboxOverride;
|
||||
use codex_sandboxing::SandboxManager;
|
||||
use codex_sandboxing::SandboxablePreference;
|
||||
use codex_tools::ToolName;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
use tokio_util::task::AbortOnDropHandle;
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::ExecCommandRequest;
|
||||
use super::UnifiedExecContext;
|
||||
use super::process_manager::apply_unified_exec_env;
|
||||
use super::process_manager::exec_env_policy_from_shell_policy;
|
||||
@@ -27,6 +25,7 @@ use crate::exec_env::inject_session_env;
|
||||
use crate::session::session::Session;
|
||||
use crate::session::step_context::StepContext;
|
||||
use crate::shell::ShellType;
|
||||
use crate::tools::handlers::unified_exec::preparation::PreparedExecCommand;
|
||||
use crate::tools::sandboxing::executor_windows_sandbox_selection;
|
||||
|
||||
impl Session {
|
||||
@@ -168,19 +167,18 @@ impl Session {
|
||||
}
|
||||
|
||||
pub(super) fn shell_snapshot_request(
|
||||
request: &ExecCommandRequest,
|
||||
cwd: &PathUri,
|
||||
request: &PreparedExecCommand,
|
||||
context: &UnifiedExecContext,
|
||||
) -> Option<ShellSnapshotRequest> {
|
||||
if !context.session.features().enabled(Feature::ShellSnapshotV2)
|
||||
|| !request.turn_environment.shell_snapshot_v2_supported
|
||||
|| request.turn_environment.selection.cwd != *cwd
|
||||
|| !request.shell.is_posix_login()
|
||||
|| request.turn_environment.selection.cwd != request.cwd
|
||||
|| !request.resolved.shell.is_posix_login()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let shell = &request.shell.shell;
|
||||
let shell = &request.resolved.shell.shell;
|
||||
Some(ShellSnapshotRequest {
|
||||
scope_id: format!(
|
||||
"{}:{}",
|
||||
|
||||
Reference in New Issue
Block a user