mirror of
https://github.com/openai/codex.git
synced 2026-10-29 19:08:43 +00:00
refactor: model unified-exec shell mode explicitly
This commit is contained in:
@@ -871,6 +871,7 @@ impl TurnContext {
|
||||
sandbox_policy: self.sandbox_policy.get(),
|
||||
windows_sandbox_level: self.windows_sandbox_level,
|
||||
})
|
||||
.with_unified_exec_shell_mode(self.tools_config.unified_exec_shell_mode.clone())
|
||||
.with_web_search_config(self.tools_config.web_search_config.clone())
|
||||
.with_allow_login_shell(self.tools_config.allow_login_shell)
|
||||
.with_agent_roles(config.agent_roles.clone());
|
||||
@@ -1252,6 +1253,9 @@ impl Session {
|
||||
session_telemetry: &SessionTelemetry,
|
||||
provider: ModelProviderInfo,
|
||||
session_configuration: &SessionConfiguration,
|
||||
user_shell: &shell::Shell,
|
||||
shell_zsh_path: Option<&PathBuf>,
|
||||
main_execve_wrapper_exe: Option<&PathBuf>,
|
||||
per_turn_config: Config,
|
||||
model_info: ModelInfo,
|
||||
models_manager: &ModelsManager,
|
||||
@@ -1283,6 +1287,11 @@ impl Session {
|
||||
sandbox_policy: session_configuration.sandbox_policy.get(),
|
||||
windows_sandbox_level: session_configuration.windows_sandbox_level,
|
||||
})
|
||||
.with_unified_exec_shell_mode_for_session(
|
||||
user_shell,
|
||||
shell_zsh_path,
|
||||
main_execve_wrapper_exe,
|
||||
)
|
||||
.with_web_search_config(per_turn_config.web_search_config.clone())
|
||||
.with_allow_login_shell(per_turn_config.permissions.allow_login_shell)
|
||||
.with_agent_roles(per_turn_config.agent_roles.clone());
|
||||
@@ -2348,6 +2357,9 @@ impl Session {
|
||||
&self.services.session_telemetry,
|
||||
session_configuration.provider.clone(),
|
||||
&session_configuration,
|
||||
self.services.user_shell.as_ref(),
|
||||
self.services.shell_zsh_path.as_ref(),
|
||||
self.services.main_execve_wrapper_exe.as_ref(),
|
||||
per_turn_config,
|
||||
model_info,
|
||||
&self.services.models_manager,
|
||||
@@ -5229,6 +5241,11 @@ async fn spawn_review_thread(
|
||||
sandbox_policy: parent_turn_context.sandbox_policy.get(),
|
||||
windows_sandbox_level: parent_turn_context.windows_sandbox_level,
|
||||
})
|
||||
.with_unified_exec_shell_mode_for_session(
|
||||
sess.services.user_shell.as_ref(),
|
||||
sess.services.shell_zsh_path.as_ref(),
|
||||
sess.services.main_execve_wrapper_exe.as_ref(),
|
||||
)
|
||||
.with_web_search_config(None)
|
||||
.with_allow_login_shell(config.permissions.allow_login_shell)
|
||||
.with_agent_roles(config.agent_roles.clone());
|
||||
|
||||
@@ -2173,6 +2173,9 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
||||
&session_telemetry,
|
||||
session_configuration.provider.clone(),
|
||||
&session_configuration,
|
||||
services.user_shell.as_ref(),
|
||||
services.shell_zsh_path.as_ref(),
|
||||
services.main_execve_wrapper_exe.as_ref(),
|
||||
per_turn_config,
|
||||
model_info,
|
||||
&models_manager,
|
||||
@@ -2843,6 +2846,9 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx(
|
||||
&session_telemetry,
|
||||
session_configuration.provider.clone(),
|
||||
&session_configuration,
|
||||
services.user_shell.as_ref(),
|
||||
services.shell_zsh_path.as_ref(),
|
||||
services.main_execve_wrapper_exe.as_ref(),
|
||||
per_turn_config,
|
||||
model_info,
|
||||
&models_manager,
|
||||
|
||||
@@ -102,6 +102,7 @@ fn shell_command_for_invocation(invocation: &ToolInvocation) -> Option<(Vec<Stri
|
||||
let command = crate::tools::handlers::unified_exec::get_command(
|
||||
¶ms,
|
||||
invocation.session.user_shell(),
|
||||
&invocation.turn.tools_config.unified_exec_shell_mode,
|
||||
invocation.turn.tools_config.allow_login_shell,
|
||||
)
|
||||
.ok()?;
|
||||
|
||||
@@ -19,6 +19,7 @@ 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::spec::UnifiedExecShellMode;
|
||||
use crate::unified_exec::ExecCommandRequest;
|
||||
use crate::unified_exec::UnifiedExecContext;
|
||||
use crate::unified_exec::UnifiedExecProcessManager;
|
||||
@@ -107,6 +108,7 @@ impl ToolHandler for UnifiedExecHandler {
|
||||
let command = match get_command(
|
||||
¶ms,
|
||||
invocation.session.user_shell(),
|
||||
&invocation.turn.tools_config.unified_exec_shell_mode,
|
||||
invocation.turn.tools_config.allow_login_shell,
|
||||
) {
|
||||
Ok(command) => command,
|
||||
@@ -154,6 +156,7 @@ impl ToolHandler for UnifiedExecHandler {
|
||||
let command = get_command(
|
||||
&args,
|
||||
session.user_shell(),
|
||||
&turn.tools_config.unified_exec_shell_mode,
|
||||
turn.tools_config.allow_login_shell,
|
||||
)
|
||||
.map_err(FunctionCallError::RespondToModel)?;
|
||||
@@ -320,15 +323,9 @@ impl ToolHandler for UnifiedExecHandler {
|
||||
pub(crate) fn get_command(
|
||||
args: &ExecCommandArgs,
|
||||
session_shell: Arc<Shell>,
|
||||
shell_mode: &UnifiedExecShellMode,
|
||||
allow_login_shell: bool,
|
||||
) -> Result<Vec<String>, String> {
|
||||
let model_shell = args.shell.as_ref().map(|shell_str| {
|
||||
let mut shell = get_shell_by_model_provided_path(&PathBuf::from(shell_str));
|
||||
shell.shell_snapshot = crate::shell::empty_shell_snapshot_receiver();
|
||||
shell
|
||||
});
|
||||
|
||||
let shell = model_shell.as_ref().unwrap_or(session_shell.as_ref());
|
||||
let use_login_shell = match args.login {
|
||||
Some(true) if !allow_login_shell => {
|
||||
return Err(
|
||||
@@ -339,7 +336,22 @@ pub(crate) fn get_command(
|
||||
None => allow_login_shell,
|
||||
};
|
||||
|
||||
Ok(shell.derive_exec_args(&args.cmd, use_login_shell))
|
||||
match shell_mode {
|
||||
UnifiedExecShellMode::Direct => {
|
||||
let model_shell = args.shell.as_ref().map(|shell_str| {
|
||||
let mut shell = get_shell_by_model_provided_path(&PathBuf::from(shell_str));
|
||||
shell.shell_snapshot = crate::shell::empty_shell_snapshot_receiver();
|
||||
shell
|
||||
});
|
||||
let shell = model_shell.as_ref().unwrap_or(session_shell.as_ref());
|
||||
Ok(shell.derive_exec_args(&args.cmd, use_login_shell))
|
||||
}
|
||||
UnifiedExecShellMode::ZshFork { shell_zsh_path, .. } => Ok(vec![
|
||||
shell_zsh_path.to_string_lossy().to_string(),
|
||||
if use_login_shell { "-lc" } else { "-c" }.to_string(),
|
||||
args.cmd.clone(),
|
||||
]),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -18,8 +18,13 @@ fn test_get_command_uses_default_shell_when_unspecified() -> anyhow::Result<()>
|
||||
|
||||
assert!(args.shell.is_none());
|
||||
|
||||
let command =
|
||||
get_command(&args, Arc::new(default_user_shell()), true).map_err(anyhow::Error::msg)?;
|
||||
let command = get_command(
|
||||
&args,
|
||||
Arc::new(default_user_shell()),
|
||||
&UnifiedExecShellMode::Direct,
|
||||
true,
|
||||
)
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
|
||||
assert_eq!(command.len(), 3);
|
||||
assert_eq!(command[2], "echo hello");
|
||||
@@ -34,8 +39,13 @@ fn test_get_command_respects_explicit_bash_shell() -> anyhow::Result<()> {
|
||||
|
||||
assert_eq!(args.shell.as_deref(), Some("/bin/bash"));
|
||||
|
||||
let command =
|
||||
get_command(&args, Arc::new(default_user_shell()), true).map_err(anyhow::Error::msg)?;
|
||||
let command = get_command(
|
||||
&args,
|
||||
Arc::new(default_user_shell()),
|
||||
&UnifiedExecShellMode::Direct,
|
||||
true,
|
||||
)
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
|
||||
assert_eq!(command.last(), Some(&"echo hello".to_string()));
|
||||
if command
|
||||
@@ -55,8 +65,13 @@ fn test_get_command_respects_explicit_powershell_shell() -> anyhow::Result<()> {
|
||||
|
||||
assert_eq!(args.shell.as_deref(), Some("powershell"));
|
||||
|
||||
let command =
|
||||
get_command(&args, Arc::new(default_user_shell()), true).map_err(anyhow::Error::msg)?;
|
||||
let command = get_command(
|
||||
&args,
|
||||
Arc::new(default_user_shell()),
|
||||
&UnifiedExecShellMode::Direct,
|
||||
true,
|
||||
)
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
|
||||
assert_eq!(command[2], "echo hello");
|
||||
Ok(())
|
||||
@@ -70,8 +85,13 @@ fn test_get_command_respects_explicit_cmd_shell() -> anyhow::Result<()> {
|
||||
|
||||
assert_eq!(args.shell.as_deref(), Some("cmd"));
|
||||
|
||||
let command =
|
||||
get_command(&args, Arc::new(default_user_shell()), true).map_err(anyhow::Error::msg)?;
|
||||
let command = get_command(
|
||||
&args,
|
||||
Arc::new(default_user_shell()),
|
||||
&UnifiedExecShellMode::Direct,
|
||||
true,
|
||||
)
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
|
||||
assert_eq!(command[2], "echo hello");
|
||||
Ok(())
|
||||
@@ -82,8 +102,13 @@ fn test_get_command_rejects_explicit_login_when_disallowed() -> anyhow::Result<(
|
||||
let json = r#"{"cmd": "echo hello", "login": true}"#;
|
||||
|
||||
let args: ExecCommandArgs = parse_arguments(json)?;
|
||||
let err = get_command(&args, Arc::new(default_user_shell()), false)
|
||||
.expect_err("explicit login should be rejected");
|
||||
let err = get_command(
|
||||
&args,
|
||||
Arc::new(default_user_shell()),
|
||||
&UnifiedExecShellMode::Direct,
|
||||
false,
|
||||
)
|
||||
.expect_err("explicit login should be rejected");
|
||||
|
||||
assert!(
|
||||
err.contains("login shell is disabled by config"),
|
||||
@@ -92,6 +117,38 @@ fn test_get_command_rejects_explicit_login_when_disallowed() -> anyhow::Result<(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_command_ignores_explicit_shell_in_zsh_fork_mode() -> anyhow::Result<()> {
|
||||
let json = r#"{"cmd": "echo hello", "shell": "/bin/bash"}"#;
|
||||
let args: ExecCommandArgs = parse_arguments(json)?;
|
||||
let shell_zsh_path = AbsolutePathBuf::from_absolute_path(if cfg!(windows) {
|
||||
r"C:\opt\codex\zsh"
|
||||
} else {
|
||||
"/opt/codex/zsh"
|
||||
})?;
|
||||
let shell_mode = UnifiedExecShellMode::ZshFork {
|
||||
shell_zsh_path: shell_zsh_path.clone(),
|
||||
main_execve_wrapper_exe: AbsolutePathBuf::from_absolute_path(if cfg!(windows) {
|
||||
r"C:\opt\codex\codex-execve-wrapper"
|
||||
} else {
|
||||
"/opt/codex/codex-execve-wrapper"
|
||||
})?,
|
||||
};
|
||||
|
||||
let command = get_command(&args, Arc::new(default_user_shell()), &shell_mode, true)
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
|
||||
assert_eq!(
|
||||
command,
|
||||
vec![
|
||||
shell_zsh_path.to_string_lossy().to_string(),
|
||||
"-lc".to_string(),
|
||||
"echo hello".to_string()
|
||||
]
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exec_command_args_resolve_relative_additional_permissions_against_workdir() -> anyhow::Result<()>
|
||||
{
|
||||
|
||||
@@ -226,20 +226,9 @@ pub(crate) async fn prepare_unified_exec_zsh_fork(
|
||||
_attempt: &SandboxAttempt<'_>,
|
||||
ctx: &ToolCtx,
|
||||
exec_request: ExecRequest,
|
||||
shell_zsh_path: &std::path::Path,
|
||||
main_execve_wrapper_exe: &std::path::Path,
|
||||
) -> Result<Option<PreparedUnifiedExecZshFork>, ToolError> {
|
||||
let Some(shell_zsh_path) = ctx.session.services.shell_zsh_path.as_ref() else {
|
||||
tracing::warn!("ZshFork backend specified, but shell_zsh_path is not configured.");
|
||||
return Ok(None);
|
||||
};
|
||||
if !ctx.session.features().enabled(Feature::ShellZshFork) {
|
||||
tracing::warn!("ZshFork backend specified, but ShellZshFork feature is not enabled.");
|
||||
return Ok(None);
|
||||
}
|
||||
if !matches!(ctx.session.user_shell().shell_type, ShellType::Zsh) {
|
||||
tracing::warn!("ZshFork backend specified, but user shell is not Zsh.");
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let parsed = match extract_shell_script(&exec_request.command) {
|
||||
Ok(parsed) => parsed,
|
||||
Err(err) => {
|
||||
@@ -282,16 +271,6 @@ pub(crate) async fn prepare_unified_exec_zsh_fork(
|
||||
codex_linux_sandbox_exe: ctx.turn.codex_linux_sandbox_exe.clone(),
|
||||
use_legacy_landlock: ctx.turn.features.use_legacy_landlock(),
|
||||
};
|
||||
let main_execve_wrapper_exe = ctx
|
||||
.session
|
||||
.services
|
||||
.main_execve_wrapper_exe
|
||||
.clone()
|
||||
.ok_or_else(|| {
|
||||
ToolError::Rejected(
|
||||
"zsh fork feature enabled, but execve wrapper is not configured".to_string(),
|
||||
)
|
||||
})?;
|
||||
let escalation_policy = CoreShellActionProvider {
|
||||
policy: Arc::clone(&exec_policy),
|
||||
session: Arc::clone(&ctx.session),
|
||||
@@ -312,8 +291,8 @@ pub(crate) async fn prepare_unified_exec_zsh_fork(
|
||||
};
|
||||
|
||||
let escalate_server = EscalateServer::new(
|
||||
shell_zsh_path.clone(),
|
||||
main_execve_wrapper_exe,
|
||||
shell_zsh_path.to_path_buf(),
|
||||
main_execve_wrapper_exe.to_path_buf(),
|
||||
escalation_policy,
|
||||
);
|
||||
let escalation_session = escalate_server
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::tools::sandboxing::SandboxAttempt;
|
||||
use crate::tools::sandboxing::ToolCtx;
|
||||
use crate::tools::sandboxing::ToolError;
|
||||
use crate::unified_exec::SpawnLifecycleHandle;
|
||||
use std::path::Path;
|
||||
|
||||
pub(crate) struct PreparedUnifiedExecSpawn {
|
||||
pub(crate) exec_request: ExecRequest,
|
||||
@@ -37,8 +38,18 @@ pub(crate) async fn maybe_prepare_unified_exec(
|
||||
attempt: &SandboxAttempt<'_>,
|
||||
ctx: &ToolCtx,
|
||||
exec_request: ExecRequest,
|
||||
shell_zsh_path: &Path,
|
||||
main_execve_wrapper_exe: &Path,
|
||||
) -> Result<Option<PreparedUnifiedExecSpawn>, ToolError> {
|
||||
imp::maybe_prepare_unified_exec(req, attempt, ctx, exec_request).await
|
||||
imp::maybe_prepare_unified_exec(
|
||||
req,
|
||||
attempt,
|
||||
ctx,
|
||||
exec_request,
|
||||
shell_zsh_path,
|
||||
main_execve_wrapper_exe,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
@@ -83,9 +94,18 @@ mod imp {
|
||||
attempt: &SandboxAttempt<'_>,
|
||||
ctx: &ToolCtx,
|
||||
exec_request: ExecRequest,
|
||||
shell_zsh_path: &Path,
|
||||
main_execve_wrapper_exe: &Path,
|
||||
) -> Result<Option<PreparedUnifiedExecSpawn>, ToolError> {
|
||||
let Some(prepared) =
|
||||
unix_escalation::prepare_unified_exec_zsh_fork(req, attempt, ctx, exec_request).await?
|
||||
let Some(prepared) = unix_escalation::prepare_unified_exec_zsh_fork(
|
||||
req,
|
||||
attempt,
|
||||
ctx,
|
||||
exec_request,
|
||||
shell_zsh_path,
|
||||
main_execve_wrapper_exe,
|
||||
)
|
||||
.await?
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
@@ -118,8 +138,17 @@ mod imp {
|
||||
attempt: &SandboxAttempt<'_>,
|
||||
ctx: &ToolCtx,
|
||||
exec_request: ExecRequest,
|
||||
shell_zsh_path: &Path,
|
||||
main_execve_wrapper_exe: &Path,
|
||||
) -> Result<Option<PreparedUnifiedExecSpawn>, ToolError> {
|
||||
let _ = (req, attempt, ctx, exec_request);
|
||||
let _ = (
|
||||
req,
|
||||
attempt,
|
||||
ctx,
|
||||
exec_request,
|
||||
shell_zsh_path,
|
||||
main_execve_wrapper_exe,
|
||||
);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ use crate::tools::sandboxing::ToolError;
|
||||
use crate::tools::sandboxing::ToolRuntime;
|
||||
use crate::tools::sandboxing::sandbox_override_for_first_attempt;
|
||||
use crate::tools::sandboxing::with_cached_approval;
|
||||
use crate::tools::spec::UnifiedExecBackendConfig;
|
||||
use crate::tools::spec::UnifiedExecShellMode;
|
||||
use crate::unified_exec::NoopSpawnLifecycle;
|
||||
use crate::unified_exec::UnifiedExecError;
|
||||
use crate::unified_exec::UnifiedExecProcess;
|
||||
@@ -71,12 +71,15 @@ pub struct UnifiedExecApprovalKey {
|
||||
|
||||
pub struct UnifiedExecRuntime<'a> {
|
||||
manager: &'a UnifiedExecProcessManager,
|
||||
backend: UnifiedExecBackendConfig,
|
||||
shell_mode: UnifiedExecShellMode,
|
||||
}
|
||||
|
||||
impl<'a> UnifiedExecRuntime<'a> {
|
||||
pub fn new(manager: &'a UnifiedExecProcessManager, backend: UnifiedExecBackendConfig) -> Self {
|
||||
Self { manager, backend }
|
||||
pub fn new(manager: &'a UnifiedExecProcessManager, shell_mode: UnifiedExecShellMode) -> Self {
|
||||
Self {
|
||||
manager,
|
||||
shell_mode,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,7 +211,11 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecProcess> for UnifiedExecRunt
|
||||
if let Some(network) = req.network.as_ref() {
|
||||
network.apply_to_env(&mut env);
|
||||
}
|
||||
if self.backend == UnifiedExecBackendConfig::ZshFork {
|
||||
if let UnifiedExecShellMode::ZshFork {
|
||||
shell_zsh_path,
|
||||
main_execve_wrapper_exe,
|
||||
} = &self.shell_mode
|
||||
{
|
||||
let spec = build_command_spec(
|
||||
&command,
|
||||
&req.cwd,
|
||||
@@ -222,7 +229,16 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecProcess> for UnifiedExecRunt
|
||||
let exec_env = attempt
|
||||
.env_for(spec, req.network.as_ref())
|
||||
.map_err(|err| ToolError::Codex(err.into()))?;
|
||||
match zsh_fork_backend::maybe_prepare_unified_exec(req, attempt, ctx, exec_env).await? {
|
||||
match zsh_fork_backend::maybe_prepare_unified_exec(
|
||||
req,
|
||||
attempt,
|
||||
ctx,
|
||||
exec_env,
|
||||
shell_zsh_path.as_path(),
|
||||
main_execve_wrapper_exe.as_path(),
|
||||
)
|
||||
.await?
|
||||
{
|
||||
Some(prepared) => {
|
||||
return self
|
||||
.manager
|
||||
|
||||
@@ -8,6 +8,8 @@ use crate::features::Features;
|
||||
use crate::mcp_connection_manager::ToolInfo;
|
||||
use crate::models_manager::collaboration_mode_presets::CollaborationModesConfig;
|
||||
use crate::original_image_detail::can_request_original_image_detail;
|
||||
use crate::shell::Shell;
|
||||
use crate::shell::ShellType;
|
||||
use crate::tools::code_mode::PUBLIC_TOOL_NAME;
|
||||
use crate::tools::code_mode::WAIT_TOOL_NAME;
|
||||
use crate::tools::code_mode::is_code_mode_nested_tool;
|
||||
@@ -46,12 +48,14 @@ use codex_protocol::openai_models::WebSearchToolType;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::protocol::SubAgentSource;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use serde_json::Value as JsonValue;
|
||||
use serde_json::json;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
const TOOL_SEARCH_DESCRIPTION_TEMPLATE: &str =
|
||||
include_str!("../../templates/search_tool/tool_description.md");
|
||||
@@ -203,10 +207,40 @@ pub enum ShellCommandBackendConfig {
|
||||
ZshFork,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub enum UnifiedExecBackendConfig {
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum UnifiedExecShellMode {
|
||||
Direct,
|
||||
ZshFork,
|
||||
ZshFork {
|
||||
shell_zsh_path: AbsolutePathBuf,
|
||||
main_execve_wrapper_exe: AbsolutePathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
impl UnifiedExecShellMode {
|
||||
pub fn for_session(
|
||||
shell_command_backend: ShellCommandBackendConfig,
|
||||
user_shell: &Shell,
|
||||
shell_zsh_path: Option<&PathBuf>,
|
||||
main_execve_wrapper_exe: Option<&PathBuf>,
|
||||
) -> Self {
|
||||
if cfg!(unix)
|
||||
&& shell_command_backend == ShellCommandBackendConfig::ZshFork
|
||||
&& matches!(user_shell.shell_type, ShellType::Zsh)
|
||||
&& let (Some(shell_zsh_path), Some(main_execve_wrapper_exe)) =
|
||||
(shell_zsh_path, main_execve_wrapper_exe)
|
||||
&& let (Ok(shell_zsh_path), Ok(main_execve_wrapper_exe)) = (
|
||||
AbsolutePathBuf::try_from(shell_zsh_path.as_path()),
|
||||
AbsolutePathBuf::try_from(main_execve_wrapper_exe.as_path()),
|
||||
)
|
||||
{
|
||||
Self::ZshFork {
|
||||
shell_zsh_path,
|
||||
main_execve_wrapper_exe,
|
||||
}
|
||||
} else {
|
||||
Self::Direct
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -214,7 +248,7 @@ pub(crate) struct ToolsConfig {
|
||||
pub available_models: Vec<ModelPreset>,
|
||||
pub shell_type: ConfigShellToolType,
|
||||
shell_command_backend: ShellCommandBackendConfig,
|
||||
pub unified_exec_backend: UnifiedExecBackendConfig,
|
||||
pub unified_exec_shell_mode: UnifiedExecShellMode,
|
||||
pub allow_login_shell: bool,
|
||||
pub apply_patch_tool_type: Option<ApplyPatchToolType>,
|
||||
pub web_search_mode: Option<WebSearchMode>,
|
||||
@@ -300,13 +334,6 @@ impl ToolsConfig {
|
||||
} else {
|
||||
ShellCommandBackendConfig::Classic
|
||||
};
|
||||
let unified_exec_backend =
|
||||
if features.enabled(Feature::ShellTool) && features.enabled(Feature::ShellZshFork) {
|
||||
UnifiedExecBackendConfig::ZshFork
|
||||
} else {
|
||||
UnifiedExecBackendConfig::Direct
|
||||
};
|
||||
|
||||
let unified_exec_allowed = unified_exec_allowed_in_environment(
|
||||
cfg!(target_os = "windows"),
|
||||
sandbox_policy,
|
||||
@@ -353,7 +380,7 @@ impl ToolsConfig {
|
||||
available_models: available_models_ref.to_vec(),
|
||||
shell_type,
|
||||
shell_command_backend,
|
||||
unified_exec_backend,
|
||||
unified_exec_shell_mode: UnifiedExecShellMode::Direct,
|
||||
allow_login_shell: true,
|
||||
apply_patch_tool_type,
|
||||
web_search_mode: *web_search_mode,
|
||||
@@ -390,6 +417,29 @@ impl ToolsConfig {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_unified_exec_shell_mode(
|
||||
mut self,
|
||||
unified_exec_shell_mode: UnifiedExecShellMode,
|
||||
) -> Self {
|
||||
self.unified_exec_shell_mode = unified_exec_shell_mode;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_unified_exec_shell_mode_for_session(
|
||||
mut self,
|
||||
user_shell: &Shell,
|
||||
shell_zsh_path: Option<&PathBuf>,
|
||||
main_execve_wrapper_exe: Option<&PathBuf>,
|
||||
) -> Self {
|
||||
self.unified_exec_shell_mode = UnifiedExecShellMode::for_session(
|
||||
self.shell_command_backend,
|
||||
user_shell,
|
||||
shell_zsh_path,
|
||||
main_execve_wrapper_exe,
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_web_search_config(mut self, web_search_config: Option<WebSearchConfig>) -> Self {
|
||||
self.web_search_config = web_search_config;
|
||||
self
|
||||
|
||||
@@ -2,6 +2,8 @@ use crate::client_common::tools::FreeformTool;
|
||||
use crate::config::test_config;
|
||||
use crate::models_manager::manager::ModelsManager;
|
||||
use crate::models_manager::model_info::with_config_overrides;
|
||||
use crate::shell::Shell;
|
||||
use crate::shell::ShellType;
|
||||
use crate::tools::ToolRouter;
|
||||
use crate::tools::registry::ConfiguredToolSpec;
|
||||
use crate::tools::router::ToolRouterParams;
|
||||
@@ -9,7 +11,9 @@ use codex_app_server_protocol::AppInfo;
|
||||
use codex_protocol::openai_models::InputModality;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
use codex_protocol::openai_models::ModelsResponse;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -1381,6 +1385,11 @@ fn shell_zsh_fork_prefers_shell_command_over_unified_exec() {
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let user_shell = Shell {
|
||||
shell_type: ShellType::Zsh,
|
||||
shell_path: PathBuf::from("/bin/zsh"),
|
||||
shell_snapshot: crate::shell::empty_shell_snapshot_receiver(),
|
||||
};
|
||||
|
||||
assert_eq!(tools_config.shell_type, ConfigShellToolType::ShellCommand);
|
||||
assert_eq!(
|
||||
@@ -1388,8 +1397,36 @@ fn shell_zsh_fork_prefers_shell_command_over_unified_exec() {
|
||||
ShellCommandBackendConfig::ZshFork
|
||||
);
|
||||
assert_eq!(
|
||||
tools_config.unified_exec_backend,
|
||||
UnifiedExecBackendConfig::ZshFork
|
||||
tools_config.unified_exec_shell_mode,
|
||||
UnifiedExecShellMode::Direct
|
||||
);
|
||||
assert_eq!(
|
||||
tools_config
|
||||
.with_unified_exec_shell_mode_for_session(
|
||||
&user_shell,
|
||||
Some(&PathBuf::from(if cfg!(windows) {
|
||||
r"C:\opt\codex\zsh"
|
||||
} else {
|
||||
"/opt/codex/zsh"
|
||||
})),
|
||||
Some(&PathBuf::from(if cfg!(windows) {
|
||||
r"C:\opt\codex\codex-execve-wrapper"
|
||||
} else {
|
||||
"/opt/codex/codex-execve-wrapper"
|
||||
})),
|
||||
)
|
||||
.unified_exec_shell_mode,
|
||||
if cfg!(unix) {
|
||||
UnifiedExecShellMode::ZshFork {
|
||||
shell_zsh_path: AbsolutePathBuf::from_absolute_path("/opt/codex/zsh").unwrap(),
|
||||
main_execve_wrapper_exe: AbsolutePathBuf::from_absolute_path(
|
||||
"/opt/codex/codex-execve-wrapper",
|
||||
)
|
||||
.unwrap(),
|
||||
}
|
||||
} else {
|
||||
UnifiedExecShellMode::Direct
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -578,8 +578,10 @@ impl UnifiedExecProcessManager {
|
||||
Some(context.session.conversation_id),
|
||||
));
|
||||
let mut orchestrator = ToolOrchestrator::new();
|
||||
let mut runtime =
|
||||
UnifiedExecRuntime::new(self, context.turn.tools_config.unified_exec_backend);
|
||||
let mut runtime = UnifiedExecRuntime::new(
|
||||
self,
|
||||
context.turn.tools_config.unified_exec_shell_mode.clone(),
|
||||
);
|
||||
let exec_approval_requirement = context
|
||||
.session
|
||||
.services
|
||||
|
||||
Reference in New Issue
Block a user